Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

feat(db): store user agent and last-access time in sessionTokens #65

Merged
merged 1 commit into from Aug 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 28 additions & 3 deletions fxa-auth-db-server/docs/DB_API.md
Expand Up @@ -15,6 +15,7 @@ There are a number of methods that a DB storage backend should implement:
* .accountExists(emailBuffer)
* Session Tokens
* .createSessionToken(tokenId, sessionToken)
* .updateSessionToken(tokenId, sessionToken)
* .sessionToken(id)
* .deleteSessionToken(tokenId)
* Key Fetch Tokens
Expand Down Expand Up @@ -249,7 +250,7 @@ Parameters.

Each token takes the following fields for it's create method respectively:

* sessionToken : data, uid, createdAt
* sessionToken : data, uid, createdAt, uaBrowser, uaBrowserVersion, uaOS, uaOSVersion, uaDeviceType
* keyFetchToken : authKey, uid, keyBundle, createdAt
* passwordChangeToken : data, uid, createdAt
* passwordForgotToken : data, uid, passCode, createdAt, triesxb
Expand Down Expand Up @@ -288,7 +289,9 @@ Returns:
Each token returns different fields. These fields are represented as `t.*` for a field
from the token and `a.*` for a field from the corresponding account.

* sessionToken : t.tokenData, t.uid, t.createdAt, a.emailVerified, a.email, a.emailCode, a.verifierSetAt
* sessionToken : t.tokenData, t.uid, t.createdAt, t.uaBrowser, t.uaBrowserVersion,
t.uaOS, t.uaOSVersion, t.uaDeviceType, t.lastAccessTime,
a.emailVerified, a.email, a.emailCode, a.verifierSetAt
* keyFetchToken : t.authKey, t.uid, t.keyBundle, t.createdAt, a.emailVerified, a.verifierSetAt
* passwordChangeToken : t.tokenData, t.uid, t.createdAt, a.verifierSetAt
* passwordForgotToken : t.tokenData, t.uid, t.createdAt, t.passCode, t.tries, a.email, a.verifierSetAt
Expand All @@ -302,6 +305,28 @@ from the token and `a.*` for a field from the corresponding account.

Will delete the token of the correct type designated by the given `tokenId`.

## .updateSessionToken(tokenId, token) ##

An extra function for `sessionTokens`. Just updates the `uaBrowser`, `uaBrowserVersion`, `uaOS`, `uaOSVersion`, `uaDeviceType` and `lastAccessTime` fields of the token.

Parameters.

* tokenId : (Buffer32) the unique id for this token
* token : (Object) -
* uaBrowser : (string)
* uaBrowserVersion : (string)
* uaOS : (string)
* uaOSVersion : (string)
* uaDeviceType : (string)
* lastAccessTime : (number)

Returns:

* resolves with:
* an object `{}` (whether a row was updated or not, ie. even if `tokenId` does not exist.)
* rejects with:
* any error from the underlying storage system (wrapped in `error.wrap()`)

## .updatePasswordForgotToken(tokenId, token) ##

