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 28, 2015
1 parent 97fcee3 commit 046e59c
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 15 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
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
23 changes: 18 additions & 5 deletions lib/db/mysql.js
Expand Up @@ -179,8 +179,9 @@ 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, lastAccessTime = $6
var CREATE_SESSION_TOKEN = 'CALL createSessionToken_2(?, ?, ?, ?, ?, ?)'

MySql.prototype.createSessionToken = function (tokenId, sessionToken) {
return this.write(
Expand All @@ -189,7 +190,9 @@ module.exports = function (log, error) {
tokenId,
sessionToken.data,
sessionToken.uid,
sessionToken.createdAt
sessionToken.createdAt,
sessionToken.userAgent,
sessionToken.lastAccessTime
]
)
}
Expand Down Expand Up @@ -301,9 +304,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 +377,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
71 changes: 71 additions & 0 deletions lib/db/schema/patch-014-015.sql
@@ -0,0 +1,71 @@
-- Add userAgent and lastAccessTime fields to sessionTokens table

ALTER TABLE sessionTokens ADD COLUMN userAgent VARCHAR(255) NOT NULL DEFAULT '';
ALTER TABLE sessionTokens ADD COLUMN lastAccessTime BIGINT UNSIGNED NOT NULL DEFAULT 0;

CREATE PROCEDURE `createSessionToken_2` (
IN tokenId BINARY(32),
IN tokenData BINARY(32),
IN uid BINARY(16),
IN createdAt BIGINT UNSIGNED,
IN userAgent VARCHAR(255),
IN lastAccessTime BIGINT UNSIGNED
)
BEGIN
INSERT INTO sessionTokens(
tokenId,
tokenData,
uid,
createdAt,
userAgent,
lastAccessTime
)
VALUES(
tokenId,
tokenData,
uid,
createdAt,
userAgent,
lastAccessTime
);
END;

CREATE PROCEDURE `sessionToken_2` (
IN `inTokenId` BINARY(32)
)
BEGIN
SELECT
t.tokenData,
t.uid,
t.createdAt,
t.userAgent,
t.lastAccessTime,
a.emailVerified,
a.email,
a.emailCode,
a.verifierSetAt,
a.locale
FROM
sessionTokens t,
accounts a
WHERE
t.tokenId = inTokenId
AND
t.uid = a.uid
;
END;

CREATE PROCEDURE `updateSessionToken_1` (
IN userAgentArg VARCHAR(255),
IN lastAccessTimeArg BIGINT UNSIGNED,
IN tokenIdArg BINARY(32)
)
BEGIN
UPDATE sessionTokens
SET userAgent = userAgentArg,
lastAccessTime = lastAccessTimeArg
WHERE tokenId = tokenIdArg;
END;

UPDATE dbMetadata SET value = '15' WHERE name = 'schema-patch-level';

11 changes: 11 additions & 0 deletions lib/db/schema/patch-015-014.sql
@@ -0,0 +1,11 @@
-- -- drop new stored procedures
-- DROP PROCEDURE `createSessionToken_2`;
-- DROP PROCEDURE `sessionToken_2`;
-- DROP PROCEDURE `updateSessionToken_1`;

-- -- drop new columns
-- ALTER TABLE sessionTokens DROP COLUMN userAgent;
-- ALTER TABLE sessionTokens DROP COLUMN lastAccessTime;

-- -- Schema patch-level decrement.
-- UPDATE dbMetadata SET value = '14' WHERE name = 'schema-patch-level';

0 comments on commit 046e59c

Please sign in to comment.