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

feat(totp): Add initial totp session verification logic #309

Merged
merged 1 commit into from
Feb 27, 2018

Conversation

vbudhram
Copy link
Contributor

@vbudhram vbudhram commented Feb 21, 2018

This PR is the next stage for TOTP tokens. It adds the ability to verify TOTP tokens and sessions.

Fixes #301

@vbudhram vbudhram added this to the FxA-143: MFA milestone Feb 21, 2018
@vbudhram vbudhram self-assigned this Feb 21, 2018
@ghost ghost added the waffle:active label Feb 21, 2018
@vbudhram vbudhram force-pushed the feature.totp-verification branch 5 times, most recently from ebfb75f to 848838c Compare February 26, 2018 14:56
@@ -125,6 +126,7 @@ function createServer(db) {

api.get('/keyFetchToken/:id/verified', withIdAndBody(db.keyFetchTokenWithVerificationStatus))
api.post('/tokens/:id/verify', withIdAndBody(db.verifyTokens))
api.post('/tokens/:id/verifyWith', withIdAndBody(db.verifyTokensWithMethod))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this endpoint name to /tokens/:id/verifyWith and db to verifyTokensWithMethod over verifySessionWithMethod. This made sense because it verifies more than session tokens. Open to other names as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea maybe verifyWith --> verifyWithMethod to make it verbose

@@ -1524,8 +1524,8 @@ module.exports = function(cfg, makeServer) {
.spread((sessionTokenResp, keyFetchTokenResp) => {
respOk(sessionTokenResp)
respOk(keyFetchTokenResp)
const sessionToken = sessionTokenResp
const keyFetchToken = keyFetchTokenResp
const sessionToken = sessionTokenResp.obj
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but noticed that this wasn't testing against the correct response object.

@@ -38,7 +38,7 @@ var DEVICE_FIELDS = [
'callbackIsExpired'
]

var SESSION_FIELDS = [
const SESSION_DEVICE_FIELDS = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated change that confused me while working on this PR. These are session device fields that are returned when updating/retrieving devices.

SELECT @updateCount;
END;

CREATE PROCEDURE `sessionWithDevice_12` (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The procedures below have been updated to return the mustVerify property of session tokens from either the session table or unverified tokens table.

@vbudhram
Copy link
Contributor Author

@mozilla/fxa-devs r?

lib/db/mem.js Outdated
item.mustVerify = sessionTokens[id].mustVerify
} else {
item.mustVerify = unverifiedTokens[id].mustVerify
}
Copy link
Contributor

@philbooth philbooth Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code confused me initially. Given that item.mustVerify is already initialised to sessionTokens[id].mustVerify || null on line 546, could it be simplified to a simple single if (! item.mustVerify) { condition?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/simple/single/

@@ -1761,7 +1761,7 @@ module.exports = function (config, DB) {
})
.then((session) => {
// Returns verified session
assert.equal(session.mustVerify, null, 'mustVerify is not set')
assert.equal(!! session.mustVerify, false, 'mustVerify is false')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purely out of curiousity, do we actually need to keep using !! every time we test these properties?

assert.equal already uses == under the hood, so presumably null coerces to false anyway. And true is always true. Do any of them fail without the !!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this would break some tests because the mysql column is using TINYINT(1) instead of BOOLEAN. I don't believe there is any harm in using the boolean variable since it will return true/false instead of 0,1 which needed to be coerced. Will update!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this would break some tests because the mysql column is using TINYINT(1) instead of BOOLEAN

Turns that that was not correct and tests do pass. The only other explanation I have is that it was needed when we were using TAP. In either case, they have been updated.

)
BEGIN

UPDATE `totp` SET verified = verifiedArg, enable = enableArg WHERE uid = uidArg;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pedantry: Looks like 2-space indentation elsewhere...

Copy link
Contributor

@philbooth philbooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

docs/DB_API.md Outdated
The uid of the owning account
* `tokenData`
* `verified`: Boolean whether TOTP token has been verified
* `enable`: Boolean whether TOTP token is enabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only occurred to me while reviewing the auth server PR. Should it be enabled rather than enable? That way sounds more like a boolean state and less like an instruction to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes, looks I am not consistent with these fields/docs. I will update to use enabled.

// Currently, users can only update the verified and enable flags.
// Updating shared secret and epoch will break clients.
totpToken.verified = token.verified
totpToken.enabled = token.enabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...oh and here you are setting enabled, not enable. Is one of them wrong?

-- Add columns `verified` and `enable` to help manage TOTP token state
ALTER TABLE `totp`
ADD COLUMN `verified` TINYINT(1) DEFAULT FALSE,
ADD COLUMN `enable` TINYINT(1) DEFAULT TRUE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...yeah (he said, continuing the conversation with himself), I think enabled would be better actually, it seems more consistent with other column names.

@vbudhram vbudhram merged commit ee19e1b into master Feb 27, 2018
@vbudhram
Copy link
Contributor Author

@philbooth Thank you!

@@ -97,6 +97,7 @@ function createServer(db) {
return db.deleteEmail(req.params.id, req.params.email)
})
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side note in this file, above we have db.deleteEmail(req.params.id, req.params.email) but all methods below use Buffer( on the email. might be worth filing a clean up issue, I'm not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, looks like this might have been missed on the original secondary email implementation. Opened issue #311.

@@ -91,13 +91,15 @@ The following datatypes are used throughout this document:
* forgotPasswordVerified : `POST /passwordForgotToken/:id/verified`
* Unverified tokens:
* verifyTokens : `POST /tokens/:tokenVerificationId/verify`
* verifyTokensWithMethod : `POST /tokens/:tokenId/verifyWith`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this new doc uses tokenId, but above it is tokenVerificationId, both are {:id} in the route definition. Intentional ?

Copy link
Contributor Author

@vbudhram vbudhram Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea this was intentional. The store procedure will look up the tokenVerificationId directly from the tokenId. I used id since it allowed me to reuse the helper function withIdAndBody. I don't mind updating if it makes it more clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool 👍

@@ -125,6 +126,7 @@ function createServer(db) {

api.get('/keyFetchToken/:id/verified', withIdAndBody(db.keyFetchTokenWithVerificationStatus))
api.post('/tokens/:id/verify', withIdAndBody(db.verifyTokens))
api.post('/tokens/:id/verifyWith', withIdAndBody(db.verifyTokensWithMethod))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea maybe verifyWith --> verifyWithMethod to make it verbose

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants