@@ -11,6 +11,7 @@ const P = require('../promise')
1111
1212const patch = require ( './patch' )
1313const dbUtil = require ( './util' )
14+ const config = require ( '../../config' )
1415
1516const REQUIRED_CHARSET = 'UTF8MB4_BIN'
1617const DATABASE_NAME = require ( '../constants' ) . DATABASE_NAME
@@ -32,6 +33,10 @@ const ER_LOCK_ABORTED = 1689
3233const ER_DELETE_PRIMARY_EMAIL = 2100
3334const ER_EXPIRED_TOKEN_VERIFICATION_CODE = 2101
3435
36+
37+ const RECOVERY_CODE_KEYSPACE = config . recoveryCodes . keyspace
38+ const RECOVERY_CODE_LENGTH = config . recoveryCodes . length
39+
3540module . exports = function ( log , error ) {
3641
3742 var LOCK_ERRNOS = [
@@ -1451,34 +1456,30 @@ module.exports = function (log, error) {
14511456 }
14521457
14531458 const DELETE_RECOVERY_CODES = 'CALL deleteRecoveryCodes_1(?)'
1454- const INSERT_RECOVERY_CODE = 'CALL createRecoveryCode_1( ?, ?)'
1459+ const INSERT_RECOVERY_CODE = 'CALL createRecoveryCode_2(?, ?, ?)'
14551460 MySql . prototype . replaceRecoveryCodes = function ( uid , count ) {
14561461
14571462 // Because of the hashing requirements the process of replacing
14581463 // recovery codes is done is two separate procedures. First one
14591464 // deletes all current codes and the second one inserts the
14601465 // hashed randomly generated codes.
1461- return dbUtil . generateRecoveryCodes ( count )
1462- . then ( ( codeList ) => {
1466+ return dbUtil . generateRecoveryCodes ( count , RECOVERY_CODE_KEYSPACE , RECOVERY_CODE_LENGTH )
1467+ . then ( ( codes ) => {
14631468 return this . read ( DELETE_RECOVERY_CODES , [ uid ] )
1464- . then ( ( ) => {
1465- if ( codeList <= 0 ) {
1466- return P . resolve ( [ ] )
1467- }
1468-
1469+ . then ( ( ) => codes . map ( ( code ) => dbUtil . createHashScrypt ( code ) ) )
1470+ . all ( )
1471+ . then ( ( items ) => {
14691472 const queries = [ ]
1470- codeList . forEach ( ( code ) => {
1473+ items . forEach ( ( item ) => {
14711474 queries . push ( {
14721475 sql : INSERT_RECOVERY_CODE ,
1473- params : [ uid , dbUtil . createHashSha512 ( code ) ]
1476+ params : [ uid , item . hash , item . salt ]
14741477 } )
14751478 } )
14761479
14771480 return this . writeMultiple ( queries )
14781481 } )
1479- . then ( ( ) => {
1480- return P . resolve ( codeList )
1481- } )
1482+ . then ( ( ) => codes )
14821483 . catch ( ( err ) => {
14831484 if ( err . errno === 1643 ) {
14841485 throw error . notFound ( )
@@ -1490,9 +1491,36 @@ module.exports = function (log, error) {
14901491 } )
14911492 }
14921493
1493- const CONSUME_RECOVERY_CODE = 'CALL consumeRecoveryCode_1(?, ?)'
1494- MySql . prototype . consumeRecoveryCode = function ( uid , code ) {
1495- return this . readFirstResult ( CONSUME_RECOVERY_CODE , [ uid , dbUtil . createHashSha512 ( code ) ] )
1494+ const CONSUME_RECOVERY_CODE = 'CALL consumeRecoveryCode_2(?, ?)'
1495+ const RECOVERY_CODES = 'CALL recoveryCodes_1(?)'
1496+ MySql . prototype . consumeRecoveryCode = function ( uid , submittedCode ) {
1497+ // Consuming a recovery code is done in a two step process because
1498+ // the stored scrypt hash will need to be calculated against the recovery
1499+ // code salt.
1500+ return this . readOneFromFirstResult ( RECOVERY_CODES , [ uid ] )
1501+ . then ( ( results ) => {
1502+ // Throw if this user has no recovery codes
1503+ if ( results . length === 0 ) {
1504+ throw error . notFound ( )
1505+ }
1506+
1507+ const compareResults = results . map ( ( code ) => {
1508+ return dbUtil . compareHashScrypt ( submittedCode , code . codeHash , code . salt )
1509+ . then ( ( equals ) => {
1510+ return { code, equals}
1511+ } )
1512+ } )
1513+
1514+ // Filter only matching code
1515+ return P . filter ( compareResults , result => result . equals )
1516+ . map ( ( result ) => result . code )
1517+ } )
1518+ . then ( ( result ) => {
1519+ if ( result . length === 0 ) {
1520+ throw error . notFound ( )
1521+ }
1522+ return this . readFirstResult ( CONSUME_RECOVERY_CODE , [ uid , result [ 0 ] . codeHash ] )
1523+ } )
14961524 . then ( ( result ) => {
14971525 return P . resolve ( {
14981526 remaining : result . count
0 commit comments