feat(codes): add support for verifying token short code#287
feat(codes): add support for verifying token short code#287
Conversation
2bc32ba to
bb4a7d7
Compare
aa15106 to
c867338
Compare
docs/API.md
Outdated
|
|
||
| * sessionToken : data, uid, createdAt, uaBrowser, uaBrowserVersion, uaOS, uaOSVersion, uaDeviceType, | ||
| uaFormFactor, mustVerify, tokenVerificationId | ||
| uaFormFactor, mustVerify, tokenVerificationId, shortCode, shortCodeExpiresAt |
There was a problem hiding this comment.
naming nit: I wonder if we should include the word "verification" somewhere in the name of this thing, to make it more clear what it's for.
There was a problem hiding this comment.
(that may be less of a concern if it's only for internal use by this repo, and is return returned to the auth-server)
fxa-auth-db-server/index.js
Outdated
| api.get('/sessionToken/:id/verified', withIdAndBody(db.sessionTokenWithVerificationStatus)) | ||
| api.get('/keyFetchToken/:id/verified', withIdAndBody(db.keyFetchTokenWithVerificationStatus)) | ||
| api.post('/tokens/:id/verify', withIdAndBody(db.verifyTokens)) | ||
| api.post('/tokens/:shortCode/verifyShortCode', op(req => db.verifyTokensShortCode(req.params.shortCode, { uid: req.body.uid } ))) |
There was a problem hiding this comment.
Is the withIdAndBody helper not usable here? It seems a little odd to have this route as the only one that's not using the helper.
There was a problem hiding this comment.
Yea it wasn't useable. Using that would create a buffer instead of sending the string. This was based off how signinCodes do it.
| }) | ||
| }) | ||
|
|
||
| describe('db.verifyTokensShortCodes', () => { |
There was a problem hiding this comment.
nit: "ShortCodes" plural in the name here doesn't seem quite right, should it be singular?
lib/db/schema/patch-067-068.sql
Outdated
| ALGORITHM = INPLACE LOCK=NONE; | ||
|
|
||
| ALTER TABLE `unverifiedTokens` | ||
| ADD COLUMN `shortCodeExpiresAt` BIGINT UNSIGNED DEFAULT NULL, |
There was a problem hiding this comment.
You should be able to add both of these columns in a single ALTER TABLE statement, which will be easier for ops to run.
|
|
||
| -- ALTER TABLE `unverifiedTokens` | ||
| -- DROP COLUMN `shortCodeExpiresAt` BIGINT UNSIGNED, | ||
| -- ALGORITHM = INPLACE, LOCK = NONE; |
There was a problem hiding this comment.
Should we also drop the unverifiedTokens_shortCodeHash index here, potentially before removing the columns in question?
| IF @updateCount > 0 THEN | ||
| SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 2101, MESSAGE_TEXT = 'Expired shortcode.'; | ||
| END IF; | ||
| END IF; |
There was a problem hiding this comment.
If we expect the common case to be using a valid, unexpired shortCode, then we should optimize for doing a single read in that case. I think we can achieve that by having the first query include the expiry check, like:
SELECT tokenVerificationId INTO @tokenVerificationId FROM unverifiedTokens WHERE uid = uidArg AND shortCodeHash = shortCodeHashArg and shortCodeExpiresAt >= (UNIX_TIMESTAMP(NOW(3)) * 1000)
And then, if we didn't find one, we can whether it exists but is expired with a separate query like above.
docs/API.md
Outdated
| d.callbackAuthKey AS deviceCallbackAuthKey, | ||
| d.callbackIsExpired AS deviceCallbackIsExpired, | ||
| ut.mustVerify, ut.tokenVerificationId | ||
| ut.mustVerify, ut.tokenVerificationId, ut.shortCode, ut.shortCodeExpiresAt |
There was a problem hiding this comment.
Likewise here, this returns the shortCodeHash.
lib/db/schema/patch-067-068.sql
Outdated
| SET @tokenVerificationId = NULL; | ||
| SELECT tokenVerificationId INTO @tokenVerificationId FROM unverifiedTokens WHERE uid = uidArg AND shortCodeHash = shortCodeHashArg; | ||
|
|
||
| IF @tokenVerificationId IS NOT NULL THEN |
There was a problem hiding this comment.
What happens if @tokenVerificationId is NULL here? IIUC control flow will fall through and execute the UPDATE securityEvents below, which will set verified = true for all security events where tokenVerificationId is NULL. I think that's OK because such events will already have verified=true anyway, but it might be safer to bail out early if we don't find any matching tokenVerificationId.
| IN `uidArg` BINARY(16) | ||
| ) | ||
| BEGIN | ||
| DECLARE EXIT HANDLER FOR SQLEXCEPTION |
There was a problem hiding this comment.
This exit-handler logic is only necessary if you're going to use a transaction, which actually it might be worth doing here anyway because we're modifying multiple tables.
|
|
||
| CREATE INDEX `unverifiedTokens_shortCodeHash` | ||
| ON `unverifiedTokens` (`shortCodeHash`) | ||
| ALGORITHM = INPLACE LOCK=NONE; |
There was a problem hiding this comment.
I wonder if it's worth making this a unique index on (shortCodeHash, uid) in order to prevent the accidental creation of duplicate shortcodes for different login sessions by the same user. That should never happen in theory, but sometimes it's worth ensuring that theory and practice will match :-)
664dd10 to
6851f4e
Compare
|
@rfk Thanks for review! Updated based on feedback
Review at your liesure. |
lib/db/mem.js
Outdated
| mustVerify: !! sessionToken.mustVerify, | ||
| uid: sessionToken.uid | ||
| tokenVerificationId: sessionToken.tokenVerificationId, | ||
| tokenVerificationCodeHash: sessionToken.tokenVerificationCode, |
There was a problem hiding this comment.
IIUC, this is not actually hashing the tokenVerificationCode, so the field name here is wrong. Should it hash it, for consistency with the mysql version?
lib/db/mem.js
Outdated
| } | ||
|
|
||
| // Is code expired? | ||
| if (t.tokenVerificationCodeHash.toString('hex') === tokenVerificationCode) { |
There was a problem hiding this comment.
Likewise here, it looks slightly off the compare a field named tokenVerificationCodeHash to a variable named tokenVerificationCode, we should either do the hashing or rename the field.
rfk
left a comment
There was a problem hiding this comment.
A couple of nits, but this LGTM!
lib/db/schema/patch-067-068.sql
Outdated
| ADD COLUMN `tokenVerificationCodeExpiresAt` BIGINT UNSIGNED DEFAULT NULL, | ||
| ALGORITHM = INPLACE, LOCK = NONE; | ||
|
|
||
| CREATE INDEX `unverifiedTokens_tokenVerificationCodeHash_uid` |
There was a problem hiding this comment.
Did you intend to make this a CREATE UNIQUE INDEX? If not, I don't think it needs to contain the uid column at all.
lib/db/schema/patch-067-068.sql
Outdated
| AND tokenVerificationCodeExpiresAt >= (UNIX_TIMESTAMP(NOW(3)) * 1000); | ||
|
|
||
| IF @tokenVerificationId IS NULL THEN | ||
| SET @updateCount = 0; |
There was a problem hiding this comment.
tiny nit: I don't really understand what the name "updateCount" is supposed to mean here; maybe "expiredCount" or something to make it clearer?
fxa-auth-db-server/index.js
Outdated
| api.get('/sessionToken/:id/verified', withIdAndBody(db.sessionTokenWithVerificationStatus)) | ||
| api.get('/keyFetchToken/:id/verified', withIdAndBody(db.keyFetchTokenWithVerificationStatus)) | ||
| api.post('/tokens/:id/verify', withIdAndBody(db.verifyTokens)) | ||
| api.post('/tokens/:code/verifyCode', op(req => db.verifyTokenCode(req.params.code, { uid: req.body.uid } ))) |
There was a problem hiding this comment.
should this use something like withParams ?
lib/db/mem.js
Outdated
|
|
||
| Memory.prototype.verifyTokenCode = function (tokenVerificationCode, accountData) { | ||
| const uid = accountData.uid.toString('hex') | ||
| tokenVerificationCode = tokenVerificationCode.toString('hex') |
There was a problem hiding this comment.
nit: rename this to tokenVerificationCodeHex ?
lib/db/mysql.js
Outdated
| MySql.prototype.verifyTokenCode = function (tokenVerificationCode, accountData) { | ||
| return this.readFirstResult(VERIFY_TOKEN_CODE, [createHash(tokenVerificationCode), accountData.uid]) | ||
| .then(function (result) { | ||
| if (result['@rowCount'] === 0) { |
There was a problem hiding this comment.
We don't use this @rowCount check anywhere else, is this the only way we can find out if nothing was found?
There was a problem hiding this comment.
Unfortunately, this was the only way I could find that would return the row count from within a transaction. That might also explain why the affectedRows property was not returned.
| @@ -0,0 +1,165 @@ | |||
| ALTER TABLE `unverifiedTokens` | |||
There was a problem hiding this comment.
is this file missing the boilerplate for SET NAMES, via https://github.com/mozilla/fxa-auth-db-mysql/blob/master/scripts/migration.sh#L35 ?
lib/db/schema/patch-067-068.sql
Outdated
| WHERE t.tokenId = tokenIdArg; | ||
| END; | ||
|
|
||
| UPDATE dbMetadata SET value = '68' WHERE name = 'schema-patch-level'; No newline at end of file |
d0dc563 to
238e6e3
Compare
|
Made changes @vladikoff mind r? |
lib/db/util.js
Outdated
|
|
||
| createHash () { | ||
| const hash = crypto.createHash('sha256') | ||
| // Use ...rest operator and forEach when we're on node 6 |
There was a problem hiding this comment.
I thought we were on node 6 already?
There was a problem hiding this comment.
Yes, I believe we are comfortably on node 6; if this repo is still testing node 4 support, feel free to drop it
|
Thanks @vbudhram !! |
This PR exposes new APIs to create and verify sessionTokens using a short code. Couple notes
verifyTokensShortCodeused to explicitly verify shortCodes.Connects with mozilla/fxa-auth-server#2218
Feature doc: https://docs.google.com/document/d/1xQtCGBJ9XZuF1q9faZV3YIiOnxomQMHKiTP62YSf3v8/edit
@mozilla/fxa-devs r?