An extra function for `passwordForgotTokens`. Just updates the `tries` field of the token.
Expand All @@ -317,7 +342,7 @@ Returns:
* resolves with:
* an object `{}` (whether a row was updated or not, ie. even if `tokenId` does not exist.)
* rejects with:
* any error from the underlying storage system (wrapped in `error.wrap()`
* any error from the underlying storage system (wrapped in `error.wrap()`)

## .forgotPasswordVerified(tokenId, accountResetToken) ##

Expand Down
59 changes: 58 additions & 1 deletion fxa-auth-db-server/docs/Server_API.md
Expand Up @@ -61,6 +61,7 @@ The following datatypes are used throughout this document:
* sessionToken : `GET /sessionToken/:id`
* deleteSessionToken : `DEL /sessionToken/:id`
* createSessionToken : `PUT /sessionToken/:id`
* updateSessionToken : `POST /sessionToken/:id/update`
* Account Reset Tokens:
* accountResetToken : `GET /accountResetToken/:id`
* deleteAccountResetToken : `DEL /accountResetToken/:id`
Expand Down Expand Up @@ -482,6 +483,62 @@ Content-Length: 2
* Content-Type : 'application/json'
* Body : {"code":"InternalError","message":"...<message related to the error>..."}

## updateSessionToken : `POST /sessionToken/<tokenId>/update`

This updates the user agent and last-access time for a particular token.

### Example

```
curl \
-v \
-X POST \
-H "Content-Type: application/json" \
-d '{
"uaBrowser" : "Firefox",
"uaBrowserVersion" : "42",
"usOS" : "Android",
"usOSVersion" : "5.1",
"uaDeviceType": "mobile",
"lastAccessTime": 1437992394186
}' \
http://localhost:8000/sessionToken/522c251a1623e1f1db1f4fe68b9594d26772d6e77e04cb68e110c58600f97a77/update
```

### Request

* Method : POST
* Path : `/sessionToken/<tokenId>/update`
* tokenId : hex256
* Params:
* uaBrowser : string
* uaBrowserVersion : string
* uaOS : string
* uaOSVersion : string
* uaDeviceType : string
* lastAccessTime : epoch

### Response

```
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2

{}
```

* Status Code : 200 OK
* Content-Type : 'application/json'
* Body : {}
* Status Code : 404 Not Found
* Conditions: if this session `tokenId` doesn't exist
* Content-Type : 'application/json'
* Body : `{"message":"Not Found"}`
* Status Code : 500 Internal Server Error
* Conditions: if something goes wrong on the server
* Content-Type : 'application/json'
* Body : `{"code":"InternalError","message":"...<message related to the error>..."}`

## accountDevices : `GET /account/<uid>/devices`

Expand Down Expand Up @@ -1199,7 +1256,7 @@ curl \

### Request

* Method : PUT
* Method : POST
* Path : `/passwordForgotToken/<tokenId>/update`
* tokenId : hex256
* Params:
Expand Down
1 change: 1 addition & 0 deletions fxa-auth-db-server/index.js
Expand Up @@ -83,6 +83,7 @@ function createServer(db) {
api.get('/sessionToken/:id', reply(db.sessionToken))
api.del('/sessionToken/:id', reply(db.deleteSessionToken))
api.put('/sessionToken/:id', reply(db.createSessionToken))
api.post('/sessionToken/:id/update', reply(db.updateSessionToken))

api.get('/keyFetchToken/:id', reply(db.keyFetchToken))
api.del('/keyFetchToken/:id', reply(db.deleteKeyFetchToken))
Expand Down
46 changes: 43 additions & 3 deletions fxa-auth-db-server/test/backend/db_tests.js
Expand Up @@ -41,7 +41,12 @@ var SESSION_TOKEN_ID = hex32()
var SESSION_TOKEN = {
data : hex32(),
uid : ACCOUNT.uid,
createdAt: now + 1
createdAt : now + 1,
uaBrowser : 'mock browser',
uaBrowserVersion : 'mock browser version',
uaOS : 'mock OS',
uaOSVersion : 'mock OS version',
uaDeviceType : 'mock device type'
}

var KEY_FETCH_TOKEN_ID = hex32()
Expand Down Expand Up @@ -197,7 +202,7 @@ module.exports = function(config, DB) {
test(
'session token handling',
function (t) {
t.plan(10)
t.plan(30)
return db.createSessionToken(SESSION_TOKEN_ID, SESSION_TOKEN)
.then(function(result) {
t.deepEqual(result, {}, 'Returned an empty object on session token creation')
Expand All @@ -208,6 +213,41 @@ module.exports = function(config, DB) {
t.deepEqual(token.tokenData, SESSION_TOKEN.data, 'token data matches')
t.deepEqual(token.uid, ACCOUNT.uid, 'token belongs to this account')
t.equal(token.createdAt, SESSION_TOKEN.createdAt, 'createdAt is correct')
t.equal(token.uaBrowser, SESSION_TOKEN.uaBrowser, 'uaBrowser is correct')
t.equal(token.uaBrowserVersion, SESSION_TOKEN.uaBrowserVersion, 'uaBrowserVersion is correct')
t.equal(token.uaOS, SESSION_TOKEN.uaOS, 'uaOS is correct')
t.equal(token.uaOSVersion, SESSION_TOKEN.uaOSVersion, 'uaOSVersion is correct')
t.equal(token.uaDeviceType, SESSION_TOKEN.uaDeviceType, 'uaDeviceType is correct')
t.equal(token.lastAccessTime, SESSION_TOKEN.createdAt, 'lastAccessTime was set')
t.equal(!!token.emailVerified, ACCOUNT.emailVerified, 'token emailVerified is same as account emailVerified')
t.equal(token.email, ACCOUNT.email, 'token email same as account email')
t.deepEqual(token.emailCode, ACCOUNT.emailCode, 'token emailCode same as account emailCode')
t.equal(token.verifierSetAt, ACCOUNT.verifierSetAt, 'verifierSetAt is correct')
})
.then(function() {
return db.updateSessionToken(SESSION_TOKEN_ID, {
uaBrowser: 'foo',
uaBrowserVersion: '1',
uaOS: 'bar',
uaOSVersion: '2',
uaDeviceType: 'baz',
lastAccessTime: 42
})
})
.then(function(result) {
t.deepEqual(result, {}, 'Returned an empty object on session token update')
return db.sessionToken(SESSION_TOKEN_ID)
})
.then(function(token) {
t.deepEqual(token.tokenData, SESSION_TOKEN.data, 'token data matches')
t.deepEqual(token.uid, ACCOUNT.uid, 'token belongs to this account')
t.equal(token.createdAt, SESSION_TOKEN.createdAt, 'createdAt is correct')
t.equal(token.uaBrowser, 'foo', 'uaBrowser is correct')
t.equal(token.uaBrowserVersion, '1', 'uaBrowserVersion is correct')
t.equal(token.uaOS, 'bar', 'uaOS is correct')
t.equal(token.uaOSVersion, '2', 'uaOSVersion is correct')
t.equal(token.uaDeviceType, 'baz', 'uaDeviceType is correct')
t.equal(token.lastAccessTime, 42, 'lastAccessTime is correct')
t.equal(!!token.emailVerified, ACCOUNT.emailVerified, 'token emailVerified is same as account emailVerified')
t.equal(token.email, ACCOUNT.email, 'token email same as account email')
t.deepEqual(token.emailCode, ACCOUNT.emailCode, 'token emailCode same as account emailCode')
Expand Down Expand Up @@ -749,7 +789,7 @@ module.exports = function(config, DB) {
var anotherSessionToken = {
data : hex32(),
uid : ACCOUNT.uid,
createdAt: Date.now(),
createdAt: Date.now()
}
db.createSessionToken(SESSION_TOKEN_ID, SESSION_TOKEN)
.then(function(sessionToken) {
Expand Down
40 changes: 37 additions & 3 deletions fxa-auth-db-server/test/backend/remote.js
Expand Up @@ -180,7 +180,7 @@ module.exports = function(cfg, server) {
test(
'session token handling',
function (t) {
t.plan(14)
t.plan(31)
var user = fake.newUserDataHex()
client.putThen('/account/' + user.accountId, user.account)
.then(function() {
Expand All @@ -202,13 +202,47 @@ module.exports = function(cfg, server) {
// tokenId is not returned from db.sessionToken()
t.deepEqual(token.tokenData, user.sessionToken.data, 'token data matches')
t.deepEqual(token.uid, user.accountId, 'token belongs to this account')
t.ok(token.createdAt, 'Got a createdAt')
t.equal(token.createdAt, user.sessionToken.createdAt, 'createdAt matches')
t.equal(token.uaBrowser, user.sessionToken.uaBrowser, 'uaBrowser matches')
t.equal(token.uaBrowserVersion, user.sessionToken.uaBrowserVersion, 'uaBrowserVersion matches')
t.equal(token.uaOS, user.sessionToken.uaOS, 'uaOS matches')
t.equal(token.uaOSVersion, user.sessionToken.uaOSVersion, 'uaOSVersion matches')
t.equal(token.uaDeviceType, user.sessionToken.uaDeviceType, 'uaDeviceType matches')
t.equal(token.lastAccessTime, token.createdAt, 'lastAccessTime was set')
t.equal(!!token.emailVerified, user.account.emailVerified, 'emailVerified same as account emailVerified')
t.equal(token.email, user.account.email, 'token.email same as account email')
t.deepEqual(token.emailCode, user.account.emailCode, 'token emailCode same as account emailCode')
t.ok(token.verifierSetAt, 'verifierSetAt is set to a truthy value')

// now delete it
// update the session token
return client.postThen('/sessionToken/' + user.sessionTokenId + '/update', {
uaBrowser: 'different browser',
uaBrowserVersion: 'different browser version',
uaOS: 'different OS',
uaOSVersion: 'different OS version',
uaDeviceType: 'different device type',
lastAccessTime: 42
})
})
.then(function(r) {
respOk(t, r)
return client.getThen('/sessionToken/' + user.sessionTokenId)
})
.then(function(r) {
var token = r.obj

// tokenId is not returned from db.sessionToken()
t.deepEqual(token.tokenData, user.sessionToken.data, 'token data matches')
t.deepEqual(token.uid, user.accountId, 'token belongs to this account')
t.equal(token.createdAt, user.sessionToken.createdAt, 'createdAt was not updated')
t.equal(token.uaBrowser, 'different browser', 'uaBrowser was updated')
t.equal(token.uaBrowserVersion, 'different browser version', 'uaBrowserVersion was updated')
t.equal(token.uaOS, 'different OS', 'uaOS was updated')
t.equal(token.uaOSVersion, 'different OS version', 'uaOSVersion was updated')
t.equal(token.uaDeviceType, 'different device type', 'uaDeviceType was updated')
t.equal(token.lastAccessTime, 42, 'lastAccessTime was updated')

// delete the session token
return client.delThen('/sessionToken/' + user.sessionTokenId)
})
.then(function(r) {
Expand Down
7 changes: 6 additions & 1 deletion fxa-auth-db-server/test/fake.js
Expand Up @@ -43,7 +43,12 @@ module.exports.newUserDataHex = function() {
data.sessionToken = {
data : hex32(),
uid : data.accountId,
createdAt: Date.now()
createdAt: Date.now(),
uaBrowser: 'fake browser',
uaBrowserVersion: 'fake browser version',
uaOS: 'fake OS',
uaOSVersion: 'fake OS version',
uaDeviceType: 'fake device type'
}

// keyFetchToken
Expand Down
29 changes: 29 additions & 0 deletions lib/db/mem.js
Expand Up @@ -70,6 +70,12 @@ module.exports = function (log, error) {
data: sessionToken.data,
uid: sessionToken.uid,
createdAt: sessionToken.createdAt,
uaBrowser: sessionToken.uaBrowser,
uaBrowserVersion: sessionToken.uaBrowserVersion,
uaOS: sessionToken.uaOS,
uaOSVersion: sessionToken.uaOSVersion,
uaDeviceType: sessionToken.uaDeviceType,
lastAccessTime: sessionToken.createdAt
}

var account = accounts[sessionToken.uid.toString('hex')]
Expand Down Expand Up @@ -294,6 +300,12 @@ module.exports = function (log, error) {
item.tokenData = sessionTokens[id].data
item.uid = sessionTokens[id].uid
item.createdAt = sessionTokens[id].createdAt
item.uaBrowser = sessionTokens[id].uaBrowser
item.uaBrowserVersion = sessionTokens[id].uaBrowserVersion
item.uaOS = sessionTokens[id].uaOS
item.uaOSVersion = sessionTokens[id].uaOSVersion
item.uaDeviceType = sessionTokens[id].uaDeviceType
item.lastAccessTime = sessionTokens[id].lastAccessTime

var accountId = sessionTokens[id].uid.toString('hex')
var account = accounts[accountId]
Expand Down Expand Up @@ -511,13 +523,30 @@ module.exports = function (log, error) {
return P.resolve(unlockCode)
}

// UPDATE

Memory.prototype.updatePasswordForgotToken = function (id, data) {
var token = passwordForgotTokens[id.toString('hex')]
if (!token) { return P.reject(error.notFound()) }
token.tries = data.tries
return P.resolve({})
}

Memory.prototype.updateSessionToken = function (id, data) {
var token = sessionTokens[id.toString('hex')]
if (!token) {
return P.reject(error.notFound())
}
token.uaBrowser = data.uaBrowser
token.uaBrowserVersion = data.uaBrowserVersion
token.uaOS = data.uaOS
token.uaOSVersion = data.uaOSVersion
token.uaDeviceType = data.uaDeviceType
token.lastAccessTime = data.lastAccessTime
return P.resolve({})
}


// UTILITY FUNCTIONS

Memory.prototype.ping = function () {
Expand Down