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

Commit ee19e1b

Browse files
authored
feat(totp): Add initial totp session verification logic (#309), r=@philbooth
1 parent da2e9ef commit ee19e1b

File tree

13 files changed

+684
-34
lines changed

13 files changed

+684
-34
lines changed

db-server/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ function createServer(db) {
9797
return db.deleteEmail(req.params.id, req.params.email)
9898
})
9999
)
100+
100101
api.get('/email/:email',
101102
op(function (req) {
102103
return db.getSecondaryEmail(Buffer(req.params.email, 'hex'))
@@ -125,6 +126,7 @@ function createServer(db) {
125126

126127
api.get('/keyFetchToken/:id/verified', withIdAndBody(db.keyFetchTokenWithVerificationStatus))
127128
api.post('/tokens/:id/verify', withIdAndBody(db.verifyTokens))
129+
api.post('/tokens/:id/verifyWith', withIdAndBody(db.verifyTokensWithMethod))
128130
api.post('/tokens/:code/verifyCode', withParamsAndBody(db.verifyTokenCode))
129131

130132
api.get('/accountResetToken/:id', withIdAndBody(db.accountResetToken))
@@ -156,6 +158,7 @@ function createServer(db) {
156158
api.get('/totp/:id', withIdAndBody(db.totpToken))
157159
api.del('/totp/:id', withIdAndBody(db.deleteTotpToken))
158160
api.put('/totp/:id', withIdAndBody(db.createTotpToken))
161+
api.post('/totp/:id/update', withIdAndBody(db.updateTotpToken))
159162

160163
api.get('/__heartbeat__', withIdAndBody(db.ping))
161164

db-server/lib/error.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ AppError.expiredTokenVerificationCode = function () {
7272
)
7373
}
7474

75+
AppError.invalidVerificationMethod = function () {
76+
return new AppError(
77+
{
78+
code: 400,
79+
error: 'Bad request',
80+
errno: 138,
81+
message: 'Invalid verification method'
82+
}
83+
)
84+
}
85+
7586
AppError.wrap = function (err) {
7687
return new AppError(
7788
{

db-server/test/backend/db_tests.js

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ module.exports = function (config, DB) {
353353
assert.deepEqual(token.emailCode, accountData.emailCode, 'token emailCode same as account emailCode')
354354
assert.equal(token.verifierSetAt, accountData.verifierSetAt, 'verifierSetAt is correct')
355355
assert.equal(token.accountCreatedAt, accountData.createdAt, 'accountCreatedAt is correct')
356-
assert.equal(!! token.mustVerify, !! sessionTokenData.mustVerify, 'mustVerify is set')
356+
assert.equal(token.mustVerify, sessionTokenData.mustVerify, 'mustVerify is set')
357357
assert.deepEqual(token.tokenVerificationId, sessionTokenData.tokenVerificationId, 'tokenVerificationId is set')
358358
})
359359
})
@@ -403,7 +403,7 @@ module.exports = function (config, DB) {
403403
assert.deepEqual(token.emailCode, accountData.emailCode, 'token emailCode same as account emailCode')
404404
assert.equal(token.verifierSetAt, accountData.verifierSetAt, 'verifierSetAt is correct')
405405
assert.equal(token.accountCreatedAt, accountData.createdAt, 'accountCreatedAt is correct')
406-
assert.equal(!! token.mustVerify, !! sessionTokenData.mustVerify, 'mustVerify is correct')
406+
assert.equal(token.mustVerify, sessionTokenData.mustVerify, 'mustVerify is correct')
407407
assert.deepEqual(token.tokenVerificationId, sessionTokenData.tokenVerificationId, 'tokenVerificationId is correct')
408408

409409
})
@@ -418,7 +418,7 @@ module.exports = function (config, DB) {
418418
return db.sessionToken(sessionTokenData.tokenId)
419419
})
420420
.then((token) => {
421-
assert.equal(!! token.mustVerify, !! sessionTokenData.mustVerify, 'mustVerify is correct')
421+
assert.equal(token.mustVerify, sessionTokenData.mustVerify, 'mustVerify is correct')
422422
assert.deepEqual(token.tokenVerificationId, sessionTokenData.tokenVerificationId, 'tokenVerificationId is correct')
423423
})
424424
})
@@ -432,7 +432,7 @@ module.exports = function (config, DB) {
432432
return db.sessionToken(sessionTokenData.tokenId)
433433
})
434434
.then((token) => {
435-
assert.equal(!! token.mustVerify, !! sessionTokenData.mustVerify, 'mustVerify is correct')
435+
assert.equal(token.mustVerify, sessionTokenData.mustVerify, 'mustVerify is correct')
436436
assert.deepEqual(token.tokenVerificationId, sessionTokenData.tokenVerificationId, 'tokenVerificationId is correct')
437437
})
438438
})
@@ -443,7 +443,7 @@ module.exports = function (config, DB) {
443443
return db.sessionToken(sessionTokenData.tokenId)
444444
}, assert.fail)
445445
.then((token) => {
446-
assert.equal(token.mustVerify, null, 'mustVerify is null')
446+
assert.equal(!! token.mustVerify, false, 'mustVerify is null')
447447
assert.equal(token.tokenVerificationId, null, 'tokenVerificationId is null')
448448
})
449449
})
@@ -1761,7 +1761,7 @@ module.exports = function (config, DB) {
17611761
})
17621762
.then((session) => {
17631763
// Returns verified session
1764-
assert.equal(session.mustVerify, null, 'mustVerify is not set')
1764+
assert.equal(!! session.mustVerify, false, 'mustVerify is false')
17651765
assert.equal(session.tokenVerificationId, null, 'tokenVerificationId is not set')
17661766
assert.equal(session.tokenVerificationCodeHash, null, 'tokenVerificationCodeHash is not set')
17671767
assert.equal(session.tokenVerificationCodeExpiresAt, null, 'tokenVerificationCodeExpiresAt is not set')
@@ -1835,6 +1835,8 @@ module.exports = function (config, DB) {
18351835
.then((token) => {
18361836
assert.equal(token.sharedSecret, sharedSecret, 'correct sharedSecret')
18371837
assert.equal(token.epoch, epoch, 'correct epoch')
1838+
assert.equal(token.verified, false, 'correct verified')
1839+
assert.equal(token.enabled, true, 'correct enabled')
18381840
})
18391841
})
18401842

@@ -1862,6 +1864,83 @@ module.exports = function (config, DB) {
18621864
})
18631865
})
18641866
})
1867+
1868+
it('should update totp token', () => {
1869+
return db.updateTotpToken(accountData.uid, {verified: true, enabled: true})
1870+
.then((result) => {
1871+
assert.ok(result)
1872+
return db.totpToken(accountData.uid)
1873+
.then((token) => {
1874+
assert.equal(token.sharedSecret, sharedSecret, 'correct sharedSecret')
1875+
assert.equal(token.epoch, epoch, 'correct epoch')
1876+
assert.equal(token.verified, true, 'correct verified')
1877+
assert.equal(token.enabled, true, 'correct enable')
1878+
})
1879+
})
1880+
})
1881+
1882+
it('should fail to update unknown totp token', () => {
1883+
return db.updateTotpToken(newUuid(), {verified: true, enabled: true})
1884+
.then(assert.fail, (err) => {
1885+
assert.equal(err.errno, 116, 'correct errno, not found')
1886+
})
1887+
})
1888+
})
1889+
1890+
describe('db.verifyTokensWithMethod', () => {
1891+
let account, sessionToken, tokenId
1892+
before(() => {
1893+
account = createAccount()
1894+
account.emailVerified = true
1895+
tokenId = hex32()
1896+
sessionToken = makeMockSessionToken(account.uid, false)
1897+
return db.createAccount(account.uid, account)
1898+
.then(() => db.createSessionToken(tokenId, sessionToken))
1899+
.then(() => db.sessionToken(tokenId))
1900+
.then((session) => {
1901+
// Returns unverified session
1902+
assert.equal(session.tokenVerificationId.toString('hex'), sessionToken.tokenVerificationId.toString('hex'), 'tokenVerificationId must match sessionToken')
1903+
assert.equal(session.verificationMethod, undefined, 'verificationMethod not set')
1904+
})
1905+
})
1906+
1907+
it('should fail to verify with unknown sessionId', () => {
1908+
const verifyOptions = {
1909+
verificationMethod: 'totp-2fa'
1910+
}
1911+
return db.verifyTokensWithMethod(hex32(), verifyOptions)
1912+
.then(assert.fail, (err) => {
1913+
assert.equal(err.errno, 116, 'correct errno, not found')
1914+
})
1915+
})
1916+
1917+
it('should fail to verify unknown verification method', () => {
1918+
const verifyOptions = {
1919+
verificationMethod: 'super-invalid-method'
1920+
}
1921+
return db.verifyTokensWithMethod(tokenId, verifyOptions)
1922+
.then(assert.fail, (err) => {
1923+
assert.equal(err.errno, 138, 'correct errno, invalid verification method')
1924+
})
1925+
})
1926+
1927+
it('should verify with verification method', () => {
1928+
const verifyOptions = {
1929+
verificationMethod: 'totp-2fa'
1930+
}
1931+
return db.verifyTokensWithMethod(tokenId, verifyOptions)
1932+
.then((res) => {
1933+
assert.ok(res)
1934+
1935+
// Ensure session really has been verified and correct methods set
1936+
return db.sessionToken(tokenId)
1937+
})
1938+
.then((session) => {
1939+
assert.equal(session.tokenVerificationId, undefined, 'tokenVerificationId must be undefined')
1940+
assert.equal(session.verificationMethod, 2, 'verificationMethod set')
1941+
assert.ok(session.verifiedAt, 'verifiedAt set')
1942+
})
1943+
})
18651944
})
18661945

