diff --git a/db/heap.js b/db/heap.js index 288089a6..6a173883 100644 --- a/db/heap.js +++ b/db/heap.js @@ -105,7 +105,7 @@ module.exports = function ( Heap.prototype.createForgotPasswordToken = function (emailRecord) { log.trace({ op: 'Heap.createForgotPasswordToken', uid: emailRecord && emailRecord.uid }) - return ForgotPasswordToken.create(emailRecord.uid) + return ForgotPasswordToken.create(emailRecord.uid, emailRecord.email) .then( function (forgotPasswordToken) { var account = this.accounts[forgotPasswordToken.uid] @@ -183,7 +183,7 @@ module.exports = function ( log.trace({ op: 'Heap.forgotPasswordToken', id: id }) var forgotPasswordToken = this.forgotPasswordTokens[id] if (!forgotPasswordToken) { return P.reject(error.invalidToken()) } - var account = this.accounts[sessionToken.uid] + var account = this.accounts[forgotPasswordToken.uid] if (!account) { return P.reject(error.unknownAccount()) } forgotPasswordToken.email = account.email return P(forgotPasswordToken) @@ -205,7 +205,7 @@ module.exports = function ( // UPDATE - Heap.prototype.udateForgotPasswordToken = function (forgotPasswordToken) { + Heap.prototype.updateForgotPasswordToken = function (forgotPasswordToken) { log.trace({ op: 'Heap.udateForgotPasswordToken', uid: forgotPasswordToken && forgotPasswordToken.uid }) return P(true) } diff --git a/routes/account.js b/routes/account.js index 65d74d70..8d8bdad3 100644 --- a/routes/account.js +++ b/routes/account.js @@ -2,7 +2,7 @@ * 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/. */ -module.exports = function (log, crypto, uuid, isA, error, db, mailer, config) { +module.exports = function (log, crypto, P, uuid, isA, error, db, mailer, config) { const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/ @@ -78,16 +78,19 @@ module.exports = function (log, crypto, uuid, isA, error, db, mailer, config) { } ) ) - // .then( - // function (account) { - // return sendVerifyCode( - // account.email, - // account.emailCode, - // account.uid - // ) - // .then(function () { return account }) - // } - // ) + .then( + function (account) { + if (account.verified) { + return P(account) + } + return sendVerifyCode( + account.email, + account.emailCode, + account.uid + ) + .then(function () { return account }) + } + ) .done( function (account) { request.reply( @@ -247,7 +250,7 @@ module.exports = function (log, crypto, uuid, isA, error, db, mailer, config) { db.account(uid) .then( function (account) { - if (code !== account.code) { + if (code !== account.emailCode) { throw error.invalidVerificationCode() } return db.verifyEmail(account) diff --git a/routes/auth.js b/routes/auth.js index eeaae16f..3b1eef70 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -7,15 +7,7 @@ module.exports = function (log, isA, error, db, Token) { const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/ function clientData(srpToken) { - return { - srpToken: srpToken.id, - passwordStretching: srpToken.passwordStretching, - srp: { - type: 'SRP-6a/SHA256/2048/v1', - salt: srpToken.s, - B: srpToken.B.toString('hex') - } - } + return srpToken.clientData() } function bundleAuth(K, authToken) { diff --git a/routes/index.js b/routes/index.js index 64b76f26..5b5dc075 100644 --- a/routes/index.js +++ b/routes/index.js @@ -23,7 +23,7 @@ module.exports = function ( var auth = require('./auth')(log, isA, error, db, Token) var defaults = require('./defaults')(log, P, db) var idp = require('./idp')(log, serverPublicKey) - var account = require('./account')(log, crypto, uuid, isA, error, db, mailer, config) + var account = require('./account')(log, crypto, P, uuid, isA, error, db, mailer, config) var password = require('./password')(log, isA, error, db, mailer) var session = require('./session')(log, isA, error, db) var sign = require('./sign')(log, isA, error, signer, config.domain) diff --git a/routes/password.js b/routes/password.js index 00290ac8..4e556b9d 100644 --- a/routes/password.js +++ b/routes/password.js @@ -5,7 +5,6 @@ module.exports = function (log, isA, error, db, mailer) { const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/ - const FPT_LIFETIME = 1000 * 60 * 15 function bundleAccountReset(authToken, keyFetchToken, accountResetToken) { return authToken.bundleKeys('password/change', keyFetchToken, accountResetToken) @@ -14,21 +13,18 @@ module.exports = function (log, isA, error, db, mailer) { function sendRecoveryCode(forgotPasswordToken) { log.trace({ op: 'sendRecoveryCode', id: forgotPasswordToken.id }) return mailer.sendRecoveryCode( - Buffer(forgotPasswordToken.email, 'hex').toString(), + Buffer(forgotPasswordToken.email, 'hex').toString('utf8'), forgotPasswordToken.passcode ) + .then(function () { return forgotPasswordToken }) } function ttl(forgotPasswordToken) { - return Math.max( - Math.ceil((FPT_LIFETIME - (Date.now() - forgotPasswordToken.created)) / 1000), - 0 - ) + return forgotPasswordToken.ttl() } function failVerifyAttempt(forgotPasswordToken) { - forgotPasswordToken.tries-- - return (forgotPasswordToken.tries < 1) ? + return (forgotPasswordToken.failAttempt()) ? db.deleteForgotPasswordToken(forgotPasswordToken) : db.updateForgotPasswordToken(forgotPasswordToken) } @@ -90,7 +86,7 @@ module.exports = function (log, isA, error, db, mailer) { } ) .done( - function () { + function (forgotPasswordToken) { request.reply( { forgotPasswordToken: forgotPasswordToken.data, @@ -179,7 +175,7 @@ module.exports = function (log, isA, error, db, mailer) { log.begin('Password.forgotVerify', request) var forgotPasswordToken = request.auth.credentials var code = +(request.payload.code) - if (forgotPasswordToken.code === code && ttl(forgotPasswordToken) > 0) { + if (forgotPasswordToken.passcode === code && ttl(forgotPasswordToken) > 0) { db.forgotPasswordVerified(forgotPasswordToken) .done( function (accountResetToken) { diff --git a/test/run/account_reset_token_tests.js b/test/run/account_reset_token_tests.js index af9d77ec..d47c5ec9 100644 --- a/test/run/account_reset_token_tests.js +++ b/test/run/account_reset_token_tests.js @@ -1,15 +1,9 @@ var test = require('tap').test var crypto = require('crypto') -var P = require('p-promise') -var config = require('../../config').root() var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) - -var mailer = {} - -var models = require('../../models')(log, config, dbs, mailer) -var AccountResetToken = models.tokens.AccountResetToken +var tokens = require('../../tokens')(log) +var AccountResetToken = tokens.AccountResetToken test( 'bundle / unbundle works', @@ -35,12 +29,3 @@ test( .done(end, end) } ) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/account_tests.js b/test/run/account_tests.js deleted file mode 100644 index 66453367..00000000 --- a/test/run/account_tests.js +++ /dev/null @@ -1,540 +0,0 @@ -var test = require('tap').test -var P = require('p-promise') -var config = require('../../config').root() -var log = { trace: function() {} } - -var dbs = require('../../kv')(config, log) - -var mailer = { - sendVerifyCode: function () { return P(null) } -} - -var models = require('../../models')(log, config, dbs, mailer) -var Account = models.Account -var RecoveryEmail = models.RecoveryEmail -var SessionToken = models.tokens.SessionToken -var AccountResetToken = models.tokens.AccountResetToken - -var a = { - uid: 'xxx', - email: Buffer('somebody@example.com').toString('hex'), - srp: { - verifier: 'BAD1', - salt: 'BAD2' - }, - kA: 'BAD3', - wrapKb: 'BAD4' -} - -test( - 'Account.principal uses the given uid and adds the domain', - function (t) { - t.equal(Account.principal('xyz'), 'xyz@' + config.domain) - t.end() - } -) - -test( - 'Account.create adds a new account', - function (t) { - Account.create(a) - .then(Account.get.bind(null, a.uid)) - .then( - function (account) { - t.equal(account.email, a.email) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() }) - } -) - -test( - 'Account.create adds a primary recovery method', - function (t) { - var code = null - Account.create(a) - .then(Account.get.bind(null, a.uid)) - .then( - function (account) { - code = Object.keys(account.recoveryEmailCodes)[0] - t.ok(code) - } - ) - .then( - function () { - return RecoveryEmail.get(a.uid, code) - } - ) - .then( - function (rm) { - t.equal(rm.uid, a.uid) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() }) - } -) - -test( - 'Account.create returns an error if the account exists', - function (t) { - Account.create(a) - .then(Account.create.bind(null, a)) - .then( - function () { - t.fail('should not have created an account') - }, - function (err) { - t.equal(err.errno, 101) - t.equal(err.message, 'Account already exists') - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() }) - } -) - -test( - 'Account.get of an invalid uid returns null', - function (t) { - Account.get('foobar') - .done( - function (x) { - t.equal(x, null) - t.end() - } - ) - } -) - -test( - 'Account.getId returns the uid given an email', - function (t) { - Account.create(a) - .then(Account.getId.bind(null, a.email)) - .then( - function (id) { - t.equal(id, a.uid) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() }) - } -) - -test( - 'Account.exists returns false if the email is not in use', - function (t) { - Account.exists(Buffer('nobody@example.com').toString('hex')).done( - function (exists) { - t.equal(exists, false) - t.end() - }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'Account.exists returns true if the email is in use', - function (t) { - Account.create(a) - .then(Account.exists.bind(null, a.email)) - .then( - function (exists) { - t.equal(exists, true) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'Account.del deletes the account with the given uid', - function (t) { - Account.create(a) - .then(Account.exists.bind(null, a.email)) - .then( - function (exists) { - t.equal(exists, true) - } - ) - .then(Account.del.bind(null, a.uid)) - .then(Account.get.bind(null, a.uid)) - .done( - function (account) { - t.equal(account, null) - t.end() - }, - function (err) { - t.fail(err) - t.end() - } - ) - } -) - -test( - 'Account.del deletes all data related to the account', - function (t) { - var account = null - var session = null - var reset = null - // This test is only valid for memory db - if (!dbs.store.kv.data) { - return t.end() - } - t.equal(Object.keys(dbs.store.kv.data).length, 0) - Account.create(a) - .then( - function (a) { - account = a - } - ) - .then(SessionToken.create.bind(null, a.uid)) - .then( - function (t) { - session = t - return account.addSessionToken(session) - } - ) - .then(AccountResetToken.create.bind(null, a.uid)) - .then( - function (t) { - reset = t - return account.setResetToken(reset) - } - ) - .then( - function () { - // 5: uid, user, recovery, reset, session - t.equal(Object.keys(dbs.store.kv.data).length, 5) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { - t.equal(Object.keys(dbs.store.kv.data).length, 0) - t.end() - }, - function (err) { - t.fail(err) - t.end() - } - ) - } -) - -test( - 'Account.del of an invalid uid returns null', - function (t) { - Account.del('foobar') - .done( - function (x) { - t.equal(x, null) - t.end() - } - ) - } -) - -test( - 'Account.verify sets verified to true when the recovery method is primary', - function (t) { - Account.create(a) - .then( - function (account) { - var code = Object.keys(account.recoveryEmailCodes)[0] - return RecoveryEmail.get(account.uid, code) - } - ) - .then( - function (x) { - x.verified = true - return Account.verify(x) - } - ) - .then( - function (account) { - t.equal(account.verified, true) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'Account.verify does not set verified true if recovery method is not verified', - function (t) { - Account.create(a) - .then( - function (account) { - var code = Object.keys(account.recoveryEmailCodes)[0] - return RecoveryEmail.get(account.uid, code) - } - ) - .then( - function (x) { - return Account.verify(x) - } - ) - .then( - function (account) { - t.equal(account.verified, false) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'account.setResetToken deletes existing token', - function (t) { - var account = null - var token1 = null - var token2 = null - AccountResetToken.create(a.uid) - .then( - function (x) { - token1 = x - } - ) - .then(Account.create.bind(null, a)) - .then( - function (x) { - account = x - return account.setResetToken(token1) - } - ) - .then(AccountResetToken.create.bind(null, a.uid)) - .then( - function (x) { - t.equal(account.resetTokenId, token1.id) - token2 = x - return account.setResetToken(token2) - } - ) - .then( - function () { - t.equal(account.resetTokenId, token2.id) - return AccountResetToken.get(token1.id) - } - ) - .then( - function (x) { - t.equal(x, null) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'Account.getByEmail works', - function (t) { - Account.create(a) - .then(Account.getByEmail.bind(null, a.email)) - .then( - function (account) { - t.equal(account.email, a.email) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'account.addSessionToken works', - function (t) { - var account = null - var token = null - Account.create(a) - .then( - function (x) { - account = x - return SessionToken.create(x.uid) - } - ) - .then( - function (t) { - token = t - return account.addSessionToken(t) - } - ) - .then( - function (x) { - t.equal(x.sessionTokenIds[token.id], true) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'account.recoveryEmails returns an array of RecoveryEmail objects', - function (t) { - Account.create(a) - .then( - function (account) { - return account.recoveryEmails() - } - ) - .then( - function (rms) { - t.equal(rms.length, 1) - t.equal(rms[0] instanceof RecoveryEmail, true) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'account.reset changes wrapKb and verifier', - function (t) { - var form = { - wrapKb: 'DEADBEEF', - srp: { - type: 'SRP-6a/SHA256/2048/v1', - verifier: 'FEEDFACE', - salt: '12345678' - }, - passwordStretching: { - stuff: true - } - } - Account.create(a) - .then( - function (account) { - return account.reset(form) - } - ) - .then( - function (account) { - t.equal(account.wrapKb, form.wrapKb) - t.equal(account.srp.verifier, form.srp.verifier) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'account.reset deletes all tokens', - function (t) { - var account = null - var session = null - var reset = null - var form = { - wrapKb: 'DEADBEEF', - srp: { - type: 'SRP-6a/SHA256/2048/v1', - verifier: 'FEEDFACE', - salt: '12345678' - }, - passwordStretching: { - stuff: true - } - } - Account.create(a) - .then( - function (a) { - account = a - } - ) - .then(SessionToken.create.bind(null, a.uid)) - .then( - function (x) { - session = x - return account.addSessionToken(session) - } - ) - .then(AccountResetToken.create.bind(null, a.uid)) - .then( - function (x) { - reset = x - return account.setResetToken(reset) - } - ) - .then( - function () { - return account.reset(form) - } - ) - .then( - function () { - return AccountResetToken.get(reset.id) - } - ) - .then( - function (x) { - t.equal(x, null) - } - ) - .then( - function () { - return SessionToken.get(session.id) - } - ) - .then( - function (x) { - t.equal(x, null) - } - ) - .then(Account.del.bind(null, a.uid)) - .done( - function () { t.end() }, - function (err) { t.fail(err); t.end() } - ) - } -) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/auth_token_tests.js b/test/run/auth_token_tests.js index 914305af..f67b3d5a 100644 --- a/test/run/auth_token_tests.js +++ b/test/run/auth_token_tests.js @@ -1,16 +1,9 @@ var test = require('tap').test var crypto = require('crypto') -var P = require('p-promise') -var config = require('../../config').root() var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) -var mailer = { - sendVerifyCode: function () { return P(null) } -} - -var models = require('../../models')(log, config, dbs, mailer) -var AuthToken = models.tokens.AuthToken +var tokens = require('../../tokens')(log) +var AuthToken = tokens.AuthToken test( 'bundle / unbundle works', @@ -36,12 +29,3 @@ test( .done(end, end) } ) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/bundle_tests.js b/test/run/bundle_tests.js deleted file mode 100644 index a525784e..00000000 --- a/test/run/bundle_tests.js +++ /dev/null @@ -1,347 +0,0 @@ -var test = require('tap').test -var inherits = require('util').inherits -var crypto = require('crypto') -var P = require('p-promise') -var hkdf = require('../../hkdf') -var config = require('../../config').root() -var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) - -function fakeCrypto(bytes) { - return { - randomBytes: function (size, cb) { - cb(null, bytes) - }, - createHmac: crypto.createHmac - } -} - -// Test vectors -// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#Test_Vectors - -var useSession = { - sessionToken: Buffer( - '8081828384858687' + - '88898a8b8c8d8e8f' + - '9091929394959697' + - '98999a9b9c9d9e9f', - 'hex'), - tokenId: - '31217a79ba0d62e9' + - 'c6e33cee374f0879' + - '3171b2a39d14cc8f' + - 'f680540b5028d069', - key: - '6c87cfeba3a216d4' + - 'b1829e62478500ac' + - 'd2953158130cae0b' + - '2c92ef8a2ea6089a' -} - -var accountKeys = { - keyFetchToken: Buffer( - '6061626364656667' + - '68696a6b6c6d6e6f' + - '7071727374757677' + - '78797a7b7c7d7e7f', - 'hex'), - tokenId: - '7f784ba2bd89097f' + - '743632d21316d987' + - '38e146a9e7123a98' + - '39a87c96b3bb99cb', - key: - '6dedf96237deb067' + - 'f4232af00b3c7148' + - 'e815635c147a7215' + - 'a64906bdb2823471', - hmacKey: - 'ca24f43285899356' + - '5d698251dbe6c7f7' + - 'da5f9ad003835a41' + - 'edf7c813124c5499', - xorKey: - '9dff4835ffdbacd6' + - '5e27f5dde15a1f18' + - '994ff75f70bab7db' + - 'b5c4c9771e657704' + - '4666cf97273e2a96' + - '02993f5b1e258d8f' + - '3b4d837e505f8458' + - '41a986882ef36631', - kA: - '2021222324252627' + - '28292a2b2c2d2e2f' + - '3031323334353637' + - '38393a3b3c3d3e3f', - wrapKb: - '4041424344454647' + - '48494a4b4c4d4e4f' + - '5051525354555657' + - '58595a5b5c5d5e5f', - ciphertext: - 'bdde6a16dbfe8af1' + - '760edff6cd773137' + - 'a97ec56c448f81ec' + - '8dfdf34c2258493b' + - '06278dd4637b6cd1' + - '4ad075105268c3c0' + - '6b1cd12d040ad20f' + - '19f0dcd372ae386e', - hmac: - '6f7972302f00dfe8' + - '2d5a8ce0553b0ffe' + - '80e073078d4f30f9' + - '0c48537f8ca92222' -} - -var sessionAuth = { - K: Buffer( - 'e68fd0112bfa31dc' + - 'ffc8e9c96a1cbadb' + - '4c3145978ff35c73' + - 'e5bf8d30bbc7499a', - 'hex'), - hmacKey: - 'e252adb2c217c2a1' + - '02b4bd3f71294430' + - 'e367145b107d1e8d' + - 'e35684bbdf13f1e9', - xorKey: - '75a6ff483b6afe43' + - 'f80f95b5e2061ce3' + - '961996ec4c2eeb9c' + - '350ebfabdd766549' + - '342a0b2d910c9f5b' + - 'b2dee20f2af61849' + - 'a4a20ff16ee4a25f' + - 'cb6e832effa77f59', - keyFetchToken: - '6061626364656667' + - '68696a6b6c6d6e6f' + - '7071727374757677' + - '78797a7b7c7d7e7f', - sessionToken: - '8081828384858687' + - '88898a8b8c8d8e8f' + - '9091929394959697' + - '98999a9b9c9d9e9f', - ciphertext: - '15c79d2b5f0f9824' + - '9066ffde8e6b728c' + - 'e668e49f385b9deb' + - '4d77c5d0a10b1b36' + - 'b4ab89ae158919dc' + - '3a576884a67b96c6' + - '34339d62fa7134c8' + - '53f719b5633ae1c6', - hmac: - 'b27381d49ca93e61' + - '3247c49a0cd0c901' + - '0332f186bb07c23f' + - '33ad176916d607c4' -} - -var passwordChange = { - xorKey: - 'aaf041fd5f2c23e9' + - '0c3636f93a170ef0' + - '60456d7edf7678df' + - '2d5297797626a07d' + - 'a96803cfe941a0c8' + - 'ea140e371871ea20' + - '1ec38ad41a233b8e' + - '39ff1bedf6ce0aec', - hmacKey: - '81a03345184a09fd' + - '9aef6ec1a1ddf80f' + - 'c4e3d354bf8af42f' + - 'a4b32696384cb9b9', - ciphertext: - 'ca91239e3b49458e' + - '645f5c92567a609f' + - '10341f0dab030ea8' + - '552bed020a5bde02' + - '09c9a16c4de4066f' + - '42bda49cb4dc448f' + - 'ae723867ae968d39' + - '8146a1564a73b453', - hmac: - '442223ac3a149d00' + - 'cc319a73189b8572' + - 'e323084b662f74a5' + - 'b5d1f32925ea50de' -} - -var accountReset = { - accountResetToken: Buffer( - 'a0a1a2a3a4a5a6a7' + - 'a8a9aaabacadaeaf' + - 'b0b1b2b3b4b5b6b7' + - 'b8b9babbbcbdbebf', - 'hex'), - tokenId: - 'b421fa511242b33f' + - 'feebdef63089242f' + - 'fde11c811fd5474d' + - 'b888ade257861e23', - key: - 'da5fb4a8e1a7fc77' + - 'dfcf43be71455f69' + - 'f6776e24f369e253' + - 'ff1f541fbb5e9bc3', - xorKey: - 'def723a6ece08e37' + 'd5b598a25a031eda' + - 'acad44ef5186fef0' + '2a76417dc245379b' + - '1c5825ac741dd558' + '632d933cc9455875' + - 'f099cbe46d926ace' + '201616119d47f115' + - 'ab7623e63c29c518' + '187a6139570f8457' + - '03c84be42720bbb6' + '6097f90172a7ebf4' + - '0a44f140828f0cd4' + '16028e67e0ef3b4c' + - 'f6e0b43055bd008a' + '1305b2b5f579b0f0' + - 'ca91d70e28265713' + 'b4d2dc5197e64dec' + - 'f0e6ee2b8acdef73' + 'ea1951f7dea374cf' + - '2f56ac2a76f5f1e1' + '2ba46852bf6d315e' + - '2e9419c8d4d43676' + '168044e45862c3e4' + - '3e4a390b00950870' + '953f36112d697b43' + - '6fd661567ca29c7e' + '68fea229b016cdad' + - 'c19bf3430a0b52c7' + 'cdd232e774c10882' + - '507bd85a3b0c14fe' + '795367422374d774' + - 'dfa43df9f91d723d' + '4480e2d2f0776794' + - '67481cab9c835602' + '69fa7f3086efc88e', - plaintext: - '4041424344454647' + '48494a4b4c4d4e4f' + - '5051525354555657' + '58595a5b5c5d5e5f' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111' + - '1111111111111111' + '1111111111111111', - ciphertext: - '9eb661e5a8a5c870' + '9dfcd2e9164e5095' + - 'fcfc16bc05d3a8a7' + '722f1b269e1869c4' + - '0d4934bd650cc449' + '723c822dd8544964' + - 'e188daf57c837bdf' + '310707008c56e004' + - 'ba6732f72d38d409' + '096b7028461e9546' + - '12d95af53631aaa7' + '7186e81063b6fae5' + - '1b55e051939e1dc5' + '07139f76f1fe2a5d' + - 'e7f1a52144ac119b' + '0214a3a4e468a1e1' + - 'db80c61f39374602' + 'a5c3cd4086f75cfd' + - 'e1f7ff3a9bdcfe62' + 'fb0840e6cfb265de' + - '3e47bd3b67e4e0f0' + '3ab57943ae7c204f' + - '3f8508d9c5c52767' + '079155f54973d2f5' + - '2f5b281a11841961' + '842e27003c786a52' + - '7ec770476db38d6f' + '79efb338a107dcbc' + - 'd08ae2521b1a43d6' + 'dcc323f665d01993' + - '416ac94b2a1d05ef' + '684276533265c665' + - 'ceb52ce8e80c632c' + '5591f3c3e1667685' + - '76590dba8d924713' + '78eb6e2197fed99f' -} - -var KBundle = require('../../bundle/bundle')(fakeCrypto(accountKeys.keyFetchToken), P, hkdf) -var KToken = require('../../models/token')(log, inherits, KBundle) - -var SBundle = require('../../bundle/bundle')(fakeCrypto(useSession.sessionToken), P, hkdf) -var SToken = require('../../models/token')(log, inherits, SBundle) - -var RBundle = require('../../bundle/bundle')(fakeCrypto(accountReset.accountResetToken), P, hkdf) -var RToken = require('../../models/token')(log, inherits, RBundle) - -var KeyFetchToken = require('../../models/key_fetch_token')(log, inherits, KToken, dbs.store) -var AccountResetToken = require('../../models/account_reset_token')(log, inherits, RToken, crypto, dbs.store) -var SessionToken = require('../../models/session_token')(log, inherits, SToken, dbs.store) - -var AuthToken = require('../../models/auth_token')(log, inherits, SToken, dbs.store) -var tokens = { - AuthToken: AuthToken, - KeyFetchToken: KeyFetchToken, - AccountResetToken: AccountResetToken, - SessionToken: SessionToken -} - -function FakeAccount() { - this.sessionTokenIds = {} - this.resetTokenId = null -} -var account = new FakeAccount() -FakeAccount.get = function () { return P(account) } -FakeAccount.prototype.addSessionToken = function (t) { - this.sessionTokenIds[t.id] = true - return P(null) -} -FakeAccount.prototype.setAuthToken = function (t) { - this.authTokenId = t.id - return P(null) -} - -var AuthBundle = require('../../models/auth_bundle')(log, inherits, require('../../bundle'), FakeAccount, tokens) - -test( - 'create / get', - function (t) { - KeyFetchToken.create('xxx') - .then( - function () { - return KeyFetchToken.get(accountKeys.tokenId) - } - ) - .done( - function (token) { - t.equal(token.uid, 'xxx') - t.equal(token.id.toString('hex'), accountKeys.tokenId) - t.equal(token.key.toString('hex'), accountKeys.key) - t.equal(token.hmacKey.toString('hex'), accountKeys.hmacKey) - t.equal(token.xorKey.toString('hex'), accountKeys.xorKey) - t.end() - }, - function (err) { - t.fail(err) - t.end() - } - ) - } -) - -test( - '/account/keys', - function (t) { - KeyFetchToken.create('xxx') - .done( - function (token) { - t.equal(token.uid, 'xxx') - t.equal(token.id.toString('hex'), accountKeys.tokenId) - t.equal(token.key.toString('hex'), accountKeys.key) - t.equal(token.hmacKey.toString('hex'), accountKeys.hmacKey) - t.equal(token.xorKey.toString('hex'), accountKeys.xorKey) - - var b = token.bundle(accountKeys.kA, accountKeys.wrapKb) - t.equal(b.toString('hex'), accountKeys.ciphertext + accountKeys.hmac) - t.end() - }, - function (err) { - t.fail(err) - t.end() - } - ) - } -) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/forgot_password_token_tests.js b/test/run/forgot_password_token_tests.js index 02667f55..38592730 100644 --- a/test/run/forgot_password_token_tests.js +++ b/test/run/forgot_password_token_tests.js @@ -1,17 +1,11 @@ var test = require('tap').test var crypto = require('crypto') -var P = require('p-promise') -var config = require('../../config').root() var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) var sends = 0 -var mailer = { - sendRecoveryCode: function () { sends++; return P(null) } -} -var models = require('../../models')(log, config, dbs, mailer) -var ForgotPasswordToken = models.tokens.ForgotPasswordToken +var tokens = require('../../tokens')(log) +var ForgotPasswordToken = tokens.ForgotPasswordToken var email = Buffer('test@example.com').toString('hex') @@ -35,74 +29,20 @@ test( } ) -test( - 'sendRecoveryCode calls the mailer', - function (t) { - ForgotPasswordToken.create('xxx', email) - .then( - function (x) { - return x.sendRecoveryCode() - } - ) - .done( - function () { - t.equal(sends, 1, 'mail sent') - t.end() - } - ) - } -) - test( 'failAttempt decrements `tries`', function (t) { ForgotPasswordToken.create('xxx', email) - .then( + .done( function (x) { t.equal(x.tries, 3) - return x.failAttempt() - } - ) - .done( - function (x) { + t.equal(x.failAttempt(), false) t.equal(x.tries, 2) + t.equal(x.failAttempt(), false) + t.equal(x.tries, 1) + t.equal(x.failAttempt(), true) t.end() } ) } ) - -test( - 'failAttempt deletes the token if out of tries', - function (t) { - var tokenId = null - ForgotPasswordToken.create('xxx', email) - .then( - function (x) { - tokenId = x.id - x.tries = 1 - return x.failAttempt() - } - ) - .then( - function () { - return ForgotPasswordToken.get(tokenId) - } - ) - .done( - function (x) { - t.equal(x, null) - t.end() - } - ) - } -) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/key_fetch_token_tests.js b/test/run/key_fetch_token_tests.js deleted file mode 100644 index aec5e498..00000000 --- a/test/run/key_fetch_token_tests.js +++ /dev/null @@ -1,47 +0,0 @@ -var test = require('tap').test -var crypto = require('crypto') -var P = require('p-promise') -var config = require('../../config').root() -var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) - -var mailer = { - sendVerifyCode: function () { return P(null) } -} - -var models = require('../../models')(log, config, dbs, mailer) -var KeyFetchToken = models.tokens.KeyFetchToken - -test( - 'bundle / unbundle works', - function (t) { - function end() { t.end() } - KeyFetchToken.create('xxx') - .then( - function (x) { - var kA = crypto.randomBytes(32).toString('hex') - var wrapKb = crypto.randomBytes(32).toString('hex') - var b = x.bundle(kA, wrapKb) - var ub = x.unbundle(b) - t.equal(ub.kA, kA) - t.equal(ub.wrapKb, wrapKb) - return x - } - ) - .then( - function (x) { - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/recovery_email_tests.js b/test/run/recovery_email_tests.js deleted file mode 100644 index 9640ffbb..00000000 --- a/test/run/recovery_email_tests.js +++ /dev/null @@ -1,137 +0,0 @@ -var test = require('tap').test -var crypto = require('crypto') -var P = require('p-promise') -var config = require('../../config').root() -var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) - -const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/ - -var sends = 0 -var mailer = { - sendVerifyCode: function () { sends++; return P(null) } -} - -var models = require('../../models')(log, config, dbs, mailer) -var RecoveryEmail = models.RecoveryEmail - -var email = Buffer('me@example.com').toString('hex') - -test( - 'RecoveryEmail.create generates a random 32 byte code as a hex string', - function (t) { - function end() { t.end() } - RecoveryEmail.create('xxx', email, true) - .then( - function (x) { - t.equal(x.code.length, 8) - t.equal(HEX_STRING.test(x.code), true) - return x - } - ) - .then( - function (x) { - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'RecoveryEmail.create calls mailer.sendVerifyCode', - function (t) { - sends = 0 - function end() { t.end() } - RecoveryEmail.create('xxx', email, true) - .then( - function (x) { - t.equal(sends, 1) - sends = 0 - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'recoveryEmail.verify sets verified to true if the codes match', - function (t) { - function end() { t.end() } - RecoveryEmail.create('xxx', email, true) - .then( - function (x) { - t.equal(x.verified, false) - var c = x.code - return x.verify(c) - } - ) - .then( - function (x) { - t.equal(x.verified, true) - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'recoveryEmail.verify does not set verified if codes do not match', - function (t) { - function end() { t.end() } - RecoveryEmail.create('xxx', email, true) - .then( - function (x) { - t.equal(x.verified, false) - var c = crypto.randomBytes(32).toString('hex') - return x.verify(c) - } - ) - .then( - function (x) { - t.equal(x.verified, false) - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'recoveryEmail.verify will not unset the verified flag from true to false', - function (t) { - function end() { t.end() } - RecoveryEmail.create('xxx', email, true) - .then( - function (x) { - t.equal(x.verified, false) - var c = x.code - return x.verify(c) - } - ) - .then( - function (x) { - t.equal(x.verified, true) - return x.verify('bad1') - } - ) - .then( - function (x) { - t.equal(x.verified, true) - return x.del() - } - ) - .done(end, end) - } -) - -test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } -) diff --git a/test/run/srp_session_tests.js b/test/run/srp_session_tests.js index 5728fbc0..89f59021 100644 --- a/test/run/srp_session_tests.js +++ b/test/run/srp_session_tests.js @@ -3,15 +3,21 @@ var P = require('p-promise') var srp = require('srp') var config = require('../../config').root() var log = { trace: function() {} } -var dbs = require('../../kv')(config, log) -var mailer = { - sendVerifyCode: function () { return P(null) } -} +var Token = require('../../tokens')(log) +var DB = require('../../db/heap')( + log, + Token.error, + Token.AuthToken, + Token.SessionToken, + Token.KeyFetchToken, + Token.AccountResetToken, + Token.SrpToken, + Token.ForgotPasswordToken +) +var db = new DB() -var models = require('../../models')(log, config, dbs, mailer) -var Account = models.Account -var SrpSession = models.SrpSession +var SrpToken = Token.SrpToken var alice = { uid: 'xxx', @@ -34,14 +40,14 @@ alice.srp.verifier = srp.getv( 'sha256' ).toString('hex') -Account.create(alice) +db.createAccount(alice) .done( function (a) { test( 'create login session works', function (t) { - SrpSession.create(a) + SrpToken.create(a) .done( function (s) { t.equal(s.uid, a.uid) @@ -57,17 +63,17 @@ Account.create(alice) function (t) { var session = null var K = null - SrpSession.create(a) + SrpToken.create(a) .then( function (s) { session = s - return SrpSession.client2(s.clientData(), alice.email, alice.password) + return SrpToken.client2(s.clientData(), alice.email, alice.password) } ) .then( function (x) { K = x.K - return SrpSession.finish(session.id, x.A, x.M) + return session.finish(x.A, x.M) } ) .done( @@ -78,14 +84,5 @@ Account.create(alice) ) } ) - - test( - 'teardown', - function (t) { - dbs.cache.close() - dbs.store.close() - t.end() - } - ) } ) diff --git a/tokens/forgot_password_token.js b/tokens/forgot_password_token.js index b5a19431..4f23bb7e 100644 --- a/tokens/forgot_password_token.js +++ b/tokens/forgot_password_token.js @@ -56,5 +56,17 @@ module.exports = function (log, inherits, Token, crypto) { ) } + ForgotPasswordToken.prototype.ttl = function () { + return Math.max( + Math.ceil((LIFETIME - (Date.now() - this.created)) / 1000), + 0 + ) + } + + ForgotPasswordToken.prototype.failAttempt = function () { + this.tries-- + return this.tries < 1 + } + return ForgotPasswordToken } diff --git a/tokens/srp_token.js b/tokens/srp_token.js index 7e0ece01..77f83768 100644 --- a/tokens/srp_token.js +++ b/tokens/srp_token.js @@ -81,6 +81,18 @@ module.exports = function (log, P, uuid, srp, error) { return this } + SrpToken.prototype.clientData = function () { + return { + srpToken: this.id, + passwordStretching: this.passwordStretching, + srp: { + type: 'SRP-6a/SHA256/2048/v1', + salt: this.s, + B: this.B.toString('hex') + } + } + } + SrpToken.client2 = function (session, email, password) { return srpGenKey() .then(