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

Commit

Permalink
feat(db): store user agent and last-access time in sessionTokens
Browse files Browse the repository at this point in the history
  • Loading branch information
philbooth committed Jul 30, 2015
1 parent 63a8ba4 commit 06966d7
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 17 deletions.
25 changes: 22 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, userAgent
* keyFetchToken : authKey, uid, keyBundle, createdAt
* passwordChangeToken : data, uid, createdAt
* passwordForgotToken : data, uid, passCode, createdAt, triesxb
Expand Down Expand Up @@ -288,7 +289,7 @@ 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.userAgent, 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 +303,24 @@ 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 `userAgent` and `lastAccessTime` fields of the token.

Parameters.

* tokenId : (Buffer32) the unique id for this token
* token : (Object) -
* userAgent : (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 +336,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
51 changes: 50 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,54 @@ 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 '{
"userAgent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0",
"lastAccessTime": 1437992394186
}' \
http://localhost:8000/sessionToken/522c251a1623e1f1db1f4fe68b9594d26772d6e77e04cb68e110c58600f97a77/update
```

### Request

* Method : POST
* Path : `/sessionToken/<tokenId>/update`
* tokenId : hex256
* Params:
* userAgent : 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 +1248,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
29 changes: 27 additions & 2 deletions fxa-auth-db-server/test/backend/db_tests.js
Expand Up @@ -41,7 +41,8 @@ var SESSION_TOKEN_ID = hex32()
var SESSION_TOKEN = {
data : hex32(),
uid : ACCOUNT.uid,
createdAt: now + 1
createdAt : now + 1,
userAgent : 'mock user agent'
}

var KEY_FETCH_TOKEN_ID = hex32()
Expand Down Expand Up @@ -197,7 +198,7 @@ module.exports = function(config, DB) {
test(
'session token handling',
function (t) {
t.plan(10)
t.plan(22)
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 +209,29 @@ 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.userAgent, SESSION_TOKEN.userAgent, 'userAgent 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, {
userAgent: 'foo',
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.userAgent, 'foo', 'userAgent 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 @@ -750,6 +774,7 @@ module.exports = function(config, DB) {
data : hex32(),
uid : ACCOUNT.uid,
createdAt: Date.now(),
userAgent: ''
}
db.createSessionToken(SESSION_TOKEN_ID, SESSION_TOKEN)
.then(function(sessionToken) {
Expand Down
28 changes: 25 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(23)
var user = fake.newUserDataHex()
client.putThen('/account/' + user.accountId, user.account)
.then(function() {
Expand All @@ -202,13 +202,35 @@ 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.userAgent, user.sessionToken.userAgent, 'userAgent 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', {
userAgent: 'foo',
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.userAgent, 'foo', 'userAgent 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
6 changes: 4 additions & 2 deletions fxa-auth-db-server/test/fake.js
Expand Up @@ -43,7 +43,8 @@ module.exports.newUserDataHex = function() {
data.sessionToken = {
data : hex32(),
uid : data.accountId,
createdAt: Date.now()
createdAt: Date.now(),
userAgent: 'fake user agent string'
}

// keyFetchToken
Expand Down Expand Up @@ -110,7 +111,8 @@ module.exports.newUserDataBuffer = function() {
data.sessionToken = {
data : buf32(),
uid : data.accountId,
createdAt: Date.now()
createdAt: Date.now(),
userAgent: ''
}

// keyFetchToken
Expand Down
17 changes: 17 additions & 0 deletions lib/db/mem.js
Expand Up @@ -70,6 +70,8 @@ module.exports = function (log, error) {
data: sessionToken.data,
uid: sessionToken.uid,
createdAt: sessionToken.createdAt,
userAgent: sessionToken.userAgent,
lastAccessTime: sessionToken.createdAt
}

var account = accounts[sessionToken.uid.toString('hex')]
Expand Down Expand Up @@ -294,6 +296,8 @@ module.exports = function (log, error) {
item.tokenData = sessionTokens[id].data
item.uid = sessionTokens[id].uid
item.createdAt = sessionTokens[id].createdAt
item.userAgent = sessionTokens[id].userAgent
item.lastAccessTime = sessionTokens[id].lastAccessTime

var accountId = sessionTokens[id].uid.toString('hex')
var account = accounts[accountId]
Expand Down Expand Up @@ -511,13 +515,26 @@ 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.userAgent = data.userAgent
token.lastAccessTime = data.lastAccessTime
return P.resolve({})
}


// UTILITY FUNCTIONS

Memory.prototype.ping = function () {
Expand Down
21 changes: 16 additions & 5 deletions lib/db/mysql.js
Expand Up @@ -179,8 +179,8 @@ module.exports = function (log, error) {
}

// Insert : sessionTokens
// Values : tokenId = $1, tokenData = $2, uid = $3, createdAt = $4
var CREATE_SESSION_TOKEN = 'CALL createSessionToken_1(?, ?, ?, ?)'
// Values : tokenId = $1, tokenData = $2, uid = $3, createdAt = $4, userAgent = $5
var CREATE_SESSION_TOKEN = 'CALL createSessionToken_2(?, ?, ?, ?, ?)'

MySql.prototype.createSessionToken = function (tokenId, sessionToken) {
return this.write(
Expand All @@ -189,7 +189,8 @@ module.exports = function (log, error) {
tokenId,
sessionToken.data,
sessionToken.uid,
sessionToken.createdAt
sessionToken.createdAt,
sessionToken.userAgent
]
)
}
Expand Down Expand Up @@ -301,9 +302,10 @@ module.exports = function (log, error) {
}

// Select : sessionTokens t, accounts a
// Fields : t.tokenData, t.uid, t.createdAt, a.emailVerified, a.email, a.emailCode, a.verifierSetAt, a.locale
// Fields : t.tokenData, t.uid, t.createdAt, t.userAgent, t.lastAccessTime,
// a.emailVerified, a.email, a.emailCode, a.verifierSetAt, a.locale
// Where : t.tokenId = $1 AND t.uid = a.uid
var SESSION_TOKEN = 'CALL sessionToken_1(?)'
var SESSION_TOKEN = 'CALL sessionToken_2(?)'

MySql.prototype.sessionToken = function (id) {
return this.readFirstResult(SESSION_TOKEN, [id])
Expand Down Expand Up @@ -373,6 +375,15 @@ module.exports = function (log, error) {
return this.write(UPDATE_PASSWORD_FORGOT_TOKEN, [token.tries, tokenId])
}

// Update : sessionTokens
// Set : userAgent = $1, lastAccessTime = $2
// Where : tokenId = $3
var UPDATE_SESSION_TOKEN = 'CALL updateSessionToken_1(?, ?, ?)'

MySql.prototype.updateSessionToken = function (tokenId, token) {
return this.write(UPDATE_SESSION_TOKEN, [token.userAgent, token.lastAccessTime, tokenId])
}

// DELETE

// Delete : sessionTokens, keyFetchTokens, accountResetTokens, passwordChangeTokens, passwordForgotTokens, accountUnlockCodes, accounts
Expand Down
2 changes: 1 addition & 1 deletion lib/db/patch.js
Expand Up @@ -4,4 +4,4 @@

// The expected patch level of the database. Update if you add a new
// patch in the schema/ directory.
module.exports.level = 14
module.exports.level = 15

0 comments on commit 06966d7

Please sign in to comment.