From 6acb9e0d4d9e37f28fc715b83b1631e9b6ae4718 Mon Sep 17 00:00:00 2001 From: Phil Booth Date: Fri, 5 Aug 2016 21:47:13 +0100 Subject: [PATCH] fix(server): ensure tokens get a fresh createdAt timestamp (#1389) r=vladikoff * fix(server): ensure tokens get a fresh createdAt timestamp * fix(tokens): default sessionToken.accountCreatedAt to null --- lib/routes/account.js | 9 +--- lib/tokens/session_token.js | 7 ++- lib/tokens/token.js | 16 ++++++- test/local/session_token_tests.js | 18 ++++++++ test/local/token_tests.js | 75 +++++++++++++++++++++++++++++++ test/remote/base_path_tests.js | 3 +- 6 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 test/local/token_tests.js diff --git a/lib/routes/account.js b/lib/routes/account.js index c68ed3f88..5e16e4af0 100644 --- a/lib/routes/account.js +++ b/lib/routes/account.js @@ -213,7 +213,7 @@ module.exports = function ( emailCode: account.emailCode, emailVerified: account.emailVerified, verifierSetAt: account.verifierSetAt, - createdAt: optionallyOverrideCreatedAt(), + createdAt: parseInt(query._createdAt), tokenVerificationId: tokenVerificationId }, userAgentString) .then( @@ -237,13 +237,6 @@ module.exports = function ( ) } - function optionallyOverrideCreatedAt () { - var createdAt = parseInt(query._createdAt) - if (createdAt < Date.now() && ! config.isProduction) { - return createdAt - } - } - function sendVerifyCode () { if (! account.emailVerified) { mailer.sendVerifyCode(account, account.emailCode, { diff --git a/lib/tokens/session_token.js b/lib/tokens/session_token.js index 5b1a73e4c..0e3de5c2e 100644 --- a/lib/tokens/session_token.js +++ b/lib/tokens/session_token.js @@ -21,7 +21,12 @@ module.exports = function (log, inherits, Token) { this.emailVerified = !!details.emailVerified this.verifierSetAt = details.verifierSetAt this.locale = details.locale || null - this.accountCreatedAt = details.createdAt + + if (details.createdAt > 0) { + this.accountCreatedAt = details.createdAt + } else { + this.accountCreatedAt = null + } // Tokens are considered verified if no tokenVerificationId exists this.tokenVerificationId = details.tokenVerificationId || null diff --git a/lib/tokens/token.js b/lib/tokens/token.js index 6c4aa1bca..748207b7b 100644 --- a/lib/tokens/token.js +++ b/lib/tokens/token.js @@ -23,6 +23,8 @@ * */ +var config = require('../../config').getProperties() + module.exports = function (log, crypto, P, hkdf, Bundle, error) { // Token constructor. @@ -38,7 +40,19 @@ module.exports = function (log, crypto, P, hkdf, Bundle, error) { this.algorithm = 'sha256' this.uid = details.uid || null this.lifetime = details.lifetime || Infinity - this.createdAt = details.createdAt >= 0 ? details.createdAt : Date.now() + this.createdAt = optionallyOverrideCreatedAt(details.createdAt) + } + + function optionallyOverrideCreatedAt (timestamp) { + var now = Date.now() + + if (! config.isProduction && timestamp >= 0 && timestamp < now) { + // In the wild, all tokens should have a fresh createdAt timestamp. + // For testing purposes only, allow createdAt to be overridden. + return timestamp + } + + return now } // Create a new token of the given type. diff --git a/test/local/session_token_tests.js b/test/local/session_token_tests.js index 676891c5b..ff679d1b5 100644 --- a/test/local/session_token_tests.js +++ b/test/local/session_token_tests.js @@ -13,6 +13,7 @@ var SessionToken = tokens.SessionToken var TOKEN_FRESHNESS_THRESHOLD = require('../../lib/tokens/session_token').TOKEN_FRESHNESS_THREADHOLD var ACCOUNT = { + createdAt: Date.now(), uid: 'xxx', email: Buffer('test@example.com').toString('hex'), emailCode: '123456', @@ -67,6 +68,23 @@ test( } ) +test( + 'create with NaN createdAt', + function (t) { + return SessionToken.create({ + createdAt: NaN, + email: 'foo', + uid: 'bar' + }).then( + function (token) { + var now = Date.now() + t.ok(token.createdAt > now - 1000 && token.createdAt <= now) + t.equal(token.accountCreatedAt, null) + } + ) + } +) + test( 'sessionToken key derivations are test-vector compliant', function (t) { diff --git a/test/local/token_tests.js b/test/local/token_tests.js new file mode 100644 index 000000000..4e5ca80e4 --- /dev/null +++ b/test/local/token_tests.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict' + +var crypto = require('crypto') +var hkdf = require('../../lib/crypto/hkdf') +var mocks = require('../mocks') +var P = require('../../lib/promise') +var sinon = require('sinon') +var test = require('tap').test + +var Bundle = { + bundle: sinon.spy(), + unbundle: sinon.spy() +} +var log = mocks.spyLog() +var modulePath = '../../lib/tokens/token' + +test('NODE_ENV=dev', function (t) { + process.env.NODE_ENV = 'dev' + var Token = require(modulePath)(log, crypto, P, hkdf, Bundle, null) + + t.plan(4) + + t.test('Token constructor was exported', function (t) { + t.equal(typeof Token, 'function', 'Token is function') + t.equal(Token.name, 'Token', 'function is called Token') + t.equal(Token.length, 2, 'function expects two arguments') + t.end() + }) + + t.test('Token constructor sets createdAt', function (t) { + var now = Date.now() - 1 + var token = new Token({}, { createdAt: now }) + + t.equal(token.createdAt, now, 'token.createdAt is correct') + t.end() + }) + + t.test('Token constructor does not set createdAt if it is negative', function (t) { + var notNow = -Date.now() + var token = new Token({}, { createdAt: notNow }) + + t.ok(token.createdAt > 0, 'token.createdAt seems correct') + t.end() + }) + + t.test('Token constructor does not set createdAt if it is in the future', function (t) { + var notNow = Date.now() + 1000 + var token = new Token({}, { createdAt: notNow }) + + t.ok(token.createdAt > 0 && token.createdAt < notNow, 'token.createdAt seems correct') + t.end() + }) +}) + +test('NODE_ENV=prod', function (t) { + process.env.NODE_ENV = 'prod' + delete require.cache[require.resolve(modulePath)] + delete require.cache[require.resolve('../../config')] + var Token = require(modulePath)(log, crypto, P, hkdf, Bundle, null) + + t.plan(1) + + t.test('Token constructor does not set createdAt', function (t) { + var notNow = Date.now() - 1 + var token = new Token({}, { createdAt: notNow }) + + t.ok(token.createdAt > notNow, 'token.createdAt seems correct') + t.end() + }) +}) + diff --git a/test/remote/base_path_tests.js b/test/remote/base_path_tests.js index f0694537f..deb0409ff 100644 --- a/test/remote/base_path_tests.js +++ b/test/remote/base_path_tests.js @@ -2,13 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +process.env.PUBLIC_URL = 'http://127.0.0.1:9000/auth' + var test = require('../ptaptest') var TestServer = require('../test_server') var Client = require('../client') var P = require('../../lib/promise') var request = require('request') -process.env.PUBLIC_URL = 'http://127.0.0.1:9000/auth' var config = require('../../config').getProperties() TestServer.start(config)