18671946
after(() => db.close())

db-server/test/backend/remote.js

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ module.exports = function(cfg, makeServer) {
372372
assert.deepEqual(token.emailCode, user.account.emailCode, 'token emailCode same as account emailCode')
373373
assert(token.verifierSetAt, 'verifierSetAt is set to a truthy value')
374374
assert(token.accountCreatedAt > 0, 'accountCreatedAt is positive number')
375-
assert.equal(!! token.mustVerify, !! user.sessionToken.mustVerify, 'mustVerify is correct')
375+
assert.equal(token.mustVerify, user.sessionToken.mustVerify, 'mustVerify is correct')
376376
assert.equal(token.tokenVerificationId, user.sessionToken.tokenVerificationId, 'tokenVerificationId is correct')
377377

378378
// Create a verified session token
@@ -403,7 +403,7 @@ module.exports = function(cfg, makeServer) {
403403
assert.deepEqual(token.emailCode, verifiedUser.account.emailCode, 'token emailCode same as account emailCode')
404404
assert(token.verifierSetAt, 'verifierSetAt is set to a truthy value')
405405
assert(token.accountCreatedAt > 0, 'accountCreatedAt is positive number')
406-
assert.equal(token.mustVerify, null, 'mustVerify is null')
406+
assert.equal(token.mustVerify, true, 'mustVerify is true')
407407
assert.equal(token.tokenVerificationId, null, 'tokenVerificationId is null')
408408

409409
// Attempt to verify a non-existent session token
@@ -436,7 +436,7 @@ module.exports = function(cfg, makeServer) {
436436
return client.getThen('/sessionToken/' + user.sessionTokenId)
437437
})
438438
.then(function(r) {
439-
assert.equal(r.obj.mustVerify, null, 'mustVerify is null')
439+
assert.equal(!! r.obj.mustVerify, true, 'mustVerify is true')
440440
assert.equal(r.obj.tokenVerificationId, null, 'tokenVerificationId is null')
441441

442442
// Attempt to verify the session token again
@@ -1524,8 +1524,8 @@ module.exports = function(cfg, makeServer) {
15241524
.spread((sessionTokenResp, keyFetchTokenResp) => {
15251525
respOk(sessionTokenResp)
15261526
respOk(keyFetchTokenResp)
1527-
const sessionToken = sessionTokenResp
1528-
const keyFetchToken = keyFetchTokenResp
1527+
const sessionToken = sessionTokenResp.obj
1528+
const keyFetchToken = keyFetchTokenResp.obj
15291529
assert.equal(sessionToken.tokenVerificationId, null, 'tokenVerificationCodeHash not set')
15301530
assert.equal(sessionToken.tokenVerificationCodeHash, null, 'tokenVerificationCodeHash not set')
15311531
assert.equal(sessionToken.tokenVerificationCodeExpiresAt, null, 'tokenVerificationCodeExpiresAt not set')
@@ -1537,7 +1537,6 @@ module.exports = function(cfg, makeServer) {
15371537

15381538
describe('totp tokens', () => {
15391539
let user
1540-
15411540
beforeEach(() => {
15421541
user = fake.newUserDataHex()
15431542
return client.putThen('/account/' + user.accountId, user.account)
@@ -1554,6 +1553,8 @@ module.exports = function(cfg, makeServer) {
15541553
const result = r.obj
15551554
assert.equal(result.sharedSecret, user.totp.sharedSecret, 'sharedSecret set')
15561555
assert.equal(result.epoch, user.totp.epoch, 'epoch set')
1556+
assert.equal(result.verified, user.totp.verified, 'verified set')
1557+
assert.equal(result.enabled, user.totp.enabled, 'enabled set')
15571558
})
15581559
})
15591560

@@ -1565,6 +1566,57 @@ module.exports = function(cfg, makeServer) {
15651566
.then(assert.fail, (err) => testNotFound(err))
15661567
})
15671568
})
1569+
1570+
it('should update totp token', () => {
1571+
const totpOptions = {
1572+
verified: true,
1573+
enabled: true
1574+
}
1575+
return client.postThen('/totp/' + user.accountId + '/update', totpOptions)
1576+
.then((r) => {
1577+
respOkEmpty(r)
1578+
return client.getThen('/totp/' + user.accountId)
1579+
.then((r) => {
1580+
const result = r.obj
1581+
assert.equal(result.sharedSecret, user.totp.sharedSecret, 'sharedSecret set')
1582+
assert.equal(result.epoch, user.totp.epoch, 'epoch set')
1583+
assert.equal(result.verified, totpOptions.verified, 'verified set')
1584+
assert.equal(result.enabled, user.totp.enabled, 'enable set')
1585+
})
1586+
})
1587+
})
1588+
})
1589+
1590+
describe('should set session verification method', () => {
1591+
let user
1592+
beforeEach(() => {
1593+
user = fake.newUserDataHex()
1594+
return client.putThen('/account/' + user.accountId, user.account)
1595+
.then((r) => {
1596+
respOkEmpty(r)
1597+
return client.putThen('/totp/' + user.accountId, user.totp)
1598+
})
1599+
.then((r) => respOkEmpty(r))
1600+
.then(() => client.putThen('/sessionToken/' + user.sessionTokenId, user.sessionToken))
1601+
.then((res) => respOkEmpty(res))
1602+
})
1603+
1604+
it('set session verification method', () => {
1605+
const verifyOptions = {
1606+
verificationMethod: 'totp-2fa',
1607+
}
1608+
return client.postThen('/tokens/' + user.sessionTokenId + '/verifyWith', verifyOptions)
1609+
.then((res) => {
1610+
respOkEmpty(res)
1611+
return client.getThen('/sessionToken/' + user.sessionTokenId + '/device')
1612+
})
1613+
.then((sessionToken) => {
1614+
sessionToken = sessionToken.obj
1615+
assert.equal(sessionToken.verificationMethod, 2, 'verificationMethod set')
1616+
assert.ok(sessionToken.verifiedAt, 'verifiedAt set')
1617+
})
1618+
})
1619+
15681620
})
15691621

15701622
after(() => server.close())

db-server/test/fake.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ module.exports.newUserDataHex = function() {
128128

129129
data.totp = {
130130
sharedSecret: hex(10),
131-
epoch: 0
131+
epoch: 0,
132+
verified: false,
133+
enabled: true
132134
}
133135

134136
return data

0 commit comments

Comments
 (0)