From 1b910f0af9e479eb46988585f39147fd96f0e51f Mon Sep 17 00:00:00 2001 From: hritvi Date: Mon, 25 Mar 2019 19:25:37 +0530 Subject: [PATCH] refactor(fxa-auth-server): Added semicolons(semi rule) --- .eslintrc | 3 +- Gruntfile.js | 14 +- bin/email_notifications.js | 38 +- bin/key_server.js | 140 +- bin/profile_server_messaging.js | 24 +- config/index.js | 70 +- config/popular-email-domains.js | 6 +- config/supportedLanguages.js | 4 +- grunttasks/bump.js | 6 +- grunttasks/changelog.js | 8 +- grunttasks/copyright.js | 6 +- grunttasks/eslint.js | 8 +- grunttasks/l10n-extract.js | 32 +- grunttasks/templates.js | 8 +- grunttasks/version.js | 8 +- lib/authMethods.js | 38 +- lib/backendService.js | 88 +- lib/bounces.js | 50 +- lib/cache.js | 44 +- lib/crypto/butil.js | 32 +- lib/crypto/hkdf.js | 28 +- lib/crypto/password.js | 68 +- lib/crypto/pbkdf2.js | 16 +- lib/crypto/random.js | 66 +- lib/crypto/scrypt.js | 46 +- lib/customs.js | 80 +- lib/db.js | 1088 +++++------ lib/devices.js | 110 +- lib/email/bounces.js | 100 +- lib/email/delivery.js | 42 +- lib/email/notifications.js | 50 +- lib/email/utils/helpers.js | 134 +- lib/error.js | 374 ++-- lib/features.js | 42 +- lib/geodb.js | 42 +- lib/log.js | 148 +- lib/metrics/amplitude.js | 64 +- lib/metrics/context.js | 172 +- lib/metrics/events.js | 112 +- lib/newrelic.js | 10 +- lib/notifier.js | 42 +- lib/oauthdb/check-refresh-token.js | 10 +- lib/oauthdb/client-info.js | 10 +- lib/oauthdb/index.js | 34 +- lib/oauthdb/revoke-refresh-token-by-id.js | 8 +- lib/oauthdb/scoped-key-data.js | 10 +- lib/oauthdb/utils.js | 34 +- lib/pool.js | 92 +- lib/profile/updates.js | 22 +- lib/promise.js | 4 +- lib/push.js | 194 +- lib/pushbox.js | 66 +- lib/redis.js | 24 +- lib/routes/account.js | 562 +++--- lib/routes/defaults.js | 72 +- lib/routes/devices-and-sessions.js | 264 +-- lib/routes/emails.js | 354 ++-- lib/routes/idp.js | 32 +- lib/routes/index.js | 70 +- lib/routes/oauth.js | 16 +- lib/routes/password.js | 264 +-- lib/routes/recovery-codes.js | 94 +- lib/routes/recovery-key.js | 90 +- lib/routes/session.js | 152 +- lib/routes/sign.js | 82 +- lib/routes/signin-codes.js | 32 +- lib/routes/sms.js | 88 +- lib/routes/token-codes.js | 44 +- lib/routes/totp.js | 182 +- lib/routes/unblock-codes.js | 56 +- lib/routes/util.js | 24 +- lib/routes/utils/email.js | 12 +- lib/routes/utils/request_helper.js | 6 +- lib/routes/utils/signin.js | 190 +- lib/routes/utils/totp.js | 18 +- lib/routes/validators.js | 144 +- lib/safe-url.js | 52 +- lib/scheme-refresh-token.js | 66 +- lib/senders/email.js | 812 ++++---- lib/senders/email_service.js | 36 +- lib/senders/index.js | 272 +-- lib/senders/legacy_log.js | 18 +- lib/senders/log.js | 10 +- lib/senders/oauth_client_info.js | 44 +- lib/senders/sms.js | 110 +- lib/senders/templates/index.js | 40 +- lib/senders/translator.js | 60 +- lib/server.js | 224 +-- lib/signer.js | 16 +- lib/sqs.js | 60 +- lib/time.js | 20 +- lib/tokens/account_reset_token.js | 30 +- lib/tokens/bundle.js | 54 +- lib/tokens/index.js | 40 +- lib/tokens/key_fetch_token.js | 66 +- lib/tokens/password_change_token.js | 28 +- lib/tokens/password_forgot_token.js | 52 +- lib/tokens/session_token.js | 110 +- lib/tokens/token.js | 74 +- lib/userAgent/index.js | 44 +- lib/userAgent/safe.js | 14 +- scripts/bulk-mailer.js | 32 +- scripts/bulk-mailer/index.js | 84 +- .../nodemailer-mocks/nodemailer-mock.js | 14 +- .../nodemailer-mocks/stream-output-mock.js | 38 +- .../nodemailer-mocks/write-to-disk-mock.js | 42 +- scripts/bulk-mailer/normalize-user-records.js | 50 +- scripts/bulk-mailer/read-user-records.js | 10 +- scripts/bulk-mailer/send-email-batch.js | 24 +- scripts/bulk-mailer/send-email-batches.js | 34 +- scripts/delete-account.js | 58 +- scripts/dump-users.js | 56 +- scripts/dump-users/index.js | 44 +- scripts/e2e-email/index.js | 182 +- scripts/e2e-email/localeQuirks.js | 22 +- scripts/e2e-email/validate-email.js | 94 +- scripts/email-config.js | 128 +- scripts/gen_keys.js | 52 +- scripts/gen_vapid_keys.js | 26 +- scripts/mocha-coverage.js | 24 +- scripts/must-reset.js | 50 +- scripts/rpm-version.js | 22 +- scripts/sms/send.js | 42 +- scripts/template-version-bump.js | 52 +- scripts/test-remote-quick.js | 18 +- scripts/write-emails-to-disk.js | 86 +- test/bench/bot.js | 48 +- test/bench/index.js | 72 +- test/client/api.js | 468 ++--- test/client/index.js | 590 +++--- test/e2e/push_tests.js | 40 +- test/key_server_stub.js | 4 +- test/known-ip-location.js | 4 +- test/lib/util.js | 10 +- test/local/authMethods.js | 124 +- test/local/backendService.js | 276 +-- test/local/bounces.js | 106 +- test/local/cache.js | 246 +-- test/local/config/index.js | 44 +- test/local/crypto/butil.js | 38 +- test/local/crypto/hkdf.js | 30 +- test/local/crypto/pbkdf2.js | 48 +- test/local/crypto/random.js | 136 +- test/local/crypto/scrypt.js | 48 +- test/local/customs.js | 388 ++-- test/local/db.js | 944 +++++----- test/local/devices.js | 420 ++--- test/local/email/bounce.js | 446 ++--- test/local/email/delivery.js | 148 +- test/local/email/notifications.js | 396 ++-- test/local/email/utils.js | 232 +-- test/local/error.js | 170 +- test/local/features.js | 218 +-- test/local/geodb.js | 52 +- test/local/ip_profiling.js | 152 +- test/local/log.js | 566 +++--- test/local/mailer_locales.js | 58 +- test/local/metrics/amplitude.js | 1164 ++++++------ test/local/metrics/context.js | 836 ++++----- test/local/metrics/events.js | 728 +++---- test/local/mock-sns.js | 58 +- test/local/notifier.js | 84 +- test/local/oauthdb.js | 216 +-- test/local/oauthdb/check-refresh-token.js | 44 +- test/local/oauthdb/revoke-refresh-token.js | 34 +- test/local/password.js | 78 +- test/local/pool.js | 366 ++-- test/local/profile/updates.js | 66 +- test/local/push.js | 768 ++++---- test/local/pushbox.js | 192 +- test/local/redis.js | 164 +- test/local/routes/account.js | 1672 ++++++++--------- test/local/routes/devices-and-sessions.js | 1014 +++++----- test/local/routes/emails.js | 976 +++++----- test/local/routes/oauth.js | 92 +- test/local/routes/password.js | 358 ++-- test/local/routes/recovery-codes.js | 96 +- test/local/routes/recovery-keys.js | 346 ++-- test/local/routes/request_helper.js | 26 +- test/local/routes/session.js | 586 +++--- test/local/routes/sign.js | 190 +- test/local/routes/signin-codes.js | 196 +- test/local/routes/sms.js | 754 ++++---- test/local/routes/token-codes.js | 158 +- test/local/routes/totp.js | 304 +-- test/local/routes/unblock-codes.js | 138 +- test/local/routes/utils/signin.js | 910 ++++----- test/local/routes/validators.js | 274 +-- test/local/safe-url.js | 160 +- test/local/scheme-refresh-token.js | 80 +- test/local/senders/email.js | 1558 +++++++-------- test/local/senders/email_service.js | 152 +- test/local/senders/index.js | 420 ++--- test/local/senders/legacy_log.js | 46 +- test/local/senders/oauth_client_info.js | 130 +- test/local/senders/sms.js | 386 ++-- test/local/senders/templates.js | 54 +- test/local/senders/translator.js | 50 +- test/local/sentry.js | 34 +- test/local/server.js | 528 +++--- test/local/time.js | 66 +- test/local/tokens/account_reset_token.js | 50 +- test/local/tokens/forgot_password_token.js | 64 +- test/local/tokens/key_fetch_token.js | 128 +- test/local/tokens/session_token.js | 248 +-- test/local/tokens/token.js | 156 +- test/local/user_agent.js | 354 ++-- test/mail_helper.js | 146 +- test/mailbox.js | 72 +- test/mailer_helper.js | 16 +- test/mock-sns.js | 24 +- test/mocks.js | 238 +-- test/oauth_helper.js | 38 +- test/push_helper.js | 54 +- test/remote/account_create_tests.js | 510 ++--- test/remote/account_destroy_tests.js | 100 +- test/remote/account_locale_tests.js | 78 +- test/remote/account_login_tests.js | 254 +-- test/remote/account_profile_tests.js | 208 +- test/remote/account_reset_tests.js | 218 +-- .../account_signin_verification_tests.js | 592 +++--- test/remote/account_status_tests.js | 120 +- test/remote/account_unlock_tests.js | 56 +- test/remote/base_path_tests.js | 70 +- test/remote/certificate_sign_tests.js | 202 +- test/remote/concurrent_tests.js | 50 +- test/remote/db_tests.js | 950 +++++----- test/remote/device_tests.js | 548 +++--- test/remote/device_tests_refresh_tokens.js | 290 +-- test/remote/email_validity_tests.js | 58 +- test/remote/flow_tests.js | 104 +- test/remote/misc_tests.js | 222 +-- test/remote/password_change_tests.js | 344 ++-- test/remote/password_forgot_tests.js | 314 ++-- test/remote/push_db_tests.js | 130 +- test/remote/recovery_code_tests.js | 178 +- test/remote/recovery_email_change_email.js | 324 ++-- test/remote/recovery_email_emails.js | 932 ++++----- .../recovery_email_resend_code_tests.js | 168 +- test/remote/recovery_email_verify_tests.js | 82 +- test/remote/recovery_key_tests.js | 188 +- test/remote/redis_tests.js | 202 +- test/remote/session_tests.js | 456 ++--- test/remote/sign_key_tests.js | 44 +- test/remote/signin_code_tests.js | 60 +- test/remote/sms_tests.js | 124 +- test/remote/token_code_tests.js | 208 +- test/remote/token_expiry_tests.js | 62 +- test/remote/totp_tests.js | 298 +-- test/remote/verifier_upgrade_tests.js | 82 +- test/routes_helpers.js | 18 +- test/scripts/bulk-mailer.js | 152 +- test/scripts/bulk-mailer/index.js | 86 +- .../nodemailer-mocks/stream-output-mock.js | 34 +- .../nodemailer-mocks/write-to-disk-mock.js | 46 +- .../bulk-mailer/normalize-user-records.js | 178 +- test/scripts/bulk-mailer/read-user-records.js | 34 +- test/scripts/bulk-mailer/send-email-batch.js | 46 +- .../scripts/bulk-mailer/send-email-batches.js | 72 +- test/scripts/dump-users.js | 208 +- test/scripts/email-config.js | 250 +-- test/test_mailer_server.js | 28 +- test/test_server.js | 108 +- 263 files changed, 22601 insertions(+), 22600 deletions(-) diff --git a/.eslintrc b/.eslintrc index d1f74557..ec19b3a6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,11 +1,12 @@ plugins: - fxa + extends: plugin:fxa/server rules: handle-callback-err: 0 strict: 2 - semi: 0 + semi: [2, "always"] indent: [0, 2] parserOptions: diff --git a/Gruntfile.js b/Gruntfile.js index dd9d6220..ded79456 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; module.exports = function (grunt) { - require('load-grunt-tasks')(grunt) + require('load-grunt-tasks')(grunt); grunt.initConfig({ pkg: grunt.file.readJSON('package.json') - }) + }); - grunt.loadTasks('grunttasks') + grunt.loadTasks('grunttasks'); - grunt.registerTask('default', ['eslint', 'copyright']) - grunt.registerTask('mailer', ['templates', 'copy:strings', 'l10n-extract']) -} + grunt.registerTask('default', ['eslint', 'copyright']); + grunt.registerTask('mailer', ['templates', 'copy:strings', 'l10n-extract']); +}; diff --git a/bin/email_notifications.js b/bin/email_notifications.js index ff4d8100..2258312c 100644 --- a/bin/email_notifications.js +++ b/bin/email_notifications.js @@ -2,27 +2,27 @@ * 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/. */ -'use strict' +'use strict'; // This MUST be the first require in the program. // Only `require()` the newrelic module if explicity enabled. // If required, modules will be instrumented. -require('../lib/newrelic')() +require('../lib/newrelic')(); -const config = require('../config').getProperties() -const log = require('../lib/log')(config.log.level, 'fxa-email-bouncer') -const error = require('../lib/error') -const Token = require('../lib/tokens')(log, config) -const SQSReceiver = require('../lib/sqs')(log) -const bounces = require('../lib/email/bounces')(log, error) -const delivery = require('../lib/email/delivery')(log) -const notifications = require('../lib/email/notifications')(log, error) +const config = require('../config').getProperties(); +const log = require('../lib/log')(config.log.level, 'fxa-email-bouncer'); +const error = require('../lib/error'); +const Token = require('../lib/tokens')(log, config); +const SQSReceiver = require('../lib/sqs')(log); +const bounces = require('../lib/email/bounces')(log, error); +const delivery = require('../lib/email/delivery')(log); +const notifications = require('../lib/email/notifications')(log, error); const DB = require('../lib/db')( config, log, Token -) +); const { bounceQueueUrl, @@ -30,17 +30,17 @@ const { deliveryQueueUrl, notificationQueueUrl, region -} = config.emailNotifications +} = config.emailNotifications; -const bounceQueue = new SQSReceiver(region, [ bounceQueueUrl, complaintQueueUrl ]) -const deliveryQueue = new SQSReceiver(region, [ deliveryQueueUrl ]) -const notificationQueue = new SQSReceiver(region, [ notificationQueueUrl ]) +const bounceQueue = new SQSReceiver(region, [ bounceQueueUrl, complaintQueueUrl ]); +const deliveryQueue = new SQSReceiver(region, [ deliveryQueueUrl ]); +const notificationQueue = new SQSReceiver(region, [ notificationQueueUrl ]); DB.connect(config[config.db.backend]) .then(db => { // bounces and delivery are now deprecated, we'll delete them // as soon as we're 100% confident in fxa-email-service - bounces(bounceQueue, db) - delivery(deliveryQueue) - notifications(notificationQueue, db) - }) + bounces(bounceQueue, db); + delivery(deliveryQueue); + notifications(notificationQueue, db); + }); diff --git a/bin/key_server.js b/bin/key_server.js index 0282dd5e..40b97ecf 100644 --- a/bin/key_server.js +++ b/bin/key_server.js @@ -2,42 +2,42 @@ * 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/. */ -'use strict' +'use strict'; // This MUST be the first require in the program. // Only `require()` the newrelic module if explicity enabled. // If required, modules will be instrumented. -require('../lib/newrelic')() +require('../lib/newrelic')(); -var jwtool = require('fxa-jwtool') -var P = require('../lib/promise') +var jwtool = require('fxa-jwtool'); +var P = require('../lib/promise'); function run(config) { - var log = require('../lib/log')(config.log) - var getGeoData = require('../lib/geodb')(log) + var log = require('../lib/log')(config.log); + var getGeoData = require('../lib/geodb')(log); // Force the geo to load and run at startup, not waiting for it to run on // some route later. - const knownIp = '63.245.221.32' // Mozilla MTV - const location = getGeoData(knownIp) - log.info({ op: 'geodb.check', result: location }) + const knownIp = '63.245.221.32'; // Mozilla MTV + const location = getGeoData(knownIp); + log.info({ op: 'geodb.check', result: location }); // RegExp instances serialise to empty objects, display regex strings instead. const stringifiedConfig = JSON.stringify(config, (k, v) => v && v.constructor === RegExp ? v.toString() : v - ) + ); if (config.env !== 'prod') { - log.info(stringifiedConfig, 'starting config') + log.info(stringifiedConfig, 'starting config'); } - var error = require('../lib/error') - var Token = require('../lib/tokens')(log, config) - var Password = require('../lib/crypto/password')(log, config) - var UnblockCode = require('../lib/crypto/random').base32(config.signinUnblock.codeLength) + var error = require('../lib/error'); + var Token = require('../lib/tokens')(log, config); + var Password = require('../lib/crypto/password')(log, config); + var UnblockCode = require('../lib/crypto/random').base32(config.signinUnblock.codeLength); - var signer = require('../lib/signer')(config.secretKeyFile, config.domain) + var signer = require('../lib/signer')(config.secretKeyFile, config.domain); var serverPublicKeys = { primary: jwtool.JWK.fromFile( config.publicKeyFile, @@ -57,21 +57,21 @@ function run(config) { } ) : null - } + }; - var Customs = require('../lib/customs')(log, error) + var Customs = require('../lib/customs')(log, error); - const Server = require('../lib/server') - let server = null - let senders = null - let statsInterval = null - let database = null - let customs = null - let oauthdb = null + const Server = require('../lib/server'); + let server = null; + let senders = null; + let statsInterval = null; + let database = null; + let customs = null; + let oauthdb = null; function logStatInfo() { - log.stat(server.stat()) - log.stat(Password.stat()) + log.stat(server.stat()); + log.stat(Password.stat()); } var DB = require('../lib/db')( @@ -79,7 +79,7 @@ function run(config) { log, Token, UnblockCode - ) + ); return P.all([ DB.connect(config[config.db.backend]), @@ -87,14 +87,14 @@ function run(config) { ]) .spread( (db, translator) => { - database = db - const bounces = require('../lib/bounces')(config, db) - oauthdb = require('../lib/oauthdb')(log, config) + database = db; + const bounces = require('../lib/bounces')(config, db); + oauthdb = require('../lib/oauthdb')(log, config); return require('../lib/senders')(log, config, error, bounces, translator, oauthdb) .then(result => { - senders = result - customs = new Customs(config.customsUrl) + senders = result; + customs = new Customs(config.customsUrl); var routes = require('../lib/routes')( log, serverPublicKeys, @@ -106,32 +106,32 @@ function run(config) { Password, config, customs - ) + ); - statsInterval = setInterval(logStatInfo, 15000) + statsInterval = setInterval(logStatInfo, 15000); async function init() { - server = await Server.create(log, error, config, routes, db, oauthdb, translator) + server = await Server.create(log, error, config, routes, db, oauthdb, translator); try { - await server.start() - log.info({op: 'server.start.1', msg: 'running on ' + server.info.uri}) + await server.start(); + log.info({op: 'server.start.1', msg: 'running on ' + server.info.uri}); } catch (err) { log.error( { op: 'server.start.1', msg: 'failed startup with error', err: {message: err.message} } - ) + ); } } - init() + init(); - }) + }); }, function (err) { - log.error({ op: 'DB.connect', err: { message: err.message } }) - process.exit(1) + log.error({ op: 'DB.connect', err: { message: err.message } }); + process.exit(1); } ) .then(() => { @@ -139,56 +139,56 @@ function run(config) { log: log, close() { return new P((resolve) => { - log.info({ op: 'shutdown' }) - clearInterval(statsInterval) + log.info({ op: 'shutdown' }); + clearInterval(statsInterval); server.stop().then(() => { - customs.close() - oauthdb.close() + customs.close(); + oauthdb.close(); try { - senders.email.stop() + senders.email.stop(); } catch (e) { // XXX: simplesmtp module may quit early and set socket to `false`, stopping it may fail - log.warn({op: 'shutdown', message: 'Mailer client already disconnected'}) + log.warn({op: 'shutdown', message: 'Mailer client already disconnected'}); } - database.close() - resolve() - }) - }) + database.close(); + resolve(); + }); + }); } - } - }) + }; + }); } function main() { - const config = require('../config').getProperties() + const config = require('../config').getProperties(); run(config).then(server => { process.on('uncaughtException', (err) => { - server.log.fatal(err) - process.exit(8) - }) + server.log.fatal(err); + process.exit(8); + }); process.on('unhandledRejection', (reason, promise) => { server.log.fatal({ op: 'promise.unhandledRejection', error: reason - }) - }) - process.on('SIGINT', shutdown) - server.log.on('error', shutdown) + }); + }); + process.on('SIGINT', shutdown); + server.log.on('error', shutdown); function shutdown() { server.close().then(() => { - process.exit() //XXX: because of openid dep ಠ_ಠ - }) + process.exit(); //XXX: because of openid dep ಠ_ಠ + }); } }) .catch((err) => { - console.error(err) // eslint-disable-line no-console - process.exit(8) - }) + console.error(err); // eslint-disable-line no-console + process.exit(8); + }); } if (require.main === module) { - main() + main(); } else { - module.exports = run + module.exports = run; } diff --git a/bin/profile_server_messaging.js b/bin/profile_server_messaging.js index d5c57262..3f39fbed 100644 --- a/bin/profile_server_messaging.js +++ b/bin/profile_server_messaging.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; // This MUST be the first require in the program. // Only `require()` the newrelic module if explicity enabled. // If required, modules will be instrumented. -require('../lib/newrelic')() +require('../lib/newrelic')(); -var config = require('../config').getProperties() -var log = require('../lib/log')(config.log.level, 'profile-server-messaging') -var Token = require('../lib/tokens')(log, config) -var SQSReceiver = require('../lib/sqs')(log) -var profileUpdates = require('../lib/profile/updates')(log) -var push = require('../lib/push') +var config = require('../config').getProperties(); +var log = require('../lib/log')(config.log.level, 'profile-server-messaging'); +var Token = require('../lib/tokens')(log, config); +var SQSReceiver = require('../lib/sqs')(log); +var profileUpdates = require('../lib/profile/updates')(log); +var push = require('../lib/push'); var DB = require('../lib/db')( config, log, Token -) +); var profileUpdatesQueue = new SQSReceiver(config.profileServerMessaging.region, [ config.profileServerMessaging.profileUpdatesQueueUrl -]) +]); DB.connect(config[config.db.backend]) .then( function (db) { - profileUpdates(profileUpdatesQueue, push(log, db, config), db) + profileUpdates(profileUpdatesQueue, push(log, db, config), db); } - ) + ); diff --git a/config/index.js b/config/index.js index 29989c9b..dea51035 100644 --- a/config/index.js +++ b/config/index.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; -var fs = require('fs') -var path = require('path') -var url = require('url') -var convict = require('convict') -var DEFAULT_SUPPORTED_LANGUAGES = require('./supportedLanguages') +var fs = require('fs'); +var path = require('path'); +var url = require('url'); +var convict = require('convict'); +var DEFAULT_SUPPORTED_LANGUAGES = require('./supportedLanguages'); -const ONE_DAY = 1000 * 60 * 60 * 24 -const ONE_YEAR = ONE_DAY * 365 -const FIVE_MINUTES = 1000 * 60 * 5 +const ONE_DAY = 1000 * 60 * 60 * 24; +const ONE_YEAR = ONE_DAY * 365; +const FIVE_MINUTES = 1000 * 60 * 5; var conf = convict({ env: { @@ -877,48 +877,48 @@ var conf = convict({ } } } -}) +}); // handle configuration files. you can specify a CSV list of configuration // files to process, which will be overlayed in order, in the CONFIG_FILES // environment variable. -var envConfig = path.join(__dirname, conf.get('env') + '.json') -envConfig = envConfig + ',' + (process.env.CONFIG_FILES || '') -var files = envConfig.split(',').filter(fs.existsSync) -conf.loadFile(files) -conf.validate({ allowed: 'strict' }) +var envConfig = path.join(__dirname, conf.get('env') + '.json'); +envConfig = envConfig + ',' + (process.env.CONFIG_FILES || ''); +var files = envConfig.split(',').filter(fs.existsSync); +conf.loadFile(files); +conf.validate({ allowed: 'strict' }); // set the public url as the issuer domain for assertions -conf.set('domain', url.parse(conf.get('publicUrl')).host) +conf.set('domain', url.parse(conf.get('publicUrl')).host); // derive fxa-auth-mailer configuration from our content-server url -conf.set('smtp.accountSettingsUrl', conf.get('contentServer.url') + '/settings') -conf.set('smtp.accountRecoveryCodesUrl', conf.get('contentServer.url') + '/settings/two_step_authentication/recovery_codes') -conf.set('smtp.verificationUrl', conf.get('contentServer.url') + '/verify_email') -conf.set('smtp.passwordResetUrl', conf.get('contentServer.url') + '/complete_reset_password') -conf.set('smtp.initiatePasswordResetUrl', conf.get('contentServer.url') + '/reset_password') -conf.set('smtp.initiatePasswordChangeUrl', conf.get('contentServer.url') + '/settings/change_password') -conf.set('smtp.verifyLoginUrl', conf.get('contentServer.url') + '/complete_signin') -conf.set('smtp.reportSignInUrl', conf.get('contentServer.url') + '/report_signin') -conf.set('smtp.revokeAccountRecoveryUrl', conf.get('contentServer.url') + '/settings/account_recovery/confirm_revoke') -conf.set('smtp.createAccountRecoveryUrl', conf.get('contentServer.url') + '/settings/account_recovery/confirm_password') -conf.set('smtp.verifyPrimaryEmailUrl', conf.get('contentServer.url') + '/verify_primary_email') -conf.set('smtp.verifySecondaryEmailUrl', conf.get('contentServer.url') + '/verify_secondary_email') +conf.set('smtp.accountSettingsUrl', conf.get('contentServer.url') + '/settings'); +conf.set('smtp.accountRecoveryCodesUrl', conf.get('contentServer.url') + '/settings/two_step_authentication/recovery_codes'); +conf.set('smtp.verificationUrl', conf.get('contentServer.url') + '/verify_email'); +conf.set('smtp.passwordResetUrl', conf.get('contentServer.url') + '/complete_reset_password'); +conf.set('smtp.initiatePasswordResetUrl', conf.get('contentServer.url') + '/reset_password'); +conf.set('smtp.initiatePasswordChangeUrl', conf.get('contentServer.url') + '/settings/change_password'); +conf.set('smtp.verifyLoginUrl', conf.get('contentServer.url') + '/complete_signin'); +conf.set('smtp.reportSignInUrl', conf.get('contentServer.url') + '/report_signin'); +conf.set('smtp.revokeAccountRecoveryUrl', conf.get('contentServer.url') + '/settings/account_recovery/confirm_revoke'); +conf.set('smtp.createAccountRecoveryUrl', conf.get('contentServer.url') + '/settings/account_recovery/confirm_password'); +conf.set('smtp.verifyPrimaryEmailUrl', conf.get('contentServer.url') + '/verify_primary_email'); +conf.set('smtp.verifySecondaryEmailUrl', conf.get('contentServer.url') + '/verify_secondary_email'); -conf.set('isProduction', conf.get('env') === 'prod') +conf.set('isProduction', conf.get('env') === 'prod'); //sns endpoint is not to be set in production if (conf.has('snsTopicEndpoint') && conf.get('env') !== 'dev') { - throw new Error('snsTopicEndpoint is only allowed in dev env') + throw new Error('snsTopicEndpoint is only allowed in dev env'); } if (conf.get('env') === 'dev'){ if (! process.env.AWS_ACCESS_KEY_ID) { - process.env.AWS_ACCESS_KEY_ID = 'DEV_KEY_ID' + process.env.AWS_ACCESS_KEY_ID = 'DEV_KEY_ID'; } if (! process.env.AWS_SECRET_ACCESS_KEY) { - process.env.AWS_SECRET_ACCESS_KEY = 'DEV_ACCESS_KEY' + process.env.AWS_SECRET_ACCESS_KEY = 'DEV_ACCESS_KEY'; } } @@ -928,12 +928,12 @@ if (conf.get('isProduction')) { 'pushbox.key', 'metrics.flow_id_key', 'oauth.secretKey', - ] + ]; for (const key of SECRET_SETTINGS) { if (conf.get(key) === conf.default(key)) { - throw new Error(`Config '${key}' must be set in production`) + throw new Error(`Config '${key}' must be set in production`); } } } -module.exports = conf +module.exports = conf; diff --git a/config/popular-email-domains.js b/config/popular-email-domains.js index 9224b01f..1b92b303 100644 --- a/config/popular-email-domains.js +++ b/config/popular-email-domains.js @@ -2,8 +2,8 @@ * 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/. */ -'use strict' +'use strict'; -const { popularDomains } = require('fxa-shared').email +const { popularDomains } = require('fxa-shared').email; -module.exports = new Set(popularDomains) +module.exports = new Set(popularDomains); diff --git a/config/supportedLanguages.js b/config/supportedLanguages.js index 6c4d637f..145954c6 100644 --- a/config/supportedLanguages.js +++ b/config/supportedLanguages.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; // The list below should be kept in sync with: // https://raw.githubusercontent.com/mozilla/fxa-content-server/master/server/config/production-locales.json -module.exports = require('fxa-shared').l10n.supportedLanguages +module.exports = require('fxa-shared').l10n.supportedLanguages; diff --git a/grunttasks/bump.js b/grunttasks/bump.js index a2c13e0d..7aa1f847 100644 --- a/grunttasks/bump.js +++ b/grunttasks/bump.js @@ -4,7 +4,7 @@ // takes care of bumping the version number in package.json -'use strict' +'use strict'; module.exports = function (grunt) { grunt.config('bump', { @@ -29,6 +29,6 @@ module.exports = function (grunt) { pushTo: 'origin', gitDescribeOptions: '--tags --always --abrev=1 --dirty=-d' } - }) -} + }); +}; diff --git a/grunttasks/changelog.js b/grunttasks/changelog.js index cfc0e4f2..93118e11 100644 --- a/grunttasks/changelog.js +++ b/grunttasks/changelog.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -var fxaChangelog = require('fxa-conventional-changelog')() +var fxaChangelog = require('fxa-conventional-changelog')(); module.exports = function (grunt) { grunt.config('conventionalChangelog', { @@ -16,5 +16,5 @@ module.exports = function (grunt) { release: { src: 'CHANGELOG.md' } - }) -} + }); +}; diff --git a/grunttasks/copyright.js b/grunttasks/copyright.js index d0602cb6..1a9cdac0 100644 --- a/grunttasks/copyright.js +++ b/grunttasks/copyright.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/. */ -'use strict' +'use strict'; module.exports = function (grunt) { grunt.config('copyright', { @@ -14,5 +14,5 @@ module.exports = function (grunt) { '<%= eslint.app.src %>' ] } - }) -} + }); +}; diff --git a/grunttasks/eslint.js b/grunttasks/eslint.js index 2fc57c9c..6bf71c16 100644 --- a/grunttasks/eslint.js +++ b/grunttasks/eslint.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/. */ -'use strict' +'use strict'; module.exports = function (grunt) { grunt.config('eslint', { @@ -12,6 +12,6 @@ module.exports = function (grunt) { '{fxa-oauth-server/bin/**/,fxa-oauth-server/lib/**/,fxa-oauth-server/test/**/,fxa-oauth-server/scripts/**/}*.js' ] } - }) - grunt.registerTask('quicklint', 'lint the modified files', 'newer:eslint') -} + }); + grunt.registerTask('quicklint', 'lint the modified files', 'newer:eslint'); +}; diff --git a/grunttasks/l10n-extract.js b/grunttasks/l10n-extract.js index b46f5e89..e5e4fe45 100644 --- a/grunttasks/l10n-extract.js +++ b/grunttasks/l10n-extract.js @@ -4,12 +4,12 @@ * 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/. */ -'use strict' +'use strict'; -const path = require('path') -const extract = require('jsxgettext-recursive-next') +const path = require('path'); +const extract = require('jsxgettext-recursive-next'); -const pkgroot = path.dirname(__dirname) +const pkgroot = path.dirname(__dirname); module.exports = function (grunt) { grunt.config('copy', { @@ -24,10 +24,10 @@ module.exports = function (grunt) { ] }] } - }) + }); grunt.registerTask('l10n-extract', 'Extract strings from templates for localization.', function () { - var done = this.async() + var done = this.async(); var walker = extract({ 'input-dir': path.join(pkgroot, 'lib/senders/templates'), @@ -39,7 +39,7 @@ module.exports = function (grunt) { '.txt': 'handlebars', '.html': 'handlebars' } - }) + }); walker.on('end', function () { var jsWalker = extract({ @@ -51,19 +51,19 @@ module.exports = function (grunt) { parsers: { '.js': 'javascript' } - }) + }); jsWalker.on('end', function () { - done() - }) - }) - }) + done(); + }); + }); + }); // load local Grunt tasks - grunt.registerTask('lint', 'Alias for eslint tasks', ['eslint']) - grunt.registerTask('templates', 'Alias for the template task', ['nunjucks']) + grunt.registerTask('lint', 'Alias for eslint tasks', ['eslint']); + grunt.registerTask('templates', 'Alias for the template task', ['nunjucks']); - grunt.registerTask('default', [ 'templates', 'copy:strings', 'l10n-extract' ]) + grunt.registerTask('default', [ 'templates', 'copy:strings', 'l10n-extract' ]); -} +}; diff --git a/grunttasks/templates.js b/grunttasks/templates.js index 9ffcb988..c6922106 100644 --- a/grunttasks/templates.js +++ b/grunttasks/templates.js @@ -4,7 +4,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/. */ -'use strict' +'use strict'; module.exports = function (grunt) { grunt.config('nunjucks', { @@ -31,7 +31,7 @@ module.exports = function (grunt) { } ] } - }) + }); - grunt.registerTask('templates', 'Alias for the template task', ['nunjucks']) -} + grunt.registerTask('templates', 'Alias for the template task', ['nunjucks']); +}; diff --git a/grunttasks/version.js b/grunttasks/version.js index 6746ebc7..5a5e9f0d 100644 --- a/grunttasks/version.js +++ b/grunttasks/version.js @@ -16,18 +16,18 @@ // NOTE: This task will not push this commit for you. // -'use strict' +'use strict'; module.exports = function (grunt) { grunt.registerTask('version', [ 'bump-only:minor', 'conventionalChangelog:release', 'bump-commit' - ]) + ]); grunt.registerTask('version:patch', [ 'bump-only:patch', 'conventionalChangelog:release', 'bump-commit' - ]) -} + ]); +}; diff --git a/lib/authMethods.js b/lib/authMethods.js index fc9e77c3..47d583b2 100644 --- a/lib/authMethods.js +++ b/lib/authMethods.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('./error') +const error = require('./error'); // Maps our variety of verification methods down to a few short standard // "authentication method reference" strings that we're happy to expose to @@ -19,7 +19,7 @@ const METHOD_TO_AMR = { 'email-2fa': 'email', 'totp-2fa': 'otp', 'recovery-code': 'otp' -} +}; // Maps AMR values to the type of authenticator they represent, e.g. // "something you know" vs "something you have". This helps us determine @@ -28,7 +28,7 @@ const AMR_TO_TYPE = { 'pwd': 'know', 'email': 'know', 'otp': 'have' -} +}; module.exports = { @@ -37,28 +37,28 @@ module.exports = { * for the given account, as amr value strings. */ availableAuthenticationMethods(db, account) { - const amrValues = new Set() + const amrValues = new Set(); // All accounts can authenticate with a password. - amrValues.add('pwd') + amrValues.add('pwd'); // All accounts can authenticate with an email confirmation loop. - amrValues.add('email') + amrValues.add('email'); // Some accounts have a TOTP token. return db.totpToken(account.uid) .then( res => { if (res && res.verified && res.enabled) { - amrValues.add('otp') + amrValues.add('otp'); } }, err => { if (err.errno !== error.ERRNO.TOTP_TOKEN_NOT_FOUND) { - throw err + throw err; } } ) .then(() => { - return amrValues - }) + return amrValues; + }); }, /** @@ -67,11 +67,11 @@ module.exports = { * "email" while "totp-2fa" will map to "otp". */ verificationMethodToAMR(verificationMethod) { - const amr = METHOD_TO_AMR[verificationMethod] + const amr = METHOD_TO_AMR[verificationMethod]; if (! amr) { - throw new Error('unknown verificationMethod: ' + verificationMethod) + throw new Error('unknown verificationMethod: ' + verificationMethod); } - return amr + return amr; }, /** @@ -83,10 +83,10 @@ module.exports = { * for level 3. */ maximumAssuranceLevel(amrValues) { - const types = new Set() + const types = new Set(); amrValues.forEach(amr => { - types.add(AMR_TO_TYPE[amr]) - }) - return types.size + types.add(AMR_TO_TYPE[amr]); + }); + return types.size; } -} +}; diff --git a/lib/backendService.js b/lib/backendService.js index 11bc4ffd..8ba3e607 100644 --- a/lib/backendService.js +++ b/lib/backendService.js @@ -66,55 +66,55 @@ * */ -'use strict' +'use strict'; -const Joi = require('joi') +const Joi = require('joi'); -const P = require('./promise') -const Pool = require('./pool') -const error = require('./error') +const P = require('./promise'); +const Pool = require('./pool'); +const error = require('./error'); module.exports = function createBackendServiceAPI(log, config, serviceName, methods) { - const SafeUrl = require('./safe-url')(log) + const SafeUrl = require('./safe-url')(log); function Service(url, options = {}) { - this._headers = options.headers - this._pool = new Pool(url, options) + this._headers = options.headers; + this._pool = new Pool(url, options); } Service.prototype.close = function close() { - return this._pool.close() - } + return this._pool.close(); + }; for (const methodName in methods) { - Service.prototype[methodName] = makeServiceMethod(methodName, methods[methodName]) + Service.prototype[methodName] = makeServiceMethod(methodName, methods[methodName]); } - return Service + return Service; // Each declared service method gets turned into an async function // that validates its inputs, makes the HTTP request using the // connection pool, and validates the response. function makeServiceMethod(methodName, opts) { - const path = new SafeUrl(opts.path) + const path = new SafeUrl(opts.path); - const validation = opts.validate || {} - const paramsSchema = Joi.compile(validation.params || Joi.object()) - const querySchema = Joi.compile(validation.query || Joi.object()) - const payloadSchema = Joi.compile(validation.payload || Joi.object()) - const responseSchema = Joi.compile(validation.response || Joi.any()) + const validation = opts.validate || {}; + const paramsSchema = Joi.compile(validation.params || Joi.object()); + const querySchema = Joi.compile(validation.query || Joi.object()); + const payloadSchema = Joi.compile(validation.payload || Joi.object()); + const responseSchema = Joi.compile(validation.response || Joi.any()); - let expectedNumArgs = path.params().length + let expectedNumArgs = path.params().length; if (validation.query) { - expectedNumArgs += 1 + expectedNumArgs += 1; } if (validation.payload) { - expectedNumArgs += 1 + expectedNumArgs += 1; } - const fullMethodName = `${serviceName}.${methodName}` + const fullMethodName = `${serviceName}.${methodName}`; // A thin wrapper around Joi.validate(), that logs the error and then // wraps it in a generic "internal validation error" that can be returned @@ -124,33 +124,33 @@ module.exports = function createBackendServiceAPI(log, config, serviceName, meth return new P((resolve, reject) => { Joi.validate(value, schema, options, (err, value) => { if (! err) { - return resolve(value) + return resolve(value); } log.error(fullMethodName, { error: `${location} schema validation failed`, message: err.message, value - }) - reject(error.internalValidationError(fullMethodName, { location, value })) - }) - }) + }); + reject(error.internalValidationError(fullMethodName, { location, value })); + }); + }); } // A helper to make the request and return the response, or an error. // This assumes you've done all the hard work of formulating params, body, etc. async function sendRequest(pool, method, path, params, query, payload, headers) { - log.trace(fullMethodName, { params, query, payload }) + log.trace(fullMethodName, { params, query, payload }); try { - return await pool.request(method, path, params, query, payload, headers) + return await pool.request(method, path, params, query, payload, headers); } catch (err) { // Re-throw 400-level errors, but wrap 500-level or generic errors // into a "backend service failure" to propagate to the client. if (err.errno || (err.statusCode && err.statusCode < 500)) { - throw err + throw err; } else { - log.error(`${fullMethodName}.1`, { params, query, payload, err }) - throw error.backendServiceFailure(serviceName, methodName) + log.error(`${fullMethodName}.1`, { params, query, payload, err }); + throw error.backendServiceFailure(serviceName, methodName); } } } @@ -160,28 +160,28 @@ module.exports = function createBackendServiceAPI(log, config, serviceName, meth async function theServiceMethod(...args) { // Interpret function arguments according to the declared schema. if (args.length !== expectedNumArgs) { - throw new Error(`${fullMethodName} must be called with ${expectedNumArgs} arguments (${args.length} given)`) + throw new Error(`${fullMethodName} must be called with ${expectedNumArgs} arguments (${args.length} given)`); } - let i = 0 + let i = 0; // The leading positional arguments correspond to individual path params, // in the order they appear in the path template. - let params = {} + let params = {}; for (const param of path.params()) { - params[param] = args[i++] + params[param] = args[i++]; } - params = await validate('params', params, paramsSchema) + params = await validate('params', params, paramsSchema); // Next are query params as a dict, if any. - const query = validation.query ? await validate('query', args[i++], querySchema) : {} + const query = validation.query ? await validate('query', args[i++], querySchema) : {}; // Next is request payload as a dict, if any. - const payload = validation.payload ? await validate('request', args[i++], payloadSchema) : {} + const payload = validation.payload ? await validate('request', args[i++], payloadSchema) : {}; // Unexpected extra fields in the service response should not be a fatal error, // but we also don't want them polluting our code. So, stripUnknown=true. - const response = await sendRequest(this._pool, opts.method, path, params, query, payload, this._headers) - return await validate('response', response, responseSchema, { stripUnknown: true }) + const response = await sendRequest(this._pool, opts.method, path, params, query, payload, this._headers); + return await validate('response', response, responseSchema, { stripUnknown: true }); } // Expose the options for introspection by calling code if necessary. - theServiceMethod.opts = opts - return theServiceMethod + theServiceMethod.opts = opts; + return theServiceMethod; } -} +}; diff --git a/lib/bounces.js b/lib/bounces.js index 4af8f966..2f42a8d0 100644 --- a/lib/bounces.js +++ b/lib/bounces.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('./error') -const P = require('./promise') +const error = require('./error'); +const P = require('./promise'); module.exports = (config, db) => { - const configBounces = config.smtp && config.smtp.bounces || {} - const BOUNCES_ENABLED = !! configBounces.enabled + const configBounces = config.smtp && config.smtp.bounces || {}; + const BOUNCES_ENABLED = !! configBounces.enabled; - const BOUNCE_TYPE_HARD = 1 - const BOUNCE_TYPE_SOFT = 2 - const BOUNCE_TYPE_COMPLAINT = 3 + const BOUNCE_TYPE_HARD = 1; + const BOUNCE_TYPE_SOFT = 2; + const BOUNCE_TYPE_COMPLAINT = 3; - const freeze = Object.freeze + const freeze = Object.freeze; const BOUNCE_RULES = freeze({ [BOUNCE_TYPE_HARD]: freeze(configBounces.hard || {}), [BOUNCE_TYPE_SOFT]: freeze(configBounces.soft || {}), [BOUNCE_TYPE_COMPLAINT]: freeze(configBounces.complaint || {}) - }) + }); const ERRORS = { [BOUNCE_TYPE_HARD]: error.emailBouncedHard, [BOUNCE_TYPE_SOFT]: error.emailBouncedSoft, [BOUNCE_TYPE_COMPLAINT]: error.emailComplaint - } + }; function checkBounces(email) { return db.emailBounces(email) - .then(applyRules) + .then(applyRules); } // Relies on the order of the bounces array to be sorted by date, @@ -50,31 +50,31 @@ module.exports = (config, db) => { count: 0, latest: 0 } - } - const now = Date.now() + }; + const now = Date.now(); bounces.forEach(bounce => { - const type = bounce.bounceType - const ruleSet = BOUNCE_RULES[type] + const type = bounce.bounceType; + const ruleSet = BOUNCE_RULES[type]; if (ruleSet) { - const tally = tallies[type] - const tier = ruleSet[tally.count] + const tally = tallies[type]; + const tier = ruleSet[tally.count]; if (! tally.latest) { - tally.latest = bounce.createdAt + tally.latest = bounce.createdAt; } if (tier && bounce.createdAt > now - tier) { - throw ERRORS[type](tally.latest) + throw ERRORS[type](tally.latest); } - tally.count++ + tally.count++; } - }) + }); } function disabled() { - return P.resolve() + return P.resolve(); } return { check: BOUNCES_ENABLED ? checkBounces : disabled - } -} + }; +}; diff --git a/lib/cache.js b/lib/cache.js index b8c1a4e2..b56aa5ac 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; -const Memcached = require('memcached') -const P = require('./promise') +const Memcached = require('memcached'); +const P = require('./promise'); -P.promisifyAll(Memcached.prototype) +P.promisifyAll(Memcached.prototype); -const NOP = () => P.resolve() +const NOP = () => P.resolve(); const NULL_CACHE = { addAsync: NOP, delAsync: NOP, getAsync: NOP -} +}; module.exports = (log, config, namespace) => { - let _cache + let _cache; - const CACHE_ADDRESS = config.memcached.address - const CACHE_IDLE = config.memcached.idle - const CACHE_LIFETIME = config.memcached.lifetime + const CACHE_ADDRESS = config.memcached.address; + const CACHE_IDLE = config.memcached.idle; + const CACHE_LIFETIME = config.memcached.lifetime; return { /** @@ -36,7 +36,7 @@ module.exports = (log, config, namespace) => { */ add (key, data) { return getCache() - .then(cache => cache.addAsync(key, data, CACHE_LIFETIME)) + .then(cache => cache.addAsync(key, data, CACHE_LIFETIME)); }, /** @@ -48,7 +48,7 @@ module.exports = (log, config, namespace) => { */ del (key) { return getCache() - .then(cache => cache.delAsync(key)) + .then(cache => cache.delAsync(key)); }, /** @@ -60,19 +60,19 @@ module.exports = (log, config, namespace) => { */ get (key) { return getCache() - .then(cache => cache.getAsync(key)) + .then(cache => cache.getAsync(key)); } - } + }; function getCache () { return P.resolve() .then(() => { if (_cache) { - return _cache + return _cache; } if (CACHE_ADDRESS === 'none') { - _cache = NULL_CACHE + _cache = NULL_CACHE; } else { _cache = new Memcached(CACHE_ADDRESS, { timeout: 500, @@ -81,14 +81,14 @@ module.exports = (log, config, namespace) => { reconnect: 1000, idle: CACHE_IDLE, namespace - }) + }); } - return _cache + return _cache; }) .catch(err => { - log.error('cache.getCache', { err: err }) - return NULL_CACHE - }) + log.error('cache.getCache', { err: err }); + return NULL_CACHE; + }); } -} +}; diff --git a/lib/crypto/butil.js b/lib/crypto/butil.js index ee54eaa6..7015f1cb 100644 --- a/lib/crypto/butil.js +++ b/lib/crypto/butil.js @@ -2,36 +2,36 @@ * 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/. */ -'use strict' +'use strict'; -module.exports.ONES = Buffer.from('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex') +module.exports.ONES = Buffer.from('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex'); module.exports.buffersAreEqual = function buffersAreEqual(buffer1, buffer2) { - buffer1 = Buffer.from(buffer1, 'hex') - buffer2 = Buffer.from(buffer2, 'hex') - var mismatch = buffer1.length - buffer2.length + buffer1 = Buffer.from(buffer1, 'hex'); + buffer2 = Buffer.from(buffer2, 'hex'); + var mismatch = buffer1.length - buffer2.length; if (mismatch) { - return false + return false; } for (var i = 0; i < buffer1.length; i++) { - mismatch |= buffer1[i] ^ buffer2[i] + mismatch |= buffer1[i] ^ buffer2[i]; } - return mismatch === 0 -} + return mismatch === 0; +}; module.exports.xorBuffers = function xorBuffers(buffer1, buffer2) { - buffer1 = Buffer.from(buffer1, 'hex') - buffer2 = Buffer.from(buffer2, 'hex') + buffer1 = Buffer.from(buffer1, 'hex'); + buffer2 = Buffer.from(buffer2, 'hex'); if (buffer1.length !== buffer2.length) { throw new Error( 'XOR buffers must be same length (%d != %d)', buffer1.length, buffer2.length - ) + ); } - var result = Buffer.alloc(buffer1.length) + var result = Buffer.alloc(buffer1.length); for (var i = 0; i < buffer1.length; i++) { - result[i] = buffer1[i] ^ buffer2[i] + result[i] = buffer1[i] ^ buffer2[i]; } - return result -} + return result; +}; diff --git a/lib/crypto/hkdf.js b/lib/crypto/hkdf.js index 665d1986..4f7f1fb4 100644 --- a/lib/crypto/hkdf.js +++ b/lib/crypto/hkdf.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -var HKDF = require('hkdf') -var P = require('../promise') +var HKDF = require('hkdf'); +var P = require('../promise'); -const NAMESPACE = 'identity.mozilla.com/picl/v1/' +const NAMESPACE = 'identity.mozilla.com/picl/v1/'; function KWE(name, email) { - return Buffer.from(NAMESPACE + name + ':' + email) + return Buffer.from(NAMESPACE + name + ':' + email); } function KW(name) { - return Buffer.from(NAMESPACE + name) + return Buffer.from(NAMESPACE + name); } function hkdf(km, info, salt, len) { - var d = P.defer() - var df = new HKDF('sha256', salt, km) + var d = P.defer(); + var df = new HKDF('sha256', salt, km); df.derive( KW(info), len, function(key) { - d.resolve(key) + d.resolve(key); } - ) - return d.promise + ); + return d.promise; } -hkdf.KW = KW -hkdf.KWE = KWE +hkdf.KW = KW; +hkdf.KWE = KWE; -module.exports = hkdf +module.exports = hkdf; diff --git a/lib/crypto/password.js b/lib/crypto/password.js index 9feb09d7..49b3e619 100644 --- a/lib/crypto/password.js +++ b/lib/crypto/password.js @@ -2,80 +2,80 @@ * 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/. */ -'use strict' +'use strict'; -var P = require('../promise') -var hkdf = require('./hkdf') -var butil = require('./butil') +var P = require('../promise'); +var hkdf = require('./hkdf'); +var butil = require('./butil'); module.exports = function(log, config) { - var scrypt = require('./scrypt')(log, config) + var scrypt = require('./scrypt')(log, config); var hashVersions = { 0: function (authPW, authSalt) { - return P.resolve(butil.xorBuffers(authPW, authSalt)) + return P.resolve(butil.xorBuffers(authPW, authSalt)); }, 1: function (authPW, authSalt) { - return scrypt.hash(authPW, authSalt, 65536, 8, 1, 32) + return scrypt.hash(authPW, authSalt, 65536, 8, 1, 32); } - } + }; function Password(authPW, authSalt, version) { - version = typeof(version) === 'number' ? version : 1 - this.authPW = Buffer.from(authPW, 'hex') - this.authSalt = Buffer.from(authSalt, 'hex') - this.version = version - this.stretchPromise = hashVersions[version](this.authPW, this.authSalt) - this.verifyHashPromise = this.stretchPromise.then(hkdfVerify) + version = typeof(version) === 'number' ? version : 1; + this.authPW = Buffer.from(authPW, 'hex'); + this.authSalt = Buffer.from(authSalt, 'hex'); + this.version = version; + this.stretchPromise = hashVersions[version](this.authPW, this.authSalt); + this.verifyHashPromise = this.stretchPromise.then(hkdfVerify); } Password.prototype.stretchedPassword = function () { - return this.stretchPromise - } + return this.stretchPromise; + }; Password.prototype.verifyHash = function () { - return this.verifyHashPromise - } + return this.verifyHashPromise; + }; Password.prototype.matches = function (verifyHash) { return this.verifyHash().then( function (hash) { - return butil.buffersAreEqual(hash, verifyHash) + return butil.buffersAreEqual(hash, verifyHash); } - ) - } + ); + }; Password.prototype.unwrap = function (wrapped, context) { - context = context || 'wrapwrapKey' + context = context || 'wrapwrapKey'; return this.stretchedPassword().then( function (stretched) { return hkdf(stretched, context, null, 32) .then( function (wrapper) { - return butil.xorBuffers(wrapper, wrapped).toString('hex') + return butil.xorBuffers(wrapper, wrapped).toString('hex'); } - ) + ); } - ) - } - Password.prototype.wrap = Password.prototype.unwrap + ); + }; + Password.prototype.wrap = Password.prototype.unwrap; function hkdfVerify(stretched) { - return hkdf(stretched, 'verifyHash', null, 32).then(buf => buf.toString('hex')) + return hkdf(stretched, 'verifyHash', null, 32).then(buf => buf.toString('hex')); } Password.stat = function () { // Reset the high-water-mark whenever it is read. - var numPendingHWM = scrypt.numPendingHWM - scrypt.numPendingHWM = scrypt.numPending + var numPendingHWM = scrypt.numPendingHWM; + scrypt.numPendingHWM = scrypt.numPending; return { stat: 'scrypt', maxPending: scrypt.maxPending, numPending: scrypt.numPending, numPendingHWM: numPendingHWM - } - } + }; + }; - return Password -} + return Password; +}; diff --git a/lib/crypto/pbkdf2.js b/lib/crypto/pbkdf2.js index 4e8ec86b..151a9720 100644 --- a/lib/crypto/pbkdf2.js +++ b/lib/crypto/pbkdf2.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -var sjcl = require('sjcl') -var P = require('../promise') +var sjcl = require('sjcl'); +var P = require('../promise'); /** pbkdf2 string creator * @@ -14,11 +14,11 @@ var P = require('../promise') * @return {Buffer} the derived key hex buffer. */ function derive(input, salt, iterations, len) { - var password = sjcl.codec.hex.toBits(input.toString('hex')) - var saltBits = sjcl.codec.hex.toBits(salt.toString('hex')) - var result = sjcl.misc.pbkdf2(password, saltBits, iterations, len * 8, sjcl.misc.hmac) + var password = sjcl.codec.hex.toBits(input.toString('hex')); + var saltBits = sjcl.codec.hex.toBits(salt.toString('hex')); + var result = sjcl.misc.pbkdf2(password, saltBits, iterations, len * 8, sjcl.misc.hmac); - return P.resolve(Buffer.from(sjcl.codec.hex.fromBits(result), 'hex')) + return P.resolve(Buffer.from(sjcl.codec.hex.fromBits(result), 'hex')); } -module.exports.derive = derive +module.exports.derive = derive; diff --git a/lib/crypto/random.js b/lib/crypto/random.js index 1b6c5d6b..109d94d7 100644 --- a/lib/crypto/random.js +++ b/lib/crypto/random.js @@ -2,48 +2,48 @@ * 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/. */ -'use strict' +'use strict'; -const assert = require('assert') -const randomBytes = require('../promise').promisify(require('crypto').randomBytes) +const assert = require('assert'); +const randomBytes = require('../promise').promisify(require('crypto').randomBytes); -const BASE32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' -const BASE10 = '0123456789' +const BASE32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; +const BASE10 = '0123456789'; // some sanity checks, hard to test, private to this mdoule -assert.equal(BASE32.length, 32, 'ALPHABET is 32 characters') -assert.equal(BASE32.indexOf('I'), -1, 'should not contain I') -assert.equal(BASE32.indexOf('L'), -1, 'should not contain L') -assert.equal(BASE32.indexOf('O'), -1, 'should not contain O') -assert.equal(BASE32.indexOf('U'), -1, 'should not contain U') +assert.equal(BASE32.length, 32, 'ALPHABET is 32 characters'); +assert.equal(BASE32.indexOf('I'), -1, 'should not contain I'); +assert.equal(BASE32.indexOf('L'), -1, 'should not contain L'); +assert.equal(BASE32.indexOf('O'), -1, 'should not contain O'); +assert.equal(BASE32.indexOf('U'), -1, 'should not contain U'); function random(bytes) { if (arguments.length > 1) { - bytes = Array.from(arguments) - const sum = bytes.reduce((acc, val) => acc + val, 0) + bytes = Array.from(arguments); + const sum = bytes.reduce((acc, val) => acc + val, 0); return randomBytes(sum).then(buf => { - let pos = 0 + let pos = 0; return bytes.map(num => { - const slice = buf.slice(pos, pos + num) - pos += num - return slice - }) - }) + const slice = buf.slice(pos, pos + num); + pos += num; + return slice; + }); + }); } else { - return randomBytes(bytes) + return randomBytes(bytes); } } random.hex = function hex() { return random.apply(null, arguments).then(bufs => { if (Array.isArray(bufs)) { - return bufs.map(buf => buf.toString('hex')) + return bufs.map(buf => buf.toString('hex')); } else { - return bufs.toString('hex') + return bufs.toString('hex'); } - }) -} + }); +}; function randomValue(base, len) { // To minimize bias in element selection, we generate a @@ -51,23 +51,23 @@ function randomValue(base, len) { // This requires 4 bytes of randomness per element. return random(len * 4) .then(bytes => { - const out = [] + const out = []; for (let i = 0; i < len; i++) { - const r = bytes.readUInt32BE(4 * i) / 2**32 - out.push(base[Math.floor(r * base.length)]) + const r = bytes.readUInt32BE(4 * i) / 2**32; + out.push(base[Math.floor(r * base.length)]); } - return out.join('') - }) + return out.join(''); + }); } random.base10 = function(len) { - return () => randomValue(BASE10, len) -} + return () => randomValue(BASE10, len); +}; random.base32 = function(len) { - return () => randomValue(BASE32, len) -} + return () => randomValue(BASE32, len); +}; -module.exports = random +module.exports = random; diff --git a/lib/crypto/scrypt.js b/lib/crypto/scrypt.js index b2d030df..84006fef 100644 --- a/lib/crypto/scrypt.js +++ b/lib/crypto/scrypt.js @@ -2,22 +2,22 @@ * 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/. */ -'use strict' +'use strict'; -const crypto = require('crypto') -const P = require('../promise') +const crypto = require('crypto'); +const P = require('../promise'); // Magic numbers from the node crypto docs: // https://nodejs.org/api/crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback -const DEFAULT_N = 16384 -const DEFAULT_R = 8 -const MAXMEM_MULTIPLIER = 256 -const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * DEFAULT_N * DEFAULT_R +const DEFAULT_N = 16384; +const DEFAULT_R = 8; +const MAXMEM_MULTIPLIER = 256; +const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * DEFAULT_N * DEFAULT_R; // The maximum numer of hash operations allowed concurrently. // This can be customized by setting the `maxPending` attribute on the // exported object, or by setting the `scrypt.maxPending` config option. -const DEFAULT_MAX_PENDING = 100 +const DEFAULT_MAX_PENDING = 100; module.exports = function(log, config) { @@ -29,9 +29,9 @@ module.exports = function(log, config) { numPendingHWM: 0, // The maximum number of hash operations that may be in progress. maxPending: DEFAULT_MAX_PENDING - } + }; if (config.scrypt && config.scrypt.hasOwnProperty('maxPending')) { - scrypt.maxPending = config.scrypt.maxPending + scrypt.maxPending = config.scrypt.maxPending; } /** hash - Creates an scrypt hash asynchronously @@ -41,28 +41,28 @@ module.exports = function(log, config) { * @returns {Object} d.promise Deferred promise */ function hash(input, salt, N, r, p, len) { - var d = P.defer() + var d = P.defer(); if (scrypt.maxPending > 0 && scrypt.numPending > scrypt.maxPending) { - log.warn('scrypt.maxPendingExceeded') - d.reject(new Error('too many pending scrypt hashes')) + log.warn('scrypt.maxPendingExceeded'); + d.reject(new Error('too many pending scrypt hashes')); } else { - scrypt.numPending += 1 + scrypt.numPending += 1; if (scrypt.numPending > scrypt.numPendingHWM) { - scrypt.numPendingHWM = scrypt.numPending + scrypt.numPendingHWM = scrypt.numPending; } - let maxmem = DEFAULT_MAXMEM + let maxmem = DEFAULT_MAXMEM; if (N > DEFAULT_N || r > DEFAULT_R) { // Conservatively prevent `memory limit exceeded` errors. See the docs for more info: // https://nodejs.org/api/crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback - maxmem = MAXMEM_MULTIPLIER * (N || DEFAULT_N) * (r || DEFAULT_R) + maxmem = MAXMEM_MULTIPLIER * (N || DEFAULT_N) * (r || DEFAULT_R); } crypto.scrypt(input, salt, len, { N, r, p, maxmem }, (err, hash) => { - scrypt.numPending -= 1 - return err ? d.reject(err) : d.resolve(hash.toString('hex')) - }) + scrypt.numPending -= 1; + return err ? d.reject(err) : d.resolve(hash.toString('hex')); + }); } - return d.promise + return d.promise; } - return scrypt -} + return scrypt; +}; diff --git a/lib/customs.js b/lib/customs.js index 6d8f6226..9cd86e32 100644 --- a/lib/customs.js +++ b/lib/customs.js @@ -2,15 +2,15 @@ * 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/. */ -'use strict' +'use strict'; -const Joi = require('joi') -const createBackendServiceAPI = require('./backendService') -const config = require('../config') +const Joi = require('joi'); +const createBackendServiceAPI = require('./backendService'); +const config = require('../config'); const localizeTimestamp = require('fxa-shared').l10n.localizeTimestamp({ supportedLanguages: config.get('i18n').supportedLanguages, defaultLanguage: config.get('i18n').defaultLanguage -}) +}); module.exports = function (log, error) { @@ -97,30 +97,30 @@ module.exports = function (log, error) { } }, - }) + }); // Perform a deep clone of payload and remove user password. function sanitizePayload(payload) { if (! payload) { - return + return; } - const clonePayload = Object.assign({}, payload) + const clonePayload = Object.assign({}, payload); if (clonePayload.authPW) { - delete clonePayload.authPW + delete clonePayload.authPW; } if (clonePayload.oldAuthPW) { - delete clonePayload.oldAuthPW + delete clonePayload.oldAuthPW; } - return clonePayload + return clonePayload; } function Customs(url) { if (url === 'none') { - const noblock = async function () { return { block: false }} - const noop = async function () {} + const noblock = async function () { return { block: false };}; + const noop = async function () {}; this.api = { check: noblock, checkAuthenticated: noblock, @@ -128,9 +128,9 @@ module.exports = function (log, error) { failedLoginAttempt: noop, passwordReset: noop, close: noop - } + }; } else { - this.api = new CustomsAPI(url, { timeout: 3000 }) + this.api = new CustomsAPI(url, { timeout: 3000 }); } } @@ -142,23 +142,23 @@ module.exports = function (log, error) { headers: request.headers, query: request.query, payload: sanitizePayload(request.payload) - }) - return handleCustomsResult(request, result) - } + }); + return handleCustomsResult(request, result); + }; // Annotate the request and/or throw an error // based on the check result returned by customs-server. function handleCustomsResult (request, result) { if (result.suspect) { - request.app.isSuspiciousRequest = true + request.app.isSuspiciousRequest = true; } if (result.block) { // Log a flow event that the user got blocked. - request.emitMetricsEvent('customs.blocked') + request.emitMetricsEvent('customs.blocked'); - const unblock = !! result.unblock + const unblock = !! result.unblock; if (result.retryAfter) { // Create a localized retryAfterLocalized value from retryAfter. @@ -166,12 +166,12 @@ module.exports = function (log, error) { const retryAfterLocalized = localizeTimestamp.format( Date.now() + result.retryAfter * 1000, request.headers['accept-language'] - ) + ); - throw error.tooManyRequests(result.retryAfter, retryAfterLocalized, unblock) + throw error.tooManyRequests(result.retryAfter, retryAfterLocalized, unblock); } - throw error.requestBlocked(unblock) + throw error.requestBlocked(unblock); } } @@ -180,39 +180,39 @@ module.exports = function (log, error) { action: action, ip: request.app.clientAddress, uid: uid - }) - return handleCustomsResult(request, result) - } + }); + return handleCustomsResult(request, result); + }; Customs.prototype.checkIpOnly = async function (request, action) { const result = await this.api.checkIpOnly({ ip: request.app.clientAddress, action: action - }) - return handleCustomsResult(request, result) - } + }); + return handleCustomsResult(request, result); + }; Customs.prototype.flag = async function (ip, info) { - var email = info.email - var errno = info.errno || error.ERRNO.UNEXPECTED_ERROR + var email = info.email; + var errno = info.errno || error.ERRNO.UNEXPECTED_ERROR; // There's no useful information in the HTTP response, ignore it. await this.api.failedLoginAttempt({ ip: ip, email: email, errno: errno - }) - } + }); + }; Customs.prototype.reset = async function (email) { // There's no useful information in the HTTP response, ignore it. await this.api.passwordReset({ email: email - }) - } + }); + }; Customs.prototype.close = function () { - return this.api.close() - } + return this.api.close(); + }; - return Customs -} + return Customs; +}; diff --git a/lib/db.js b/lib/db.js index b7ae1f24..2f0be494 100644 --- a/lib/db.js +++ b/lib/db.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('./error') -const P = require('./promise') -const Pool = require('./pool') -const random = require('./crypto/random') +const error = require('./error'); +const P = require('./promise'); +const Pool = require('./pool'); +const random = require('./crypto/random'); // To save space in Redis, we serialise session token updates as arrays using // fixed property indices, thereby not encoding any property names. The order @@ -20,13 +20,13 @@ const random = require('./crypto/random') const REDIS_SESSION_TOKEN_PROPERTIES = [ 'lastAccessTime', 'location', 'uaBrowser', 'uaBrowserVersion', 'uaOS', 'uaOSVersion', 'uaDeviceType', 'uaFormFactor' -] +]; -const REDIS_SESSION_TOKEN_LOCATION_INDEX = REDIS_SESSION_TOKEN_PROPERTIES.indexOf('location') +const REDIS_SESSION_TOKEN_LOCATION_INDEX = REDIS_SESSION_TOKEN_PROPERTIES.indexOf('location'); const REDIS_SESSION_TOKEN_LOCATION_PROPERTIES = [ 'city', 'state', 'stateCode', 'country', 'countryCode' -] +]; module.exports = ( config, @@ -35,95 +35,95 @@ module.exports = ( UnblockCode=null ) => { - const features = require('./features')(config) - const SafeUrl = require('./safe-url')(log) + const features = require('./features')(config); + const SafeUrl = require('./safe-url')(log); const { SessionToken, KeyFetchToken, AccountResetToken, PasswordForgotToken, PasswordChangeToken - } = Token - const MAX_AGE_SESSION_TOKEN_WITHOUT_DEVICE = config.tokenLifetimes.sessionTokenWithoutDevice - const { enabled: TOKEN_PRUNING_ENABLED, maxAge: TOKEN_PRUNING_MAX_AGE } = config.tokenPruning + } = Token; + const MAX_AGE_SESSION_TOKEN_WITHOUT_DEVICE = config.tokenLifetimes.sessionTokenWithoutDevice; + const { enabled: TOKEN_PRUNING_ENABLED, maxAge: TOKEN_PRUNING_MAX_AGE } = config.tokenPruning; - const SAFE_URLS = {} + const SAFE_URLS = {}; function setAccountEmails(account) { return this.accountEmails(account.uid) .then((emails) => { - account.emails = emails + account.emails = emails; // Set primary email on account object account.emails.forEach((item) => { - item.isVerified = !! item.isVerified - item.isPrimary = !! item.isPrimary + item.isVerified = !! item.isVerified; + item.isPrimary = !! item.isPrimary; if (item.isPrimary) { - account.primaryEmail = item + account.primaryEmail = item; } - }) + }); - return account - }) + return account; + }); } function DB(options) { - let pooleeOptions = {} + let pooleeOptions = {}; if (config && config.db && config.db.poolee) { - pooleeOptions = config.db.poolee + pooleeOptions = config.db.poolee; } - this.pool = new Pool(options.url, pooleeOptions) - this.redis = require('./redis')({ ...config.redis, ...config.redis.sessionTokens }, log) + this.pool = new Pool(options.url, pooleeOptions); + this.redis = require('./redis')({ ...config.redis, ...config.redis.sessionTokens }, log); } DB.connect = function (options) { - return P.resolve(new DB(options)) - } + return P.resolve(new DB(options)); + }; DB.prototype.close = function () { - const promises = [this.pool.close()] + const promises = [this.pool.close()]; if (this.redis) { - promises.push(this.redis.close()) + promises.push(this.redis.close()); } - return P.all(promises) - } + return P.all(promises); + }; - SAFE_URLS.ping = new SafeUrl('/__heartbeat__', 'db.ping') + SAFE_URLS.ping = new SafeUrl('/__heartbeat__', 'db.ping'); DB.prototype.ping = function () { - return this.pool.get(SAFE_URLS.ping) - } + return this.pool.get(SAFE_URLS.ping); + }; // CREATE - SAFE_URLS.createAccount = new SafeUrl('/account/:uid', 'db.createAccount') + SAFE_URLS.createAccount = new SafeUrl('/account/:uid', 'db.createAccount'); DB.prototype.createAccount = function (data) { - const { uid, email } = data - log.trace('DB.createAccount', { uid, email }) - data.createdAt = data.verifierSetAt = Date.now() - data.normalizedEmail = data.email.toLowerCase() + const { uid, email } = data; + log.trace('DB.createAccount', { uid, email }); + data.createdAt = data.verifierSetAt = Date.now(); + data.normalizedEmail = data.email.toLowerCase(); return this.pool.put(SAFE_URLS.createAccount, { uid }, data) .then( () => data, err => { if (isRecordAlreadyExistsError(err)) { - err = error.accountExists(data.email) + err = error.accountExists(data.email); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.createSessionToken = new SafeUrl('/sessionToken/:id', 'db.createSessionToken') + SAFE_URLS.createSessionToken = new SafeUrl('/sessionToken/:id', 'db.createSessionToken'); DB.prototype.createSessionToken = function (authToken) { - const { uid } = authToken + const { uid } = authToken; - log.trace('DB.createSessionToken', { uid }) + log.trace('DB.createSessionToken', { uid }); return SessionToken.create(authToken) .then(sessionToken => { - const { id } = sessionToken + const { id } = sessionToken; // Ensure there are no clashes with zombie tokens left behind in Redis return this.deleteSessionTokenFromRedis(uid, id) @@ -134,17 +134,17 @@ module.exports = ( tokenId: id }, sessionToken) )) - .then(() => sessionToken) - }) - } + .then(() => sessionToken); + }); + }; - SAFE_URLS.createKeyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.createKeyFetchToken') + SAFE_URLS.createKeyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.createKeyFetchToken'); DB.prototype.createKeyFetchToken = function (authToken) { - log.trace('DB.createKeyFetchToken', { uid: authToken && authToken.uid }) + log.trace('DB.createKeyFetchToken', { uid: authToken && authToken.uid }); return KeyFetchToken.create(authToken) .then( function (keyFetchToken) { - const { id } = keyFetchToken + const { id } = keyFetchToken; return this.pool.put( SAFE_URLS.createKeyFetchToken, { id }, @@ -159,23 +159,23 @@ module.exports = ( ) .then( function () { - return keyFetchToken + return keyFetchToken; } - ) + ); }.bind(this) - ) - } + ); + }; SAFE_URLS.createPasswordForgotToken = new SafeUrl( '/passwordForgotToken/:id', 'db.createPasswordForgotToken' - ) + ); DB.prototype.createPasswordForgotToken = function (emailRecord) { - log.trace('DB.createPasswordForgotToken', { uid: emailRecord && emailRecord.uid }) + log.trace('DB.createPasswordForgotToken', { uid: emailRecord && emailRecord.uid }); return PasswordForgotToken.create(emailRecord) .then( function (passwordForgotToken) { - const { id } = passwordForgotToken + const { id } = passwordForgotToken; return this.pool.put( SAFE_URLS.createPasswordForgotToken, { id }, @@ -190,23 +190,23 @@ module.exports = ( ) .then( function () { - return passwordForgotToken + return passwordForgotToken; } - ) + ); }.bind(this) - ) - } + ); + }; SAFE_URLS.createPasswordChangeToken = new SafeUrl( '/passwordChangeToken/:id', 'db.createPasswordChangeToken' - ) + ); DB.prototype.createPasswordChangeToken = function (data) { - log.trace('DB.createPasswordChangeToken', { uid: data.uid }) + log.trace('DB.createPasswordChangeToken', { uid: data.uid }); return PasswordChangeToken.create(data) .then( function (passwordChangeToken) { - const { id } = passwordChangeToken + const { id } = passwordChangeToken; return this.pool.put( SAFE_URLS.createPasswordChangeToken, { id }, @@ -219,18 +219,18 @@ module.exports = ( ) .then( function () { - return passwordChangeToken + return passwordChangeToken; } - ) + ); }.bind(this) - ) - } + ); + }; // READ - SAFE_URLS.checkPassword = new SafeUrl('/account/:uid/checkPassword', 'db.checkPassword') + SAFE_URLS.checkPassword = new SafeUrl('/account/:uid/checkPassword', 'db.checkPassword'); DB.prototype.checkPassword = function (uid, verifyHash) { - log.trace('DB.checkPassword', { uid, verifyHash }) + log.trace('DB.checkPassword', { uid, verifyHash }); return this.pool.post(SAFE_URLS.checkPassword, { uid }, { @@ -238,69 +238,69 @@ module.exports = ( }) .then( function () { - return true + return true; }, function (err) { if (isIncorrectPasswordError(err)) { - return false + return false; } - throw err + throw err; } - ) - } + ); + }; DB.prototype.accountExists = function (email) { - log.trace('DB.accountExists', { email: email }) + log.trace('DB.accountExists', { email: email }); return this.accountRecord(email) .then( function () { - return true + return true; }, function (err) { if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) { - return false + return false; } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.sessions = new SafeUrl('/account/:uid/sessions', 'db.sessions') + SAFE_URLS.sessions = new SafeUrl('/account/:uid/sessions', 'db.sessions'); DB.prototype.sessions = function (uid) { - log.trace('DB.sessions', { uid }) + log.trace('DB.sessions', { uid }); const promises = [ this.pool.get(SAFE_URLS.sessions, { uid }) .then(sessionTokens => { if (! MAX_AGE_SESSION_TOKEN_WITHOUT_DEVICE) { - return sessionTokens + return sessionTokens; } - const expiredSessionTokens = [] + const expiredSessionTokens = []; // Filter out any expired sessions sessionTokens = sessionTokens.filter(sessionToken => { if (sessionToken.deviceId) { - return true + return true; } if (sessionToken.createdAt > Date.now() - MAX_AGE_SESSION_TOKEN_WITHOUT_DEVICE) { - return true + return true; } - expiredSessionTokens.push(Object.assign({}, sessionToken, { id: sessionToken.tokenId })) - return false - }) + expiredSessionTokens.push(Object.assign({}, sessionToken, { id: sessionToken.tokenId })); + return false; + }); if (expiredSessionTokens.length === 0) { - return sessionTokens + return sessionTokens; } return this.pruneSessionTokens(uid, expiredSessionTokens) .catch(() => {}) - .then(() => sessionTokens) + .then(() => sessionTokens); }) - ] - let isRedisOk = true + ]; + let isRedisOk = true; if (this.redis) { promises.push( @@ -308,203 +308,203 @@ module.exports = ( .then(result => { if (result === false) { // Ensure that we don't return lastAccessTime if redis is down - isRedisOk = false + isRedisOk = false; } - return this.safeUnpackTokensFromRedis(uid, result) + return this.safeUnpackTokensFromRedis(uid, result); }) - ) + ); } return P.all(promises) .spread((mysqlSessionTokens, redisSessionTokens = {}) => { // for each db session token, if there is a matching redis token // overwrite the properties of the db token with the redis token values - const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid) + const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid); const sessions = mysqlSessionTokens.map((sessionToken) => { - const id = sessionToken.tokenId - const redisToken = redisSessionTokens[id] + const id = sessionToken.tokenId; + const redisToken = redisSessionTokens[id]; const mergedToken = Object.assign({}, sessionToken, redisToken, { // Map from the db's tokenId property to this repo's id property id - }) - delete mergedToken.tokenId + }); + delete mergedToken.tokenId; // Don't return potentially-stale lastAccessTime if (! lastAccessTimeEnabled) { - mergedToken.lastAccessTime = null + mergedToken.lastAccessTime = null; } - return mergedToken - }) + return mergedToken; + }); log.info('db.sessions.count', { mysql: mysqlSessionTokens.length, redis: redisSessionTokens.length - }) - return sessions - }) - } + }); + return sessions; + }); + }; - SAFE_URLS.keyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.keyFetchToken') + SAFE_URLS.keyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.keyFetchToken'); DB.prototype.keyFetchToken = function (id) { - log.trace('DB.keyFetchToken', { id }) + log.trace('DB.keyFetchToken', { id }); return this.pool.get(SAFE_URLS.keyFetchToken, { id }) .then( function (data) { - return KeyFetchToken.fromId(id, data) + return KeyFetchToken.fromId(id, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; SAFE_URLS.keyFetchTokenWithVerificationStatus = new SafeUrl( '/keyFetchToken/:id/verified', 'db.keyFetchTokenWithVerificationStatus' - ) + ); DB.prototype.keyFetchTokenWithVerificationStatus = function (id) { - log.trace('DB.keyFetchTokenWithVerificationStatus', { id }) + log.trace('DB.keyFetchTokenWithVerificationStatus', { id }); return this.pool.get(SAFE_URLS.keyFetchTokenWithVerificationStatus, { id }) .then( function (data) { - return KeyFetchToken.fromId(id, data) + return KeyFetchToken.fromId(id, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; - SAFE_URLS.accountResetToken = new SafeUrl('/accountResetToken/:id', 'db.accountResetToken') + SAFE_URLS.accountResetToken = new SafeUrl('/accountResetToken/:id', 'db.accountResetToken'); DB.prototype.accountResetToken = function (id) { - log.trace('DB.accountResetToken', { id }) + log.trace('DB.accountResetToken', { id }); return this.pool.get(SAFE_URLS.accountResetToken, { id }) .then( function (data) { - return AccountResetToken.fromHex(data.tokenData, data) + return AccountResetToken.fromHex(data.tokenData, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; - SAFE_URLS.passwordForgotToken = new SafeUrl('/passwordForgotToken/:id', 'db.passwordForgotToken') + SAFE_URLS.passwordForgotToken = new SafeUrl('/passwordForgotToken/:id', 'db.passwordForgotToken'); DB.prototype.passwordForgotToken = function (id) { - log.trace('DB.passwordForgotToken', { id }) + log.trace('DB.passwordForgotToken', { id }); return this.pool.get(SAFE_URLS.passwordForgotToken, { id }) .then( function (data) { - return PasswordForgotToken.fromHex(data.tokenData, data) + return PasswordForgotToken.fromHex(data.tokenData, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; - SAFE_URLS.passwordChangeToken = new SafeUrl('/passwordChangeToken/:id', 'db.passwordChangeToken') + SAFE_URLS.passwordChangeToken = new SafeUrl('/passwordChangeToken/:id', 'db.passwordChangeToken'); DB.prototype.passwordChangeToken = function (id) { - log.trace('DB.passwordChangeToken', { id }) + log.trace('DB.passwordChangeToken', { id }); return this.pool.get(SAFE_URLS.passwordChangeToken, { id }) .then( function (data) { - return PasswordChangeToken.fromHex(data.tokenData, data) + return PasswordChangeToken.fromHex(data.tokenData, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; /** * This route intended for internal use only. Please use `accountRecord` * for all other uses. */ - SAFE_URLS.emailRecord = new SafeUrl('/emailRecord/:email', 'db.emailRecord') + SAFE_URLS.emailRecord = new SafeUrl('/emailRecord/:email', 'db.emailRecord'); DB.prototype.emailRecord = function (email) { - log.trace('DB.emailRecord', { email }) + log.trace('DB.emailRecord', { email }); return this.pool.get(SAFE_URLS.emailRecord, { email: hexEncode(email) }) .then( (body) => { - return setAccountEmails.call(this, body) + return setAccountEmails.call(this, body); }, (err) => { if (isNotFoundError(err)) { - err = error.unknownAccount(email) + err = error.unknownAccount(email); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.accountRecord = new SafeUrl('/email/:email/account', 'db.accountRecord') + SAFE_URLS.accountRecord = new SafeUrl('/email/:email/account', 'db.accountRecord'); DB.prototype.accountRecord = function (email) { - log.trace('DB.accountRecord', { email }) + log.trace('DB.accountRecord', { email }); return this.pool.get(SAFE_URLS.accountRecord, { email: hexEncode(email) }) .then( (body) => { - return setAccountEmails.call(this, body) + return setAccountEmails.call(this, body); }, (err) => { if (isNotFoundError(err)) { // There is a possibility that this email exists on the account table (ex. deleted from emails table) // Lets check before throwing account not found. - return this.emailRecord(email) + return this.emailRecord(email); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.setPrimaryEmail = new SafeUrl('/email/:email/account/:uid', 'db.setPrimaryEmail') + SAFE_URLS.setPrimaryEmail = new SafeUrl('/email/:email/account/:uid', 'db.setPrimaryEmail'); DB.prototype.setPrimaryEmail = function (uid, email) { - log.trace('DB.setPrimaryEmail', { email }) + log.trace('DB.setPrimaryEmail', { email }); return this.pool.post(SAFE_URLS.setPrimaryEmail, { email: hexEncode(email), uid }) .then( function (body) { - return body + return body; }, function (err) { if (isNotFoundError(err)) { - err = error.unknownAccount(email) + err = error.unknownAccount(email); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.account = new SafeUrl('/account/:uid', 'db.account') + SAFE_URLS.account = new SafeUrl('/account/:uid', 'db.account'); DB.prototype.account = function (uid) { - log.trace('DB.account', { uid }) + log.trace('DB.account', { uid }); return this.pool.get(SAFE_URLS.account, { uid }) .then((body) => { - body.emailVerified = !! body.emailVerified + body.emailVerified = !! body.emailVerified; - return setAccountEmails.call(this, body) + return setAccountEmails.call(this, body); }, (err) => { if (isNotFoundError(err)) { - err = error.unknownAccount() + err = error.unknownAccount(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.devices = new SafeUrl('/account/:uid/devices', 'db.devices') + SAFE_URLS.devices = new SafeUrl('/account/:uid/devices', 'db.devices'); DB.prototype.devices = function (uid) { - log.trace('DB.devices', { uid }) + log.trace('DB.devices', { uid }); if (! uid) { - return P.reject(error.unknownAccount()) + return P.reject(error.unknownAccount()); } const promises = [ this.pool.get(SAFE_URLS.devices, { uid }) - ] - let isRedisOk = true + ]; + let isRedisOk = true; if (this.redis) { promises.push( @@ -512,59 +512,59 @@ module.exports = ( .then(result => { if (result === false) { // Ensure that we don't return lastAccessTime if redis is down - isRedisOk = false + isRedisOk = false; } - return this.safeUnpackTokensFromRedis(uid, result) + return this.safeUnpackTokensFromRedis(uid, result); }) - ) + ); } return P.all(promises) .spread((devices, redisSessionTokens = {}) => { - const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid) + const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid); return devices.map(device => { - return mergeDeviceInfoFromRedis(device, redisSessionTokens, lastAccessTimeEnabled) - }) + return mergeDeviceInfoFromRedis(device, redisSessionTokens, lastAccessTimeEnabled); + }); }) .catch(err =>{ if (isNotFoundError(err)) { - throw error.unknownAccount() + throw error.unknownAccount(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.sessionToken = new SafeUrl('/sessionToken/:id', 'db.sessionToken') + SAFE_URLS.sessionToken = new SafeUrl('/sessionToken/:id', 'db.sessionToken'); DB.prototype.sessionToken = function (id) { - log.trace('DB.sessionToken', { id }) + log.trace('DB.sessionToken', { id }); return this.pool.get(SAFE_URLS.sessionToken, { id }) .then( function (data) { - return SessionToken.fromHex(data.tokenData, data) + return SessionToken.fromHex(data.tokenData, data); }, function (err) { - err = wrapTokenNotFoundError(err) - throw err + err = wrapTokenNotFoundError(err); + throw err; } - ) - } + ); + }; // UPDATE SAFE_URLS.updatePasswordForgotToken = new SafeUrl( '/passwordForgotToken/:id/update', 'db.updatePasswordForgotToken' - ) + ); DB.prototype.updatePasswordForgotToken = function (token) { - log.trace('DB.udatePasswordForgotToken', { uid: token && token.uid }) - const { id } = token + log.trace('DB.udatePasswordForgotToken', { uid: token && token.uid }); + const { id } = token; return this.pool.post( SAFE_URLS.updatePasswordForgotToken, { id }, { tries: token.tries } - ) - } + ); + }; /** * Update cached session-token data, such as timestamps @@ -576,16 +576,16 @@ module.exports = ( * DB, use updateSessionToken instead. */ DB.prototype.touchSessionToken = function (token, geo) { - const { id, uid } = token + const { id, uid } = token; - log.trace('DB.touchSessionToken', { id, uid }) + log.trace('DB.touchSessionToken', { id, uid }); if (! this.redis || ! features.isLastAccessTimeEnabledForUser(uid)) { - return P.resolve() + return P.resolve(); } return this.redis.update(uid, sessionTokens => { - let location + let location; if (geo && geo.location) { location = { city: geo.location.city, @@ -593,10 +593,10 @@ module.exports = ( countryCode: geo.location.countryCode, state: geo.location.state, stateCode: geo.location.stateCode - } + }; } - sessionTokens = unpackTokensFromRedis(sessionTokens) + sessionTokens = unpackTokensFromRedis(sessionTokens); sessionTokens[id] = { lastAccessTime: token.lastAccessTime, @@ -607,11 +607,11 @@ module.exports = ( uaFormFactor: token.uaFormFactor, uaOS: token.uaOS, uaOSVersion: token.uaOSVersion, - } + }; - return packTokensForRedis(sessionTokens) - }) - } + return packTokensForRedis(sessionTokens); + }); + }; /** * Persist updated session-token data to the database. @@ -622,11 +622,11 @@ module.exports = ( * To do a cheaper write of transient metadata that only hits * redis, use touchSessionToken isntead. */ - SAFE_URLS.updateSessionToken = new SafeUrl('/sessionToken/:id/update', 'db.updateSessionToken') + SAFE_URLS.updateSessionToken = new SafeUrl('/sessionToken/:id/update', 'db.updateSessionToken'); DB.prototype.updateSessionToken = function (sessionToken, geo) { - const { id, uid } = sessionToken + const { id, uid } = sessionToken; - log.trace('DB.updateSessionToken', { id, uid }) + log.trace('DB.updateSessionToken', { id, uid }); return this.touchSessionToken(sessionToken, geo) .then(() => { @@ -644,85 +644,85 @@ module.exports = ( mustVerify: sessionToken.mustVerify, lastAccessTime: sessionToken.lastAccessTime } - ) - }) - } + ); + }); + }; DB.prototype.pruneSessionTokens = function (uid, sessionTokens) { - log.trace('DB.pruneSessionTokens', { uid, tokenCount: sessionTokens.length }) + log.trace('DB.pruneSessionTokens', { uid, tokenCount: sessionTokens.length }); if (! this.redis || ! TOKEN_PRUNING_ENABLED || ! features.isLastAccessTimeEnabledForUser(uid)) { - return P.resolve() + return P.resolve(); } const tokenIds = sessionTokens .filter(token => token.createdAt <= Date.now() - TOKEN_PRUNING_MAX_AGE) - .map(token => token.id) + .map(token => token.id); if (tokenIds.length === 0) { - return P.resolve() + return P.resolve(); } return this.redis.update(uid, sessionTokens => { if (! sessionTokens) { - return + return; } - sessionTokens = unpackTokensFromRedis(sessionTokens) + sessionTokens = unpackTokensFromRedis(sessionTokens); - tokenIds.forEach(id => delete sessionTokens[id]) + tokenIds.forEach(id => delete sessionTokens[id]); if (Object.keys(sessionTokens).length > 0) { - return packTokensForRedis(sessionTokens) + return packTokensForRedis(sessionTokens); } - }) - } + }); + }; - SAFE_URLS.device = new SafeUrl('/account/:uid/device/:deviceId', 'db.device') + SAFE_URLS.device = new SafeUrl('/account/:uid/device/:deviceId', 'db.device'); DB.prototype.device = function (uid, deviceId) { - log.trace('DB.device', { uid: uid, id: deviceId }) + log.trace('DB.device', { uid: uid, id: deviceId }); const promises = [ this.pool.get(SAFE_URLS.device, { uid, deviceId }) - ] + ]; - let isRedisOk = true + let isRedisOk = true; if (this.redis) { promises.push( this.safeRedisGet(uid) .then(result => { if (result === false) { // Ensure that we don't return lastAccessTime if redis is down - isRedisOk = false + isRedisOk = false; } - return this.safeUnpackTokensFromRedis(uid, result) + return this.safeUnpackTokensFromRedis(uid, result); }) - ) + ); } return P.all(promises) .spread((device, redisSessionTokens = {}) => { - const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid) - return mergeDeviceInfoFromRedis(device, redisSessionTokens, lastAccessTimeEnabled) + const lastAccessTimeEnabled = isRedisOk && features.isLastAccessTimeEnabledForUser(uid); + return mergeDeviceInfoFromRedis(device, redisSessionTokens, lastAccessTimeEnabled); }) .catch(err =>{ if (isNotFoundError(err)) { - throw error.unknownDevice() + throw error.unknownDevice(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.createDevice = new SafeUrl('/account/:uid/device/:id', 'db.createDevice') + SAFE_URLS.createDevice = new SafeUrl('/account/:uid/device/:id', 'db.createDevice'); DB.prototype.createDevice = function (uid, deviceInfo) { - log.trace('DB.createDevice', { uid: uid, id: deviceInfo.id }) + log.trace('DB.createDevice', { uid: uid, id: deviceInfo.id }); const sessionTokenId = deviceInfo.sessionTokenId; const refreshTokenId = deviceInfo.refreshTokenId; return random.hex(16) .then(id => { - deviceInfo.id = id - deviceInfo.createdAt = Date.now() + deviceInfo.id = id; + deviceInfo.createdAt = Date.now(); return this.pool.put( SAFE_URLS.createDevice, { uid, id }, @@ -737,12 +737,12 @@ module.exports = ( callbackAuthKey: deviceInfo.pushAuthKey, availableCommands: deviceInfo.availableCommands } - ) + ); }) .then( () => { - deviceInfo.pushEndpointExpired = false - return deviceInfo + deviceInfo.pushEndpointExpired = false; + return deviceInfo; }, err => { if (isRecordAlreadyExistsError(err)) { @@ -754,39 +754,39 @@ module.exports = ( // the problem was caused by the unique sessionToken or // refreshToken constraint so return an appropriate error. devices => { - let conflictingDeviceId + let conflictingDeviceId; const isDuplicateDeviceId = devices.reduce((is, device) => { if (is || device.id === deviceInfo.id) { - return true + return true; } if (sessionTokenId && device.sessionToken === sessionTokenId || deviceInfo.refreshTokenId && device.refreshTokenId === deviceInfo.refreshTokenId) { - conflictingDeviceId = device.id + conflictingDeviceId = device.id; } - }, false) + }, false); if (isDuplicateDeviceId) { - return this.createDevice(uid, deviceInfo) + return this.createDevice(uid, deviceInfo); } - throw error.deviceSessionConflict(conflictingDeviceId) + throw error.deviceSessionConflict(conflictingDeviceId); } - ) + ); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.updateDevice = new SafeUrl('/account/:uid/device/:id/update', 'db.updateDevice') + SAFE_URLS.updateDevice = new SafeUrl('/account/:uid/device/:id/update', 'db.updateDevice'); DB.prototype.updateDevice = function (uid, deviceInfo) { - const { id } = deviceInfo - const sessionTokenId = deviceInfo.sessionTokenId + const { id } = deviceInfo; + const sessionTokenId = deviceInfo.sessionTokenId; const refreshTokenId = deviceInfo.refreshTokenId; - log.trace('DB.updateDevice', { uid, id }) + log.trace('DB.updateDevice', { uid, id }); return this.pool.post( SAFE_URLS.updateDevice, { uid, id }, @@ -806,151 +806,151 @@ module.exports = ( () => deviceInfo, err => { if (isNotFoundError(err)) { - throw error.unknownDevice() + throw error.unknownDevice(); } if (isRecordAlreadyExistsError(err)) { // Identify the conflicting device in the error response, // to save a server round-trip for the client. return this.devices(uid) .then(devices => { - let conflictingDeviceId + let conflictingDeviceId; devices.some(device => { if (device.sessionToken === sessionTokenId) { - conflictingDeviceId = device.id - return true + conflictingDeviceId = device.id; + return true; } - }) - throw error.deviceSessionConflict(conflictingDeviceId) - }) + }); + throw error.deviceSessionConflict(conflictingDeviceId); + }); } - throw err + throw err; } - ) - } + ); + }; // DELETE - SAFE_URLS.deleteAccount = new SafeUrl('/account/:uid', 'db.deleteAccount') + SAFE_URLS.deleteAccount = new SafeUrl('/account/:uid', 'db.deleteAccount'); DB.prototype.deleteAccount = function (authToken) { - const { uid } = authToken + const { uid } = authToken; - log.trace('DB.deleteAccount', { uid }) + log.trace('DB.deleteAccount', { uid }); return P.resolve() .then(() => { if (this.redis) { - return this.redis.del(uid) + return this.redis.del(uid); } }) - .then(() => this.pool.del(SAFE_URLS.deleteAccount, { uid })) - } + .then(() => this.pool.del(SAFE_URLS.deleteAccount, { uid })); + }; - SAFE_URLS.deleteSessionToken = new SafeUrl('/sessionToken/:id', 'db.deleteSessionToken') + SAFE_URLS.deleteSessionToken = new SafeUrl('/sessionToken/:id', 'db.deleteSessionToken'); DB.prototype.deleteSessionToken = function (sessionToken) { - const { id, uid } = sessionToken + const { id, uid } = sessionToken; - log.trace('DB.deleteSessionToken', { id, uid }) + log.trace('DB.deleteSessionToken', { id, uid }); return this.deleteSessionTokenFromRedis(uid, id) - .then(() => this.pool.del(SAFE_URLS.deleteSessionToken, { id })) - } + .then(() => this.pool.del(SAFE_URLS.deleteSessionToken, { id })); + }; - SAFE_URLS.deleteKeyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.deleteKeyFetchToken') + SAFE_URLS.deleteKeyFetchToken = new SafeUrl('/keyFetchToken/:id', 'db.deleteKeyFetchToken'); DB.prototype.deleteKeyFetchToken = function (keyFetchToken) { - const { id, uid } = keyFetchToken - log.trace('DB.deleteKeyFetchToken', { id, uid }) - return this.pool.del(SAFE_URLS.deleteKeyFetchToken, { id }) - } + const { id, uid } = keyFetchToken; + log.trace('DB.deleteKeyFetchToken', { id, uid }); + return this.pool.del(SAFE_URLS.deleteKeyFetchToken, { id }); + }; SAFE_URLS.deleteAccountResetToken = new SafeUrl( '/accountResetToken/:id', 'db.deleteAccountResetToken' - ) + ); DB.prototype.deleteAccountResetToken = function (accountResetToken) { - const { id, uid } = accountResetToken - log.trace('DB.deleteAccountResetToken', { id, uid }) - return this.pool.del(SAFE_URLS.deleteAccountResetToken, { id }) - } + const { id, uid } = accountResetToken; + log.trace('DB.deleteAccountResetToken', { id, uid }); + return this.pool.del(SAFE_URLS.deleteAccountResetToken, { id }); + }; SAFE_URLS.deletePasswordForgotToken = new SafeUrl( '/passwordForgotToken/:id', 'db.deletePasswordForgotToken' - ) + ); DB.prototype.deletePasswordForgotToken = function (passwordForgotToken) { - const { id, uid } = passwordForgotToken - log.trace('DB.deletePasswordForgotToken', { id, uid }) - return this.pool.del(SAFE_URLS.deletePasswordForgotToken, { id }) - } + const { id, uid } = passwordForgotToken; + log.trace('DB.deletePasswordForgotToken', { id, uid }); + return this.pool.del(SAFE_URLS.deletePasswordForgotToken, { id }); + }; SAFE_URLS.deletePasswordChangeToken = new SafeUrl( '/passwordChangeToken/:id', 'db.deletePasswordChangeToken' - ) + ); DB.prototype.deletePasswordChangeToken = function (passwordChangeToken) { - const { id, uid } = passwordChangeToken - log.trace('DB.deletePasswordChangeToken', { id, uid }) - return this.pool.del(SAFE_URLS.deletePasswordChangeToken, { id }) - } + const { id, uid } = passwordChangeToken; + log.trace('DB.deletePasswordChangeToken', { id, uid }); + return this.pool.del(SAFE_URLS.deletePasswordChangeToken, { id }); + }; - SAFE_URLS.deleteDevice = new SafeUrl('/account/:uid/device/:deviceId', 'db.deleteDevice') + SAFE_URLS.deleteDevice = new SafeUrl('/account/:uid/device/:deviceId', 'db.deleteDevice'); DB.prototype.deleteDevice = function (uid, deviceId) { - log.trace('DB.deleteDevice', { uid, id: deviceId }) + log.trace('DB.deleteDevice', { uid, id: deviceId }); return this.pool.del(SAFE_URLS.deleteDevice, { uid, deviceId }) .then(result => this.deleteSessionTokenFromRedis(uid, result.sessionTokenId)) .catch(err => { if (isNotFoundError(err)) { - throw error.unknownDevice() + throw error.unknownDevice(); } - throw err - }) - } + throw err; + }); + }; SAFE_URLS.deviceFromTokenVerificationId = new SafeUrl( '/account/:uid/tokens/:tokenVerificationId/device', 'db.deviceFromTokenVerificationId' - ) + ); DB.prototype.deviceFromTokenVerificationId = function (uid, tokenVerificationId) { - log.trace('DB.deviceFromTokenVerificationId', { uid, tokenVerificationId }) + log.trace('DB.deviceFromTokenVerificationId', { uid, tokenVerificationId }); return this.pool.get(SAFE_URLS.deviceFromTokenVerificationId, { uid, tokenVerificationId }) .catch(err => { if (isNotFoundError(err)) { - throw error.unknownDevice() + throw error.unknownDevice(); } - throw err - }) - } + throw err; + }); + }; // BATCH - SAFE_URLS.resetAccount = new SafeUrl('/account/:uid/reset', 'db.resetAccount') + SAFE_URLS.resetAccount = new SafeUrl('/account/:uid/reset', 'db.resetAccount'); DB.prototype.resetAccount = function (accountResetToken, data) { - const { uid } = accountResetToken + const { uid } = accountResetToken; - log.trace('DB.resetAccount', { uid }) + log.trace('DB.resetAccount', { uid }); return P.resolve() .then(() => { if (this.redis) { - return this.redis.del(uid) + return this.redis.del(uid); } }) .then(() => { - data.verifierSetAt = Date.now() - return this.pool.post(SAFE_URLS.resetAccount, { uid }, data) - }) - } + data.verifierSetAt = Date.now(); + return this.pool.post(SAFE_URLS.resetAccount, { uid }, data); + }); + }; - SAFE_URLS.verifyEmail = new SafeUrl('/account/:uid/verifyEmail/:emailCode', 'db.verifyEmail') + SAFE_URLS.verifyEmail = new SafeUrl('/account/:uid/verifyEmail/:emailCode', 'db.verifyEmail'); DB.prototype.verifyEmail = function (account, emailCode) { - const { uid } = account - log.trace('DB.verifyEmail', { uid, emailCode }) - return this.pool.post(SAFE_URLS.verifyEmail, { uid, emailCode }) - } + const { uid } = account; + log.trace('DB.verifyEmail', { uid, emailCode }); + return this.pool.post(SAFE_URLS.verifyEmail, { uid, emailCode }); + }; - SAFE_URLS.verifyTokens = new SafeUrl('/tokens/:tokenVerificationId/verify', 'db.verifyTokens') + SAFE_URLS.verifyTokens = new SafeUrl('/tokens/:tokenVerificationId/verify', 'db.verifyTokens'); DB.prototype.verifyTokens = function (tokenVerificationId, accountData) { - log.trace('DB.verifyTokens', { tokenVerificationId }) + log.trace('DB.verifyTokens', { tokenVerificationId }); return this.pool.post( SAFE_URLS.verifyTokens, { tokenVerificationId }, @@ -958,33 +958,33 @@ module.exports = ( ) .then( function (body) { - return body + return body; }, function (err) { if (isNotFoundError(err)) { - err = error.invalidVerificationCode() + err = error.invalidVerificationCode(); } - throw err + throw err; } - ) - } + ); + }; SAFE_URLS.verifyTokensWithMethod = new SafeUrl( '/tokens/:tokenId/verifyWithMethod', 'db.verifyTokensWithMethod' - ) + ); DB.prototype.verifyTokensWithMethod = function (tokenId, verificationMethod) { - log.trace('DB.verifyTokensWithMethod', { tokenId, verificationMethod}) + log.trace('DB.verifyTokensWithMethod', { tokenId, verificationMethod}); return this.pool.post( SAFE_URLS.verifyTokensWithMethod, { tokenId }, {verificationMethod} - ) - } + ); + }; - SAFE_URLS.verifyTokenCode = new SafeUrl('/tokens/:code/verifyCode', 'db.verifyTokenCode') + SAFE_URLS.verifyTokenCode = new SafeUrl('/tokens/:code/verifyCode', 'db.verifyTokenCode'); DB.prototype.verifyTokenCode = function (code, accountData) { - log.trace('DB.verifyTokenCode', { code }) + log.trace('DB.verifyTokenCode', { code }); return this.pool.post( SAFE_URLS.verifyTokenCode, { code }, @@ -992,26 +992,26 @@ module.exports = ( ) .then( function (body) { - return body + return body; }, function (err) { if (isExpiredTokenVerificationCodeError(err)) { - err = error.expiredTokenVerficationCode() + err = error.expiredTokenVerficationCode(); } else if (isNotFoundError(err)) { - err = error.invalidTokenVerficationCode() + err = error.invalidTokenVerficationCode(); } - throw err + throw err; } - ) - } + ); + }; SAFE_URLS.forgotPasswordVerified = new SafeUrl( '/passwordForgotToken/:id/verified', 'db.forgotPasswordVerified' - ) + ); DB.prototype.forgotPasswordVerified = function (passwordForgotToken) { - const { id, uid } = passwordForgotToken - log.trace('DB.forgotPasswordVerified', { uid }) + const { id, uid } = passwordForgotToken; + log.trace('DB.forgotPasswordVerified', { uid }); return AccountResetToken.create({ uid }) .then( function (accountResetToken) { @@ -1027,54 +1027,54 @@ module.exports = ( ) .then( function () { - return accountResetToken + return accountResetToken; } - ) + ); }.bind(this) - ) - } + ); + }; - SAFE_URLS.updateLocale = new SafeUrl('/account/:uid/locale', 'db.updateLocale') + SAFE_URLS.updateLocale = new SafeUrl('/account/:uid/locale', 'db.updateLocale'); DB.prototype.updateLocale = function (uid, locale) { - log.trace('DB.updateLocale', { uid, locale }) + log.trace('DB.updateLocale', { uid, locale }); return this.pool.post( SAFE_URLS.updateLocale, { uid }, { locale: locale } - ) - } + ); + }; - SAFE_URLS.securityEvent = new SafeUrl('/securityEvents', 'db.securityEvent') + SAFE_URLS.securityEvent = new SafeUrl('/securityEvents', 'db.securityEvent'); DB.prototype.securityEvent = function (event) { log.trace('DB.securityEvent', { securityEvent: event - }) + }); - return this.pool.post(SAFE_URLS.securityEvent, undefined, event) - } + return this.pool.post(SAFE_URLS.securityEvent, undefined, event); + }; - SAFE_URLS.securityEvents = new SafeUrl('/securityEvents/:uid/ip/:ipAddr', 'db.securityEvents') + SAFE_URLS.securityEvents = new SafeUrl('/securityEvents/:uid/ip/:ipAddr', 'db.securityEvents'); DB.prototype.securityEvents = function (params) { log.trace('DB.securityEvents', { params: params - }) - const { ipAddr, uid } = params - return this.pool.get(SAFE_URLS.securityEvents, { ipAddr, uid }) - } + }); + const { ipAddr, uid } = params; + return this.pool.get(SAFE_URLS.securityEvents, { ipAddr, uid }); + }; - SAFE_URLS.createUnblockCode = new SafeUrl('/account/:uid/unblock/:unblock', 'db.createUnblockCode') + SAFE_URLS.createUnblockCode = new SafeUrl('/account/:uid/unblock/:unblock', 'db.createUnblockCode'); DB.prototype.createUnblockCode = function (uid) { if (! UnblockCode) { return Promise.reject(new Error('Unblock has not been configured')); } - log.trace('DB.createUnblockCode', { uid }) + log.trace('DB.createUnblockCode', { uid }); return UnblockCode() .then( (unblock) => { return this.pool.put(SAFE_URLS.createUnblockCode, { uid, unblock }) .then( () => { - return unblock + return unblock; }, (err) => { // duplicates should be super rare, but it's feasible that a @@ -1083,142 +1083,142 @@ module.exports = ( log.error('DB.createUnblockCode.duplicate', { err: err, uid: uid - }) - return this.createUnblockCode(uid) + }); + return this.createUnblockCode(uid); } - throw err + throw err; } - ) + ); } - ) - } + ); + }; - SAFE_URLS.consumeUnblockCode = new SafeUrl('/account/:uid/unblock/:code', 'db.consumeUnblockCode') + SAFE_URLS.consumeUnblockCode = new SafeUrl('/account/:uid/unblock/:code', 'db.consumeUnblockCode'); DB.prototype.consumeUnblockCode = function (uid, code) { - log.trace('DB.consumeUnblockCode', { uid }) + log.trace('DB.consumeUnblockCode', { uid }); return this.pool.del(SAFE_URLS.consumeUnblockCode, { uid, code }) .catch( function (err) { if (isNotFoundError(err)) { - throw error.invalidUnblockCode() + throw error.invalidUnblockCode(); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.createEmailBounce = new SafeUrl('/emailBounces', 'db.createEmailBounce') + SAFE_URLS.createEmailBounce = new SafeUrl('/emailBounces', 'db.createEmailBounce'); DB.prototype.createEmailBounce = function (bounceData) { log.trace('DB.createEmailBounce', { bouceData: bounceData - }) + }); - return this.pool.post(SAFE_URLS.createEmailBounce, undefined, bounceData) - } + return this.pool.post(SAFE_URLS.createEmailBounce, undefined, bounceData); + }; - SAFE_URLS.emailBounces = new SafeUrl('/emailBounces/:email', 'db.emailBounces') + SAFE_URLS.emailBounces = new SafeUrl('/emailBounces/:email', 'db.emailBounces'); DB.prototype.emailBounces = function (email) { - log.trace('DB.emailBounces', { email }) + log.trace('DB.emailBounces', { email }); - return this.pool.get(SAFE_URLS.emailBounces, { email: hexEncode(email) }) - } + return this.pool.get(SAFE_URLS.emailBounces, { email: hexEncode(email) }); + }; - SAFE_URLS.accountEmails = new SafeUrl('/account/:uid/emails', 'db.accountEmails') + SAFE_URLS.accountEmails = new SafeUrl('/account/:uid/emails', 'db.accountEmails'); DB.prototype.accountEmails = function (uid) { - log.trace('DB.accountEmails', { uid }) + log.trace('DB.accountEmails', { uid }); - return this.pool.get(SAFE_URLS.accountEmails, { uid }) - } + return this.pool.get(SAFE_URLS.accountEmails, { uid }); + }; - SAFE_URLS.getSecondaryEmail = new SafeUrl('/email/:email', 'db.getSecondaryEmail') + SAFE_URLS.getSecondaryEmail = new SafeUrl('/email/:email', 'db.getSecondaryEmail'); DB.prototype.getSecondaryEmail = function (email) { - log.trace('DB.getSecondaryEmail', { email }) + log.trace('DB.getSecondaryEmail', { email }); return this.pool.get(SAFE_URLS.getSecondaryEmail, { email: hexEncode(email) }) .catch((err) => { if (isNotFoundError(err)) { - throw error.unknownSecondaryEmail() + throw error.unknownSecondaryEmail(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.createEmail = new SafeUrl('/account/:uid/emails', 'db.createEmail') + SAFE_URLS.createEmail = new SafeUrl('/account/:uid/emails', 'db.createEmail'); DB.prototype.createEmail = function (uid, emailData) { log.trace('DB.createEmail', { email: emailData.email, uid - }) + }); return this.pool.post(SAFE_URLS.createEmail, { uid }, emailData) .catch( function (err) { if (isEmailAlreadyExistsError(err)) { - throw error.emailExists() + throw error.emailExists(); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.deleteEmail = new SafeUrl('/account/:uid/emails/:email', 'db.deleteEmail') + SAFE_URLS.deleteEmail = new SafeUrl('/account/:uid/emails/:email', 'db.deleteEmail'); DB.prototype.deleteEmail = function (uid, email) { - log.trace('DB.deleteEmail', { uid }) + log.trace('DB.deleteEmail', { uid }); return this.pool.del(SAFE_URLS.deleteEmail, { uid, email: hexEncode(email) }) .catch( function (err) { if (isEmailDeletePrimaryError(err)) { - throw error.cannotDeletePrimaryEmail() + throw error.cannotDeletePrimaryEmail(); } - throw err + throw err; } - ) - } + ); + }; - SAFE_URLS.createSigninCode = new SafeUrl('/signinCodes/:code', 'db.createSigninCode') + SAFE_URLS.createSigninCode = new SafeUrl('/signinCodes/:code', 'db.createSigninCode'); DB.prototype.createSigninCode = function (uid, flowId) { - log.trace('DB.createSigninCode') + log.trace('DB.createSigninCode'); return random.hex(config.signinCodeSize) .then(code => { - const data = { uid, createdAt: Date.now(), flowId } + const data = { uid, createdAt: Date.now(), flowId }; return this.pool.put(SAFE_URLS.createSigninCode, { code }, data) .then(() => code, err => { if (isRecordAlreadyExistsError(err)) { - log.warn('DB.createSigninCode.duplicate') - return this.createSigninCode(uid) + log.warn('DB.createSigninCode.duplicate'); + return this.createSigninCode(uid); } - throw err - }) - }) - } + throw err; + }); + }); + }; - SAFE_URLS.consumeSigninCode = new SafeUrl('/signinCodes/:code/consume', 'db.consumeSigninCode') + SAFE_URLS.consumeSigninCode = new SafeUrl('/signinCodes/:code/consume', 'db.consumeSigninCode'); DB.prototype.consumeSigninCode = function (code) { - log.trace('DB.consumeSigninCode', { code }) + log.trace('DB.consumeSigninCode', { code }); return this.pool.post(SAFE_URLS.consumeSigninCode, { code }) .catch(err => { if (isNotFoundError(err)) { - throw error.invalidSigninCode() + throw error.invalidSigninCode(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.resetAccountTokens = new SafeUrl('/account/:uid/resetTokens', 'db.resetAccountTokens') + SAFE_URLS.resetAccountTokens = new SafeUrl('/account/:uid/resetTokens', 'db.resetAccountTokens'); DB.prototype.resetAccountTokens = function (uid) { - log.trace('DB.resetAccountTokens', { uid }) + log.trace('DB.resetAccountTokens', { uid }); - return this.pool.post(SAFE_URLS.resetAccountTokens, { uid }) - } + return this.pool.post(SAFE_URLS.resetAccountTokens, { uid }); + }; - SAFE_URLS.createTotpToken = new SafeUrl('/totp/:uid', 'db.createTotpToken') + SAFE_URLS.createTotpToken = new SafeUrl('/totp/:uid', 'db.createTotpToken'); DB.prototype.createTotpToken = function (uid, sharedSecret, epoch) { - log.trace('DB.createTotpToken', { uid}) + log.trace('DB.createTotpToken', { uid}); return this.pool.put(SAFE_URLS.createTotpToken, { uid }, { sharedSecret: sharedSecret, @@ -1226,41 +1226,41 @@ module.exports = ( }) .catch(err => { if (isRecordAlreadyExistsError(err)) { - throw error.totpTokenAlreadyExists() + throw error.totpTokenAlreadyExists(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.totpToken = new SafeUrl('/totp/:uid', 'db.totpToken') + SAFE_URLS.totpToken = new SafeUrl('/totp/:uid', 'db.totpToken'); DB.prototype.totpToken = function (uid) { - log.trace('DB.totpToken', { uid}) + log.trace('DB.totpToken', { uid}); return this.pool.get(SAFE_URLS.totpToken, { uid }) .catch(err => { if (isNotFoundError(err)) { - throw error.totpTokenNotFound() + throw error.totpTokenNotFound(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.deleteTotpToken = new SafeUrl('/totp/:uid', 'db.deleteTotpToken') + SAFE_URLS.deleteTotpToken = new SafeUrl('/totp/:uid', 'db.deleteTotpToken'); DB.prototype.deleteTotpToken = function (uid) { - log.trace('DB.deleteTotpToken', { uid}) + log.trace('DB.deleteTotpToken', { uid}); return this.pool.del(SAFE_URLS.deleteTotpToken, { uid }) .catch(err => { if (isNotFoundError(err)) { - throw error.totpTokenNotFound() + throw error.totpTokenNotFound(); } - throw err - }) - } + throw err; + }); + }; - SAFE_URLS.updateTotpToken = new SafeUrl('/totp/:uid/update', 'db.updateTotpToken') + SAFE_URLS.updateTotpToken = new SafeUrl('/totp/:uid/update', 'db.updateTotpToken'); DB.prototype.updateTotpToken = function (uid, data) { - log.trace('DB.updateTotpToken', { uid, data}) + log.trace('DB.updateTotpToken', { uid, data}); return this.pool.post(SAFE_URLS.updateTotpToken, { uid }, { verified: data.verified, @@ -1268,104 +1268,104 @@ module.exports = ( }) .catch(err => { if (isNotFoundError(err)) { - throw error.totpTokenNotFound() + throw error.totpTokenNotFound(); } - throw err - }) - } + throw err; + }); + }; SAFE_URLS.replaceRecoveryCodes = new SafeUrl( '/account/:uid/recoveryCodes', 'db.replaceRecoveryCodes' - ) + ); DB.prototype.replaceRecoveryCodes = function (uid, count) { - log.trace('DB.replaceRecoveryCodes', { uid}) + log.trace('DB.replaceRecoveryCodes', { uid}); - return this.pool.post(SAFE_URLS.replaceRecoveryCodes, { uid }, { count }) - } + return this.pool.post(SAFE_URLS.replaceRecoveryCodes, { uid }, { count }); + }; SAFE_URLS.consumeRecoveryCode = new SafeUrl( '/account/:uid/recoveryCodes/:code', 'db.consumeRecoveryCode' - ) + ); DB.prototype.consumeRecoveryCode = function (uid, code) { - log.trace('DB.consumeRecoveryCode', { uid}) + log.trace('DB.consumeRecoveryCode', { uid}); return this.pool.post(SAFE_URLS.consumeRecoveryCode, { uid, code }) .catch((err) => { if (isNotFoundError(err)) { - throw error.recoveryCodeNotFound() + throw error.recoveryCodeNotFound(); } - throw err - }) - } + throw err; + }); + }; SAFE_URLS.createRecoveryKey = new SafeUrl( '/account/:uid/recoveryKey', 'db.createRecoveryKey' - ) + ); DB.prototype.createRecoveryKey = function (uid, recoveryKeyId, recoveryData) { - log.trace('DB.createRecoveryKey', { uid}) + log.trace('DB.createRecoveryKey', { uid}); return this.pool.post(SAFE_URLS.createRecoveryKey, { uid }, { recoveryKeyId, recoveryData }) .catch((err) => { if (isRecordAlreadyExistsError(err)) { - throw error.recoveryKeyExists() + throw error.recoveryKeyExists(); } - throw err - }) - } + throw err; + }); + }; SAFE_URLS.getRecoveryKey = new SafeUrl( '/account/:uid/recoveryKey/:recoveryKeyId', 'db.getRecoveryKey' - ) + ); DB.prototype.getRecoveryKey = function (uid, recoveryKeyId) { - log.trace('DB.getRecoveryKey', { uid}) + log.trace('DB.getRecoveryKey', { uid}); return this.pool.get(SAFE_URLS.getRecoveryKey, {uid, recoveryKeyId}) .catch(err => { if (isNotFoundError(err)) { - throw error.recoveryKeyNotFound() + throw error.recoveryKeyNotFound(); } if (isInvalidRecoveryError(err)) { - throw error.recoveryKeyInvalid() + throw error.recoveryKeyInvalid(); } - throw err - }) - } + throw err; + }); + }; SAFE_URLS.recoveryKeyExists = new SafeUrl( '/account/:uid/recoveryKey', 'db.recoveryKeyExists' - ) + ); DB.prototype.recoveryKeyExists = function (uid) { - log.trace('DB.recoveryKeyExists', { uid}) + log.trace('DB.recoveryKeyExists', { uid}); - return this.pool.get(SAFE_URLS.recoveryKeyExists, {uid}) - } + return this.pool.get(SAFE_URLS.recoveryKeyExists, {uid}); + }; SAFE_URLS.deleteRecoveryKey = new SafeUrl( '/account/:uid/recoveryKey', 'db.deleteRecoveryKey' - ) + ); DB.prototype.deleteRecoveryKey = function (uid) { - log.trace('DB.deleteRecoveryKey', { uid}) + log.trace('DB.deleteRecoveryKey', { uid}); - return this.pool.del(SAFE_URLS.deleteRecoveryKey, { uid }) - } + return this.pool.del(SAFE_URLS.deleteRecoveryKey, { uid }); + }; DB.prototype.safeRedisGet = function (key) { return this.redis.get(key) .catch(err => { - log.error('redis.get.error', { key, err: err.message }) + log.error('redis.get.error', { key, err: err.message }); // Allow callers to distinguish between the null result and connection errors - return false - }) - } + return false; + }); + }; // Unpacks a tokens string from Redis, with logic to recover from it being // invalid JSON. In this case, "recover" means "delete the data from Redis and @@ -1378,42 +1378,42 @@ module.exports = ( return P.resolve() .then(() => unpackTokensFromRedis(tokens)) .catch(err => { - log.error('db.unpackTokensFromRedis.error', { err: err.message }) + log.error('db.unpackTokensFromRedis.error', { err: err.message }); if (err instanceof SyntaxError) { return this.redis.del(uid) - .then(() => ({})) + .then(() => ({})); } - throw err - }) - } + throw err; + }); + }; DB.prototype.deleteSessionTokenFromRedis = function (uid, id) { if (! this.redis) { - return P.resolve() + return P.resolve(); } return this.redis.update(uid, sessionTokens => { if (! sessionTokens) { - return + return; } - sessionTokens = unpackTokensFromRedis(sessionTokens) + sessionTokens = unpackTokensFromRedis(sessionTokens); - delete sessionTokens[id] + delete sessionTokens[id]; if (Object.keys(sessionTokens).length > 0) { - return packTokensForRedis(sessionTokens) + return packTokensForRedis(sessionTokens); } - }) - } + }); + }; function mergeDeviceInfoFromRedis(device, redisSessionTokens, lastAccessTimeEnabled) { // If there's a matching sessionToken in redis, use the more up-to-date // location and access-time info from there rather than from the DB. - const token = redisSessionTokens[device.sessionTokenId] - const mergedInfo = Object.assign({}, device, token) + const token = redisSessionTokens[device.sessionTokenId]; + const mergedInfo = Object.assign({}, device, token); return { id: mergedInfo.id, sessionToken: mergedInfo.sessionTokenId, @@ -1433,7 +1433,7 @@ module.exports = ( uaOSVersion: mergedInfo.uaOSVersion, uaDeviceType: mergedInfo.uaDeviceType, uaFormFactor: mergedInfo.uaFormFactor - } + }; } // Reduce redis memory usage by not encoding the keys. Store properties @@ -1441,41 +1441,41 @@ module.exports = ( // structure as its argument, returns the packed string. function packTokensForRedis (tokens) { return JSON.stringify(Object.keys(tokens).reduce((result, tokenId) => { - const unpackedToken = tokens[tokenId] + const unpackedToken = tokens[tokenId]; result[tokenId] = truncatePackedArray(REDIS_SESSION_TOKEN_PROPERTIES.map( (property, index) => { - const value = unpackedToken[property] + const value = unpackedToken[property]; if (index === REDIS_SESSION_TOKEN_LOCATION_INDEX && value) { return truncatePackedArray(REDIS_SESSION_TOKEN_LOCATION_PROPERTIES.map( locationProperty => value[locationProperty] - )) + )); } - return unpackedToken[property] + return unpackedToken[property]; } - )) + )); - return result - }, {})) + return result; + }, {})); } // Trailing null and undefined don't need to be stored. function truncatePackedArray (array) { - const length = array.length + const length = array.length; if (length === 0) { - return array + return array; } - const item = array[length - 1] + const item = array[length - 1]; if (item !== null && item !== undefined) { - return array + return array; } - array.pop() + array.pop(); - return truncatePackedArray(array) + return truncatePackedArray(array); } // Sanely unpack both the packed and raw formats from redis. Takes a redis @@ -1484,79 +1484,79 @@ module.exports = ( // structure. function unpackTokensFromRedis (tokens) { if (! tokens) { - return {} + return {}; } - tokens = JSON.parse(tokens) + tokens = JSON.parse(tokens); return Object.keys(tokens).reduce((result, tokenId) => { - const packedToken = tokens[tokenId] + const packedToken = tokens[tokenId]; if (Array.isArray(packedToken)) { - const unpackedToken = unpackToken(packedToken, REDIS_SESSION_TOKEN_PROPERTIES) + const unpackedToken = unpackToken(packedToken, REDIS_SESSION_TOKEN_PROPERTIES); - const location = unpackedToken.location + const location = unpackedToken.location; if (Array.isArray(location)) { - unpackedToken.location = unpackToken(location, REDIS_SESSION_TOKEN_LOCATION_PROPERTIES) + unpackedToken.location = unpackToken(location, REDIS_SESSION_TOKEN_LOCATION_PROPERTIES); } - result[tokenId] = unpackedToken + result[tokenId] = unpackedToken; } else { - result[tokenId] = packedToken + result[tokenId] = packedToken; } - return result - }, {}) + return result; + }, {}); } function unpackToken (packedToken, properties) { return properties.reduce((result, property, index) => { - result[property] = packedToken[index] - return result - }, {}) + result[property] = packedToken[index]; + return result; + }, {}); } function wrapTokenNotFoundError (err) { if (isNotFoundError(err)) { - err = error.invalidToken('The authentication token could not be found') + err = error.invalidToken('The authentication token could not be found'); } - return err + return err; } function hexEncode(str) { - return Buffer.from(str, 'utf8').toString('hex') + return Buffer.from(str, 'utf8').toString('hex'); } - return DB -} + return DB; +}; // Note that these errno's are defined in the fxa-auth-db-mysql repo // and don't necessarily match the errnos in this repo... function isRecordAlreadyExistsError (err) { - return err.statusCode === 409 && err.errno === 101 + return err.statusCode === 409 && err.errno === 101; } function isIncorrectPasswordError (err) { - return err.statusCode === 400 && err.errno === 103 + return err.statusCode === 400 && err.errno === 103; } function isNotFoundError (err) { - return err.statusCode === 404 && err.errno === 116 + return err.statusCode === 404 && err.errno === 116; } function isEmailAlreadyExistsError (err) { - return err.statusCode === 409 && err.errno === 101 + return err.statusCode === 409 && err.errno === 101; } function isEmailDeletePrimaryError (err) { - return err.statusCode === 400 && err.errno === 136 + return err.statusCode === 400 && err.errno === 136; } function isExpiredTokenVerificationCodeError (err) { - return err.statusCode === 400 && err.errno === 137 + return err.statusCode === 400 && err.errno === 137; } function isInvalidRecoveryError (err) { - return err.statusCode === 400 && err.errno === 159 + return err.statusCode === 400 && err.errno === 159; } diff --git a/lib/devices.js b/lib/devices.js index 6dc3c790..9b9f5b80 100644 --- a/lib/devices.js +++ b/lib/devices.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const isA = require('joi') -const validators = require('./routes/validators') +const isA = require('joi'); +const validators = require('./routes/validators'); const { DISPLAY_SAFE_UNICODE_WITH_NON_BMP, HEX_STRING, URL_SAFE_BASE_64 -} = validators -const PUSH_SERVER_REGEX = require('../config').get('push.allowedServerRegex') +} = validators; +const PUSH_SERVER_REGEX = require('../config').get('push.allowedServerRegex'); const SCHEMA = { id: isA.string().length(32).regex(HEX_STRING), @@ -32,98 +32,98 @@ const SCHEMA = { pushEndpointExpired: isA.boolean().strict(), // An object mapping command names to metadata bundles. availableCommands: isA.object().pattern(validators.DEVICE_COMMAND_NAME, isA.string().max(2048)) -} +}; module.exports = (log, db, push) => { - return { isSpuriousUpdate, upsert, synthesizeName } + return { isSpuriousUpdate, upsert, synthesizeName }; // Clients have been known to send spurious device updates, // which generates lots of unnecessary database load. // Check if anything has actually changed. function isSpuriousUpdate (payload, token) { if (! token.deviceId || payload.id !== token.deviceId) { - return false + return false; } if (payload.name && payload.name !== token.deviceName) { - return false + return false; } if (payload.type && payload.type !== token.deviceType) { - return false + return false; } if (payload.pushCallback && payload.pushCallback !== token.deviceCallbackURL) { - return false + return false; } if (payload.pushPublicKey && payload.pushPublicKey !== token.deviceCallbackPublicKey) { - return false + return false; } if (payload.availableCommands) { if (! token.deviceAvailableCommands) { - return false + return false; } if (! isLike(token.deviceAvailableCommands, payload.availableCommands)) { - return false + return false; } if (! isLike(payload.availableCommands, token.deviceAvailableCommands)) { - return false + return false; } } - return true + return true; } function upsert (request, credentials, deviceInfo) { - let operation, event, result + let operation, event, result; if (deviceInfo.id) { - operation = 'updateDevice' - event = 'device.updated' + operation = 'updateDevice'; + event = 'device.updated'; } else { - operation = 'createDevice' - event = 'device.created' + operation = 'createDevice'; + event = 'device.created'; if (! deviceInfo.name) { - deviceInfo.name = credentials.client && credentials.client.name || '' + deviceInfo.name = credentials.client && credentials.client.name || ''; } } - deviceInfo.sessionTokenId = credentials.id - deviceInfo.refreshTokenId = credentials.refreshTokenId + deviceInfo.sessionTokenId = credentials.id; + deviceInfo.refreshTokenId = credentials.refreshTokenId; - const isPlaceholderDevice = ! deviceInfo.id && ! deviceInfo.name && ! deviceInfo.type + const isPlaceholderDevice = ! deviceInfo.id && ! deviceInfo.name && ! deviceInfo.type; return db[operation](credentials.uid, deviceInfo) .then(device => { - result = device + result = device; return request.emitMetricsEvent(event, { uid: credentials.uid, device_id: result.id, is_placeholder: isPlaceholderDevice - }) + }); }) .then(() => { if (operation === 'createDevice') { // Clients expect this notification to always include a name, // so try to synthesize one if necessary. - let deviceName = result.name + let deviceName = result.name; if (! deviceName) { - deviceName = synthesizeName(deviceInfo) + deviceName = synthesizeName(deviceInfo); } if (credentials.tokenVerified) { request.app.devices.then(devices => { - const otherDevices = devices.filter(device => device.id !== result.id) - return push.notifyDeviceConnected(credentials.uid, otherDevices, deviceName) - }) + const otherDevices = devices.filter(device => device.id !== result.id); + return push.notifyDeviceConnected(credentials.uid, otherDevices, deviceName); + }); } if (isPlaceholderDevice) { log.info('device:createPlaceholder', { uid: credentials.uid, id: result.id - }) + }); } return log.notifyAttachedServices('device:create', request, { uid: credentials.uid, @@ -131,55 +131,55 @@ module.exports = (log, db, push) => { type: result.type, timestamp: result.createdAt, isPlaceholder: isPlaceholderDevice - }) + }); } }) .then(function () { - delete result.sessionTokenId - delete result.refreshTokenId - return result - }) + delete result.sessionTokenId; + delete result.refreshTokenId; + return result; + }); } function synthesizeName (device) { - const uaBrowser = device.uaBrowser - const uaBrowserVersion = device.uaBrowserVersion - const uaOS = device.uaOS - const uaOSVersion = device.uaOSVersion - const uaFormFactor = device.uaFormFactor - let result = '' + const uaBrowser = device.uaBrowser; + const uaBrowserVersion = device.uaBrowserVersion; + const uaOS = device.uaOS; + const uaOSVersion = device.uaOSVersion; + const uaFormFactor = device.uaFormFactor; + let result = ''; if (uaBrowser) { if (uaBrowserVersion) { - const splitIndex = uaBrowserVersion.indexOf('.') - result = `${uaBrowser} ${splitIndex === -1 ? uaBrowserVersion : uaBrowserVersion.substr(0, splitIndex)}` + const splitIndex = uaBrowserVersion.indexOf('.'); + result = `${uaBrowser} ${splitIndex === -1 ? uaBrowserVersion : uaBrowserVersion.substr(0, splitIndex)}`; } else { - result = uaBrowser + result = uaBrowser; } if (uaOS || uaFormFactor) { - result += ', ' + result += ', '; } } if (uaFormFactor) { - return `${result}${uaFormFactor}` + return `${result}${uaFormFactor}`; } if (uaOS) { - result += uaOS + result += uaOS; if (uaOSVersion) { - result += ` ${uaOSVersion}` + result += ` ${uaOSVersion}`; } } - return result + return result; } -} +}; -module.exports.schema = SCHEMA +module.exports.schema = SCHEMA; function isLike (object, archetype) { - return Object.entries(archetype).every(([ key, value ]) => object[key] === value) + return Object.entries(archetype).every(([ key, value ]) => object[key] === value); } diff --git a/lib/email/bounces.js b/lib/email/bounces.js index d9b57c1c..bc1a992b 100644 --- a/lib/email/bounces.js +++ b/lib/email/bounces.js @@ -2,32 +2,32 @@ * 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/. */ -'use strict' +'use strict'; -const eaddrs = require('email-addresses') -const P = require('./../promise') -const utils = require('./utils/helpers') -const isValidEmailAddress = require('./../routes/validators').isValidEmailAddress -const SIX_HOURS = 1000 * 60 * 60 * 6 +const eaddrs = require('email-addresses'); +const P = require('./../promise'); +const utils = require('./utils/helpers'); +const isValidEmailAddress = require('./../routes/validators').isValidEmailAddress; +const SIX_HOURS = 1000 * 60 * 60 * 6; module.exports = function (log, error) { return function start(bounceQueue, db) { function accountDeleted(uid, email) { - log.info('accountDeleted', { uid: uid, email: email }) + log.info('accountDeleted', { uid: uid, email: email }); } function gotError(email, err) { - log.error('databaseError', { email: email, err: err }) + log.error('databaseError', { email: email, err: err }); } function findEmailRecord(email) { - return db.accountRecord(email) + return db.accountRecord(email); } function recordBounce(bounce) { - return db.createEmailBounce(bounce) + return db.createEmailBounce(bounce); } function deleteAccountIfUnverifiedNew(record) { @@ -37,43 +37,43 @@ module.exports = function (log, error) { .then( accountDeleted.bind(null, record.uid, record.email), gotError.bind(null, record.email) - ) + ); } } function handleBounce(message) { - utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'bounce') + utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'bounce'); - var recipients = [] + var recipients = []; // According to the AWS SES docs, a notification will never // include multiple types, so it's fine for us to check for // EITHER bounce OR complaint here. if (message.bounce) { - recipients = message.bounce.bouncedRecipients + recipients = message.bounce.bouncedRecipients; } else if (message.complaint) { - recipients = message.complaint.complainedRecipients + recipients = message.complaint.complainedRecipients; } // SES can now send custom headers if enabled on topic. // Headers are stored as an array of name/value pairs. // Log the `X-Template-Name` header to help track the email template that bounced. // Ref: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html - const templateName = utils.getHeaderValue('X-Template-Name', message) - const language = utils.getHeaderValue('Content-Language', message) + const templateName = utils.getHeaderValue('X-Template-Name', message); + const language = utils.getHeaderValue('Content-Language', message); return P.each(recipients, function (recipient) { // The email address in the bounce message has been handled by an external // system, and depending on the system it can have had some strange things // done to it. Try to normalize as best we can. - let email - let emailIsValid = true - const parsedAddress = eaddrs.parseOneAddress(recipient.emailAddress) + let email; + let emailIsValid = true; + const parsedAddress = eaddrs.parseOneAddress(recipient.emailAddress); if (parsedAddress !== null) { - email = parsedAddress.address + email = parsedAddress.address; } else { - email = recipient.emailAddress + email = recipient.emailAddress; if (! isValidEmailAddress(email)) { - emailIsValid = false + emailIsValid = false; // We couldn't make the recipient address look like a valid email. // Log a warning but don't error out because we still want to // emit flow metrics etc. @@ -81,10 +81,10 @@ module.exports = function (log, error) { email: email, action: recipient.action, diagnosticCode: recipient.diagnosticCode - }) + }); } } - const emailDomain = utils.getAnonymizedEmailDomain(email) + const emailDomain = utils.getAnonymizedEmailDomain(email); const logData = { action: recipient.action, email: email, @@ -92,47 +92,47 @@ module.exports = function (log, error) { bounce: !! message.bounce, diagnosticCode: recipient.diagnosticCode, status: recipient.status - } + }; const bounce = { email: email - } + }; // Template name corresponds directly with the email template that was used if (templateName) { - logData.template = templateName + logData.template = templateName; } if (language) { - logData.lang = language + logData.lang = language; } // Log the type of bounce that occurred // Ref: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#bounce-types if (message.bounce && message.bounce.bounceType) { - bounce.bounceType = logData.bounceType = message.bounce.bounceType + bounce.bounceType = logData.bounceType = message.bounce.bounceType; if (message.bounce.bounceSubType) { - bounce.bounceSubType = logData.bounceSubType = message.bounce.bounceSubType + bounce.bounceSubType = logData.bounceSubType = message.bounce.bounceSubType; } } else if (message.complaint) { // Log the type of complaint and userAgent reported - logData.complaint = !! message.complaint - bounce.bounceType = 'Complaint' + logData.complaint = !! message.complaint; + bounce.bounceType = 'Complaint'; if (message.complaint.userAgent) { - logData.complaintUserAgent = message.complaint.userAgent + logData.complaintUserAgent = message.complaint.userAgent; } if (message.complaint.complaintFeedbackType) { - bounce.bounceSubType = logData.complaintFeedbackType = message.complaint.complaintFeedbackType + bounce.bounceSubType = logData.complaintFeedbackType = message.complaint.complaintFeedbackType; } } // Log the bounced flowEvent and emailEvent metrics - utils.logFlowEventFromMessage(log, message, 'bounced') - utils.logEmailEventFromMessage(log, message, 'bounced', emailDomain) - log.info('handleBounce', logData) + utils.logFlowEventFromMessage(log, message, 'bounced'); + utils.logEmailEventFromMessage(log, message, 'bounced', emailDomain); + log.info('handleBounce', logData); /** * Docs: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#bounce-types @@ -142,36 +142,36 @@ module.exports = function (log, error) { * Code below will fetch the email record and if it is an unverified new account then it will delete * the account. */ - const suggestAccountDeletion = !! bounce.bounceType - const work = [] + const suggestAccountDeletion = !! bounce.bounceType; + const work = []; if (emailIsValid) { work.push(recordBounce(bounce) - .catch(gotError.bind(null, email))) + .catch(gotError.bind(null, email))); if (suggestAccountDeletion) { work.push(findEmailRecord(email) .then( deleteAccountIfUnverifiedNew, gotError.bind(null, email) - )) + )); } } - return P.all(work) + return P.all(work); }).then( function () { // We always delete the message, even if handling some addrs failed. - message.del() + message.del(); } - ) + ); } - bounceQueue.on('data', handleBounce) - bounceQueue.start() + bounceQueue.on('data', handleBounce); + bounceQueue.start(); return { bounceQueue: bounceQueue, handleBounce: handleBounce - } - } -} + }; + }; +}; diff --git a/lib/email/delivery.js b/lib/email/delivery.js index d8c6833b..b44e5a43 100644 --- a/lib/email/delivery.js +++ b/lib/email/delivery.js @@ -2,63 +2,63 @@ * 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/. */ -'use strict' +'use strict'; -var P = require('./../promise') -var utils = require('./utils/helpers') +var P = require('./../promise'); +var utils = require('./utils/helpers'); module.exports = function (log) { return function start(deliveryQueue) { function handleDelivery(message) { - utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'delivery') + utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'delivery'); - var recipients = [] + var recipients = []; if (message.delivery && message.notificationType === 'Delivery') { - recipients = message.delivery.recipients + recipients = message.delivery.recipients; } // SES can now send custom headers if enabled on topic. // Headers are stored as an array of name/value pairs. // Log the `X-Template-Name` header to help track the email template that delivered. // Ref: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html - const templateName = utils.getHeaderValue('X-Template-Name', message) + const templateName = utils.getHeaderValue('X-Template-Name', message); return P.each(recipients, function (recipient) { - var email = recipient - var emailDomain = utils.getAnonymizedEmailDomain(email) + var email = recipient; + var emailDomain = utils.getAnonymizedEmailDomain(email); var logData = { email: email, domain: emailDomain, processingTimeMillis: message.delivery.processingTimeMillis - } + }; // Template name corresponds directly with the email template that was used if (templateName) { - logData.template = templateName + logData.template = templateName; } // Log the delivery flowEvent and emailEvent metrics if available - utils.logFlowEventFromMessage(log, message, 'delivered') - utils.logEmailEventFromMessage(log, message, 'delivered', emailDomain) + utils.logFlowEventFromMessage(log, message, 'delivered'); + utils.logEmailEventFromMessage(log, message, 'delivered', emailDomain); - log.info('handleDelivery', logData) + log.info('handleDelivery', logData); }).then( function () { // We always delete the message, even if handling some addrs failed. - message.del() + message.del(); } - ) + ); } - deliveryQueue.on('data', handleDelivery) - deliveryQueue.start() + deliveryQueue.on('data', handleDelivery); + deliveryQueue.start(); return { deliveryQueue: deliveryQueue, handleDelivery: handleDelivery - } - } -} + }; + }; +}; diff --git a/lib/email/notifications.js b/lib/email/notifications.js index 997f91df..d51d891a 100644 --- a/lib/email/notifications.js +++ b/lib/email/notifications.js @@ -2,58 +2,58 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('../promise') -const utils = require('./utils/helpers') +const P = require('../promise'); +const utils = require('./utils/helpers'); // Account deletion threshold for new unverified accounts that receive // a bounce or complaint notification. Unverified accounts younger than // 6 hours old will be deleted if a bounce or complaint occurs. -const SIX_HOURS = 1000 * 60 * 60 * 6 +const SIX_HOURS = 1000 * 60 * 60 * 6; module.exports = (log, error) => { return (queue, db) => { - queue.start() + queue.start(); queue.on('data', async message => { try { - utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'notification') + utils.logErrorIfHeadersAreWeirdOrMissing(log, message, 'notification'); - let addresses = [], eventType = 'bounced', isDeletionCandidate = false + let addresses = [], eventType = 'bounced', isDeletionCandidate = false; if (message.bounce) { - addresses = message.bounce.bouncedRecipients - isDeletionCandidate = true + addresses = message.bounce.bouncedRecipients; + isDeletionCandidate = true; } else if (message.complaint) { - addresses = message.complaint.complainedRecipients - isDeletionCandidate = true + addresses = message.complaint.complainedRecipients; + isDeletionCandidate = true; } else if (message.delivery) { - addresses = message.delivery.recipients - eventType = 'delivered' + addresses = message.delivery.recipients; + eventType = 'delivered'; } await P.all(addresses.map(async address => { - const domain = utils.getAnonymizedEmailDomain(address) + const domain = utils.getAnonymizedEmailDomain(address); - utils.logFlowEventFromMessage(log, message, eventType) - utils.logEmailEventFromMessage(log, message, eventType, domain) + utils.logFlowEventFromMessage(log, message, eventType); + utils.logEmailEventFromMessage(log, message, eventType, domain); if (isDeletionCandidate) { - const emailRecord = await db.accountRecord(address) + const emailRecord = await db.accountRecord(address); if (! emailRecord.emailVerified && emailRecord.createdAt >= Date.now() - SIX_HOURS) { // A bounce or complaint on a new unverified account is grounds for deletion - await db.deleteAccount(emailRecord) + await db.deleteAccount(emailRecord); - log.info('accountDeleted', { ...emailRecord }) + log.info('accountDeleted', { ...emailRecord }); } } - })) + })); } catch (err) { - log.error('email.notification.error', { err }) + log.error('email.notification.error', { err }); } - message.del() - }) - } -} + message.del(); + }); + }; +}; diff --git a/lib/email/utils/helpers.js b/lib/email/utils/helpers.js index 4ea13261..1ccd83d8 100644 --- a/lib/email/utils/helpers.js +++ b/lib/email/utils/helpers.js @@ -2,82 +2,82 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const config = require(`${ROOT_DIR}/config`) -const emailDomains = require(`${ROOT_DIR}/config/popular-email-domains`) -const P = require('../../promise') +const config = require(`${ROOT_DIR}/config`); +const emailDomains = require(`${ROOT_DIR}/config/popular-email-domains`); +const P = require('../../promise'); -let amplitude +let amplitude; function getInsensitiveHeaderValueFromArray(headerName, headers) { - var value = '' - var headerNameNormalized = headerName.toLowerCase() + var value = ''; + var headerNameNormalized = headerName.toLowerCase(); headers.some(function (header) { if (header.name.toLowerCase() === headerNameNormalized) { - value = header.value - return true + value = header.value; + return true; } - return false - }) + return false; + }); - return value + return value; } function getInsensitiveHeaderValueFromObject(headerName, headers) { - var headerNameNormalized = headerName.toLowerCase() - var value = '' + var headerNameNormalized = headerName.toLowerCase(); + var value = ''; Object.keys(headers).some(function (name) { if (name.toLowerCase() === headerNameNormalized) { - value = headers[name] - return true + value = headers[name]; + return true; } - return false - }) - return value + return false; + }); + return value; } function getHeaderValue(headerName, message){ - const headers = getHeaders(message) + const headers = getHeaders(message); if (Array.isArray(headers)) { - return getInsensitiveHeaderValueFromArray(headerName, headers) + return getInsensitiveHeaderValueFromArray(headerName, headers); } if (headers) { - return getInsensitiveHeaderValueFromObject(headerName, headers) + return getInsensitiveHeaderValueFromObject(headerName, headers); } - return '' + return ''; } function getHeaders (message) { - return message.mail && message.mail.headers || message.headers + return message.mail && message.mail.headers || message.headers; } function logErrorIfHeadersAreWeirdOrMissing (log, message, origin) { - const headers = getHeaders(message) + const headers = getHeaders(message); if (headers) { - const type = typeof headers + const type = typeof headers; if (type === 'object') { - const deviceId = getHeaderValue('X-Device-Id', message) - const uid = getHeaderValue('X-Uid', message) + const deviceId = getHeaderValue('X-Device-Id', message); + const uid = getHeaderValue('X-Uid', message); if (! deviceId && ! uid) { log.warn('emailHeaders.keys', { keys: Object.keys(headers).join(','), template: getHeaderValue('X-Template-Name', message), origin - }) + }); } } else { - log.error('emailHeaders.weird', { type, origin }) + log.error('emailHeaders.weird', { type, origin }); } } else { - log.error('emailHeaders.missing', { origin }) + log.error('emailHeaders.missing', { origin }); } } @@ -87,26 +87,26 @@ function logEmailEventSent(log, message) { templateVersion: message.templateVersion, type: 'sent', flow_id: message.flowId - } + }; - emailEventInfo.locale = getHeaderValue('Content-Language', message) + emailEventInfo.locale = getHeaderValue('Content-Language', message); - const addrs = [message.email].concat(message.ccEmails || []) + const addrs = [message.email].concat(message.ccEmails || []); addrs.forEach(addr => { - const msg = Object.assign({}, emailEventInfo) - msg.domain = getAnonymizedEmailDomain(addr) - log.info('emailEvent', msg) - }) + const msg = Object.assign({}, emailEventInfo); + msg.domain = getAnonymizedEmailDomain(addr); + log.info('emailEvent', msg); + }); logAmplitudeEvent(log, message, Object.assign({ domain: getAnonymizedEmailDomain(message.email) - }, emailEventInfo)) + }, emailEventInfo)); } function logAmplitudeEvent (log, message, eventInfo) { if (! amplitude) { - amplitude = require('../../metrics/amplitude')(log, config.getProperties()) + amplitude = require('../../metrics/amplitude')(log, config.getProperties()); } amplitude(`email.${eventInfo.template}.${eventInfo.type}`, { @@ -133,14 +133,14 @@ function logAmplitudeEvent (log, message, eventInfo) { flowBeginTime: message.flowBeginTime || getHeaderValue('X-Flow-Begin-Time', message), flow_id: eventInfo.flow_id, time: Date.now() - }) + }); } function logEmailEventFromMessage(log, message, type, emailDomain) { - const templateName = getHeaderValue('X-Template-Name', message) - const templateVersion = getHeaderValue('X-Template-Version', message) - const flowId = getHeaderValue('X-Flow-Id', message) - const locale = getHeaderValue('Content-Language', message) + const templateName = getHeaderValue('X-Template-Name', message); + const templateVersion = getHeaderValue('X-Template-Version', message); + const flowId = getHeaderValue('X-Flow-Id', message); + const locale = getHeaderValue('Content-Language', message); const emailEventInfo = { domain: emailDomain, @@ -148,36 +148,36 @@ function logEmailEventFromMessage(log, message, type, emailDomain) { template: templateName, templateVersion, type - } + }; if (flowId) { - emailEventInfo['flow_id'] = flowId + emailEventInfo['flow_id'] = flowId; } if (message.bounce) { - emailEventInfo.bounced = true + emailEventInfo.bounced = true; } if (message.complaint) { - emailEventInfo.complaint = true + emailEventInfo.complaint = true; } - log.info('emailEvent', emailEventInfo) + log.info('emailEvent', emailEventInfo); - logAmplitudeEvent(log, message, emailEventInfo) + logAmplitudeEvent(log, message, emailEventInfo); } function logFlowEventFromMessage(log, message, type) { - const currentTime = Date.now() - const templateName = getHeaderValue('X-Template-Name', message) + const currentTime = Date.now(); + const templateName = getHeaderValue('X-Template-Name', message); // Log flow metrics if `flowId` and `flowBeginTime` specified in headers - const flowId = getHeaderValue('X-Flow-Id', message) - const flowBeginTime = getHeaderValue('X-Flow-Begin-Time', message) - const elapsedTime = currentTime - flowBeginTime + const flowId = getHeaderValue('X-Flow-Id', message); + const flowBeginTime = getHeaderValue('X-Flow-Begin-Time', message); + const elapsedTime = currentTime - flowBeginTime; if (flowId && flowBeginTime && (elapsedTime > 0) && type && templateName) { - const eventName = `email.${templateName}.${type}` + const eventName = `email.${templateName}.${type}`; // Flow events have a specific event and structure that must be emitted. // Ref `gather` in https://github.com/mozilla/fxa-auth-server/blob/master/lib/metrics/context.js @@ -186,11 +186,11 @@ function logFlowEventFromMessage(log, message, type) { time: currentTime, flow_id: flowId, flow_time: elapsedTime - } + }; - log.flowEvent(flowEventInfo) + log.flowEvent(flowEventInfo); } else { - log.error('handleBounce.flowEvent', { templateName, type, flowId, flowBeginTime, currentTime}) + log.error('handleBounce.flowEvent', { templateName, type, flowId, flowBeginTime, currentTime}); } } @@ -198,14 +198,14 @@ function getAnonymizedEmailDomain(email) { // This function returns an email domain if it is considered a popular domain, // which means it is in `./config/popular-email-domains.js`. Otherwise, it // return `other` as domain. - const tokens = email.split('@') - const emailDomain = tokens[1] - var anonymizedEmailDomain = 'other' + const tokens = email.split('@'); + const emailDomain = tokens[1]; + var anonymizedEmailDomain = 'other'; if (emailDomain && emailDomains.has(emailDomain)) { - anonymizedEmailDomain = emailDomain + anonymizedEmailDomain = emailDomain; } - return anonymizedEmailDomain + return anonymizedEmailDomain; } module.exports = { @@ -215,4 +215,4 @@ module.exports = { logFlowEventFromMessage, getHeaderValue, getAnonymizedEmailDomain -} +}; diff --git a/lib/error.js b/lib/error.js index 562d1436..1cb660e2 100644 --- a/lib/error.js +++ b/lib/error.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -var inherits = require('util').inherits -var messages = require('joi/lib/language').errors +var inherits = require('util').inherits; +var messages = require('joi/lib/language').errors; var ERRNO = { SERVER_CONFIG_ERROR: 100, @@ -82,7 +82,7 @@ var ERRNO = { INTERNAL_VALIDATION_ERROR: 998, UNEXPECTED_ERROR: 999 -} +}; const DEFAULTS = { code: 500, @@ -90,16 +90,16 @@ const DEFAULTS = { errno: ERRNO.UNEXPECTED_ERROR, message: 'Unspecified error', info: 'https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format' -} +}; -const TOO_LARGE = /^Payload (?:content length|size) greater than maximum allowed/ +const TOO_LARGE = /^Payload (?:content length|size) greater than maximum allowed/; const BAD_SIGNATURE_ERRORS = [ 'Bad mac', 'Unknown algorithm', 'Missing required payload hash', 'Payload is invalid' -] +]; // Payload properties that might help us debug unexpected errors // when they show up in production. Obviously we don't want to @@ -132,16 +132,16 @@ const DEBUGGABLE_PAYLOAD_KEYS = new Set([ 'type', 'unblockCode', 'verificationMethod', -]) +]); function AppError(options, extra, headers) { - this.message = options.message || DEFAULTS.message - this.isBoom = true - this.stack = options.stack + this.message = options.message || DEFAULTS.message; + this.isBoom = true; + this.stack = options.stack; if (! this.stack) { - Error.captureStackTrace(this, AppError) + Error.captureStackTrace(this, AppError); } - this.errno = options.errno || DEFAULTS.errno + this.errno = options.errno || DEFAULTS.errno; this.output = { statusCode: options.code || DEFAULTS.code, payload: { @@ -152,69 +152,69 @@ function AppError(options, extra, headers) { info: options.info || DEFAULTS.info }, headers: headers || {} - } - var keys = Object.keys(extra || {}) + }; + var keys = Object.keys(extra || {}); for (var i = 0; i < keys.length; i++) { - this.output.payload[keys[i]] = extra[keys[i]] + this.output.payload[keys[i]] = extra[keys[i]]; } } -inherits(AppError, Error) +inherits(AppError, Error); AppError.prototype.toString = function () { - return 'Error: ' + this.message -} + return 'Error: ' + this.message; +}; AppError.prototype.header = function (name, value) { - this.output.headers[name] = value -} + this.output.headers[name] = value; +}; AppError.prototype.backtrace = function (traced) { - this.output.payload.log = traced -} + this.output.payload.log = traced; +}; /** Translates an error from Hapi format to our format */ AppError.translate = function (request, response) { - var error + var error; if (response instanceof AppError) { - return response + return response; } - var payload = response.output.payload - var reason = response.reason + var payload = response.output.payload; + var reason = response.reason; if (! payload) { - error = AppError.unexpectedError(request) + error = AppError.unexpectedError(request); } else if (payload.statusCode === 500 && /(socket hang up|ECONNREFUSED)/.test(reason)) { // A connection to a remote service either was not made or timed out. - error = AppError.backendServiceFailure() + error = AppError.backendServiceFailure(); } else if (payload.statusCode === 401) { // These are common errors generated by Hawk auth lib. if (payload.message === 'Unknown credentials' || payload.message === 'Invalid credentials') { - error = AppError.invalidToken('Invalid authentication token: ' + payload.message) + error = AppError.invalidToken('Invalid authentication token: ' + payload.message); } else if (payload.message === 'Stale timestamp') { - error = AppError.invalidTimestamp() + error = AppError.invalidTimestamp(); } else if (payload.message === 'Invalid nonce') { - error = AppError.invalidNonce() + error = AppError.invalidNonce(); } else if (BAD_SIGNATURE_ERRORS.indexOf(payload.message) !== -1) { - error = AppError.invalidSignature(payload.message) + error = AppError.invalidSignature(payload.message); } else { - error = AppError.invalidToken('Invalid authentication token: ' + payload.message) + error = AppError.invalidToken('Invalid authentication token: ' + payload.message); } } else if (payload.validation) { if (payload.message && payload.message.indexOf(messages.any.required) >= 0) { - error = AppError.missingRequestParameter(payload.validation.keys[0]) + error = AppError.missingRequestParameter(payload.validation.keys[0]); } else { - error = AppError.invalidRequestParameter(payload.validation) + error = AppError.invalidRequestParameter(payload.validation); } } else if (payload.statusCode === 413 && TOO_LARGE.test(payload.message)) { - error = AppError.requestBodyTooLarge() + error = AppError.requestBodyTooLarge(); } else { error = new AppError({ @@ -224,13 +224,13 @@ AppError.translate = function (request, response) { errno: payload.errno, info: payload.info, stack: response.stack - }) + }); if (payload.statusCode >= 500) { - decorateErrorWithRequest(error, request) + decorateErrorWithRequest(error, request); } } - return error -} + return error; +}; // Helper functions for creating particular response types. @@ -246,8 +246,8 @@ AppError.dbIncorrectPatchLevel = function (level, levelRequired) { level: level, levelRequired: levelRequired } - ) -} + ); +}; AppError.accountExists = function (email) { return new AppError( @@ -260,8 +260,8 @@ AppError.accountExists = function (email) { { email: email } - ) -} + ); +}; AppError.unknownAccount = function (email) { return new AppError( @@ -274,8 +274,8 @@ AppError.unknownAccount = function (email) { { email: email } - ) -} + ); +}; AppError.incorrectPassword = function (dbEmail, requestEmail) { if (dbEmail !== requestEmail) { @@ -289,7 +289,7 @@ AppError.incorrectPassword = function (dbEmail, requestEmail) { { email: dbEmail } - ) + ); } return new AppError( { @@ -301,8 +301,8 @@ AppError.incorrectPassword = function (dbEmail, requestEmail) { { email: dbEmail } - ) -} + ); +}; AppError.unverifiedAccount = function () { return new AppError({ @@ -310,8 +310,8 @@ AppError.unverifiedAccount = function () { error: 'Bad Request', errno: ERRNO.ACCOUNT_UNVERIFIED, message: 'Unverified account' - }) -} + }); +}; AppError.invalidVerificationCode = function (details) { return new AppError( @@ -322,8 +322,8 @@ AppError.invalidVerificationCode = function (details) { message: 'Invalid verification code' }, details - ) -} + ); +}; AppError.invalidRequestBody = function () { return new AppError({ @@ -331,8 +331,8 @@ AppError.invalidRequestBody = function () { error: 'Bad Request', errno: ERRNO.INVALID_JSON, message: 'Invalid JSON in request body' - }) -} + }); +}; AppError.invalidRequestParameter = function (validation) { return new AppError( @@ -345,8 +345,8 @@ AppError.invalidRequestParameter = function (validation) { { validation: validation } - ) -} + ); +}; AppError.missingRequestParameter = function (param) { return new AppError( @@ -359,8 +359,8 @@ AppError.missingRequestParameter = function (param) { { param: param } - ) -} + ); +}; AppError.invalidSignature = function (message) { return new AppError({ @@ -368,8 +368,8 @@ AppError.invalidSignature = function (message) { error: 'Unauthorized', errno: ERRNO.INVALID_REQUEST_SIGNATURE, message: message || 'Invalid request signature' - }) -} + }); +}; AppError.invalidToken = function (message) { return new AppError({ @@ -377,8 +377,8 @@ AppError.invalidToken = function (message) { error: 'Unauthorized', errno: ERRNO.INVALID_TOKEN, message: message || 'Invalid authentication token in request signature' - }) -} + }); +}; AppError.invalidTimestamp = function () { return new AppError( @@ -391,8 +391,8 @@ AppError.invalidTimestamp = function () { { serverTime: Math.floor(+new Date() / 1000) } - ) -} + ); +}; AppError.invalidNonce = function () { return new AppError({ @@ -400,8 +400,8 @@ AppError.invalidNonce = function () { error: 'Unauthorized', errno: ERRNO.INVALID_NONCE, message: 'Invalid nonce in request signature' - }) -} + }); +}; AppError.missingContentLength = function () { return new AppError({ @@ -409,8 +409,8 @@ AppError.missingContentLength = function () { error: 'Length Required', errno: ERRNO.MISSING_CONTENT_LENGTH_HEADER, message: 'Missing content-length header' - }) -} + }); +}; AppError.requestBodyTooLarge = function () { return new AppError({ @@ -418,25 +418,25 @@ AppError.requestBodyTooLarge = function () { error: 'Request Entity Too Large', errno: ERRNO.REQUEST_TOO_LARGE, message: 'Request body too large' - }) -} + }); +}; AppError.tooManyRequests = function (retryAfter, retryAfterLocalized, canUnblock) { if (! retryAfter) { - retryAfter = 30 + retryAfter = 30; } var extraData = { retryAfter: retryAfter - } + }; if (retryAfterLocalized) { - extraData.retryAfterLocalized = retryAfterLocalized + extraData.retryAfterLocalized = retryAfterLocalized; } if (canUnblock) { - extraData.verificationMethod = 'email-captcha' - extraData.verificationReason = 'login' + extraData.verificationMethod = 'email-captcha'; + extraData.verificationReason = 'login'; } return new AppError( @@ -450,28 +450,28 @@ AppError.tooManyRequests = function (retryAfter, retryAfterLocalized, canUnblock { 'retry-after': retryAfter } - ) -} + ); +}; AppError.requestBlocked = function (canUnblock) { - var extra + var extra; if (canUnblock) { extra = { verificationMethod: 'email-captcha', verificationReason: 'login' - } + }; } return new AppError({ code: 400, error: 'Request blocked', errno: ERRNO.REQUEST_BLOCKED, message: 'The request was blocked for security reasons' - }, extra) -} + }, extra); +}; AppError.serviceUnavailable = function (retryAfter) { if (! retryAfter) { - retryAfter = 30 + retryAfter = 30; } return new AppError( { @@ -486,12 +486,12 @@ AppError.serviceUnavailable = function (retryAfter) { { 'retry-after': retryAfter } - ) -} + ); +}; AppError.featureNotEnabled = function (retryAfter) { if (! retryAfter) { - retryAfter = 30 + retryAfter = 30; } return new AppError( { @@ -506,8 +506,8 @@ AppError.featureNotEnabled = function (retryAfter) { { 'retry-after': retryAfter } - ) -} + ); +}; AppError.gone = function () { return new AppError({ @@ -515,8 +515,8 @@ AppError.gone = function () { error: 'Gone', errno: ERRNO.ENDPOINT_NOT_SUPPORTED, message: 'This endpoint is no longer supported' - }) -} + }); +}; AppError.mustResetAccount = function (email) { return new AppError( @@ -529,8 +529,8 @@ AppError.mustResetAccount = function (email) { { email: email } - ) -} + ); +}; AppError.unknownDevice = function () { return new AppError( @@ -540,8 +540,8 @@ AppError.unknownDevice = function () { errno: ERRNO.DEVICE_UNKNOWN, message: 'Unknown device' } - ) -} + ); +}; AppError.deviceSessionConflict = function (deviceId) { return new AppError({ @@ -549,8 +549,8 @@ AppError.deviceSessionConflict = function (deviceId) { error: 'Bad Request', errno: ERRNO.DEVICE_CONFLICT, message: 'Session already registered by another device' - }, { deviceId }) -} + }, { deviceId }); +}; AppError.invalidUnblockCode = function () { return new AppError({ @@ -558,8 +558,8 @@ AppError.invalidUnblockCode = function () { error: 'Bad Request', errno: ERRNO.INVALID_UNBLOCK_CODE, message: 'Invalid unblock code' - }) -} + }); +}; AppError.invalidPhoneNumber = () => { return new AppError({ @@ -567,8 +567,8 @@ AppError.invalidPhoneNumber = () => { error: 'Bad Request', errno: ERRNO.INVALID_PHONE_NUMBER, message: 'Invalid phone number' - }) -} + }); +}; AppError.invalidRegion = region => { return new AppError({ @@ -578,8 +578,8 @@ AppError.invalidRegion = region => { message: 'Invalid region' }, { region - }) -} + }); +}; AppError.invalidMessageId = () => { return new AppError({ @@ -587,8 +587,8 @@ AppError.invalidMessageId = () => { error: 'Bad Request', errno: ERRNO.INVALID_MESSAGE_ID, message: 'Invalid message id' - }) -} + }); +}; AppError.messageRejected = (reason, reasonCode) => { return new AppError({ @@ -599,8 +599,8 @@ AppError.messageRejected = (reason, reasonCode) => { }, { reason, reasonCode - }) -} + }); +}; AppError.emailComplaint = (bouncedAt) => { return new AppError({ @@ -610,8 +610,8 @@ AppError.emailComplaint = (bouncedAt) => { message: 'Email account sent complaint' }, { bouncedAt - }) -} + }); +}; AppError.emailBouncedHard = (bouncedAt) => { return new AppError({ @@ -621,8 +621,8 @@ AppError.emailBouncedHard = (bouncedAt) => { message: 'Email account hard bounced' }, { bouncedAt - }) -} + }); +}; AppError.emailBouncedSoft = (bouncedAt) => { return new AppError({ @@ -632,8 +632,8 @@ AppError.emailBouncedSoft = (bouncedAt) => { message: 'Email account soft bounced' }, { bouncedAt - }) -} + }); +}; AppError.emailExists = () => { return new AppError({ @@ -641,8 +641,8 @@ AppError.emailExists = () => { error: 'Bad Request', errno: ERRNO.EMAIL_EXISTS, message: 'Email already exists' - }) -} + }); +}; AppError.cannotDeletePrimaryEmail = () => { return new AppError({ @@ -650,8 +650,8 @@ AppError.cannotDeletePrimaryEmail = () => { error: 'Bad Request', errno: ERRNO.EMAIL_DELETE_PRIMARY, message: 'Can not delete primary email' - }) -} + }); +}; AppError.unverifiedSession = function () { return new AppError({ @@ -659,8 +659,8 @@ AppError.unverifiedSession = function () { error: 'Bad Request', errno: ERRNO.SESSION_UNVERIFIED, message: 'Unverified session' - }) -} + }); +}; AppError.yourPrimaryEmailExists = () => { return new AppError({ @@ -668,8 +668,8 @@ AppError.yourPrimaryEmailExists = () => { error: 'Bad Request', errno: ERRNO.USER_PRIMARY_EMAIL_EXISTS, message: 'Can not add secondary email that is same as your primary' - }) -} + }); +}; AppError.verifiedPrimaryEmailAlreadyExists = () => { return new AppError({ @@ -677,8 +677,8 @@ AppError.verifiedPrimaryEmailAlreadyExists = () => { error: 'Bad Request', errno: ERRNO.VERIFIED_PRIMARY_EMAIL_EXISTS, message: 'Email already exists' - }) -} + }); +}; AppError.verifiedSecondaryEmailAlreadyExists = () => { return new AppError({ @@ -686,8 +686,8 @@ AppError.verifiedSecondaryEmailAlreadyExists = () => { error: 'Bad Request', errno: ERRNO.VERIFIED_SECONDARY_EMAIL_EXISTS, message: 'Email already exists' - }) -} + }); +}; // This error is thrown when someone attempts to add a secondary email // that is the same as the primary email of another account, but the account @@ -698,8 +698,8 @@ AppError.unverifiedPrimaryEmailNewlyCreated = () => { error: 'Bad Request', errno: ERRNO.UNVERIFIED_PRIMARY_EMAIL_NEWLY_CREATED, message: 'Email already exists' - }) -} + }); +}; AppError.cannotLoginWithSecondaryEmail = () => { return new AppError({ @@ -707,8 +707,8 @@ AppError.cannotLoginWithSecondaryEmail = () => { error: 'Bad Request', errno: ERRNO.LOGIN_WITH_SECONDARY_EMAIL, message: 'Sign in with this email type is not currently supported' - }) -} + }); +}; AppError.unknownSecondaryEmail = () => { return new AppError({ @@ -716,8 +716,8 @@ AppError.unknownSecondaryEmail = () => { error: 'Bad Request', errno: ERRNO.SECONDARY_EMAIL_UNKNOWN, message: 'Unknown email' - }) -} + }); +}; AppError.cannotResetPasswordWithSecondaryEmail = () => { return new AppError({ @@ -725,8 +725,8 @@ AppError.cannotResetPasswordWithSecondaryEmail = () => { error: 'Bad Request', errno: ERRNO.RESET_PASSWORD_WITH_SECONDARY_EMAIL, message: 'Reset password with this email type is not currently supported' - }) -} + }); +}; AppError.invalidSigninCode = function () { return new AppError({ @@ -734,8 +734,8 @@ AppError.invalidSigninCode = function () { error: 'Bad Request', errno: ERRNO.INVALID_SIGNIN_CODE, message: 'Invalid signin code' - }) -} + }); +}; AppError.cannotChangeEmailToUnverifiedEmail = function () { return new AppError({ @@ -743,8 +743,8 @@ AppError.cannotChangeEmailToUnverifiedEmail = function () { error: 'Bad Request', errno: ERRNO.CHANGE_EMAIL_TO_UNVERIFIED_EMAIL, message: 'Can not change primary email to an unverified email' - }) -} + }); +}; AppError.cannotChangeEmailToUnownedEmail = function () { return new AppError({ @@ -752,8 +752,8 @@ AppError.cannotChangeEmailToUnownedEmail = function () { error: 'Bad Request', errno: ERRNO.CHANGE_EMAIL_TO_UNOWNED_EMAIL, message: 'Can not change primary email to an email that does not belong to this account' - }) -} + }); +}; AppError.cannotLoginWithEmail = function () { return new AppError({ @@ -761,8 +761,8 @@ AppError.cannotLoginWithEmail = function () { error: 'Bad Request', errno: ERRNO.LOGIN_WITH_INVALID_EMAIL, message: 'This email can not currently be used to login' - }) -} + }); +}; AppError.cannotResendEmailCodeToUnownedEmail = function () { return new AppError({ @@ -770,8 +770,8 @@ AppError.cannotResendEmailCodeToUnownedEmail = function () { error: 'Bad Request', errno: ERRNO.RESEND_EMAIL_CODE_TO_UNOWNED_EMAIL, message: 'Can not resend email code to an email that does not belong to this account' - }) -} + }); +}; AppError.cannotSendEmail = function (isNewAddress) { if (! isNewAddress) { @@ -780,15 +780,15 @@ AppError.cannotSendEmail = function (isNewAddress) { error: 'Internal Server Error', errno: ERRNO.FAILED_TO_SEND_EMAIL, message: 'Failed to send email' - }) + }); } return new AppError({ code: 422, error: 'Unprocessable Entity', errno: ERRNO.FAILED_TO_SEND_EMAIL, message: 'Failed to send email' - }) -} + }); +}; AppError.invalidTokenVerficationCode = function (details) { return new AppError( @@ -799,8 +799,8 @@ AppError.invalidTokenVerficationCode = function (details) { message: 'Invalid token verification code' }, details - ) -} + ); +}; AppError.expiredTokenVerficationCode = function (details) { return new AppError( @@ -811,8 +811,8 @@ AppError.expiredTokenVerficationCode = function (details) { message: 'Expired token verification code' }, details - ) -} + ); +}; AppError.totpTokenAlreadyExists = () => { return new AppError({ @@ -820,8 +820,8 @@ AppError.totpTokenAlreadyExists = () => { error: 'Bad Request', errno: ERRNO.TOTP_TOKEN_EXISTS, message: 'TOTP token already exists for this account.' - }) -} + }); +}; AppError.totpTokenNotFound = () => { return new AppError({ @@ -829,8 +829,8 @@ AppError.totpTokenNotFound = () => { error: 'Bad Request', errno: ERRNO.TOTP_TOKEN_NOT_FOUND, message: 'TOTP token not found.' - }) -} + }); +}; AppError.recoveryCodeNotFound = () => { return new AppError({ @@ -838,8 +838,8 @@ AppError.recoveryCodeNotFound = () => { error: 'Bad Request', errno: ERRNO.RECOVERY_CODE_NOT_FOUND, message: 'Recovery code not found.' - }) -} + }); +}; AppError.unavailableDeviceCommand = () => { return new AppError({ @@ -847,8 +847,8 @@ AppError.unavailableDeviceCommand = () => { error: 'Bad Request', errno: ERRNO.DEVICE_COMMAND_UNAVAILABLE, message: 'Unavailable device command.' - }) -} + }); +}; AppError.recoveryKeyNotFound = () => { return new AppError({ @@ -856,8 +856,8 @@ AppError.recoveryKeyNotFound = () => { error: 'Bad Request', errno: ERRNO.RECOVERY_KEY_NOT_FOUND, message: 'Recovery key not found.' - }) -} + }); +}; AppError.recoveryKeyInvalid = () => { return new AppError({ @@ -865,8 +865,8 @@ AppError.recoveryKeyInvalid = () => { error: 'Bad Request', errno: ERRNO.RECOVERY_KEY_INVALID, message: 'Recovery key is not valid.' - }) -} + }); +}; AppError.totpRequired = () => { return new AppError({ @@ -874,8 +874,8 @@ AppError.totpRequired = () => { error: 'Bad Request', errno: ERRNO.TOTP_REQUIRED, message: 'This request requires two step authentication enabled on your account.' - }) -} + }); +}; AppError.recoveryKeyExists = () => { return new AppError({ @@ -883,8 +883,8 @@ AppError.recoveryKeyExists = () => { error: 'Bad Request', errno: ERRNO.RECOVERY_KEY_EXISTS, message: 'Recovery key already exists.' - }) -} + }); +}; AppError.unknownClientId = (clientId) => { return new AppError({ @@ -894,8 +894,8 @@ AppError.unknownClientId = (clientId) => { message: 'Unknown client_id' }, { clientId - }) -} + }); +}; AppError.staleAuthAt = (authAt) => { return new AppError({ @@ -905,8 +905,8 @@ AppError.staleAuthAt = (authAt) => { message: 'Stale auth timestamp' }, { authAt - }) -} + }); +}; AppError.notPublicClient = function notPublicClient() { return new AppError({ @@ -923,8 +923,8 @@ AppError.redisConflict = () => { error: 'Conflict', errno: ERRNO.REDIS_CONFLICT, message: 'Redis WATCH detected a conflicting update' - }) -} + }); +}; AppError.backendServiceFailure = (service, operation) => { return new AppError({ @@ -935,8 +935,8 @@ AppError.backendServiceFailure = (service, operation) => { }, { service, operation - }) -} + }); +}; AppError.internalValidationError = (op, data) => { return new AppError({ @@ -947,17 +947,17 @@ AppError.internalValidationError = (op, data) => { }, { op, data - }) -} + }); +}; AppError.unexpectedError = request => { - const error = new AppError({}) - decorateErrorWithRequest(error, request) - return error -} + const error = new AppError({}); + decorateErrorWithRequest(error, request); + return error; +}; -module.exports = AppError -module.exports.ERRNO = ERRNO +module.exports = AppError; +module.exports.ERRNO = ERRNO; function decorateErrorWithRequest (error, request) { if (request) { @@ -971,20 +971,20 @@ function decorateErrorWithRequest (error, request) { query: request.query, payload: scrubPii(request.payload), headers: request.headers - } + }; } } function scrubPii (payload) { if (! payload) { - return + return; } return Object.entries(payload).reduce((scrubbed, [ key, value ]) => { if (DEBUGGABLE_PAYLOAD_KEYS.has(key)) { - scrubbed[key] = value + scrubbed[key] = value; } - return scrubbed - }, {}) + return scrubbed; + }, {}); } diff --git a/lib/features.js b/lib/features.js index 4a421b48..438c6f43 100644 --- a/lib/features.js +++ b/lib/features.js @@ -2,15 +2,15 @@ * 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/. */ -'use strict' +'use strict'; -const crypto = require('crypto') -const isA = require('joi') +const crypto = require('crypto'); +const isA = require('joi'); -const SCHEMA = isA.array().items(isA.string()).optional() +const SCHEMA = isA.array().items(isA.string()).optional(); module.exports = config => { - const lastAccessTimeUpdates = config.lastAccessTimeUpdates || {} + const lastAccessTimeUpdates = config.lastAccessTimeUpdates || {}; return { /** @@ -22,7 +22,7 @@ module.exports = config => { */ isLastAccessTimeEnabledForUser (uid) { return lastAccessTimeUpdates.enabled && - isSampledUser(lastAccessTimeUpdates.sampleRate, uid, 'lastAccessTimeUpdates') + isSampledUser(lastAccessTimeUpdates.sampleRate, uid, 'lastAccessTimeUpdates'); }, /** @@ -34,21 +34,21 @@ module.exports = config => { * @param key String */ isSampledUser: isSampledUser - } -} + }; +}; -const HASH_LENGTH = hash('', '').length -const MAX_SAFE_HEX = Number.MAX_SAFE_INTEGER.toString(16) -const MAX_SAFE_HEX_LENGTH = MAX_SAFE_HEX.length - MAX_SAFE_HEX.indexOf('f') -const COHORT_DIVISOR = parseInt(Array(MAX_SAFE_HEX_LENGTH).fill('f').join(''), 16) +const HASH_LENGTH = hash('', '').length; +const MAX_SAFE_HEX = Number.MAX_SAFE_INTEGER.toString(16); +const MAX_SAFE_HEX_LENGTH = MAX_SAFE_HEX.length - MAX_SAFE_HEX.indexOf('f'); +const COHORT_DIVISOR = parseInt(Array(MAX_SAFE_HEX_LENGTH).fill('f').join(''), 16); function isSampledUser (sampleRate, uid, key) { if (sampleRate === 1) { - return true + return true; } if (sampleRate === 0) { - return false + return false; } // Extract the maximum entropy we can safely handle as a number then reduce @@ -56,19 +56,19 @@ function isSampledUser (sampleRate, uid, key) { const cohort = parseInt( hash(uid, key).substr(HASH_LENGTH - MAX_SAFE_HEX_LENGTH), 16 - ) / COHORT_DIVISOR - return cohort < sampleRate + ) / COHORT_DIVISOR; + return cohort < sampleRate; } function hash (uid, key) { // Note that we don't need anything cryptographically secure here, // speed and a good distribution are the requirements. - const h = crypto.createHash('sha1') - h.update(uid) - h.update(key) - return h.digest('hex') + const h = crypto.createHash('sha1'); + h.update(uid); + h.update(key); + return h.digest('hex'); } // Joi schema for endpoints that can take a `features` parameter. -module.exports.schema = SCHEMA +module.exports.schema = SCHEMA; diff --git a/lib/geodb.js b/lib/geodb.js index be9b4bb3..9061d6e7 100644 --- a/lib/geodb.js +++ b/lib/geodb.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const config = require('../config').get('geodb') -const geodb = require('fxa-geodb')(config) -const ACCURACY_MAX_KM = 200 -const ACCURACY_MIN_KM = 25 +const config = require('../config').get('geodb'); +const geodb = require('fxa-geodb')(config); +const ACCURACY_MAX_KM = 200; +const ACCURACY_MIN_KM = 25; /** * Thin wrapper around geodb, to help log the accuracy @@ -15,30 +15,30 @@ const ACCURACY_MIN_KM = 25 * `location` data. On failure, returns an empty object **/ module.exports = log => { - log.info('geodb.start', { enabled: config.enabled, dbPath: config.dbPath }) + log.info('geodb.start', { enabled: config.enabled, dbPath: config.dbPath }); return ip => { if (config.enabled === false) { - return {} + return {}; } try { - const location = geodb(ip) - const accuracy = location.accuracy - let confidence = 'fxa.location.accuracy.' + const location = geodb(ip); + const accuracy = location.accuracy; + let confidence = 'fxa.location.accuracy.'; if (accuracy > ACCURACY_MAX_KM) { - confidence += 'unknown' + confidence += 'unknown'; } else if (accuracy > ACCURACY_MIN_KM && accuracy <= ACCURACY_MAX_KM) { - confidence += 'uncertain' + confidence += 'uncertain'; } else if (accuracy <= ACCURACY_MIN_KM) { - confidence += 'confident' + confidence += 'confident'; } else { - confidence += 'no_accuracy_data' + confidence += 'no_accuracy_data'; } - log.info('geodb.accuracy', { accuracy }) - log.info('geodb.accuracy_confidence', { accuracy_confidence: confidence }) + log.info('geodb.accuracy', { accuracy }); + log.info('geodb.accuracy_confidence', { accuracy_confidence: confidence }); return { location: { @@ -49,10 +49,10 @@ module.exports = log => { stateCode: location.stateCode }, timeZone: location.timeZone - } + }; } catch (err) { - log.error('geodb.1', { err: err.message }) - return {} + log.error('geodb.1', { err: err.message }); + return {}; } - } -} + }; +}; diff --git a/lib/log.js b/lib/log.js index eea7bd59..3d257be8 100644 --- a/lib/log.js +++ b/lib/log.js @@ -2,41 +2,41 @@ * 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/. */ -'use strict' +'use strict'; -const EventEmitter = require('events').EventEmitter -const util = require('util') -const mozlog = require('mozlog') -const config = require('../config') -const logConfig = config.get('log') +const EventEmitter = require('events').EventEmitter; +const util = require('util'); +const mozlog = require('mozlog'); +const config = require('../config'); +const logConfig = config.get('log'); -const CLIENT_ID_TO_SERVICE_NAMES = config.get('oauth.clientIds') || {} +const CLIENT_ID_TO_SERVICE_NAMES = config.get('oauth.clientIds') || {}; function Lug(options) { - EventEmitter.call(this) - this.name = options.name || 'fxa-auth-server' + EventEmitter.call(this); + this.name = options.name || 'fxa-auth-server'; mozlog.config({ app: this.name, level: options.level, stream: options.stderr || process.stderr, fmt: options.fmt - }) - this.logger = mozlog() + }); + this.logger = mozlog(); - this.stdout = options.stdout || process.stdout + this.stdout = options.stdout || process.stdout; - this.notifier = require('./notifier')(this) + this.notifier = require('./notifier')(this); } -util.inherits(Lug, EventEmitter) +util.inherits(Lug, EventEmitter); Lug.prototype.close = function() { -} +}; // Expose the standard error/warn/info/debug/etc log methods. Lug.prototype.trace = function (op, data) { - this.logger.debug(op, data) -} + this.logger.debug(op, data); +}; Lug.prototype.error = function (op, data) { // If the error object contains an email address, @@ -44,32 +44,32 @@ Lug.prototype.error = function (op, data) { // PII-scrubbing tool is able to find it. if (data.err && data.err.email) { if (! data.email) { - data.email = data.err.email + data.email = data.err.email; } - data.err.email = null + data.err.email = null; } - this.logger.error(op, data) -} + this.logger.error(op, data); +}; Lug.prototype.fatal = function (op, data) { - this.logger.critical(op, data) -} + this.logger.critical(op, data); +}; Lug.prototype.warn = function (op, data) { - this.logger.warn(op, data) -} + this.logger.warn(op, data); +}; Lug.prototype.info = function (op, data) { - this.logger.info(op, data) -} + this.logger.info(op, data); +}; Lug.prototype.begin = function (op, request) { - this.logger.debug(op) -} + this.logger.debug(op); +}; Lug.prototype.stat = function (stats) { - this.logger.info('stat', stats) -} + this.logger.info('stat', stats); +}; // Log a request summary line. // This gets called once for each completed request. @@ -78,15 +78,15 @@ Lug.prototype.stat = function (stats) { Lug.prototype.summary = function (request, response) { if (request.method === 'options') { - return + return; } - request.emitRouteFlowEvent(response) + request.emitRouteFlowEvent(response); - const payload = request.payload || {} - const query = request.query || {} - const credentials = (request.auth && request.auth.credentials) || {} - const responseBody = (response && response.source) || {} + const payload = request.payload || {}; + const query = request.query || {}; + const credentials = (request.auth && request.auth.credentials) || {}; + const responseBody = (response && response.source) || {}; const line = { status: (response.isBoom) ? response.output.statusCode : response.statusCode, @@ -111,17 +111,17 @@ Lug.prototype.summary = function (request, response) { method: request.method, email: credentials.email || payload.email || query.email, phoneNumber: responseBody.formattedPhoneNumber, - } + }; if (line.status >= 500) { - line.trace = request.app.traced - line.stack = response.stack - this.error('request.summary', line, response.message) + line.trace = request.app.traced; + line.stack = response.stack; + this.error('request.summary', line, response.message); } else { - this.info('request.summary', line) + this.info('request.summary', line); } -} +}; // Broadcast an event to attached services, such as sync. @@ -132,24 +132,24 @@ Lug.prototype.notifyAttachedServices = function (name, request, data) { metricsContextData => { // Add a timestamp that this event occurred to help attached services resolve any // potential timing issues - data.ts = data.ts || Date.now() / 1000 // Convert to float seconds + data.ts = data.ts || Date.now() / 1000; // Convert to float seconds // convert an oauth client-id to a human readable format, if a name is available. // If no name is available, continue to use the client_id. if (data.service && data.service !== 'sync') { - data.service = CLIENT_ID_TO_SERVICE_NAMES[data.service] || data.service + data.service = CLIENT_ID_TO_SERVICE_NAMES[data.service] || data.service; } const e = { event: name, data: data - } - e.data.metricsContext = metricsContextData - this.info('notify.attached', e) - this.notifier.send(e) + }; + e.data.metricsContext = metricsContextData; + this.info('notify.attached', e); + this.notifier.send(e); } - ) -} + ); +}; // Log an activity metrics event. // These events indicate key points at which a particular @@ -157,53 +157,53 @@ Lug.prototype.notifyAttachedServices = function (name, request, data) { Lug.prototype.activityEvent = function (data) { if (! data || ! data.event || ! data.uid) { - this.error('log.activityEvent', { data }) - return + this.error('log.activityEvent', { data }); + return; } - this.logger.info('activityEvent', data) -} + this.logger.info('activityEvent', data); +}; // Log a flow metrics event. // These events help understand the user's sign-in or sign-up journey. Lug.prototype.flowEvent = function (data) { if (! data || ! data.event || ! data.flow_id || ! data.flow_time || ! data.time) { - this.error('flow.missingData', { data }) - return + this.error('flow.missingData', { data }); + return; } - this.logger.info('flowEvent', data) -} + this.logger.info('flowEvent', data); +}; Lug.prototype.amplitudeEvent = function (data) { if (! data || ! data.event_type || (! data.device_id && ! data.user_id)) { - this.error('amplitude.missingData', { data }) - return + this.error('amplitude.missingData', { data }); + return; } - this.logger.info('amplitudeEvent', data) -} + this.logger.info('amplitudeEvent', data); +}; module.exports = function (level, name, options = {}) { if (arguments.length === 1 && typeof level === 'object') { - options = level - level = options.level - name = options.name + options = level; + level = options.level; + name = options.name; } - options.name = name - options.level = level - options.fmt = logConfig.fmt - var log = new Lug(options) + options.name = name; + options.level = level; + options.fmt = logConfig.fmt; + var log = new Lug(options); log.stdout.on( 'error', function (err) { if (err.code === 'EPIPE') { - log.emit('error', err) + log.emit('error', err); } } - ) + ); - return log -} + return log; +}; diff --git a/lib/metrics/amplitude.js b/lib/metrics/amplitude.js index d2b81ec2..a93b3445 100644 --- a/lib/metrics/amplitude.js +++ b/lib/metrics/amplitude.js @@ -10,10 +10,10 @@ // // https://docs.google.com/spreadsheets/d/1G_8OJGOxeWXdGJ1Ugmykk33Zsl-qAQL05CONSeD4Uz4 -'use strict' +'use strict'; -const { GROUPS, initialize } = require('fxa-shared/metrics/amplitude') -const P = require('../promise') +const { GROUPS, initialize } = require('fxa-shared/metrics/amplitude'); +const P = require('../promise'); // Maps template name to email type const EMAIL_TYPES = { @@ -41,7 +41,7 @@ const EMAIL_TYPES = { verifyPrimaryEmail: 'verify', verifySyncEmail: 'registration', verifySecondaryEmail: 'secondary_email' -} +}; const EVENTS = { 'account.confirmed': { @@ -80,7 +80,7 @@ const EVENTS = { group: GROUPS.sms, event: 'sent' } -} +}; const FUZZY_EVENTS = new Map([ [ /^email\.(\w+)\.bounced$/, { @@ -95,34 +95,34 @@ const FUZZY_EVENTS = new Map([ group: eventCategory => GROUPS[eventCategory], event: 'complete' } ] -]) +]); -const ACCOUNT_RESET_COMPLETE = `${GROUPS.login} - forgot_complete` -const LOGIN_COMPLETE = `${GROUPS.login} - complete` +const ACCOUNT_RESET_COMPLETE = `${GROUPS.login} - forgot_complete`; +const LOGIN_COMPLETE = `${GROUPS.login} - complete`; module.exports = (log, config) => { if (! log || ! config.oauth.clientIds) { - throw new TypeError('Missing argument') + throw new TypeError('Missing argument'); } - const transformEvent = initialize(config.oauth.clientIds, EVENTS, FUZZY_EVENTS) + const transformEvent = initialize(config.oauth.clientIds, EVENTS, FUZZY_EVENTS); - return receiveEvent + return receiveEvent; function receiveEvent (event, request, data = {}, metricsContext = {}) { if (! event || ! request) { - log.error('amplitude.badArgument', { err: 'Bad argument', event, hasRequest: !! request }) - return P.resolve() + log.error('amplitude.badArgument', { err: 'Bad argument', event, hasRequest: !! request }); + return P.resolve(); } return request.app.devices .catch(() => {}) .then(devices => { - const { formFactor } = request.app.ua + const { formFactor } = request.app.ua; if (event === 'flow.complete') { // HACK: Push flowType into the event so it can be parsed as eventCategory - event += `.${metricsContext.flowType}` + event += `.${metricsContext.flowType}`; } const amplitudeEvent = transformEvent({ @@ -141,10 +141,10 @@ module.exports = (log, config) => { emailService: data.email_service, emailTypes: EMAIL_TYPES, service: getService(request, data, metricsContext) - }, getOs(request), getBrowser(request), getLocation(request))) + }, getOs(request), getBrowser(request), getLocation(request))); if (amplitudeEvent) { - log.amplitudeEvent(amplitudeEvent) + log.amplitudeEvent(amplitudeEvent); // HACK: Account reset returns a session token so emit login complete too if (amplitudeEvent.event_type === ACCOUNT_RESET_COMPLETE) { @@ -152,64 +152,64 @@ module.exports = (log, config) => { ...amplitudeEvent, event_type: LOGIN_COMPLETE, time: amplitudeEvent.time + 1 - }) + }); } } - }) + }); } -} +}; function getFromToken (request, key) { if (request.auth && request.auth.credentials) { - return request.auth.credentials[key] + return request.auth.credentials[key]; } } function getFromMetricsContext (metricsContext, key, request, payloadKey) { return metricsContext[key] || - (request.payload && request.payload.metricsContext && request.payload.metricsContext[payloadKey]) + (request.payload && request.payload.metricsContext && request.payload.metricsContext[payloadKey]); } function getOs (request) { - const { os, osVersion } = request.app.ua + const { os, osVersion } = request.app.ua; if (os) { - return { os, osVersion } + return { os, osVersion }; } } function getBrowser (request) { - const { browser, browserVersion } = request.app.ua + const { browser, browserVersion } = request.app.ua; if (browser) { - return { browser, browserVersion } + return { browser, browserVersion }; } } function getLocation (request) { - const { location } = request.app.geo + const { location } = request.app.geo; if (location && (location.country || location.state)) { return { country: location.country, region: location.state - } + }; } } function getService (request, data, metricsContext) { if (data.service) { - return data.service + return data.service; } if (request.payload && request.payload.service) { - return request.payload.service + return request.payload.service; } if (request.query && request.query.service) { - return request.query.service + return request.query.service; } - return metricsContext.service + return metricsContext.service; } diff --git a/lib/metrics/context.js b/lib/metrics/context.js index 4a973b96..b582b1a3 100644 --- a/lib/metrics/context.js +++ b/lib/metrics/context.js @@ -2,21 +2,21 @@ * 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/. */ -'use strict' +'use strict'; -const bufferEqualConstantTime = require('buffer-equal-constant-time') -const crypto = require('crypto') -const HEX_STRING = require('../routes/validators').HEX_STRING -const isA = require('joi') -const P = require('../promise') +const bufferEqualConstantTime = require('buffer-equal-constant-time'); +const crypto = require('crypto'); +const HEX_STRING = require('../routes/validators').HEX_STRING; +const isA = require('joi'); +const P = require('../promise'); -const FLOW_ID_LENGTH = 64 +const FLOW_ID_LENGTH = 64; // These match validation in the content server backend. // We should probably refactor them to fxa-shared. -const ENTRYPOINT_SCHEMA = isA.string().max(128).regex(/^[\w.:-]+$/) -const UTM_SCHEMA = isA.string().max(128).regex(/^[\w\/.%-]+$/) -const UTM_CAMPAIGN_SCHEMA = UTM_SCHEMA.allow('page+referral+-+not+part+of+a+campaign') +const ENTRYPOINT_SCHEMA = isA.string().max(128).regex(/^[\w.:-]+$/); +const UTM_SCHEMA = isA.string().max(128).regex(/^[\w\/.%-]+$/); +const UTM_CAMPAIGN_SCHEMA = UTM_SCHEMA.allow('page+referral+-+not+part+of+a+campaign'); const SCHEMA = isA.object({ // The metrics context device id is a client-generated property @@ -34,10 +34,10 @@ const SCHEMA = isA.object({ utmTerm: UTM_SCHEMA.optional() }) .unknown(false) - .and('flowId', 'flowBeginTime') + .and('flowId', 'flowBeginTime'); module.exports = function (log, config) { - const cache = require('../cache')(log, config, 'fxa-metrics~') + const cache = require('../cache')(log, config, 'fxa-metrics~'); return { stash, @@ -47,7 +47,7 @@ module.exports = function (log, config) { clear, validate, setFlowCompleteSignal - } + }; /** * Stashes metrics context metadata using a key derived from a token. @@ -62,25 +62,25 @@ module.exports = function (log, config) { * @param token token to stash the metadata against */ function stash (token) { - const metadata = this.payload && this.payload.metricsContext + const metadata = this.payload && this.payload.metricsContext; if (! metadata) { - return P.resolve() + return P.resolve(); } - metadata.service = this.payload.service || this.query.service + metadata.service = this.payload.service || this.query.service; return P.resolve() .then(() => { return cache.add(getKey(token), metadata) - .catch(err => log.warn('metricsContext.stash.add', { err })) + .catch(err => log.warn('metricsContext.stash.add', { err })); }) .catch(err => log.error('metricsContext.stash', { err, hasToken: !! token, hasId: !! (token && token.id), hasUid: !! (token && token.uid) - })) + })); } /** @@ -96,18 +96,18 @@ module.exports = function (log, config) { * @param request */ async function get (request) { - let token + let token; try { - const metadata = request.payload && request.payload.metricsContext + const metadata = request.payload && request.payload.metricsContext; if (metadata) { - return metadata + return metadata; } - token = getToken(request) + token = getToken(request); if (token) { - return await cache.get(getKey(token)) || {} + return await cache.get(getKey(token)) || {}; } } catch (err) { log.error('metricsContext.get', { @@ -115,10 +115,10 @@ module.exports = function (log, config) { hasToken: !! token, hasId: !! (token && token.id), hasUid: !! (token && token.uid) - }) + }); } - return {} + return {}; } /** @@ -132,45 +132,45 @@ module.exports = function (log, config) { * @param data target object */ async function gather (data) { - const metadata = await this.app.metricsContext + const metadata = await this.app.metricsContext; if (metadata) { - data.time = Date.now() - data.device_id = metadata.deviceId - data.flow_id = metadata.flowId - data.flow_time = calculateFlowTime(data.time, metadata.flowBeginTime) - data.flowBeginTime = metadata.flowBeginTime - data.flowCompleteSignal = metadata.flowCompleteSignal - data.flowType = metadata.flowType + data.time = Date.now(); + data.device_id = metadata.deviceId; + data.flow_id = metadata.flowId; + data.flow_time = calculateFlowTime(data.time, metadata.flowBeginTime); + data.flowBeginTime = metadata.flowBeginTime; + data.flowCompleteSignal = metadata.flowCompleteSignal; + data.flowType = metadata.flowType; if (metadata.service) { - data.service = metadata.service + data.service = metadata.service; } - const doNotTrack = this.headers && this.headers.dnt === '1' + const doNotTrack = this.headers && this.headers.dnt === '1'; if (! doNotTrack) { - data.entrypoint = metadata.entrypoint - data.utm_campaign = metadata.utmCampaign - data.utm_content = metadata.utmContent - data.utm_medium = metadata.utmMedium - data.utm_source = metadata.utmSource - data.utm_term = metadata.utmTerm + data.entrypoint = metadata.entrypoint; + data.utm_campaign = metadata.utmCampaign; + data.utm_content = metadata.utmContent; + data.utm_medium = metadata.utmMedium; + data.utm_source = metadata.utmSource; + data.utm_term = metadata.utmTerm; } } - return data + return data; } function getToken (request) { if (request.auth && request.auth.credentials) { - return request.auth.credentials + return request.auth.credentials; } if (request.payload && request.payload.uid && request.payload.code) { return { uid: request.payload.uid, id: request.payload.code - } + }; } } @@ -184,12 +184,12 @@ module.exports = function (log, config) { * @param newToken token to stash the metadata against */ function propagate (oldToken, newToken) { - const oldKey = getKey(oldToken) + const oldKey = getKey(oldToken); return cache.get(oldKey) .then(metadata => { if (metadata) { return cache.add(getKey(newToken), metadata) - .catch(err => log.warn('metricsContext.propagate.add', { err })) + .catch(err => log.warn('metricsContext.propagate.add', { err })); } }) .catch(err => log.error('metricsContext.propagate', { @@ -200,7 +200,7 @@ module.exports = function (log, config) { hasNewToken: !! newToken, hasNewTokenId: !! (newToken && newToken.id), hasNewTokenUid: !! (newToken && newToken.uid), - })) + })); } /** @@ -212,11 +212,11 @@ module.exports = function (log, config) { function clear () { return P.resolve() .then(() => { - const token = getToken(this) + const token = getToken(this); if (token) { - return cache.del(getKey(token)) + return cache.del(getKey(token)); } - }) + }); } /** @@ -229,72 +229,72 @@ module.exports = function (log, config) { */ function validate() { if (! this.payload) { - return logInvalidContext(this, 'missing payload') + return logInvalidContext(this, 'missing payload'); } - const metadata = this.payload.metricsContext + const metadata = this.payload.metricsContext; if (! metadata) { - return logInvalidContext(this, 'missing context') + return logInvalidContext(this, 'missing context'); } if (! metadata.flowId) { - return logInvalidContext(this, 'missing flowId') + return logInvalidContext(this, 'missing flowId'); } if (! metadata.flowBeginTime) { - return logInvalidContext(this, 'missing flowBeginTime') + return logInvalidContext(this, 'missing flowBeginTime'); } - const age = Date.now() - metadata.flowBeginTime + const age = Date.now() - metadata.flowBeginTime; if (age > config.metrics.flow_id_expiry || age <= 0) { - return logInvalidContext(this, 'expired flowBeginTime') + return logInvalidContext(this, 'expired flowBeginTime'); } if (! HEX_STRING.test(metadata.flowId)) { - return logInvalidContext(this, 'invalid flowId') + return logInvalidContext(this, 'invalid flowId'); } // The first half of the id is random bytes, the second half is a HMAC of // additional contextual information about the request. It's a simple way // to check that the metrics came from the right place, without having to // share state between content-server and auth-server. - const flowSignature = metadata.flowId.substr(FLOW_ID_LENGTH / 2, FLOW_ID_LENGTH) - const flowSignatureBytes = Buffer.from(flowSignature, 'hex') - const expectedSignatureBytes = calculateFlowSignatureBytes(metadata) + const flowSignature = metadata.flowId.substr(FLOW_ID_LENGTH / 2, FLOW_ID_LENGTH); + const flowSignatureBytes = Buffer.from(flowSignature, 'hex'); + const expectedSignatureBytes = calculateFlowSignatureBytes(metadata); if (! bufferEqualConstantTime(flowSignatureBytes, expectedSignatureBytes)) { - return logInvalidContext(this, 'invalid signature') + return logInvalidContext(this, 'invalid signature'); } log.info('metrics.context.validate', { valid: true - }) - return true + }); + return true; } function logInvalidContext(request, reason) { if (request.payload && request.payload.metricsContext) { - delete request.payload.metricsContext.flowId - delete request.payload.metricsContext.flowBeginTime + delete request.payload.metricsContext.flowId; + delete request.payload.metricsContext.flowBeginTime; } log.warn('metrics.context.validate', { valid: false, reason: reason - }) - return false + }); + return false; } function calculateFlowSignatureBytes (metadata) { const hmacData = [ metadata.flowId.substr(0, FLOW_ID_LENGTH / 2), metadata.flowBeginTime.toString(16) - ] + ]; // We want a digest that's half the length of a flowid, // and we want the length in bytes rather than hex. - const signatureLength = FLOW_ID_LENGTH / 2 / 2 - const key = config.metrics.flow_id_key + const signatureLength = FLOW_ID_LENGTH / 2 / 2; + const key = config.metrics.flow_id_key; return crypto.createHmac('sha256', key) .update(hmacData.join('\n')) .digest() - .slice(0, signatureLength) + .slice(0, signatureLength); } /** @@ -306,35 +306,35 @@ module.exports = function (log, config) { */ function setFlowCompleteSignal (flowCompleteSignal, flowType) { if (this.payload && this.payload.metricsContext) { - this.payload.metricsContext.flowCompleteSignal = flowCompleteSignal - this.payload.metricsContext.flowType = flowType + this.payload.metricsContext.flowCompleteSignal = flowCompleteSignal; + this.payload.metricsContext.flowType = flowType; } } -} +}; function calculateFlowTime (time, flowBeginTime) { if (time <= flowBeginTime) { - return 0 + return 0; } - return time - flowBeginTime + return time - flowBeginTime; } function getKey (token) { if (! token || ! token.uid || ! token.id) { - const err = new Error('Invalid token') - throw err + const err = new Error('Invalid token'); + throw err; } - const hash = crypto.createHash('sha256') - hash.update(token.uid) - hash.update(token.id) + const hash = crypto.createHash('sha256'); + hash.update(token.uid); + hash.update(token.id); - return hash.digest('base64') + return hash.digest('base64'); } // HACK: Force the API docs to expand SCHEMA inline -module.exports.SCHEMA = SCHEMA -module.exports.schema = SCHEMA.optional() -module.exports.requiredSchema = SCHEMA.required() +module.exports.SCHEMA = SCHEMA; +module.exports.schema = SCHEMA.optional(); +module.exports.requiredSchema = SCHEMA.required(); diff --git a/lib/metrics/events.js b/lib/metrics/events.js index 06cfb608..5fd95d75 100644 --- a/lib/metrics/events.js +++ b/lib/metrics/events.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../error') -const P = require('../promise') +const error = require('../error'); +const P = require('../promise'); const ACTIVITY_EVENTS = new Set([ 'account.changedPassword', @@ -21,7 +21,7 @@ const ACTIVITY_EVENTS = new Set([ 'device.deleted', 'device.updated', 'sync.sentTabToDevice' -]) +]); // We plan to emit a vast number of flow events to cover all // kinds of success and error steps of the sign-in/up journey. @@ -34,7 +34,7 @@ const NOT_FLOW_EVENTS = new Set([ 'device.deleted', 'device.updated', 'sync.sentTabToDevice' -]) +]); // It's an error if a flow event doesn't have a flow_id // but some events are also emitted outside of user flows. @@ -43,11 +43,11 @@ const OPTIONAL_FLOW_EVENTS = { 'account.keyfetch': true, 'account.reset': true, 'account.signed': true -} +}; const IGNORE_FLOW_EVENTS_FROM_SERVICES = { 'account.signed': 'content-server' -} +}; const IGNORE_ROUTE_FLOW_EVENTS_FOR_PATHS = new Set([ '/account/devices', @@ -56,14 +56,14 @@ const IGNORE_ROUTE_FLOW_EVENTS_FOR_PATHS = new Set([ '/certificate/sign', '/password/forgot/status', '/recovery_email/status' -]) +]); -const IGNORE_ROUTE_FLOW_EVENTS_REGEX = /^\/recoveryKey\/[0-9A-Fa-f]+$/ +const IGNORE_ROUTE_FLOW_EVENTS_REGEX = /^\/recoveryKey\/[0-9A-Fa-f]+$/; -const PATH_PREFIX = /^\/v1/ +const PATH_PREFIX = /^\/v1/; module.exports = (log, config) => { - const amplitude = require('./amplitude')(log, config) + const amplitude = require('./amplitude')(log, config); return { /** @@ -77,48 +77,48 @@ module.exports = (log, config) => { */ emit (event, data) { if (! event) { - log.error('metricsEvents.emit', { missingEvent: true }) - return P.resolve() + log.error('metricsEvents.emit', { missingEvent: true }); + return P.resolve(); } - const request = this - let isFlowCompleteSignal = false + const request = this; + let isFlowCompleteSignal = false; return P.resolve().then(() => { if (ACTIVITY_EVENTS.has(event)) { - emitActivityEvent(event, request, data) + emitActivityEvent(event, request, data); } }) .then(() => { if (NOT_FLOW_EVENTS.has(event)) { - return + return; } - const service = request.query && request.query.service + const service = request.query && request.query.service; if (service && IGNORE_FLOW_EVENTS_FROM_SERVICES[event] === service) { - return + return; } - return emitFlowEvent(event, request, data) + return emitFlowEvent(event, request, data); }) .then(metricsContext => { if (metricsContext) { - isFlowCompleteSignal = event === metricsContext.flowCompleteSignal - return metricsContext + isFlowCompleteSignal = event === metricsContext.flowCompleteSignal; + return metricsContext; } - return request.gatherMetricsContext({}) + return request.gatherMetricsContext({}); }) .then(metricsContext => { return amplitude(event, request, data, metricsContext) .then(() => { if (isFlowCompleteSignal) { - log.flowEvent(Object.assign({}, metricsContext, { event: 'flow.complete' })) + log.flowEvent(Object.assign({}, metricsContext, { event: 'flow.complete' })); return amplitude('flow.complete', request, data, metricsContext) - .then(() => request.clearMetricsContext()) + .then(() => request.clearMetricsContext()); } - }) - }) + }); + }); }, /** @@ -130,22 +130,22 @@ module.exports = (log, config) => { * @returns {Promise} */ emitRouteFlowEvent (response) { - const request = this - const path = request.path.replace(PATH_PREFIX, '') - let status = response.statusCode || response.output.statusCode + const request = this; + const path = request.path.replace(PATH_PREFIX, ''); + let status = response.statusCode || response.output.statusCode; if (status === 404 || IGNORE_ROUTE_FLOW_EVENTS_FOR_PATHS.has(path) || IGNORE_ROUTE_FLOW_EVENTS_REGEX.test(path)) { - return P.resolve() + return P.resolve(); } if (status >= 400) { - const errno = response.errno || (response.output && response.output.errno) + const errno = response.errno || (response.output && response.output.errno); if (errno === error.ERRNO.INVALID_PARAMETER && ! request.validateMetricsContext()) { // Don't emit flow events if the metrics context failed validation - return P.resolve() + return P.resolve(); } - status = `${status}.${errno || 999}` + status = `${status}.${errno || 999}`; } return emitFlowEvent(`route.${path}.${status}`, request) @@ -153,33 +153,33 @@ module.exports = (log, config) => { if (status >= 200 && status < 300) { // Limit to success responses so that short-cut logic (e.g. errors, 304s) // doesn't skew distribution of the performance data - return emitPerformanceEvent(path, request, data) + return emitPerformanceEvent(path, request, data); } - }) + }); } - } + }; function emitActivityEvent (event, request, data) { - const { location } = request.app.geo + const { location } = request.app.geo; data = Object.assign({ country: location && location.country, event, region: location && location.state, userAgent: request.headers['user-agent'] - }, data) + }, data); - optionallySetService(data, request) + optionallySetService(data, request); - log.activityEvent(data) + log.activityEvent(data); } function emitFlowEvent (event, request, optionalData) { if (! request || ! request.headers) { - log.error('metricsEvents.emitFlowEvent', { event, badRequest: true }) - return P.resolve() + log.error('metricsEvents.emitFlowEvent', { event, badRequest: true }); + return P.resolve(); } - const { location } = request.app.geo + const { location } = request.app.geo; return request.gatherMetricsContext({ country: location && location.country, event: event, @@ -188,45 +188,45 @@ module.exports = (log, config) => { userAgent: request.headers['user-agent'] }).then(data => { if (data.flow_id) { - const uid = coalesceUid(optionalData, request) + const uid = coalesceUid(optionalData, request); if (uid) { - data.uid = uid + data.uid = uid; } - log.flowEvent(data) + log.flowEvent(data); } else if (! OPTIONAL_FLOW_EVENTS[event]) { - log.error('metricsEvents.emitFlowEvent', { event, missingFlowId: true }) + log.error('metricsEvents.emitFlowEvent', { event, missingFlowId: true }); } - return data - }) + return data; + }); } function emitPerformanceEvent (path, request, data) { return log.flowEvent(Object.assign({}, data, { event: `route.performance.${path}`, flow_time: Date.now() - request.info.received - })) + })); } -} +}; function optionallySetService (data, request) { if (data.service) { - return + return; } data.service = (request.payload && request.payload.service) || - (request.query && request.query.service) + (request.query && request.query.service); } function coalesceUid (data, request) { if (data && data.uid) { - return data.uid + return data.uid; } return request.auth && request.auth.credentials && - request.auth.credentials.uid + request.auth.credentials.uid; } diff --git a/lib/newrelic.js b/lib/newrelic.js index aac7915c..6af50bab 100644 --- a/lib/newrelic.js +++ b/lib/newrelic.js @@ -2,21 +2,21 @@ * 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/. */ -'use strict' +'use strict'; // To be enabled via the environment of stage or prod. NEW_RELIC_HIGH_SECURITY // and NEW_RELIC_LOG should be set in addition to NEW_RELIC_APP_NAME and // NEW_RELIC_LICENSE_KEY. function maybeRequireNewRelic() { - var env = process.env + var env = process.env; if (env.NEW_RELIC_APP_NAME && env.NEW_RELIC_LICENSE_KEY) { - return require('newrelic') + return require('newrelic'); } - return null + return null; } -module.exports = maybeRequireNewRelic +module.exports = maybeRequireNewRelic; diff --git a/lib/notifier.js b/lib/notifier.js index 15a40c61..a8175be8 100644 --- a/lib/notifier.js +++ b/lib/notifier.js @@ -2,52 +2,52 @@ * 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/. */ -'use strict' +'use strict'; /** * This notifier is called by the logger via `notifyAttachedServices` * to send notifications to Amazon SNS/SQS. */ -const AWS = require('aws-sdk') -const config = require('../config') +const AWS = require('aws-sdk'); +const config = require('../config'); -const notifierSnsTopicArn = config.get('snsTopicArn') -const notifierSnsTopicEndpoint = config.get('snsTopicEndpoint') +const notifierSnsTopicArn = config.get('snsTopicArn'); +const notifierSnsTopicEndpoint = config.get('snsTopicEndpoint'); let sns = { publish: function (msg, cb) { - cb(null, {disabled: true}) -}} + cb(null, {disabled: true}); +}}; if (notifierSnsTopicArn !== 'disabled') { // Pull the region info out of the topic arn. // For some reason we need to pass this in explicitly. // Format is "arn:aws:sns::" - const region = notifierSnsTopicArn.split(':')[3] + const region = notifierSnsTopicArn.split(':')[3]; // This will pull in default credentials, region data etc // from the metadata service available to the instance. // It's magic, and it's awesome. - sns = new AWS.SNS({endpoint: notifierSnsTopicEndpoint, region: region}) + sns = new AWS.SNS({endpoint: notifierSnsTopicEndpoint, region: region}); } function formatMessageAttributes(msg) { - const attrs = {} + const attrs = {}; attrs.event_type = { DataType: 'String', StringValue: msg.event - } + }; if (msg.email) { attrs.email_domain = { DataType: 'String', StringValue: msg.email.split('@')[1] - } + }; } - return attrs + return attrs; } module.exports = function notifierLog(log) { return { send: (event, callback) => { - const msg = event.data || {} - msg.event = event.event + const msg = event.data || {}; + msg.event = event.event; sns.publish({ TopicArn: notifierSnsTopicArn, @@ -55,18 +55,18 @@ module.exports = function notifierLog(log) { MessageAttributes: formatMessageAttributes(msg) }, (err, data) => { if (err) { - log.error('Notifier.publish', { err: err}) + log.error('Notifier.publish', { err: err}); } else { - log.trace('Notifier.publish', { success: true, data: data}) + log.trace('Notifier.publish', { success: true, data: data}); } if (callback) { - callback(err, data) + callback(err, data); } - }) + }); }, // exported for testing purposes __sns: sns - } -} + }; +}; diff --git a/lib/oauthdb/check-refresh-token.js b/lib/oauthdb/check-refresh-token.js index 1a476381..77771293 100644 --- a/lib/oauthdb/check-refresh-token.js +++ b/lib/oauthdb/check-refresh-token.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const Joi = require('joi') -const validators = require('../routes/validators') +const Joi = require('joi'); +const validators = require('../routes/validators'); module.exports = (config) => { return { @@ -30,5 +30,5 @@ module.exports = (config) => { 'fxa-lastUsedAt': Joi.number().optional() } } - } -} + }; +}; diff --git a/lib/oauthdb/client-info.js b/lib/oauthdb/client-info.js index c4529748..89d55f49 100644 --- a/lib/oauthdb/client-info.js +++ b/lib/oauthdb/client-info.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const Joi = require('joi') -const validators = require('../routes/validators') +const Joi = require('joi'); +const validators = require('../routes/validators'); module.exports = (config) => { return { @@ -23,5 +23,5 @@ module.exports = (config) => { redirect_uri: Joi.string().required().allow('') } } - } -} + }; +}; diff --git a/lib/oauthdb/index.js b/lib/oauthdb/index.js index 83ad5d01..675f67b7 100644 --- a/lib/oauthdb/index.js +++ b/lib/oauthdb/index.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/. */ -'use strict' +'use strict'; /* Operations on OAuth database state. * @@ -18,8 +18,8 @@ * Ref: https://docs.google.com/document/d/1CnTv0Eamy7Lnbmf1ALH00oTKMPhGu70elRivJYjx5v0/ */ -const createBackendServiceAPI = require('../backendService') -const { mapOAuthError, makeAssertionJWT } = require('./utils') +const createBackendServiceAPI = require('../backendService'); +const { mapOAuthError, makeAssertionJWT } = require('./utils'); module.exports = (log, config) => { @@ -28,25 +28,25 @@ module.exports = (log, config) => { revokeRefreshTokenById: require('./revoke-refresh-token-by-id')(config), getClientInfo: require('./client-info')(config), getScopedKeyData: require('./scoped-key-data')(config), - }) + }); - const api = new OAuthAPI(config.oauth.url, config.oauth.poolee) + const api = new OAuthAPI(config.oauth.url, config.oauth.poolee); return { api, close() { - api.close() + api.close(); }, async checkRefreshToken(token) { try { return await api.checkRefreshToken({ token: token, - }) + }); } catch (err) { - throw mapOAuthError(log, err) + throw mapOAuthError(log, err); } }, @@ -54,26 +54,26 @@ module.exports = (log, config) => { try { return await api.revokeRefreshTokenById({ refresh_token_id: refreshTokenId, - }) + }); } catch (err) { - throw mapOAuthError(log, err) + throw mapOAuthError(log, err); } }, async getClientInfo(clientId) { try { - return await api.getClientInfo(clientId) + return await api.getClientInfo(clientId); } catch (err) { - throw mapOAuthError(log, err) + throw mapOAuthError(log, err); } }, async getScopedKeyData(sessionToken, oauthParams) { - oauthParams.assertion = await makeAssertionJWT(config, sessionToken) + oauthParams.assertion = await makeAssertionJWT(config, sessionToken); try { - return await api.getScopedKeyData(oauthParams) + return await api.getScopedKeyData(oauthParams); } catch (err) { - throw mapOAuthError(log, err) + throw mapOAuthError(log, err); } }, @@ -101,5 +101,5 @@ module.exports = (log, config) => { * */ - } -} + }; +}; diff --git a/lib/oauthdb/revoke-refresh-token-by-id.js b/lib/oauthdb/revoke-refresh-token-by-id.js index 7c89e741..fb798dbb 100644 --- a/lib/oauthdb/revoke-refresh-token-by-id.js +++ b/lib/oauthdb/revoke-refresh-token-by-id.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -const Joi = require('joi') +const Joi = require('joi'); module.exports = (config) => { return { @@ -16,5 +16,5 @@ module.exports = (config) => { }, response: {} } - } -} + }; +}; diff --git a/lib/oauthdb/scoped-key-data.js b/lib/oauthdb/scoped-key-data.js index 4c4fa9cb..75a362cc 100644 --- a/lib/oauthdb/scoped-key-data.js +++ b/lib/oauthdb/scoped-key-data.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const Joi = require('joi') -const validators = require('../routes/validators') +const Joi = require('joi'); +const validators = require('../routes/validators'); module.exports = (config) => { return { @@ -23,5 +23,5 @@ module.exports = (config) => { keyRotationTimestamp: Joi.number().required(), })) } - } -} + }; +}; diff --git a/lib/oauthdb/utils.js b/lib/oauthdb/utils.js index ff654cc7..09ea0068 100644 --- a/lib/oauthdb/utils.js +++ b/lib/oauthdb/utils.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('../promise') -const signJWT = P.promisify(require('jsonwebtoken').sign) +const P = require('../promise'); +const signJWT = P.promisify(require('jsonwebtoken').sign); -const error = require('../error') +const error = require('../error'); module.exports = { @@ -18,24 +18,24 @@ module.exports = { // If it's already an instance of our internal error type, // then just return it as-is. if (err instanceof error) { - return err + return err; } switch (err.errno) { case 101: - return error.unknownClientId(err.clientId) + return error.unknownClientId(err.clientId); case 108: - return error.invalidToken() + return error.invalidToken(); case 116: - return error.notPublicClient() + return error.notPublicClient(); case 119: - return error.staleAuthAt(err.authAt) + return error.staleAuthAt(err.authAt); default: log.warn('oauthdb.mapOAuthError', { err: err, errno: err.errno, warning: 'unmapped oauth-server errno' - }) - return error.unexpectedError() + }); + return error.unexpectedError(); } }, @@ -48,17 +48,17 @@ module.exports = { makeAssertionJWT: function makeAssertionJWT(config, credentials) { if (! credentials.emailVerified) { - throw error.unverifiedAccount() + throw error.unverifiedAccount(); } if (credentials.mustVerify && ! credentials.tokenVerified) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } const opts = { algorithm: 'HS256', expiresIn: 60, audience: config.oauth.url, issuer: config.domain - } + }; const claims = { 'sub': credentials.uid, 'fxa-generation': credentials.verifierSetAt, @@ -67,7 +67,7 @@ module.exports = { 'fxa-tokenVerified': credentials.tokenVerified, 'fxa-amr': Array.from(credentials.authenticationMethods), 'fxa-aal': credentials.authenticatorAssuranceLevel - } - return signJWT(claims, config.oauth.secretKey, opts) + }; + return signJWT(claims, config.oauth.secretKey, opts); } -} +}; diff --git a/lib/pool.js b/lib/pool.js index c8ea8e0b..46bcd7e9 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -2,24 +2,24 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('./promise') -const Poolee = require('poolee') -const url = require('url') +const P = require('./promise'); +const Poolee = require('poolee'); +const url = require('url'); const PROTOCOL_MODULES = { 'http': require('http'), 'https': require('https') -} +}; function Pool(uri, options = {}) { - const parsed = url.parse(uri) - const {protocol, host} = parsed - const protocolModule = PROTOCOL_MODULES[protocol.slice(0, -1)] + const parsed = url.parse(uri); + const {protocol, host} = parsed; + const protocolModule = PROTOCOL_MODULES[protocol.slice(0, -1)]; if (! protocolModule) { - throw new Error(`Protocol ${protocol} is not supported.`) + throw new Error(`Protocol ${protocol} is not supported.`); } - const port = parsed.port || protocolModule.globalAgent.defaultPort + const port = parsed.port || protocolModule.globalAgent.defaultPort; this.poolee = new Poolee( protocolModule, [`${host}:${port}`], @@ -29,23 +29,23 @@ function Pool(uri, options = {}) { keepAlive: true, maxRetries: 0 } - ) + ); } Pool.prototype.request = function (method, url, params, query, body, headers = {}) { - let path + let path; try { - path = url.render(params, query) + path = url.render(params, query); } catch (err) { - return P.reject(err) + return P.reject(err); } - var d = P.defer() - let data + var d = P.defer(); + let data; if (body) { - headers['Content-Type'] = 'application/json' - data = JSON.stringify(body) + headers['Content-Type'] = 'application/json'; + data = JSON.stringify(body); } this.poolee.request( { @@ -55,64 +55,64 @@ Pool.prototype.request = function (method, url, params, query, body, headers = { data }, handleResponse - ) - return d.promise + ); + return d.promise; function handleResponse (err, res, body) { - var parsedBody = safeParse(body) + var parsedBody = safeParse(body); if (err) { - return d.reject(err) + return d.reject(err); } if (res.statusCode < 200 || res.statusCode >= 300) { - var error = new Error() + var error = new Error(); if (! parsedBody) { - error.message = body + error.message = body; } else { - Object.assign(error, parsedBody) + Object.assign(error, parsedBody); } - error.statusCode = res.statusCode - return d.reject(error) + error.statusCode = res.statusCode; + return d.reject(error); } if (! body) { - return d.resolve() + return d.resolve(); } if (! parsedBody) { - return d.reject(new Error('Invalid JSON')) + return d.reject(new Error('Invalid JSON')); } - d.resolve(parsedBody) + d.resolve(parsedBody); } -} +}; Pool.prototype.post = function (path, params, body, {query = {}, headers = {}} = {}) { - return this.request('POST', path, params, query, body, headers) -} + return this.request('POST', path, params, query, body, headers); +}; Pool.prototype.put = function (path, params, body, {query = {}, headers = {}} = {}) { - return this.request('PUT', path, params, query, body, headers) -} + return this.request('PUT', path, params, query, body, headers); +}; Pool.prototype.get = function (path, params, {query = {}, headers = {}} = {}) { - return this.request('GET', path, params, query, null, headers) -} + return this.request('GET', path, params, query, null, headers); +}; Pool.prototype.del = function (path, params, body, {query = {}, headers = {}} = {}) { - return this.request('DELETE', path, params, query, body, headers) -} + return this.request('DELETE', path, params, query, body, headers); +}; Pool.prototype.head = function (path, params, {query = {}, headers = {}} = {}) { - return this.request('HEAD', path, params, query, null, headers) -} + return this.request('HEAD', path, params, query, null, headers); +}; Pool.prototype.close = function () { /*/ This is a hack to coax the server to close its existing connections /*/ - var socketCount = this.poolee.options.maxSockets || 20 + var socketCount = this.poolee.options.maxSockets || 20; function noop() {} for (var i = 0; i < socketCount; i++) { this.poolee.request( @@ -124,15 +124,15 @@ Pool.prototype.close = function () { } }, noop - ) + ); } -} +}; -module.exports = Pool +module.exports = Pool; function safeParse (json) { try { - return JSON.parse(json) + return JSON.parse(json); } catch (e) { } diff --git a/lib/profile/updates.js b/lib/profile/updates.js index 8e60e229..0f67442b 100644 --- a/lib/profile/updates.js +++ b/lib/profile/updates.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; module.exports = function (log) { return function start(messageQueue, push, db) { function handleProfileUpdated(message) { - const uid = message && message.uid + const uid = message && message.uid; - log.info('handleProfileUpdated', { uid, action: 'notify' }) + log.info('handleProfileUpdated', { uid, action: 'notify' }); return db.devices(uid) .then(devices => push.notifyProfileUpdated(uid, devices)) .catch(err => log.error('handleProfileUpdated', { uid, action: 'error', err, stack: err && err.stack })) .then(() => { - log.info('handleProfileUpdated', { uid, action: 'delete' }) + log.info('handleProfileUpdated', { uid, action: 'delete' }); // We always delete the message, we are not really mission critical - message.del() - }) + message.del(); + }); } - messageQueue.on('data', handleProfileUpdated) - messageQueue.start() + messageQueue.on('data', handleProfileUpdated); + messageQueue.start(); return { messageQueue: messageQueue, handleProfileUpdated: handleProfileUpdated - } - } -} + }; + }; +}; diff --git a/lib/promise.js b/lib/promise.js index 3ebc5344..206a3e1f 100644 --- a/lib/promise.js +++ b/lib/promise.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/. */ -'use strict' +'use strict'; // for easy promise lib switching -module.exports = require('bluebird') +module.exports = require('bluebird'); diff --git a/lib/push.js b/lib/push.js index 53abe17e..4a046bae 100644 --- a/lib/push.js +++ b/lib/push.js @@ -2,20 +2,20 @@ * 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/. */ -'use strict' +'use strict'; -var crypto = require('crypto') -var base64url = require('base64url') -var webpush = require('web-push') -var P = require('./promise') +var crypto = require('crypto'); +var base64url = require('base64url'); +var webpush = require('web-push'); +var P = require('./promise'); -var ERR_NO_PUSH_CALLBACK = 'No Push Callback' -var ERR_DATA_BUT_NO_KEYS = 'Data payload present but missing key(s)' -var ERR_TOO_MANY_DEVICES = 'Too many devices connected to account' +var ERR_NO_PUSH_CALLBACK = 'No Push Callback'; +var ERR_DATA_BUT_NO_KEYS = 'Data payload present but missing key(s)'; +var ERR_TOO_MANY_DEVICES = 'Too many devices connected to account'; -var LOG_OP_PUSH_TO_DEVICES = 'push.sendPush' +var LOG_OP_PUSH_TO_DEVICES = 'push.sendPush'; -var PUSH_PAYLOAD_SCHEMA_VERSION = 1 +var PUSH_PAYLOAD_SCHEMA_VERSION = 1; var PUSH_COMMANDS = { DEVICE_CONNECTED: 'fxaccounts:device_connected', DEVICE_DISCONNECTED: 'fxaccounts:device_disconnected', @@ -24,26 +24,26 @@ var PUSH_COMMANDS = { PASSWORD_RESET: 'fxaccounts:password_reset', ACCOUNT_DESTROYED: 'fxaccounts:account_destroyed', COMMAND_RECEIVED: 'fxaccounts:command_received' -} +}; -const TTL_DEVICE_DISCONNECTED = 5 * 3600 // 5 hours -const TTL_PASSWORD_CHANGED = 6 * 3600 // 6 hours -const TTL_PASSWORD_RESET = TTL_PASSWORD_CHANGED -const TTL_ACCOUNT_DESTROYED = TTL_DEVICE_DISCONNECTED -const TTL_COMMAND_RECEIVED = TTL_PASSWORD_CHANGED +const TTL_DEVICE_DISCONNECTED = 5 * 3600; // 5 hours +const TTL_PASSWORD_CHANGED = 6 * 3600; // 6 hours +const TTL_PASSWORD_RESET = TTL_PASSWORD_CHANGED; +const TTL_ACCOUNT_DESTROYED = TTL_DEVICE_DISCONNECTED; +const TTL_COMMAND_RECEIVED = TTL_PASSWORD_CHANGED; // An arbitrary, but very generous, limit on the number of active devices. // Currently only for metrics purposes, not enforced. -var MAX_ACTIVE_DEVICES = 200 +var MAX_ACTIVE_DEVICES = 200; const pushReasonsToEvents = (() => { const reasons = ['accountVerify', 'accountConfirm', 'passwordReset', 'passwordChange', 'deviceConnected', 'deviceDisconnected', 'profileUpdated', 'devicesNotify', 'accountDestroyed', - 'commandReceived'] - const events = {} + 'commandReceived']; + const events = {}; for (const reason of reasons) { - const id = reason.replace(/[A-Z]/, c => `_${c.toLowerCase()}`) // snake-cased. + const id = reason.replace(/[A-Z]/, c => `_${c.toLowerCase()}`); // snake-cased. events[reason] = { send: `push.${id}.send`, success: `push.${id}.success`, @@ -51,10 +51,10 @@ const pushReasonsToEvents = (() => { failed: `push.${id}.failed`, noCallback: `push.${id}.no_push_callback`, noKeys: `push.${id}.data_but_no_keys` - } + }; } - return events -})() + return events; +})(); /** * A device object returned by the db, @@ -63,14 +63,14 @@ const pushReasonsToEvents = (() => { */ module.exports = function (log, db, config) { - var vapid + var vapid; if (config.vapidKeysFile) { - var vapidKeys = require(config.vapidKeysFile) + var vapidKeys = require(config.vapidKeysFile); vapid = { privateKey: vapidKeys.privateKey, publicKey: vapidKeys.publicKey, subject: config.publicUrl - } + }; } /** @@ -85,7 +85,7 @@ module.exports = function (log, db, config) { uid: uid, deviceId: deviceId, err: err - }) + }); } /** @@ -97,7 +97,7 @@ module.exports = function (log, db, config) { if (name) { log.info(LOG_OP_PUSH_TO_DEVICES, { name: name - }) + }); } } @@ -113,43 +113,43 @@ module.exports = function (log, db, config) { * The list of devices to which to send the push. */ function filterSupportedDevices(payload, devices) { - const command = (payload && payload.command) || null - let canSendToIOSVersion/* ({Number} version) => bool */ + const command = (payload && payload.command) || null; + let canSendToIOSVersion;/* ({Number} version) => bool */ switch (command) { case 'fxaccounts:command_received': - canSendToIOSVersion = () => true - break + canSendToIOSVersion = () => true; + break; case 'sync:collection_changed': - canSendToIOSVersion = () => payload.data.reason !== 'firstsync' - break + canSendToIOSVersion = () => payload.data.reason !== 'firstsync'; + break; case null: // In the null case this is an account verification push message canSendToIOSVersion = (deviceVersion, deviceBrowser) => { - return deviceVersion >= 10.0 && deviceBrowser === 'Firefox Beta' - } - break + return deviceVersion >= 10.0 && deviceBrowser === 'Firefox Beta'; + }; + break; case 'fxaccounts:device_connected': case 'fxaccounts:device_disconnected': - canSendToIOSVersion = deviceVersion => deviceVersion >= 10.0 - break + canSendToIOSVersion = deviceVersion => deviceVersion >= 10.0; + break; default: - canSendToIOSVersion = () => false + canSendToIOSVersion = () => false; } return devices.filter(function(device) { - const deviceOS = device.uaOS && device.uaOS.toLowerCase() + const deviceOS = device.uaOS && device.uaOS.toLowerCase(); if (deviceOS === 'ios') { - const deviceVersion = device.uaBrowserVersion ? parseFloat(device.uaBrowserVersion) : 0 - const deviceBrowserName = device.uaBrowser + const deviceVersion = device.uaBrowserVersion ? parseFloat(device.uaBrowserVersion) : 0; + const deviceBrowserName = device.uaBrowser; if (! canSendToIOSVersion(deviceVersion, deviceBrowserName)) { log.info('push.filteredUnsupportedDevice', { command: command, uaOS: device.uaOS, uaBrowserVersion: device.uaBrowserVersion - }) - return false + }); + return false; } } - return true - }) + return true; + }); } /** @@ -164,29 +164,29 @@ module.exports = function (log, db, config) { * The public key as a b64url string. */ - var dummySigner = crypto.createSign('RSA-SHA256') - var dummyKey = Buffer.alloc(0) - var dummyCurve = crypto.createECDH('prime256v1') - dummyCurve.generateKeys() + var dummySigner = crypto.createSign('RSA-SHA256'); + var dummyKey = Buffer.alloc(0); + var dummyCurve = crypto.createECDH('prime256v1'); + dummyCurve.generateKeys(); function isValidPublicKey(publicKey) { // Try to use the key in an ECDH agreement. // If the key is invalid then this will throw an error. try { - dummyCurve.computeSecret(base64url.toBuffer(publicKey)) - return true + dummyCurve.computeSecret(base64url.toBuffer(publicKey)); + return true; } catch (err) { log.info('push.isValidPublicKey', { name: 'Bad public key detected' - }) + }); // However! The above call might have left some junk // sitting around on the openssl error stack. // Clear it by deliberately triggering a signing error // before anything yields the event loop. try { - dummySigner.sign(dummyKey) + dummySigner.sign(dummyKey); } catch (e) {} - return false + return false; } } @@ -207,7 +207,7 @@ module.exports = function (log, db, config) { */ notifyCommandReceived(uid, device, command, sender, index, url, ttl) { if (typeof ttl === 'undefined') { - ttl = TTL_COMMAND_RECEIVED + ttl = TTL_COMMAND_RECEIVED; } const options = { data: { @@ -221,8 +221,8 @@ module.exports = function (log, db, config) { } }, TTL: ttl - } - return this.sendPush(uid, [device], 'commandReceived', options) + }; + return this.sendPush(uid, [device], 'commandReceived', options); }, /** @@ -242,7 +242,7 @@ module.exports = function (log, db, config) { deviceName } } - }) + }); }, /** @@ -263,7 +263,7 @@ module.exports = function (log, db, config) { } }, TTL: TTL_DEVICE_DISCONNECTED - }) + }); }, /** @@ -279,7 +279,7 @@ module.exports = function (log, db, config) { version: PUSH_PAYLOAD_SCHEMA_VERSION, command: PUSH_COMMANDS.PROFILE_UPDATED } - }) + }); }, /** @@ -296,7 +296,7 @@ module.exports = function (log, db, config) { command: PUSH_COMMANDS.PASSWORD_CHANGED }, TTL: TTL_PASSWORD_CHANGED - }) + }); }, /** @@ -313,7 +313,7 @@ module.exports = function (log, db, config) { command: PUSH_COMMANDS.PASSWORD_RESET }, TTL: TTL_PASSWORD_RESET - }) + }); }, /** @@ -325,7 +325,7 @@ module.exports = function (log, db, config) { * @promise */ notifyAccountUpdated (uid, devices, reason) { - return this.sendPush(uid, devices, reason) + return this.sendPush(uid, devices, reason); }, /** @@ -345,7 +345,7 @@ module.exports = function (log, db, config) { } }, TTL: TTL_ACCOUNT_DESTROYED - }) + }); }, /** @@ -360,59 +360,59 @@ module.exports = function (log, db, config) { * @promise */ sendPush (uid, devices, reason, options = {}) { - devices = filterSupportedDevices(options.data, devices) - var events = pushReasonsToEvents[reason] + devices = filterSupportedDevices(options.data, devices); + var events = pushReasonsToEvents[reason]; if (! events) { - return P.reject('Unknown push reason: ' + reason) + return P.reject('Unknown push reason: ' + reason); } // There's no spec-compliant way to error out as a result of having // too many devices to notify. For now, just log metrics about it. if (devices.length > MAX_ACTIVE_DEVICES) { - reportPushError(new Error(ERR_TOO_MANY_DEVICES), uid, null) + reportPushError(new Error(ERR_TOO_MANY_DEVICES), uid, null); } return P.each(devices, function(device) { - var deviceId = device.id + var deviceId = device.id; log.trace(LOG_OP_PUSH_TO_DEVICES, { uid: uid, deviceId: deviceId, pushCallback: device.pushCallback - }) + }); if (device.pushCallback && ! device.pushEndpointExpired) { // send the push notification - incrementPushAction(events.send) - var pushSubscription = { endpoint: device.pushCallback } - var pushPayload = null - var pushOptions = { 'TTL': options.TTL || '0' } + incrementPushAction(events.send); + var pushSubscription = { endpoint: device.pushCallback }; + var pushPayload = null; + var pushOptions = { 'TTL': options.TTL || '0' }; if (options.data) { if (! device.pushPublicKey || ! device.pushAuthKey) { - reportPushError(new Error(ERR_DATA_BUT_NO_KEYS), uid, deviceId) - incrementPushAction(events.noKeys) - return + reportPushError(new Error(ERR_DATA_BUT_NO_KEYS), uid, deviceId); + incrementPushAction(events.noKeys); + return; } pushSubscription.keys = { p256dh: device.pushPublicKey, auth: device.pushAuthKey - } - pushPayload = Buffer.from(JSON.stringify(options.data)) + }; + pushPayload = Buffer.from(JSON.stringify(options.data)); } if (vapid) { - pushOptions.vapidDetails = vapid + pushOptions.vapidDetails = vapid; } return webpush.sendNotification(pushSubscription, pushPayload, pushOptions) .then( function () { - incrementPushAction(events.success) + incrementPushAction(events.success); }, function (err) { // If we've stored an invalid key in the db for some reason, then we // might get an encryption failure here. Check the key, which also // happens to work around bugginess in node's handling of said failures. - var keyWasInvalid = false + var keyWasInvalid = false; if (! err.statusCode && device.pushPublicKey) { if (! isValidPublicKey(device.pushPublicKey)) { - keyWasInvalid = true + keyWasInvalid = true; } } // 404 or 410 error from the push servers means @@ -421,25 +421,25 @@ module.exports = function (log, db, config) { if (err.statusCode === 404 || err.statusCode === 410 || keyWasInvalid) { // set the push endpoint expired flag // Warning: this method is called without any session tokens or auth validation. - device.pushEndpointExpired = true + device.pushEndpointExpired = true; return db.updateDevice(uid, device).catch(function (err) { - reportPushError(err, uid, deviceId) + reportPushError(err, uid, deviceId); }).then(function() { - incrementPushAction(events.resetSettings) - }) + incrementPushAction(events.resetSettings); + }); } else { - reportPushError(err, uid, deviceId) - incrementPushAction(events.failed) + reportPushError(err, uid, deviceId); + incrementPushAction(events.failed); } } - ) + ); } else { // keep track if there are any devices with no push urls. - reportPushError(new Error(ERR_NO_PUSH_CALLBACK), uid, deviceId) - incrementPushAction(events.noCallback) + reportPushError(new Error(ERR_NO_PUSH_CALLBACK), uid, deviceId); + incrementPushAction(events.noCallback); } - }) + }); } - } -} + }; +}; diff --git a/lib/pushbox.js b/lib/pushbox.js index 1eae5442..c92cb8e5 100644 --- a/lib/pushbox.js +++ b/lib/pushbox.js @@ -14,14 +14,14 @@ * oauth-authenticated service, once we get more experience with using it. */ -'use strict' +'use strict'; -const isA = require('joi') -const error = require('./error') -const createBackendServiceAPI = require('./backendService') -const validators = require('./routes/validators') +const isA = require('joi'); +const error = require('./error'); +const createBackendServiceAPI = require('./backendService'); +const validators = require('./routes/validators'); -const base64url = require('base64url') +const base64url = require('base64url'); const PUSHBOX_RETRIEVE_SCHEMA = isA.object({ last: isA.boolean().optional(), @@ -32,24 +32,24 @@ const PUSHBOX_RETRIEVE_SCHEMA = isA.object({ })).optional(), status: isA.number().required(), error: isA.string().optional() -}).and('last', 'messages').or('index', 'error') +}).and('last', 'messages').or('index', 'error'); const PUSHBOX_STORE_SCHEMA = isA.object({ index: isA.number().optional(), error: isA.string().optional(), status: isA.number().required() -}).or('index', 'error') +}).or('index', 'error'); // Pushbox stores strings, so these are a little pair // of helper functions to allow us to store arbitrary // JSON-serializable objects. function encodeForStorage(data) { - return base64url.encode(JSON.stringify(data)) + return base64url.encode(JSON.stringify(data)); } function decodeFromStorage(data) { - return JSON.parse(base64url.decode(data)) + return JSON.parse(base64url.decode(data)); } @@ -57,12 +57,12 @@ module.exports = function (log, config) { if (! config.pushbox.enabled) { return { retrieve() { - return Promise.reject(error.featureNotEnabled()) + return Promise.reject(error.featureNotEnabled()); }, store() { - return Promise.reject(error.featureNotEnabled()) + return Promise.reject(error.featureNotEnabled()); } - } + }; } const PushboxAPI = createBackendServiceAPI(log, config, 'pushbox', { @@ -99,15 +99,15 @@ module.exports = function (log, config) { } }, - }) + }); const api = new PushboxAPI(config.pushbox.url, { headers: {Authorization: `FxA-Server-Key ${config.pushbox.key}`}, timeout: 15000 - }) + }); // pushbox expects this in seconds, not millis. - const maxTTL = Math.round(config.pushbox.maxTTL / 1000) + const maxTTL = Math.round(config.pushbox.maxTTL / 1000); return { /** @@ -125,15 +125,15 @@ module.exports = function (log, config) { async retrieve (uid, deviceId, limit, index) { const query = { limit: limit.toString() - } + }; if (index) { - query.index = index.toString() + query.index = index.toString(); } - const body = await api.retrieve(uid, deviceId, query) - log.info('pushbox.retrieve.response', { body: body }) + const body = await api.retrieve(uid, deviceId, query); + log.info('pushbox.retrieve.response', { body: body }); if (body.error) { - log.error('pushbox.retrieve', { status: body.status, error: body.error }) - throw error.backendServiceFailure() + log.error('pushbox.retrieve', { status: body.status, error: body.error }); + throw error.backendServiceFailure(); } return { last: body.last, @@ -142,9 +142,9 @@ module.exports = function (log, config) { return { index: msg.index, data: decodeFromStorage(msg.data) - } + }; }) - } + }; }, /** @@ -160,17 +160,17 @@ module.exports = function (log, config) { */ async store (uid, deviceId, data, ttl) { if (typeof ttl === 'undefined' || ttl > maxTTL) { - ttl = maxTTL + ttl = maxTTL; } - const body = await api.store(uid, deviceId, {data: encodeForStorage(data), ttl}) - log.info('pushbox.store.response', { body: body }) + const body = await api.store(uid, deviceId, {data: encodeForStorage(data), ttl}); + log.info('pushbox.store.response', { body: body }); if (body.error) { - log.error('pushbox.store', { status: body.status, error: body.error }) - throw error.backendServiceFailure() + log.error('pushbox.store', { status: body.status, error: body.error }); + throw error.backendServiceFailure(); } - return body + return body; } - } -} + }; +}; -module.exports.RETRIEVE_SCHEMA = PUSHBOX_RETRIEVE_SCHEMA +module.exports.RETRIEVE_SCHEMA = PUSHBOX_RETRIEVE_SCHEMA; diff --git a/lib/redis.js b/lib/redis.js index f380d893..1126e811 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -2,39 +2,39 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -'use strict' +'use strict'; -const error = require('./error') +const error = require('./error'); module.exports = (config, log) => { - const redis = require('fxa-shared/redis')(config, log) + const redis = require('fxa-shared/redis')(config, log); if (! redis) { - return + return; } return Object.entries(redis).reduce((object, [ key, value ]) => { if (typeof value === 'function') { object[key] = async (...args) => { try { - return await value(...args) + return await value(...args); } catch (err) { if (err.message === 'redis.watch.conflict') { // This error is nothing to worry about, just a sign that our // protection against concurrent updates is working correctly. // fxa-shared is responsible for logging. - throw error.redisConflict() + throw error.redisConflict(); } // If you see this line in a stack trace in Sentry // it means something unexpected has really occurred. // fxa-shared is responsible for logging. - throw error.unexpectedError() + throw error.unexpectedError(); } - } + }; } else { - object[key] = value + object[key] = value; } - return object - }, {}) -} + return object; + }, {}); +}; diff --git a/lib/routes/account.js b/lib/routes/account.js index a05143e0..a9ee2add 100644 --- a/lib/routes/account.js +++ b/lib/routes/account.js @@ -2,34 +2,34 @@ * 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/. */ -'use strict' +'use strict'; -const emailUtils = require('./utils/email') -const error = require('../error') -const isA = require('joi') -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema -const P = require('../promise') -const random = require('../crypto/random') -const requestHelper = require('./utils/request_helper') -const uuid = require('uuid') -const validators = require('./validators') -const authMethods = require('../authMethods') -const ScopeSet = require('fxa-shared').oauth.scopes +const emailUtils = require('./utils/email'); +const error = require('../error'); +const isA = require('joi'); +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; +const P = require('../promise'); +const random = require('../crypto/random'); +const requestHelper = require('./utils/request_helper'); +const uuid = require('uuid'); +const validators = require('./validators'); +const authMethods = require('../authMethods'); +const ScopeSet = require('fxa-shared').oauth.scopes; -const HEX_STRING = validators.HEX_STRING +const HEX_STRING = validators.HEX_STRING; -const MS_ONE_HOUR = 1000 * 60 * 60 -const MS_ONE_DAY = MS_ONE_HOUR * 24 -const MS_ONE_WEEK = MS_ONE_DAY * 7 -const MS_ONE_MONTH = MS_ONE_DAY * 30 +const MS_ONE_HOUR = 1000 * 60 * 60; +const MS_ONE_DAY = MS_ONE_HOUR * 24; +const MS_ONE_WEEK = MS_ONE_DAY * 7; +const MS_ONE_MONTH = MS_ONE_DAY * 30; module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) => { - const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode - const tokenCodeLifetime = tokenCodeConfig && tokenCodeConfig.codeLifetime || MS_ONE_HOUR - const tokenCodeLength = tokenCodeConfig && tokenCodeConfig.codeLength || 8 - const TokenCode = random.base10(tokenCodeLength) - const totpUtils = require('./utils/totp')(log, config, db) - const skipConfirmationForEmailAddresses = config.signinConfirmation.skipForEmailAddresses + const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode; + const tokenCodeLifetime = tokenCodeConfig && tokenCodeConfig.codeLifetime || MS_ONE_HOUR; + const tokenCodeLength = tokenCodeConfig && tokenCodeConfig.codeLength || 8; + const TokenCode = random.base10(tokenCodeLength); + const totpUtils = require('./utils/totp')(log, config, db); + const skipConfirmationForEmailAddresses = config.signinConfirmation.skipForEmailAddresses; const routes = [ { @@ -61,22 +61,22 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - log.begin('Account.create', request) - const form = request.payload - const query = request.query - const email = form.email - const authPW = form.authPW - const locale = request.app.acceptLanguage - const userAgentString = request.headers['user-agent'] - const service = form.service || query.service - const preVerified = !! form.preVerified - const ip = request.app.clientAddress + log.begin('Account.create', request); + const form = request.payload; + const query = request.query; + const email = form.email; + const authPW = form.authPW; + const locale = request.app.acceptLanguage; + const userAgentString = request.headers['user-agent']; + const service = form.service || query.service; + const preVerified = !! form.preVerified; + const ip = request.app.clientAddress; let password, verifyHash, account, sessionToken, keyFetchToken, emailCode, tokenVerificationId, tokenVerificationCode, - authSalt + authSalt; - request.validateMetricsContext() + request.validateMetricsContext(); - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; return customs.check(request, email, 'accountCreate') .then(deleteAccountIfUnverified) @@ -88,7 +88,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) .then(sendVerifyCode) .then(createKeyFetchToken) .then(recordSecurityEvent) - .then(createResponse) + .then(createResponse); function deleteAccountIfUnverified() { @@ -98,56 +98,56 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) // secondary email address if (secondaryEmailRecord.isPrimary) { if (secondaryEmailRecord.isVerified) { - throw error.accountExists(secondaryEmailRecord.email) + throw error.accountExists(secondaryEmailRecord.email); } - request.app.accountRecreated = true + request.app.accountRecreated = true; return db.deleteAccount(secondaryEmailRecord) - .then(() => log.info('accountDeleted.unverifiedSecondaryEmail', { ...secondaryEmailRecord })) + .then(() => log.info('accountDeleted.unverifiedSecondaryEmail', { ...secondaryEmailRecord })); } else { if (secondaryEmailRecord.isVerified) { - throw error.verifiedSecondaryEmailAlreadyExists() + throw error.verifiedSecondaryEmailAlreadyExists(); } - return db.deleteEmail(secondaryEmailRecord.uid, secondaryEmailRecord.email) + return db.deleteEmail(secondaryEmailRecord.uid, secondaryEmailRecord.email); } }) .catch((err) => { if (err.errno !== error.ERRNO.SECONDARY_EMAIL_UNKNOWN) { - throw err + throw err; } - }) + }); } function setMetricsFlowCompleteSignal () { - let flowCompleteSignal + let flowCompleteSignal; if (service === 'sync') { - flowCompleteSignal = 'account.signed' + flowCompleteSignal = 'account.signed'; } else { - flowCompleteSignal = 'account.verified' + flowCompleteSignal = 'account.verified'; } - request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'registration') + request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'registration'); - return P.resolve() + return P.resolve(); } function generateRandomValues() { return P.all([random.hex(16), random.hex(32), TokenCode()]) .spread((hex16, hex32, tokenCode) => { - emailCode = hex16 - tokenVerificationId = emailCode - tokenVerificationCode = tokenCode - authSalt = hex32 - }) + emailCode = hex16; + tokenVerificationId = emailCode; + tokenVerificationCode = tokenCode; + authSalt = hex32; + }); } function createPassword () { - password = new Password(authPW, authSalt, config.verifierVersion) + password = new Password(authPW, authSalt, config.verifierVersion); return password.verifyHash() .then( function (result) { - verifyHash = result + verifyHash = result; } - ) + ); } async function createAccount () { @@ -158,10 +158,10 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) email: email, locale: locale, agent: userAgentString - }) + }); } - const hexes = await random.hex(32, 32) + const hexes = await random.hex(32, 32); account = await db.createAccount({ uid: uuid.v4('binary').toString('hex'), createdAt: Date.now(), @@ -177,11 +177,11 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) verifyHash: verifyHash, verifierSetAt: Date.now(), locale: locale - }) + }); await request.emitMetricsEvent('account.created', { uid: account.uid - }) + }); if (account.emailVerified) { await log.notifyAttachedServices('verified', request, { @@ -189,7 +189,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) locale: account.locale, service, uid: account.uid, - }) + }); } await log.notifyAttachedServices('login', request, { @@ -198,13 +198,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) service, uid: account.uid, userAgent: userAgentString, - }) + }); } function createSessionToken () { // Verified sessions should only be created for preverified accounts. if (preVerified) { - tokenVerificationId = undefined + tokenVerificationId = undefined; } const { @@ -214,7 +214,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) osVersion: uaOSVersion, deviceType: uaDeviceType, formFactor: uaFormFactor - } = request.app.ua + } = request.app.ua; return db.createSessionToken({ uid: account.uid, @@ -235,8 +235,8 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) }) .then( function (result) { - sessionToken = result - return request.stashMetricsContext(sessionToken) + sessionToken = result; + return request.stashMetricsContext(sessionToken); } ) .then( @@ -246,9 +246,9 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) return request.stashMetricsContext({ uid: account.uid, id: account.emailCode - }) + }); } - ) + ); } function sendVerifyCode () { @@ -277,11 +277,11 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) log.info('account.create.confirm.start', { uid: account.uid, tokenVerificationId: tokenVerificationId - }) + }); } }) .catch(function (err) { - log.error('mailer.sendVerifyCode.1', { err: err}) + log.error('mailer.sendVerifyCode.1', { err: err}); if (tokenVerificationId) { // Log possible email bounce, used for confirming verification rates @@ -289,13 +289,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uid: account.uid, err: err, tokenVerificationId: tokenVerificationId - }) + }); } // show an error to the user, the account is already created. // the user can come back later and try again. - throw emailUtils.sendError(err, true) - }) + throw emailUtils.sendError(err, true); + }); } } @@ -310,15 +310,15 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) wrapKb: wrapKb, emailVerified: account.emailVerified, tokenVerificationId: tokenVerificationId - }) + }); } ) .then( function (result) { - keyFetchToken = result - return request.stashMetricsContext(keyFetchToken) + keyFetchToken = result; + return request.stashMetricsContext(keyFetchToken); } - ) + ); } } @@ -328,7 +328,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uid: account.uid, ipAddr: request.app.clientAddress, tokenId: sessionToken.id - }) + }); } function createResponse () { @@ -336,13 +336,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uid: account.uid, sessionToken: sessionToken.data, authAt: sessionToken.lastAuthAt() - } + }; if (keyFetchToken) { - response.keyFetchToken = keyFetchToken.data + response.keyFetchToken = keyFetchToken.data; } - return response + return response; } } }, @@ -393,19 +393,19 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - log.begin('Account.login', request) + log.begin('Account.login', request); - const form = request.payload - const email = form.email - const authPW = form.authPW - const originalLoginEmail = request.payload.originalLoginEmail - let verificationMethod = request.payload.verificationMethod - const requestNow = Date.now() + const form = request.payload; + const email = form.email; + const authPW = form.authPW; + const originalLoginEmail = request.payload.originalLoginEmail; + let verificationMethod = request.payload.verificationMethod; + const requestNow = Date.now(); - let accountRecord, password, sessionToken, keyFetchToken, didSigninUnblock - let securityEventRecency = Infinity, securityEventVerified = false + let accountRecord, password, sessionToken, keyFetchToken, didSigninUnblock; + let securityEventRecency = Infinity, securityEventVerified = false; - request.validateMetricsContext() + request.validateMetricsContext(); return checkCustomsAndLoadAccount() .then(checkEmailAndPassword) @@ -414,15 +414,15 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) .then(createSessionToken) .then(sendSigninNotifications) .then(createKeyFetchToken) - .then(createResponse) + .then(createResponse); function checkCustomsAndLoadAccount() { return signinUtils.checkCustomsAndLoadAccount(request, email).then((res) => { - accountRecord = res.accountRecord + accountRecord = res.accountRecord; // Remember whether they did a signin-unblock, // because we can use it to bypass token verification. - didSigninUnblock = res.didSigninUnblock - }) + didSigninUnblock = res.didSigninUnblock; + }); } function checkEmailAndPassword() { @@ -432,14 +432,14 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) authPW, accountRecord.authSalt, accountRecord.verifierVersion - ) - return signinUtils.checkPassword(accountRecord, password, request.app.clientAddress) + ); + return signinUtils.checkPassword(accountRecord, password, request.app.clientAddress); }) .then(match => { if (! match) { - throw error.incorrectPassword(accountRecord.email, email) + throw error.incorrectPassword(accountRecord.email, email); } - }) + }); } function checkSecurityHistory () { @@ -450,38 +450,38 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) .then( (events) => { if (events.length > 0) { - let latest = 0 + let latest = 0; events.forEach(function(ev) { if (ev.verified) { - securityEventVerified = true + securityEventVerified = true; if (ev.createdAt > latest) { - latest = ev.createdAt + latest = ev.createdAt; } } - }) + }); if (securityEventVerified) { - securityEventRecency = requestNow - latest - let coarseRecency + securityEventRecency = requestNow - latest; + let coarseRecency; if (securityEventRecency < MS_ONE_DAY) { - coarseRecency = 'day' + coarseRecency = 'day'; } else if (securityEventRecency < MS_ONE_WEEK) { - coarseRecency = 'week' + coarseRecency = 'week'; } else if (securityEventRecency < MS_ONE_MONTH) { - coarseRecency = 'month' + coarseRecency = 'month'; } else { - coarseRecency = 'old' + coarseRecency = 'old'; } log.info('Account.history.verified', { uid: accountRecord.uid, events: events.length, recency: coarseRecency - }) + }); } else { log.info('Account.history.unverified', { uid: accountRecord.uid, events: events.length - }) + }); } } }, @@ -492,9 +492,9 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) log.error('Account.history.error', { err: err, uid: accountRecord.uid - }) + }); } - ) + ); } function checkTotpToken() { @@ -505,17 +505,17 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) .then((result) => { if (result) { // User has enabled TOTP, no way around it, they must verify TOTP token - verificationMethod = 'totp-2fa' + verificationMethod = 'totp-2fa'; } else if (! result && verificationMethod === 'totp-2fa') { // Error if requesting TOTP verification with TOTP not setup - throw error.totpRequired() + throw error.totpRequired(); } - }) + }); } function createSessionToken () { // All sessions are considered unverified by default. - let needsVerificationId = true + let needsVerificationId = true; // However! To help simplify the login flow, we can use some heuristics to // decide whether to consider the session pre-verified. Some accounts @@ -523,33 +523,33 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) // to know for sure what flow they're going to see. if (! forceTokenVerification(request, accountRecord)) { if (skipTokenVerification(request, accountRecord)) { - needsVerificationId = false + needsVerificationId = false; } } // If they just went through the sigin-unblock flow, they have already verified their email. // We don't need to force them to do that again, just make a verified session. if (didSigninUnblock) { - needsVerificationId = false + needsVerificationId = false; } // If the request wants keys , user *must* confirm their login session before they can actually // use it. Otherwise, they don't *have* to verify their session. All sessions are created // unverified because it prevents them from being used for sync. - let mustVerifySession = needsVerificationId && requestHelper.wantsKeys(request) + let mustVerifySession = needsVerificationId && requestHelper.wantsKeys(request); // For accounts with TOTP, we always force verifying a session. if (verificationMethod === 'totp-2fa') { - mustVerifySession = true - needsVerificationId = true + mustVerifySession = true; + needsVerificationId = true; } return P.resolve() .then(() => { if (! needsVerificationId) { - return [] + return []; } - return [random.hex(16), TokenCode()] + return [random.hex(16), TokenCode()]; }) .spread((tokenVerificationId, tokenVerificationCode) => { const { @@ -559,7 +559,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) osVersion: uaOSVersion, deviceType: uaDeviceType, formFactor: uaFormFactor - } = request.app.ua + } = request.app.ua; const sessionTokenOptions = { uid: accountRecord.uid, @@ -577,77 +577,77 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uaOSVersion, uaDeviceType, uaFormFactor - } + }; - return db.createSessionToken(sessionTokenOptions) + return db.createSessionToken(sessionTokenOptions); }) .then(result => { - sessionToken = result - }) + sessionToken = result; + }); } function forceTokenVerification (request, account) { // If there was anything suspicious about the request, // we should force token verification. if (request.app.isSuspiciousRequest) { - return true + return true; } // If it's an email address used for testing etc, // we should force token verification. if (config.signinConfirmation) { if (config.signinConfirmation.forcedEmailAddresses) { if (config.signinConfirmation.forcedEmailAddresses.test(account.primaryEmail.email)) { - return true + return true; } } } - return false + return false; } function skipTokenVerification (request, account) { // If they're logging in from an IP address on which they recently did // another, successfully-verified login, then we can consider this one // verified as well without going through the loop again. - const allowedRecency = config.securityHistory.ipProfiling.allowedRecency || 0 + const allowedRecency = config.securityHistory.ipProfiling.allowedRecency || 0; if (securityEventVerified && securityEventRecency < allowedRecency) { log.info('Account.ipprofiling.seenAddress', { uid: account.uid - }) - return true + }); + return true; } // If the account was recently created, don't make the user // confirm sign-in for a configurable amount of time. This will reduce // the friction of a user adding a second device. - const skipForNewAccounts = config.signinConfirmation.skipForNewAccounts + const skipForNewAccounts = config.signinConfirmation.skipForNewAccounts; if (skipForNewAccounts && skipForNewAccounts.enabled) { - const accountAge = requestNow - account.createdAt + const accountAge = requestNow - account.createdAt; if (accountAge <= skipForNewAccounts.maxAge) { log.info('account.signin.confirm.bypass.age', { uid: account.uid - }) - return true + }); + return true; } } // Certain accounts have the ability to *always* skip sign-in confirmation // regardless of account age or device. This is for internal use where we need // to guarantee the login experience. - const lowerCaseEmail = account.primaryEmail.normalizedEmail.toLowerCase() - const alwaysSkip = skipConfirmationForEmailAddresses && skipConfirmationForEmailAddresses.includes(lowerCaseEmail) + const lowerCaseEmail = account.primaryEmail.normalizedEmail.toLowerCase(); + const alwaysSkip = skipConfirmationForEmailAddresses && skipConfirmationForEmailAddresses.includes(lowerCaseEmail); if (alwaysSkip) { log.info('account.signin.confirm.bypass.always', { uid: account.uid - }) - return true + }); + return true; } - return false + return false; } async function sendSigninNotifications() { - await signinUtils.sendSigninNotifications(request, accountRecord, sessionToken, verificationMethod) + await signinUtils.sendSigninNotifications(request, accountRecord, sessionToken, verificationMethod); // For new sync logins that don't send some other sort of email, // send an after-the-fact notification email so that the user @@ -655,10 +655,10 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) if (accountRecord.primaryEmail.isVerified) { if (sessionToken.tokenVerified || ! sessionToken.mustVerify) { if (requestHelper.wantsKeys(request)) { - const geoData = request.app.geo - const service = request.payload.service || request.query.service - const ip = request.app.clientAddress - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const geoData = request.app.geo; + const service = request.payload.service || request.query.service; + const ip = request.app.clientAddress; + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; try { await mailer.sendNewDeviceLoginNotification( @@ -680,13 +680,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid } - ) + ); } catch (err) { // If we couldn't email them, no big deal. Log // and pretend everything worked. log.trace('Account.login.sendNewDeviceLoginNotification.error', { error: err - }) + }); } } } @@ -697,8 +697,8 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) if (requestHelper.wantsKeys(request)) { return signinUtils.createKeyFetchToken(request, accountRecord, password, sessionToken) .then(result => { - keyFetchToken = result - }) + keyFetchToken = result; + }); } } @@ -707,15 +707,15 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uid: sessionToken.uid, sessionToken: sessionToken.data, authAt: sessionToken.lastAuthAt() - } + }; if (keyFetchToken) { - response.keyFetchToken = keyFetchToken.data + response.keyFetchToken = keyFetchToken.data; } - Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)) + Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)); - return response + return response; } } }, @@ -734,27 +734,27 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - var sessionToken = request.auth.credentials + var sessionToken = request.auth.credentials; if (sessionToken) { - return { exists: true, locale: sessionToken.locale } + return { exists: true, locale: sessionToken.locale }; } else if (request.query.uid) { - var uid = request.query.uid + var uid = request.query.uid; return db.account(uid) .then( function (account) { - return { exists: true } + return { exists: true }; }, function (err) { if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) { - return { exists: false } + return { exists: false }; } - throw err + throw err; } - ) + ); } else { - throw error.missingRequestParameter('uid') + throw error.missingRequestParameter('uid'); } } }, @@ -774,28 +774,28 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - var email = request.payload.email + var email = request.payload.email; return customs.check( request, email, 'accountStatusCheck') .then(() => { - return db.accountExists(email) + return db.accountExists(email); }) .then( function (exist) { return { exists: exist - } + }; }, function (err) { if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) { - return { exists: false } + return { exists: false }; } - throw err + throw err; } - ) + ); } }, { @@ -819,43 +819,43 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - const auth = request.auth - let uid, scope, account + const auth = request.auth; + let uid, scope, account; if (auth.strategy === 'sessionToken') { - uid = auth.credentials.uid - scope = { contains: () => true } + uid = auth.credentials.uid; + scope = { contains: () => true }; } else { - uid = auth.credentials.user - scope = ScopeSet.fromArray(auth.credentials.scope) + uid = auth.credentials.user; + scope = ScopeSet.fromArray(auth.credentials.scope); } - const res = {} + const res = {}; return db.account(uid) .then(result => { - account = result + account = result; if (scope.contains('profile:email')) { - res.email = account.primaryEmail.email + res.email = account.primaryEmail.email; } if (scope.contains('profile:locale')) { - res.locale = account.locale + res.locale = account.locale; } if (scope.contains('profile:amr')) { return authMethods.availableAuthenticationMethods(db, account) .then(amrValues => { - res.authenticationMethods = Array.from(amrValues) - res.authenticatorAssuranceLevel = authMethods.maximumAssuranceLevel(amrValues) - }) + res.authenticationMethods = Array.from(amrValues); + res.authenticatorAssuranceLevel = authMethods.maximumAssuranceLevel(amrValues); + }); } }) .then(() => { // If no keys set on the response, there was no valid profile scope found. We only // want to return `profileChangedAt` if a valid scope was found and set. if (Object.keys(res).length !== 0) { - res.profileChangedAt = account.profileChangedAt + res.profileChangedAt = account.profileChangedAt; } - return res - }) + return res; + }); } }, { @@ -872,29 +872,29 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function accountKeys(request) { - log.begin('Account.keys', request) - var keyFetchToken = request.auth.credentials + log.begin('Account.keys', request); + var keyFetchToken = request.auth.credentials; - var verified = keyFetchToken.tokenVerified && keyFetchToken.emailVerified + var verified = keyFetchToken.tokenVerified && keyFetchToken.emailVerified; if (! verified) { // don't delete the token on use until the account is verified - throw error.unverifiedAccount() + throw error.unverifiedAccount(); } return db.deleteKeyFetchToken(keyFetchToken) .then( function () { return request.emitMetricsEvent('account.keyfetch', { uid: keyFetchToken.uid - }) + }); } ) .then( function () { return { bundle: keyFetchToken.keyBundle - } + }; } - ) + ); } }, { @@ -906,8 +906,8 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - log.error('Account.UnlockCodeResend', { request: request }) - throw error.gone() + log.error('Account.UnlockCodeResend', { request: request }); + throw error.gone(); } }, { @@ -919,8 +919,8 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - log.error('Account.UnlockCodeVerify', { request: request }) - throw error.gone() + log.error('Account.UnlockCodeVerify', { request: request }); + throw error.gone(); } }, { @@ -944,13 +944,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function accountReset(request) { - log.begin('Account.reset', request) - const accountResetToken = request.auth.credentials - const authPW = request.payload.authPW - const hasSessionToken = request.payload.sessionToken - let wrapKb = request.payload.wrapKb - const recoveryKeyId = request.payload.recoveryKeyId - let account, sessionToken, keyFetchToken, verifyHash, wrapWrapKb, password, hasTotpToken = false, tokenVerificationId + log.begin('Account.reset', request); + const accountResetToken = request.auth.credentials; + const authPW = request.payload.authPW; + const hasSessionToken = request.payload.sessionToken; + let wrapKb = request.payload.wrapKb; + const recoveryKeyId = request.payload.recoveryKeyId; + let account, sessionToken, keyFetchToken, verifyHash, wrapWrapKb, password, hasTotpToken = false, tokenVerificationId; return checkRecoveryKey() .then(checkTotpToken) @@ -959,35 +959,35 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) .then(createSessionToken) .then(createKeyFetchToken) .then(recordSecurityEvent) - .then(createResponse) + .then(createResponse); function checkRecoveryKey() { if (recoveryKeyId) { - return db.getRecoveryKey(accountResetToken.uid, recoveryKeyId) + return db.getRecoveryKey(accountResetToken.uid, recoveryKeyId); } - return P.resolve() + return P.resolve(); } function checkTotpToken() { return totpUtils.hasTotpToken({uid: accountResetToken.uid}) .then((result) => { - hasTotpToken = result - }) + hasTotpToken = result; + }); } function resetAccountData () { - let authSalt + let authSalt; return random.hex(32) .then(hex => { - authSalt = hex - password = new Password(authPW, authSalt, config.verifierVersion) - return password.verifyHash() + authSalt = hex; + password = new Password(authPW, authSalt, config.verifierVersion); + return password.verifyHash(); }) .then((verifyHashData) => { - verifyHash = verifyHashData + verifyHash = verifyHashData; - return setupKb() + return setupKb(); }) .then(() => { return db.resetAccount( @@ -998,19 +998,19 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) wrapWrapKb, verifierVersion: password.version } - ) + ); }) .then(() => { // Delete all passwordChangeTokens, passwordForgotTokens and // accountResetTokens associated with this uid - return db.resetAccountTokens(accountResetToken.uid) + return db.resetAccountTokens(accountResetToken.uid); }) .then(() => { // Notify the devices that the account has changed. - request.app.devices.then(devices => push.notifyPasswordReset(accountResetToken.uid, devices)) + request.app.devices.then(devices => push.notifyPasswordReset(accountResetToken.uid, devices)); return db.account(accountResetToken.uid) - .then((accountData) => account = accountData) + .then((accountData) => account = accountData); }) .then(() => { return P.all([ @@ -1023,20 +1023,20 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) generation: account.verifierSetAt }), customs.reset(account.email) - ]) - }) + ]); + }); } function setupKb() { if (recoveryKeyId) { // We have the previous kB, just re-wrap it with the new password. - return password.wrap(wrapKb).then(result => wrapWrapKb = result) + return password.wrap(wrapKb).then(result => wrapWrapKb = result); } else { // We need to regenerate kB and wrap it with the new password. return random.hex(32).then(result => { - wrapWrapKb = result - return password.unwrap(wrapWrapKb).then(result => wrapKb = result) - }) + wrapWrapKb = result; + return password.unwrap(wrapWrapKb).then(result => wrapKb = result); + }); } } @@ -1046,8 +1046,8 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) if (recoveryKeyId) { return db.deleteRecoveryKey(account.uid) .then(() => { - const geoData = request.app.geo - const ip = request.app.clientAddress + const geoData = request.app.geo; + const ip = request.app.clientAddress; const emailOptions = { acceptLanguage: request.app.acceptLanguage, ip: ip, @@ -1059,13 +1059,13 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: account.uid - } + }; - return mailer.sendPasswordResetAccountRecoveryNotification(account.emails, account, emailOptions) - }) + return mailer.sendPasswordResetAccountRecoveryNotification(account.emails, account, emailOptions); + }); } - return P.resolve() + return P.resolve(); } function createSessionToken () { @@ -1077,7 +1077,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) osVersion: uaOSVersion, deviceType: uaDeviceType, formFactor: uaFormFactor - } = request.app.ua + } = request.app.ua; return Promise.resolve() .then(() => { @@ -1085,12 +1085,12 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) // link from the user's email, we create a verified sessionToken // **unless** the user has a TOTP token. if (! hasTotpToken) { - return + return; } - return random.hex(16) + return random.hex(16); }) .then((randomHex) => { - tokenVerificationId = randomHex + tokenVerificationId = randomHex; const sessionTokenOptions = { uid: account.uid, email: account.primaryEmail.email, @@ -1105,14 +1105,14 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uaOSVersion, uaDeviceType, uaFormFactor - } + }; return db.createSessionToken(sessionTokenOptions) .then(function (result) { - sessionToken = result - return request.propagateMetricsContext(accountResetToken, sessionToken) - }) - }) + sessionToken = result; + return request.propagateMetricsContext(accountResetToken, sessionToken); + }); + }); } } @@ -1121,7 +1121,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) if (! hasSessionToken) { // Sanity-check: any client requesting keys, // should also be requesting a sessionToken. - throw error.missingRequestParameter('sessionToken') + throw error.missingRequestParameter('sessionToken'); } return db.createKeyFetchToken({ uid: account.uid, @@ -1132,10 +1132,10 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) }) .then( function (result) { - keyFetchToken = result - return request.propagateMetricsContext(accountResetToken, keyFetchToken) + keyFetchToken = result; + return request.propagateMetricsContext(accountResetToken, keyFetchToken); } - ) + ); } } @@ -1145,14 +1145,14 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) uid: account.uid, ipAddr: request.app.clientAddress, tokenId: sessionToken && sessionToken.id - }) + }); } function createResponse () { // If no sessionToken, this could be a legacy client // attempting to reset an account password, return legacy response. if (! hasSessionToken) { - return {} + return {}; } @@ -1161,16 +1161,16 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) sessionToken: sessionToken.data, verified: sessionToken.emailVerified, authAt: sessionToken.lastAuthAt() - } + }; if (requestHelper.wantsKeys(request)) { - response.keyFetchToken = keyFetchToken.data + response.keyFetchToken = keyFetchToken.data; } - const verificationMethod = hasTotpToken ? 'totp-2fa' : undefined - Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)) + const verificationMethod = hasTotpToken ? 'totp-2fa' : undefined; + Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)); - return response + return response; } } }, @@ -1190,58 +1190,58 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function accountDestroy(request) { - log.begin('Account.destroy', request) - const form = request.payload - const authPW = form.authPW + log.begin('Account.destroy', request); + const form = request.payload; + const authPW = form.authPW; return customs.check(request, form.email, 'accountDestroy') .then(db.accountRecord.bind(db, form.email)) .then((emailRecord) => { return totpUtils.hasTotpToken(emailRecord) .then((hasToken) => { - const sessionToken = request.auth && request.auth.credentials + const sessionToken = request.auth && request.auth.credentials; // Someone tried to delete an account with TOTP but did not specify a session. // This shouldn't happen in practice, but just in case we throw unverified session. if (! sessionToken && hasToken) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } // If TOTP is enabled, ensure that the session has the correct assurance level before // deleting account. if (sessionToken && hasToken && (sessionToken.tokenVerificationId || sessionToken.authenticatorAssuranceLevel <= 1)) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } // In other scenarios, fall back to the default behavior and let the user // delete the account - return emailRecord - }) + return emailRecord; + }); }) .then((emailRecord) => { - const uid = emailRecord.uid - const password = new Password(authPW, emailRecord.authSalt, emailRecord.verifierVersion) - let devicesToNotify + const uid = emailRecord.uid; + const password = new Password(authPW, emailRecord.authSalt, emailRecord.verifierVersion); + let devicesToNotify; return signinUtils.checkPassword(emailRecord, password, request.app.clientAddress) .then((match) => { if (! match) { - throw error.incorrectPassword(emailRecord.email, form.email) + throw error.incorrectPassword(emailRecord.email, form.email); } // We fetch the devices to notify before deleteAccount() // because obviously we can't retrieve the devices list after! - return db.devices(uid) + return db.devices(uid); }) .then((devices) => { - devicesToNotify = devices + devicesToNotify = devices; return db.deleteAccount(emailRecord) - .then(() => log.info('accountDeleted.byRequest', { ...emailRecord })) + .then(() => log.info('accountDeleted.byRequest', { ...emailRecord })); }) .then(() => { push.notifyAccountDestroyed(uid, devicesToNotify) .catch(() => { // Ignore notification errors since this account no longer exists - }) + }); return P.all([ log.notifyAttachedServices('delete', request, { @@ -1249,24 +1249,24 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) iss: config.domain }), request.emitMetricsEvent('account.deleted', {uid}) - ]) - }) + ]); + }); }, (err) => { if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) { customs.flag(request.app.clientAddress, { email: form.email, errno: err.errno - }) + }); } - throw err - }) + throw err; + }); } } - ] + ]; if (config.isProduction) { - delete routes[0].options.validate.payload.preVerified + delete routes[0].options.validate.payload.preVerified; } else { // programmatic account lockout was only available in // non-production mode. @@ -1279,11 +1279,11 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) } }, handler: async function (request) { - log.error('Account.lock', { request: request }) - throw error.gone() + log.error('Account.lock', { request: request }); + throw error.gone(); } - }) + }); } - return routes -} + return routes; +}; diff --git a/lib/routes/defaults.js b/lib/routes/defaults.js index 9e7eb673..e512e759 100644 --- a/lib/routes/defaults.js +++ b/lib/routes/defaults.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; -const path = require('path') -const cp = require('child_process') -const error = require('../error') +const path = require('path'); +const cp = require('child_process'); +const error = require('../error'); -const version = require('../../package.json').version -var commitHash -var sourceRepo +const version = require('../../package.json').version; +var commitHash; +var sourceRepo; -const UNKNOWN = 'unknown' +const UNKNOWN = 'unknown'; // Production and stage provide './config/version.json'. Try to load this at // startup; punt on failure. For dev environments, we'll get this from `git` // for dev environments. try { - var versionJson = path.join(__dirname, '..', '..', 'config', 'version.json') - var info = require(versionJson) - commitHash = info.version.hash - sourceRepo = info.version.source + var versionJson = path.join(__dirname, '..', '..', 'config', 'version.json'); + var info = require(versionJson); + commitHash = info.version.hash; + sourceRepo = info.version.source; } catch (e) { /* ignore */ } @@ -29,22 +29,22 @@ try { module.exports = (log, db) => { async function versionHandler(request, h) { - log.begin('Defaults.root', request) + log.begin('Defaults.root', request); function getVersion() { return new Promise(function (resolve, reject) { // ignore errors and default to 'unknown' if not found - var gitDir = path.resolve(__dirname, '..', '..', '.git') + var gitDir = path.resolve(__dirname, '..', '..', '.git'); cp.exec('git rev-parse HEAD', { cwd: gitDir }, function(err, stdout1) { - var configPath = path.join(gitDir, 'config') - var cmd = 'git config --get remote.origin.url' + var configPath = path.join(gitDir, 'config'); + var cmd = 'git config --get remote.origin.url'; cp.exec(cmd, { env: { GIT_CONFIG: configPath, PATH: process.env.PATH } }, function(err, stdout2) { - commitHash = (stdout1 && stdout1.trim()) || UNKNOWN - sourceRepo = (stdout2 && stdout2.trim()) || UNKNOWN - resolve() - }) - }) + commitHash = (stdout1 && stdout1.trim()) || UNKNOWN; + sourceRepo = (stdout2 && stdout2.trim()) || UNKNOWN; + resolve(); + }); + }); }); } @@ -53,15 +53,15 @@ module.exports = (log, db) => { version: version, commit: commitHash, source: sourceRepo - }).spaces(2).suffix('\n') + }).spaces(2).suffix('\n'); } // if we already have the commitHash, send the reply and return if (commitHash) { - return getResp() + return getResp(); } - await getVersion() + await getVersion(); return getResp(); } @@ -81,25 +81,25 @@ module.exports = (log, db) => { method: 'GET', path: '/__heartbeat__', handler: async function heartbeat(request) { - log.begin('Defaults.heartbeat', request) + log.begin('Defaults.heartbeat', request); return db.ping() .then( function () { - return {} + return {}; }, function (err) { - log.error('heartbeat', { err: err }) - throw error.serviceUnavailable() + log.error('heartbeat', { err: err }); + throw error.serviceUnavailable(); } - ) + ); } }, { method: 'GET', path: '/__lbheartbeat__', handler: async function heartbeat(request) { - log.begin('Defaults.lbheartbeat', request) - return {} + log.begin('Defaults.lbheartbeat', request); + return {}; } }, { @@ -112,11 +112,11 @@ module.exports = (log, db) => { } }, handler: async function v0(request) { - log.begin('Defaults.v0', request) - throw error.gone() + log.begin('Defaults.v0', request); + throw error.gone(); } } - ] + ]; - return routes -} + return routes; +}; diff --git a/lib/routes/devices-and-sessions.js b/lib/routes/devices-and-sessions.js index f64709b5..8fc6dfe1 100644 --- a/lib/routes/devices-and-sessions.js +++ b/lib/routes/devices-and-sessions.js @@ -2,61 +2,61 @@ * 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/. */ -'use strict' +'use strict'; -const { URL } = require('url') -const Ajv = require('ajv') -const ajv = new Ajv() -const error = require('../error') -const fs = require('fs') -const i18n = require('i18n-abide') -const isA = require('joi') -const P = require('../promise') -const path = require('path') -const validators = require('./validators') +const { URL } = require('url'); +const Ajv = require('ajv'); +const ajv = new Ajv(); +const error = require('../error'); +const fs = require('fs'); +const i18n = require('i18n-abide'); +const isA = require('joi'); +const P = require('../promise'); +const path = require('path'); +const validators = require('./validators'); -const HEX_STRING = validators.HEX_STRING -const DEVICES_SCHEMA = require('../devices').schema -const PUSH_PAYLOADS_SCHEMA_PATH = path.resolve(__dirname, '../../docs/pushpayloads.schema.json') +const HEX_STRING = validators.HEX_STRING; +const DEVICES_SCHEMA = require('../devices').schema; +const PUSH_PAYLOADS_SCHEMA_PATH = path.resolve(__dirname, '../../docs/pushpayloads.schema.json'); // Assign a default TTL for well-known commands if a request didn't specify it. const DEFAULT_COMMAND_TTL = new Map([ ['https://identity.mozilla.com/cmd/open-uri', 30 * 24 * 3600], // 30 days -]) +]); module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => { // Loads and compiles a json validator for the payloads received // in /account/devices/notify - const validatePushSchema = JSON.parse(fs.readFileSync(PUSH_PAYLOADS_SCHEMA_PATH)) - const validatePushPayloadAjv = ajv.compile(validatePushSchema) - const { supportedLanguages, defaultLanguage } = config.i18n + const validatePushSchema = JSON.parse(fs.readFileSync(PUSH_PAYLOADS_SCHEMA_PATH)); + const validatePushPayloadAjv = ajv.compile(validatePushSchema); + const { supportedLanguages, defaultLanguage } = config.i18n; const localizeTimestamp = require('fxa-shared').l10n.localizeTimestamp({ supportedLanguages, defaultLanguage - }) - const earliestSaneTimestamp = config.lastAccessTimeUpdates.earliestSaneTimestamp + }); + const earliestSaneTimestamp = config.lastAccessTimeUpdates.earliestSaneTimestamp; function validatePushPayload(payload, endpoint) { if (endpoint === 'accountVerify') { if (isEmpty(payload)) { - return true + return true; } - return false + return false; } - return validatePushPayloadAjv(payload) + return validatePushPayloadAjv(payload); } function isEmpty(payload) { - return payload && Object.keys(payload).length === 0 + return payload && Object.keys(payload).length === 0; } function marshallLastAccessTime (lastAccessTime, request) { - const languages = request.app.acceptLanguage + const languages = request.app.acceptLanguage; const result = { lastAccessTime, lastAccessTimeFormatted: localizeTimestamp.format(lastAccessTime, languages), - } + }; if (lastAccessTime < earliestSaneTimestamp) { // Values older than earliestSaneTimestamp are probably wrong. @@ -64,24 +64,24 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => // an approximate string like "last sync over 2 months ago". // And do it using additional properties so we don't affect // older content servers that are unfamiliar with the change. - result.approximateLastAccessTime = earliestSaneTimestamp - result.approximateLastAccessTimeFormatted = localizeTimestamp.format(earliestSaneTimestamp, languages) + result.approximateLastAccessTime = earliestSaneTimestamp; + result.approximateLastAccessTimeFormatted = localizeTimestamp.format(earliestSaneTimestamp, languages); } - return result + return result; } function marshallLocation (location, request) { - let language + let language; if (! location) { // Shortcut the error logging if location isn't set - return {} + return {}; } try { - const languages = i18n.parseAcceptLanguage(request.app.acceptLanguage) - language = i18n.bestLanguage(languages, supportedLanguages, defaultLanguage) + const languages = i18n.parseAcceptLanguage(request.app.acceptLanguage); + language = i18n.bestLanguage(languages, supportedLanguages, defaultLanguage); if (language[0] === 'e' && language[1] === 'n') { // For English, return all of the location components @@ -90,25 +90,25 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => country: location.country, state: location.state, stateCode: location.stateCode - } + }; } // For other languages, only return what we can translate - const territories = require(`cldr-localenames-full/main/${language}/territories.json`) + const territories = require(`cldr-localenames-full/main/${language}/territories.json`); return { country: territories.main[language].localeDisplayNames.territories[location.countryCode] - } + }; } catch (err) { log.warn('devices.marshallLocation.warning', { err: err.message, languages: request.app.acceptLanguage, language, location - }) + }); } // If something failed, don't return location - return {} + return {}; } // Creates a "full" device response, provided a credentials object and an optional @@ -128,7 +128,7 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => name: (device && device.name) || credentials.deviceName || devices.synthesizeName(credentials), type: (device && device.type) || credentials.deviceType || 'desktop', availableCommands: (device && device.availableCommands) || credentials.deviceAvailableCommands || {}, - } + }; } return [ @@ -173,51 +173,51 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.device', request) + log.begin('Account.device', request); - const payload = request.payload - const credentials = request.auth.credentials + const payload = request.payload; + const credentials = request.auth.credentials; // Remove obsolete field, so we don't try to echo it back to the client. - delete payload.capabilities + delete payload.capabilities; // Some additional, slightly tricky validation to detect bad public keys. if (payload.pushPublicKey && ! push.isValidPublicKey(payload.pushPublicKey)) { - throw error.invalidRequestParameter('invalid pushPublicKey') + throw error.invalidRequestParameter('invalid pushPublicKey'); } if (payload.id) { // Don't write out the update if nothing has actually changed. if (devices.isSpuriousUpdate(payload, credentials)) { - return buildDeviceResponse(credentials) + return buildDeviceResponse(credentials); } // We also reserve the right to disable updates until // we're confident clients are behaving correctly. if (config.deviceUpdatesEnabled === false) { - throw error.featureNotEnabled() + throw error.featureNotEnabled(); } } else if (credentials.deviceId) { // Keep the old id, which is probably from a synthesized device record - payload.id = credentials.deviceId + payload.id = credentials.deviceId; } const pushEndpointOk = ! payload.id || // New device. (payload.id && payload.pushCallback && - payload.pushCallback !== credentials.deviceCallbackURL) // Updating the pushCallback + payload.pushCallback !== credentials.deviceCallbackURL); // Updating the pushCallback if (pushEndpointOk) { - payload.pushEndpointExpired = false + payload.pushEndpointExpired = false; } // We're doing a gradual rollout of the 'device commands' feature // in support of pushbox, so accept an 'availableCommands' field // if pushbox is enabled. if (payload.availableCommands && ! config.pushbox.enabled) { - payload.availableCommands = {} + payload.availableCommands = {}; } - const device = await devices.upsert(request, credentials, payload) - return buildDeviceResponse(credentials, device) + const device = await devices.upsert(request, credentials, payload); + return buildDeviceResponse(credentials, device); } }, { @@ -252,19 +252,19 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.deviceCommands', request) + log.begin('Account.deviceCommands', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const deviceId = sessionToken.deviceId - const query = request.query || {} - const {index, limit} = query + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const deviceId = sessionToken.deviceId; + const query = request.query || {}; + const {index, limit} = query; return pushbox.retrieve(uid, deviceId, limit, index) .then(resp => { - log.info('commands.fetch', { resp: resp }) - return resp - }) + log.info('commands.fetch', { resp: resp }); + return resp; + }); } }, { @@ -290,19 +290,19 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.invokeDeviceCommand', request) + log.begin('Account.invokeDeviceCommand', request); - const {target, command, payload} = request.payload - let {ttl} = request.payload - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const sender = sessionToken.deviceId + const {target, command, payload} = request.payload; + let {ttl} = request.payload; + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const sender = sessionToken.deviceId; return customs.checkAuthenticated(request, uid, 'invokeDeviceCommand') .then(() => db.device(uid, target)) .then(device => { if (! device.availableCommands.hasOwnProperty(command)) { - throw error.unavailableDeviceCommand() + throw error.unavailableDeviceCommand(); } // 0 is perfectly acceptable TTL, hence the strict equality check. if (ttl === undefined && DEFAULT_COMMAND_TTL.has(command)) { @@ -312,16 +312,16 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => command, payload, sender, - } + }; return pushbox.store(uid, device.id, data, ttl) .then(({index}) => { - const url = new URL('v1/account/device/commands', config.publicUrl) - url.searchParams.set('index', index) - url.searchParams.set('limit', 1) - return push.notifyCommandReceived(uid, device, command, sender, index, url.href, ttl) - }) + const url = new URL('v1/account/device/commands', config.publicUrl); + url.searchParams.set('index', index); + url.searchParams.set('limit', 1); + return push.notifyCommandReceived(uid, device, command, sender, index, url.href, ttl); + }); }) - .then(() => { return {} }) + .then(() => { return {}; }); } }, { @@ -356,54 +356,54 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.devicesNotify', request) + log.begin('Account.devicesNotify', request); // We reserve the right to disable notifications until // we're confident clients are behaving correctly. if (config.deviceNotificationsEnabled === false) { - throw error.featureNotEnabled() + throw error.featureNotEnabled(); } - const body = request.payload - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const payload = body.payload - const endpointAction = body._endpointAction || 'devicesNotify' + const body = request.payload; + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const payload = body.payload; + const endpointAction = body._endpointAction || 'devicesNotify'; if (! validatePushPayload(payload, endpointAction)) { - throw error.invalidRequestParameter('invalid payload') + throw error.invalidRequestParameter('invalid payload'); } const pushOptions = { data: payload - } + }; if (body.TTL) { - pushOptions.TTL = body.TTL + pushOptions.TTL = body.TTL; } return customs.checkAuthenticated(request, uid, endpointAction) .then(() => request.app.devices) .then(devices => { if (body.to !== 'all') { - const include = new Set(body.to) - devices = devices.filter(device => include.has(device.id)) + const include = new Set(body.to); + devices = devices.filter(device => include.has(device.id)); if (devices.length === 0) { log.error('Account.devicesNotify', { uid: uid, error: 'devices empty' - }) - return + }); + return; } } else if (body.excluded) { - const exclude = new Set(body.excluded) - devices = devices.filter(device => ! exclude.has(device.id)) + const exclude = new Set(body.excluded); + devices = devices.filter(device => ! exclude.has(device.id)); } return push.sendPush(uid, devices, endpointAction, pushOptions) - .catch(catchPushError) + .catch(catchPushError); }) .then(() => { // Emit a metrics event for when a user sends tabs between devices. @@ -416,20 +416,20 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => payload.data.collections.length === 1 && payload.data.collections[0] === 'clients' ) { - let deviceId = undefined + let deviceId = undefined; if (sessionToken.deviceId) { - deviceId = sessionToken.deviceId + deviceId = sessionToken.deviceId; } return request.emitMetricsEvent('sync.sentTabToDevice', { device_id: deviceId, service: 'sync', uid: uid - }) + }); } }) - .then(() => { return {} }) + .then(() => { return {}; }); function catchPushError (err) { // push may fail due to not found devices or a bad push action @@ -437,7 +437,7 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => log.error('Account.devicesNotify', { uid: uid, error: err - }) + }); } } }, @@ -471,9 +471,9 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.devices', request) + log.begin('Account.devices', request); - const sessionToken = request.auth.credentials + const sessionToken = request.auth.credentials; return request.app.devices .then(deviceArray => { @@ -489,10 +489,10 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => pushAuthKey: device.pushAuthKey, pushEndpointExpired: device.pushEndpointExpired, availableCommands: device.availableCommands - }, marshallLastAccessTime(device.lastAccessTime, request)) - }) + }, marshallLastAccessTime(device.lastAccessTime, request)); + }); } - ) + ); } }, { @@ -532,30 +532,30 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.sessions', request) + log.begin('Account.sessions', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; return db.sessions(uid) .then(sessions => { return sessions.map(session => { - const deviceId = session.deviceId - const isDevice = !! deviceId + const deviceId = session.deviceId; + const isDevice = !! deviceId; - let deviceName = session.deviceName + let deviceName = session.deviceName; if (! deviceName) { - deviceName = devices.synthesizeName(session) + deviceName = devices.synthesizeName(session); } - let userAgent + let userAgent; if (! session.uaBrowser) { - userAgent = '' + userAgent = ''; } else if (! session.uaBrowserVersion) { - userAgent = session.uaBrowser + userAgent = session.uaBrowser; } else { - const { uaBrowser: browser, uaBrowserVersion: version } = session - userAgent = `${browser} ${version.split('.')[0]}` + const { uaBrowser: browser, uaBrowserVersion: version } = session; + userAgent = `${browser} ${version.split('.')[0]}`; } return Object.assign({ @@ -578,10 +578,10 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => ), os: session.uaOS, userAgent - }, marshallLastAccessTime(session.lastAccessTime, request)) - }) + }, marshallLastAccessTime(session.lastAccessTime, request)); + }); } - ) + ); } }, { @@ -604,32 +604,32 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => } }, handler: async function (request) { - log.begin('Account.deviceDestroy', request) + log.begin('Account.deviceDestroy', request); - const credentials = request.auth.credentials - const uid = credentials.uid - const id = request.payload.id - let devices + const credentials = request.auth.credentials; + const uid = credentials.uid; + const id = request.payload.id; + let devices; // We want to include the disconnected device in the list // of devices to notify, so list them before disconnecting. return request.app.devices .then(res => { - devices = res - return db.deleteDevice(uid, id) + devices = res; + return db.deleteDevice(uid, id); }) .then(() => { - const deviceToDelete = devices.find(d => d.id === id) + const deviceToDelete = devices.find(d => d.id === id); if (deviceToDelete && deviceToDelete.refreshTokenId) { // attempt to clean up the refreshToken in the OAuth DB return oauthdb.revokeRefreshTokenById(deviceToDelete.refreshTokenId).catch((err) => { - log.error('deviceDestroy.revokeRefreshTokenById.error', {err: err.message}) - }) + log.error('deviceDestroy.revokeRefreshTokenById.error', {err: err.message}); + }); } }) .then(() => { push.notifyDeviceDisconnected(uid, devices, id) - .catch(() => {}) + .catch(() => {}); return P.all([ request.emitMetricsEvent('device.deleted', { uid: uid, @@ -640,10 +640,10 @@ module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => id: id, timestamp: Date.now() }) - ]) + ]); }) - .then(() => { return {} }) + .then(() => { return {}; }); } } - ] -} + ]; +}; diff --git a/lib/routes/emails.js b/lib/routes/emails.js index 67a9da29..d40029a9 100644 --- a/lib/routes/emails.js +++ b/lib/routes/emails.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; -const butil = require('../crypto/butil') -const emailUtils = require('./utils/email') -const error = require('../error') -const isA = require('joi') -const P = require('../promise') -const random = require('../crypto/random') -const validators = require('./validators') +const butil = require('../crypto/butil'); +const emailUtils = require('./utils/email'); +const error = require('../error'); +const isA = require('joi'); +const P = require('../promise'); +const random = require('../crypto/random'); +const validators = require('./validators'); -const HEX_STRING = validators.HEX_STRING +const HEX_STRING = validators.HEX_STRING; module.exports = (log, db, mailer, config, customs, push) => { return [ @@ -40,22 +40,22 @@ module.exports = (log, db, mailer, config, customs, push) => { } }, handler: async function (request) { - log.begin('Account.RecoveryEmailStatus', request) + log.begin('Account.RecoveryEmailStatus', request); - const sessionToken = request.auth.credentials + const sessionToken = request.auth.credentials; if (request.query && request.query.reason === 'push') { // log to the push namespace that account was verified via push log.info('push.pushToDevices', { name: 'recovery_email_reason.push' - }) + }); } return cleanUpIfAccountInvalid() - .then(createResponse) + .then(createResponse); function cleanUpIfAccountInvalid () { - const now = new Date().getTime() - const staleTime = now - config.emailStatusPollingTimeout + const now = new Date().getTime(); + const staleTime = now - config.emailStatusPollingTimeout; if (sessionToken.createdAt < staleTime) { log.info('recovery_email.status.stale', { @@ -65,7 +65,7 @@ module.exports = (log, db, mailer, config, customs, push) => { emailVerified: sessionToken.emailVerified, tokenVerified: sessionToken.tokenVerified, browser: `${sessionToken.uaBrowser} ${sessionToken.uaBrowserVersion}` - }) + }); } if (! sessionToken.emailVerified) { // Some historical bugs mean we've allowed creation @@ -75,20 +75,20 @@ module.exports = (log, db, mailer, config, customs, push) => { if (! validators.isValidEmailAddress(sessionToken.email)) { return db.deleteAccount(sessionToken) .then(() => { - log.info('accountDeleted.invalidEmailAddress', { ...sessionToken }) + log.info('accountDeleted.invalidEmailAddress', { ...sessionToken }); // Act as though we deleted the account asynchronously // and caused the sessionToken to become invalid. - throw error.invalidToken('This account was invalid and has been deleted') - }) + throw error.invalidToken('This account was invalid and has been deleted'); + }); } } - return P.resolve() + return P.resolve(); } function createResponse () { - const sessionVerified = sessionToken.tokenVerified - const emailVerified = !! sessionToken.emailVerified + const sessionVerified = sessionToken.tokenVerified; + const emailVerified = !! sessionToken.emailVerified; // For backwards-compatibility reasons, the reported verification status // depends on whether the sessionToken was created with keys=true and @@ -97,9 +97,9 @@ module.exports = (log, db, mailer, config, customs, push) => { // has been verified. Otherwise, desktop clients will attempt to use // an unverified session to connect to sync, and produce a very confusing // user experience. - let isVerified = emailVerified + let isVerified = emailVerified; if (sessionToken.mustVerify) { - isVerified = isVerified && sessionVerified + isVerified = isVerified && sessionVerified; } return { @@ -107,7 +107,7 @@ module.exports = (log, db, mailer, config, customs, push) => { verified: isVerified, sessionVerified: sessionVerified, emailVerified: emailVerified - } + }; } } }, @@ -133,38 +133,38 @@ module.exports = (log, db, mailer, config, customs, push) => { } }, handler: async function (request) { - log.begin('Account.RecoveryEmailResend', request) + log.begin('Account.RecoveryEmailResend', request); - const email = request.payload.email - const sessionToken = request.auth.credentials - const service = request.payload.service || request.query.service - const type = request.payload.type || request.query.type - const ip = request.app.clientAddress - const geoData = request.app.geo + const email = request.payload.email; + const sessionToken = request.auth.credentials; + const service = request.payload.service || request.query.service; + const type = request.payload.type || request.query.type; + const ip = request.app.clientAddress; + const geoData = request.app.geo; // This endpoint can resend multiple types of codes, set these values once it // is known what is being verified. - let code - let verifyFunction - let event - let emails = [] - let sendEmail = true + let code; + let verifyFunction; + let event; + let emails = []; + let sendEmail = true; // Return immediately if this session or token is already verified. Only exception // is if the email param has been specified, which means that this is // a request to verify a secondary email. if (sessionToken.emailVerified && sessionToken.tokenVerified && ! email) { - return {} + return {}; } - const { flowId, flowBeginTime } = await request.app.metricsContext + const { flowId, flowBeginTime } = await request.app.metricsContext; return customs.check(request, sessionToken.email, 'recoveryEmailResendCode') .then(setVerifyCode) .then(setVerifyFunction) .then(() => { if (! sendEmail) { - return + return; } const mailerOpts = { @@ -186,12 +186,12 @@ module.exports = (log, db, mailer, config, customs, push) => { uaOSVersion: sessionToken.uaOSVersion, uaDeviceType: sessionToken.uaDeviceType, uid: sessionToken.uid - } + }; return verifyFunction(emails, sessionToken, mailerOpts) - .then(() => request.emitMetricsEvent(`email.${event}.resent`)) + .then(() => request.emitMetricsEvent(`email.${event}.resent`)); }) - .then(() => { return {} }) + .then(() => { return {}; }); function setVerifyCode () { return db.accountEmails(sessionToken.uid) @@ -199,65 +199,65 @@ module.exports = (log, db, mailer, config, customs, push) => { if (email) { // If an email address is specified in payload, this is a request to verify // a secondary email. This should return the corresponding email code for verification. - let emailVerified = false + let emailVerified = false; emailData.some((userEmail) => { if (userEmail.normalizedEmail === email.toLowerCase()) { - code = userEmail.emailCode - emailVerified = userEmail.isVerified - emails = [userEmail] - return true + code = userEmail.emailCode; + emailVerified = userEmail.isVerified; + emails = [userEmail]; + return true; } - }) + }); // This user is attempting to verify a secondary email that doesn't belong to the account. if (emails.length === 0) { - throw error.cannotResendEmailCodeToUnownedEmail() + throw error.cannotResendEmailCodeToUnownedEmail(); } // Don't resend code for already verified emails if (emailVerified) { - return {} + return {}; } } else if (sessionToken.tokenVerificationId) { - emails = emailData - code = sessionToken.tokenVerificationId + emails = emailData; + code = sessionToken.tokenVerificationId; // Check to see if this account has a verified TOTP token. If so, then it should // not be allowed to bypass TOTP requirement by sending a sign-in confirmation email. return db.totpToken(sessionToken.uid) .then((result) => { if (result && result.verified && result.enabled) { - sendEmail = false - return + sendEmail = false; + return; } - code = sessionToken.tokenVerificationId + code = sessionToken.tokenVerificationId; }, (err) => { if (err.errno === error.ERRNO.TOTP_TOKEN_NOT_FOUND) { - code = sessionToken.tokenVerificationId - return + code = sessionToken.tokenVerificationId; + return; } - throw err - }) + throw err; + }); } else { - code = sessionToken.emailCode + code = sessionToken.emailCode; } - }) + }); } function setVerifyFunction () { if (type && type === 'upgradeSession') { - verifyFunction = mailer.sendVerifyPrimaryEmail - event = 'verification_email_primary' + verifyFunction = mailer.sendVerifyPrimaryEmail; + event = 'verification_email_primary'; } else if (email) { - verifyFunction = mailer.sendVerifySecondaryEmail - event = 'verification_email' + verifyFunction = mailer.sendVerifySecondaryEmail; + event = 'verification_email'; } else if (! sessionToken.emailVerified) { - verifyFunction = mailer.sendVerifyCode - event = 'verification' + verifyFunction = mailer.sendVerifyCode; + event = 'verification'; } else { - verifyFunction = mailer.sendVerifyLoginEmail - event = 'confirmation' + verifyFunction = mailer.sendVerifyLoginEmail; + event = 'confirmation'; } } } @@ -279,15 +279,15 @@ module.exports = (log, db, mailer, config, customs, push) => { } }, handler: async function (request) { - log.begin('Account.RecoveryEmailVerify', request) + log.begin('Account.RecoveryEmailVerify', request); - const { code, marketingOptIn, service, type, uid } = request.payload + const { code, marketingOptIn, service, type, uid } = request.payload; // verify_code because we don't know what type this is yet, but // we want to record right away before anything could fail, so // we can see in a flow that a user tried to verify, even if it // failed right away. - request.emitMetricsEvent('email.verify_code.clicked') + request.emitMetricsEvent('email.verify_code.clicked'); /** * Below is a summary of the verify_code flow. This flow is used to verify emails, sign-in and @@ -305,60 +305,60 @@ module.exports = (log, db, mailer, config, customs, push) => { // This endpoint is not authenticated, so we need to look up // the target email address before we can check it with customs. return customs.check(request, account.email, 'recoveryEmailVerifyCode') - .then(() => { return account }) + .then(() => { return account; }); }) .then((account) => { // Check if param `type` is specified and equal to `secondary` // If so, verify the secondary email and respond if (type && type === 'secondary') { - let matchedEmail + let matchedEmail; return db.accountEmails(uid) .then((emails) => { const isEmailVerification = emails.some((email) => { if (email.emailCode && (code === email.emailCode)) { - matchedEmail = email - log.info('account.verifyEmail.secondary.started', { uid, code }) - return true + matchedEmail = email; + log.info('account.verifyEmail.secondary.started', { uid, code }); + return true; } - }) + }); // Attempt to verify email token not associated with account if (! isEmailVerification) { - throw error.invalidVerificationCode() + throw error.invalidVerificationCode(); } // User is attempting to verify a secondary email that has already been verified. // Silently succeed and don't send post verification email. if (matchedEmail.isVerified) { - log.info('account.verifyEmail.secondary.already-verified', { uid, code }) - return P.resolve() + log.info('account.verifyEmail.secondary.already-verified', { uid, code }); + return P.resolve(); } return db.verifyEmail(account, code) .then(() => { - log.info('account.verifyEmail.secondary.confirmed', { uid, code }) + log.info('account.verifyEmail.secondary.confirmed', { uid, code }); return mailer.sendPostVerifySecondaryEmail([], account, { acceptLanguage: request.app.acceptLanguage, secondaryEmail: matchedEmail.email, service, uid - }) - }) - }) + }); + }); + }); } - const isAccountVerification = butil.buffersAreEqual(code, account.emailCode) - let device + const isAccountVerification = butil.buffersAreEqual(code, account.emailCode); + let device; return db.deviceFromTokenVerificationId(uid, code) .then( associatedDevice => { - device = associatedDevice + device = associatedDevice; }, err => { if (err.errno !== error.ERRNO.DEVICE_UNKNOWN) { - log.error('Account.RecoveryEmailVerify', { err, uid, code }) + log.error('Account.RecoveryEmailVerify', { err, uid, code }); } } ) @@ -374,35 +374,35 @@ module.exports = (log, db, mailer, config, customs, push) => { * * 3) Verify account email if not already verified. */ - return db.verifyTokens(code, account) + return db.verifyTokens(code, account); }) .then(() => { if (! isAccountVerification) { // Don't log sign-in confirmation success for the account verification case - log.info('account.signin.confirm.success', { uid, code }) + log.info('account.signin.confirm.success', { uid, code }); - request.emitMetricsEvent('account.confirmed', { uid }) + request.emitMetricsEvent('account.confirmed', { uid }); request.app.devices.then(devices => push.notifyAccountUpdated(uid, devices, 'accountConfirm') - ) + ); } }) .catch(err => { if (err.errno === error.ERRNO.INVALID_VERIFICATION_CODE && isAccountVerification) { // The code is just for the account, not for any sessions - return + return; } - log.error('account.signin.confirm.invalid', { err, uid, code }) - throw err + log.error('account.signin.confirm.invalid', { err, uid, code }); + throw err; }) .then(() => { if (device) { request.app.devices.then(devices => { - const otherDevices = devices.filter(d => d.id !== device.id) - return push.notifyDeviceConnected(uid, otherDevices, device.name) - }) + const otherDevices = devices.filter(d => d.id !== device.id); + return push.notifyDeviceConnected(uid, otherDevices, device.name); + }); } }) .then(() => { @@ -410,7 +410,7 @@ module.exports = (log, db, mailer, config, customs, push) => { // for sign-in confirmation or they may have been clicking a // stale link. Silently succeed. if (account.emailVerified) { - return + return; } // Any matching code verifies the account @@ -430,13 +430,13 @@ module.exports = (log, db, mailer, config, customs, push) => { marketingOptIn: marketingOptIn || false, uid }) - ]) + ]); }) .then(() => { // send a push notification to all devices that the account changed request.app.devices.then(devices => push.notifyAccountUpdated(uid, devices, 'accountVerify') - ) + ); }) .then(() => { // Our post-verification email is very specific to sync, @@ -446,12 +446,12 @@ module.exports = (log, db, mailer, config, customs, push) => { acceptLanguage: request.app.acceptLanguage, service, uid - }) + }); } - }) - }) + }); + }); }) - .then(() => { return {} }) + .then(() => { return {}; }); } }, @@ -472,22 +472,22 @@ module.exports = (log, db, mailer, config, customs, push) => { } }, handler: async function (request) { - log.begin('Account.RecoveryEmailEmails', request) + log.begin('Account.RecoveryEmailEmails', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; return db.account(uid) .then((account) => { - return createResponse(account.emails) - }) + return createResponse(account.emails); + }); function createResponse (emails) { return emails.map((email) => ({ email: email.email, isPrimary: !! email.isPrimary, verified: !! email.isVerified - })) + })); } } }, @@ -506,85 +506,85 @@ module.exports = (log, db, mailer, config, customs, push) => { response: {} }, handler: async function (request) { - log.begin('Account.RecoveryEmailCreate', request) + log.begin('Account.RecoveryEmailCreate', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const primaryEmail = sessionToken.email - const ip = request.app.clientAddress - const email = request.payload.email + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const primaryEmail = sessionToken.email; + const ip = request.app.clientAddress; + const email = request.payload.email; const emailData = { email: email, normalizedEmail: email.toLowerCase(), isVerified: false, isPrimary: false, uid: uid - } + }; return customs.check(request, primaryEmail, 'createEmail') .then(() => { if (! sessionToken.emailVerified) { - throw error.unverifiedAccount() + throw error.unverifiedAccount(); } if (sessionToken.tokenVerificationId) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } if (sessionToken.email.toLowerCase() === email.toLowerCase()) { - throw error.yourPrimaryEmailExists() + throw error.yourPrimaryEmailExists(); } }) .then(deleteAccountIfUnverified) .then(generateRandomValues) .then(createEmail) .then(sendEmailVerification) - .then(() => { return {} }) + .then(() => { return {}; }); function deleteAccountIfUnverified() { return db.getSecondaryEmail(email) .then((secondaryEmailRecord) => { if (secondaryEmailRecord.isPrimary) { if (secondaryEmailRecord.isVerified) { - throw error.verifiedPrimaryEmailAlreadyExists() + throw error.verifiedPrimaryEmailAlreadyExists(); } - const msSinceCreated = Date.now() - secondaryEmailRecord.createdAt - const minUnverifiedAccountTime = config.secondaryEmail.minUnverifiedAccountTime + const msSinceCreated = Date.now() - secondaryEmailRecord.createdAt; + const minUnverifiedAccountTime = config.secondaryEmail.minUnverifiedAccountTime; if (msSinceCreated >= minUnverifiedAccountTime) { return db.deleteAccount(secondaryEmailRecord) - .then(() => log.info('accountDeleted.unverifiedSecondaryEmail', { ...secondaryEmailRecord })) + .then(() => log.info('accountDeleted.unverifiedSecondaryEmail', { ...secondaryEmailRecord })); } else { - throw error.unverifiedPrimaryEmailNewlyCreated() + throw error.unverifiedPrimaryEmailNewlyCreated(); } } // Only delete secondary email if it is unverified and does not belong // to the current user. if (! secondaryEmailRecord.isVerified && ! butil.buffersAreEqual(secondaryEmailRecord.uid, uid)) { - return db.deleteEmail(secondaryEmailRecord.uid, secondaryEmailRecord.email) + return db.deleteEmail(secondaryEmailRecord.uid, secondaryEmailRecord.email); } }) .catch((err) => { if (err.errno !== error.ERRNO.SECONDARY_EMAIL_UNKNOWN) { - throw err + throw err; } - }) + }); } function generateRandomValues () { return random.hex(16) .then(hex => { - emailData.emailCode = hex - }) + emailData.emailCode = hex; + }); } function createEmail () { - return db.createEmail(uid, emailData) + return db.createEmail(uid, emailData); } function sendEmailVerification() { - const geoData = request.app.geo + const geoData = request.app.geo; return mailer.sendVerifySecondaryEmail([emailData], sessionToken, { code: emailData.emailCode, deviceId: sessionToken.deviceId, @@ -601,12 +601,12 @@ module.exports = (log, db, mailer, config, customs, push) => { uid }) .catch((err) => { - log.error('mailer.sendVerifySecondaryEmail', { err: err}) + log.error('mailer.sendVerifySecondaryEmail', { err: err}); return db.deleteEmail(emailData.uid, emailData.normalizedEmail) .then(() => { - throw emailUtils.sendError(err, true) - }) - }) + throw emailUtils.sendError(err, true); + }); + }); } } }, @@ -625,23 +625,23 @@ module.exports = (log, db, mailer, config, customs, push) => { response: {} }, handler: async function (request) { - log.begin('Account.RecoveryEmailDestroy', request) + log.begin('Account.RecoveryEmailDestroy', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const primaryEmail = sessionToken.email - const email = request.payload.email - let account + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const primaryEmail = sessionToken.email; + const email = request.payload.email; + let account; return customs.check(request, primaryEmail, 'deleteEmail') .then(() => { - return db.account(uid) + return db.account(uid); }) .then((result) => { - account = result + account = result; if (sessionToken.tokenVerificationId) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } }) .then(deleteEmail) @@ -649,35 +649,35 @@ module.exports = (log, db, mailer, config, customs, push) => { .then(() => { // Find the email object that corresponds to the email being deleted const emailIsVerified = account.emails.find((item) => { - return item.normalizedEmail === email.toLowerCase() && item.isVerified - }) + return item.normalizedEmail === email.toLowerCase() && item.isVerified; + }); // Don't bother sending a notification if removing an email that was never verified if (! emailIsVerified) { - return P.resolve() + return P.resolve(); } // Notify only primary email and all *other* verified secondary emails about the // deletion. const emails = account.emails.filter((item) => { if (item.normalizedEmail !== email.toLowerCase()) { - return item + return item; } - }) + }); return mailer.sendPostRemoveSecondaryEmail(emails, account, { deviceId: sessionToken.deviceId, secondaryEmail: email, uid - }) + }); }) - .then(() => { return {} }) + .then(() => { return {}; }); function deleteEmail () { - return db.deleteEmail(uid, email.toLowerCase()) + return db.deleteEmail(uid, email.toLowerCase()); } function resetAccountTokens () { - return db.resetAccountTokens(uid) + return db.resetAccountTokens(uid); } } }, @@ -696,58 +696,58 @@ module.exports = (log, db, mailer, config, customs, push) => { response: {} }, handler: async function (request) { - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const primaryEmail = sessionToken.email - const email = request.payload.email + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const primaryEmail = sessionToken.email; + const email = request.payload.email; - log.begin('Account.RecoveryEmailSetPrimary', request) + log.begin('Account.RecoveryEmailSetPrimary', request); return customs.check(request, primaryEmail, 'setPrimaryEmail') .then(() => { if (sessionToken.tokenVerificationId) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } }) .then(setPrimaryEmail) .then(() => { - return {} - }) + return {}; + }); function setPrimaryEmail() { return db.getSecondaryEmail(email) .then((email) => { if (email.uid !== uid) { - throw error.cannotChangeEmailToUnownedEmail() + throw error.cannotChangeEmailToUnownedEmail(); } if (! email.isVerified) { - throw error.cannotChangeEmailToUnverifiedEmail() + throw error.cannotChangeEmailToUnverifiedEmail(); } if (email.isPrimary) { - return + return; } - return db.setPrimaryEmail(uid, email.normalizedEmail) + return db.setPrimaryEmail(uid, email.normalizedEmail); }) .then(() => { - request.app.devices.then(devices => push.notifyProfileUpdated(uid, devices)) + request.app.devices.then(devices => push.notifyProfileUpdated(uid, devices)); log.notifyAttachedServices('primaryEmailChanged', request, { uid, email: email - }) + }); - return db.account(uid) + return db.account(uid); }) .then((account) => { return mailer.sendPostChangePrimaryEmail(account.emails, account, { acceptLanguage: request.app.acceptLanguage, uid - }) - }) + }); + }); } } } - ] -} + ]; +}; diff --git a/lib/routes/idp.js b/lib/routes/idp.js index 5b27c91b..81346fc0 100644 --- a/lib/routes/idp.js +++ b/lib/routes/idp.js @@ -2,21 +2,21 @@ * 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/. */ -'use strict' +'use strict'; -var jwtool = require('fxa-jwtool') +var jwtool = require('fxa-jwtool'); function b64toDec(str) { - var n = new jwtool.BN(Buffer.from(str, 'base64')) - return n.toString(10) + var n = new jwtool.BN(Buffer.from(str, 'base64')); + return n.toString(10); } function toDec(str) { - return /^[0-9]+$/.test(str) ? str : b64toDec(str) + return /^[0-9]+$/.test(str) ? str : b64toDec(str); } function browseridFormat(keys) { - var primary = keys[0] + var primary = keys[0]; return { 'public-key': { kid: primary.jwk.kid, @@ -28,14 +28,14 @@ function browseridFormat(keys) { authentication: '/.well-known/browserid/nonexistent.html', provisioning: '/.well-known/browserid/nonexistent.html', keys: keys - } + }; } module.exports = function (log, serverPublicKeys) { - var keys = [ serverPublicKeys.primary ] - if (serverPublicKeys.secondary) { keys.push(serverPublicKeys.secondary) } + var keys = [ serverPublicKeys.primary ]; + if (serverPublicKeys.secondary) { keys.push(serverPublicKeys.secondary); } - var browserid = browseridFormat(keys) + var browserid = browseridFormat(keys); var routes = [ { @@ -48,8 +48,8 @@ module.exports = function (log, serverPublicKeys) { } }, handler: async function (request) { - log.begin('browserid', request) - return browserid + log.begin('browserid', request); + return browserid; } }, { @@ -59,10 +59,10 @@ module.exports = function (log, serverPublicKeys) { // FOR DEV PURPOSES ONLY return { keys: keys - } + }; } } - ] + ]; - return routes -} + return routes; +}; diff --git a/lib/routes/index.js b/lib/routes/index.js index d7eadad2..d2586217 100644 --- a/lib/routes/index.js +++ b/lib/routes/index.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -const url = require('url') +const url = require('url'); module.exports = function ( log, @@ -19,13 +19,13 @@ module.exports = function ( customs ) { // Various extra helpers. - const push = require('../push')(log, db, config) - const pushbox = require('../pushbox')(log, config) - const devicesImpl = require('../devices')(log, db, push) - const signinUtils = require('./utils/signin')(log, config, customs, db, mailer) + const push = require('../push')(log, db, config); + const pushbox = require('../pushbox')(log, config); + const devicesImpl = require('../devices')(log, db, push); + const signinUtils = require('./utils/signin')(log, config, customs, db, mailer); // The routing modules themselves. - const defaults = require('./defaults')(log, db) - const idp = require('./idp')(log, serverPublicKeys) + const defaults = require('./defaults')(log, db); + const idp = require('./idp')(log, serverPublicKeys); const account = require('./account')( log, db, @@ -35,10 +35,10 @@ module.exports = function ( customs, signinUtils, push - ) - const oauth = require('./oauth')(log, config, oauthdb) - const devicesSessions = require('./devices-and-sessions')(log, db, config, customs, push, pushbox, devicesImpl, oauthdb) - const emails = require('./emails')(log, db, mailer, config, customs, push) + ); + const oauth = require('./oauth')(log, config, oauthdb); + const devicesSessions = require('./devices-and-sessions')(log, db, config, customs, push, pushbox, devicesImpl, oauthdb); + const emails = require('./emails')(log, db, mailer, config, customs, push); const password = require('./password')( log, db, @@ -50,24 +50,24 @@ module.exports = function ( signinUtils, push, config - ) - const tokenCodes = require('./token-codes')(log, db, config, customs) - const session = require('./session')(log, db, Password, config, signinUtils) - const sign = require('./sign')(log, signer, db, config.domain, devicesImpl) - const signinCodes = require('./signin-codes')(log, db, customs) - const smsRoute = require('./sms')(log, db, config, customs, smsImpl) - const unblockCodes = require('./unblock-codes')(log, db, mailer, config.signinUnblock, customs) - const totp = require('./totp')(log, db, mailer, customs, config.totp) - const recoveryCodes = require('./recovery-codes')(log, db, config.totp, customs, mailer) - const recoveryKey = require('./recovery-key')(log, db, Password, config.verifierVersion, customs, mailer) + ); + const tokenCodes = require('./token-codes')(log, db, config, customs); + const session = require('./session')(log, db, Password, config, signinUtils); + const sign = require('./sign')(log, signer, db, config.domain, devicesImpl); + const signinCodes = require('./signin-codes')(log, db, customs); + const smsRoute = require('./sms')(log, db, config, customs, smsImpl); + const unblockCodes = require('./unblock-codes')(log, db, mailer, config.signinUnblock, customs); + const totp = require('./totp')(log, db, mailer, customs, config.totp); + const recoveryCodes = require('./recovery-codes')(log, db, config.totp, customs, mailer); + const recoveryKey = require('./recovery-key')(log, db, Password, config.verifierVersion, customs, mailer); const util = require('./util')( log, config, config.smtp.redirectDomain - ) + ); - let basePath = url.parse(config.publicUrl).path - if (basePath === '/') { basePath = '' } + let basePath = url.parse(config.publicUrl).path; + if (basePath === '/') { basePath = ''; } const v1Routes = [].concat( account, @@ -85,24 +85,24 @@ module.exports = function ( unblockCodes, util, recoveryKey - ) - v1Routes.forEach(r => { r.path = basePath + '/v1' + r.path }) - defaults.forEach(r => { r.path = basePath + r.path }) - const allRoutes = defaults.concat(idp, v1Routes) + ); + v1Routes.forEach(r => { r.path = basePath + '/v1' + r.path; }); + defaults.forEach(r => { r.path = basePath + r.path; }); + const allRoutes = defaults.concat(idp, v1Routes); allRoutes.forEach(r => { // Default auth.payload to 'optional' for all authenticated routes. // We'll validate the payload hash if the client provides it, // but allow them to skip it if they can't or don't want to. - const auth = r.options && r.options.auth + const auth = r.options && r.options.auth; if (auth && ! auth.hasOwnProperty('payload')) { - auth.payload = 'optional' + auth.payload = 'optional'; } // Remove custom `apidoc` key which we use for generating docs, // but which Hapi doesn't like if it's there at runtime. - delete r.apidoc - }) + delete r.apidoc; + }); - return allRoutes -} + return allRoutes; +}; diff --git a/lib/routes/oauth.js b/lib/routes/oauth.js index 35e93dc3..c26216a0 100644 --- a/lib/routes/oauth.js +++ b/lib/routes/oauth.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/. */ -'use strict' +'use strict'; /* Routes for managing OAuth authorization grants. * @@ -15,7 +15,7 @@ * */ -const Joi = require('joi') +const Joi = require('joi'); module.exports = (log, config, oauthdb) => { const routes = [ @@ -31,7 +31,7 @@ module.exports = (log, config, oauthdb) => { } }, handler: async function (request) { - return oauthdb.getClientInfo(request.params.client_id) + return oauthdb.getClientInfo(request.params.client_id); } }, { @@ -51,10 +51,10 @@ module.exports = (log, config, oauthdb) => { } }, handler: async function (request) { - const sessionToken = request.auth.credentials - return oauthdb.getScopedKeyData(sessionToken, request.payload) + const sessionToken = request.auth.credentials; + return oauthdb.getScopedKeyData(sessionToken, request.payload); } }, - ] - return routes -} + ]; + return routes; +}; diff --git a/lib/routes/password.js b/lib/routes/password.js index 3ce4afef..54a79c8a 100644 --- a/lib/routes/password.js +++ b/lib/routes/password.js @@ -2,19 +2,19 @@ * 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/. */ -'use strict' +'use strict'; -const validators = require('./validators') -const HEX_STRING = validators.HEX_STRING +const validators = require('./validators'); +const HEX_STRING = validators.HEX_STRING; -const butil = require('../crypto/butil') -const error = require('../error') -const isA = require('joi') -const P = require('../promise') -const random = require('../crypto/random') -const requestHelper = require('../routes/utils/request_helper') +const butil = require('../crypto/butil'); +const error = require('../error'); +const isA = require('joi'); +const P = require('../promise'); +const random = require('../crypto/random'); +const requestHelper = require('../routes/utils/request_helper'); -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; module.exports = function ( log, @@ -29,12 +29,12 @@ module.exports = function ( config ) { - const totpUtils = require('../../lib/routes/utils/totp')(log, config, db) + const totpUtils = require('../../lib/routes/utils/totp')(log, config, db); function failVerifyAttempt(passwordForgotToken) { return (passwordForgotToken.failAttempt()) ? db.deletePasswordForgotToken(passwordForgotToken) : - db.updatePasswordForgotToken(passwordForgotToken) + db.updatePasswordForgotToken(passwordForgotToken); } var routes = [ @@ -50,9 +50,9 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.changeStart', request) - var form = request.payload - var oldAuthPW = form.oldAuthPW + log.begin('Password.changeStart', request); + var form = request.payload; + var oldAuthPW = form.oldAuthPW; return customs.check( request, @@ -61,19 +61,19 @@ module.exports = function ( .then(db.accountRecord.bind(db, form.email)) .then( function (emailRecord) { - const password = new Password(oldAuthPW, emailRecord.authSalt, emailRecord.verifierVersion) + const password = new Password(oldAuthPW, emailRecord.authSalt, emailRecord.verifierVersion); return signinUtils.checkPassword(emailRecord, password, request.app.clientAddress) .then( function (match) { if (! match) { - throw error.incorrectPassword(emailRecord.email, form.email) + throw error.incorrectPassword(emailRecord.email, form.email); } var password = new Password( oldAuthPW, emailRecord.authSalt, emailRecord.verifierVersion - ) - return password.unwrap(emailRecord.wrapWrapKb) + ); + return password.unwrap(emailRecord.wrapWrapKb); } ) .then( @@ -96,22 +96,22 @@ module.exports = function ( return { keyFetchToken: keyFetchToken, passwordChangeToken: passwordChangeToken - } + }; } - ) + ); } - ) + ); } - ) + ); }, function (err) { if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) { customs.flag(request.app.clientAddress, { email: form.email, errno: err.errno - }) + }); } - throw err + throw err; } ) .then( @@ -120,10 +120,10 @@ module.exports = function ( keyFetchToken: tokens.keyFetchToken.data, passwordChangeToken: tokens.passwordChangeToken.data, verified: tokens.keyFetchToken.emailVerified - } + }; } - ) + ); } }, { @@ -145,15 +145,15 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.changeFinish', request) - var passwordChangeToken = request.auth.credentials - var authPW = request.payload.authPW - var wrapKb = request.payload.wrapKb - var sessionTokenId = request.payload.sessionToken - var wantsKeys = requestHelper.wantsKeys(request) - const ip = request.app.clientAddress + log.begin('Password.changeFinish', request); + var passwordChangeToken = request.auth.credentials; + var authPW = request.payload.authPW; + var wrapKb = request.payload.wrapKb; + var sessionTokenId = request.payload.sessionToken; + var wantsKeys = requestHelper.wantsKeys(request); + const ip = request.app.clientAddress; var account, verifyHash, sessionToken, keyFetchToken, verifiedStatus, - devicesToNotify, originatingDeviceId, hasTotp = false + devicesToNotify, originatingDeviceId, hasTotp = false; return checkTotpToken() .then(getSessionVerificationStatus) @@ -162,21 +162,21 @@ module.exports = function ( .then(notifyAccount) .then(createSessionToken) .then(createKeyFetchToken) - .then(createResponse) + .then(createResponse); function checkTotpToken() { return totpUtils.hasTotpToken(passwordChangeToken) .then((result) => { - hasTotp = result + hasTotp = result; // Currently, users that have a TOTP token must specify a sessionTokenId to complete the // password change process. While the `sessionTokenId` is optional, we require it // in the case of TOTP because we want to check that session has been verified // by TOTP. if (result && ! sessionTokenId) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } - }) + }); } function getSessionVerificationStatus() { @@ -184,20 +184,20 @@ module.exports = function ( return db.sessionToken(sessionTokenId) .then( function (tokenData) { - verifiedStatus = tokenData.tokenVerified + verifiedStatus = tokenData.tokenVerified; if (tokenData.deviceId) { - originatingDeviceId = tokenData.deviceId + originatingDeviceId = tokenData.deviceId; } if (hasTotp && tokenData.authenticatorAssuranceLevel <= 1) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } } - ) + ); } else { // Don't create a verified session unless they already had one. - verifiedStatus = false - return P.resolve() + verifiedStatus = false; + return P.resolve(); } } @@ -205,33 +205,33 @@ module.exports = function ( // We fetch the devices to notify before changePassword() because // db.resetAccount() deletes all the devices saved in the account. return request.app.devices.then(devices => { - devicesToNotify = devices + devicesToNotify = devices; // If the originating sessionToken belongs to a device, // do not send the notification to that device. It will // get informed about the change via WebChannel message. if (originatingDeviceId) { - devicesToNotify = devicesToNotify.filter(d => (d.id !== originatingDeviceId)) + devicesToNotify = devicesToNotify.filter(d => (d.id !== originatingDeviceId)); } - }) + }); } function changePassword() { - let authSalt, password + let authSalt, password; return random.hex(32) .then(hex => { - authSalt = hex - password = new Password(authPW, authSalt, verifierVersion) - return db.deletePasswordChangeToken(passwordChangeToken) + authSalt = hex; + password = new Password(authPW, authSalt, verifierVersion); + return db.deletePasswordChangeToken(passwordChangeToken); }) .then( function () { - return password.verifyHash() + return password.verifyHash(); } ) .then( function (hash) { - verifyHash = hash - return password.wrap(wrapKb) + verifyHash = hash; + return password.wrap(wrapKb); } ) .then( @@ -245,7 +245,7 @@ module.exports = function ( wrapWrapKb: wrapWrapKb, verifierVersion: password.version } - ) + ); } ) .then( @@ -255,42 +255,42 @@ module.exports = function ( }) .then( function () { - return result + return result; } - ) + ); } - ) + ); } function notifyAccount() { if (devicesToNotify) { // Notify the devices that the account has changed. - push.notifyPasswordChanged(passwordChangeToken.uid, devicesToNotify) + push.notifyPasswordChanged(passwordChangeToken.uid, devicesToNotify); } return db.account(passwordChangeToken.uid) .then( function (accountData) { - account = accountData + account = accountData; log.notifyAttachedServices('passwordChange', request, { uid: passwordChangeToken.uid, iss: config.domain, generation: account.verifierSetAt - }) - return db.accountEmails(passwordChangeToken.uid) + }); + return db.accountEmails(passwordChangeToken.uid); } ) .then( function (emails) { - const geoData = request.app.geo + const geoData = request.app.geo; const { browser: uaBrowser, browserVersion: uaBrowserVersion, os: uaOS, osVersion: uaOSVersion, deviceType: uaDeviceType - } = request.app.ua + } = request.app.ua; return mailer.sendPasswordChangedNotification(emails, account, { acceptLanguage: request.app.acceptLanguage, @@ -309,17 +309,17 @@ module.exports = function ( // and pretend everything worked. log.trace('Password.changeFinish.sendPasswordChangedNotification.error', { error: e - }) - }) + }); + }); } - ) + ); } function createSessionToken() { return P.resolve() .then(() => { if (! verifiedStatus) { - return random.hex(16) + return random.hex(16); } }) .then(maybeToken => { @@ -330,7 +330,7 @@ module.exports = function ( osVersion: uaOSVersion, deviceType: uaDeviceType, formFactor: uaFormFactor - } = request.app.ua + } = request.app.ua; // Create a sessionToken with the verification status of the current session const sessionTokenOptions = { @@ -347,15 +347,15 @@ module.exports = function ( uaOSVersion, uaDeviceType, uaFormFactor - } + }; - return db.createSessionToken(sessionTokenOptions) + return db.createSessionToken(sessionTokenOptions); }) .then( function (result) { - sessionToken = result + sessionToken = result; } - ) + ); } function createKeyFetchToken() { @@ -370,9 +370,9 @@ module.exports = function ( }) .then( function (result) { - keyFetchToken = result + keyFetchToken = result; } - ) + ); } } @@ -380,7 +380,7 @@ module.exports = function ( // If no sessionToken, this could be a legacy client // attempting to change password, return legacy response. if (! sessionTokenId) { - return {} + return {}; } var response = { @@ -388,13 +388,13 @@ module.exports = function ( sessionToken: sessionToken.data, verified: sessionToken.emailVerified && sessionToken.tokenVerified, authAt: sessionToken.lastAuthAt() - } + }; if (wantsKeys) { - response.keyFetchToken = keyFetchToken.data + response.keyFetchToken = keyFetchToken.data; } - return response + return response; } } }, @@ -425,24 +425,24 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.forgotSend', request) - var email = request.payload.email - var service = request.payload.service || request.query.service - const ip = request.app.clientAddress + log.begin('Password.forgotSend', request); + var email = request.payload.email; + var service = request.payload.service || request.query.service; + const ip = request.app.clientAddress; - request.validateMetricsContext() + request.validateMetricsContext(); - let flowCompleteSignal + let flowCompleteSignal; if (requestHelper.wantsKeys(request)) { - flowCompleteSignal = 'account.signed' + flowCompleteSignal = 'account.signed'; } else { - flowCompleteSignal = 'account.reset' + flowCompleteSignal = 'account.reset'; } - request.setMetricsFlowCompleteSignal(flowCompleteSignal) + request.setMetricsFlowCompleteSignal(flowCompleteSignal); - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; - let passwordForgotToken + let passwordForgotToken; return P.all([ request.emitMetricsEvent('password.forgot.send_code.start'), @@ -451,29 +451,29 @@ module.exports = function ( .then(db.accountRecord.bind(db, email)) .then(accountRecord => { if (accountRecord.primaryEmail.normalizedEmail !== email.toLowerCase()) { - throw error.cannotResetPasswordWithSecondaryEmail() + throw error.cannotResetPasswordWithSecondaryEmail(); } // The token constructor sets createdAt from its argument. // Clobber the timestamp to prevent prematurely expired tokens. - accountRecord.createdAt = undefined - return db.createPasswordForgotToken(accountRecord) + accountRecord.createdAt = undefined; + return db.createPasswordForgotToken(accountRecord); }) .then(result => { - passwordForgotToken = result + passwordForgotToken = result; return P.all([ request.stashMetricsContext(passwordForgotToken), db.accountEmails(passwordForgotToken.uid) - ]) + ]); }) .then(([_, emails]) => { - const geoData = request.app.geo + const geoData = request.app.geo; const { browser: uaBrowser, browserVersion: uaBrowserVersion, os: uaOS, osVersion: uaOSVersion, deviceType: uaDeviceType - } = request.app.ua + } = request.app.ua; return mailer.sendRecoveryCode(emails, passwordForgotToken, { token: passwordForgotToken, @@ -494,7 +494,7 @@ module.exports = function ( uaOSVersion, uaDeviceType, uid: passwordForgotToken.uid - }) + }); }) .then(() => request.emitMetricsEvent('password.forgot.send_code.completed')) .then(() => ({ @@ -502,7 +502,7 @@ module.exports = function ( ttl: passwordForgotToken.ttl(), codeLength: passwordForgotToken.passCode.length, tries: passwordForgotToken.tries - })) + })); } }, { @@ -533,12 +533,12 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.forgotResend', request) - var passwordForgotToken = request.auth.credentials - var service = request.payload.service || request.query.service - const ip = request.app.clientAddress + log.begin('Password.forgotResend', request); + var passwordForgotToken = request.auth.credentials; + var service = request.payload.service || request.query.service; + const ip = request.app.clientAddress; - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; return P.all([ request.emitMetricsEvent('password.forgot.resend_code.start'), @@ -548,14 +548,14 @@ module.exports = function ( function () { return db.accountEmails(passwordForgotToken.uid) .then(emails => { - const geoData = request.app.geo + const geoData = request.app.geo; const { browser: uaBrowser, browserVersion: uaBrowserVersion, os: uaOS, osVersion: uaOSVersion, deviceType: uaDeviceType - } = request.app.ua + } = request.app.ua; return mailer.sendRecoveryCode(emails, passwordForgotToken, { code: passwordForgotToken.passCode, @@ -576,13 +576,13 @@ module.exports = function ( uaOSVersion, uaDeviceType, uid: passwordForgotToken.uid - }) - }) + }); + }); } ) .then( function(){ - return request.emitMetricsEvent('password.forgot.resend_code.completed') + return request.emitMetricsEvent('password.forgot.resend_code.completed'); } ) .then( @@ -592,9 +592,9 @@ module.exports = function ( ttl: passwordForgotToken.ttl(), codeLength: passwordForgotToken.passCode.length, tries: passwordForgotToken.tries - } + }; } - ) + ); } }, { @@ -617,14 +617,14 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.forgotVerify', request) - var passwordForgotToken = request.auth.credentials - var code = request.payload.code - const accountResetWithRecoveryKey = request.payload.accountResetWithRecoveryKey + log.begin('Password.forgotVerify', request); + var passwordForgotToken = request.auth.credentials; + var code = request.payload.code; + const accountResetWithRecoveryKey = request.payload.accountResetWithRecoveryKey; - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; - let accountResetToken + let accountResetToken; return P.all([ request.emitMetricsEvent('password.forgot.verify_code.start'), @@ -632,7 +632,7 @@ module.exports = function ( ]) .then(() => { if (butil.buffersAreEqual(passwordForgotToken.passCode, code) && passwordForgotToken.ttl() > 0) { - return db.forgotPasswordVerified(passwordForgotToken) + return db.forgotPasswordVerified(passwordForgotToken); } return failVerifyAttempt(passwordForgotToken) @@ -640,22 +640,22 @@ module.exports = function ( throw error.invalidVerificationCode({ tries: passwordForgotToken.tries, ttl: passwordForgotToken.ttl() - }) - }) + }); + }); }) .then(result => { - accountResetToken = result + accountResetToken = result; return P.all([ request.propagateMetricsContext(passwordForgotToken, accountResetToken), db.accountEmails(passwordForgotToken.uid) - ]) + ]); }) .then(([_, emails]) => { if (accountResetWithRecoveryKey) { // To prevent multiple password change emails being sent to a user, // we check for a flag to see if this is a reset using an account recovery key. // If it is, then the notification email will be sent in `/account/reset` - return P.resolve() + return P.resolve(); } return mailer.sendPasswordResetNotification( @@ -669,12 +669,12 @@ module.exports = function ( flowBeginTime, uid: passwordForgotToken.uid } - ) + ); }) .then(() => request.emitMetricsEvent('password.forgot.verify_code.completed')) .then(() => ({ accountResetToken: accountResetToken.data - })) + })); } }, { @@ -692,16 +692,16 @@ module.exports = function ( } }, handler: async function (request) { - log.begin('Password.forgotStatus', request) - var passwordForgotToken = request.auth.credentials + log.begin('Password.forgotStatus', request); + var passwordForgotToken = request.auth.credentials; return { tries: passwordForgotToken.tries, ttl: passwordForgotToken.ttl() - } + }; } } - ] + ]; - return routes -} + return routes; +}; diff --git a/lib/routes/recovery-codes.js b/lib/routes/recovery-codes.js index e709b79e..6f065a9e 100644 --- a/lib/routes/recovery-codes.js +++ b/lib/routes/recovery-codes.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const errors = require('../error') -const isA = require('joi') -const BASE_36 = require('./validators').BASE_36 -const RECOVERY_CODE_SANE_MAX_LENGTH = 20 +const errors = require('../error'); +const isA = require('joi'); +const BASE_36 = require('./validators').BASE_36; +const RECOVERY_CODE_SANE_MAX_LENGTH = 20; module.exports = (log, db, config, customs, mailer) => { - const codeConfig = config.recoveryCodes - const RECOVERY_CODE_COUNT = codeConfig && codeConfig.count || 8 + const codeConfig = config.recoveryCodes; + const RECOVERY_CODE_COUNT = codeConfig && codeConfig.count || 8; return [ { @@ -28,13 +28,13 @@ module.exports = (log, db, config, customs, mailer) => { } }, handler: async function (request) { - log.begin('replaceRecoveryCodes', request) + log.begin('replaceRecoveryCodes', request); - const uid = request.auth.credentials.uid - const sessionToken = request.auth.credentials - const geoData = request.app.geo - const ip = request.app.clientAddress - let codes + const uid = request.auth.credentials.uid; + const sessionToken = request.auth.credentials; + const geoData = request.app.geo; + const ip = request.app.clientAddress; + let codes; return replaceRecoveryCodes() .then(sendEmailNotification) @@ -42,20 +42,20 @@ module.exports = (log, db, config, customs, mailer) => { .then(() => { return { recoveryCodes: codes - } - }) + }; + }); function replaceRecoveryCodes() { // Since TOTP and recovery codes go hand in hand, you should only be // able to replace recovery codes in a TOTP verified session. if (! sessionToken.authenticatorAssuranceLevel || sessionToken.authenticatorAssuranceLevel <= 1) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } return db.replaceRecoveryCodes(uid, RECOVERY_CODE_COUNT) .then((result) => { - codes = result - }) + codes = result; + }); } function sendEmailNotification() { @@ -72,17 +72,17 @@ module.exports = (log, db, config, customs, mailer) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - }) - }) + }); + }); } function emitMetrics() { log.info('account.recoveryCode.replaced', { uid: uid - }) + }); return request.emitMetricsEvent('recoveryCode.replaced', {uid: uid}) - .then(() => ({})) + .then(() => ({})); } } }, @@ -105,14 +105,14 @@ module.exports = (log, db, config, customs, mailer) => { } }, handler: async function (request) { - log.begin('session.verify.recoveryCode', request) + log.begin('session.verify.recoveryCode', request); - const code = request.payload.code - const uid = request.auth.credentials.uid - const sessionToken = request.auth.credentials - const geoData = request.app.geo - const ip = request.app.clientAddress - let remainingRecoveryCodes + const code = request.payload.code; + const uid = request.auth.credentials.uid; + const sessionToken = request.auth.credentials; + const geoData = request.app.geo; + const ip = request.app.clientAddress; + let remainingRecoveryCodes; return customs.check(request, sessionToken.email, 'verifyRecoveryCode') .then(consumeRecoveryCode) @@ -122,31 +122,31 @@ module.exports = (log, db, config, customs, mailer) => { .then(() => { return { remaining: remainingRecoveryCodes - } - }) + }; + }); function consumeRecoveryCode() { return db.consumeRecoveryCode(uid, code) .then((result) => { - remainingRecoveryCodes = result.remaining + remainingRecoveryCodes = result.remaining; if (remainingRecoveryCodes === 0) { log.info('account.recoveryCode.consumedAllCodes', { uid - }) + }); } - }) + }); } function verifySession() { if (sessionToken.tokenVerificationId) { - return db.verifyTokensWithMethod(sessionToken.id, 'recovery-code') + return db.verifyTokensWithMethod(sessionToken.id, 'recovery-code'); } } function sendEmailNotification() { return db.account(sessionToken.uid) .then((account) => { - const defers = [] + const defers = []; const sendConsumeEmail = mailer.sendPostConsumeRecoveryCodeNotification(account.emails, account, { acceptLanguage: request.app.acceptLanguage, @@ -159,34 +159,34 @@ module.exports = (log, db, config, customs, mailer) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - }) - defers.push(sendConsumeEmail) + }); + defers.push(sendConsumeEmail); if (remainingRecoveryCodes <= codeConfig.notifyLowCount) { log.info('account.recoveryCode.notifyLowCount', { uid, remaining: remainingRecoveryCodes - }) + }); const sendLowCodesEmail = mailer.sendLowRecoveryCodeNotification(account.emails, account, { acceptLanguage: request.app.acceptLanguage, uid: sessionToken.uid - }) - defers.push(sendLowCodesEmail) + }); + defers.push(sendLowCodesEmail); } - return Promise.all(defers) - }) + return Promise.all(defers); + }); } function emitMetrics() { log.info('account.recoveryCode.verified', { uid: uid - }) + }); return request.emitMetricsEvent('recoveryCode.verified', {uid: uid}) - .then(() => ({})) + .then(() => ({})); } } } - ] -} + ]; +}; diff --git a/lib/routes/recovery-key.js b/lib/routes/recovery-key.js index 26a99fd0..21d0196f 100644 --- a/lib/routes/recovery-key.js +++ b/lib/routes/recovery-key.js @@ -2,11 +2,11 @@ * 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/. */ -'use strict' +'use strict'; -const errors = require('../error') -const validators = require('./validators') -const isA = require('joi') +const errors = require('../error'); +const validators = require('./validators'); +const isA = require('joi'); module.exports = (log, db, Password, verifierVersion, customs, mailer) => { return [ @@ -25,40 +25,40 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { } }, handler: async function (request) { - log.begin('createRecoveryKey', request) + log.begin('createRecoveryKey', request); - const uid = request.auth.credentials.uid - const sessionToken = request.auth.credentials - const {recoveryKeyId, recoveryData} = request.payload + const uid = request.auth.credentials.uid; + const sessionToken = request.auth.credentials; + const {recoveryKeyId, recoveryData} = request.payload; return createRecoveryKey() .then(emitMetrics) .then(sendNotificationEmails) .then(() => { - return {} - }) + return {}; + }); function createRecoveryKey() { if (sessionToken.tokenVerificationId) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } - return db.createRecoveryKey(uid, recoveryKeyId, recoveryData) + return db.createRecoveryKey(uid, recoveryKeyId, recoveryData); } function emitMetrics() { log.info('account.recoveryKey.created', { uid - }) + }); - return request.emitMetricsEvent('recoveryKey.created', {uid}) + return request.emitMetricsEvent('recoveryKey.created', {uid}); } function sendNotificationEmails() { return db.account(uid) .then((account) => { - const geoData = request.app.geo - const ip = request.app.clientAddress + const geoData = request.app.geo; + const ip = request.app.clientAddress; const emailOptions = { acceptLanguage: request.app.acceptLanguage, ip: ip, @@ -70,10 +70,10 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - } + }; - return mailer.sendPostAddAccountRecoveryNotification(account.emails, account, emailOptions) - }) + return mailer.sendPostAddAccountRecoveryNotification(account.emails, account, emailOptions); + }); } } }, @@ -91,21 +91,21 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { } }, handler: async function (request) { - log.begin('getRecoveryKey', request) + log.begin('getRecoveryKey', request); - const uid = request.auth.credentials.uid - const recoveryKeyId = request.params.recoveryKeyId - let recoveryData + const uid = request.auth.credentials.uid; + const recoveryKeyId = request.params.recoveryKeyId; + let recoveryData; return customs.checkAuthenticated(request, uid, 'getRecoveryKey') .then(getRecoveryKey) .then(() => { - return {recoveryData} - }) + return {recoveryData}; + }); function getRecoveryKey() { return db.getRecoveryKey(uid, recoveryKeyId) - .then((res) => recoveryData = res.recoveryData) + .then((res) => recoveryData = res.recoveryData); } } }, @@ -129,13 +129,13 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { } }, handler(request) { - log.begin('recoveryKeyExists', request) + log.begin('recoveryKeyExists', request); - const email = request.payload.email - let uid + const email = request.payload.email; + let uid; if (request.auth.credentials) { - uid = request.auth.credentials.uid + uid = request.auth.credentials.uid; } return Promise.resolve() @@ -146,18 +146,18 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { // password reset page and allows us to redirect the user to either // the regular password reset or account recovery password reset. if (! email) { - throw errors.missingRequestParameter('email') + throw errors.missingRequestParameter('email'); } return customs.check(request, email, 'recoveryKeyExists') .then(() => db.accountRecord(email)) - .then((result) => uid = result.uid) + .then((result) => uid = result.uid); } // When checking from `/settings` a sessionToken is required and the // request is not rate limited. }) - .then(() => db.recoveryKeyExists(uid)) + .then(() => db.recoveryKeyExists(uid)); } }, { @@ -169,28 +169,28 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { } }, handler(request) { - log.begin('recoveryKeyDelete', request) + log.begin('recoveryKeyDelete', request); - const sessionToken = request.auth.credentials + const sessionToken = request.auth.credentials; return Promise.resolve() .then(deleteRecoveryKey) .then(sendNotificationEmail) .then(() => { - return {} - }) + return {}; + }); function deleteRecoveryKey() { if (sessionToken.tokenVerificationId) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } - return db.deleteRecoveryKey(sessionToken.uid) + return db.deleteRecoveryKey(sessionToken.uid); } function sendNotificationEmail() { - const geoData = request.app.geo - const ip = request.app.clientAddress + const geoData = request.app.geo; + const ip = request.app.clientAddress; const emailOptions = { acceptLanguage: request.app.acceptLanguage, ip: ip, @@ -202,12 +202,12 @@ module.exports = (log, db, Password, verifierVersion, customs, mailer) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - } + }; return db.account(sessionToken.uid) - .then((account) => mailer.sendPostRemoveAccountRecoveryNotification(account.emails, account, emailOptions)) + .then((account) => mailer.sendPostRemoveAccountRecoveryNotification(account.emails, account, emailOptions)); } } } - ] -} + ]; +}; diff --git a/lib/routes/session.js b/lib/routes/session.js index 0bf635e8..826a5e02 100644 --- a/lib/routes/session.js +++ b/lib/routes/session.js @@ -2,20 +2,20 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../error') -const isA = require('joi') -const requestHelper = require('../routes/utils/request_helper') -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema -const P = require('../promise') -const random = require('../crypto/random') +const error = require('../error'); +const isA = require('joi'); +const requestHelper = require('../routes/utils/request_helper'); +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; +const P = require('../promise'); +const random = require('../crypto/random'); -const validators = require('./validators') -const HEX_STRING = validators.HEX_STRING +const validators = require('./validators'); +const HEX_STRING = validators.HEX_STRING; module.exports = function (log, db, Password, config, signinUtils) { - const totpUtils = require('../../lib/routes/utils/totp')(log, config, db) + const totpUtils = require('../../lib/routes/utils/totp')(log, config, db); const routes = [ { @@ -32,14 +32,14 @@ module.exports = function (log, db, Password, config, signinUtils) { } }, handler: async function (request) { - log.begin('Session.destroy', request) - var sessionToken = request.auth.credentials - var uid = request.auth.credentials.uid + log.begin('Session.destroy', request); + var sessionToken = request.auth.credentials; + var uid = request.auth.credentials.uid; return P.resolve() .then(() => { if (request.payload && request.payload.customSessionToken) { - const customSessionToken = request.payload.customSessionToken + const customSessionToken = request.payload.customSessionToken; return db.sessionToken(customSessionToken) .then(function (tokenData) { @@ -48,21 +48,21 @@ module.exports = function (log, db, Password, config, signinUtils) { sessionToken = { id: customSessionToken, uid: uid, - } + }; - return sessionToken + return sessionToken; } else { - throw error.invalidToken('Invalid session token') + throw error.invalidToken('Invalid session token'); } - }) + }); } else { - return sessionToken + return sessionToken; } }) .then((sessionToken) => { - return db.deleteSessionToken(sessionToken) + return db.deleteSessionToken(sessionToken); }) - .then(() => { return {} }) + .then(() => { return {}; }); } }, { @@ -113,17 +113,17 @@ module.exports = function (log, db, Password, config, signinUtils) { } }, handler: async function (request) { - log.begin('Session.reauth', request) + log.begin('Session.reauth', request); - const sessionToken = request.auth.credentials - const email = request.payload.email - const authPW = request.payload.authPW - const originalLoginEmail = request.payload.originalLoginEmail - let verificationMethod = request.payload.verificationMethod + const sessionToken = request.auth.credentials; + const email = request.payload.email; + const authPW = request.payload.authPW; + const originalLoginEmail = request.payload.originalLoginEmail; + let verificationMethod = request.payload.verificationMethod; - let accountRecord, password, keyFetchToken + let accountRecord, password, keyFetchToken; - request.validateMetricsContext() + request.validateMetricsContext(); return checkCustomsAndLoadAccount() .then(checkEmailAndPassword) @@ -131,7 +131,7 @@ module.exports = function (log, db, Password, config, signinUtils) { .then(updateSessionToken) .then(sendSigninNotifications) .then(createKeyFetchToken) - .then(createResponse) + .then(createResponse); function checkTotpToken() { // Check to see if the user has a TOTP token and it is verified and @@ -141,18 +141,18 @@ module.exports = function (log, db, Password, config, signinUtils) { .then((result) => { if (result) { // User has enabled TOTP, no way around it, they must verify TOTP token - verificationMethod = 'totp-2fa' + verificationMethod = 'totp-2fa'; } else if (! result && verificationMethod === 'totp-2fa') { // Error if requesting TOTP verification with TOTP not setup - throw error.totpRequired() + throw error.totpRequired(); } - }) + }); } function checkCustomsAndLoadAccount() { return signinUtils.checkCustomsAndLoadAccount(request, email).then(res => { - accountRecord = res.accountRecord - }) + accountRecord = res.accountRecord; + }); } function checkEmailAndPassword() { @@ -162,18 +162,18 @@ module.exports = function (log, db, Password, config, signinUtils) { authPW, accountRecord.authSalt, accountRecord.verifierVersion - ) - return signinUtils.checkPassword(accountRecord, password, request.app.clientAddress) + ); + return signinUtils.checkPassword(accountRecord, password, request.app.clientAddress); }) .then(match => { if (! match) { - throw error.incorrectPassword(accountRecord.email, email) + throw error.incorrectPassword(accountRecord.email, email); } - }) + }); } function updateSessionToken() { - sessionToken.authAt = sessionToken.lastAccessTime = Date.now() + sessionToken.authAt = sessionToken.lastAccessTime = Date.now(); sessionToken.setUserAgentInfo({ uaBrowser: request.app.ua.browser, uaBrowserVersion: request.app.ua.browserVersion, @@ -181,23 +181,23 @@ module.exports = function (log, db, Password, config, signinUtils) { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uaFormFactor: request.app.ua.formFactor - }) + }); if (! sessionToken.mustVerify && (requestHelper.wantsKeys(request) || verificationMethod)) { - sessionToken.mustVerify = true + sessionToken.mustVerify = true; } - return db.updateSessionToken(sessionToken) + return db.updateSessionToken(sessionToken); } function sendSigninNotifications() { - return signinUtils.sendSigninNotifications(request, accountRecord, sessionToken, verificationMethod) + return signinUtils.sendSigninNotifications(request, accountRecord, sessionToken, verificationMethod); } function createKeyFetchToken() { if (requestHelper.wantsKeys(request)) { return signinUtils.createKeyFetchToken(request, accountRecord, password, sessionToken) .then(result => { - keyFetchToken = result - }) + keyFetchToken = result; + }); } } @@ -205,15 +205,15 @@ module.exports = function (log, db, Password, config, signinUtils) { var response = { uid: sessionToken.uid, authAt: sessionToken.lastAuthAt() - } + }; if (keyFetchToken) { - response.keyFetchToken = keyFetchToken.data + response.keyFetchToken = keyFetchToken.data; } - Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)) + Object.assign(response, signinUtils.getSessionVerificationStatus(sessionToken, verificationMethod)); - return response + return response; } } }, @@ -232,12 +232,12 @@ module.exports = function (log, db, Password, config, signinUtils) { } }, handler: async function (request) { - log.begin('Session.status', request) - const sessionToken = request.auth.credentials + log.begin('Session.status', request); + const sessionToken = request.auth.credentials; return { state: sessionToken.state, uid: sessionToken.uid - } + }; } }, { @@ -254,30 +254,30 @@ module.exports = function (log, db, Password, config, signinUtils) { } }, handler: async function (request) { - log.begin('Session.duplicate', request) - const origSessionToken = request.auth.credentials + log.begin('Session.duplicate', request); + const origSessionToken = request.auth.credentials; return P.resolve() .then(duplicateVerificationState) .then(createSessionToken) - .then(formatResponse) + .then(formatResponse); function duplicateVerificationState() { // Copy verification state of the token, but generate // independent verification codes. - const newVerificationState = {} + const newVerificationState = {}; if (origSessionToken.tokenVerificationId) { - newVerificationState.tokenVerificationId = random.hex(origSessionToken.tokenVerificationId.length / 2) + newVerificationState.tokenVerificationId = random.hex(origSessionToken.tokenVerificationId.length / 2); } if (origSessionToken.tokenVerificationCode) { // Using expiresAt=0 here prevents the new token from being verified via email code. // That's OK, because we don't send them a new email with the new verification code // unless they explicitly ask us to resend it, and resend only handles email links // rather than email codes. - newVerificationState.tokenVerificationCode = random.hex(origSessionToken.tokenVerificationCode.length / 2) - newVerificationState.tokenVerificationCodeExpiresAt = 0 + newVerificationState.tokenVerificationCode = random.hex(origSessionToken.tokenVerificationCode.length / 2); + newVerificationState.tokenVerificationCodeExpiresAt = 0; } - return P.props(newVerificationState) + return P.props(newVerificationState); } function createSessionToken(newVerificationState) { @@ -289,15 +289,15 @@ module.exports = function (log, db, Password, config, signinUtils) { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uaFormFactor: request.app.ua.formFactor - } + }; // Copy all other details from the original sessionToken. // We have to lie a little here and copy the creation time // of the original sessionToken. If we set createdAt to the // current time, we would falsely report the new session's // `lastAuthAt` value as the current timestamp. - const sessionTokenOptions = Object.assign({}, origSessionToken, newUAInfo, newVerificationState) - return db.createSessionToken(sessionTokenOptions) + const sessionTokenOptions = Object.assign({}, origSessionToken, newUAInfo, newVerificationState); + return db.createSessionToken(sessionTokenOptions); } function formatResponse(newSessionToken) { @@ -305,24 +305,24 @@ module.exports = function (log, db, Password, config, signinUtils) { uid: newSessionToken.uid, sessionToken: newSessionToken.data, authAt: newSessionToken.lastAuthAt() - } + }; if (! newSessionToken.emailVerified) { - response.verified = false - response.verificationMethod = 'email' - response.verificationReason = 'signup' + response.verified = false; + response.verificationMethod = 'email'; + response.verificationReason = 'signup'; } else if (! newSessionToken.tokenVerified) { - response.verified = false - response.verificationMethod = 'email' - response.verificationReason = 'login' + response.verified = false; + response.verificationMethod = 'email'; + response.verificationReason = 'login'; } else { - response.verified = true + response.verified = true; } - return response + return response; } } - } ] + } ]; - return routes -} + return routes; +}; diff --git a/lib/routes/sign.js b/lib/routes/sign.js index 41a14d21..823b6a3f 100644 --- a/lib/routes/sign.js +++ b/lib/routes/sign.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../error') -const isA = require('joi') -const P = require('../promise') -const validators = require('./validators') +const error = require('../error'); +const isA = require('joi'); +const P = require('../promise'); +const validators = require('./validators'); module.exports = (log, signer, db, domain, devices) => { - const HOUR = 1000 * 60 * 60 + const HOUR = 1000 * 60 * 60; var routes = [ { @@ -42,12 +42,12 @@ module.exports = (log, signer, db, domain, devices) => { } }, handler: async function certificateSign(request) { - log.begin('Sign.cert', request) - var sessionToken = request.auth.credentials - var publicKey = request.payload.publicKey - var duration = request.payload.duration - var service = request.query.service - var deviceId, uid, certResult + log.begin('Sign.cert', request); + var sessionToken = request.auth.credentials; + var publicKey = request.payload.publicKey; + var duration = request.payload.duration; + var service = request.query.service; + var deviceId, uid, certResult; if (request.headers['user-agent']) { const { browser: uaBrowser, @@ -56,7 +56,7 @@ module.exports = (log, signer, db, domain, devices) => { osVersion: uaOSVersion, deviceType: uaDeviceType, formFactor: uaFormFactor - } = request.app.ua + } = request.app.ua; sessionToken.setUserAgentInfo({ uaBrowser, uaBrowserVersion, @@ -65,26 +65,26 @@ module.exports = (log, signer, db, domain, devices) => { uaDeviceType, uaFormFactor, lastAccessTime: Date.now() - }) + }); // No need to wait for a response, update in the background. - db.touchSessionToken(sessionToken, request.app.geo) + db.touchSessionToken(sessionToken, request.app.geo); } else { log.warn('signer.updateSessionToken', { message: 'no user agent string, session token not updated' - }) + }); } if (! sessionToken.emailVerified) { - throw error.unverifiedAccount() + throw error.unverifiedAccount(); } if (sessionToken.mustVerify && ! sessionToken.tokenVerified) { - throw error.unverifiedSession() + throw error.unverifiedSession(); } return P.resolve() .then( function () { if (sessionToken.deviceId) { - deviceId = sessionToken.deviceId + deviceId = sessionToken.deviceId; } else if (! service || service === 'sync') { // Synthesize a device record for Sync sessions that don't already have one. // Include the UA info so that we can synthesize a device name @@ -94,19 +94,19 @@ module.exports = (log, signer, db, domain, devices) => { uaBrowserVersion: sessionToken.uaBrowserVersion, uaOS: sessionToken.uaOS, uaOSVersion: sessionToken.uaOSVersion - } + }; return devices.upsert(request, sessionToken, deviceInfo) .then(result => { - deviceId = result.id + deviceId = result.id; }) .catch(err => { // There's a small chance that a device registration was performed // concurrently. If so, just use that device id. if (err.errno !== error.ERRNO.DEVICE_CONFLICT) { - throw err + throw err; } - deviceId = err.output.payload.deviceId - }) + deviceId = err.output.payload.deviceId; + }); } } ) @@ -114,24 +114,24 @@ module.exports = (log, signer, db, domain, devices) => { function () { if (publicKey.algorithm === 'RS') { if (! publicKey.n) { - throw error.missingRequestParameter('n') + throw error.missingRequestParameter('n'); } if (! publicKey.e) { - throw error.missingRequestParameter('e') + throw error.missingRequestParameter('e'); } } else { // DS if (! publicKey.y) { - throw error.missingRequestParameter('y') + throw error.missingRequestParameter('y'); } if (! publicKey.p) { - throw error.missingRequestParameter('p') + throw error.missingRequestParameter('p'); } if (! publicKey.q) { - throw error.missingRequestParameter('q') + throw error.missingRequestParameter('q'); } if (! publicKey.g) { - throw error.missingRequestParameter('g') + throw error.missingRequestParameter('g'); } } @@ -140,8 +140,8 @@ module.exports = (log, signer, db, domain, devices) => { // Log details to sanity-check locale backfilling. log.info('signer.updateLocale', { locale: request.app.acceptLanguage - }) - db.updateLocale(sessionToken.uid, request.app.acceptLanguage) + }); + db.updateLocale(sessionToken.uid, request.app.acceptLanguage); // meh on the result } else { // We're seeing a surprising number of accounts that don't get @@ -150,10 +150,10 @@ module.exports = (log, signer, db, domain, devices) => { email: sessionToken.email, locale: request.app.acceptLanguage, agent: request.headers['user-agent'] - }) + }); } } - uid = sessionToken.uid + uid = sessionToken.uid; return signer.sign( { @@ -170,22 +170,22 @@ module.exports = (log, signer, db, domain, devices) => { authenticatorAssuranceLevel: sessionToken.authenticatorAssuranceLevel, profileChangedAt: sessionToken.profileChangedAt } - ) + ); } ) .then( function(result) { - certResult = result + certResult = result; return request.emitMetricsEvent('account.signed', { uid: uid, device_id: deviceId - }) + }); } ) - .then(() => { return certResult }) + .then(() => { return certResult; }); } } - ] + ]; - return routes -} + return routes; +}; diff --git a/lib/routes/signin-codes.js b/lib/routes/signin-codes.js index d045ce61..fb76a513 100644 --- a/lib/routes/signin-codes.js +++ b/lib/routes/signin-codes.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const isA = require('joi') -const validators = require('./validators') +const isA = require('joi'); +const validators = require('./validators'); -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').requiredSchema +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').requiredSchema; module.exports = (log, db, customs) => { return [ @@ -28,22 +28,22 @@ module.exports = (log, db, customs) => { } }, handler: async function (request) { - log.begin('signinCodes.consume', request) - request.validateMetricsContext() + log.begin('signinCodes.consume', request); + request.validateMetricsContext(); return customs.checkIpOnly(request, 'consumeSigninCode') .then(hexSigninCode) - .then(consumeSigninCode) + .then(consumeSigninCode); function hexSigninCode () { - let base64 = request.payload.code.replace(/-/g, '+').replace(/_/g, '/') + let base64 = request.payload.code.replace(/-/g, '+').replace(/_/g, '/'); - const padCount = base64.length % 4 + const padCount = base64.length % 4; for (let i = 0; i < padCount; ++i) { - base64 += '=' + base64 += '='; } - return Buffer.from(base64, 'base64').toString('hex') + return Buffer.from(base64, 'base64').toString('hex'); } function consumeSigninCode (code) { @@ -52,14 +52,14 @@ module.exports = (log, db, customs) => { return request.emitMetricsEvent('signinCode.consumed') .then(() => { if (result.flowId) { - return request.emitMetricsEvent(`flow.continued.${result.flowId}`) + return request.emitMetricsEvent(`flow.continued.${result.flowId}`); } }) - .then(() => ({ email: result.email })) - }) + .then(() => ({ email: result.email })); + }); } } } - ] -} + ]; +}; diff --git a/lib/routes/sms.js b/lib/routes/sms.js index 2657a5eb..9be954e0 100644 --- a/lib/routes/sms.js +++ b/lib/routes/sms.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../error') -const isA = require('joi') -const PhoneNumberUtil = require('google-libphonenumber').PhoneNumberUtil -const validators = require('./validators') +const error = require('../error'); +const isA = require('joi'); +const PhoneNumberUtil = require('google-libphonenumber').PhoneNumberUtil; +const validators = require('./validators'); -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema -const FEATURES_SCHEMA = require('../features').schema +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; +const FEATURES_SCHEMA = require('../features').schema; const TEMPLATE_NAMES = new Map([ [ 1, 'installFirefox' ] -]) +]); module.exports = (log, db, config, customs, sms) => { if (! config.sms.enabled) { - return [] + return []; } - const REGIONS = new Set(config.sms.countryCodes) - const IS_STATUS_GEO_ENABLED = config.sms.isStatusGeoEnabled + const REGIONS = new Set(config.sms.countryCodes); + const IS_STATUS_GEO_ENABLED = config.sms.isStatusGeoEnabled; return [ { @@ -41,15 +41,15 @@ module.exports = (log, db, config, customs, sms) => { } }, handler: async function (request) { - log.begin('sms.send', request) - request.validateMetricsContext() + log.begin('sms.send', request); + request.validateMetricsContext(); - const sessionToken = request.auth.credentials - const phoneNumber = request.payload.phoneNumber - const templateName = TEMPLATE_NAMES.get(request.payload.messageId) - const acceptLanguage = request.app.acceptLanguage + const sessionToken = request.auth.credentials; + const phoneNumber = request.payload.phoneNumber; + const templateName = TEMPLATE_NAMES.get(request.payload.messageId); + const acceptLanguage = request.app.acceptLanguage; - let phoneNumberUtil, parsedPhoneNumber + let phoneNumberUtil, parsedPhoneNumber; return customs.check(request, sessionToken.email, 'connectDeviceSms') .then(parsePhoneNumber) @@ -58,51 +58,51 @@ module.exports = (log, db, config, customs, sms) => { .then(createSigninCode) .then(sendMessage) .then(logSuccess) - .then(createResponse) + .then(createResponse); function parsePhoneNumber () { try { - phoneNumberUtil = PhoneNumberUtil.getInstance() - parsedPhoneNumber = phoneNumberUtil.parse(phoneNumber) + phoneNumberUtil = PhoneNumberUtil.getInstance(); + parsedPhoneNumber = phoneNumberUtil.parse(phoneNumber); } catch (err) { - throw error.invalidPhoneNumber() + throw error.invalidPhoneNumber(); } } function validatePhoneNumber () { if (! phoneNumberUtil.isValidNumber(parsedPhoneNumber)) { - throw error.invalidPhoneNumber() + throw error.invalidPhoneNumber(); } } function validateRegion () { - const region = phoneNumberUtil.getRegionCodeForNumber(parsedPhoneNumber) - request.emitMetricsEvent(`sms.region.${region}`) + const region = phoneNumberUtil.getRegionCodeForNumber(parsedPhoneNumber); + request.emitMetricsEvent(`sms.region.${region}`); if (! REGIONS.has(region)) { - throw error.invalidRegion(region) + throw error.invalidRegion(region); } } function createSigninCode () { if (request.app.features.has('signinCodes')) { return request.gatherMetricsContext({}) - .then(metricsContext => db.createSigninCode(sessionToken.uid, metricsContext.flow_id)) + .then(metricsContext => db.createSigninCode(sessionToken.uid, metricsContext.flow_id)); } } function sendMessage (signinCode) { - return sms.send(phoneNumber, templateName, acceptLanguage, signinCode) + return sms.send(phoneNumber, templateName, acceptLanguage, signinCode); } function logSuccess () { - return request.emitMetricsEvent(`sms.${templateName}.sent`) + return request.emitMetricsEvent(`sms.${templateName}.sent`); } function createResponse () { return { formattedPhoneNumber: phoneNumberUtil.format(parsedPhoneNumber, 'international') - } + }; } } }, @@ -120,40 +120,40 @@ module.exports = (log, db, config, customs, sms) => { } }, handler: async function (request) { - log.begin('sms.status', request) + log.begin('sms.status', request); - let country + let country; - return createResponse(getLocation()) + return createResponse(getLocation()); function getLocation () { - country = request.query.country + country = request.query.country; if (! country) { if (! IS_STATUS_GEO_ENABLED) { - log.warn('sms.getGeoData', { warning: 'skipping geolocation step' }) - return true + log.warn('sms.getGeoData', { warning: 'skipping geolocation step' }); + return true; } - const location = request.app.geo.location + const location = request.app.geo.location; if (location && location.countryCode) { - country = location.countryCode + country = location.countryCode; } } if (country) { - return REGIONS.has(country) + return REGIONS.has(country); } - log.error('sms.getGeoData', { err: 'missing location data' }) - return false + log.error('sms.getGeoData', { err: 'missing location data' }); + return false; } function createResponse (isLocationOk) { - return { ok: isLocationOk && sms.isBudgetOk(), country } + return { ok: isLocationOk && sms.isBudgetOk(), country }; } } } - ] -} + ]; +}; diff --git a/lib/routes/token-codes.js b/lib/routes/token-codes.js index eb87a4aa..ccd3fc40 100644 --- a/lib/routes/token-codes.js +++ b/lib/routes/token-codes.js @@ -2,18 +2,18 @@ * 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/. */ -'use strict' +'use strict'; -const errors = require('../error') -const isA = require('joi') -const validators = require('./validators') -const HEX_STRING = validators.HEX_STRING -const DIGITS = validators.DIGITS -const P = require('../promise') +const errors = require('../error'); +const isA = require('joi'); +const validators = require('./validators'); +const HEX_STRING = validators.HEX_STRING; +const DIGITS = validators.DIGITS; +const P = require('../promise'); module.exports = (log, db, config, customs) => { - const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode - const TOKEN_CODE_LENGTH = tokenCodeConfig && tokenCodeConfig.codeLength || 6 + const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode; + const TOKEN_CODE_LENGTH = tokenCodeConfig && tokenCodeConfig.codeLength || 6; return [ { @@ -31,23 +31,23 @@ module.exports = (log, db, config, customs) => { } }, handler: async function (request) { - log.begin('session.verify.token', request) + log.begin('session.verify.token', request); - const code = request.payload.code.toUpperCase() - const uid = request.auth.credentials.uid - const email = request.auth.credentials.email + const code = request.payload.code.toUpperCase(); + const uid = request.auth.credentials.uid; + const email = request.auth.credentials.email; return customs.check(request, email, 'verifyTokenCode') .then(checkOptionalUidParam) .then(verifyCode) .then(emitMetrics) - .then(() => { return {} }) + .then(() => { return {}; }); function checkOptionalUidParam() { // For b/w compat we accept `uid` in the request body, // but it must match the uid of the sessionToken. if (request.payload.uid && request.payload.uid !== uid) { - throw errors.invalidRequestParameter('uid') + throw errors.invalidRequestParameter('uid'); } } @@ -58,22 +58,22 @@ module.exports = (log, db, config, customs) => { log.error('account.token.code.expired', { uid: uid, err: err - }) + }); } - throw err - }) + throw err; + }); } function emitMetrics() { log.info('account.token.code.verified', { uid: uid - }) + }); return P.all([request.emitMetricsEvent('tokenCodes.verified', {uid: uid}), request.emitMetricsEvent('account.confirmed', {uid: uid})]) - .then(() => ({})) + .then(() => ({})); } } } - ] -} + ]; +}; diff --git a/lib/routes/totp.js b/lib/routes/totp.js index df1767de..d3478fbf 100644 --- a/lib/routes/totp.js +++ b/lib/routes/totp.js @@ -2,36 +2,36 @@ * 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/. */ -'use strict' +'use strict'; -const errors = require('../error') -const validators = require('./validators') -const isA = require('joi') -const P = require('../promise') -const otplib = require('otplib') -const qrcode = require('qrcode') -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema +const errors = require('../error'); +const validators = require('./validators'); +const isA = require('joi'); +const P = require('../promise'); +const otplib = require('otplib'); +const qrcode = require('qrcode'); +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; module.exports = (log, db, mailer, customs, config) => { - const totpUtils = require('../../lib/routes/utils/totp')(log, config, db) + const totpUtils = require('../../lib/routes/utils/totp')(log, config, db); // Default options for TOTP otplib.authenticator.options = { encoding: 'hex', step: config.step, window: config.window - } + }; // Currently, QR codes are rendered with the highest possible // error correction, which should in theory allow clients to // scan the image better. // Ref: https://github.com/soldair/node-qrcode#error-correction-level - const qrCodeOptions = {errorCorrectionLevel: 'H'} + const qrCodeOptions = {errorCorrectionLevel: 'H'}; - const RECOVERY_CODE_COUNT = config.recoveryCodes && config.recoveryCodes.count || 8 + const RECOVERY_CODE_COUNT = config.recoveryCodes && config.recoveryCodes.count || 8; - P.promisify(qrcode.toDataURL) + P.promisify(qrcode.toDataURL); return [ { @@ -54,49 +54,49 @@ module.exports = (log, db, mailer, customs, config) => { } }, handler: async function (request) { - log.begin('totp.create', request) + log.begin('totp.create', request); - let response - let secret - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const authenticator = new otplib.authenticator.Authenticator() - authenticator.options = otplib.authenticator.options + let response; + let secret; + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = otplib.authenticator.options; return customs.check(request, sessionToken.email, 'totpCreate') .then(() => { - secret = authenticator.generateSecret() - return createTotpToken() + secret = authenticator.generateSecret(); + return createTotpToken(); }) .then(emitMetrics) .then(createResponse) - .then(() => response) + .then(() => response); function createTotpToken() { if (sessionToken.tokenVerificationId) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } - return db.createTotpToken(uid, secret, 0) + return db.createTotpToken(uid, secret, 0); } function createResponse() { - const otpauth = authenticator.keyuri(sessionToken.email, config.serviceName, secret) + const otpauth = authenticator.keyuri(sessionToken.email, config.serviceName, secret); return qrcode.toDataURL(otpauth, qrCodeOptions) .then((qrCodeUrl) => { response = { qrCodeUrl, secret - } - }) + }; + }); } function emitMetrics() { log.info('totpToken.created', { uid: uid - }) - return request.emitMetricsEvent('totpToken.created', {uid: uid}) + }); + return request.emitMetricsEvent('totpToken.created', {uid: uid}); } } }, @@ -110,47 +110,47 @@ module.exports = (log, db, mailer, customs, config) => { response: {} }, handler: async function (request) { - log.begin('totp.destroy', request) + log.begin('totp.destroy', request); - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - let hasEnabledToken = false + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + let hasEnabledToken = false; return customs.check(request, sessionToken.email, 'totpDestroy') .then(checkTotpToken) .then(deleteTotpToken) .then(sendEmailNotification) - .then(() => { return {} }) + .then(() => { return {}; }); function checkTotpToken() { // If a TOTP token is not verified, we should be able to safely delete regardless of session // verification state. return totpUtils.hasTotpToken({uid}) - .then((result) => hasEnabledToken = result) + .then((result) => hasEnabledToken = result); } function deleteTotpToken() { if (hasEnabledToken && (sessionToken.tokenVerificationId || sessionToken.authenticatorAssuranceLevel <= 1)) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } return db.deleteTotpToken(uid) .then(() => { return log.notifyAttachedServices('profileDataChanged', request, { uid: sessionToken.uid - }) - }) + }); + }); } function sendEmailNotification() { if (! hasEnabledToken) { - return + return; } return db.account(sessionToken.uid) .then((account) => { - const geoData = request.app.geo - const ip = request.app.clientAddress + const geoData = request.app.geo; + const ip = request.app.clientAddress; const emailOptions = { acceptLanguage: request.app.acceptLanguage, ip: ip, @@ -162,10 +162,10 @@ module.exports = (log, db, mailer, customs, config) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - } + }; - mailer.sendPostRemoveTwoStepAuthNotification(account.emails, account, emailOptions) - }) + mailer.sendPostRemoveTwoStepAuthNotification(account.emails, account, emailOptions); + }); } } }, @@ -183,22 +183,22 @@ module.exports = (log, db, mailer, customs, config) => { } }, handler: async function (request) { - log.begin('totp.exists', request) + log.begin('totp.exists', request); - const sessionToken = request.auth.credentials - let exists = false + const sessionToken = request.auth.credentials; + let exists = false; return getTotpToken() - .then(() => { return {exists} }) + .then(() => { return {exists}; }); function getTotpToken() { return P.resolve() .then(() => { if (sessionToken.tokenVerificationId) { - throw errors.unverifiedSession() + throw errors.unverifiedSession(); } - return db.totpToken(sessionToken.uid) + return db.totpToken(sessionToken.uid); }) .then((token) => { @@ -208,18 +208,18 @@ module.exports = (log, db, mailer, customs, config) => { if (! token.verified) { return db.deleteTotpToken(sessionToken.uid) .then(() => { - exists = false - }) + exists = false; + }); } else { - exists = true + exists = true; } }, (err) => { if (err.errno === errors.ERRNO.TOTP_TOKEN_NOT_FOUND) { - exists = false - return + exists = false; + return; } - throw err - }) + throw err; + }); } } }, @@ -244,13 +244,13 @@ module.exports = (log, db, mailer, customs, config) => { } }, handler: async function (request) { - log.begin('session.verify.totp', request) + log.begin('session.verify.totp', request); - const code = request.payload.code - const sessionToken = request.auth.credentials - const uid = sessionToken.uid - const email = sessionToken.email - let sharedSecret, isValidCode, tokenVerified, recoveryCodes + const code = request.payload.code; + const sessionToken = request.auth.credentials; + const uid = sessionToken.uid; + const email = sessionToken.email; + let sharedSecret, isValidCode, tokenVerified, recoveryCodes; return customs.check(request, email, 'verifyTotpCode') .then(getTotpToken) @@ -263,27 +263,27 @@ module.exports = (log, db, mailer, customs, config) => { .then(() => { const response = { success: isValidCode - } + }; if (recoveryCodes) { - response.recoveryCodes = recoveryCodes + response.recoveryCodes = recoveryCodes; } - return response - }) + return response; + }); function getTotpToken() { return db.totpToken(sessionToken.uid) .then((token) => { - sharedSecret = token.sharedSecret - tokenVerified = token.verified - }) + sharedSecret = token.sharedSecret; + tokenVerified = token.verified; + }); } function verifyTotpCode() { - const authenticator = new otplib.authenticator.Authenticator() - authenticator.options = Object.assign({}, otplib.authenticator.options, {secret: sharedSecret}) - isValidCode = authenticator.check(code, sharedSecret) + const authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = Object.assign({}, otplib.authenticator.options, {secret: sharedSecret}); + isValidCode = authenticator.check(code, sharedSecret); } // Once a valid TOTP code has been detected, the token becomes verified @@ -296,8 +296,8 @@ module.exports = (log, db, mailer, customs, config) => { }).then(() => { return log.notifyAttachedServices('profileDataChanged', request, { uid: sessionToken.uid - }) - }) + }); + }); } } @@ -305,14 +305,14 @@ module.exports = (log, db, mailer, customs, config) => { function replaceRecoveryCodes() { if (isValidCode && ! tokenVerified) { return db.replaceRecoveryCodes(uid, RECOVERY_CODE_COUNT) - .then((result) => recoveryCodes = result) + .then((result) => recoveryCodes = result); } } // If a valid code was sent, this verifies the session using the `totp-2fa` method. function verifySession() { if (isValidCode && sessionToken.authenticatorAssuranceLevel <= 1) { - return db.verifyTokensWithMethod(sessionToken.id, 'totp-2fa') + return db.verifyTokensWithMethod(sessionToken.id, 'totp-2fa'); } } @@ -320,22 +320,22 @@ module.exports = (log, db, mailer, customs, config) => { if (isValidCode) { log.info('totp.verified', { uid: uid - }) - request.emitMetricsEvent('totpToken.verified', {uid: uid}) + }); + request.emitMetricsEvent('totpToken.verified', {uid: uid}); } else { log.info('totp.unverified', { uid: uid - }) - request.emitMetricsEvent('totpToken.unverified', {uid: uid}) + }); + request.emitMetricsEvent('totpToken.unverified', {uid: uid}); } } function sendEmailNotification() { return db.account(sessionToken.uid) .then((account) => { - const geoData = request.app.geo - const ip = request.app.clientAddress - const service = request.payload.service || request.query.service + const geoData = request.app.geo; + const ip = request.app.clientAddress; + const service = request.payload.service || request.query.service; const emailOptions = { acceptLanguage: request.app.acceptLanguage, ip: ip, @@ -348,13 +348,13 @@ module.exports = (log, db, mailer, customs, config) => { uaOSVersion: request.app.ua.osVersion, uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid - } + }; // Check to see if this token was just verified, if it is, then this means // the user has enabled two step authentication, otherwise send new device // login email. if (isValidCode && ! tokenVerified) { - return mailer.sendPostAddTwoStepAuthNotification(account.emails, account, emailOptions) + return mailer.sendPostAddTwoStepAuthNotification(account.emails, account, emailOptions); } // All accounts that have a TOTP token, force the session to be verified, therefore @@ -362,12 +362,12 @@ module.exports = (log, db, mailer, customs, config) => { // login email. Instead, lets perform a basic check that the service is `sync`, otherwise // don't send. if (isValidCode && service === 'sync') { - return mailer.sendNewDeviceLoginNotification(account.emails, account, emailOptions) + return mailer.sendNewDeviceLoginNotification(account.emails, account, emailOptions); } - }) + }); } } } - ] -} + ]; +}; diff --git a/lib/routes/unblock-codes.js b/lib/routes/unblock-codes.js index 5b9bfc8e..4fadb376 100644 --- a/lib/routes/unblock-codes.js +++ b/lib/routes/unblock-codes.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const isA = require('joi') -const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema -const validators = require('./validators') +const isA = require('joi'); +const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; +const validators = require('./validators'); -const { HEX_STRING, BASE_36 } = validators +const { HEX_STRING, BASE_36 } = validators; module.exports = (log, db, mailer, config, customs) => { - const unblockCodeLen = config && config.codeLength || 0 + const unblockCodeLen = config && config.codeLength || 0; return [ { @@ -26,45 +26,45 @@ module.exports = (log, db, mailer, config, customs) => { } }, handler: async function (request) { - log.begin('Account.SendUnblockCode', request) + log.begin('Account.SendUnblockCode', request); - const email = request.payload.email - let emailRecord + const email = request.payload.email; + let emailRecord; - request.validateMetricsContext() + request.validateMetricsContext(); - const { flowId, flowBeginTime } = await request.app.metricsContext + const { flowId, flowBeginTime } = await request.app.metricsContext; return customs.check(request, email, 'sendUnblockCode') .then(lookupAccount) .then(createUnblockCode) .then(mailUnblockCode) .then(() => request.emitMetricsEvent('account.login.sentUnblockCode')) - .then(() => { return {} }) + .then(() => { return {}; }); function lookupAccount () { return db.accountRecord(email) .then(record => { - emailRecord = record - return record.uid - }) + emailRecord = record; + return record.uid; + }); } function createUnblockCode (uid) { - return db.createUnblockCode(uid) + return db.createUnblockCode(uid); } function mailUnblockCode (code) { return db.accountEmails(emailRecord.uid) .then(emails => { - const geoData = request.app.geo + const geoData = request.app.geo; const { browser: uaBrowser, browserVersion: uaBrowserVersion, os: uaOS, osVersion: uaOSVersion, deviceType: uaDeviceType - } = request.app.ua + } = request.app.ua; return mailer.sendUnblockCode(emails, emailRecord, { acceptLanguage: request.app.acceptLanguage, @@ -80,8 +80,8 @@ module.exports = (log, db, mailer, config, customs) => { uaOSVersion, uaDeviceType, uid: emailRecord.uid - }) - }) + }); + }); } } }, @@ -97,17 +97,17 @@ module.exports = (log, db, mailer, config, customs) => { } }, handler: async function (request) { - log.begin('Account.RejectUnblockCode', request) + log.begin('Account.RejectUnblockCode', request); - const uid = request.payload.uid - const code = request.payload.unblockCode.toUpperCase() + const uid = request.payload.uid; + const code = request.payload.unblockCode.toUpperCase(); return db.consumeUnblockCode(uid, code) .then(() => { - log.info('account.login.rejectedUnblockCode', { uid, unblockCode: code }) - return {} - }) + log.info('account.login.rejectedUnblockCode', { uid, unblockCode: code }); + return {}; + }); } } - ] -} + ]; +}; diff --git a/lib/routes/util.js b/lib/routes/util.js index 05435d51..81b4caae 100644 --- a/lib/routes/util.js +++ b/lib/routes/util.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const isA = require('joi') -const random = require('../crypto/random') -const validators = require('./validators') -const HEX_STRING = validators.HEX_STRING +const isA = require('joi'); +const random = require('../crypto/random'); +const validators = require('./validators'); +const HEX_STRING = validators.HEX_STRING; module.exports = (log, config, redirectDomain) => { return [ @@ -17,9 +17,9 @@ module.exports = (log, config, redirectDomain) => { handler: async function getRandomBytes(request) { return random(32) .then( - bytes => { return { data: bytes.toString('hex') }}, - err => { throw err } - ) + bytes => { return { data: bytes.toString('hex') };}, + err => { throw err; } + ); } }, { @@ -36,7 +36,7 @@ module.exports = (log, config, redirectDomain) => { } }, handler: async function (request, h) { - return h.redirect(config.contentServer.url + request.raw.req.url) + return h.redirect(config.contentServer.url + request.raw.req.url); } }, { @@ -54,8 +54,8 @@ module.exports = (log, config, redirectDomain) => { } }, handler: async function (request, h) { - return h.redirect(config.contentServer.url + request.raw.req.url) + return h.redirect(config.contentServer.url + request.raw.req.url); } } - ] -} + ]; +}; diff --git a/lib/routes/utils/email.js b/lib/routes/utils/email.js index 1c690fa4..cad4fa3d 100644 --- a/lib/routes/utils/email.js +++ b/lib/routes/utils/email.js @@ -2,22 +2,22 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../../error') +const error = require('../../error'); const BOUNCE_ERRORS = new Set([ error.ERRNO.BOUNCE_COMPLAINT, error.ERRNO.BOUNCE_HARD, error.ERRNO.BOUNCE_SOFT -]) +]); module.exports = { sendError (err, isNewAddress) { if (err && BOUNCE_ERRORS.has(err.errno)) { - return err + return err; } - return error.cannotSendEmail(isNewAddress) + return error.cannotSendEmail(isNewAddress); } -} +}; diff --git a/lib/routes/utils/request_helper.js b/lib/routes/utils/request_helper.js index 84f5b64b..a8b6fff1 100644 --- a/lib/routes/utils/request_helper.js +++ b/lib/routes/utils/request_helper.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/. */ -'use strict' +'use strict'; /** * Returns `true` if request has a keys=true query param. @@ -11,9 +11,9 @@ * @returns {boolean} */ function wantsKeys (request) { - return !! (request.query && request.query.keys) + return !! (request.query && request.query.keys); } module.exports = { wantsKeys: wantsKeys -} +}; diff --git a/lib/routes/utils/signin.js b/lib/routes/utils/signin.js index 7a786757..3bc3629b 100644 --- a/lib/routes/utils/signin.js +++ b/lib/routes/utils/signin.js @@ -2,25 +2,25 @@ * 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/. */ -'use strict' +'use strict'; -const emailUtils = require('./email') -const isA = require('joi') -const validators = require('../validators') -const P = require('../../promise') -const butil = require('../../crypto/butil') -const error = require('../../error') +const emailUtils = require('./email'); +const isA = require('joi'); +const validators = require('../validators'); +const P = require('../../promise'); +const butil = require('../../crypto/butil'); +const error = require('../../error'); -const BASE_36 = validators.BASE_36 +const BASE_36 = validators.BASE_36; // An arbitrary, but very generous, limit on the number of active sessions. // Currently only for metrics purposes, not enforced. -const MAX_ACTIVE_SESSIONS = 200 +const MAX_ACTIVE_SESSIONS = 200; module.exports = (log, config, customs, db, mailer) => { - const unblockCodeLifetime = config.signinUnblock && config.signinUnblock.codeLifetime || 0 - const unblockCodeLen = config.signinUnblock && config.signinUnblock.codeLength || 8 + const unblockCodeLifetime = config.signinUnblock && config.signinUnblock.codeLifetime || 0; + const unblockCodeLen = config.signinUnblock && config.signinUnblock.codeLength || 8; return { @@ -40,22 +40,22 @@ module.exports = (log, config, customs, db, mailer) => { email: accountRecord.email, errno: error.ERRNO.ACCOUNT_RESET }).then(() => { - throw error.mustResetAccount(accountRecord.email) - }) + throw error.mustResetAccount(accountRecord.email); + }); } return password.verifyHash() .then(verifyHash => { - return db.checkPassword(accountRecord.uid, verifyHash) + return db.checkPassword(accountRecord.uid, verifyHash); }) .then(match => { if (match) { - return match + return match; } return customs.flag(clientAddress, { email: accountRecord.email, errno: error.ERRNO.INCORRECT_PASSWORD - }).then(() => match) - }) + }).then(() => match); + }); }, /** @@ -67,13 +67,13 @@ module.exports = (log, config, customs, db, mailer) => { // that the user typed into the login form. This might differ from the address // used for calculating the password hash, which is provided in `email` param. if (! originalLoginEmail) { - originalLoginEmail = email + originalLoginEmail = email; } // Logging in with a secondary email address is not currently supported. if (originalLoginEmail.toLowerCase() !== accountRecord.primaryEmail.normalizedEmail) { - throw error.cannotLoginWithSecondaryEmail() + throw error.cannotLoginWithSecondaryEmail(); } - return P.resolve(true) + return P.resolve(true); }, /** @@ -90,81 +90,81 @@ module.exports = (log, config, customs, db, mailer) => { * } */ checkCustomsAndLoadAccount(request, email) { - let accountRecord, originalError - let didSigninUnblock = false + let accountRecord, originalError; + let didSigninUnblock = false; return P.resolve().then(() => { // For testing purposes, some email addresses are forced // to go through signin unblock on every login attempt. - const forced = config.signinUnblock && config.signinUnblock.forcedEmailAddresses + const forced = config.signinUnblock && config.signinUnblock.forcedEmailAddresses; if (forced && forced.test(email)) { - return P.reject(error.requestBlocked(true)) + return P.reject(error.requestBlocked(true)); } - return customs.check(request, email, 'accountLogin') + return customs.check(request, email, 'accountLogin'); }).catch((e) => { - originalError = e + originalError = e; // Non-customs-related errors get thrown straight back to the caller. if (e.errno !== error.ERRNO.REQUEST_BLOCKED && e.errno !== error.ERRNO.THROTTLED) { - throw e + throw e; } return request.emitMetricsEvent('account.login.blocked').then(() => { // If this customs error cannot be bypassed with email confirmation, // throw it straight back to the caller. - var verificationMethod = e.output.payload.verificationMethod + var verificationMethod = e.output.payload.verificationMethod; if (verificationMethod !== 'email-captcha' || ! request.payload.unblockCode) { - throw e + throw e; } // Check for a valid unblockCode, to allow the request to proceed. // This requires that we load the accountRecord to learn the uid. - const unblockCode = request.payload.unblockCode.toUpperCase() + const unblockCode = request.payload.unblockCode.toUpperCase(); return db.accountRecord(email).then(result => { - accountRecord = result + accountRecord = result; return db.consumeUnblockCode(accountRecord.uid, unblockCode).then(code => { if (Date.now() - code.createdAt > unblockCodeLifetime) { log.info('Account.login.unblockCode.expired', { uid: accountRecord.uid - }) - throw error.invalidUnblockCode() + }); + throw error.invalidUnblockCode(); } }).then(() => { - didSigninUnblock = true - return request.emitMetricsEvent('account.login.confirmedUnblockCode') + didSigninUnblock = true; + return request.emitMetricsEvent('account.login.confirmedUnblockCode'); }).catch((e) => { if (e.errno !== error.ERRNO.INVALID_UNBLOCK_CODE) { - throw e + throw e; } return request.emitMetricsEvent('account.login.invalidUnblockCode').then(() => { - throw e - }) - }) - }) - }) + throw e; + }); + }); + }); + }); }).then(() => { // If we didn't load it above while checking unblock codes, // it's now safe to load the account record from the db. if (! accountRecord) { return db.accountRecord(email).then(result => { - accountRecord = result - }) + accountRecord = result; + }); } }).then(() => { - return { accountRecord, didSigninUnblock } + return { accountRecord, didSigninUnblock }; }).catch((e) => { // Some errors need to be flagged with customs. if (e.errno === error.ERRNO.INVALID_UNBLOCK_CODE || e.errno === error.ERRNO.ACCOUNT_UNKNOWN) { customs.flag(request.app.clientAddress, { email: email, errno: e.errno - }) + }); } // For any error other than INVALID_UNBLOCK_CODE, hide it behind the original customs error. // This prevents us from accidentally leaking additional info to a caller that's been // blocked, including e.g. whether or not the target account exists. if (originalError && e.errno !== error.ERRNO.INVALID_UNBLOCK_CODE) { - throw originalError + throw originalError; } - throw e - }) + throw e; + }); }, /** @@ -173,38 +173,38 @@ module.exports = (log, config, customs, db, mailer) => { * notifying attached services. */ async sendSigninNotifications (request, accountRecord, sessionToken, verificationMethod) { - const service = request.payload.service || request.query.service - const redirectTo = request.payload.redirectTo - const resume = request.payload.resume - const ip = request.app.clientAddress - const isUnverifiedAccount = ! accountRecord.primaryEmail.isVerified + const service = request.payload.service || request.query.service; + const redirectTo = request.payload.redirectTo; + const resume = request.payload.resume; + const ip = request.app.clientAddress; + const isUnverifiedAccount = ! accountRecord.primaryEmail.isVerified; - let sessions + let sessions; - const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext + const { deviceId, flowId, flowBeginTime } = await request.app.metricsContext; - const mustVerifySession = sessionToken.mustVerify && ! sessionToken.tokenVerified + const mustVerifySession = sessionToken.mustVerify && ! sessionToken.tokenVerified; // The final event to complete the login flow depends on the details // of the flow being undertaken, so prepare accordingly. - let flowCompleteSignal + let flowCompleteSignal; if (service === 'sync') { // Sync signins are only complete when the browser actually syncs. - flowCompleteSignal = 'account.signed' + flowCompleteSignal = 'account.signed'; } else if (mustVerifySession) { // Sessions that require verification are only complete once confirmed. - flowCompleteSignal = 'account.confirmed' + flowCompleteSignal = 'account.confirmed'; } else { // Otherwise, the login itself is the end of the flow. - flowCompleteSignal = 'account.login' + flowCompleteSignal = 'account.login'; } - request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'login') + request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'login'); return stashMetricsContext() .then(checkNumberOfActiveSessions) .then(emitLoginEvent) .then(sendEmail) - .then(recordSecurityEvent) + .then(recordSecurityEvent); function stashMetricsContext() { return request.stashMetricsContext(sessionToken) @@ -215,15 +215,15 @@ module.exports = (log, config, customs, db, mailer) => { return request.stashMetricsContext({ uid: accountRecord.uid, id: sessionToken.tokenVerificationId - }) + }); } - }) + }); } function checkNumberOfActiveSessions () { return db.sessions(accountRecord.uid) .then(s => { - sessions = s + sessions = s; if (sessions.length > MAX_ACTIVE_SESSIONS) { // There's no spec-compliant way to error out // as a result of having too many active sessions. @@ -232,15 +232,15 @@ module.exports = (log, config, customs, db, mailer) => { uid: accountRecord.uid, userAgent: request.headers['user-agent'], numSessions: sessions.length - }) + }); } - }) + }); } async function emitLoginEvent () { await request.emitMetricsEvent('account.login', { uid: accountRecord.uid - }) + }); if (request.payload.reason === 'signin') { await log.notifyAttachedServices('login', request, { @@ -249,18 +249,18 @@ module.exports = (log, config, customs, db, mailer) => { service, uid: accountRecord.uid, userAgent: request.headers['user-agent'] - }) + }); } } function sendEmail() { // For unverified accounts, we always re-send the account verification email. if (isUnverifiedAccount) { - return sendVerifyAccountEmail() + return sendVerifyAccountEmail(); } // If the session needs to be verified, send the sign-in confirmation email. if (mustVerifySession) { - return sendVerifySessionEmail() + return sendVerifySessionEmail(); } // Otherwise, no email is necessary. } @@ -268,7 +268,7 @@ module.exports = (log, config, customs, db, mailer) => { function sendVerifyAccountEmail() { // If the session doesn't require verification, // fall back to the account-level email code for the link. - const emailCode = sessionToken.tokenVerificationId || accountRecord.primaryEmail.emailCode + const emailCode = sessionToken.tokenVerificationId || accountRecord.primaryEmail.emailCode; return mailer.sendVerifyCode([], accountRecord, { code: emailCode, @@ -288,7 +288,7 @@ module.exports = (log, config, customs, db, mailer) => { uaDeviceType: request.app.ua.deviceType, uid: sessionToken.uid }) - .then(() => request.emitMetricsEvent('email.verification.sent')) + .then(() => request.emitMetricsEvent('email.verification.sent')); } function sendVerifySessionEmail() { @@ -297,21 +297,21 @@ module.exports = (log, config, customs, db, mailer) => { switch (verificationMethod) { case 'email': // Sends an email containing a link to verify login - return sendVerifyLoginEmail() + return sendVerifyLoginEmail(); case 'email-2fa': // Sends an email containing a code that can verify a login - return sendVerifyLoginCodeEmail() + return sendVerifyLoginCodeEmail(); case 'email-captcha': // `email-captcha` is a custom verification method used only for // unblock codes. We do not need to send a verification email // in this case. - break + break; case 'totp-2fa': // This verification method requires a user to use a third-party // application. - break + break; default: - return sendVerifyLoginEmail() + return sendVerifyLoginEmail(); } } @@ -319,9 +319,9 @@ module.exports = (log, config, customs, db, mailer) => { log.info('account.signin.confirm.start', { uid: accountRecord.uid, tokenVerificationId: sessionToken.tokenVerificationId - }) + }); - const geoData = request.app.geo + const geoData = request.app.geo; return mailer.sendVerifyLoginEmail( accountRecord.emails, accountRecord, @@ -347,18 +347,18 @@ module.exports = (log, config, customs, db, mailer) => { ) .then(() => request.emitMetricsEvent('email.confirmation.sent')) .catch(err => { - log.error('mailer.confirmation.error', { err }) + log.error('mailer.confirmation.error', { err }); - throw emailUtils.sendError(err, isUnverifiedAccount) - }) + throw emailUtils.sendError(err, isUnverifiedAccount); + }); } function sendVerifyLoginCodeEmail() { log.info('account.token.code.start', { uid: accountRecord.uid - }) + }); - const geoData = request.app.geo + const geoData = request.app.geo; return mailer.sendVerifyLoginCodeEmail( accountRecord.emails, accountRecord, @@ -382,7 +382,7 @@ module.exports = (log, config, customs, db, mailer) => { uid: sessionToken.uid } ) - .then(() => request.emitMetricsEvent('email.tokencode.sent')) + .then(() => request.emitMetricsEvent('email.tokencode.sent')); } function recordSecurityEvent() { @@ -391,7 +391,7 @@ module.exports = (log, config, customs, db, mailer) => { uid: accountRecord.uid, ipAddr: ip, tokenId: sessionToken.id - }) + }); } }, @@ -404,12 +404,12 @@ module.exports = (log, config, customs, db, mailer) => { wrapKb: wrapKb, emailVerified: accountRecord.primaryEmail.isVerified, tokenVerificationId: sessionToken.tokenVerificationId - }) + }); }) .then(keyFetchToken => { return request.stashMetricsContext(keyFetchToken) - .then(() => { return keyFetchToken } ) - }) + .then(() => { return keyFetchToken; } ); + }); }, getSessionVerificationStatus(sessionToken, verificationMethod) { @@ -418,7 +418,7 @@ module.exports = (log, config, customs, db, mailer) => { verified: false, verificationMethod: 'email', verificationReason: 'signup' - } + }; } if (sessionToken.mustVerify && ! sessionToken.tokenVerified) { return { @@ -426,10 +426,10 @@ module.exports = (log, config, customs, db, mailer) => { // Override the verification method if it was explicitly specified in the request. verificationMethod: verificationMethod || 'email', verificationReason: 'login' - } + }; } - return { verified: true } + return { verified: true }; }, - } -} + }; +}; diff --git a/lib/routes/utils/totp.js b/lib/routes/utils/totp.js index a7039907..8edb3e71 100644 --- a/lib/routes/utils/totp.js +++ b/lib/routes/utils/totp.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -const errors = require('../../error') +const errors = require('../../error'); module.exports = (log, config, db) => { @@ -17,18 +17,18 @@ module.exports = (log, config, db) => { * @returns boolean */ hasTotpToken(account) { - const {uid} = account + const {uid} = account; return db.totpToken(uid) .then((result) => { if (result && result.verified && result.enabled) { - return true + return true; } }, (err) => { if (err.errno === errors.ERRNO.TOTP_TOKEN_NOT_FOUND) { - return false + return false; } - throw err - }) + throw err; + }); } - } -} + }; +}; diff --git a/lib/routes/validators.js b/lib/routes/validators.js index f6833078..9f4fbd77 100644 --- a/lib/routes/validators.js +++ b/lib/routes/validators.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const { URL } = require('url') -const punycode = require('punycode.js') -const isA = require('joi') +const { URL } = require('url'); +const punycode = require('punycode.js'); +const isA = require('joi'); // Match any non-empty hex-encoded string. -const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/ -module.exports.HEX_STRING = HEX_STRING +const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/; +module.exports.HEX_STRING = HEX_STRING; -module.exports.BASE_36 = /^[a-zA-Z0-9]*$/ +module.exports.BASE_36 = /^[a-zA-Z0-9]*$/; // RFC 4648, section 5 -module.exports.URL_SAFE_BASE_64 = /^[A-Za-z0-9_-]+$/ +module.exports.URL_SAFE_BASE_64 = /^[A-Za-z0-9_-]+$/; // Crude phone number validation. The handler code does it more thoroughly. -exports.E164_NUMBER = /^\+[1-9]\d{1,14}$/ +exports.E164_NUMBER = /^\+[1-9]\d{1,14}$/; -exports.DIGITS = /^[0-9]+$/ +exports.DIGITS = /^[0-9]+$/; -exports.DEVICE_COMMAND_NAME = /^[a-zA-Z0-9._\/\-:]{1,100}$/ +exports.DEVICE_COMMAND_NAME = /^[a-zA-Z0-9._\/\-:]{1,100}$/; -exports.IP_ADDRESS = isA.string().ip() +exports.IP_ADDRESS = isA.string().ip(); // Match display-safe unicode characters. // We're pretty liberal with what's allowed in a unicode string, @@ -40,16 +40,16 @@ exports.IP_ADDRESS = isA.string().ip() // // We might tweak this list in future. -const DISPLAY_SAFE_UNICODE = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uD800-\uDFFF\uE000-\uF8FF\uFFF9-\uFFFF])*$/ -module.exports.DISPLAY_SAFE_UNICODE = DISPLAY_SAFE_UNICODE +const DISPLAY_SAFE_UNICODE = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uD800-\uDFFF\uE000-\uF8FF\uFFF9-\uFFFF])*$/; +module.exports.DISPLAY_SAFE_UNICODE = DISPLAY_SAFE_UNICODE; // Similar display-safe match but includes non-BMP characters -const DISPLAY_SAFE_UNICODE_WITH_NON_BMP = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFF])*$/ -module.exports.DISPLAY_SAFE_UNICODE_WITH_NON_BMP = DISPLAY_SAFE_UNICODE_WITH_NON_BMP +const DISPLAY_SAFE_UNICODE_WITH_NON_BMP = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFF])*$/; +module.exports.DISPLAY_SAFE_UNICODE_WITH_NON_BMP = DISPLAY_SAFE_UNICODE_WITH_NON_BMP; // Bearer auth header regex -const BEARER_AUTH_REGEX = /^Bearer\s+([a-z0-9+\/]+)$/i -module.exports.BEARER_AUTH_REGEX = BEARER_AUTH_REGEX +const BEARER_AUTH_REGEX = /^Bearer\s+([a-z0-9+\/]+)$/i; +module.exports.BEARER_AUTH_REGEX = BEARER_AUTH_REGEX; // Joi validator to match any valid email address. // This is different to Joi's builtin email validator, and @@ -60,29 +60,29 @@ module.exports.BEARER_AUTH_REGEX = BEARER_AUTH_REGEX // see examples here: https://github.com/hapijs/joi/blob/master/lib/string.js module.exports.email = function() { - var email = isA.string().max(255).regex(DISPLAY_SAFE_UNICODE) + var email = isA.string().max(255).regex(DISPLAY_SAFE_UNICODE); // Imma add a custom test to this Joi object using internal // properties because I can't find a nice API to do that. email._tests.push({ func: function(value, state, options) { if (value !== undefined && value !== null) { if (module.exports.isValidEmailAddress(value)) { - return value + return value; } } - return email.createError('string.base', { value }, state, options) + return email.createError('string.base', { value }, state, options); - }}) + }}); - return email -} + return email; +}; -module.exports.service = isA.string().max(16).regex(/^[a-zA-Z0-9\-]*$/) -module.exports.hexString = isA.string().regex(HEX_STRING) -module.exports.clientId = module.exports.hexString.length(16) -module.exports.accessToken = module.exports.hexString.length(64) -module.exports.refreshToken = module.exports.hexString.length(64) -module.exports.scope = isA.string().max(256).regex(/^[a-zA-Z0-9 _\/.:-]+$/) +module.exports.service = isA.string().max(16).regex(/^[a-zA-Z0-9\-]*$/); +module.exports.hexString = isA.string().regex(HEX_STRING); +module.exports.clientId = module.exports.hexString.length(16); +module.exports.accessToken = module.exports.hexString.length(64); +module.exports.refreshToken = module.exports.hexString.length(64); +module.exports.scope = isA.string().max(256).regex(/^[a-zA-Z0-9 _\/.:-]+$/); module.exports.assertion = isA.string().min(50).max(10240).regex(/^[a-zA-Z0-9_\-\.~=]+$/); module.exports.jwe = isA.string().max(1024) // JWE token format: 'protectedheader.encryptedkey.iv.cyphertext.authenticationtag' @@ -98,72 +98,72 @@ module.exports.jwe = isA.string().max(1024) // // https://github.com/mozilla/fxa-email-service/blob/6fc6c31043598b246102cd1fdd27fc325f4514fb/src/validate/mod.rs#L28-L30 -const EMAIL_USER = /^[A-Z0-9.!#$%&'*+\/=?^_`{|}~-]{1,64}$/i -const EMAIL_DOMAIN = /^[A-Z0-9](?:[A-Z0-9-]{0,253}[A-Z0-9])?(?:\.[A-Z0-9](?:[A-Z0-9-]{0,253}[A-Z0-9])?)+$/i +const EMAIL_USER = /^[A-Z0-9.!#$%&'*+\/=?^_`{|}~-]{1,64}$/i; +const EMAIL_DOMAIN = /^[A-Z0-9](?:[A-Z0-9-]{0,253}[A-Z0-9])?(?:\.[A-Z0-9](?:[A-Z0-9-]{0,253}[A-Z0-9])?)+$/i; module.exports.isValidEmailAddress = function(value) { if (! value) { - return false + return false; } - const parts = value.split('@') + const parts = value.split('@'); if (parts.length !== 2 || parts[1].length > 255) { - return false + return false; } if (! EMAIL_USER.test(punycode.toASCII(parts[0]))) { - return false + return false; } if (! EMAIL_DOMAIN.test(punycode.toASCII(parts[1]))) { - return false + return false; } - return true -} + return true; +}; module.exports.redirectTo = function redirectTo(base) { - const validator = isA.string().max(512) - let hostnameRegex = null + const validator = isA.string().max(512); + let hostnameRegex = null; if (base) { - hostnameRegex = new RegExp('(?:\\.|^)' + base.replace('.', '\\.') + '$') + hostnameRegex = new RegExp('(?:\\.|^)' + base.replace('.', '\\.') + '$'); } validator._tests.push( { func: (value, state, options) => { if (value !== undefined && value !== null) { if (isValidUrl(value, hostnameRegex)) { - return value + return value; } } - return validator.createError('string.base', { value }, state, options) + return validator.createError('string.base', { value }, state, options); } } - ) - return validator -} + ); + return validator; +}; module.exports.url = function url(options) { - const validator = isA.string().uri(options) + const validator = isA.string().uri(options); validator._tests.push( { func: (value, state, options) => { if (value !== undefined && value !== null) { if (isValidUrl(value)) { - return value + return value; } } - return validator.createError('string.base', { value }, state, options) + return validator.createError('string.base', { value }, state, options); } } - ) - return validator -} + ); + return validator; +}; module.exports.pushCallbackUrl = function pushUrl(options) { - const validator = isA.string().uri(options) + const validator = isA.string().uri(options); validator._tests.push( { func: (value, state, options) => { @@ -172,33 +172,33 @@ module.exports.pushCallbackUrl = function pushUrl(options) { // Fx Desktop registers https push urls with a :443 which causes `isValidUrl` // to fail because the :443 is expected to have been normalized away. if (/^https:\/\/[a-zA-Z0-9._-]+(:443)($|\/)/.test(value)) { - normalizedValue = value.replace(':443', '') + normalizedValue = value.replace(':443', ''); } if (isValidUrl(normalizedValue)) { - return value + return value; } } - return validator.createError('string.base', { value }, state, options) + return validator.createError('string.base', { value }, state, options); } } - ) - return validator -} + ); + return validator; +}; function isValidUrl(url, hostnameRegex) { - let parsed + let parsed; try { - parsed = new URL(url) + parsed = new URL(url); } catch (err) { - return false + return false; } if (hostnameRegex && ! hostnameRegex.test(parsed.hostname)) { - return false + return false; } if (! /^https?:$/.test(parsed.protocol)) { - return false + return false; } // Reject anything that won't round-trip unambiguously // through a parse. This puts the onus on the requestor @@ -207,15 +207,15 @@ function isValidUrl(url, hostnameRegex) { // slash if there's no path component, which is why we also // compare to `origin` below. if (parsed.href !== url && parsed.origin !== url) { - return false + return false; } - return parsed.href + return parsed.href; } -module.exports.verificationMethod = isA.string().valid(['email', 'email-2fa', 'email-captcha', 'totp-2fa']) +module.exports.verificationMethod = isA.string().valid(['email', 'email-2fa', 'email-captcha', 'totp-2fa']); -module.exports.authPW = isA.string().length(64).regex(HEX_STRING).required() -module.exports.wrapKb = isA.string().length(64).regex(HEX_STRING) +module.exports.authPW = isA.string().length(64).regex(HEX_STRING).required(); +module.exports.wrapKb = isA.string().length(64).regex(HEX_STRING); -module.exports.recoveryKeyId = isA.string().regex(HEX_STRING).max(32) -module.exports.recoveryData = isA.string().regex(/[a-zA-Z0-9.]/).max(1024).required() +module.exports.recoveryKeyId = isA.string().regex(HEX_STRING).max(32); +module.exports.recoveryData = isA.string().regex(/[a-zA-Z0-9.]/).max(1024).required(); diff --git a/lib/safe-url.js b/lib/safe-url.js index 985fa20f..e1a910bb 100644 --- a/lib/safe-url.js +++ b/lib/safe-url.js @@ -25,68 +25,68 @@ // url.render({}) // throws error.internalValidationError() // url.render({ uid: 'foo', id: 'bar' }) // throws error.internalValidationError() -'use strict' +'use strict'; -const error = require('./error') -const impl = require('safe-url-assembler')() +const error = require('./error'); +const impl = require('safe-url-assembler')(); -const SAFE_URL_COMPONENT = /^[\w.]+$/ +const SAFE_URL_COMPONENT = /^[\w.]+$/; module.exports = log => class SafeUrl { constructor (path, caller) { const expectedKeys = path.split('/') .filter(part => part.indexOf(':') === 0) - .map(part => part.substr(1)) + .map(part => part.substr(1)); this._expectedKeys = { array: expectedKeys, set: new Set(expectedKeys) - } - this._template = impl.template(path) - this._caller = caller + }; + this._template = impl.template(path); + this._caller = caller; } params () { - return this._expectedKeys.array.slice(0) + return this._expectedKeys.array.slice(0); } render (params = {}, query = {}) { - const paramsKeys = Object.keys(params) - const { array: expected, set: expectedSet } = this._expectedKeys + const paramsKeys = Object.keys(params); + const { array: expected, set: expectedSet } = this._expectedKeys; if (paramsKeys.length !== expected.length) { - this._fail('safeUrl.params.mismatch', { keys: paramsKeys, expected }) + this._fail('safeUrl.params.mismatch', { keys: paramsKeys, expected }); } paramsKeys.forEach(key => { if (! expectedSet.has(key)) { - this._fail('safeUrl.params.unexpected', { key, expected }) + this._fail('safeUrl.params.unexpected', { key, expected }); } - const value = params[key] - this._checkSafe('paramVal', key, value) - }) + const value = params[key]; + this._checkSafe('paramVal', key, value); + }); Object.keys(query).forEach(key => { - const value = query[key] - this._checkSafe('queryKey', key, key) - this._checkSafe('queryVal', key, value) - }) + const value = query[key]; + this._checkSafe('queryKey', key, key); + this._checkSafe('queryVal', key, value); + }); - return this._template.param(params).query(query).toString() + return this._template.param(params).query(query).toString(); } _checkSafe(location, key, value) { if (! value || typeof value !== 'string') { - this._fail('safeUrl.bad', { location, key, value }) + this._fail('safeUrl.bad', { location, key, value }); } if (! SAFE_URL_COMPONENT.test(value)) { - this._fail('safeUrl.unsafe', { location, key, value }) + this._fail('safeUrl.unsafe', { location, key, value }); } } _fail (op, data) { - log.error(op, Object.assign({ caller: this._caller }, data)) - throw error.internalValidationError(op, data) + log.error(op, Object.assign({ caller: this._caller }, data)); + throw error.internalValidationError(op, data); } -} +}; diff --git a/lib/scheme-refresh-token.js b/lib/scheme-refresh-token.js index 90561c96..ff2d0c4d 100644 --- a/lib/scheme-refresh-token.js +++ b/lib/scheme-refresh-token.js @@ -2,71 +2,71 @@ * 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/. */ -'use strict' +'use strict'; -const AppError = require('./error') -const joi = require('joi') -const validators = require('./routes/validators') -const { BEARER_AUTH_REGEX } = require('./routes/validators') -const ScopeSet = require('fxa-shared').oauth.scopes +const AppError = require('./error'); +const joi = require('joi'); +const validators = require('./routes/validators'); +const { BEARER_AUTH_REGEX } = require('./routes/validators'); +const ScopeSet = require('fxa-shared').oauth.scopes; // the refresh token scheme is currently used by things connected to sync, // and we're at a transitionary stage of its evolution into something more generic, // so we limit to the scope below as a safety mechanism -const ALLOWED_REFRESH_TOKEN_SCHEME_SCOPES = ScopeSet.fromArray(['https://identity.mozilla.com/apps/oldsync']) +const ALLOWED_REFRESH_TOKEN_SCHEME_SCOPES = ScopeSet.fromArray(['https://identity.mozilla.com/apps/oldsync']); module.exports = function schemeRefreshTokenScheme(db, oauthdb) { return function schemeRefreshToken(server, options) { return { async authenticate (request, h) { - const bearerMatch = BEARER_AUTH_REGEX.exec(request.headers.authorization) - const bearerMatchErr = new AppError.invalidRequestParameter('authorization') - const refreshToken = bearerMatch && bearerMatch[1] + const bearerMatch = BEARER_AUTH_REGEX.exec(request.headers.authorization); + const bearerMatchErr = new AppError.invalidRequestParameter('authorization'); + const refreshToken = bearerMatch && bearerMatch[1]; if (refreshToken) { - joi.attempt(bearerMatch[1], validators.refreshToken, bearerMatchErr) + joi.attempt(bearerMatch[1], validators.refreshToken, bearerMatchErr); } else { - throw bearerMatchErr + throw bearerMatchErr; } - const refreshTokenInfo = await oauthdb.checkRefreshToken(refreshToken) + const refreshTokenInfo = await oauthdb.checkRefreshToken(refreshToken); if (! refreshTokenInfo || ! refreshTokenInfo.active) { - return h.unauthenticated() + return h.unauthenticated(); } const credentials = { uid: refreshTokenInfo.sub, tokenVerified: true, refreshTokenId: refreshTokenInfo.jti - } + }; - const scopeSet = ScopeSet.fromString(refreshTokenInfo.scope) + const scopeSet = ScopeSet.fromString(refreshTokenInfo.scope); if (! scopeSet.intersects(ALLOWED_REFRESH_TOKEN_SCHEME_SCOPES)) { // unauthenticated if refreshToken is missing the required scope - return h.unauthenticated() + return h.unauthenticated(); } - credentials.client = await oauthdb.getClientInfo(refreshTokenInfo.client_id) - const devices = await db.devices(refreshTokenInfo.sub) + credentials.client = await oauthdb.getClientInfo(refreshTokenInfo.client_id); + const devices = await db.devices(refreshTokenInfo.sub); // use the hashed refreshToken id to find devices - const device = devices.filter(device => device.refreshTokenId === credentials.refreshTokenId)[0] + const device = devices.filter(device => device.refreshTokenId === credentials.refreshTokenId)[0]; if (device) { - credentials.deviceId = device.id - credentials.deviceName = device.name - credentials.deviceType = device.type - credentials.deviceCreatedAt = device.createdAt - credentials.deviceCallbackURL = device.callbackURL - credentials.deviceCallbackPublicKey = device.callbackPublicKey - credentials.deviceCallbackAuthKey = device.callbackAuthKey - credentials.deviceCallbackIsExpired = device.callbackIsExpired - credentials.deviceAvailableCommands = device.availableCommands + credentials.deviceId = device.id; + credentials.deviceName = device.name; + credentials.deviceType = device.type; + credentials.deviceCreatedAt = device.createdAt; + credentials.deviceCallbackURL = device.callbackURL; + credentials.deviceCallbackPublicKey = device.callbackPublicKey; + credentials.deviceCallbackAuthKey = device.callbackAuthKey; + credentials.deviceCallbackIsExpired = device.callbackIsExpired; + credentials.deviceAvailableCommands = device.availableCommands; } return h.authenticated({ credentials: credentials - }) + }); } - } - } -} + }; + }; +}; diff --git a/lib/senders/email.js b/lib/senders/email.js index 2eb515fb..456561af 100644 --- a/lib/senders/email.js +++ b/lib/senders/email.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; -const emailUtils = require('../email/utils/helpers') -const moment = require('moment-timezone') -const nodemailer = require('nodemailer') -const P = require('bluebird') -const qs = require('querystring') -const safeRegex = require('safe-regex') -const safeUserAgent = require('../userAgent/safe') -const Sandbox = require('sandbox') -const url = require('url') +const emailUtils = require('../email/utils/helpers'); +const moment = require('moment-timezone'); +const nodemailer = require('nodemailer'); +const P = require('bluebird'); +const qs = require('querystring'); +const safeRegex = require('safe-regex'); +const safeUserAgent = require('../userAgent/safe'); +const Sandbox = require('sandbox'); +const url = require('url'); -const TEMPLATE_VERSIONS = require('./templates/_versions.json') +const TEMPLATE_VERSIONS = require('./templates/_versions.json'); -const DEFAULT_LOCALE = 'en' -const DEFAULT_TIMEZONE = 'Etc/UTC' -const UTM_PREFIX = 'fx-' +const DEFAULT_LOCALE = 'en'; +const DEFAULT_TIMEZONE = 'Etc/UTC'; +const UTM_PREFIX = 'fx-'; -const X_SES_CONFIGURATION_SET = 'X-SES-CONFIGURATION-SET' -const X_SES_MESSAGE_TAGS = 'X-SES-MESSAGE-TAGS' +const X_SES_CONFIGURATION_SET = 'X-SES-CONFIGURATION-SET'; +const X_SES_MESSAGE_TAGS = 'X-SES-MESSAGE-TAGS'; const SERVICES = { internal: Symbol(), @@ -30,14 +30,14 @@ const SERVICES = { socketlabs: Symbol(), ses: Symbol() } -} +}; module.exports = function (log, config, oauthdb) { - const oauthClientInfo = require('./oauth_client_info')(log, config, oauthdb) + const oauthClientInfo = require('./oauth_client_info')(log, config, oauthdb); const redis = require('../redis')(Object.assign({}, config.redis, config.redis.email), log) || { // Fallback to a stub implementation if redis is disabled get: () => P.resolve() - } + }; // Email template to UTM campaign map, each of these should be unique and // map to exactly one email template. @@ -66,7 +66,7 @@ module.exports = function (log, config, oauthdb) { 'verifyPrimaryEmail': 'welcome-primary', 'verifySyncEmail': 'welcome-sync', 'verifySecondaryEmail': 'welcome-secondary' - } + }; // Email template to UTM content, this is typically the main call out link/button // in template. @@ -95,43 +95,43 @@ module.exports = function (log, config, oauthdb) { 'verifyPrimaryEmail': 'activate', 'verifySyncEmail': 'activate-sync', 'verifySecondaryEmail': 'activate', - } + }; function extend(target, source) { for (var key in source) { - target[key] = source[key] + target[key] = source[key]; } - return target + return target; } // helper used to ensure strings are extracted function gettext(txt) { - return txt + return txt; } function linkAttributes(url) { // Not very nice to have presentation code in here, but this is to help l10n // contributors not deal with extraneous noise in strings. - return 'href="' + url + '" style="color: #0a84ff; text-decoration: none; font-family: sans-serif;"' + return 'href="' + url + '" style="color: #0a84ff; text-decoration: none; font-family: sans-serif;"'; } function constructLocalTimeString (timeZone, locale) { // if no timeZone is passed, use DEFAULT_TIMEZONE - moment.tz.setDefault(DEFAULT_TIMEZONE) + moment.tz.setDefault(DEFAULT_TIMEZONE); // if no locale is passed, use DEFAULT_LOCALE - locale = locale || DEFAULT_LOCALE - moment.locale(locale) - var time = moment() + locale = locale || DEFAULT_LOCALE; + moment.locale(locale); + var time = moment(); if (timeZone) { - time = time.tz(timeZone) + time = time.tz(timeZone); } // return a locale-specific time - return time.format('LTS (z) dddd, ll') + return time.format('LTS (z) dddd, ll'); } function sesMessageTagsHeaderValue(templateName, serviceName) { - return `messageType=fxa-${templateName}, app=fxa, service=${serviceName}` + return `messageType=fxa-${templateName}, app=fxa, service=${serviceName}`; } function Mailer(translator, templates, mailerConfig, sender) { @@ -140,142 +140,142 @@ module.exports = function (log, config, oauthdb) { secure: mailerConfig.secure, ignoreTLS: ! mailerConfig.secure, port: mailerConfig.port - } + }; if (mailerConfig.user && mailerConfig.password) { options.auth = { user: mailerConfig.user, pass: mailerConfig.password - } + }; } - this.accountSettingsUrl = mailerConfig.accountSettingsUrl - this.accountRecoveryCodesUrl = mailerConfig.accountRecoveryCodesUrl - this.androidUrl = mailerConfig.androidUrl - this.initiatePasswordChangeUrl = mailerConfig.initiatePasswordChangeUrl - this.initiatePasswordResetUrl = mailerConfig.initiatePasswordResetUrl - this.iosUrl = mailerConfig.iosUrl - this.iosAdjustUrl = mailerConfig.iosAdjustUrl - this.mailer = sender || nodemailer.createTransport(options) - this.emailService = sender || require('./email_service')(config) - this.passwordManagerInfoUrl = mailerConfig.passwordManagerInfoUrl - this.passwordResetUrl = mailerConfig.passwordResetUrl - this.privacyUrl = mailerConfig.privacyUrl - this.reportSignInUrl = mailerConfig.reportSignInUrl - this.revokeAccountRecoveryUrl = mailerConfig.revokeAccountRecoveryUrl - this.createAccountRecoveryUrl = mailerConfig.createAccountRecoveryUrl - this.sender = mailerConfig.sender - this.sesConfigurationSet = mailerConfig.sesConfigurationSet - this.supportUrl = mailerConfig.supportUrl - this.syncUrl = mailerConfig.syncUrl - this.templates = templates - this.translator = translator.getTranslator - this.verificationUrl = mailerConfig.verificationUrl - this.verifyLoginUrl = mailerConfig.verifyLoginUrl - this.verifySecondaryEmailUrl = mailerConfig.verifySecondaryEmailUrl - this.verifyPrimaryEmailUrl = mailerConfig.verifyPrimaryEmailUrl - this.prependVerificationSubdomain = mailerConfig.prependVerificationSubdomain + this.accountSettingsUrl = mailerConfig.accountSettingsUrl; + this.accountRecoveryCodesUrl = mailerConfig.accountRecoveryCodesUrl; + this.androidUrl = mailerConfig.androidUrl; + this.initiatePasswordChangeUrl = mailerConfig.initiatePasswordChangeUrl; + this.initiatePasswordResetUrl = mailerConfig.initiatePasswordResetUrl; + this.iosUrl = mailerConfig.iosUrl; + this.iosAdjustUrl = mailerConfig.iosAdjustUrl; + this.mailer = sender || nodemailer.createTransport(options); + this.emailService = sender || require('./email_service')(config); + this.passwordManagerInfoUrl = mailerConfig.passwordManagerInfoUrl; + this.passwordResetUrl = mailerConfig.passwordResetUrl; + this.privacyUrl = mailerConfig.privacyUrl; + this.reportSignInUrl = mailerConfig.reportSignInUrl; + this.revokeAccountRecoveryUrl = mailerConfig.revokeAccountRecoveryUrl; + this.createAccountRecoveryUrl = mailerConfig.createAccountRecoveryUrl; + this.sender = mailerConfig.sender; + this.sesConfigurationSet = mailerConfig.sesConfigurationSet; + this.supportUrl = mailerConfig.supportUrl; + this.syncUrl = mailerConfig.syncUrl; + this.templates = templates; + this.translator = translator.getTranslator; + this.verificationUrl = mailerConfig.verificationUrl; + this.verifyLoginUrl = mailerConfig.verifyLoginUrl; + this.verifySecondaryEmailUrl = mailerConfig.verifySecondaryEmailUrl; + this.verifyPrimaryEmailUrl = mailerConfig.verifyPrimaryEmailUrl; + this.prependVerificationSubdomain = mailerConfig.prependVerificationSubdomain; } Mailer.prototype.stop = function () { - this.mailer.close() - } + this.mailer.close(); + }; Mailer.prototype._supportLinkAttributes = function (templateName) { - return linkAttributes(this.createSupportLink(templateName)) - } + return linkAttributes(this.createSupportLink(templateName)); + }; Mailer.prototype._passwordResetLinkAttributes = function (email, templateName, emailToHashWith) { - return linkAttributes(this.createPasswordResetLink(email, templateName, emailToHashWith)) - } + return linkAttributes(this.createPasswordResetLink(email, templateName, emailToHashWith)); + }; Mailer.prototype._passwordChangeLinkAttributes = function (email, templateName) { - return linkAttributes(this.createPasswordChangeLink(email, templateName)) - } + return linkAttributes(this.createPasswordChangeLink(email, templateName)); + }; Mailer.prototype._formatUserAgentInfo = function (message) { // Build a first cut at a device description, // without using any new strings. // Future iterations can localize this better. - var translator = this.translator(message.acceptLanguage) - var uaBrowser = safeUserAgent.name(message.uaBrowser) - var uaOS = safeUserAgent.name(message.uaOS) - var uaOSVersion = safeUserAgent.version(message.uaOSVersion) + var translator = this.translator(message.acceptLanguage); + var uaBrowser = safeUserAgent.name(message.uaBrowser); + var uaOS = safeUserAgent.name(message.uaOS); + var uaOSVersion = safeUserAgent.version(message.uaOSVersion); if (uaBrowser && uaOS && uaOSVersion) { return translator.format(translator.gettext('%(uaBrowser)s on %(uaOS)s %(uaOSVersion)s'), - { uaBrowser: uaBrowser, uaOS: uaOS, uaOSVersion: uaOSVersion }) + { uaBrowser: uaBrowser, uaOS: uaOS, uaOSVersion: uaOSVersion }); } else if (uaBrowser && uaOS) { return translator.format(translator.gettext('%(uaBrowser)s on %(uaOS)s'), - { uaBrowser: uaBrowser, uaOS: uaOS }) + { uaBrowser: uaBrowser, uaOS: uaOS }); } else { if (uaBrowser) { - return uaBrowser + return uaBrowser; } else if (uaOS) { if (uaOSVersion) { - var parts = uaOS + ' ' + uaOSVersion - return parts + var parts = uaOS + ' ' + uaOSVersion; + return parts; } else { - return uaOS + return uaOS; } } else { - return '' + return ''; } } - } + }; Mailer.prototype._constructLocationString = function (message) { - var translator = this.translator(message.acceptLanguage) - var location = message.location + var translator = this.translator(message.acceptLanguage); + var location = message.location; // construct the location string from the location object if (location) { if (location.city && location.stateCode) { - return translator.format(translator.gettext('%(city)s, %(stateCode)s, %(country)s (estimated)'), location) + return translator.format(translator.gettext('%(city)s, %(stateCode)s, %(country)s (estimated)'), location); } else if (location.city) { - return translator.format(translator.gettext('%(city)s, %(country)s (estimated)'), location) + return translator.format(translator.gettext('%(city)s, %(country)s (estimated)'), location); } else if (location.stateCode) { - return translator.format(translator.gettext('%(stateCode)s, %(country)s (estimated)'), location) + return translator.format(translator.gettext('%(stateCode)s, %(country)s (estimated)'), location); } else { - return translator.format(translator.gettext('%(country)s (estimated)'), location) + return translator.format(translator.gettext('%(country)s (estimated)'), location); } } - return '' - } + return ''; + }; Mailer.prototype._constructLocalTimeString = function (timeZone, acceptLanguage) { - var translator = this.translator(acceptLanguage) - return constructLocalTimeString(timeZone, translator.language) - } + var translator = this.translator(acceptLanguage); + return constructLocalTimeString(timeZone, translator.language); + }; Mailer.prototype.localize = function (message) { - var translator = this.translator(message.acceptLanguage) + var translator = this.translator(message.acceptLanguage); var localized = this.templates[message.template](extend({ translator: translator - }, message.templateValues)) + }, message.templateValues)); return { html: localized.html, language: translator.language, subject: translator.gettext(message.subject), text: localized.text - } - } + }; + }; Mailer.prototype.send = function (message) { - log.trace(`mailer.${message.template}`, { email: message.email, uid: message.uid }) - const localized = this.localize(message) + log.trace(`mailer.${message.template}`, { email: message.email, uid: message.uid }); + const localized = this.localize(message); - const template = message.template - let templateVersion = TEMPLATE_VERSIONS[template] + const template = message.template; + let templateVersion = TEMPLATE_VERSIONS[template]; if (! templateVersion) { - log.error('emailTemplateVersion.missing', { template }) - templateVersion = 1 + log.error('emailTemplateVersion.missing', { template }); + templateVersion = 1; } - message.templateVersion = templateVersion + message.templateVersion = templateVersion; return this.selectEmailServices(message) .then(services => { @@ -292,27 +292,27 @@ module.exports = function (log, config, oauthdb) { optionalHeader('X-Flow-Begin-Time', message.flowBeginTime), optionalHeader('X-Service-Id', message.service), optionalHeader('X-Uid', message.uid) - ) + ); - const { mailer, emailAddresses, emailService, emailSender } = service + const { mailer, emailAddresses, emailService, emailSender } = service; // Set headers that let us attribute success/failure correctly - message.emailService = headers['X-Email-Service'] = emailService - message.emailSender = headers['X-Email-Sender'] = emailSender + message.emailService = headers['X-Email-Service'] = emailService; + message.emailSender = headers['X-Email-Sender'] = emailSender; if (this.sesConfigurationSet && emailSender === 'ses') { // Note on SES Event Publishing: The X-SES-CONFIGURATION-SET and // X-SES-MESSAGE-TAGS email headers will be stripped by SES from the // actual outgoing email messages. - headers[X_SES_CONFIGURATION_SET] = this.sesConfigurationSet - headers[X_SES_MESSAGE_TAGS] = sesMessageTagsHeaderValue(message.metricsTemplate || template, emailService) + headers[X_SES_CONFIGURATION_SET] = this.sesConfigurationSet; + headers[X_SES_MESSAGE_TAGS] = sesMessageTagsHeaderValue(message.metricsTemplate || template, emailService); } log.info('mailer.send', { email: emailAddresses[0], template, headers: Object.keys(headers).join(',') - }) + }); const emailConfig = { sender: this.sender, @@ -323,17 +323,17 @@ module.exports = function (log, config, oauthdb) { html: localized.html, xMailer: false, headers - } + }; if (emailAddresses.length > 1) { - emailConfig.cc = emailAddresses.slice(1) + emailConfig.cc = emailAddresses.slice(1); } if (emailService === 'fxa-email-service') { - emailConfig.provider = emailSender + emailConfig.provider = emailSender; } - const d = P.defer() + const d = P.defer(); mailer.sendMail(emailConfig, (err, status) => { if (err) { log.error('mailer.send.error', { @@ -344,9 +344,9 @@ module.exports = function (log, config, oauthdb) { to: emailConfig && emailConfig.to, emailSender, emailService - }) + }); - return d.reject(err) + return d.reject(err); } log.info('mailer.send.1', { @@ -355,17 +355,17 @@ module.exports = function (log, config, oauthdb) { to: emailConfig && emailConfig.to, emailSender, emailService - }) + }); - emailUtils.logEmailEventSent(log, Object.assign({}, message, { headers })) + emailUtils.logEmailEventSent(log, Object.assign({}, message, { headers })); - return d.resolve(status) - }) + return d.resolve(status); + }); - return d.promise - })) - }) - } + return d.promise; + })); + }); + }; // Based on the to and cc email addresses of a message, return an array of // `Service` objects that control how email traffic will be routed. @@ -422,9 +422,9 @@ module.exports = function (log, config, oauthdb) { // used for both metrics and sent as the // `provider` param in external requests. Mailer.prototype.selectEmailServices = function (message) { - const emailAddresses = [ message.email ] + const emailAddresses = [ message.email ]; if (Array.isArray(message.ccEmails)) { - emailAddresses.push(...message.ccEmails) + emailAddresses.push(...message.ccEmails); } return redis.get('config') @@ -432,50 +432,50 @@ module.exports = function (log, config, oauthdb) { .then(liveConfig => { if (liveConfig) { try { - liveConfig = JSON.parse(liveConfig) + liveConfig = JSON.parse(liveConfig); } catch (err) { - log.error('emailConfig.parse.error', { err: err.message }) + log.error('emailConfig.parse.error', { err: err.message }); } } return emailAddresses.reduce((promise, emailAddress) => { - let services, isMatched + let services, isMatched; return promise .then(s => { - services = s + services = s; if (liveConfig) { return [ 'sendgrid', 'socketlabs', 'ses' ].reduce((promise, key) => { - const senderConfig = liveConfig[key] + const senderConfig = liveConfig[key]; return promise .then(() => { if (senderConfig) { - return isLiveConfigMatch(senderConfig, emailAddress) + return isLiveConfigMatch(senderConfig, emailAddress); } }) .then(result => { if (isMatched) { - return + return; } - isMatched = result + isMatched = result; if (isMatched) { upsertServicesMap(services, SERVICES.external[key], emailAddress, { mailer: this.emailService, emailService: 'fxa-email-service', emailSender: key - }) + }); } - }) - }, promise) + }); + }, promise); } }) .then(() => { if (isMatched) { - return services + return services; } if (config.emailService.forcedEmailAddresses.test(emailAddress)) { @@ -483,89 +483,89 @@ module.exports = function (log, config, oauthdb) { mailer: this.emailService, emailService: 'fxa-email-service', emailSender: 'ses' - }) + }); } return upsertServicesMap(services, SERVICES.internal, emailAddress, { mailer: this.mailer, emailService: 'fxa-auth-server', emailSender: 'ses' - }) - }) - }, P.resolve(new Map())) + }); + }); + }, P.resolve(new Map())); }) - .then(services => Array.from(services.values())) + .then(services => Array.from(services.values())); function isLiveConfigMatch (liveConfig, emailAddress) { return new P(resolve => { - const { percentage, regex } = liveConfig + const { percentage, regex } = liveConfig; if (percentage >= 0 && percentage < 100 && Math.floor(Math.random() * 100) >= percentage) { - resolve(false) - return + resolve(false); + return; } if (regex) { if (regex.indexOf('"') !== -1 || emailAddress.indexOf('"') !== -1 || ! safeRegex(regex)) { - resolve(false) - return + resolve(false); + return; } // Execute the regex inside a sandbox and kill it if it takes > 100 ms - const sandbox = new Sandbox({ timeout: 100 }) + const sandbox = new Sandbox({ timeout: 100 }); sandbox.run(`new RegExp("${regex}").test("${emailAddress}")`, output => { - resolve(output.result === 'true') - }) - return + resolve(output.result === 'true'); + }); + return; } - resolve(true) - }) + resolve(true); + }); } function upsertServicesMap (services, service, emailAddress, data) { if (services.has(service)) { - services.get(service).emailAddresses.push(emailAddress) + services.get(service).emailAddresses.push(emailAddress); } else { services.set(service, Object.assign({ emailAddresses: [ emailAddress ] - }, data)) + }, data)); } - return services + return services; } - } + }; Mailer.prototype.verifyEmail = async function (message) { - log.trace('mailer.verifyEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.verifyEmail', { email: message.email, uid: message.uid }); - var templateName = 'verifyEmail' - const metricsTemplateName = templateName - var subject = gettext('Verify your Firefox Account') + var templateName = 'verifyEmail'; + const metricsTemplateName = templateName; + var subject = gettext('Verify your Firefox Account'); var query = { uid: message.uid, code: message.code - } + }; - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } - var links = this._generateLinks(this.verificationUrl, message.email, query, templateName) + var links = this._generateLinks(this.verificationUrl, message.email, query, templateName); var headers = { 'X-Link': links.link, 'X-Verify-Code': message.code - } + }; - let serviceName + let serviceName; if (message.service === 'sync') { - subject = gettext('Confirm your email and start to sync!') - templateName = 'verifySyncEmail' + subject = gettext('Confirm your email and start to sync!'); + templateName = 'verifySyncEmail'; } else if (message.service) { - const clientInfo = await oauthClientInfo.fetch(message.service) - serviceName = clientInfo.name + const clientInfo = await oauthClientInfo.fetch(message.service); + serviceName = clientInfo.name; } return this.send(Object.assign({}, message, { @@ -586,25 +586,25 @@ module.exports = function (log, config, oauthdb) { supportLinkAttributes: links.supportLinkAttributes }, metricsTemplate: metricsTemplateName - })) - } + })); + }; Mailer.prototype.unblockCodeEmail = function (message) { - log.trace('mailer.unblockCodeEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.unblockCodeEmail', { email: message.email, uid: message.uid }); - var templateName = 'unblockCodeEmail' + var templateName = 'unblockCodeEmail'; var query = { unblockCode: message.unblockCode, email: message.email, uid: message.uid - } + }; - var links = this._generateLinks(null, message.email, query, templateName) + var links = this._generateLinks(null, message.email, query, templateName); var headers = { 'X-Unblock-Code': message.unblockCode, 'X-Report-SignIn-Link': links.reportSignInLink - } + }; return this.send(Object.assign({}, message, { headers, @@ -621,35 +621,35 @@ module.exports = function (log, config, oauthdb) { timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage), unblockCode: message.unblockCode } - })) - } + })); + }; Mailer.prototype.verifyLoginEmail = function (message) { - log.trace('mailer.verifyLoginEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.verifyLoginEmail', { email: message.email, uid: message.uid }); - var templateName = 'verifyLoginEmail' + var templateName = 'verifyLoginEmail'; var query = { code: message.code, uid: message.uid - } - var translator = this.translator(message.acceptLanguage) + }; + var translator = this.translator(message.acceptLanguage); - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } - var links = this._generateLinks(this.verifyLoginUrl, message.email, query, templateName) + var links = this._generateLinks(this.verifyLoginUrl, message.email, query, templateName); var headers = { 'X-Link': links.link, 'X-Verify-Code': message.code - } + }; return oauthClientInfo.fetch(message.service).then((clientInfo) => { - const clientName = clientInfo.name + const clientName = clientInfo.name; const subject = translator.format(translator.gettext('Confirm new sign-in to %(clientName)s'), { clientName: clientName - }) + }); return this.send(Object.assign({}, message, { headers, @@ -670,29 +670,29 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - }) + })); + }); - } + }; Mailer.prototype.verifyLoginCodeEmail = function (message) { - log.trace('mailer.verifyLoginCodeEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.verifyLoginCodeEmail', { email: message.email, uid: message.uid }); - var templateName = 'verifyLoginCodeEmail' + var templateName = 'verifyLoginCodeEmail'; var query = { code: message.code, uid: message.uid - } + }; - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } - var links = this._generateLinks(this.verifyLoginUrl, message.email, query, templateName) + var links = this._generateLinks(this.verifyLoginUrl, message.email, query, templateName); var headers = { 'X-Signin-Verify-Code': message.code - } + }; return this.send(Object.assign({}, message, { headers, @@ -711,30 +711,30 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.verifyPrimaryEmail = function (message) { - log.trace('mailer.verifyPrimaryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.verifyPrimaryEmail', { email: message.email, uid: message.uid }); - const templateName = 'verifyPrimaryEmail' + const templateName = 'verifyPrimaryEmail'; const query = { code: message.code, uid: message.uid, type: 'primary', primary_email_verified: message.email - } + }; - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } - const links = this._generateLinks(this.verifyPrimaryEmailUrl, message.email, query, templateName) + const links = this._generateLinks(this.verifyPrimaryEmailUrl, message.email, query, templateName); const headers = { 'X-Link': links.link, 'X-Verify-Code': message.code - } + }; return this.send(Object.assign({}, message, { headers, @@ -754,30 +754,30 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.verifySecondaryEmail = function (message) { - log.trace('mailer.verifySecondaryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.verifySecondaryEmail', { email: message.email, uid: message.uid }); - var templateName = 'verifySecondaryEmail' + var templateName = 'verifySecondaryEmail'; var query = { code: message.code, uid: message.uid, type: 'secondary', secondary_email_verified: message.email - } + }; - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } - var links = this._generateLinks(this.verifySecondaryEmailUrl, message.email, query, templateName) + var links = this._generateLinks(this.verifySecondaryEmailUrl, message.email, query, templateName); var headers = { 'X-Link': links.link, 'X-Verify-Code': message.code - } + }; return this.send(Object.assign({}, message, { headers, @@ -800,28 +800,28 @@ module.exports = function (log, config, oauthdb) { timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage), primaryEmail: message.primaryEmail } - })) - } + })); + }; Mailer.prototype.recoveryEmail = function (message) { - var templateName = 'recoveryEmail' + var templateName = 'recoveryEmail'; var query = { uid: message.uid, token: message.token, code: message.code, email: message.email - } - if (message.service) { query.service = message.service } - if (message.redirectTo) { query.redirectTo = message.redirectTo } - if (message.resume) { query.resume = message.resume } - if (message.emailToHashWith) { query.emailToHashWith = message.emailToHashWith } + }; + if (message.service) { query.service = message.service; } + if (message.redirectTo) { query.redirectTo = message.redirectTo; } + if (message.resume) { query.resume = message.resume; } + if (message.emailToHashWith) { query.emailToHashWith = message.emailToHashWith; } - var links = this._generateLinks(this.passwordResetUrl, message.email, query, templateName) + var links = this._generateLinks(this.passwordResetUrl, message.email, query, templateName); var headers = { 'X-Link': links.link, 'X-Recovery-Code': message.code - } + }; return this.send(Object.assign({}, message, { headers, @@ -839,17 +839,17 @@ module.exports = function (log, config, oauthdb) { supportLinkAttributes: links.supportLinkAttributes, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.passwordChangedEmail = function (message) { - var templateName = 'passwordChangedEmail' + var templateName = 'passwordChangedEmail'; - var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName) + var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName); var headers = { 'X-Link': links.resetLink - } + }; return this.send(Object.assign({}, message, { headers, @@ -866,16 +866,16 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.passwordResetEmail = function (message) { - var templateName = 'passwordResetEmail' - var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName) + var templateName = 'passwordResetEmail'; + var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName); var headers = { 'X-Link': links.resetLink - } + }; return this.send(Object.assign({}, message, { headers, @@ -888,16 +888,16 @@ module.exports = function (log, config, oauthdb) { supportLinkAttributes: links.supportLinkAttributes, supportUrl: links.supportUrl } - })) - } + })); + }; Mailer.prototype.passwordResetRequiredEmail = function (message) { - var templateName = 'passwordResetRequiredEmail' - var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName) + var templateName = 'passwordResetRequiredEmail'; + var links = this._generateLinks(this.initiatePasswordResetUrl, message.email, {}, templateName); var headers = { 'X-Link': links.resetLink - } + }; return this.send(Object.assign({}, message, { headers, @@ -908,24 +908,24 @@ module.exports = function (log, config, oauthdb) { privacyUrl: links.privacyUrl, resetLink: links.resetLink } - })) - } + })); + }; Mailer.prototype.newDeviceLoginEmail = function (message) { - log.trace('mailer.newDeviceLoginEmail', { email: message.email, uid: message.uid }) - var templateName = 'newDeviceLoginEmail' - var links = this._generateSettingLinks(message, templateName) - var translator = this.translator(message.acceptLanguage) + log.trace('mailer.newDeviceLoginEmail', { email: message.email, uid: message.uid }); + var templateName = 'newDeviceLoginEmail'; + var links = this._generateSettingLinks(message, templateName); + var translator = this.translator(message.acceptLanguage); var headers = { 'X-Link': links.passwordChangeLink - } + }; return oauthClientInfo.fetch(message.service).then((clientInfo) => { - const clientName = clientInfo.name + const clientName = clientInfo.name; const subject = translator.format(translator.gettext('New sign-in to %(clientName)s'), { clientName: clientName - }) + }); return this.send(Object.assign({}, message, { headers, @@ -944,20 +944,20 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - }) + })); + }); - } + }; Mailer.prototype.postVerifyEmail = function (message) { - log.trace('mailer.postVerifyEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postVerifyEmail', { email: message.email, uid: message.uid }); - var templateName = 'postVerifyEmail' - var links = this._generateLinks(this.syncUrl, message.email, {}, templateName) + var templateName = 'postVerifyEmail'; + var links = this._generateLinks(this.syncUrl, message.email, {}, templateName); var headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -973,18 +973,18 @@ module.exports = function (log, config, oauthdb) { supportUrl: links.supportUrl, supportLinkAttributes: links.supportLinkAttributes } - })) - } + })); + }; Mailer.prototype.postVerifySecondaryEmail = function (message) { - log.trace('mailer.postVerifySecondaryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postVerifySecondaryEmail', { email: message.email, uid: message.uid }); - var templateName = 'postVerifySecondaryEmail' - var links = this._generateSettingLinks(message, templateName) + var templateName = 'postVerifySecondaryEmail'; + var links = this._generateSettingLinks(message, templateName); var headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1001,18 +1001,18 @@ module.exports = function (log, config, oauthdb) { secondaryEmail: message.secondaryEmail, supportLinkAttributes: links.supportLinkAttributes } - })) - } + })); + }; Mailer.prototype.postChangePrimaryEmail = function (message) { - log.trace('mailer.postChangePrimaryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postChangePrimaryEmail', { email: message.email, uid: message.uid }); - const templateName = 'postChangePrimaryEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postChangePrimaryEmail'; + const links = this._generateSettingLinks(message, templateName); var headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1029,18 +1029,18 @@ module.exports = function (log, config, oauthdb) { email: message.email, supportLinkAttributes: links.supportLinkAttributes } - })) - } + })); + }; Mailer.prototype.postRemoveSecondaryEmail = function (message) { - log.trace('mailer.postRemoveSecondaryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postRemoveSecondaryEmail', { email: message.email, uid: message.uid }); - const templateName = 'postRemoveSecondaryEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postRemoveSecondaryEmail'; + const links = this._generateSettingLinks(message, templateName); var headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1055,18 +1055,18 @@ module.exports = function (log, config, oauthdb) { secondaryEmail: message.secondaryEmail, supportLinkAttributes: links.supportLinkAttributes } - })) - } + })); + }; Mailer.prototype.postAddTwoStepAuthenticationEmail = function (message) { - log.trace('mailer.postAddTwoStepAuthenticationEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postAddTwoStepAuthenticationEmail', { email: message.email, uid: message.uid }); - const templateName = 'postAddTwoStepAuthenticationEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postAddTwoStepAuthenticationEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1087,18 +1087,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.postRemoveTwoStepAuthenticationEmail = function (message) { - log.trace('mailer.postRemoveTwoStepAuthenticationEmail', { email: message.email, uid: message.uid}) + log.trace('mailer.postRemoveTwoStepAuthenticationEmail', { email: message.email, uid: message.uid}); - const templateName = 'postRemoveTwoStepAuthenticationEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postRemoveTwoStepAuthenticationEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1119,18 +1119,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.postNewRecoveryCodesEmail = function (message) { - log.trace('mailer.postNewRecoveryCodesEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postNewRecoveryCodesEmail', { email: message.email, uid: message.uid }); - const templateName = 'postNewRecoveryCodesEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postNewRecoveryCodesEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1151,18 +1151,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.postConsumeRecoveryCodeEmail = function (message) { - log.trace('mailer.postConsumeRecoveryCodeEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postConsumeRecoveryCodeEmail', { email: message.email, uid: message.uid }); - const templateName = 'postConsumeRecoveryCodeEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postConsumeRecoveryCodeEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1183,18 +1183,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.lowRecoveryCodesEmail = function (message) { - log.trace('mailer.lowRecoveryCodesEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.lowRecoveryCodesEmail', { email: message.email, uid: message.uid }); - const templateName = 'lowRecoveryCodesEmail' - const links = this._generateLowRecoveryCodesLinks(message, templateName) + const templateName = 'lowRecoveryCodesEmail'; + const links = this._generateLowRecoveryCodesLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1211,18 +1211,18 @@ module.exports = function (log, config, oauthdb) { email: message.email, supportLinkAttributes: links.supportLinkAttributes } - })) - } + })); + }; Mailer.prototype.postAddAccountRecoveryEmail = function (message) { - log.trace('mailer.postAddAccountRecoveryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postAddAccountRecoveryEmail', { email: message.email, uid: message.uid }); - const templateName = 'postAddAccountRecoveryEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postAddAccountRecoveryEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1245,18 +1245,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.postRemoveAccountRecoveryEmail = function (message) { - log.trace('mailer.postRemoveAccountRecoveryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.postRemoveAccountRecoveryEmail', { email: message.email, uid: message.uid }); - const templateName = 'postRemoveAccountRecoveryEmail' - const links = this._generateSettingLinks(message, templateName) + const templateName = 'postRemoveAccountRecoveryEmail'; + const links = this._generateSettingLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1277,18 +1277,18 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype.passwordResetAccountRecoveryEmail = function (message) { - log.trace('mailer.passwordResetAccountRecoveryEmail', { email: message.email, uid: message.uid }) + log.trace('mailer.passwordResetAccountRecoveryEmail', { email: message.email, uid: message.uid }); - const templateName = 'passwordResetAccountRecoveryEmail' - const links = this._generateCreateAccountRecoveryLinks(message, templateName) + const templateName = 'passwordResetAccountRecoveryEmail'; + const links = this._generateCreateAccountRecoveryLinks(message, templateName); const headers = { 'X-Link': links.link - } + }; return this.send(Object.assign({}, message, { headers, @@ -1309,160 +1309,160 @@ module.exports = function (log, config, oauthdb) { location: this._constructLocationString(message), timestamp: this._constructLocalTimeString(message.timeZone, message.acceptLanguage) } - })) - } + })); + }; Mailer.prototype._generateUTMLink = function (link, query, templateName, content) { - var parsedLink = url.parse(link) + var parsedLink = url.parse(link); // Extract current params from link, passed in query params will override any param in a link - var parsedQuery = qs.parse(parsedLink.query) + var parsedQuery = qs.parse(parsedLink.query); Object.keys(query).forEach(function (key) { - parsedQuery[key] = query[key] - }) + parsedQuery[key] = query[key]; + }); - parsedQuery['utm_medium'] = 'email' + parsedQuery['utm_medium'] = 'email'; - var campaign = templateNameToCampaignMap[templateName] + var campaign = templateNameToCampaignMap[templateName]; if (campaign && ! parsedQuery['utm_campaign']) { - parsedQuery['utm_campaign'] = UTM_PREFIX + campaign + parsedQuery['utm_campaign'] = UTM_PREFIX + campaign; } if (content) { - parsedQuery['utm_content'] = UTM_PREFIX + content + parsedQuery['utm_content'] = UTM_PREFIX + content; } - parsedLink.query = parsedQuery + parsedLink.query = parsedQuery; - const isAccountOrEmailVerification = link === this.verificationUrl || link === this.verifyLoginUrl + const isAccountOrEmailVerification = link === this.verificationUrl || link === this.verifyLoginUrl; if (this.prependVerificationSubdomain.enabled && isAccountOrEmailVerification) { - parsedLink.host = `${this.prependVerificationSubdomain.subdomain}.${parsedLink.host}` + parsedLink.host = `${this.prependVerificationSubdomain.subdomain}.${parsedLink.host}`; } - return url.format(parsedLink) - } + return url.format(parsedLink); + }; Mailer.prototype._generateLinks = function (primaryLink, email, query, templateName) { // Generate all possible links. The option to use a specific link // is left up to the template. - var links = {} + var links = {}; - var utmContent = templateNameToContentMap[templateName] + var utmContent = templateNameToContentMap[templateName]; if (primaryLink && utmContent) { - links['link'] = this._generateUTMLink(primaryLink, query, templateName, utmContent) + links['link'] = this._generateUTMLink(primaryLink, query, templateName, utmContent); } - links['privacyUrl'] = this.createPrivacyLink(templateName) + links['privacyUrl'] = this.createPrivacyLink(templateName); - links['supportLinkAttributes'] = this._supportLinkAttributes(templateName) - links['supportUrl'] = this.createSupportLink(templateName) + links['supportLinkAttributes'] = this._supportLinkAttributes(templateName); + links['supportUrl'] = this.createSupportLink(templateName); - links['passwordChangeLink'] = this.createPasswordChangeLink(email, templateName) - links['passwordChangeLinkAttributes'] = this._passwordChangeLinkAttributes(email, templateName) + links['passwordChangeLink'] = this.createPasswordChangeLink(email, templateName); + links['passwordChangeLinkAttributes'] = this._passwordChangeLinkAttributes(email, templateName); - links['resetLink'] = this.createPasswordResetLink(email, templateName, query.emailToHashWith) - links['resetLinkAttributes'] = this._passwordResetLinkAttributes(email, templateName, query.emailToHashWith) + links['resetLink'] = this.createPasswordResetLink(email, templateName, query.emailToHashWith); + links['resetLinkAttributes'] = this._passwordResetLinkAttributes(email, templateName, query.emailToHashWith); - links['androidLink'] = this._generateUTMLink(this.androidUrl, query, templateName, 'connect-android') - links['iosLink'] = this._generateUTMLink(this.iosUrl, query, templateName, 'connect-ios') + links['androidLink'] = this._generateUTMLink(this.androidUrl, query, templateName, 'connect-android'); + links['iosLink'] = this._generateUTMLink(this.iosUrl, query, templateName, 'connect-ios'); - links['passwordManagerInfoUrl'] = this._generateUTMLink(this.passwordManagerInfoUrl, query, templateName, 'password-info') + links['passwordManagerInfoUrl'] = this._generateUTMLink(this.passwordManagerInfoUrl, query, templateName, 'password-info'); - links['reportSignInLink'] = this.createReportSignInLink(templateName, query) - links['reportSignInLinkAttributes'] = this._reportSignInLinkAttributes(email, templateName, query) + links['reportSignInLink'] = this.createReportSignInLink(templateName, query); + links['reportSignInLinkAttributes'] = this._reportSignInLinkAttributes(email, templateName, query); - links['revokeAccountRecoveryLink'] = this.createRevokeAccountRecoveryLink(templateName) - links['revokeAccountRecoveryLinkAttributes'] = this._revokeAccountRecoveryLinkAttributes(templateName) + links['revokeAccountRecoveryLink'] = this.createRevokeAccountRecoveryLink(templateName); + links['revokeAccountRecoveryLinkAttributes'] = this._revokeAccountRecoveryLinkAttributes(templateName); - links['createAccountRecoveryLink'] = this.createAccountRecoveryLink(templateName) + links['createAccountRecoveryLink'] = this.createAccountRecoveryLink(templateName); - var queryOneClick = extend(query, {one_click: true}) + var queryOneClick = extend(query, {one_click: true}); if (primaryLink && utmContent) { - links['oneClickLink'] = this._generateUTMLink(primaryLink, queryOneClick, templateName, utmContent + '-oneclick') + links['oneClickLink'] = this._generateUTMLink(primaryLink, queryOneClick, templateName, utmContent + '-oneclick'); } - return links - } + return links; + }; Mailer.prototype._generateSettingLinks = function (message, templateName) { // Generate all possible links where the primary link is `accountSettingsUrl`. - const query = {} - if (message.email) {query.email = message.email} - if (message.uid) {query.uid = message.uid} + const query = {}; + if (message.email) {query.email = message.email;} + if (message.uid) {query.uid = message.uid;} - return this._generateLinks(this.accountSettingsUrl, message.email, query, templateName) - } + return this._generateLinks(this.accountSettingsUrl, message.email, query, templateName); + }; Mailer.prototype._generateLowRecoveryCodesLinks = function (message, templateName) { // Generate all possible links where the primary link is `accountRecoveryCodesUrl`. - const query = {low_recovery_codes: true} - if (message.email) {query.email = message.email} - if (message.uid) {query.uid = message.uid} + const query = {low_recovery_codes: true}; + if (message.email) {query.email = message.email;} + if (message.uid) {query.uid = message.uid;} - return this._generateLinks(this.accountRecoveryCodesUrl, message.email, query, templateName) - } + return this._generateLinks(this.accountRecoveryCodesUrl, message.email, query, templateName); + }; Mailer.prototype._generateCreateAccountRecoveryLinks = function (message, templateName) { // Generate all possible links where the primary link is `createAccountRecoveryUrl`. - const query = {} - if (message.email) {query.email = message.email} - if (message.uid) {query.uid = message.uid} + const query = {}; + if (message.email) {query.email = message.email;} + if (message.uid) {query.uid = message.uid;} - return this._generateLinks(this.createAccountRecoveryUrl, message.email, query, templateName) - } + return this._generateLinks(this.createAccountRecoveryUrl, message.email, query, templateName); + }; Mailer.prototype.createPasswordResetLink = function (email, templateName, emailToHashWith) { // Default `reset_password_confirm` to false, to show warnings about // resetting password and sync data - var query = { email: email, reset_password_confirm: false, email_to_hash_with : emailToHashWith} + var query = { email: email, reset_password_confirm: false, email_to_hash_with : emailToHashWith}; - return this._generateUTMLink(this.initiatePasswordResetUrl, query, templateName, 'reset-password') - } + return this._generateUTMLink(this.initiatePasswordResetUrl, query, templateName, 'reset-password'); + }; Mailer.prototype.createPasswordChangeLink = function (email, templateName) { - var query = {email: email} + var query = {email: email}; - return this._generateUTMLink(this.initiatePasswordChangeUrl, query, templateName, 'change-password') - } + return this._generateUTMLink(this.initiatePasswordChangeUrl, query, templateName, 'change-password'); + }; Mailer.prototype.createReportSignInLink = function (templateName, data) { var query = { uid: data.uid, unblockCode: data.unblockCode - } - return this._generateUTMLink(this.reportSignInUrl, query, templateName, 'report') - } + }; + return this._generateUTMLink(this.reportSignInUrl, query, templateName, 'report'); + }; Mailer.prototype._reportSignInLinkAttributes = function (email, templateName, query) { - return linkAttributes(this.createReportSignInLink(templateName, query)) - } + return linkAttributes(this.createReportSignInLink(templateName, query)); + }; Mailer.prototype.createSupportLink = function (templateName) { - return this._generateUTMLink(this.supportUrl, {}, templateName, 'support') - } + return this._generateUTMLink(this.supportUrl, {}, templateName, 'support'); + }; Mailer.prototype.createPrivacyLink = function (templateName) { - return this._generateUTMLink(this.privacyUrl, {}, templateName, 'privacy') - } + return this._generateUTMLink(this.privacyUrl, {}, templateName, 'privacy'); + }; Mailer.prototype.createRevokeAccountRecoveryLink = function (templateName) { - return this._generateUTMLink(this.revokeAccountRecoveryUrl, {}, templateName, 'report') - } + return this._generateUTMLink(this.revokeAccountRecoveryUrl, {}, templateName, 'report'); + }; Mailer.prototype._revokeAccountRecoveryLinkAttributes = function (templateName) { - return linkAttributes(this.createRevokeAccountRecoveryLink(templateName)) - } + return linkAttributes(this.createRevokeAccountRecoveryLink(templateName)); + }; Mailer.prototype.createAccountRecoveryLink = function (templateName) { - return this._generateUTMLink(this.createAccountRecoveryUrl, {}, templateName) - } + return this._generateUTMLink(this.createAccountRecoveryUrl, {}, templateName); + }; - return Mailer -} + return Mailer; +}; function optionalHeader (key, value) { if (value) { - return { [key]: value } + return { [key]: value }; } } diff --git a/lib/senders/email_service.js b/lib/senders/email_service.js index 77853951..74eb2f19 100644 --- a/lib/senders/email_service.js +++ b/lib/senders/email_service.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; -const request = require('request') -const error = require('../error') +const request = require('request'); +const error = require('../error'); const ERRNO = { // From fxa-email-service, src/app_errors/mod.rs COMPLAINT: 106, SOFT_BOUNCE: 107, HARD_BOUNCE: 108 -} +}; module.exports = (config) => { function sendMail(emailConfig, cb) { // Email service requires that all headers are strings. - const headers = {} + const headers = {}; for (const header in emailConfig.headers) { // Check to make sure header is not null. Issue #2771 if (emailConfig.headers[header]) { - headers[header] = emailConfig.headers[header].toString() + headers[header] = emailConfig.headers[header].toString(); } } const options = { @@ -38,42 +38,42 @@ module.exports = (config) => { html: emailConfig.html } } - } + }; if (emailConfig.provider) { - options.body.provider = emailConfig.provider + options.body.provider = emailConfig.provider; } request(options, function(err, res, body) { if (! err && res.statusCode >= 400) { - err = marshallError(res.statusCode, body) + err = marshallError(res.statusCode, body); } cb(err, { messageId: body && body.messageId, message: body && body.message - }) - }) + }); + }); } function marshallError (status, body) { if (status === 429) { // Error structure is changing in mozilla/fxa-email-service#198, // temporarily handle both formats - return marshallBounceError(body.errno, body.bouncedAt || body.time) + return marshallBounceError(body.errno, body.bouncedAt || body.time); } - return error.unexpectedError() + return error.unexpectedError(); } function marshallBounceError (errno, bouncedAt) { switch (errno) { case ERRNO.COMPLAINT: - return error.emailComplaint(bouncedAt) + return error.emailComplaint(bouncedAt); case ERRNO.SOFT_BOUNCE: - return error.emailBouncedSoft(bouncedAt) + return error.emailBouncedSoft(bouncedAt); case ERRNO.HARD_BOUNCE: default: - return error.emailBouncedHard(bouncedAt) + return error.emailBouncedHard(bouncedAt); } } @@ -84,5 +84,5 @@ module.exports = (config) => { return { sendMail, close - } -} + }; +}; diff --git a/lib/senders/index.js b/lib/senders/index.js index b416660a..baea2911 100644 --- a/lib/senders/index.js +++ b/lib/senders/index.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const createMailer = require('./email') -const createSms = require('./sms') -const P = require('../promise') +const createMailer = require('./email'); +const createSms = require('./sms'); +const P = require('../promise'); module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { - const defaultLanguage = config.i18n.defaultLanguage + const defaultLanguage = config.i18n.defaultLanguage; function createSenders() { - const Mailer = createMailer(log, config, oauthdb) + const Mailer = createMailer(log, config, oauthdb); return require('./templates').init() .then(function (templates) { return { email: new Mailer(translator, templates, config.smtp, sender), sms: createSms(log, translator, templates, config) - } - }) + }; + }); } return createSenders() .then(function (senders) { - const ungatedMailer = senders.email + const ungatedMailer = senders.email; function getSafeMailer(email) { return bounces.check(email) @@ -32,41 +32,41 @@ module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { .catch(function (e) { const info = { errno: e.errno - } - const bouncedAt = e.output && e.output.payload && e.output.payload.bouncedAt + }; + const bouncedAt = e.output && e.output.payload && e.output.payload.bouncedAt; if (bouncedAt) { - info.bouncedAt = bouncedAt + info.bouncedAt = bouncedAt; } - log.info('mailer.blocked', info) - throw e - }) + log.info('mailer.blocked', info); + throw e; + }); } function getSafeMailerWithEmails(emails) { - let ungatedPrimaryEmail - const ungatedCcEmails = [] - const gatedEmailErrors = [] + let ungatedPrimaryEmail; + const ungatedCcEmails = []; + const gatedEmailErrors = []; return P.filter(emails, (email) => { // We will only send to primary, or verified secondary. - return email.isPrimary || email.isVerified + return email.isPrimary || email.isVerified; }).then((emails) => { if (emails.length === 0) { // No emails we can even attempt to send to? Should never happen! - throw new Error('Empty list of sendable email addresses') + throw new Error('Empty list of sendable email addresses'); } - return emails + return emails; }).each((email) => { // We only send to addresses that are not gated, to protect our sender score. return getSafeMailer(email.email).then(() => { if (email.isPrimary) { - ungatedPrimaryEmail = email.email + ungatedPrimaryEmail = email.email; } else { - ungatedCcEmails.push(email.email) + ungatedCcEmails.push(email.email); } }, (err) => { - gatedEmailErrors.push(err) - }) + gatedEmailErrors.push(err); + }); }).then(() => { if (! ungatedPrimaryEmail) { // This user is having a bad time, their primary email is bouncing. @@ -74,63 +74,63 @@ module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { if (ungatedCcEmails.length === 0) { // Nope. Block the send, using first error reported. // Since we always check at least one email, there will be at least one error here. - throw gatedEmailErrors[0] + throw gatedEmailErrors[0]; } - ungatedPrimaryEmail = ungatedCcEmails.shift() + ungatedPrimaryEmail = ungatedCcEmails.shift(); } return { ungatedMailer: ungatedMailer, ungatedPrimaryEmail: ungatedPrimaryEmail, ungatedCcEmails: ungatedCcEmails - } - }) + }; + }); } senders.email = { sendVerifyCode: function (emails, account, opts) { - const primaryEmail = account.email + const primaryEmail = account.email; return getSafeMailer(primaryEmail) .then(function (mailer) { return mailer.verifyEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, email: primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendVerifyLoginEmail: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.verifyLoginEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendVerifyLoginCodeEmail: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.verifyLoginCodeEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendVerifyPrimaryEmail: function (emails, account, opts) { - const primaryEmail = account.email + const primaryEmail = account.email; return getSafeMailer(primaryEmail) .then(function (mailer) { @@ -138,12 +138,12 @@ module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { acceptLanguage: opts.acceptLanguage || defaultLanguage, email: primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendVerifySecondaryEmail: function (emails, account, opts) { - const primaryEmail = account.email - const verifyEmailAddress = emails[0].email + const primaryEmail = account.email; + const verifyEmailAddress = emails[0].email; return getSafeMailer(primaryEmail) .then(function (mailer) { @@ -152,15 +152,15 @@ module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { email: verifyEmailAddress, primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendRecoveryCode: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.recoveryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, @@ -168,236 +168,236 @@ module.exports = (log, config, error, bounces, translator, oauthdb, sender) => { email: primaryEmail, emailToHashWith: account.email, token: opts.token.data - })) - }) + })); + }); }, sendPasswordChangedNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.passwordChangedEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPasswordResetNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.passwordResetEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendNewDeviceLoginNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.newDeviceLoginEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostVerifyEmail: function (emails, account, opts) { - const primaryEmail = account.email + const primaryEmail = account.email; return getSafeMailer(primaryEmail) .then(function (mailer) { return mailer.postVerifyEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, email: primaryEmail - })) - }) + })); + }); }, sendPostRemoveSecondaryEmail: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postRemoveSecondaryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostVerifySecondaryEmail: function (emails, account, opts) { - const primaryEmail = account.primaryEmail.email + const primaryEmail = account.primaryEmail.email; return getSafeMailer(primaryEmail) .then(function (mailer) { return mailer.postVerifySecondaryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, email: primaryEmail - })) - }) + })); + }); }, sendPostChangePrimaryEmail: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postChangePrimaryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostNewRecoveryCodesNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postNewRecoveryCodesEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostConsumeRecoveryCodeNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postConsumeRecoveryCodeEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendLowRecoveryCodeNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.lowRecoveryCodesEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendUnblockCode: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.unblockCodeEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail, uid: account.uid - })) - }) + })); + }); }, sendPostAddTwoStepAuthNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postAddTwoStepAuthenticationEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostRemoveTwoStepAuthNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postRemoveTwoStepAuthenticationEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostAddAccountRecoveryNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postAddAccountRecoveryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPostRemoveAccountRecoveryNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.postRemoveAccountRecoveryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, sendPasswordResetAccountRecoveryNotification: function (emails, account, opts) { return getSafeMailerWithEmails(emails) .then(function (result) { - const mailer = result.ungatedMailer - const primaryEmail = result.ungatedPrimaryEmail - const ccEmails = result.ungatedCcEmails + const mailer = result.ungatedMailer; + const primaryEmail = result.ungatedPrimaryEmail; + const ccEmails = result.ungatedCcEmails; return mailer.passwordResetAccountRecoveryEmail(Object.assign({}, opts, { acceptLanguage: opts.acceptLanguage || defaultLanguage, ccEmails, email: primaryEmail - })) - }) + })); + }); }, translator: function () { - return ungatedMailer.translator.apply(ungatedMailer, arguments) + return ungatedMailer.translator.apply(ungatedMailer, arguments); }, stop: function () { - return ungatedMailer.stop() + return ungatedMailer.stop(); }, _ungatedMailer: ungatedMailer - } - return senders - }) -} + }; + return senders; + }); +}; diff --git a/lib/senders/legacy_log.js b/lib/senders/legacy_log.js index 61ae702a..049995a4 100644 --- a/lib/senders/legacy_log.js +++ b/lib/senders/legacy_log.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/. */ -'use strict' +'use strict'; /** * This module converts old logging messages to mozlog @@ -13,22 +13,22 @@ module.exports = function (log) { return { trace: function (data) { - log.debug(data.op, data) + log.debug(data.op, data); }, error: function (data) { - log.error(data.op, data) + log.error(data.op, data); }, fatal: function (data) { - log.critical(data.op, data) + log.critical(data.op, data); }, warn: function (data) { - log.warn(data.op, data) + log.warn(data.op, data); }, info: function (data) { - log.info(data.op, data) + log.info(data.op, data); }, amplitudeEvent: function (data) { - log.info(data.op, data) + log.info(data.op, data); } - } -} + }; +}; diff --git a/lib/senders/log.js b/lib/senders/log.js index b71e8315..9df43373 100644 --- a/lib/senders/log.js +++ b/lib/senders/log.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -var mozlog = require('mozlog') +var mozlog = require('mozlog'); -var logConfig = require('../../config').get('log') +var logConfig = require('../../config').get('log'); -mozlog.config(logConfig) +mozlog.config(logConfig); -module.exports = mozlog +module.exports = mozlog; diff --git a/lib/senders/oauth_client_info.js b/lib/senders/oauth_client_info.js index 0eb3ffc8..de465426 100644 --- a/lib/senders/oauth_client_info.js +++ b/lib/senders/oauth_client_info.js @@ -2,22 +2,22 @@ * 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/. */ -'use strict' +'use strict'; -const Keyv = require('keyv') +const Keyv = require('keyv'); module.exports = (log, config, oauthdb) => { - const OAUTH_CLIENT_INFO_CACHE_TTL = config.oauth.clientInfoCacheTTL - const OAUTH_CLIENT_INFO_CACHE_NAMESPACE = 'oauthClientInfo' + const OAUTH_CLIENT_INFO_CACHE_TTL = config.oauth.clientInfoCacheTTL; + const OAUTH_CLIENT_INFO_CACHE_NAMESPACE = 'oauthClientInfo'; const FIREFOX_CLIENT = { name: 'Firefox' - } + }; const clientCache = new Keyv({ ttl: OAUTH_CLIENT_INFO_CACHE_TTL, namespace: OAUTH_CLIENT_INFO_CACHE_NAMESPACE - }) + }); /** * Fetches OAuth client info from the OAuth server. @@ -26,42 +26,42 @@ module.exports = (log, config, oauthdb) => { * @returns {Promise} */ async function fetch(clientId) { - log.trace('fetch.start') + log.trace('fetch.start'); if (! clientId || clientId === 'sync') { - log.trace('fetch.sync') - return FIREFOX_CLIENT + log.trace('fetch.sync'); + return FIREFOX_CLIENT; } - const cachedRecord = await clientCache.get(clientId) + const cachedRecord = await clientCache.get(clientId); if (cachedRecord) { // used the cachedRecord if it exists - log.trace('fetch.usedCache') - return cachedRecord + log.trace('fetch.usedCache'); + return cachedRecord; } - let clientInfo + let clientInfo; try { - clientInfo = await oauthdb.getClientInfo(clientId) + clientInfo = await oauthdb.getClientInfo(clientId); } catch (err) { // fallback to the Firefox client if request fails if (! err.statusCode) { - log.fatal('fetch.failed', { err: err }) + log.fatal('fetch.failed', { err: err }); } else { - log.warn('fetch.failedForClient', { clientId }) + log.warn('fetch.failedForClient', { clientId }); } - return FIREFOX_CLIENT + return FIREFOX_CLIENT; } - log.trace('fetch.usedServer', { body: clientInfo }) + log.trace('fetch.usedServer', { body: clientInfo }); // We deliberately don't wait for this to resolve, since the // client doesn't need to wait for us to write to the cache. - clientCache.set(clientId, clientInfo) - return clientInfo + clientCache.set(clientId, clientInfo); + return clientInfo; } return { fetch: fetch, __clientCache: clientCache - } -} + }; +}; diff --git a/lib/senders/sms.js b/lib/senders/sms.js index da7b8a8c..bcb4e125 100644 --- a/lib/senders/sms.js +++ b/lib/senders/sms.js @@ -2,49 +2,49 @@ * 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/. */ -'use strict' +'use strict'; -const Cloudwatch = require('aws-sdk/clients/cloudwatch') -const error = require('../error') -const MockSns = require('../../test/mock-sns') -const P = require('bluebird') -const Sns = require('aws-sdk/clients/sns') -const time = require('../time') +const Cloudwatch = require('aws-sdk/clients/cloudwatch'); +const error = require('../error'); +const MockSns = require('../../test/mock-sns'); +const P = require('bluebird'); +const Sns = require('aws-sdk/clients/sns'); +const time = require('../time'); -const SECONDS_PER_MINUTE = 60 -const MILLISECONDS_PER_MINUTE = SECONDS_PER_MINUTE * 1000 -const MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * 60 -const PERIOD_IN_MINUTES = 5 +const SECONDS_PER_MINUTE = 60; +const MILLISECONDS_PER_MINUTE = SECONDS_PER_MINUTE * 1000; +const MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * 60; +const PERIOD_IN_MINUTES = 5; class MockCloudwatch { getMetricStatistics () { return { promise: () => P.resolve({ Datapoints: [ { Maximum: 0 } ] }) - } + }; } } module.exports = (log, translator, templates, config) => { - const cloudwatch = initService(config, Cloudwatch, MockCloudwatch) - const sns = initService(config, Sns, MockSns) + const cloudwatch = initService(config, Cloudwatch, MockCloudwatch); + const sns = initService(config, Sns, MockSns); - const { minimumCreditThresholdUSD: CREDIT_THRESHOLD } = config.sms + const { minimumCreditThresholdUSD: CREDIT_THRESHOLD } = config.sms; - let isBudgetOk = true + let isBudgetOk = true; if (config.sms.enableBudgetChecks) { - setImmediate(pollCurrentSpend) + setImmediate(pollCurrentSpend); } return { isBudgetOk: () => isBudgetOk, send (phoneNumber, templateName, acceptLanguage, signinCode) { - log.trace('sms.send', { templateName, acceptLanguage }) + log.trace('sms.send', { templateName, acceptLanguage }); return P.resolve() .then(() => { - const message = getMessage(templateName, acceptLanguage, signinCode) + const message = getMessage(templateName, acceptLanguage, signinCode); const params = { Message: message.trim(), MessageAttributes: { @@ -65,7 +65,7 @@ module.exports = (log, translator, templates, config) => { } }, PhoneNumber: phoneNumber - } + }; return sns.publish(params).promise() .then(result => { @@ -73,30 +73,30 @@ module.exports = (log, translator, templates, config) => { templateName, acceptLanguage, messageId: result.MessageId - }) + }); }) .catch(sendError => { - const { message, code, statusCode } = sendError - log.error('sms.send.error', { message, code, statusCode }) + const { message, code, statusCode } = sendError; + log.error('sms.send.error', { message, code, statusCode }); - throw error.messageRejected(message, code) - }) - }) + throw error.messageRejected(message, code); + }); + }); } - } + }; function pollCurrentSpend () { - let limit + let limit; sns.getSMSAttributes({ attributes: [ 'MonthlySpendLimit' ] }).promise() .then(result => { - limit = parseFloat(result.attributes.MonthlySpendLimit) + limit = parseFloat(result.attributes.MonthlySpendLimit); if (isNaN(limit)) { - throw new Error(`Invalid getSMSAttributes result "${result.attributes.MonthlySpendLimit}"`) + throw new Error(`Invalid getSMSAttributes result "${result.attributes.MonthlySpendLimit}"`); } - const endTime = new Date() - const startTime = new Date(endTime.getTime() - PERIOD_IN_MINUTES * MILLISECONDS_PER_MINUTE) + const endTime = new Date(); + const startTime = new Date(endTime.getTime() - PERIOD_IN_MINUTES * MILLISECONDS_PER_MINUTE); return cloudwatch.getMetricStatistics({ Namespace: 'AWS/SNS', MetricName: 'SMSMonthToDateSpentUSD', @@ -104,63 +104,63 @@ module.exports = (log, translator, templates, config) => { EndTime: time.startOfMinute(endTime), Period: PERIOD_IN_MINUTES * SECONDS_PER_MINUTE, Statistics: [ 'Maximum' ] - }).promise() + }).promise(); }) .then(result => { - let current + let current; try { - current = parseFloat(result.Datapoints[0].Maximum) + current = parseFloat(result.Datapoints[0].Maximum); } catch (err) { - err.result = JSON.stringify(result) - throw err + err.result = JSON.stringify(result); + throw err; } if (isNaN(current)) { - throw new Error(`Invalid getMetricStatistics result "${result.Datapoints[0].Maximum}"`) + throw new Error(`Invalid getMetricStatistics result "${result.Datapoints[0].Maximum}"`); } - isBudgetOk = current <= limit - CREDIT_THRESHOLD - log.info('sms.budget.ok', { isBudgetOk, current, limit, threshold: CREDIT_THRESHOLD }) + isBudgetOk = current <= limit - CREDIT_THRESHOLD; + log.info('sms.budget.ok', { isBudgetOk, current, limit, threshold: CREDIT_THRESHOLD }); }) .catch(err => { - log.error('sms.budget.error', { err: err.message, result: err.result }) + log.error('sms.budget.error', { err: err.message, result: err.result }); // If we failed to query the data, assume current spend is fine - isBudgetOk = true + isBudgetOk = true; }) - .then(() => setTimeout(pollCurrentSpend, MILLISECONDS_PER_HOUR)) + .then(() => setTimeout(pollCurrentSpend, MILLISECONDS_PER_HOUR)); } function getMessage (templateName, acceptLanguage, signinCode) { - const template = templates[`sms.${templateName}`] + const template = templates[`sms.${templateName}`]; if (! template) { - log.error('sms.getMessage.error', { templateName }) - throw error.invalidMessageId() + log.error('sms.getMessage.error', { templateName }); + throw error.invalidMessageId(); } - let link + let link; if (signinCode) { - link = `${config.sms.installFirefoxWithSigninCodeBaseUri}/${urlSafeBase64(signinCode)}` + link = `${config.sms.installFirefoxWithSigninCodeBaseUri}/${urlSafeBase64(signinCode)}`; } else { - link = config.sms[`${templateName}Link`] + link = config.sms[`${templateName}Link`]; } - return template({ link, translator: translator.getTranslator(acceptLanguage) }).text + return template({ link, translator: translator.getTranslator(acceptLanguage) }).text; } -} +}; function initService (config, Class, MockClass) { const options = { region: config.sms.apiRegion - } + }; if (config.sms.useMock) { - return new MockClass(options, config) + return new MockClass(options, config); } - return new Class(options) + return new Class(options); } function urlSafeBase64 (hex) { @@ -168,5 +168,5 @@ function urlSafeBase64 (hex) { .toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') - .replace(/=/g, '') + .replace(/=/g, ''); } diff --git a/lib/senders/templates/index.js b/lib/senders/templates/index.js index 63a6586a..200c6078 100644 --- a/lib/senders/templates/index.js +++ b/lib/senders/templates/index.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; -var path = require('path') -var P = require('bluebird') -var handlebars = require('handlebars') -var readFile = P.promisify(require('fs').readFile) +var path = require('path'); +var P = require('bluebird'); +var handlebars = require('handlebars'); +var readFile = P.promisify(require('fs').readFile); handlebars.registerHelper( 't', function (string) { if (this.translator) { - return this.translator.format(this.translator.gettext(string), this) + return this.translator.format(this.translator.gettext(string), this); } - return string + return string; } -) +); function generateTemplateName (str) { if (/^sms\.[A-Za-z]+/.test(str)) { - return str + return str; } return str.replace(/_(.)/g, function(match, c) { - return c.toUpperCase() + return c.toUpperCase(); } - ) + 'Email' + ) + 'Email'; } function loadTemplates(name) { @@ -40,19 +40,19 @@ function loadTemplates(name) { ) .spread( function (text, html) { - var renderText = handlebars.compile(text) - var renderHtml = handlebars.compile(html) + var renderText = handlebars.compile(text); + var renderHtml = handlebars.compile(html); return { name: generateTemplateName(name), fn: function (values) { return { text: renderText(values), html: renderHtml(values) - } + }; } - } + }; } - ) + ); } module.exports = { @@ -96,11 +96,11 @@ module.exports = { // } return templates.reduce( function (result, template) { - result[template.name] = template.fn - return result + result[template.name] = template.fn; + return result; }, {} - ) + ); } ) -} +}; diff --git a/lib/senders/translator.js b/lib/senders/translator.js index 8e700c9e..dc9efad3 100644 --- a/lib/senders/translator.js +++ b/lib/senders/translator.js @@ -2,22 +2,22 @@ * 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/. */ -'use strict' +'use strict'; -var path = require('path') -var i18n = require('i18n-abide') -var Jed = require('jed') -var P = require('bluebird') -var po2json = require('po2json') -var poParseFile = P.promisify(po2json.parseFile) +var path = require('path'); +var i18n = require('i18n-abide'); +var Jed = require('jed'); +var P = require('bluebird'); +var po2json = require('po2json'); +var poParseFile = P.promisify(po2json.parseFile); -Jed.prototype.format = i18n.format +Jed.prototype.format = i18n.format; -var parseCache = {} +var parseCache = {}; function parseLocale(locale) { if (parseCache[locale]) { - return P.resolve(parseCache[locale]) + return P.resolve(parseCache[locale]); } return poParseFile( @@ -32,9 +32,9 @@ function parseLocale(locale) { format: 'jed' } ).then(function (parsed) { - parseCache[locale] = parsed - return parsed - }) + parseCache[locale] = parsed; + return parsed; + }); } module.exports = function (locales, defaultLanguage) { @@ -43,38 +43,38 @@ module.exports = function (locales, defaultLanguage) { ) .then( function (translations) { - var languageTranslations = {} - var supportedLanguages = [] + var languageTranslations = {}; + var supportedLanguages = []; for (var i = 0; i < translations.length; i++) { - var t = translations[i] - const localeMessageData = t.locale_data.messages[''] + var t = translations[i]; + const localeMessageData = t.locale_data.messages['']; if (localeMessageData.lang === 'ar') { // NOTE: there seems to be some incompatibility with Jed and Arabic plural forms from Pontoon // We disable plural forms manually below, we don't use them anyway. Issue #2714 - localeMessageData.plural_forms = null + localeMessageData.plural_forms = null; } - var language = i18n.normalizeLanguage(i18n.languageFrom(localeMessageData.lang)) - supportedLanguages.push(language) - var translator = new Jed(t) - translator.language = language - languageTranslations[language] = translator + var language = i18n.normalizeLanguage(i18n.languageFrom(localeMessageData.lang)); + supportedLanguages.push(language); + var translator = new Jed(t); + translator.language = language; + languageTranslations[language] = translator; } return { getTranslator: function (acceptLanguage) { - return languageTranslations[getLocale(acceptLanguage)] + return languageTranslations[getLocale(acceptLanguage)]; }, getLocale: getLocale - } + }; function getLocale (acceptLanguage) { - var languages = i18n.parseAcceptLanguage(acceptLanguage) - var bestLanguage = i18n.bestLanguage(languages, supportedLanguages, defaultLanguage) - return i18n.normalizeLanguage(bestLanguage) + var languages = i18n.parseAcceptLanguage(acceptLanguage); + var bestLanguage = i18n.bestLanguage(languages, supportedLanguages, defaultLanguage); + return i18n.normalizeLanguage(bestLanguage); } } - ) -} + ); +}; diff --git a/lib/server.js b/lib/server.js index fa3fb911..09839f36 100644 --- a/lib/server.js +++ b/lib/server.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; -const fs = require('fs') -const Hapi = require('hapi') -const joi = require('joi') -const Raven = require('raven') -const path = require('path') -const url = require('url') -const userAgent = require('./userAgent') -const schemeRefreshToken = require('./scheme-refresh-token') +const fs = require('fs'); +const Hapi = require('hapi'); +const joi = require('joi'); +const Raven = require('raven'); +const path = require('path'); +const url = require('url'); +const userAgent = require('./userAgent'); +const schemeRefreshToken = require('./scheme-refresh-token'); -const { HEX_STRING, IP_ADDRESS } = require('./routes/validators') +const { HEX_STRING, IP_ADDRESS } = require('./routes/validators'); function trimLocale(header) { if (! header) { - return header + return header; } if (header.length < 256) { - return header.trim() + return header.trim(); } - var parts = header.split(',') - var str = parts[0] - if (str.length >= 255) { return null } + var parts = header.split(','); + var str = parts[0]; + if (str.length >= 255) { return null; } for (var i = 1; i < parts.length && str.length + parts[i].length < 255; i++) { - str += ',' + parts[i] + str += ',' + parts[i]; } - return str.trim() + return str.trim(); } function logEndpointErrors(response, log) { @@ -39,25 +39,25 @@ function logEndpointErrors(response, log) { var endpointLog = { message: response.message, reason: response.reason - } + }; if (response.attempt && response.attempt.method) { // log the DB attempt to understand the action - endpointLog.method = response.attempt.method + endpointLog.method = response.attempt.method; } - log.error('server.EndpointError', endpointLog) + log.error('server.EndpointError', endpointLog); } } function configureSentry(server, config) { - const sentryDsn = config.sentryDsn + const sentryDsn = config.sentryDsn; if (sentryDsn) { - Raven.config(sentryDsn, {}) + Raven.config(sentryDsn, {}); server.events.on({ name: 'request', channels: 'error' }, function (request, event) { - const err = event && event.error || null - let exception = '' + const err = event && event.error || null; + let exception = ''; if (err && err.stack) { try { - exception = err.stack.split('\n')[0] + exception = err.stack.split('\n')[0]; } catch (e) { // ignore bad stack frames } @@ -67,24 +67,24 @@ function configureSentry(server, config) { extra: { exception: exception } - }) - }) + }); + }); } } async function create (log, error, config, routes, db, oauthdb, translator) { - const getGeoData = require('./geodb')(log) - const metricsContext = require('./metrics/context')(log, config) - const metricsEvents = require('./metrics/events')(log, config) + const getGeoData = require('./geodb')(log); + const metricsContext = require('./metrics/context')(log, config); + const metricsEvents = require('./metrics/events')(log, config); // Hawk needs to calculate request signatures based on public URL, // not the local URL to which it is bound. - var publicURL = url.parse(config.publicUrl) + var publicURL = url.parse(config.publicUrl); var defaultPorts = { 'http:': 80, 'https:': 443 - } + }; var hawkOptions = { host: publicURL.hostname, port: publicURL.port ? publicURL.port : defaultPorts[publicURL.protocol], @@ -98,35 +98,35 @@ async function create (log, error, config, routes, db, oauthdb, translator) { // Since we've disabled timestamp checks, there's not much point // keeping a nonce cache. Instead we use this as an opportunity // to report on the clock skew values seen in the wild. - var skew = (Date.now() / 1000) - (+ts) - log.trace('server.nonceFunc', { skew: skew }) + var skew = (Date.now() / 1000) - (+ts); + log.trace('server.nonceFunc', { skew: skew }); } - } + }; function makeCredentialFn(dbGetFn) { return function (id) { - log.trace('DB.getToken', { id: id }) + log.trace('DB.getToken', { id: id }); if (! HEX_STRING.test(id)) { - return null + return null; } return dbGetFn(id) .then(token => { if (token.expired(Date.now())) { - const err = error.invalidToken('The authentication token has expired') + const err = error.invalidToken('The authentication token has expired'); if (token.constructor.tokenTypeID === 'sessionToken') { return db.pruneSessionTokens(token.uid, [ token ]) .catch(() => {}) - .then(() => { throw err }) + .then(() => { throw err; }); } - return null + return null; } - return token - }) + return token; + }); - } + }; } var serverOptions = { @@ -169,61 +169,61 @@ async function create (log, error, config, routes, db, oauthdb, translator) { sampleInterval: 1000, maxEventLoopDelay: config.maxEventLoopDelay }, - } + }; if (config.useHttps) { serverOptions.tls = { key: fs.readFileSync(config.keyPath), cert: fs.readFileSync(config.certPath) - } + }; } - var server = new Hapi.Server(serverOptions) + var server = new Hapi.Server(serverOptions); server.ext('onRequest', (request, h) => { - log.begin('server.onRequest', request) - return h.continue - }) + log.begin('server.onRequest', request); + return h.continue; + }); server.ext('onPreAuth', (request, h) => { defineLazyGetter(request.app, 'remoteAddressChain', () => { - const xff = (request.headers['x-forwarded-for'] || '').split(/\s*,\s*/) + const xff = (request.headers['x-forwarded-for'] || '').split(/\s*,\s*/); - xff.push(request.info.remoteAddress) + xff.push(request.info.remoteAddress); return xff.map(address => address.trim()) - .filter(address => ! joi.validate(address, IP_ADDRESS.required()).error) - }) + .filter(address => ! joi.validate(address, IP_ADDRESS.required()).error); + }); defineLazyGetter(request.app, 'clientAddress', () => { - const remoteAddressChain = request.app.remoteAddressChain - let clientAddressIndex = remoteAddressChain.length - (config.clientAddressDepth || 1) + const remoteAddressChain = request.app.remoteAddressChain; + let clientAddressIndex = remoteAddressChain.length - (config.clientAddressDepth || 1); if (clientAddressIndex < 0) { - clientAddressIndex = 0 + clientAddressIndex = 0; } - return remoteAddressChain[clientAddressIndex] - }) + return remoteAddressChain[clientAddressIndex]; + }); - defineLazyGetter(request.app, 'acceptLanguage', () => trimLocale(request.headers['accept-language'])) - defineLazyGetter(request.app, 'locale', () => translator.getLocale(request.app.acceptLanguage)) + defineLazyGetter(request.app, 'acceptLanguage', () => trimLocale(request.headers['accept-language'])); + defineLazyGetter(request.app, 'locale', () => translator.getLocale(request.app.acceptLanguage)); - defineLazyGetter(request.app, 'ua', () => userAgent(request.headers['user-agent'])) - defineLazyGetter(request.app, 'geo', () => getGeoData(request.app.clientAddress)) - defineLazyGetter(request.app, 'metricsContext', () => metricsContext.get(request)) + defineLazyGetter(request.app, 'ua', () => userAgent(request.headers['user-agent'])); + defineLazyGetter(request.app, 'geo', () => getGeoData(request.app.clientAddress)); + defineLazyGetter(request.app, 'metricsContext', () => metricsContext.get(request)); defineLazyGetter(request.app, 'devices', () => { - let uid + let uid; if (request.auth && request.auth.credentials) { - uid = request.auth.credentials.uid + uid = request.auth.credentials.uid; } else if (request.payload && request.payload.uid) { - uid = request.payload.uid + uid = request.payload.uid; } - return db.devices(uid) - }) + return db.devices(uid); + }); if (request.headers.authorization) { // Log some helpful details for debugging authentication problems. @@ -232,55 +232,55 @@ async function create (log, error, config, routes, db, oauthdb, translator) { path: request.path, auth: request.headers.authorization, type: request.headers['content-type'] || '' - }) + }); } - return h.continue - }) + return h.continue; + }); server.ext('onPreHandler', (request, h) => { - const features = request.payload && request.payload.features - request.app.features = new Set(Array.isArray(features) ? features : []) + const features = request.payload && request.payload.features; + request.app.features = new Set(Array.isArray(features) ? features : []); - return h.continue - }) + return h.continue; + }); server.ext('onPreResponse', (request) => { - let response = request.response + let response = request.response; if (response.isBoom) { - logEndpointErrors(response, log) - response = error.translate(request, response) + logEndpointErrors(response, log); + response = error.translate(request, response); if (config.env !== 'prod') { - response.backtrace(request.app.traced) + response.backtrace(request.app.traced); } } - response.header('Timestamp', '' + Math.floor(Date.now() / 1000)) - log.summary(request, response) - return response - }) + response.header('Timestamp', '' + Math.floor(Date.now() / 1000)); + log.summary(request, response); + return response; + }); // configure Sentry - configureSentry(server, config) + configureSentry(server, config); - server.decorate('request', 'stashMetricsContext', metricsContext.stash) - server.decorate('request', 'gatherMetricsContext', metricsContext.gather) - server.decorate('request', 'propagateMetricsContext', metricsContext.propagate) - server.decorate('request', 'clearMetricsContext', metricsContext.clear) - server.decorate('request', 'validateMetricsContext', metricsContext.validate) - server.decorate('request', 'setMetricsFlowCompleteSignal', metricsContext.setFlowCompleteSignal) + server.decorate('request', 'stashMetricsContext', metricsContext.stash); + server.decorate('request', 'gatherMetricsContext', metricsContext.gather); + server.decorate('request', 'propagateMetricsContext', metricsContext.propagate); + server.decorate('request', 'clearMetricsContext', metricsContext.clear); + server.decorate('request', 'validateMetricsContext', metricsContext.validate); + server.decorate('request', 'setMetricsFlowCompleteSignal', metricsContext.setFlowCompleteSignal); - server.decorate('request', 'emitMetricsEvent', metricsEvents.emit) - server.decorate('request', 'emitRouteFlowEvent', metricsEvents.emitRouteFlowEvent) + server.decorate('request', 'emitMetricsEvent', metricsEvents.emit); + server.decorate('request', 'emitRouteFlowEvent', metricsEvents.emitRouteFlowEvent); server.stat = function() { return { stat: 'mem', rss: server.load.rss, heapUsed: server.load.heapUsed - } - } + }; + }; - await server.register(require('hapi-auth-hawk')) + await server.register(require('hapi-auth-hawk')); server.auth.strategy( 'sessionToken', @@ -289,7 +289,7 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.sessionToken.bind(db)), hawk: hawkOptions } - ) + ); server.auth.strategy( 'keyFetchToken', 'hawk', @@ -297,7 +297,7 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.keyFetchToken.bind(db)), hawk: hawkOptions } - ) + ); server.auth.strategy( // This strategy fetches the keyFetchToken with its // verification state. It doesn't check that state. @@ -307,7 +307,7 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.keyFetchTokenWithVerificationStatus.bind(db)), hawk: hawkOptions } - ) + ); server.auth.strategy( 'accountResetToken', 'hawk', @@ -315,7 +315,7 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.accountResetToken.bind(db)), hawk: hawkOptions } - ) + ); server.auth.strategy( 'passwordForgotToken', 'hawk', @@ -323,7 +323,7 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.passwordForgotToken.bind(db)), hawk: hawkOptions } - ) + ); server.auth.strategy( 'passwordChangeToken', 'hawk', @@ -331,33 +331,33 @@ async function create (log, error, config, routes, db, oauthdb, translator) { getCredentialsFunc: makeCredentialFn(db.passwordChangeToken.bind(db)), hawk: hawkOptions } - ) - await server.register(require('hapi-fxa-oauth')) + ); + await server.register(require('hapi-fxa-oauth')); - server.auth.strategy('oauthToken', 'fxa-oauth', config.oauth) + server.auth.strategy('oauthToken', 'fxa-oauth', config.oauth); - server.auth.scheme('fxa-oauth-refreshToken', schemeRefreshToken(db, oauthdb)) + server.auth.scheme('fxa-oauth-refreshToken', schemeRefreshToken(db, oauthdb)); - server.auth.strategy('refreshToken', 'fxa-oauth-refreshToken') + server.auth.strategy('refreshToken', 'fxa-oauth-refreshToken'); // routes should be registered after all auth strategies have initialized: // ref: http://hapijs.com/tutorials/auth - server.route(routes) - return server + server.route(routes); + return server; } function defineLazyGetter (object, key, getter) { - let value + let value; Object.defineProperty(object, key, { get () { if (! value) { - value = getter() + value = getter(); } - return value + return value; } - }) + }); } module.exports = { @@ -366,4 +366,4 @@ module.exports = { _configureSentry: configureSentry, _logEndpointErrors: logEndpointErrors, _trimLocale: trimLocale -} +}; diff --git a/lib/signer.js b/lib/signer.js index c5e95bc4..1a8def87 100644 --- a/lib/signer.js +++ b/lib/signer.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; -var jwtool = require('fxa-jwtool') +var jwtool = require('fxa-jwtool'); module.exports = function (secretKeyFile, domain) { - var key = jwtool.JWK.fromFile(secretKeyFile, {iss: domain }) + var key = jwtool.JWK.fromFile(secretKeyFile, {iss: domain }); return { sign: function (data) { - var now = Date.now() + var now = Date.now(); return key.sign( { 'public-key': data.publicKey, @@ -33,9 +33,9 @@ module.exports = function (secretKeyFile, domain) { ) .then( function (cert) { - return { cert: cert } + return { cert: cert }; } - ) + ); } - } -} + }; +}; diff --git a/lib/sqs.js b/lib/sqs.js index 7d2b36ea..6bf2aa7b 100644 --- a/lib/sqs.js +++ b/lib/sqs.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -var AWS = require('aws-sdk') -var inherits = require('util').inherits -var EventEmitter = require('events').EventEmitter +var AWS = require('aws-sdk'); +var inherits = require('util').inherits; +var EventEmitter = require('events').EventEmitter; module.exports = function (log) { function SQSReceiver(region, urls) { - this.sqs = new AWS.SQS({ region : region }) - this.queueUrls = urls || [] - EventEmitter.call(this) + this.sqs = new AWS.SQS({ region : region }); + this.queueUrls = urls || []; + EventEmitter.call(this); } - inherits(SQSReceiver, EventEmitter) + inherits(SQSReceiver, EventEmitter); function checkDeleteError(err) { if (err) { - log.error('deleteMessage', { err: err }) + log.error('deleteMessage', { err: err }); } } SQSReceiver.prototype.fetch = function (url) { - var errTimer = null + var errTimer = null; this.sqs.receiveMessage( { QueueUrl: url, @@ -34,13 +34,13 @@ module.exports = function (log) { }, function (err, data) { if (err) { - log.error('fetch', { url: url, err: err }) + log.error('fetch', { url: url, err: err }); if (! errTimer) { // unacceptable! this aws lib will call the callback // more than once with different errors. ಠ_ಠ - errTimer = setTimeout(this.fetch.bind(this, url), 2000) + errTimer = setTimeout(this.fetch.bind(this, url), 2000); } - return + return; } function deleteMessage(message) { this.sqs.deleteMessage( @@ -49,33 +49,33 @@ module.exports = function (log) { ReceiptHandle: message.ReceiptHandle }, checkDeleteError - ) + ); } - data.Messages = data.Messages || [] + data.Messages = data.Messages || []; for (var i = 0; i < data.Messages.length; i++) { - var msg = data.Messages[i] - var deleteFromQueue = deleteMessage.bind(this, msg) + var msg = data.Messages[i]; + var deleteFromQueue = deleteMessage.bind(this, msg); try { - var body = JSON.parse(msg.Body) - var message = JSON.parse(body.Message) - message.del = deleteFromQueue - this.emit('data', message) + var body = JSON.parse(msg.Body); + var message = JSON.parse(body.Message); + message.del = deleteFromQueue; + this.emit('data', message); } catch (e) { - log.error('fetch', { url: url, err: e }) - deleteFromQueue() + log.error('fetch', { url: url, err: e }); + deleteFromQueue(); } } - this.fetch(url) + this.fetch(url); }.bind(this) - ) - } + ); + }; SQSReceiver.prototype.start = function () { for (var i = 0; i < this.queueUrls.length; i++) { - this.fetch(this.queueUrls[i]) + this.fetch(this.queueUrls[i]); } - } + }; - return SQSReceiver -} + return SQSReceiver; +}; diff --git a/lib/time.js b/lib/time.js index fbde87be..e7cab223 100644 --- a/lib/time.js +++ b/lib/time.js @@ -2,24 +2,24 @@ * 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/. */ -'use strict' +'use strict'; module.exports = { startOfMinute (date) { - const year = date.getUTCFullYear() - const month = date.getUTCMonth() + 1 - const day = date.getUTCDate() - const hour = date.getUTCHours() - const minute = date.getUTCMinutes() - return `${year}-${pad(month)}-${pad(day)}T${pad(hour)}:${pad(minute)}:00Z` + const year = date.getUTCFullYear(); + const month = date.getUTCMonth() + 1; + const day = date.getUTCDate(); + const hour = date.getUTCHours(); + const minute = date.getUTCMinutes(); + return `${year}-${pad(month)}-${pad(day)}T${pad(hour)}:${pad(minute)}:00Z`; } -} +}; function pad (number) { if (number < 10) { - return `0${number}` + return `0${number}`; } - return number + return number; } diff --git a/lib/tokens/account_reset_token.js b/lib/tokens/account_reset_token.js index 39e65b6d..7db0b750 100644 --- a/lib/tokens/account_reset_token.js +++ b/lib/tokens/account_reset_token.js @@ -2,30 +2,30 @@ * 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/. */ -'use strict' +'use strict'; -const inherits = require('util').inherits +const inherits = require('util').inherits; module.exports = function (log, Token, lifetime) { function AccountResetToken(keys, details) { - details.lifetime = lifetime - Token.call(this, keys, details) + details.lifetime = lifetime; + Token.call(this, keys, details); } - inherits(AccountResetToken, Token) + inherits(AccountResetToken, Token); - AccountResetToken.tokenTypeID = 'accountResetToken' + AccountResetToken.tokenTypeID = 'accountResetToken'; AccountResetToken.create = function (details) { - log.trace('AccountResetToken.create', { uid: details && details.uid }) - return Token.createNewToken(AccountResetToken, details || {}) - } + log.trace('AccountResetToken.create', { uid: details && details.uid }); + return Token.createNewToken(AccountResetToken, details || {}); + }; AccountResetToken.fromHex = function (string, details) { - log.trace('AccountResetToken.fromHex') - details = details || {} - return Token.createTokenFromHexData(AccountResetToken, string, details) - } + log.trace('AccountResetToken.fromHex'); + details = details || {}; + return Token.createTokenFromHexData(AccountResetToken, string, details); + }; - return AccountResetToken -} + return AccountResetToken; +}; diff --git a/lib/tokens/bundle.js b/lib/tokens/bundle.js index 681ef01b..4bc8a99f 100644 --- a/lib/tokens/bundle.js +++ b/lib/tokens/bundle.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/. */ -'use strict' +'use strict'; /* Utility functions for working with encrypted data bundles. @@ -25,52 +25,52 @@ * */ -const butil = require('../crypto/butil') -const crypto = require('crypto') -const error = require('../error') -const hkdf = require('../crypto/hkdf') +const butil = require('../crypto/butil'); +const crypto = require('crypto'); +const error = require('../error'); +const hkdf = require('../crypto/hkdf'); -const HASH_ALGORITHM = 'sha256' +const HASH_ALGORITHM = 'sha256'; module.exports = { // Encrypt the given buffer into a hex ciphertext string. // bundle(key, keyInfo, payload) { - key = Buffer.from(key, 'hex') - payload = Buffer.from(payload, 'hex') + key = Buffer.from(key, 'hex'); + payload = Buffer.from(payload, 'hex'); return deriveBundleKeys(key, keyInfo, payload.length) .then( function (keys) { - var ciphertext = butil.xorBuffers(payload, keys.xorKey) - var hmac = crypto.createHmac(HASH_ALGORITHM, keys.hmacKey) - hmac.update(ciphertext) - var mac = hmac.digest() - return Buffer.concat([ciphertext, mac]).toString('hex') + var ciphertext = butil.xorBuffers(payload, keys.xorKey); + var hmac = crypto.createHmac(HASH_ALGORITHM, keys.hmacKey); + hmac.update(ciphertext); + var mac = hmac.digest(); + return Buffer.concat([ciphertext, mac]).toString('hex'); } - ) + ); }, // Decrypt the given hex string into a buffer of plaintext data. // unbundle(key, keyInfo, payload) { - key = Buffer.from(key, 'hex') - payload = Buffer.from(payload, 'hex') - var ciphertext = payload.slice(0, -32) - var expectedHmac = payload.slice(-32) + key = Buffer.from(key, 'hex'); + payload = Buffer.from(payload, 'hex'); + var ciphertext = payload.slice(0, -32); + var expectedHmac = payload.slice(-32); return deriveBundleKeys(key, keyInfo, ciphertext.length) .then( function (keys) { - var hmac = crypto.createHmac(HASH_ALGORITHM, keys.hmacKey) - hmac.update(ciphertext) - var mac = hmac.digest() + var hmac = crypto.createHmac(HASH_ALGORITHM, keys.hmacKey); + hmac.update(ciphertext); + var mac = hmac.digest(); if (! butil.buffersAreEqual(mac, expectedHmac)) { - throw error.invalidSignature() + throw error.invalidSignature(); } - return butil.xorBuffers(ciphertext, keys.xorKey).toString('hex') + return butil.xorBuffers(ciphertext, keys.xorKey).toString('hex'); } - ) + ); } -} +}; // Derive the HMAC and XOR keys required to encrypt a given size of payload. @@ -82,8 +82,8 @@ function deriveBundleKeys(key, keyInfo, payloadSize) { return { hmacKey: keyMaterial.slice(0, 32), xorKey: keyMaterial.slice(32) - } + }; } - ) + ); } diff --git a/lib/tokens/index.js b/lib/tokens/index.js index fbd618d5..70304d75 100644 --- a/lib/tokens/index.js +++ b/lib/tokens/index.js @@ -2,46 +2,46 @@ * 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/. */ -'use strict' +'use strict'; -const error = require('../error') +const error = require('../error'); module.exports = (log, config) => { - config = config || {} + config = config || {}; const lifetimes = config.tokenLifetimes = config.tokenLifetimes || { accountResetToken: 1000 * 60 * 15, passwordChangeToken: 1000 * 60 * 15, passwordForgotToken: 1000 * 60 * 15 - } - const Bundle = require('./bundle') - const Token = require('./token')(log, config) + }; + const Bundle = require('./bundle'); + const Token = require('./token')(log, config); - const KeyFetchToken = require('./key_fetch_token')(log, Token) + const KeyFetchToken = require('./key_fetch_token')(log, Token); const AccountResetToken = require('./account_reset_token')( log, Token, lifetimes.accountResetToken - ) - const SessionToken = require('./session_token')(log, Token, config) + ); + const SessionToken = require('./session_token')(log, Token, config); const PasswordForgotToken = require('./password_forgot_token')( log, Token, lifetimes.passwordForgotToken - ) + ); const PasswordChangeToken = require('./password_change_token')( log, Token, lifetimes.passwordChangeToken - ) + ); - Token.error = error - Token.Bundle = Bundle - Token.AccountResetToken = AccountResetToken - Token.KeyFetchToken = KeyFetchToken - Token.SessionToken = SessionToken - Token.PasswordForgotToken = PasswordForgotToken - Token.PasswordChangeToken = PasswordChangeToken + Token.error = error; + Token.Bundle = Bundle; + Token.AccountResetToken = AccountResetToken; + Token.KeyFetchToken = KeyFetchToken; + Token.SessionToken = SessionToken; + Token.PasswordForgotToken = PasswordForgotToken; + Token.PasswordChangeToken = PasswordChangeToken; - return Token -} + return Token; +}; diff --git a/lib/tokens/key_fetch_token.js b/lib/tokens/key_fetch_token.js index 0be8becc..02dbaa88 100644 --- a/lib/tokens/key_fetch_token.js +++ b/lib/tokens/key_fetch_token.js @@ -2,71 +2,71 @@ * 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/. */ -'use strict' +'use strict'; -const inherits = require('util').inherits -const P = require('../promise') +const inherits = require('util').inherits; +const P = require('../promise'); module.exports = function (log, Token) { function KeyFetchToken(keys, details) { - Token.call(this, keys, details) - this.keyBundle = details.keyBundle - this.emailVerified = !! details.emailVerified + Token.call(this, keys, details); + this.keyBundle = details.keyBundle; + this.emailVerified = !! details.emailVerified; // Tokens are considered verified if no tokenVerificationId exists - this.tokenVerificationId = details.tokenVerificationId || null - this.tokenVerified = this.tokenVerificationId ? false : true + this.tokenVerificationId = details.tokenVerificationId || null; + this.tokenVerified = this.tokenVerificationId ? false : true; } - inherits(KeyFetchToken, Token) + inherits(KeyFetchToken, Token); - KeyFetchToken.tokenTypeID = 'keyFetchToken' + KeyFetchToken.tokenTypeID = 'keyFetchToken'; KeyFetchToken.create = function (details) { - log.trace('KeyFetchToken.create', { uid: details && details.uid }) + log.trace('KeyFetchToken.create', { uid: details && details.uid }); return Token.createNewToken(KeyFetchToken, details || {}) .then( function (token) { return token.bundleKeys(details.kA, details.wrapKb) .then( function (keyBundle) { - token.keyBundle = keyBundle - return token + token.keyBundle = keyBundle; + return token; } - ) + ); } - ) - } + ); + }; KeyFetchToken.fromId = function (id, details) { - log.trace('KeyFetchToken.fromId') - return P.resolve(new KeyFetchToken({ id, authKey: details.authKey }, details)) - } + log.trace('KeyFetchToken.fromId'); + return P.resolve(new KeyFetchToken({ id, authKey: details.authKey }, details)); + }; KeyFetchToken.fromHex = function (string, details) { - log.trace('KeyFetchToken.fromHex') - return Token.createTokenFromHexData(KeyFetchToken, string, details || {}) - } + log.trace('KeyFetchToken.fromHex'); + return Token.createTokenFromHexData(KeyFetchToken, string, details || {}); + }; KeyFetchToken.prototype.bundleKeys = function (kA, wrapKb) { - log.trace('keyFetchToken.bundleKeys', { id: this.id }) - kA = Buffer.from(kA, 'hex') - wrapKb = Buffer.from(wrapKb, 'hex') - return this.bundle('account/keys', Buffer.concat([kA, wrapKb])) - } + log.trace('keyFetchToken.bundleKeys', { id: this.id }); + kA = Buffer.from(kA, 'hex'); + wrapKb = Buffer.from(wrapKb, 'hex'); + return this.bundle('account/keys', Buffer.concat([kA, wrapKb])); + }; KeyFetchToken.prototype.unbundleKeys = function (bundle) { - log.trace('keyFetchToken.unbundleKeys', { id: this.id }) + log.trace('keyFetchToken.unbundleKeys', { id: this.id }); return this.unbundle('account/keys', bundle) .then( function (plaintext) { return { kA: plaintext.slice(0, 64), // strings, not buffers wrapKb: plaintext.slice(64, 128) - } + }; } - ) - } + ); + }; - return KeyFetchToken -} + return KeyFetchToken; +}; diff --git a/lib/tokens/password_change_token.js b/lib/tokens/password_change_token.js index 5e8f390b..99df1e15 100644 --- a/lib/tokens/password_change_token.js +++ b/lib/tokens/password_change_token.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const inherits = require('util').inherits +const inherits = require('util').inherits; module.exports = function (log, Token, lifetime) { function PasswordChangeToken(keys, details) { - details.lifetime = lifetime - Token.call(this, keys, details) + details.lifetime = lifetime; + Token.call(this, keys, details); } - inherits(PasswordChangeToken, Token) + inherits(PasswordChangeToken, Token); - PasswordChangeToken.tokenTypeID = 'passwordChangeToken' + PasswordChangeToken.tokenTypeID = 'passwordChangeToken'; PasswordChangeToken.create = function (details) { - log.trace('PasswordChangeToken.create', { uid: details && details.uid }) - return Token.createNewToken(PasswordChangeToken, details || {}) - } + log.trace('PasswordChangeToken.create', { uid: details && details.uid }); + return Token.createNewToken(PasswordChangeToken, details || {}); + }; PasswordChangeToken.fromHex = function (string, details) { - log.trace('PasswordChangeToken.fromHex') - return Token.createTokenFromHexData(PasswordChangeToken, string, details || {}) - } + log.trace('PasswordChangeToken.fromHex'); + return Token.createTokenFromHexData(PasswordChangeToken, string, details || {}); + }; - return PasswordChangeToken -} + return PasswordChangeToken; +}; diff --git a/lib/tokens/password_forgot_token.js b/lib/tokens/password_forgot_token.js index cae2e2d7..41424f42 100644 --- a/lib/tokens/password_forgot_token.js +++ b/lib/tokens/password_forgot_token.js @@ -2,48 +2,48 @@ * 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/. */ -'use strict' +'use strict'; -const inherits = require('util').inherits -const random = require('../crypto/random') +const inherits = require('util').inherits; +const random = require('../crypto/random'); module.exports = (log, Token, lifetime) => { function PasswordForgotToken(keys, details) { - details.lifetime = lifetime - Token.call(this, keys, details) - this.email = details.email || null - this.passCode = details.passCode || null - this.tries = details.tries || null + details.lifetime = lifetime; + Token.call(this, keys, details); + this.email = details.email || null; + this.passCode = details.passCode || null; + this.tries = details.tries || null; } - inherits(PasswordForgotToken, Token) + inherits(PasswordForgotToken, Token); - PasswordForgotToken.tokenTypeID = 'passwordForgotToken' + PasswordForgotToken.tokenTypeID = 'passwordForgotToken'; PasswordForgotToken.create = function (details) { - details = details || {} + details = details || {}; log.trace('PasswordForgotToken.create', { uid: details.uid, email: details.email - }) + }); return random.hex(16) .then(bytes => { - details.passCode = bytes - details.tries = 3 - return Token.createNewToken(PasswordForgotToken, details) - }) - } + details.passCode = bytes; + details.tries = 3; + return Token.createNewToken(PasswordForgotToken, details); + }); + }; PasswordForgotToken.fromHex = function (string, details) { - log.trace('PasswordForgotToken.fromHex') - details = details || {} - return Token.createTokenFromHexData(PasswordForgotToken, string, details) - } + log.trace('PasswordForgotToken.fromHex'); + details = details || {}; + return Token.createTokenFromHexData(PasswordForgotToken, string, details); + }; PasswordForgotToken.prototype.failAttempt = function () { - this.tries -- - return this.tries < 1 - } + this.tries --; + return this.tries < 1; + }; - return PasswordForgotToken -} + return PasswordForgotToken; +}; diff --git a/lib/tokens/session_token.js b/lib/tokens/session_token.js index 08a94e0d..be715842 100644 --- a/lib/tokens/session_token.js +++ b/lib/tokens/session_token.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const authMethods = require('../authMethods') +const authMethods = require('../authMethods'); module.exports = (log, Token, config) => { - const MAX_AGE_WITHOUT_DEVICE = config.tokenLifetimes.sessionTokenWithoutDevice + const MAX_AGE_WITHOUT_DEVICE = config.tokenLifetimes.sessionTokenWithoutDevice; // Convert verificationMethod to a more readable format. Maps to // https://github.com/mozilla/fxa-auth-db-mysql/blob/master/lib/db/util.js#L34 @@ -18,105 +18,105 @@ module.exports = (log, Token, config) => { [2, 'totp-2fa'], [3, 'recovery-code'] ] - ) + ); class SessionToken extends Token { constructor(keys, details) { - super(keys, details) + super(keys, details); if (MAX_AGE_WITHOUT_DEVICE && ! details.deviceId) { - this.lifetime = MAX_AGE_WITHOUT_DEVICE + this.lifetime = MAX_AGE_WITHOUT_DEVICE; } - this.setUserAgentInfo(details) - this.setDeviceInfo(details) - this.email = details.email || null - this.emailCode = details.emailCode || null - this.emailVerified = !! details.emailVerified - this.verifierSetAt = details.verifierSetAt - this.profileChangedAt = details.profileChangedAt - this.authAt = details.authAt || 0 - this.locale = details.locale || null - this.mustVerify = !! details.mustVerify || false + this.setUserAgentInfo(details); + this.setDeviceInfo(details); + this.email = details.email || null; + this.emailCode = details.emailCode || null; + this.emailVerified = !! details.emailVerified; + this.verifierSetAt = details.verifierSetAt; + this.profileChangedAt = details.profileChangedAt; + this.authAt = details.authAt || 0; + this.locale = details.locale || null; + this.mustVerify = !! details.mustVerify || false; // Tokens are considered verified if no tokenVerificationId exists - this.tokenVerificationId = details.tokenVerificationId || null - this.tokenVerified = this.tokenVerificationId ? false : true + this.tokenVerificationId = details.tokenVerificationId || null; + this.tokenVerified = this.tokenVerificationId ? false : true; - this.tokenVerificationCode = details.tokenVerificationCode || null - this.tokenVerificationCodeExpiresAt = details.tokenVerificationCodeExpiresAt || null + this.tokenVerificationCode = details.tokenVerificationCode || null; + this.tokenVerificationCodeExpiresAt = details.tokenVerificationCodeExpiresAt || null; - this.verificationMethod = details.verificationMethod || null - this.verificationMethodValue = VERIFICATION_METHODS.get(this.verificationMethod) - this.verifiedAt = details.verifiedAt || null + this.verificationMethod = details.verificationMethod || null; + this.verificationMethodValue = VERIFICATION_METHODS.get(this.verificationMethod); + this.verifiedAt = details.verifiedAt || null; } static create(details) { - details = details || {} - log.trace('SessionToken.create', { uid: details.uid }) - return Token.createNewToken(SessionToken, details) + details = details || {}; + log.trace('SessionToken.create', { uid: details.uid }); + return Token.createNewToken(SessionToken, details); } static fromHex(string, details) { - log.trace('SessionToken.fromHex') - return Token.createTokenFromHexData(SessionToken, string, details || {}) + log.trace('SessionToken.fromHex'); + return Token.createTokenFromHexData(SessionToken, string, details || {}); } lastAuthAt() { - return Math.floor((this.authAt || this.createdAt) / 1000) + return Math.floor((this.authAt || this.createdAt) / 1000); } get state() { if (this.tokenVerified) { - return 'verified' + return 'verified'; } else { - return 'unverified' + return 'unverified'; } } get authenticationMethods() { - const amrValues = new Set() + const amrValues = new Set(); // All sessionTokens require password authentication. - amrValues.add('pwd') + amrValues.add('pwd'); // Verified sessionTokens imply some additional authentication method(s). if (this.verificationMethodValue) { - amrValues.add(authMethods.verificationMethodToAMR(this.verificationMethodValue)) + amrValues.add(authMethods.verificationMethodToAMR(this.verificationMethodValue)); } else if (this.tokenVerified) { - amrValues.add('email') + amrValues.add('email'); } - return amrValues + return amrValues; } get authenticatorAssuranceLevel() { - return authMethods.maximumAssuranceLevel(this.authenticationMethods) + return authMethods.maximumAssuranceLevel(this.authenticationMethods); } setUserAgentInfo(data) { - this.uaBrowser = data.uaBrowser - this.uaBrowserVersion = data.uaBrowserVersion - this.uaOS = data.uaOS - this.uaOSVersion = data.uaOSVersion - this.uaDeviceType = data.uaDeviceType - this.uaFormFactor = data.uaFormFactor + this.uaBrowser = data.uaBrowser; + this.uaBrowserVersion = data.uaBrowserVersion; + this.uaOS = data.uaOS; + this.uaOSVersion = data.uaOSVersion; + this.uaDeviceType = data.uaDeviceType; + this.uaFormFactor = data.uaFormFactor; if (data.lastAccessTime) { - this.lastAccessTime = data.lastAccessTime + this.lastAccessTime = data.lastAccessTime; } } setDeviceInfo(data) { - this.deviceId = data.deviceId - this.deviceName = data.deviceName - this.deviceType = data.deviceType - this.deviceCreatedAt = data.deviceCreatedAt - this.callbackURL = data.callbackURL - this.callbackPublicKey = data.callbackPublicKey - this.callbackAuthKey = data.callbackAuthKey - this.callbackIsExpired = data.callbackIsExpired + this.deviceId = data.deviceId; + this.deviceName = data.deviceName; + this.deviceType = data.deviceType; + this.deviceCreatedAt = data.deviceCreatedAt; + this.callbackURL = data.callbackURL; + this.callbackPublicKey = data.callbackPublicKey; + this.callbackAuthKey = data.callbackAuthKey; + this.callbackIsExpired = data.callbackIsExpired; } } - SessionToken.tokenTypeID = 'sessionToken' - return SessionToken -} + SessionToken.tokenTypeID = 'sessionToken'; + return SessionToken; +}; diff --git a/lib/tokens/token.js b/lib/tokens/token.js index 3aab0351..8364a1b9 100644 --- a/lib/tokens/token.js +++ b/lib/tokens/token.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/. */ -'use strict' +'use strict'; /* Base class for handling various types of token. * @@ -25,11 +25,11 @@ * */ -const Bundle = require('./bundle') -const hkdf = require('../crypto/hkdf') -const random = require('../crypto/random') +const Bundle = require('./bundle'); +const hkdf = require('../crypto/hkdf'); +const random = require('../crypto/random'); -const KEYS = ['data', 'id', 'authKey', 'bundleKey'] +const KEYS = ['data', 'id', 'authKey', 'bundleKey']; module.exports = (log, config) => { @@ -40,11 +40,11 @@ module.exports = (log, config) => { // function Token(keys, details) { KEYS.forEach(name => { - this[name] = keys[name] && keys[name].toString('hex') - }) - this.uid = details.uid || null - this.lifetime = details.lifetime || Infinity - this.createdAt = details.createdAt || 0 + this[name] = keys[name] && keys[name].toString('hex'); + }); + this.uid = details.uid || null; + this.lifetime = details.lifetime || Infinity; + this.createdAt = details.createdAt || 0; } // Create a new token of the given type. @@ -52,22 +52,22 @@ module.exports = (log, config) => { // Token.createNewToken = function(TokenType, details) { // Avoid modifying the argument. - details = Object.assign({}, details) - details.createdAt = Date.now() + details = Object.assign({}, details); + details.createdAt = Date.now(); return random(32) .then(bytes => Token.deriveTokenKeys(TokenType, bytes)) - .then(keys => new TokenType(keys, details)) - } + .then(keys => new TokenType(keys, details)); + }; // Re-create an existing token of the given type. // This uses known seed data to derive the keys. // Token.createTokenFromHexData = function(TokenType, hexData, details) { - var data = Buffer.from(hexData, 'hex') + var data = Buffer.from(hexData, 'hex'); return Token.deriveTokenKeys(TokenType, data) - .then(keys => new TokenType(keys, details || {})) - } + .then(keys => new TokenType(keys, details || {})); + }; // Derive id, authKey and bundleKey from token seed data. @@ -81,49 +81,49 @@ module.exports = (log, config) => { id: keyMaterial.slice(0, 32), authKey: keyMaterial.slice(32, 64), bundleKey: keyMaterial.slice(64, 96) - } + }; } - ) - } + ); + }; // Convenience method to bundle a payload using token bundleKey. // Token.prototype.bundle = function(keyInfo, payload) { - log.trace('Token.bundle') - return Bundle.bundle(this.bundleKey, keyInfo, payload) - } + log.trace('Token.bundle'); + return Bundle.bundle(this.bundleKey, keyInfo, payload); + }; // Convenience method to unbundle a payload using token bundleKey. // Token.prototype.unbundle = function(keyInfo, payload) { - log.trace('Token.unbundle') - return Bundle.unbundle(this.bundleKey, keyInfo, payload) - } + log.trace('Token.unbundle'); + return Bundle.unbundle(this.bundleKey, keyInfo, payload); + }; Token.prototype.ttl = function (asOf) { - asOf = asOf || Date.now() - var ttl = (this.lifetime - (asOf - this.createdAt)) / 1000 - return Math.max(Math.ceil(ttl), 0) - } + asOf = asOf || Date.now(); + var ttl = (this.lifetime - (asOf - this.createdAt)) / 1000; + return Math.max(Math.ceil(ttl), 0); + }; Token.prototype.expired = function (asOf) { - return this.ttl(asOf) === 0 - } + return this.ttl(asOf) === 0; + }; // Properties defined for HAWK Object.defineProperties( Token.prototype, { key: { - get: function () { return Buffer.from(this.authKey, 'hex') } + get: function () { return Buffer.from(this.authKey, 'hex'); } }, algorithm: { - get: function () { return 'sha256' } + get: function () { return 'sha256'; } } } - ) + ); - return Token -} + return Token; +}; diff --git a/lib/userAgent/index.js b/lib/userAgent/index.js index 98b7906e..e38b0ba8 100644 --- a/lib/userAgent/index.js +++ b/lib/userAgent/index.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const ua = require('node-uap') -const safe = require('./safe') +const ua = require('node-uap'); +const safe = require('./safe'); const MOBILE_OS_FAMILIES = new Set([ 'Android', @@ -25,7 +25,7 @@ const MOBILE_OS_FAMILIES = new Set([ 'Windows CE', 'Windows Mobile', 'Windows Phone' -]) +]); // $1 = 'Firefox' indicates Firefox Sync, 'Mobile' indicates Sync mobile library // $2 = OS @@ -33,10 +33,10 @@ const MOBILE_OS_FAMILIES = new Set([ // $4 = form factor // $5 = OS version // $6 = application name -const SYNC_USER_AGENT = /^(Firefox|Mobile)-(\w+)-(?:FxA(?:ccounts)?|Sync)\/([^\sb]*)(?:b\S+)? ?(?:\(([\w\s]+); [\w\s]+ ([^\s()]+)\))?(?: \((.+)\))?$/ +const SYNC_USER_AGENT = /^(Firefox|Mobile)-(\w+)-(?:FxA(?:ccounts)?|Sync)\/([^\sb]*)(?:b\S+)? ?(?:\(([\w\s]+); [\w\s]+ ([^\s()]+)\))?(?: \((.+)\))?$/; module.exports = function (userAgentString) { - const matches = SYNC_USER_AGENT.exec(userAgentString) + const matches = SYNC_USER_AGENT.exec(userAgentString); if (matches && matches.length > 2) { // Always parse known Sync user-agents ourselves, // because node-uap makes a pig's ear of it. @@ -47,10 +47,10 @@ module.exports = function (userAgentString) { osVersion: safe.version(matches[5]), deviceType: marshallDeviceType(matches[4]), formFactor: safe.name(matches[4]) - } + }; } - const userAgentData = ua.parse(userAgentString) + const userAgentData = ua.parse(userAgentString); return { browser: safe.name(getFamily(userAgentData.ua)), browserVersion: safe.version(userAgentData.ua.toVersionString()), @@ -58,62 +58,62 @@ module.exports = function (userAgentString) { osVersion: safe.version(userAgentData.os.toVersionString()), deviceType: getDeviceType(userAgentData) || null, formFactor: safe.name(getFormFactor(userAgentData)) - } -} + }; +}; function getFamily (data) { if (data.family && data.family !== 'Other') { - return data.family + return data.family; } } function getDeviceType (data) { if (getFamily(data.device) || isMobileOS(data.os)) { if (isTablet(data)) { - return 'tablet' + return 'tablet'; } else { - return 'mobile' + return 'mobile'; } } } function isMobileOS (os) { - return MOBILE_OS_FAMILIES.has(os.family) + return MOBILE_OS_FAMILIES.has(os.family); } function isTablet(data) { - return isIpad(data) || isAndroidTablet(data) || isKindle(data) || isGenericTablet(data) + return isIpad(data) || isAndroidTablet(data) || isKindle(data) || isGenericTablet(data); } function isIpad (data) { - return /iPad/.test(data.device.family) + return /iPad/.test(data.device.family); } function isAndroidTablet (data) { return data.os.family === 'Android' && data.userAgent.indexOf('Mobile') === -1 && - data.userAgent.indexOf('AndroidSync') === -1 + data.userAgent.indexOf('AndroidSync') === -1; } function isKindle (data) { - return /Kindle/.test(data.device.family) + return /Kindle/.test(data.device.family); } function isGenericTablet (data) { - return data.device.brand === 'Generic' && data.device.model === 'Tablet' + return data.device.brand === 'Generic' && data.device.model === 'Tablet'; } function getFormFactor (data) { if (data.device.brand !== 'Generic') { - return getFamily(data.device) + return getFamily(data.device); } } function marshallDeviceType (formFactor) { if (/iPad/.test(formFactor) || /tablet/i.test(formFactor)) { - return 'tablet' + return 'tablet'; } - return 'mobile' + return 'mobile'; } diff --git a/lib/userAgent/safe.js b/lib/userAgent/safe.js index cbe47c4a..a79261f9 100644 --- a/lib/userAgent/safe.js +++ b/lib/userAgent/safe.js @@ -2,25 +2,25 @@ * 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/. */ -'use strict' +'use strict'; // We know this won't match "Symbian^3", "UI/WKWebView" or "Mail.ru" but // it's simpler and better to limit to alphanumerics and space. -const VALID_NAME = /^[\w ]{1,32}$/ +const VALID_NAME = /^[\w ]{1,32}$/; -const VALID_VERSION = /^[\w.]{1,16}$/ +const VALID_VERSION = /^[\w.]{1,16}$/; module.exports = { name (string) { - return returnSafely(string, VALID_NAME) + return returnSafely(string, VALID_NAME); }, version (string) { - return returnSafely(string, VALID_VERSION) + return returnSafely(string, VALID_VERSION); } -} +}; function returnSafely (string, regex) { - return regex.test(string) ? string : null + return regex.test(string) ? string : null; } diff --git a/scripts/bulk-mailer.js b/scripts/bulk-mailer.js index e3989416..f35819a4 100755 --- a/scripts/bulk-mailer.js +++ b/scripts/bulk-mailer.js @@ -4,10 +4,10 @@ * 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/. */ -'use strict' +'use strict'; -const program = require('commander') -const path = require('path') +const program = require('commander'); +const path = require('path'); program .option('-b, --batchsize [size]', 'Number of emails to send in a batch. Defaults to 10', parseInt) @@ -17,40 +17,40 @@ program .option('-v, --verbose', 'Verbose logging') .option('-w, --write [directory]', 'Directory where emails should be stored') .option('--send', 'Send emails, for real. *** THIS REALLY SENDS ***') - .parse(process.argv) + .parse(process.argv); -const BATCH_DELAY_MS = typeof program.delay === 'undefined' ? 5000 : program.delay * 1000 -const BATCH_SIZE = program.batchsize || 10 +const BATCH_DELAY_MS = typeof program.delay === 'undefined' ? 5000 : program.delay * 1000; +const BATCH_SIZE = program.batchsize || 10; const requiredOptions = [ 'input', 'method' -] +]; -requiredOptions.forEach(checkRequiredOption) +requiredOptions.forEach(checkRequiredOption); // Loading the bulk-mailer is slow, only do // so after checking all the required options. -const bulkMailer = require('./bulk-mailer/index') +const bulkMailer = require('./bulk-mailer/index'); return bulkMailer(path.resolve(program.input), program.method, BATCH_SIZE, BATCH_DELAY_MS, program.send, program.write, program.verbose) .then(() => { console.log('done'); - process.exit(0) + process.exit(0); }, (err) => { if (/InvalidMethodName/.test(err.message)) { - console.error(program.method, 'is not a valid method. Can be one of:\n') - console.error(' * ' + err.validNames.sort().join('\n * ')) + console.error(program.method, 'is not a valid method. Can be one of:\n'); + console.error(' * ' + err.validNames.sort().join('\n * ')); } else { - console.error('Error', String(err)) + console.error('Error', String(err)); } process.exit(1); - }) + }); function checkRequiredOption(optionName) { if (! program[optionName]) { - console.error('--' + optionName + ' is required') - process.exit(1) + console.error('--' + optionName + ' is required'); + process.exit(1); } } diff --git a/scripts/bulk-mailer/index.js b/scripts/bulk-mailer/index.js index d17611ea..a5b0b065 100644 --- a/scripts/bulk-mailer/index.js +++ b/scripts/bulk-mailer/index.js @@ -2,27 +2,27 @@ * 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/. */ -'use strict' +'use strict'; -const chunk = require('lodash.chunk') -const config = require('../../config').getProperties() -const error = require('../../lib/error') -const P = require('../../lib/promise') -const readUserRecords = require('./read-user-records') -const sendEmailBatches = require('./send-email-batches') -const Senders = require('../../lib/senders') -const Translator = require('../../lib/senders/translator') -const UserRecordNormalizer = require('./normalize-user-records') -const WriteToStreamSenderMock = require('./nodemailer-mocks/stream-output-mock') -const WriteToDiskSenderMock = require('./nodemailer-mocks/write-to-disk-mock') +const chunk = require('lodash.chunk'); +const config = require('../../config').getProperties(); +const error = require('../../lib/error'); +const P = require('../../lib/promise'); +const readUserRecords = require('./read-user-records'); +const sendEmailBatches = require('./send-email-batches'); +const Senders = require('../../lib/senders'); +const Translator = require('../../lib/senders/translator'); +const UserRecordNormalizer = require('./normalize-user-records'); +const WriteToStreamSenderMock = require('./nodemailer-mocks/stream-output-mock'); +const WriteToDiskSenderMock = require('./nodemailer-mocks/write-to-disk-mock'); const bouncesMock = { check: () => P.resolve() -} +}; const oauthdbMock = { getClientInfo: () => P.reject('should not get called') -} +}; /** * Send an email to users listed in the file `userRecordFilename` using `mailerMethodName` @@ -47,62 +47,62 @@ module.exports = async function (userRecordsFilename, mailerMethodName, batchSiz error: console.error, info (msg) { if (useVerboseLogging) { - console.info(JSON.stringify(msg)) + console.info(JSON.stringify(msg)); } }, trace (msg) { if (useVerboseLogging) { - console.info(JSON.stringify(msg)) + console.info(JSON.stringify(msg)); } } - } + }; - const translator = await createTranslator(config) - const mailer = await createMailer(logMock, config, translator, shouldSend, emailOutputDirname) - const sendDelegate = createSendDelegate(mailer, mailerMethodName) + const translator = await createTranslator(config); + const mailer = await createMailer(logMock, config, translator, shouldSend, emailOutputDirname); + const sendDelegate = createSendDelegate(mailer, mailerMethodName); - const userRecords = await readUserRecords(userRecordsFilename) - const normalizedUserRecords = await normalizeUserRecords(userRecords, translator) - const batches = chunk(normalizedUserRecords, batchSize) + const userRecords = await readUserRecords(userRecordsFilename); + const normalizedUserRecords = await normalizeUserRecords(userRecords, translator); + const batches = chunk(normalizedUserRecords, batchSize); - const isTest = ! shouldSend + const isTest = ! shouldSend; - return sendEmailBatches(batches, batchDelayMS, sendDelegate, logMock, isTest) -} + return sendEmailBatches(batches, batchDelayMS, sendDelegate, logMock, isTest); +}; function normalizeUserRecords(userRecords, translator) { - const normalizer = new UserRecordNormalizer() - return normalizer.normalize(userRecords, translator) + const normalizer = new UserRecordNormalizer(); + return normalizer.normalize(userRecords, translator); } function getValidMailerMethodNames(mailer) { return Object.keys(mailer).filter(name => { - return typeof mailer[name] === 'function' && ! /translator|stop|_ungatedMailer/.test(name) - }) + return typeof mailer[name] === 'function' && ! /translator|stop|_ungatedMailer/.test(name); + }); } function isValidMailerMethod(mailer, method) { - const validMethods = getValidMailerMethodNames(mailer) + const validMethods = getValidMailerMethodNames(mailer); - return validMethods.indexOf(method) > -1 + return validMethods.indexOf(method) > -1; } function createSendDelegate (mailer, mailerMethodName) { if (! isValidMailerMethod(mailer, mailerMethodName)) { - const err = new Error(`InvalidMethodName: ${mailerMethodName}`) - err.validNames = getValidMailerMethodNames(mailer) - throw err + const err = new Error(`InvalidMethodName: ${mailerMethodName}`); + err.validNames = getValidMailerMethodNames(mailer); + throw err; } return (userRecord) => { - return mailer[mailerMethodName](userRecord.emails, userRecord, { acceptLanguage: userRecord.locale }) - } + return mailer[mailerMethodName](userRecord.emails, userRecord, { acceptLanguage: userRecord.locale }); + }; } async function createMailer (log, config, translator, shouldSend, emailOutputDirname) { - const sender = shouldSend ? null : createSenderMock(emailOutputDirname) + const sender = shouldSend ? null : createSenderMock(emailOutputDirname); - return (await Senders(log, config, error, bouncesMock, translator, oauthdbMock, sender)).email + return (await Senders(log, config, error, bouncesMock, translator, oauthdbMock, sender)).email; } function createSenderMock(emailOutputDirname) { @@ -110,15 +110,15 @@ function createSenderMock(emailOutputDirname) { return new WriteToDiskSenderMock({ failureRate: 0, outputDir: emailOutputDirname - }) + }); } return new WriteToStreamSenderMock({ failureRate: 0, stream: process.stdout - }) + }); } function createTranslator (config) { - return Translator(config.i18n.supportedLanguages, config.i18n.defaultLanguage) + return Translator(config.i18n.supportedLanguages, config.i18n.defaultLanguage); } diff --git a/scripts/bulk-mailer/nodemailer-mocks/nodemailer-mock.js b/scripts/bulk-mailer/nodemailer-mocks/nodemailer-mock.js index 4236e62e..dab4b780 100644 --- a/scripts/bulk-mailer/nodemailer-mocks/nodemailer-mock.js +++ b/scripts/bulk-mailer/nodemailer-mocks/nodemailer-mock.js @@ -2,26 +2,26 @@ * 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/. */ -'use strict' +'use strict'; module.exports = class NodemailerMock { constructor (config) { - this.messageId = 0 - this.failureRate = config.failureRate + this.messageId = 0; + this.failureRate = config.failureRate; } sendMail (emailConfig, callback) { if (Math.random() > this.failureRate) { - this.messageId++ + this.messageId++; callback(null, { message: 'good', messageId: this.messageId - }) + }); } else { - callback(new Error('uh oh')) + callback(new Error('uh oh')); } } close () { } -} +}; diff --git a/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js b/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js index 773836cf..2d5c4d00 100644 --- a/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js +++ b/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js @@ -2,32 +2,32 @@ * 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/. */ -'use strict' +'use strict'; -const NodemailerMock = require('./nodemailer-mock') +const NodemailerMock = require('./nodemailer-mock'); module.exports = class StreamOutputMock extends NodemailerMock { constructor(config) { - super(config) + super(config); - this.stream = config.stream || process.stdout + this.stream = config.stream || process.stdout; } sendMail (emailConfig, callback) { - this.stream.write('-----------------------------------\n') - this.stream.write(`headers: ${emailConfig.to}\n`) - this.stream.write('-----------------------------------\n') - this.stream.write(JSON.stringify(emailConfig.headers, null, 2) + '\n') - this.stream.write('-----------------------------------\n') - this.stream.write(`html: ${emailConfig.to}\n`) - this.stream.write('-----------------------------------\n') - this.stream.write(emailConfig.html + '\n') - this.stream.write('-----------------------------------\n') - this.stream.write(`text: ${emailConfig.to}\n`) - this.stream.write('-----------------------------------\n') - this.stream.write(emailConfig.text + '\n') - this.stream.write('===================================\n\n') + this.stream.write('-----------------------------------\n'); + this.stream.write(`headers: ${emailConfig.to}\n`); + this.stream.write('-----------------------------------\n'); + this.stream.write(JSON.stringify(emailConfig.headers, null, 2) + '\n'); + this.stream.write('-----------------------------------\n'); + this.stream.write(`html: ${emailConfig.to}\n`); + this.stream.write('-----------------------------------\n'); + this.stream.write(emailConfig.html + '\n'); + this.stream.write('-----------------------------------\n'); + this.stream.write(`text: ${emailConfig.to}\n`); + this.stream.write('-----------------------------------\n'); + this.stream.write(emailConfig.text + '\n'); + this.stream.write('===================================\n\n'); - return super.sendMail(emailConfig, callback) + return super.sendMail(emailConfig, callback); } -} +}; diff --git a/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js b/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js index f47958ef..8c992fc4 100644 --- a/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js +++ b/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js @@ -2,49 +2,49 @@ * 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/. */ -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') -const NodemailerMock = require('./nodemailer-mock') +const fs = require('fs'); +const path = require('path'); +const NodemailerMock = require('./nodemailer-mock'); module.exports = class WriteToDiskMock extends NodemailerMock { constructor (config) { - super(config) + super(config); - ensureOutputDirExists(config.outputDir) - this.outputDir = config.outputDir + ensureOutputDirExists(config.outputDir); + this.outputDir = config.outputDir; } sendMail (emailConfig, callback) { const targets = [emailConfig.to].concat(emailConfig.cc || []); targets.forEach(email => { - const outputPath = path.join(this.outputDir, email) + const outputPath = path.join(this.outputDir, email); - const textPath = outputPath + '.txt' - fs.writeFileSync(textPath, emailConfig.text) + const textPath = outputPath + '.txt'; + fs.writeFileSync(textPath, emailConfig.text); - const htmlPath = outputPath + '.html' - fs.writeFileSync(htmlPath, emailConfig.html) + const htmlPath = outputPath + '.html'; + fs.writeFileSync(htmlPath, emailConfig.html); - const headersPath = outputPath + '.headers' - fs.writeFileSync(headersPath, JSON.stringify(emailConfig.headers, null, 2)) + const headersPath = outputPath + '.headers'; + fs.writeFileSync(headersPath, JSON.stringify(emailConfig.headers, null, 2)); }); - return super.sendMail(emailConfig, callback) + return super.sendMail(emailConfig, callback); } -} +}; function ensureOutputDirExists(outputDir) { - let dirStats + let dirStats; try { - dirStats = fs.statSync(outputDir) + dirStats = fs.statSync(outputDir); } catch (e) { - fs.mkdirSync(outputDir) - return + fs.mkdirSync(outputDir); + return; } if (! dirStats.isDirectory()) { - throw new Error(`${outputDir} is not a directory`) + throw new Error(`${outputDir} is not a directory`); } } diff --git a/scripts/bulk-mailer/normalize-user-records.js b/scripts/bulk-mailer/normalize-user-records.js index b6f93e81..abd24397 100644 --- a/scripts/bulk-mailer/normalize-user-records.js +++ b/scripts/bulk-mailer/normalize-user-records.js @@ -2,9 +2,9 @@ * 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/. */ -'use strict' +'use strict'; -const leftpad = require('leftpad') +const leftpad = require('leftpad'); module.exports = class UserRecordNormalizer { /** @@ -19,15 +19,15 @@ module.exports = class UserRecordNormalizer { return userRecords // no email can be sent if the record does not contain an email .filter(record => !! record.email) - .map(userRecord => this.normalizeUserRecord(userRecord, translator)) + .map(userRecord => this.normalizeUserRecord(userRecord, translator)); } normalizeUserRecord (userRecord, translator) { - this.normalizeAcceptLanguage(userRecord) - this.normalizeLanguage(userRecord, translator) - this.normalizeLocations(userRecord) + this.normalizeAcceptLanguage(userRecord); + this.normalizeLanguage(userRecord, translator); + this.normalizeLocations(userRecord); - return userRecord + return userRecord; } normalizeAcceptLanguage(userRecord) { @@ -35,58 +35,58 @@ module.exports = class UserRecordNormalizer { // specified. We put these translations into "zh-cn", use "zh-cn" for // Taiwan as well. if (! userRecord.acceptLanguage && userRecord.locale) { - userRecord.acceptLanguage = userRecord.locale.replace(/zh-tw/gi, 'zh-cn') + userRecord.acceptLanguage = userRecord.locale.replace(/zh-tw/gi, 'zh-cn'); } } normalizeLanguage(userRecord, translator) { - const { language } = translator.getTranslator(userRecord.acceptLanguage) - userRecord.language = language + const { language } = translator.getTranslator(userRecord.acceptLanguage); + userRecord.language = language; } normalizeLocations(userRecord) { if (! userRecord.locations) { - userRecord.locations = [] + userRecord.locations = []; } else { - userRecord.locations.forEach(location => this.normalizeLocation(location, userRecord.language)) + userRecord.locations.forEach(location => this.normalizeLocation(location, userRecord.language)); } } normalizeLocation(location, language) { - this.normalizeLocationTimestamp(location) - this.normalizeLocationName(location, language) + this.normalizeLocationTimestamp(location); + this.normalizeLocationName(location, language); } normalizeLocationTimestamp(location) { - const timestamp = new Date(location.timestamp || location.date) - location.timestamp = this.formatDate(timestamp) + const timestamp = new Date(location.timestamp || location.date); + location.timestamp = this.formatDate(timestamp); } formatDate(date) { - return `${date.getUTCFullYear()}-${leftpad(date.getUTCMonth(), 2)}-${leftpad(date.getUTCDate(), 2)} @ ${leftpad(date.getUTCHours(), 2)}:${leftpad(date.getUTCMinutes(), 2)} UTC` + return `${date.getUTCFullYear()}-${leftpad(date.getUTCMonth(), 2)}-${leftpad(date.getUTCDate(), 2)} @ ${leftpad(date.getUTCHours(), 2)}:${leftpad(date.getUTCMinutes(), 2)} UTC`; } normalizeLocationName (location, language) { // first, try to generate a localized locality if (! location.location && location.citynames && location.countrynames) { - const parts = [] + const parts = []; - const localizedCityName = location.citynames[language] + const localizedCityName = location.citynames[language]; if (localizedCityName) { - parts.push(localizedCityName) + parts.push(localizedCityName); } - const localizedCountryName = location.countrynames[language] + const localizedCountryName = location.countrynames[language]; if (localizedCountryName) { - parts.push(localizedCountryName) + parts.push(localizedCountryName); } - location.location = parts.join(', ') + location.location = parts.join(', '); } // if that can't be done, fall back to the english locality if (! location.location && location.locality) { - location.location = location.locality + location.location = location.locality; } } -} +}; diff --git a/scripts/bulk-mailer/read-user-records.js b/scripts/bulk-mailer/read-user-records.js index 043723bc..3f428325 100644 --- a/scripts/bulk-mailer/read-user-records.js +++ b/scripts/bulk-mailer/read-user-records.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/. */ -'use strict' +'use strict'; /** * Read an array of user records from `userRecordsPath` @@ -11,11 +11,11 @@ * @returns {Promise} resolves to an array of user records when complete */ module.exports = async function readUserRecords(userRecordsPath) { - const records = require(userRecordsPath) + const records = require(userRecordsPath); if (! records || ! records.length) { - throw new Error('No records found') + throw new Error('No records found'); } - return records -} + return records; +}; diff --git a/scripts/bulk-mailer/send-email-batch.js b/scripts/bulk-mailer/send-email-batch.js index 6aa884ee..d6bf94dd 100644 --- a/scripts/bulk-mailer/send-email-batch.js +++ b/scripts/bulk-mailer/send-email-batch.js @@ -2,43 +2,43 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('../../lib/promise') +const P = require('../../lib/promise'); module.exports = function sendBatch(batch, sendEmail, log) { - let successCount = 0 - let errorCount = 0 + let successCount = 0; + let errorCount = 0; return P.all( batch.map(userRecord => { return sendEmail(userRecord) .then( () => { - successCount++ + successCount++; log.info({ op: 'send.success', email: userRecord.email - }) + }); }, (error) => { - errorCount++ + errorCount++; log.error({ op: 'send.error', email: userRecord.email, error: error - }) + }); // swallow errors, keep sending. We'll have to resend this manually. } - ) + ); }) ) .then(() => { return { errorCount, successCount, - } - }) -} + }; + }); +}; diff --git a/scripts/bulk-mailer/send-email-batches.js b/scripts/bulk-mailer/send-email-batches.js index 8497cbc8..0dcda1b9 100644 --- a/scripts/bulk-mailer/send-email-batches.js +++ b/scripts/bulk-mailer/send-email-batches.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('../../lib/promise') -const sendEmailBatch = require('./send-email-batch') +const P = require('../../lib/promise'); +const sendEmailBatch = require('./send-email-batch'); /** * Send batches of emails. @@ -17,34 +17,34 @@ const sendEmailBatch = require('./send-email-batch') * @param {Boolean} [isTest=false] is this a test run? */ module.exports = async function(userRecordBatches, batchDelayMS, sendEmail, log, isTest) { - let successCount = 0 - let errorCount = 0 - const lastBatchIndex = userRecordBatches.length - 1 - const totalCount = countEmails(userRecordBatches) + let successCount = 0; + let errorCount = 0; + const lastBatchIndex = userRecordBatches.length - 1; + const totalCount = countEmails(userRecordBatches); - logBegin(log, countEmails(userRecordBatches), isTest) + logBegin(log, countEmails(userRecordBatches), isTest); await P.each(userRecordBatches, (currentBatch, index) => { return sendEmailBatch(currentBatch, sendEmail, log) .then(result => { - successCount += result.successCount - errorCount += result.errorCount + successCount += result.successCount; + errorCount += result.errorCount; if (index !== lastBatchIndex) { // no delay on the last batch, with the // effect that a lone batch has no delay - return P.delay(batchDelayMS) + return P.delay(batchDelayMS); } - }) + }); }).finally(() => { // sending errors are swallowed in sendEmailBatch, even so, // that assumption might not always be valid, hence the .finally here logComplete(log, successCount, errorCount, totalCount - successCount - errorCount); - }) -} + }); +}; function countEmails(emailBatches) { - return emailBatches.reduce((total, batch) => total += batch.length, 0) + return emailBatches.reduce((total, batch) => total += batch.length, 0); } function logBegin(log, count, isTest) { @@ -52,7 +52,7 @@ function logBegin(log, count, isTest) { op: 'send.begin', count, test: isTest - }) + }); } function logComplete(log, successCount, errorCount, unsentCount) { @@ -62,6 +62,6 @@ function logComplete(log, successCount, errorCount, unsentCount) { errorCount, successCount, unsentCount, - }) + }); } diff --git a/scripts/delete-account.js b/scripts/delete-account.js index c3c1132a..9a403d79 100644 --- a/scripts/delete-account.js +++ b/scripts/delete-account.js @@ -18,34 +18,34 @@ /*/ -'use strict' +'use strict'; -const readline = require('readline') -const P = require('../lib/promise') -const config = require('../config').getProperties() -const log = require('../lib/log')(config.log.level) -const Token = require('../lib/tokens')(log, config) -const mailer = null +const readline = require('readline'); +const P = require('../lib/promise'); +const config = require('../config').getProperties(); +const log = require('../lib/log')(config.log.level); +const Token = require('../lib/tokens')(log, config); +const mailer = null; var DB = require('../lib/db')( config, log, Token -) +); return DB.connect(config[config.db.backend]).then(db => { // Bypass customs checks. - const mockCustoms = { check: () => { return P.resolve() } } + const mockCustoms = { check: () => { return P.resolve(); } }; // Bypass password checks. function MockPassword() { } - const signinUtils = require('../lib/routes/utils/signin')(log, config, mockCustoms, db, mailer) - signinUtils.checkPassword = function() { return P.resolve(true) } + const signinUtils = require('../lib/routes/utils/signin')(log, config, mockCustoms, db, mailer); + signinUtils.checkPassword = function() { return P.resolve(true); }; // Bypass TOTP checks. - db.totpToken = () => { return P.resolve(false) } + db.totpToken = () => { return P.resolve(false); }; // Load the account-deletion route, so we can use its logic directly. const accountDestroyRoute = require('../lib/routes/account')( @@ -57,7 +57,7 @@ return DB.connect(config[config.db.backend]).then(db => { mockCustoms, signinUtils, require('../lib/push')(log, db, config) - ).find(r => r.path === '/account/destroy') + ).find(r => r.path === '/account/destroy'); P.each(process.argv.slice(2), email => { @@ -65,16 +65,16 @@ return DB.connect(config[config.db.backend]).then(db => { // This is a pretty destructive action, ask the operator // to confirm each individual account deletion in turn. - console.log('Found account record:') - console.log(' uid:', account.uid) - console.log(' email:', account.email) + console.log('Found account record:'); + console.log(' uid:', account.uid); + console.log(' email:', account.email); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, - }) + }); return new P((resolve, reject) => { rl.question('Really delete this account? (y/n) ', answer => { - rl.close() + rl.close(); if (['y', 'yes'].indexOf(answer.toLowerCase()) === -1) { return reject('Cancelled'); } @@ -85,22 +85,22 @@ return DB.connect(config[config.db.backend]).then(db => { app: { clientAddress: '0.0.0.0' }, - emitMetricsEvent: () => { return P.resolve() }, - gatherMetricsContext: () => { return P.resolve({}) }, + emitMetricsEvent: () => { return P.resolve(); }, + gatherMetricsContext: () => { return P.resolve({}); }, payload: { email: email, authPW: 'mock password' } - } - accountDestroyRoute.handler(mockRequest).then(resolve, reject) - }) - }) + }; + accountDestroyRoute.handler(mockRequest).then(resolve, reject); + }); + }); }); }).then(() => { - console.log('ok') + console.log('ok'); }, err => { - console.log('ERROR:', err.message || err) + console.log('ERROR:', err.message || err); }).finally(() => { - return db.close() - }) -}) + return db.close(); + }); +}); diff --git a/scripts/dump-users.js b/scripts/dump-users.js index d122562f..5aa4feec 100644 --- a/scripts/dump-users.js +++ b/scripts/dump-users.js @@ -2,66 +2,66 @@ * 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/. */ -'use strict' +'use strict'; -const dumpUsers = require('./dump-users/index') -const fs = require('fs') -const path = require('path') -const program = require('commander') +const dumpUsers = require('./dump-users/index'); +const fs = require('fs'); +const path = require('path'); +const program = require('commander'); program .option('-e, --emails [emails]', 'Email addresses to dump, comma separated') .option('-u, --uids [uids]', 'User IDs to dump, comma separated') .option('-i, --input ', 'Input filename from which to read input if not specified on the command line') .option('-p, --pretty', 'Display output in a pretty fashion') - .parse(process.argv) + .parse(process.argv); if (! program.emails && ! program.uids) { - console.error('One of `emails` or `uids` must be specified') - process.exit(1) + console.error('One of `emails` or `uids` must be specified'); + process.exit(1); } else if (program.emails && program.uids) { - console.error('Only one of `emails` or `uids` can be specified, not both') - process.exit(1) + console.error('Only one of `emails` or `uids` can be specified, not both'); + process.exit(1); } -let emails = [] -let uids = [] +let emails = []; +let uids = []; if (program.emails) { - emails = getItems('emails') + emails = getItems('emails'); } else if (program.uids) { - uids = getItems('uids') + uids = getItems('uids'); } if (! emails.length && ! uids.length) { - console.error('No `emails` or `uids` specified') - process.exit(1) + console.error('No `emails` or `uids` specified'); + process.exit(1); } -const keys = emails.length ? emails : uids -const dbFunc = emails.length ? 'accountRecord': 'account' -dumpUsers(keys, dbFunc, program.pretty) +const keys = emails.length ? emails : uids; +const dbFunc = emails.length ? 'accountRecord': 'account'; +dumpUsers(keys, dbFunc, program.pretty); function getItems (type) { - let input = '' + let input = ''; if (typeof program[type] === 'string') { - input = program[type] + input = program[type]; } else if (! program.input) { - console.error(`--input must be specified if no argument given for ${type}`) - process.exit(1) + console.error(`--input must be specified if no argument given for ${type}`); + process.exit(1); } else { - input = fs.readFileSync(path.resolve(__dirname, program.input)).toString('utf8') + input = fs.readFileSync(path.resolve(__dirname, program.input)).toString('utf8'); } - return marshallInput(input) + return marshallInput(input); } function marshallInput(input = '') { if (! input.length) { - return [] + return []; } - const items = input.split(/[,\s]+/) - return items.filter(item => !! item.trim().length) + const items = input.split(/[,\s]+/); + return items.filter(item => !! item.trim().length); } diff --git a/scripts/dump-users/index.js b/scripts/dump-users/index.js index a48b3fa2..1817536f 100644 --- a/scripts/dump-users/index.js +++ b/scripts/dump-users/index.js @@ -2,28 +2,28 @@ * 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/. */ -'use strict' +'use strict'; -const pick = require('lodash.pick') +const pick = require('lodash.pick'); module.exports = function dumpUsers(keys, dbFunc, usePretty) { - const config = require('../../config').getProperties() + const config = require('../../config').getProperties(); const log = { error: (msg) => {}, info: (msg) => {}, trace: (msg) => {}, - } + }; - const Token = require('../../lib/tokens')(log, config) - const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength) - const P = require('../../lib/promise') + const Token = require('../../lib/tokens')(log, config); + const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength); + const P = require('../../lib/promise'); const DB = require('../../lib/db')( config, log, Token, UnblockCode - ) + ); let db; @@ -31,24 +31,24 @@ module.exports = function dumpUsers(keys, dbFunc, usePretty) { .then(_db => { db = _db; return P.mapSeries(keys, (item) => db[dbFunc](item).catch(err => { - console.error(String(err) + ' - ' + item) - process.exit(1) - })) + console.error(String(err) + ' - ' + item); + process.exit(1); + })); }) .then(marshallUserRecords) .then(records => { if (usePretty) { - console.log(JSON.stringify(records, null, 2)) + console.log(JSON.stringify(records, null, 2)); } else { - console.log(JSON.stringify(records)) + console.log(JSON.stringify(records)); } - return db.close() + return db.close(); }) .then(() => { - process.exit(0) - }) -} + process.exit(0); + }); +}; function marshallUserRecords(userRecords) { @@ -65,7 +65,7 @@ function marshallUserRecords(userRecords) { 'primaryEmail', 'profileChangedAt', 'uid', - ) + ); if (filteredRecord.devices) { Object.keys(filteredRecord.devices).forEach(id => { @@ -81,10 +81,10 @@ function marshallUserRecords(userRecords) { 'uaDeviceType', 'uaOS', 'uaOSVersion', - ) - }) + ); + }); } - return filteredRecord - }) + return filteredRecord; + }); } diff --git a/scripts/e2e-email/index.js b/scripts/e2e-email/index.js index 65870950..a90bf9af 100755 --- a/scripts/e2e-email/index.js +++ b/scripts/e2e-email/index.js @@ -3,18 +3,18 @@ * 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/. */ -'use strict' +'use strict'; -const crypto = require('crypto') -const commander = require('commander') +const crypto = require('crypto'); +const commander = require('commander'); -const P = require('../../lib/promise') -const Client = require('../../test/client')() -const mailbox = require('../../test/mailbox') -const validateEmail = require('./validate-email') +const P = require('../../lib/promise'); +const Client = require('../../test/client')(); +const mailbox = require('../../test/mailbox'); +const validateEmail = require('./validate-email'); -const emailMessages = {} -var program +const emailMessages = {}; +var program; function configure() { commander @@ -30,51 +30,51 @@ function configure() { .option('-L, --locale ', 'Test only this csv list of locales', function(list) { - return list.split(/,/) + return list.split(/,/); }) - .parse(process.argv) + .parse(process.argv); - commander.basename = crypto.randomBytes(8).toString('hex') - commander.password = crypto.randomBytes(16).toString('hex') + commander.basename = crypto.randomBytes(8).toString('hex'); + commander.password = crypto.randomBytes(16).toString('hex'); commander.supportedLanguages = commander.locale || - require(commander.locales).slice(0) + require(commander.locales).slice(0); - var mailserver = commander.mailserver = mailbox(commander.restmailDomain, 80) + var mailserver = commander.mailserver = mailbox(commander.restmailDomain, 80); mailserver.eventEmitter.on('email:message', function(email, message) { - emailMessages[email] = emailMessages[email] || [] - emailMessages[email].push(message) - }) + emailMessages[email] = emailMessages[email] || []; + emailMessages[email].push(message); + }); mailserver.eventEmitter.on('email:error', function(email, error) { - emailMessages[email] = emailMessages[email] || [] - emailMessages[email].push(error) - }) + emailMessages[email] = emailMessages[email] || []; + emailMessages[email].push(error); + }); - return commander + return commander; } function log(level /*, rest */) { - if (level < log.level) return - var args = Array.prototype.slice.call(arguments) - var timestamp = '[' + new Date().toISOString() + ']' - args[0] = timestamp - console.log.apply(null, args) + if (level < log.level) return; + var args = Array.prototype.slice.call(arguments); + var timestamp = '[' + new Date().toISOString() + ']'; + args[0] = timestamp; + console.log.apply(null, args); } -log.ERROR = 3 -log.INFO = 2 -log.DEBUG = 1 -log.level = log.INFO +log.ERROR = 3; +log.INFO = 2; +log.DEBUG = 1; +log.level = log.INFO; function emailFromLang(lang) { - return program.basename + '-' + lang + '@' + program.restmailDomain + return program.basename + '-' + lang + '@' + program.restmailDomain; } function langFromEmail(email) { // is like 'deadbeef-es@...' or 'deadbeef-es-AR@...' - return email.split('@')[0].match(/^[^-]*-([^-]*(?:-[^-]*)?)/)[1] + return email.split('@')[0].match(/^[^-]*-([^-]*(?:-[^-]*)?)/)[1]; } /* @@ -93,75 +93,75 @@ function langFromEmail(email) { */ function signupForSync(lang) { - var email = emailFromLang(lang) + var email = emailFromLang(lang); var options = { service: 'sync', keys: true, lang: lang - } + }; return Client.createAndVerify(program.authServer, email, program.password, program.mailserver, - options) + options); } function signinAsSecondDevice(client) { - var email = client.email - var password = program.password + var email = client.email; + var password = program.password; var opts = { service: 'sync', keys: true, reason: 'signin', lang: client.options.lang - } + }; return Client.login(program.authServer, email, password, opts) .then(function(client) { return client.keys() .then(function () { - return fetchNotificationEmail(client) - }) - }) + return fetchNotificationEmail(client); + }); + }); } function changePassword(client) { - var email = client.email - var password = program.password - var lang = langFromEmail(email) + var email = client.email; + var password = program.password; + var lang = langFromEmail(email); var headers = { 'accept-language': lang - } + }; return client.changePassword(password, headers, client.sessionToken) .then(function () { - return fetchNotificationEmail(client) - }) + return fetchNotificationEmail(client); + }); } function passwordReset(client) { - var email = client.email - var lang = langFromEmail(email) + var email = client.email; + var lang = langFromEmail(email); var headers = { 'accept-language': lang - } + }; return client.forgotPassword(lang) .then(function () { - return program.mailserver.waitForCode(email) + return program.mailserver.waitForCode(email); }) .then(function (code) { - return client.verifyPasswordResetCode(code, headers) + return client.verifyPasswordResetCode(code, headers); }) .then(function() { - return client.resetPassword(program.password, headers) + return client.resetPassword(program.password, headers); }) .then(function () { - return fetchNotificationEmail(client) - }) + return fetchNotificationEmail(client); + }); } function fetchNotificationEmail(client) { @@ -169,72 +169,72 @@ function fetchNotificationEmail(client) { // password-change, password-reset). return program.mailserver.waitForEmail(client.email) .then(function () { - return client - }) + return client; + }); } function checkLocale(lang, index) { // AWS SES in `stage` has rate-limiting of 5/sec, so start slow. - var delay = index * 750 + var delay = index * 750; return P.delay(delay) .then(function() { - log(log.INFO, 'Starting', lang) + log(log.INFO, 'Starting', lang); return signupForSync(lang) .then(signinAsSecondDevice) .then(changePassword) - .then(passwordReset) - }) + .then(passwordReset); + }); } function dumpMessages(messages) { - console.log('---') - console.log('--- Dumping messages ---') - console.log('---') + console.log('---'); + console.log('--- Dumping messages ---'); + console.log('---'); Object.keys(messages) .map(function(key) { - console.log('--- %s ---', key) + console.log('--- %s ---', key); emailMessages[key] .map(function(email) { - console.log(email.to[0], email.subject) - }) - }) + console.log(email.to[0], email.subject); + }); + }); } function main() { - program = configure() + program = configure(); - var checks = program.supportedLanguages.map(checkLocale) + var checks = program.supportedLanguages.map(checkLocale); P.all(checks) .then(function() { if (process.env.DEBUG) { - dumpMessages(emailMessages) + dumpMessages(emailMessages); } - var errors = validateEmail(emailMessages, log) - var output = [] - var errorCount = 0 + var errors = validateEmail(emailMessages, log); + var output = []; + var errorCount = 0; Object.keys(errors).sort().forEach(function(lang) { - output.push(' ' + lang + ':') - var errorList = errors[lang] + output.push(' ' + lang + ':'); + var errorList = errors[lang]; errorList.forEach(function(err) { - errorCount++ - output.push(' ' + err) - }) - }) + errorCount++; + output.push(' ' + err); + }); + }); if (errorCount > 0) { - console.log('\nLocalization or other email errors found. However, some untranslated') - console.log('locales are listed in ./localeQuirks to get the full state.\n') - console.log(output.join('\n')) - process.exit(1) + console.log('\nLocalization or other email errors found. However, some untranslated'); + console.log('locales are listed in ./localeQuirks to get the full state.\n'); + console.log(output.join('\n')); + process.exit(1); } else { - console.log('\nAll strings expected to be translated are ok.\n') - process.exit(0) + console.log('\nAll strings expected to be translated are ok.\n'); + process.exit(0); } }).catch(function(err) { - log(log.ERROR, err.stack || err) - process.exit(1) - }) + log(log.ERROR, err.stack || err); + process.exit(1); + }); } -main() +main(); diff --git a/scripts/e2e-email/localeQuirks.js b/scripts/e2e-email/localeQuirks.js index 77241a81..e02ea32f 100644 --- a/scripts/e2e-email/localeQuirks.js +++ b/scripts/e2e-email/localeQuirks.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/. */ -'use strict' +'use strict'; // Not all non-english locales have tranlated some things yet. // So there are these unfortunate bits of custom mappings that @@ -100,22 +100,22 @@ var translationQuirks = { 'ko', 'lt', ], -} +}; function ary2map(ary) { - var map = {} + var map = {}; ary.forEach(function(val) { if (map[val]) { - console.log('Duplicate!:', val) + console.log('Duplicate!:', val); } - map[val] = 1 - }) - return map + map[val] = 1; + }); + return map; } Object.keys(translationQuirks).forEach(function(quirk) { - var locales = translationQuirks[quirk] - translationQuirks[quirk] = ary2map(locales) -}) + var locales = translationQuirks[quirk]; + translationQuirks[quirk] = ary2map(locales); +}); -module.exports = translationQuirks +module.exports = translationQuirks; diff --git a/scripts/e2e-email/validate-email.js b/scripts/e2e-email/validate-email.js index 301ffe87..ccf1012a 100644 --- a/scripts/e2e-email/validate-email.js +++ b/scripts/e2e-email/validate-email.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const util = require('util') -const url = require('url') +const util = require('util'); +const url = require('url'); -const localeQuirks = require('./localeQuirks') +const localeQuirks = require('./localeQuirks'); -var errors = {} +var errors = {}; function reportError(lang, msg) { if (! errors[lang]) { - errors[lang] = [] + errors[lang] = []; } - errors[lang].push(msg) + errors[lang].push(msg); } function langFromEmail(email) { // is like 'deadbeef-es@...' or 'deadbeef-es-AR@...' - return email.split('@')[0].match(/^[^-]*-([^-]*(?:-[^-]*)?)/)[1] + return email.split('@')[0].match(/^[^-]*-([^-]*(?:-[^-]*)?)/)[1]; } function ensureHeader(headers, key, lang) { if (! headers[key]) { - reportError(lang, 'Missing header ' + key) + reportError(lang, 'Missing header ' + key); } } @@ -65,69 +65,69 @@ var messageContentChecks = [ args: [ 'email', 'reset_password_confirm' ], xheaders: [], } -] +]; function ensureSubjectLang(lang, subject, expectedSubject) { // If it's listed in quirks, expect 'en' content equivalent - var quirks = localeQuirks[expectedSubject] + var quirks = localeQuirks[expectedSubject]; if (quirks && quirks[lang]) { if (subject !== expectedSubject) { // en-GB is almost identical to en, except for... fugly - var en_sync = 'A new device is now syncing to your Firefox Account' - var en_gb_sync = 'A new device is now synchronising to your Firefox Account' + var en_sync = 'A new device is now syncing to your Firefox Account'; + var en_gb_sync = 'A new device is now synchronising to your Firefox Account'; if (! (lang === 'en-GB' && expectedSubject === en_sync && subject === en_gb_sync)) { reportError(lang, util.format('strings should be equal: "%s" vs. "%s"', - subject, expectedSubject)) + subject, expectedSubject)); } } } else { if (subject === expectedSubject) { reportError(lang, util.format('strings should not be equal: "%s" vs. "%s"', - subject, expectedSubject)) + subject, expectedSubject)); } } } function checkContent(mail, idx) { - var contentChecks = messageContentChecks[idx] - var lang = langFromEmail(mail.headers.to) - ensureSubjectLang(lang, mail.subject, contentChecks.subject) + var contentChecks = messageContentChecks[idx]; + var lang = langFromEmail(mail.headers.to); + ensureSubjectLang(lang, mail.subject, contentChecks.subject); - var missing = [] + var missing = []; contentChecks.xheaders.forEach(function(xheader) { if (! mail.headers[xheader]) { - missing.push(xheader) + missing.push(xheader); } - }) + }); if (missing.length !== 0) { - reportError(lang, 'missing x-headers ' + JSON.stringify(missing)) + reportError(lang, 'missing x-headers ' + JSON.stringify(missing)); } - var xlink = url.parse(mail.headers['x-link'], true) + var xlink = url.parse(mail.headers['x-link'], true); if (xlink.pathname !== contentChecks.pathname) { reportError(lang, util.format('wrong xlink pathname: %s vs %s', - xlink.pathname, contentChecks.pathname)) + xlink.pathname, contentChecks.pathname)); } - var args = JSON.stringify(contentChecks.args.sort()) - var queryArgs = JSON.stringify(Object.keys(xlink.query).sort()) + var args = JSON.stringify(contentChecks.args.sort()); + var queryArgs = JSON.stringify(Object.keys(xlink.query).sort()); if (args !== queryArgs) { - reportError(lang, mail.headers['x-link'] + ' - args mismatch ' + args + ' - ' + queryArgs) + reportError(lang, mail.headers['x-link'] + ' - args mismatch ' + args + ' - ' + queryArgs); } } function ensureNonZeroContent(body, errmsg, lang) { if (body.length === 0) { - reportError(lang, errmsg + ' has zero length') + reportError(lang, errmsg + ' has zero length'); } } function verifyMailbox(mbox) { - var lang = langFromEmail(mbox[0].headers.to) - var expectedMessageCount = 6 + var lang = langFromEmail(mbox[0].headers.to); + var expectedMessageCount = 6; if (mbox.length !== expectedMessageCount) { - return reportError(lang, 'Missing email response, count: ' + mbox.length) + return reportError(lang, 'Missing email response, count: ' + mbox.length); } mbox.forEach(function(mail, idx) { @@ -140,37 +140,37 @@ function verifyMailbox(mbox) { 'content-language', 'content-type', 'dkim-signature' - ] + ]; - var lang = langFromEmail(mail.headers.to) + var lang = langFromEmail(mail.headers.to); requiredHeaders.forEach(function(key) { - ensureHeader(mail.headers, key, lang) - }) + ensureHeader(mail.headers, key, lang); + }); - var quirks = localeQuirks['content-language'] + var quirks = localeQuirks['content-language']; if (quirks[lang]) { if ('en-US' !== mail.headers['content-language']) { - reportError(lang, 'content-language header is not en-US') + reportError(lang, 'content-language header is not en-US'); } } else { // See https://github.com/mozilla/fxa-content-server-l10n/issues/44 about sr-LATN if (lang !== mail.headers['content-language'] && lang !== 'sr-LATN') { - var fmt = 'content-language header is not locale specific for %s (%s)' - reportError(lang, util.format(fmt, lang, mail.headers.subject)) + var fmt = 'content-language header is not locale specific for %s (%s)'; + reportError(lang, util.format(fmt, lang, mail.headers.subject)); } } - ensureNonZeroContent(mail.html.length, 'mail message html', lang) - ensureNonZeroContent(mail.text.length, 'mail message text', lang) + ensureNonZeroContent(mail.html.length, 'mail message html', lang); + ensureNonZeroContent(mail.text.length, 'mail message text', lang); - checkContent(mail, idx) - }) + checkContent(mail, idx); + }); } module.exports = function validateEmail(messages) { Object.keys(messages) .forEach(function(key) { - verifyMailbox(messages[key]) - }) - return errors -} + verifyMailbox(messages[key]); + }); + return errors; +}; diff --git a/scripts/email-config.js b/scripts/email-config.js index 875a2324..d50adb17 100644 --- a/scripts/email-config.js +++ b/scripts/email-config.js @@ -2,20 +2,20 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '..' -const LIB_DIR = `${ROOT_DIR}/lib` +const ROOT_DIR = '..'; +const LIB_DIR = `${ROOT_DIR}/lib`; -const config = require(`${ROOT_DIR}/config`).getProperties() -const log = require(`${ROOT_DIR}/test/mocks`).mockLog() -const Promise = require(`${LIB_DIR}/promise`) -const redis = require(`${LIB_DIR}/redis`)({ ...config.redis, ...config.redis.email }, log) -const safeRegex = require('safe-regex') +const config = require(`${ROOT_DIR}/config`).getProperties(); +const log = require(`${ROOT_DIR}/test/mocks`).mockLog(); +const Promise = require(`${LIB_DIR}/promise`); +const redis = require(`${LIB_DIR}/redis`)({ ...config.redis, ...config.redis.email }, log); +const safeRegex = require('safe-regex'); if (! redis) { - console.error('Redis is disabled in config, aborting') - process.exit(1) + console.error('Redis is disabled in config, aborting'); + process.exit(1); } const COMMANDS = { @@ -23,7 +23,7 @@ const COMMANDS = { write, revert, check -} +}; // Ops note: if you need to check the raw values in redis from the production admin server do: // @@ -39,150 +39,150 @@ const COMMANDS = { const KEYS = { current: 'config', previous: 'config.previous' -} -const VALID_SERVICES = new Set([ 'sendgrid', 'ses', 'socketlabs' ]) +}; +const VALID_SERVICES = new Set([ 'sendgrid', 'ses', 'socketlabs' ]); const VALID_PROPERTIES = new Map([ [ 'percentage', value => value >= 0 && value <= 100 ], [ 'regex', value => value && typeof value === 'string' && value.indexOf('"') === -1 && safeRegex(value) ] -]) +]); -const { argv } = process +const { argv } = process; main() - .then(() => redis.close()) + .then(() => redis.close()); async function main () { try { - const command = argv[2] + const command = argv[2]; switch (command) { case 'read': case 'write': case 'revert': - assertArgs(0) - break + assertArgs(0); + break; case 'check': - assertArgs(1) - break + assertArgs(1); + break; default: - usageError() + usageError(); } - const result = await COMMANDS[command](...argv.slice(3)) + const result = await COMMANDS[command](...argv.slice(3)); if (result) { - console.log(result) + console.log(result); } } catch (error) { - console.error(error.message) - process.exit(1) + console.error(error.message); + process.exit(1); } } function assertArgs (count) { if (argv.length !== count + 3) { - usageError() + usageError(); } } function usageError () { - const scriptName = argv[1].substr(argv[1].indexOf('/scripts/') + 1) + const scriptName = argv[1].substr(argv[1].indexOf('/scripts/') + 1); throw new Error([ 'Usage:', `${scriptName} read - Read the current config to stdout`, `${scriptName} write - Write the current config from stdin`, `${scriptName} revert - Undo the last write or revert`, `${scriptName} check - Check whether matches config` - ].join('\n')) + ].join('\n')); } async function read () { - const current = await redis.get(KEYS.current) + const current = await redis.get(KEYS.current); if (current) { // Parse then stringify for pretty printing - return JSON.stringify(JSON.parse(current), null, ' ') + return JSON.stringify(JSON.parse(current), null, ' '); } } async function write () { - const current = JSON.parse(await stdin()) - const services = Object.entries(current) + const current = JSON.parse(await stdin()); + const services = Object.entries(current); if (services.length === 0) { - throw new Error('Empty config') + throw new Error('Empty config'); } services.forEach(([ service, serviceConfig ]) => { if (! VALID_SERVICES.has(service)) { - throw new Error(`Invalid service "${service}"`) + throw new Error(`Invalid service "${service}"`); } - const properties = Object.entries(serviceConfig) + const properties = Object.entries(serviceConfig); if (properties.length === 0) { - throw new Error(`Empty config for "${service}"`) + throw new Error(`Empty config for "${service}"`); } properties.forEach(([ property, value ]) => { if (! VALID_PROPERTIES.has(property)) { - throw new Error(`Invalid property "${service}.${property}"`) + throw new Error(`Invalid property "${service}.${property}"`); } if (! VALID_PROPERTIES.get(property)(value)) { - throw new Error(`Invalid value for "${service}.${property}"`) + throw new Error(`Invalid value for "${service}.${property}"`); } - }) - }) + }); + }); - const previous = await redis.get(KEYS.current) - await redis.set(KEYS.current, JSON.stringify(current)) + const previous = await redis.get(KEYS.current); + await redis.set(KEYS.current, JSON.stringify(current)); if (previous) { - await redis.set(KEYS.previous, previous) + await redis.set(KEYS.previous, previous); } } function stdin () { return new Promise((resolve, reject) => { - const chunks = [] + const chunks = []; process.stdin.on('readable', () => { - const chunk = process.stdin.read() + const chunk = process.stdin.read(); if (chunk !== null) { - chunks.push(chunk) + chunks.push(chunk); } - }) - process.stdin.on('error', reject) - process.stdin.on('end', () => resolve(chunks.join(''))) - }) + }); + process.stdin.on('error', reject); + process.stdin.on('end', () => resolve(chunks.join(''))); + }); } async function revert () { - const previous = await redis.get(KEYS.previous) - const current = await redis.get(KEYS.current) + const previous = await redis.get(KEYS.previous); + const current = await redis.get(KEYS.current); if (previous) { - await redis.set(KEYS.current, previous) + await redis.set(KEYS.current, previous); } else { - await redis.del(KEYS.current) + await redis.del(KEYS.current); } if (current) { - await redis.set(KEYS.previous, current) + await redis.set(KEYS.previous, current); } } async function check (emailAddress) { - const current = await redis.get(KEYS.current) + const current = await redis.get(KEYS.current); if (current) { - const config = JSON.parse(await redis.get(KEYS.current)) + const config = JSON.parse(await redis.get(KEYS.current)); const result = Object.entries(config) .filter(([ sender, senderConfig ]) => { if (senderConfig.regex) { - return new RegExp(senderConfig.regex).test(emailAddress) + return new RegExp(senderConfig.regex).test(emailAddress); } - return true + return true; }) .reduce((matches, [ sender, senderConfig ]) => { - matches[sender] = senderConfig - return matches - }, {}) - return JSON.stringify(result, null, ' ') + matches[sender] = senderConfig; + return matches; + }, {}); + return JSON.stringify(result, null, ' '); } } diff --git a/scripts/gen_keys.js b/scripts/gen_keys.js index a6c20740..b7478f08 100755 --- a/scripts/gen_keys.js +++ b/scripts/gen_keys.js @@ -20,26 +20,26 @@ keypair. */ -'use strict' +'use strict'; -const fs = require('fs') -const cp = require('child_process') -const assert = require('assert') -const crypto = require('crypto') +const fs = require('fs'); +const cp = require('child_process'); +const assert = require('assert'); +const crypto = require('crypto'); if (! process.env.NODE_ENV) { - process.env.NODE_ENV = 'dev' + process.env.NODE_ENV = 'dev'; } -const config = require('../config') -const pubKeyFile = config.get('publicKeyFile') -const secretKeyFile = config.get('secretKeyFile') +const config = require('../config'); +const pubKeyFile = config.get('publicKeyFile'); +const secretKeyFile = config.get('secretKeyFile'); try { - var keysExist = fs.existsSync(pubKeyFile) && fs.existsSync(secretKeyFile) - assert(! keysExist, 'keys already exists') + var keysExist = fs.existsSync(pubKeyFile) && fs.existsSync(secretKeyFile); + assert(! keysExist, 'keys already exists'); } catch (e) { - process.exit() + process.exit(); } // We tag our keys with their creation time, and a unique key id @@ -50,16 +50,16 @@ try { // "fxa-createdAt": 1489716000, // } function addKeyProperties(key) { - var now = new Date() - key.kty = 'RSA' + var now = new Date(); + key.kty = 'RSA'; key.kid = now.toISOString().slice(0, 10) + '-' + - crypto.createHash('sha256').update(key.n).update(key.e).digest('hex').slice(0, 32) + crypto.createHash('sha256').update(key.n).update(key.e).digest('hex').slice(0, 32); // Timestamp to nearest hour; consumers don't need to know the precise time. - key['fxa-createdAt'] = Math.round(now / 1000 / 3600) * 3600 - return key + key['fxa-createdAt'] = Math.round(now / 1000 / 3600) * 3600; + return key; } -console.log('Generating keypair') +console.log('Generating keypair'); cp.exec( 'openssl genrsa 2048 | ../node_modules/pem-jwk/bin/pem-jwk.js', @@ -67,18 +67,18 @@ cp.exec( cwd: __dirname }, function (err, stdout, stderr) { - var s = JSON.parse(stdout) - addKeyProperties(s) - fs.writeFileSync(secretKeyFile, JSON.stringify(s)) - console.error('Secret Key saved:', secretKeyFile) + var s = JSON.parse(stdout); + addKeyProperties(s); + fs.writeFileSync(secretKeyFile, JSON.stringify(s)); + console.error('Secret Key saved:', secretKeyFile); var pub = { kid: s.kid, kty: s.kty, 'fxa-createdAt': s['fxa-createdAt'], n: s.n, e: s.e - } - fs.writeFileSync(pubKeyFile, JSON.stringify(pub)) - console.error('Public Key saved:', pubKeyFile) + }; + fs.writeFileSync(pubKeyFile, JSON.stringify(pub)); + console.error('Public Key saved:', pubKeyFile); } -) +); diff --git a/scripts/gen_vapid_keys.js b/scripts/gen_vapid_keys.js index 577b4957..fb81f270 100755 --- a/scripts/gen_vapid_keys.js +++ b/scripts/gen_vapid_keys.js @@ -19,30 +19,30 @@ keypair. */ -'use strict' +'use strict'; -const fs = require('fs') -const webpush = require('web-push') +const fs = require('fs'); +const webpush = require('web-push'); if (! process.env.NODE_ENV) { - process.env.NODE_ENV = 'dev' + process.env.NODE_ENV = 'dev'; } -const config = require('../config') -const vapidKeysFile = config.get('vapidKeysFile') +const config = require('../config'); +const vapidKeysFile = config.get('vapidKeysFile'); -var fileExists = fs.existsSync(vapidKeysFile) +var fileExists = fs.existsSync(vapidKeysFile); if (fileExists) { - console.log('keys file already exists') - process.exit() + console.log('keys file already exists'); + process.exit(); } -console.error('Generating key for VAPID') +console.error('Generating key for VAPID'); -var keys = webpush.generateVAPIDKeys() +var keys = webpush.generateVAPIDKeys(); fs.writeFileSync(vapidKeysFile, JSON.stringify({ privateKey: keys.privateKey.toString('base64'), publicKey: keys.publicKey.toString('base64') -})) +})); -console.error('Done:', vapidKeysFile) +console.error('Done:', vapidKeysFile); diff --git a/scripts/mocha-coverage.js b/scripts/mocha-coverage.js index f055ae72..8c8ce8c7 100755 --- a/scripts/mocha-coverage.js +++ b/scripts/mocha-coverage.js @@ -4,25 +4,25 @@ * 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/. */ -'use strict' +'use strict'; -const path = require('path') -const spawn = require('child_process').spawn +const path = require('path'); +const spawn = require('child_process').spawn; -const MOCHA_BIN = path.join(path.dirname(__dirname), 'node_modules', '.bin', 'mocha') -const NYC_BIN = path.join(path.dirname(__dirname), 'node_modules', '.bin', 'nyc') +const MOCHA_BIN = path.join(path.dirname(__dirname), 'node_modules', '.bin', 'mocha'); +const NYC_BIN = path.join(path.dirname(__dirname), 'node_modules', '.bin', 'nyc'); -let bin = NYC_BIN -let argv = ['--cache', '--no-clean', MOCHA_BIN] +let bin = NYC_BIN; +let argv = ['--cache', '--no-clean', MOCHA_BIN]; if (process.env.NO_COVERAGE) { - bin = MOCHA_BIN - argv = [] + bin = MOCHA_BIN; + argv = []; } -const p = spawn(bin, argv.concat(process.argv.slice(2)), { stdio: 'inherit', env: process.env }) +const p = spawn(bin, argv.concat(process.argv.slice(2)), { stdio: 'inherit', env: process.env }); // exit this process with the same exit code as the test process p.on('close', function (code) { - process.exit(code) -}) + process.exit(code); +}); diff --git a/scripts/must-reset.js b/scripts/must-reset.js index ec60a81d..79c9cd27 100644 --- a/scripts/must-reset.js +++ b/scripts/must-reset.js @@ -15,42 +15,42 @@ /*/ -'use strict' +'use strict'; -var butil = require('../lib/crypto/butil') -var commandLineOptions = require('commander') -var config = require('../config').getProperties() -var crypto = require('crypto') -var log = require('../lib/log')(config.log.level) -var P = require('../lib/promise') -var path = require('path') -var Token = require('../lib/tokens')(log, config) +var butil = require('../lib/crypto/butil'); +var commandLineOptions = require('commander'); +var config = require('../config').getProperties(); +var crypto = require('crypto'); +var log = require('../lib/log')(config.log.level); +var P = require('../lib/promise'); +var path = require('path'); +var Token = require('../lib/tokens')(log, config); commandLineOptions .option('-i, --input ', 'JSON input file') - .parse(process.argv) + .parse(process.argv); var requiredOptions = [ 'input' -] +]; -requiredOptions.forEach(checkRequiredOption) +requiredOptions.forEach(checkRequiredOption); var DB = require('../lib/db')( config, log, Token -) +); DB.connect(config[config.db.backend]) .then( function (db) { - var json = require(path.resolve(commandLineOptions.input)) + var json = require(path.resolve(commandLineOptions.input)); var uids = json.map(function (entry) { - return entry.uid - }) + return entry.uid; + }); return P.all(uids.map( function (uid) { @@ -64,26 +64,26 @@ DB.connect(config[config.db.backend]) } ) .catch(function (err) { - log.error({ op: 'reset.failed', uid: uid, err: err }) - process.exit(1) - }) + log.error({ op: 'reset.failed', uid: uid, err: err }); + process.exit(1); + }); } )) .then( function () { - log.info({ complete: true, uidsReset: uids.length }) + log.info({ complete: true, uidsReset: uids.length }); }, function (err) { - log.error(err) + log.error(err); } ) - .then(db.close.bind(db)) + .then(db.close.bind(db)); } - ) + ); function checkRequiredOption(optionName) { if (! commandLineOptions[optionName]) { - console.error('--' + optionName + ' required') - process.exit(1) + console.error('--' + optionName + ' required'); + process.exit(1); } } diff --git a/scripts/rpm-version.js b/scripts/rpm-version.js index 96efc2ac..4e82c21b 100755 --- a/scripts/rpm-version.js +++ b/scripts/rpm-version.js @@ -3,10 +3,10 @@ * 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/. */ -'use strict' +'use strict'; -var cp = require('child_process') -var util = require('util') +var cp = require('child_process'); +var util = require('util'); // Generate legacy-format output that looks something like: // @@ -22,17 +22,17 @@ var util = require('util') // This content is placed in the stage/prod rpm at `./config/version.json`. // Ignore errors and always produce a (possibly empty struct) output. -var args = '{"hash":"%H","subject":"%s","committer date":"%ct"}' -var cmd = util.format('git --no-pager log --format=format:\'%s\' -1', args) +var args = '{"hash":"%H","subject":"%s","committer date":"%ct"}'; +var cmd = util.format('git --no-pager log --format=format:\'%s\' -1', args); cp.exec(cmd, function (err, stdout) { var info = { version: JSON.parse(stdout || '{}') - } + }; - var cmd = 'git config --get remote.origin.url' + var cmd = 'git config --get remote.origin.url'; cp.exec(cmd, function (err, stdout) { - info.version.source = (stdout && stdout.trim()) || '' - console.log(JSON.stringify(info, null, 2)) - }) -}) + info.version.source = (stdout && stdout.trim()) || ''; + console.log(JSON.stringify(info, null, 2)); + }); +}); diff --git a/scripts/sms/send.js b/scripts/sms/send.js index 99a94a5e..8fc387c5 100755 --- a/scripts/sms/send.js +++ b/scripts/sms/send.js @@ -4,52 +4,52 @@ * 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/. */ -'use strict' +'use strict'; -const config = require('../../config').getProperties() +const config = require('../../config').getProperties(); -const args = parseArgs() -const log = require('../../lib/log')(config.log.level, 'send-sms') +const args = parseArgs(); +const log = require('../../lib/log')(config.log.level, 'send-sms'); require('../../lib/senders/translator')(config.i18n.supportedLanguages, config.i18n.defaultLanguage) .then(translator => { - return require('../../lib/senders')(log, config, {}, null, translator) + return require('../../lib/senders')(log, config, {}, null, translator); }) .then(senders => { - return senders.sms.send.apply(null, args) + return senders.sms.send.apply(null, args); }) .then(() => { - console.log('SENT!') + console.log('SENT!'); }) .catch(error => { - let message = error.message + let message = error.message; if (error.reason && error.reasonCode) { - message = `${message}: ${error.reasonCode} ${error.reason}` + message = `${message}: ${error.reasonCode} ${error.reason}`; } else if (error.stack) { - message = error.stack + message = error.stack; } - fail(message) - }) + fail(message); + }); function fail (message) { - console.error(message) - process.exit(1) + console.error(message); + process.exit(1); } function parseArgs () { - let acceptLanguage, messageName, phoneNumber + let acceptLanguage, messageName, phoneNumber; switch (process.argv.length) { /* eslint-disable indent, no-fallthrough */ case 5: - acceptLanguage = process.argv[5] + acceptLanguage = process.argv[5]; case 4: - messageName = process.argv[4] + messageName = process.argv[4]; case 3: - phoneNumber = process.argv[2] - break + phoneNumber = process.argv[2]; + break; default: - fail(`Usage: ${process.argv[1]} phoneNumber [messageName] [acceptLanguage]`) + fail(`Usage: ${process.argv[1]} phoneNumber [messageName] [acceptLanguage]`); /* eslint-enable indent, no-fallthrough */ } @@ -57,6 +57,6 @@ function parseArgs () { phoneNumber, messageName || 'installFirefox', acceptLanguage || 'en' - ] + ]; } diff --git a/scripts/template-version-bump.js b/scripts/template-version-bump.js index eb7d8da6..b85ef735 100755 --- a/scripts/template-version-bump.js +++ b/scripts/template-version-bump.js @@ -10,62 +10,62 @@ // a deleted template is reinstated by some later commit or only one // format of a template is deleted. -'use strict' +'use strict'; -const cp = require('child_process') -const fs = require('fs') -const path = require('path') +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); -const ROOT_DIR = path.join(__dirname, '..') -const TEMPLATE_DIR = 'lib/senders/templates' -const VERSIONS_FILE = '_versions.json' -const IGNORE = new Set([ VERSIONS_FILE, '_pending.txt', 'index.js', 'README.md' ]) -const DEDUP = {} +const ROOT_DIR = path.join(__dirname, '..'); +const TEMPLATE_DIR = 'lib/senders/templates'; +const VERSIONS_FILE = '_versions.json'; +const IGNORE = new Set([ VERSIONS_FILE, '_pending.txt', 'index.js', 'README.md' ]); +const DEDUP = {}; -const templates = require(`../${TEMPLATE_DIR}`) -const versions = require(`../${TEMPLATE_DIR}/${VERSIONS_FILE}`) +const templates = require(`../${TEMPLATE_DIR}`); +const versions = require(`../${TEMPLATE_DIR}/${VERSIONS_FILE}`); const stagedTemplates = cp.execSync('git status --porcelain', { cwd: ROOT_DIR, encoding: 'utf8' }) .split('\n') .filter(line => line.match(`^[AM]. ${TEMPLATE_DIR}/\\w+`)) .map(line => { - const parts = line.split(' ') - return parts[2] || parts[1] + const parts = line.split(' '); + return parts[2] || parts[1]; }) .map(templatePath => templatePath.split('/')[3]) .filter(fileName => ! IGNORE.has(fileName)) .map(fileName => templates.generateTemplateName(fileName.substr(0, fileName.lastIndexOf('.')))) .filter(templateName => { if (DEDUP[templateName]) { - return false + return false; } - DEDUP[templateName] = true - return true - }) + DEDUP[templateName] = true; + return true; + }); if (stagedTemplates.length === 0) { if (process.argc === 2 || process.argv[2] !== '--silent') { - console.log('I see no work. Did you remember to `git add` your changes?') + console.log('I see no work. Did you remember to `git add` your changes?'); } } else { stagedTemplates.forEach(templateName => { - const version = versions[templateName] + const version = versions[templateName]; if (version) { - const type = typeof version + const type = typeof version; if (type !== 'number' || isNaN(version)) { - console.log(`Bad version "${version}" {${type}} for template "${templateName}"`) - process.exit(1) + console.log(`Bad version "${version}" {${type}} for template "${templateName}"`); + process.exit(1); } - versions[templateName] = version + 1 + versions[templateName] = version + 1; } else { - versions[templateName] = 1 + versions[templateName] = 1; } - }) + }); fs.writeFileSync( path.join(`${ROOT_DIR}/${TEMPLATE_DIR}/${VERSIONS_FILE}`), JSON.stringify(versions, null, ' ') - ) + ); } diff --git a/scripts/test-remote-quick.js b/scripts/test-remote-quick.js index bde93bba..eb5c8a77 100755 --- a/scripts/test-remote-quick.js +++ b/scripts/test-remote-quick.js @@ -4,12 +4,12 @@ * 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/. */ -'use strict' +'use strict'; -var path = require('path') -var spawn = require('child_process').spawn -var config = require('../config').getProperties() -var TestServer = require('../test/test_server') +var path = require('path'); +var spawn = require('child_process').spawn; +var config = require('../config').getProperties(); +var TestServer = require('../test/test_server'); TestServer.start(config, false) .then( @@ -18,11 +18,11 @@ TestServer.start(config, false) path.join(path.dirname(__dirname), 'node_modules/.bin/mocha'), ['test/remote'], { stdio: 'inherit' } - ) + ); cp.on('close', function(code) { - server.stop() - }) + server.stop(); + }); } -) +); diff --git a/scripts/write-emails-to-disk.js b/scripts/write-emails-to-disk.js index 936aaf33..fe1c69e6 100755 --- a/scripts/write-emails-to-disk.js +++ b/scripts/write-emails-to-disk.js @@ -29,70 +29,70 @@ * to give a rough idea of how they would render in real life. */ -'use strict' +'use strict'; -var P = require('bluebird') -const config = require('../config').getProperties() -const error = require('../lib/error') -const createSenders = require('../lib/senders') -var fs = require('fs') -const log = require('../lib/senders/legacy_log')(require('../lib/senders/log')('server')) -var mkdirp = require('mkdirp') -var path = require('path') +var P = require('bluebird'); +const config = require('../config').getProperties(); +const error = require('../lib/error'); +const createSenders = require('../lib/senders'); +var fs = require('fs'); +const log = require('../lib/senders/legacy_log')(require('../lib/senders/log')('server')); +var mkdirp = require('mkdirp'); +var path = require('path'); -var OUTPUT_DIRECTORY = path.join(__dirname, '..', '.mail_output') +var OUTPUT_DIRECTORY = path.join(__dirname, '..', '.mail_output'); -var messageToSend = process.argv[2] || '' +var messageToSend = process.argv[2] || ''; var mailSender = { sendMail: function (emailConfig, done) { - var htmlOutputPath = getEmailOutputPath(emailConfig.subject, 'html') - fs.writeFileSync(htmlOutputPath, emailConfig.html) + var htmlOutputPath = getEmailOutputPath(emailConfig.subject, 'html'); + fs.writeFileSync(htmlOutputPath, emailConfig.html); - var textOutputPath = getEmailOutputPath(emailConfig.subject, 'txt') - fs.writeFileSync(textOutputPath, emailConfig.text) + var textOutputPath = getEmailOutputPath(emailConfig.subject, 'txt'); + fs.writeFileSync(textOutputPath, emailConfig.text); - done(null) + done(null); }, close: function () {} -} +}; const bounces = { // this is for dev purposes, no need to check db check: () => P.resolve() -} +}; require('../lib/senders/translator')(config.i18n.supportedLanguages, config.i18n.defaultLanguage) .then(translator => { - return createSenders(log, config, error, bounces, translator, mailSender) + return createSenders(log, config, error, bounces, translator, mailSender); }) .then((senders) => { - const mailer = senders.email._ungatedMailer - checkMessageType(mailer, messageToSend) + const mailer = senders.email._ungatedMailer; + checkMessageType(mailer, messageToSend); - ensureTargetDirectoryExists() + ensureTargetDirectoryExists(); - return sendMails(mailer, getMessageTypesToWrite(mailer, messageToSend)) + return sendMails(mailer, getMessageTypesToWrite(mailer, messageToSend)); }) .then(() => { - console.info('done') - }) + console.info('done'); + }); function getEmailOutputPath(subject, extension) { - var outputFilename = subject.replace(/\s+/g, '_') + '.' + extension - return path.join(OUTPUT_DIRECTORY, outputFilename) + var outputFilename = subject.replace(/\s+/g, '_') + '.' + extension; + return path.join(OUTPUT_DIRECTORY, outputFilename); } function sendMails(mailer, messagesToSend) { - return P.all(messagesToSend.map(sendMail.bind(null, mailer))) + return P.all(messagesToSend.map(sendMail.bind(null, mailer))); } function sendMail(mailer, messageToSend) { - var parts = messageToSend.split(':') - var messageType = parts[0] - var messageSubType = parts[1] + var parts = messageToSend.split(':'); + var messageType = parts[0]; + var messageSubType = parts[1]; var message = { acceptLanguage: 'en;q=0.8,en-US;q=0.5,en;q=0.3"', @@ -118,46 +118,46 @@ function sendMail(mailer, messageToSend) { unblockCode: '1ILO0Z5P', tokenCode: 'LIT12345', uid: '6510cb04abd742c6b3e4abefc7e39c9f' - } + }; - return mailer[messageType](message) + return mailer[messageType](message); } function checkMessageType(mailer, messageToSend) { - var messageTypes = getMailerMessageTypes(mailer) - messageTypes.push('all') + var messageTypes = getMailerMessageTypes(mailer); + messageTypes.push('all'); if (messageTypes.indexOf(messageToSend) === -1) { console.error('invalid message name: `' + messageToSend + '`\n' + - 'choose from: ' + messageTypes.join(', ')) - process.exit(1) + 'choose from: ' + messageTypes.join(', ')); + process.exit(1); } } function getMailerMessageTypes(mailer) { - var messageTypes = [] + var messageTypes = []; for (var key in mailer) { if ( typeof mailer[key] === 'function' && ! /^_/.test(key) && ! /^send/.test(key) && /Email$/.test(key) ) { - messageTypes.push(key) + messageTypes.push(key); } } - return messageTypes.sort() + return messageTypes.sort(); } function getMessageTypesToWrite(mailer, messageToSend) { if (messageToSend === 'all') { - return getMailerMessageTypes(mailer) + return getMailerMessageTypes(mailer); } else { - return [messageToSend] + return [messageToSend]; } } function ensureTargetDirectoryExists() { - mkdirp.sync(OUTPUT_DIRECTORY) + mkdirp.sync(OUTPUT_DIRECTORY); } diff --git a/test/bench/bot.js b/test/bench/bot.js index f66b907d..3a390723 100644 --- a/test/bench/bot.js +++ b/test/bench/bot.js @@ -2,39 +2,39 @@ * 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/. */ -'use strict' +'use strict'; /* eslint-disable no-console */ -var Client = require('../client')() +var Client = require('../client')(); var config = { origin: 'http://127.0.0.1:9000', email: Math.random() + 'benchmark@example.com', password: 'password', duration: 120000 -} +}; var key = { algorithm: 'RS', n: '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', e: '65537' -} +}; function bindApply(fn, args) { return function() { - return fn.apply(null, args) - } + return fn.apply(null, args); + }; } function times(fn, n) { return function () { - var args = arguments - var p = fn.apply(null, args) + var args = arguments; + var p = fn.apply(null, args); for (var i = 1; i < n; i++) { - p = p.then(bindApply(fn, args)) + p = p.then(bindApply(fn, args)); } - return p - } + return p; + }; } function session(c) { @@ -43,7 +43,7 @@ function session(c) { .then(c.keys.bind(c)) .then(c.devices.bind(c)) .then(times(c.sign.bind(c, key, 10000), 10)) - .then(c.destroySession.bind(c)) + .then(c.destroySession.bind(c)); } function run(c) { @@ -52,37 +52,37 @@ function run(c) { .then(c.changePassword.bind(c, 'newPassword')) .then( function () { - return c.destroyAccount() + return c.destroyAccount(); }, function (err) { - console.error('Error during run:', err.message) - return c.destroyAccount() + console.error('Error during run:', err.message); + return c.destroyAccount(); } - ) + ); } -var client = new Client(config.origin) -client.options.preVerified = true +var client = new Client(config.origin); +client.options.preVerified = true; client.setupCredentials(config.email, config.password) .then( function () { - var begin = Date.now() + var begin = Date.now(); function loop(ms) { run(client) .then( function () { if (Date.now() - begin < ms) { - loop(ms) + loop(ms); } }, function (err) { - console.error('Error during cleanup:', err.message) + console.error('Error during cleanup:', err.message); } - ) + ); } - loop(config.duration) + loop(config.duration); } - ) + ); diff --git a/test/bench/index.js b/test/bench/index.js index ff7ab31e..73eb274c 100644 --- a/test/bench/index.js +++ b/test/bench/index.js @@ -3,19 +3,19 @@ * 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/. */ -'use strict' +'use strict'; /* eslint-disable no-console */ -var cp = require('child_process') -var split = require('binary-split') -var through = require('through') +var cp = require('child_process'); +var split = require('binary-split'); +var through = require('through'); -var clientCount = 2 -var pathStats = {} -var requests = 0 -var pass = 0 // eslint-disable-line no-unused-vars -var fail = 0 -var start = null +var clientCount = 2; +var pathStats = {}; +var requests = 0; +var pass = 0; // eslint-disable-line no-unused-vars +var fail = 0; +var start = null; var server = cp.spawn( 'node', @@ -23,7 +23,7 @@ var server = cp.spawn( { cwd: __dirname } -) +); server.stderr .pipe(split()) @@ -31,7 +31,7 @@ server.stderr through( function (data) { try { - this.emit('data', JSON.parse(data)) + this.emit('data', JSON.parse(data)); } catch (e) {} } @@ -41,25 +41,25 @@ server.stderr through( function (json) { if (json.level > 30 && json.op !== 'console') { - console.log(json) + console.log(json); } if (json.op && json.op === 'request.summary') { - if (! start) start = Date.now() - requests++ - if (json.code === 200) { pass++ } else { fail++ } - var stat = pathStats[json.path] || {} - stat.count = stat.count + 1 || 1 - stat.max = Math.max(stat.max || 0, json.t) - stat.min = Math.min(stat.min || Number.MAX_VALUE, json.t) - pathStats[json.path] = stat - this.emit('data', json) + if (! start) start = Date.now(); + requests++; + if (json.code === 200) { pass++; } else { fail++; } + var stat = pathStats[json.path] || {}; + stat.count = stat.count + 1 || 1; + stat.max = Math.max(stat.max || 0, json.t); + stat.min = Math.min(stat.min || Number.MAX_VALUE, json.t); + pathStats[json.path] = stat; + this.emit('data', json); } else if (json.op === 'server.start.1') { - startClients() + startClients(); } } ) - ) + ); function startClient() { var client = cp.spawn( @@ -68,26 +68,26 @@ function startClient() { { cwd: __dirname } - ) - client.stdout.on('data', process.stdout.write.bind(process.stdout)) - client.stderr.on('data', process.stderr.write.bind(process.stderr)) - return client + ); + client.stdout.on('data', process.stdout.write.bind(process.stdout)); + client.stderr.on('data', process.stderr.write.bind(process.stderr)); + return client; } function clientExit() { - clientCount-- + clientCount--; if (clientCount === 0) { - var seconds = (Date.now() - start) / 1000 - var rps = Math.floor(requests / seconds) - console.log('rps: %d requests: %d errors: %d', rps, requests, fail) - console.log(pathStats) - server.kill('SIGINT') + var seconds = (Date.now() - start) / 1000; + var rps = Math.floor(requests / seconds); + console.log('rps: %d requests: %d errors: %d', rps, requests, fail); + console.log(pathStats); + server.kill('SIGINT'); } } function startClients() { for (var i = 0; i < clientCount; i++) { - var c = startClient() - c.on('exit', clientExit) + var c = startClient(); + c.on('exit', clientExit); } } diff --git a/test/client/api.js b/test/client/api.js index ed60c991..18899dc6 100644 --- a/test/client/api.js +++ b/test/client/api.js @@ -2,129 +2,129 @@ * 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/. */ -'use strict' +'use strict'; module.exports = config => { - var EventEmitter = require('events').EventEmitter - var util = require('util') + var EventEmitter = require('events').EventEmitter; + var util = require('util'); - var hawk = require('hawk') - var P = require('../../lib/promise') - var request = require('request') + var hawk = require('hawk'); + var P = require('../../lib/promise'); + var request = require('request'); - const tokens = require('../../lib/tokens')({ trace: function() {}}, config) + const tokens = require('../../lib/tokens')({ trace: function() {}}, config); - util.inherits(ClientApi, EventEmitter) + util.inherits(ClientApi, EventEmitter); function ClientApi(origin) { - EventEmitter.call(this) - this.origin = origin - this.baseURL = origin + '/v1' - this.timeOffset = 0 + EventEmitter.call(this); + this.origin = origin; + this.baseURL = origin + '/v1'; + this.timeOffset = 0; } - ClientApi.prototype.Token = tokens + ClientApi.prototype.Token = tokens; function hawkHeader(token, method, url, payload, offset) { var verify = { credentials: token - } + }; if (payload) { - verify.contentType = 'application/json' - verify.payload = JSON.stringify(payload) + verify.contentType = 'application/json'; + verify.payload = JSON.stringify(payload); } if (offset) { - verify.localtimeOffsetMsec = offset + verify.localtimeOffsetMsec = offset; } - return hawk.client.header(url, method, verify).header + return hawk.client.header(url, method, verify).header; } ClientApi.prototype.doRequest = function (method, url, token, payload, headers) { - var d = P.defer() + var d = P.defer(); if (typeof headers === 'undefined') { - headers = {} + headers = {}; } // We do a shallow clone to avoid tainting the caller's copy of `headers`. - headers = Object.assign({}, headers) + headers = Object.assign({}, headers); if (token && ! headers.Authorization) { - headers.Authorization = hawkHeader(token, method, url, payload, this.timeOffset) + headers.Authorization = hawkHeader(token, method, url, payload, this.timeOffset); } var options = { url: url, method: method, headers: headers, json: payload || true - } - if (headers['accept-language'] === undefined) { delete headers['accept-language']} - this.emit('startRequest', options) + }; + if (headers['accept-language'] === undefined) { delete headers['accept-language'];} + this.emit('startRequest', options); request(options, function (err, res, body) { if (res && res.headers.timestamp) { // Record time skew - this.timeOffset = Date.now() - parseInt(res.headers.timestamp, 10) * 1000 + this.timeOffset = Date.now() - parseInt(res.headers.timestamp, 10) * 1000; } - this.emit('endRequest', options, err, res) + this.emit('endRequest', options, err, res); if (err || body.error || res.statusCode !== 200) { - return d.reject(err || body) + return d.reject(err || body); } - var allowedOrigin = res.headers['access-control-allow-origin'] + var allowedOrigin = res.headers['access-control-allow-origin']; if (allowedOrigin) { // Requiring config outside this condition causes the local tests to fail // because tokenLifetimes.passwordChangeToken is -1 - var config = require('../../config') + var config = require('../../config'); if (config.get('corsOrigin').indexOf(allowedOrigin) < 0) { - return d.reject(new Error('Unexpected allowed origin: ' + allowedOrigin)) + return d.reject(new Error('Unexpected allowed origin: ' + allowedOrigin)); } } - d.resolve(body) - }.bind(this)) - return d.promise - } + d.resolve(body); + }.bind(this)); + return d.promise; + }; ClientApi.prototype.doRequestWithBearerToken = function (method, url, token, payload, headers) { - var d = P.defer() + var d = P.defer(); if (typeof headers === 'undefined') { - headers = {} + headers = {}; } // We do a shallow clone to avoid tainting the caller's copy of `headers`. - headers = Object.assign({}, headers) + headers = Object.assign({}, headers); if (token && ! headers.Authorization) { - headers.Authorization = `Bearer ${token}` + headers.Authorization = `Bearer ${token}`; } var options = { url: url, method: method, headers: headers, json: payload || true - } - if (headers['accept-language'] === undefined) { delete headers['accept-language']} - this.emit('startRequest', options) + }; + if (headers['accept-language'] === undefined) { delete headers['accept-language'];} + this.emit('startRequest', options); request(options, function (err, res, body) { if (res && res.headers.timestamp) { // Record time skew - this.timeOffset = Date.now() - parseInt(res.headers.timestamp, 10) * 1000 + this.timeOffset = Date.now() - parseInt(res.headers.timestamp, 10) * 1000; } - this.emit('endRequest', options, err, res) + this.emit('endRequest', options, err, res); if (err || body.error || res.statusCode !== 200) { - return d.reject(err || body) + return d.reject(err || body); } - var allowedOrigin = res.headers['access-control-allow-origin'] + var allowedOrigin = res.headers['access-control-allow-origin']; if (allowedOrigin) { // Requiring config outside this condition causes the local tests to fail // because tokenLifetimes.passwordChangeToken is -1 - var config = require('../../config') + var config = require('../../config'); if (config.get('corsOrigin').indexOf(allowedOrigin) < 0) { - return d.reject(new Error('Unexpected allowed origin: ' + allowedOrigin)) + return d.reject(new Error('Unexpected allowed origin: ' + allowedOrigin)); } } - d.resolve(body) - }.bind(this)) - return d.promise - } + d.resolve(body); + }.bind(this)); + return d.promise; + }; /* * Creates a user account. @@ -147,7 +147,7 @@ module.exports = config => { */ ClientApi.prototype.accountCreate = function (email, authPW, options = {}) { - var url = this.baseURL + '/account/create' + getQueryString(options) + var url = this.baseURL + '/account/create' + getQueryString(options); return this.doRequest( 'POST', url, @@ -165,12 +165,12 @@ module.exports = config => { { 'accept-language': options.lang } - ) - } + ); + }; ClientApi.prototype.accountLogin = function (email, authPW, options) { if (! options) { - options = { keys: true } + options = { keys: true }; } return this.doRequest( @@ -191,8 +191,8 @@ module.exports = config => { { 'accept-language': options.lang } - ) - } + ); + }; ClientApi.prototype.accountKeys = function (keyFetchTokenHex) { return tokens.KeyFetchToken.fromHex(keyFetchTokenHex) @@ -202,10 +202,10 @@ module.exports = config => { 'GET', this.baseURL + '/account/keys', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountDevices = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -215,18 +215,18 @@ module.exports = config => { 'GET', this.baseURL + '/account/devices', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountDevicesWithRefreshToken = function (refreshTokenHex) { return this.doRequestWithBearerToken( 'GET', this.baseURL + '/account/devices', refreshTokenHex - ) - } + ); + }; ClientApi.prototype.accountDevice = function (sessionTokenHex, info) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -237,10 +237,10 @@ module.exports = config => { this.baseURL + '/account/device', token, info - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountDeviceWithRefreshToken = function (refreshTokenHex, info) { return this.doRequestWithBearerToken( @@ -248,8 +248,8 @@ module.exports = config => { this.baseURL + '/account/device', refreshTokenHex, info - ) - } + ); + }; ClientApi.prototype.deviceDestroy = function (sessionTokenHex, id) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -262,10 +262,10 @@ module.exports = config => { { id: id } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.deviceDestroyWithRefreshToken = function (refreshTokenHex, id) { return this.doRequestWithBearerToken( @@ -275,8 +275,8 @@ module.exports = config => { { id: id } - ) - } + ); + }; ClientApi.prototype.accountStatusByEmail = function (email) { if (email) { @@ -287,12 +287,12 @@ module.exports = config => { { email: email } - ) + ); } else { - return this.doRequest('POST', this.baseURL + '/account/status') + return this.doRequest('POST', this.baseURL + '/account/status'); } - } + }; ClientApi.prototype.accountStatus = function (uid, sessionTokenHex) { if (sessionTokenHex) { @@ -303,28 +303,28 @@ module.exports = config => { 'GET', this.baseURL + '/account/status', token - ) + ); }.bind(this) - ) + ); } else if (uid) { return this.doRequest( 'GET', this.baseURL + '/account/status?uid=' + uid - ) + ); } else { // for testing the error response only - return this.doRequest('GET', this.baseURL + '/account/status') + return this.doRequest('GET', this.baseURL + '/account/status'); } - } + }; ClientApi.prototype.accountReset = function (accountResetTokenHex, authPW, headers, options = {}) { - var qs = getQueryString(options) + var qs = getQueryString(options); // Default behavior is to request sessionToken if (options.sessionToken === undefined) { - options.sessionToken = true + options.sessionToken = true; } return tokens.AccountResetToken.fromHex(accountResetTokenHex) @@ -339,13 +339,13 @@ module.exports = config => { sessionToken: options.sessionToken }, headers - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountResetWithRecoveryKey = function (accountResetTokenHex, authPW, wrapKb, recoveryKeyId, headers, options = {}) { - const qs = getQueryString(options) + const qs = getQueryString(options); return tokens.AccountResetToken.fromHex(accountResetTokenHex) .then( @@ -361,10 +361,10 @@ module.exports = config => { recoveryKeyId }, headers - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountDestroy = function (email, authPW) { return this.doRequest( @@ -375,8 +375,8 @@ module.exports = config => { email: email, authPW: authPW.toString('hex') } - ) - } + ); + }; ClientApi.prototype.accountDestroyWithSessionToken = function (email, authPW, sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -388,9 +388,9 @@ module.exports = config => { { email, authPW: authPW.toString('hex') - }) - }) - } + }); + }); + }; ClientApi.prototype.recoveryEmailStatus = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -400,10 +400,10 @@ module.exports = config => { 'GET', this.baseURL + '/recovery_email/status', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.recoveryEmailResendCode = function (sessionTokenHex, options = {}) { @@ -421,10 +421,10 @@ module.exports = config => { email: options.email || undefined, type: options.type || undefined } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.recoveryEmailVerifyCode = function (uid, code, options = {}) { return this.doRequest( @@ -441,16 +441,16 @@ module.exports = config => { { 'accept-language': options.lang } - ) - } + ); + }; ClientApi.prototype.certificateSign = function (sessionTokenHex, publicKey, duration, locale, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) .then( function (token) { - let url = this.baseURL + '/certificate/sign' + let url = this.baseURL + '/certificate/sign'; if (options.service) { - url += '?service=' + options.service + url += '?service=' + options.service; } return this.doRequest( 'POST', @@ -463,17 +463,17 @@ module.exports = config => { { 'accept-language': locale } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.getRandomBytes = function () { return this.doRequest( 'POST', this.baseURL + '/get_random_bytes' - ) - } + ); + }; ClientApi.prototype.passwordChangeStart = function (email, oldAuthPW, headers) { return this.doRequest( @@ -485,23 +485,23 @@ module.exports = config => { oldAuthPW: oldAuthPW.toString('hex') }, headers - ) - } + ); + }; ClientApi.prototype.passwordChangeFinish = function (passwordChangeTokenHex, authPW, wrapKb, headers, sessionToken) { - var options = {} + var options = {}; return tokens.PasswordChangeToken.fromHex(passwordChangeTokenHex) .then( function (token) { var requestData = { authPW: authPW.toString('hex'), wrapKb: wrapKb.toString('hex') - } + }; if (sessionToken) { // Support legacy clients and new clients - requestData.sessionToken = sessionToken - options.keys = true + requestData.sessionToken = sessionToken; + options.keys = true; } return this.doRequest( @@ -510,18 +510,18 @@ module.exports = config => { token, requestData, headers - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.passwordForgotSendCode = function (email, options = {}, lang) { - var headers = {} + var headers = {}; if (lang) { headers = { 'accept-language': lang - } + }; } return this.doRequest( 'POST', @@ -535,8 +535,8 @@ module.exports = config => { metricsContext: options.metricsContext || undefined }, headers - ) - } + ); + }; ClientApi.prototype.passwordForgotResendCode = function (passwordForgotTokenHex, email, options = {}) { return tokens.PasswordForgotToken.fromHex(passwordForgotTokenHex) @@ -552,14 +552,14 @@ module.exports = config => { redirectTo: options.redirectTo || undefined, resume: options.resume || undefined } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.passwordForgotVerifyCode = function (passwordForgotTokenHex, code, headers, options) { if (! options) { - options = {} + options = {}; } return tokens.PasswordForgotToken.fromHex(passwordForgotTokenHex) @@ -574,10 +574,10 @@ module.exports = config => { accountResetWithRecoveryKey: options.accountResetWithRecoveryKey || undefined }, headers - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.passwordForgotStatus = function (passwordForgotTokenHex) { return tokens.PasswordForgotToken.fromHex(passwordForgotTokenHex) @@ -587,10 +587,10 @@ module.exports = config => { 'GET', this.baseURL + '/password/forgot/status', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountLock = function (email, authPW) { return this.doRequest( @@ -601,15 +601,15 @@ module.exports = config => { email: email, authPW: authPW.toString('hex') } - ) - } + ); + }; ClientApi.prototype.accountUnlockResendCode = function (email, options = {}, lang) { - var headers = {} + var headers = {}; if (lang) { headers = { 'accept-language': lang - } + }; } return this.doRequest( 'POST', @@ -622,8 +622,8 @@ module.exports = config => { resume: options.resume || undefined }, headers - ) - } + ); + }; ClientApi.prototype.accountUnlockVerifyCode = function (uid, code) { return this.doRequest( @@ -634,16 +634,16 @@ module.exports = config => { uid: uid, code: code } - ) - } + ); + }; ClientApi.prototype.sessionDestroy = function (sessionTokenHex, options) { - var data = null + var data = null; if (options && options.customSessionToken) { data = { customSessionToken: options.customSessionToken - } + }; } return tokens.SessionToken.fromHex(sessionTokenHex) @@ -654,10 +654,10 @@ module.exports = config => { this.baseURL + '/session/destroy', token, data - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.sessionReauth = function (sessionTokenHex, email, authPW, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -675,10 +675,10 @@ module.exports = config => { reason: options.reason || undefined, metricsContext: options.metricsContext || undefined } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.sessionDuplicate = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -689,10 +689,10 @@ module.exports = config => { this.baseURL + '/session/duplicate', token, {} - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.sessions = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -702,10 +702,10 @@ module.exports = config => { 'GET', this.baseURL + '/account/sessions', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.sessionStatus = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -715,13 +715,13 @@ module.exports = config => { 'GET', this.baseURL + '/session/status', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.accountProfile = function (sessionTokenHex, headers) { - var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null) + var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null); return o.then( function (token) { return this.doRequest( @@ -730,10 +730,10 @@ module.exports = config => { token, undefined, headers - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.smsSend = function (sessionTokenHex, phoneNumber, messageId, features) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -742,8 +742,8 @@ module.exports = config => { `${this.baseURL}/sms`, token, { phoneNumber, messageId, features } - )) - } + )); + }; ClientApi.prototype.smsStatus = function (sessionTokenHex, country, clientIpAddress) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -753,24 +753,24 @@ module.exports = config => { token, null, { 'X-Forwarded-For': clientIpAddress || '8.8.8.8' } - )) - } + )); + }; ClientApi.prototype.accountEmails = function (sessionTokenHex) { - var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null) + var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null); return o.then( function (token) { return this.doRequest( 'GET', this.baseURL + '/recovery_emails', token - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.createEmail = function (sessionTokenHex, email) { - var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null) + var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null); return o.then( function (token) { return this.doRequest( @@ -780,13 +780,13 @@ module.exports = config => { { email: email } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.deleteEmail = function (sessionTokenHex, email) { - var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null) + var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null); return o.then( function (token) { return this.doRequest( @@ -796,13 +796,13 @@ module.exports = config => { { email: email } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.setPrimaryEmail = function (sessionTokenHex, email) { - var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null) + var o = sessionTokenHex ? tokens.SessionToken.fromHex(sessionTokenHex) : P.resolve(null); return o.then( function (token) { return this.doRequest( @@ -812,10 +812,10 @@ module.exports = config => { { email: email } - ) + ); }.bind(this) - ) - } + ); + }; ClientApi.prototype.sendUnblockCode = function (email) { return this.doRequest( @@ -825,8 +825,8 @@ module.exports = config => { { email: email } - ) - } + ); + }; ClientApi.prototype.consumeSigninCode = function (code, metricsContext) { return this.doRequest( @@ -834,8 +834,8 @@ module.exports = config => { `${this.baseURL}/signinCodes/consume`, null, { code, metricsContext } - ) - } + ); + }; ClientApi.prototype.verifyTokenCode = function (sessionTokenHex, code, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -849,9 +849,9 @@ module.exports = config => { uid: options.uid || undefined, metricsContext: options.metricsContext } - ) - }) - } + ); + }); + }; ClientApi.prototype.createTotpToken = function (sessionTokenHex, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -863,9 +863,9 @@ module.exports = config => { { metricsContext: options.metricsContext } - ) - }) - } + ); + }); + }; ClientApi.prototype.deleteTotpToken = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -875,9 +875,9 @@ module.exports = config => { this.baseURL + '/totp/destroy', token, {} - ) - }) - } + ); + }); + }; ClientApi.prototype.checkTotpTokenExists = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -886,9 +886,9 @@ module.exports = config => { 'GET', this.baseURL + '/totp/exists', token - ).bind(this) - }) - } + ).bind(this); + }); + }; ClientApi.prototype.verifyTotpCode = function (sessionTokenHex, code, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -901,9 +901,9 @@ module.exports = config => { code: code, service: options.service } - ) - }) - } + ); + }); + }; ClientApi.prototype.replaceRecoveryCodes = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -912,9 +912,9 @@ module.exports = config => { 'GET', this.baseURL + '/recoveryCodes', token - ) - }) - } + ); + }); + }; ClientApi.prototype.consumeRecoveryCode = function (sessionTokenHex, code, options = {}) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -926,9 +926,9 @@ module.exports = config => { { code: code } - ) - }) - } + ); + }); + }; ClientApi.prototype.createRecoveryKey = function (sessionTokenHex, recoveryKeyId, recoveryData) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -941,9 +941,9 @@ module.exports = config => { recoveryKeyId, recoveryData } - ) - }) - } + ); + }); + }; ClientApi.prototype.getRecoveryKey = function (accountResetTokenHex, recoveryKeyId) { return tokens.AccountResetToken.fromHex(accountResetTokenHex) @@ -952,9 +952,9 @@ module.exports = config => { 'GET', `${this.baseURL}/recoveryKey/${recoveryKeyId}`, token - ) - }) - } + ); + }); + }; ClientApi.prototype.getRecoveryKeyExistsWithSession = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -964,9 +964,9 @@ module.exports = config => { `${this.baseURL}/recoveryKey/exists`, token, {} - ) - }) - } + ); + }); + }; ClientApi.prototype.getRecoveryKeyExistsWithEmail = function (email) { return this.doRequest( @@ -974,8 +974,8 @@ module.exports = config => { `${this.baseURL}/recoveryKey/exists`, undefined, {email} - ) - } + ); + }; ClientApi.prototype.deleteRecoveryKey = function (sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) @@ -984,35 +984,35 @@ module.exports = config => { 'DELETE', `${this.baseURL}/recoveryKey`, token - ) - }) - } + ); + }); + }; ClientApi.heartbeat = function (origin) { - return (new ClientApi(origin)).doRequest('GET', origin + '/__heartbeat__') - } + return (new ClientApi(origin)).doRequest('GET', origin + '/__heartbeat__'); + }; function getQueryString (options) { - const qs = [] + const qs = []; if (options.keys) { - qs.push('keys=true') + qs.push('keys=true'); } if (options.serviceQuery) { - qs.push('service=' + options.serviceQuery) + qs.push('service=' + options.serviceQuery); } if (options.createdAt) { - qs.push('_createdAt=' + options.createdAt) + qs.push('_createdAt=' + options.createdAt); } if (qs) { - return '?' + qs.join('&') + return '?' + qs.join('&'); } else { - return '' + return ''; } } - return ClientApi -} + return ClientApi; +}; diff --git a/test/client/index.js b/test/client/index.js index 8d46777e..b1f44cdd 100644 --- a/test/client/index.js +++ b/test/client/index.js @@ -2,86 +2,86 @@ * 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/. */ -'use strict' +'use strict'; module.exports = config => { - const otplib = require('otplib') - const crypto = require('crypto') - var P = require('../../lib/promise') - const ClientApi = require('./api')(config) - var butil = require('../../lib/crypto/butil') - var pbkdf2 = require('../../lib/crypto/pbkdf2') - var hkdf = require('../../lib/crypto/hkdf') - const tokens = require('../../lib/tokens')({ trace: function () {}}, config) + const otplib = require('otplib'); + const crypto = require('crypto'); + var P = require('../../lib/promise'); + const ClientApi = require('./api')(config); + var butil = require('../../lib/crypto/butil'); + var pbkdf2 = require('../../lib/crypto/pbkdf2'); + var hkdf = require('../../lib/crypto/hkdf'); + const tokens = require('../../lib/tokens')({ trace: function () {}}, config); function Client(origin) { - this.uid = null - this.authAt = 0 - this.api = new ClientApi(origin) - this.email = null - this.emailVerified = false - this.authToken = null - this.sessionToken = null - this.accountResetToken = null - this.keyFetchToken = null - this.passwordForgotToken = null - this.kA = null - this.wrapKb = null - this.totpAuthenticator = null - this.options = {} + this.uid = null; + this.authAt = 0; + this.api = new ClientApi(origin); + this.email = null; + this.emailVerified = false; + this.authToken = null; + this.sessionToken = null; + this.accountResetToken = null; + this.keyFetchToken = null; + this.passwordForgotToken = null; + this.kA = null; + this.wrapKb = null; + this.totpAuthenticator = null; + this.options = {}; } - Client.Api = ClientApi + Client.Api = ClientApi; Client.prototype.setupCredentials = function (email, password) { return P.resolve().then(() => { - this.email = email + this.email = email; return pbkdf2.derive(Buffer.from(password), hkdf.KWE('quickStretch', email), 1000, 32) .then( function (stretch) { return hkdf(stretch, 'authPW', null, 32) .then( function (authPW) { - this.authPW = authPW - return hkdf(stretch, 'unwrapBKey', null, 32) + this.authPW = authPW; + return hkdf(stretch, 'unwrapBKey', null, 32); }.bind(this) - ) + ); }.bind(this) ) .then( function (unwrapBKey) { - this.unwrapBKey = unwrapBKey - return this + this.unwrapBKey = unwrapBKey; + return this; }.bind(this) - ) - }) - } + ); + }); + }; Client.create = function (origin, email, password, options = {}) { - var c = new Client(origin) - c.options = options + var c = new Client(origin); + c.options = options; return c.setupCredentials(email, password) .then( function() { - return c.create(options) + return c.create(options); } - ) - } + ); + }; Client.login = function (origin, email, password, opts) { - var c = new Client(origin) + var c = new Client(origin); return c.setupCredentials(email, password) .then( function (c) { - return c.auth(opts) + return c.auth(opts); } - ) - } + ); + }; Client.changePassword = function (origin, email, oldPassword, newPassword, headers) { - var c = new Client(origin) + var c = new Client(origin); return c.setupCredentials(email, oldPassword) .then( @@ -89,12 +89,12 @@ module.exports = config => { return c.changePassword(newPassword, headers) .then( function () { - return c + return c; } - ) + ); } - ) - } + ); + }; Client.createAndVerify = function (origin, email, password, mailbox, options) { return Client.create(origin, email, password, options) @@ -103,50 +103,50 @@ module.exports = config => { return mailbox.waitForCode(email) .then( function (code) { - return client.verifyEmail(code, options) + return client.verifyEmail(code, options); } ) .then( function () { // clear the post verified email, if one was sent if (options && options.service === 'sync') { - return mailbox.waitForEmail(email) + return mailbox.waitForEmail(email); } } ) .then( function () { - return client + return client; } - ) + ); } - ) - } + ); + }; Client.createAndVerifyAndTOTP = function (origin, email, password, mailbox, options) { return Client.createAndVerify(origin, email, password, mailbox, options) .then(client => { - client.totpAuthenticator = new otplib.Authenticator() + client.totpAuthenticator = new otplib.Authenticator(); return client.createTotpToken() .then(result => { client.totpAuthenticator.options = { secret: result.secret, crypto: crypto - } - return client.verifyTotpCode(client.totpAuthenticator.generate()) + }; + return client.verifyTotpCode(client.totpAuthenticator.generate()); }) .then(() => { - return client - }) - }) - } + return client; + }); + }); + }; Client.loginAndVerify = function (origin, email, password, mailbox, options) { if (! options ) { - options = {} + options = {}; } - options.keys = options.keys || true + options.keys = options.keys || true; return Client.login(origin, email, password, options) .then( @@ -154,17 +154,17 @@ module.exports = config => { return mailbox.waitForCode(email) .then( function (code) { - return client.verifyEmail(code, options) + return client.verifyEmail(code, options); } ) .then( function () { - return client + return client; } - ) + ); } - ) - } + ); + }; Client.prototype.create = function () { return this.api.accountCreate( @@ -174,201 +174,201 @@ module.exports = config => { ) .then( function (a) { - this.uid = a.uid - this.authAt = a.authAt - this.sessionToken = a.sessionToken - this.keyFetchToken = a.keyFetchToken - this.device = a.device - return this + this.uid = a.uid; + this.authAt = a.authAt; + this.sessionToken = a.sessionToken; + this.keyFetchToken = a.keyFetchToken; + this.device = a.device; + return this; }.bind(this) - ) - } + ); + }; Client.prototype._clear = function () { - this.authToken = null - this.sessionToken = null - this.srpSession = null - this.accountResetToken = null - this.keyFetchToken = null - this.passwordForgotToken = null - this.kA = null - this.wrapKb = null - } + this.authToken = null; + this.sessionToken = null; + this.srpSession = null; + this.accountResetToken = null; + this.keyFetchToken = null; + this.passwordForgotToken = null; + this.kA = null; + this.wrapKb = null; + }; Client.prototype.stringify = function () { - return JSON.stringify(this) - } + return JSON.stringify(this); + }; Client.prototype.auth = function (opts) { return this.api.accountLogin(this.email, this.authPW, opts) .then( function (data) { - this.uid = data.uid - this.sessionToken = data.sessionToken - this.keyFetchToken = data.keyFetchToken || null - this.emailVerified = data.verified - this.authAt = data.authAt - this.device = data.device - this.verificationReason = data.verificationReason - this.verificationMethod = data.verificationMethod - this.verified = data.verified - return this + this.uid = data.uid; + this.sessionToken = data.sessionToken; + this.keyFetchToken = data.keyFetchToken || null; + this.emailVerified = data.verified; + this.authAt = data.authAt; + this.device = data.device; + this.verificationReason = data.verificationReason; + this.verificationMethod = data.verificationMethod; + this.verified = data.verified; + return this; }.bind(this) - ) - } + ); + }; Client.prototype.login = function (opts) { - return this.auth(opts) - } + return this.auth(opts); + }; Client.prototype.destroySession = function () { - var p = P.resolve(null) + var p = P.resolve(null); if (this.sessionToken) { p = this.api.sessionDestroy(this.sessionToken) .then( function () { - this.sessionToken = null - return {} + this.sessionToken = null; + return {}; }.bind(this) - ) + ); } - return p - } + return p; + }; Client.prototype.reauth = function (opts) { return this.api.sessionReauth(this.sessionToken, this.email, this.authPW, opts) .then( function (data) { - this.uid = data.uid - this.keyFetchToken = data.keyFetchToken || null - this.emailVerified = data.verified - this.authAt = data.authAt - this.verificationReason = data.verificationReason - this.verificationMethod = data.verificationMethod - this.verified = data.verified - return this + this.uid = data.uid; + this.keyFetchToken = data.keyFetchToken || null; + this.emailVerified = data.verified; + this.authAt = data.authAt; + this.verificationReason = data.verificationReason; + this.verificationMethod = data.verificationMethod; + this.verified = data.verified; + return this; }.bind(this) - ) - } + ); + }; Client.prototype.duplicate = function () { - var c = new Client(this.api.origin) - c.uid = this.uid - c.authAt = this.authAt - c.email = this.email - c.emailVerified = this.emailVerified - c.authToken = this.authToken - c.sessionToken = this.sessionToken - c.kA = this.kA - c.kB = this.kB - c.wrapKb = this.wrapKb - c.unwrapBKey = this.unwrapBKey - c.authPW = this.authPW - c.options = this.options + var c = new Client(this.api.origin); + c.uid = this.uid; + c.authAt = this.authAt; + c.email = this.email; + c.emailVerified = this.emailVerified; + c.authToken = this.authToken; + c.sessionToken = this.sessionToken; + c.kA = this.kA; + c.kB = this.kB; + c.wrapKb = this.wrapKb; + c.unwrapBKey = this.unwrapBKey; + c.authPW = this.authPW; + c.options = this.options; return P.resolve().then(() => { if (this.sessionToken) { return this.api.sessionDuplicate(this.sessionToken) .then((data) => { - c.uid = data.uid - c.sessionToken = data.sessionToken - c.authAt = data.authAt - c.verified = data.verified - c.verificationReason = data.verificationReason - c.verificationMetho = data.verificationMethod - }) + c.uid = data.uid; + c.sessionToken = data.sessionToken; + c.authAt = data.authAt; + c.verified = data.verified; + c.verificationReason = data.verificationReason; + c.verificationMetho = data.verificationMethod; + }); } }).then(() => { - return c - }) - } + return c; + }); + }; Client.prototype.verifySecondaryEmail = function (code, secondaryEmail) { const options = { type: 'secondary', secondaryEmail: secondaryEmail - } - return this.api.recoveryEmailVerifyCode(this.uid, code, options) - } + }; + return this.api.recoveryEmailVerifyCode(this.uid, code, options); + }; Client.prototype.verifyEmail = function (code, options) { - return this.api.recoveryEmailVerifyCode(this.uid, code, options) - } + return this.api.recoveryEmailVerifyCode(this.uid, code, options); + }; Client.prototype.verifyTokenCode = function (code, options) { - return this.api.verifyTokenCode(this.sessionToken, code, options) - } + return this.api.verifyTokenCode(this.sessionToken, code, options); + }; Client.prototype.emailStatus = function () { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.recoveryEmailStatus(this.sessionToken) + return this.api.recoveryEmailStatus(this.sessionToken); }.bind(this) - ) - } + ); + }; Client.prototype.requestVerifyEmail = function () { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.recoveryEmailResendCode(this.sessionToken, this.options) + return this.api.recoveryEmailResendCode(this.sessionToken, this.options); }.bind(this) - ) - } + ); + }; Client.prototype.sign = function (publicKey, duration, locale, options) { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.certificateSign(this.sessionToken, publicKey, duration, locale, options) + return this.api.certificateSign(this.sessionToken, publicKey, duration, locale, options); }.bind(this) ) .then( function (x) { - return x.cert + return x.cert; } - ) - } + ); + }; Client.prototype.changePassword = function (newPassword, headers, sessionToken) { return this.api.passwordChangeStart(this.email, this.authPW, headers) .then( function (json) { - this.keyFetchToken = json.keyFetchToken - this.passwordChangeToken = json.passwordChangeToken - return this.keys() + this.keyFetchToken = json.keyFetchToken; + this.passwordChangeToken = json.passwordChangeToken; + return this.keys(); }.bind(this) ) .then( function (/* keys */) { - return this.setupCredentials(this.email, newPassword) + return this.setupCredentials(this.email, newPassword); }.bind(this) ) .then( function () { - this.wrapKb = butil.xorBuffers(this.kB, this.unwrapBKey).toString('hex') - return this.api.passwordChangeFinish(this.passwordChangeToken, this.authPW, this.wrapKb, headers, sessionToken) + this.wrapKb = butil.xorBuffers(this.kB, this.unwrapBKey).toString('hex'); + return this.api.passwordChangeFinish(this.passwordChangeToken, this.authPW, this.wrapKb, headers, sessionToken); }.bind(this) ) .then( function (res) { - this._clear() + this._clear(); // Update to new tokens if needed - this.sessionToken = res.sessionToken ? res.sessionToken : this.sessionToken - this.authAt = res.authAt ? res.authAt : this.authAt - this.keyFetchToken = res.keyFetchToken ? res.keyFetchToken : this.keyFetchToken + this.sessionToken = res.sessionToken ? res.sessionToken : this.sessionToken; + this.authAt = res.authAt ? res.authAt : this.authAt; + this.keyFetchToken = res.keyFetchToken ? res.keyFetchToken : this.keyFetchToken; - return res + return res; }.bind(this) - ) - } + ); + }; Client.prototype.keys = function () { - var o = this.keyFetchToken ? P.resolve(null) : this.login() + var o = this.keyFetchToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.accountKeys(this.keyFetchToken) + return this.api.accountKeys(this.keyFetchToken); }.bind(this) ) .then( @@ -376,233 +376,233 @@ module.exports = config => { return tokens.KeyFetchToken.fromHex(this.keyFetchToken) .then( function (token) { - return token.unbundleKeys(data.bundle) + return token.unbundleKeys(data.bundle); } - ) + ); }.bind(this) ) .then( function (keys) { - this.keyFetchToken = null - this.kA = keys.kA - this.wrapKb = keys.wrapKb - this.kB = keys.kB = butil.xorBuffers(this.wrapKb, this.unwrapBKey).toString('hex') - return keys + this.keyFetchToken = null; + this.kA = keys.kA; + this.wrapKb = keys.wrapKb; + this.kB = keys.kB = butil.xorBuffers(this.wrapKb, this.unwrapBKey).toString('hex'); + return keys; }.bind(this), function (err) { - if (err && err.errno !== 104) { this.keyFetchToken = null } - throw err + if (err && err.errno !== 104) { this.keyFetchToken = null; } + throw err; }.bind(this) - ) - } + ); + }; Client.prototype.devices = function () { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.accountDevices(this.sessionToken) + return this.api.accountDevices(this.sessionToken); }.bind(this) - ) - } + ); + }; Client.prototype.devicesWithRefreshToken = function (refreshToken) { - return this.api.accountDevicesWithRefreshToken(refreshToken) - } + return this.api.accountDevicesWithRefreshToken(refreshToken); + }; Client.prototype.updateDevice = function (info) { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.accountDevice(this.sessionToken, info) + return this.api.accountDevice(this.sessionToken, info); }.bind(this) ) .then( function (device) { if (! this.device || this.device.id === device.id) { - this.device = device + this.device = device; } - return device + return device; }.bind(this) - ) - } + ); + }; Client.prototype.updateDeviceWithRefreshToken = function (refreshTokenId, info) { return this.api.accountDeviceWithRefreshToken(refreshTokenId, info) .then( function (device) { if (! this.device || this.device.id === device.id) { - this.device = device + this.device = device; } - return device + return device; }.bind(this) - ) - } + ); + }; Client.prototype.destroyDevice = function (id) { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.deviceDestroy(this.sessionToken, id) + return this.api.deviceDestroy(this.sessionToken, id); }.bind(this) ) .then( function () { - delete this.sessionToken + delete this.sessionToken; }.bind(this) - ) - } + ); + }; Client.prototype.destroyDeviceWithRefreshToken = function (refreshTokenId, id) { - return this.api.deviceDestroyWithRefreshToken(refreshTokenId, id) - } + return this.api.deviceDestroyWithRefreshToken(refreshTokenId, id); + }; Client.prototype.sessionStatus = function () { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.sessionStatus(this.sessionToken) + return this.api.sessionStatus(this.sessionToken); }.bind(this) - ) - } + ); + }; Client.prototype.accountProfile = function (oauthToken) { if (oauthToken) { - return this.api.accountProfile(null, { Authorization: 'Bearer ' + oauthToken }) + return this.api.accountProfile(null, { Authorization: 'Bearer ' + oauthToken }); } else { - var o = this.sessionToken ? P.resolve(null) : this.login() + var o = this.sessionToken ? P.resolve(null) : this.login(); return o.then( function () { - return this.api.accountProfile(this.sessionToken) + return this.api.accountProfile(this.sessionToken); }.bind(this) - ) + ); } - } + }; Client.prototype.destroyAccount = function () { if (this.sessionToken) { return this.api.accountDestroyWithSessionToken(this.email, this.authPW, this.sessionToken) - .then(this._clear.bind(this)) + .then(this._clear.bind(this)); } return this.api.accountDestroy(this.email, this.authPW) - .then(this._clear.bind(this)) - } + .then(this._clear.bind(this)); + }; Client.prototype.forgotPassword = function (lang) { return this.api.passwordForgotSendCode(this.email, this.options, lang) .then( function (x) { - this.passwordForgotToken = x.passwordForgotToken + this.passwordForgotToken = x.passwordForgotToken; }.bind(this) - ) - } + ); + }; Client.prototype.reforgotPassword = function () { - return this.api.passwordForgotResendCode(this.passwordForgotToken, this.email) - } + return this.api.passwordForgotResendCode(this.passwordForgotToken, this.email); + }; Client.prototype.verifyPasswordResetCode = function (code, headers, options) { return this.api.passwordForgotVerifyCode(this.passwordForgotToken, code, headers, options) .then( function (result) { - this.accountResetToken = result.accountResetToken + this.accountResetToken = result.accountResetToken; }.bind(this) - ) - } + ); + }; Client.prototype.lockAccount = function () { - return this.api.accountLock(this.email, this.authPW) - } + return this.api.accountLock(this.email, this.authPW); + }; Client.prototype.resendAccountUnlockCode = function (lang) { - return this.api.accountUnlockResendCode(this.email, this.options, lang) - } + return this.api.accountUnlockResendCode(this.email, this.options, lang); + }; Client.prototype.verifyAccountUnlockCode = function (uid, code) { - return this.api.accountUnlockVerifyCode(uid, code) - } + return this.api.accountUnlockVerifyCode(uid, code); + }; Client.prototype.accountEmails = function () { - return this.api.accountEmails(this.sessionToken) - } + return this.api.accountEmails(this.sessionToken); + }; Client.prototype.createEmail = function (email) { - return this.api.createEmail(this.sessionToken, email) - } + return this.api.createEmail(this.sessionToken, email); + }; Client.prototype.deleteEmail = function (email) { - return this.api.deleteEmail(this.sessionToken, email) - } + return this.api.deleteEmail(this.sessionToken, email); + }; Client.prototype.setPrimaryEmail = function (email) { - return this.api.setPrimaryEmail(this.sessionToken, email) - } + return this.api.setPrimaryEmail(this.sessionToken, email); + }; Client.prototype.sendUnblockCode = function (email) { - return this.api.sendUnblockCode(email) - } + return this.api.sendUnblockCode(email); + }; Client.prototype.createTotpToken = function (options = {}) { - return this.api.createTotpToken(this.sessionToken, options) - } + return this.api.createTotpToken(this.sessionToken, options); + }; Client.prototype.deleteTotpToken = function () { - return this.api.deleteTotpToken(this.sessionToken) - } + return this.api.deleteTotpToken(this.sessionToken); + }; Client.prototype.checkTotpTokenExists = function () { - return this.api.checkTotpTokenExists(this.sessionToken) - } + return this.api.checkTotpTokenExists(this.sessionToken); + }; Client.prototype.verifyTotpCode = function (code, options = {}) { - return this.api.verifyTotpCode(this.sessionToken, code, options) - } + return this.api.verifyTotpCode(this.sessionToken, code, options); + }; Client.prototype.replaceRecoveryCodes = function (options = {}) { - return this.api.replaceRecoveryCodes(this.sessionToken, options) - } + return this.api.replaceRecoveryCodes(this.sessionToken, options); + }; Client.prototype.consumeRecoveryCode = function (code, options = {}) { - return this.api.consumeRecoveryCode(this.sessionToken, code, options) - } + return this.api.consumeRecoveryCode(this.sessionToken, code, options); + }; Client.prototype.createRecoveryKey = function (recoveryKeyId, recoveryData) { - return this.api.createRecoveryKey(this.sessionToken, recoveryKeyId, recoveryData) - } + return this.api.createRecoveryKey(this.sessionToken, recoveryKeyId, recoveryData); + }; Client.prototype.getRecoveryKey = function (recoveryKeyId) { if (! this.accountResetToken) { - throw new Error('call verifyPasswordResetCode before calling getRecoveryKey') + throw new Error('call verifyPasswordResetCode before calling getRecoveryKey'); } - return this.api.getRecoveryKey(this.accountResetToken, recoveryKeyId) - } + return this.api.getRecoveryKey(this.accountResetToken, recoveryKeyId); + }; Client.prototype.getRecoveryKeyExists = function (email) { if (! email) { - return this.api.getRecoveryKeyExistsWithSession(this.sessionToken) + return this.api.getRecoveryKeyExistsWithSession(this.sessionToken); } else { - return this.api.getRecoveryKeyExistsWithEmail(email) + return this.api.getRecoveryKeyExistsWithEmail(email); } - } + }; Client.prototype.deleteRecoveryKey = function () { - return this.api.deleteRecoveryKey(this.sessionToken) - } + return this.api.deleteRecoveryKey(this.sessionToken); + }; Client.prototype.resetAccountWithRecoveryKey = function (newPassword, kB, recoveryKeyId, headers, options = {}) { if (! this.accountResetToken) { - throw new Error('call verifyPasswordResetCode before calling resetAccountWithRecoveryKey') + throw new Error('call verifyPasswordResetCode before calling resetAccountWithRecoveryKey'); } - var email = this.email + var email = this.email; if (options && options.emailToHashWith) { - email = options.emailToHashWith + email = options.emailToHashWith; } return this.setupCredentials(email, newPassword) .then((/* bundle */) => { - const wrapKb = options.undefinedWrapKb ? undefined : butil.xorBuffers(kB, this.unwrapBKey).toString('hex') + const wrapKb = options.undefinedWrapKb ? undefined : butil.xorBuffers(kB, this.unwrapBKey).toString('hex'); return this.api.accountResetWithRecoveryKey( this.accountResetToken, @@ -614,30 +614,30 @@ module.exports = config => { ) .then(response => { // Update to the new verified tokens - this.sessionToken = response.sessionToken + this.sessionToken = response.sessionToken; if (options.keys) { - this.keyFetchToken = response.keyFetchToken + this.keyFetchToken = response.keyFetchToken; } - return response - }) + return response; + }); - }) - } + }); + }; Client.prototype.resetPassword = function (newPassword, headers, options) { if (! this.accountResetToken) { - throw new Error('call verifyPasswordResetCode before calling resetPassword') + throw new Error('call verifyPasswordResetCode before calling resetPassword'); } // With introduction of change email, the client can choose what to hash the password with. // To keep consistency, we hash with the email used to originally create the account. // This will generate a new wrapKb on the server - var email = this.email + var email = this.email; if (options && options.emailToHashWith) { - email = options.emailToHashWith + email = options.emailToHashWith; } return this.setupCredentials(email, newPassword) @@ -651,34 +651,34 @@ module.exports = config => { ) .then(response => { // Update to the new verified tokens - this.sessionToken = response.sessionToken - this.keyFetchToken = response.keyFetchToken + this.sessionToken = response.sessionToken; + this.keyFetchToken = response.keyFetchToken; - return response - }) + return response; + }); }.bind(this) - ) - } + ); + }; Client.prototype.smsSend = function (phoneNumber, messageId, features, mailbox) { return this.api.smsSend(this.sessionToken, phoneNumber, messageId, features) .then(result => { if (mailbox) { - return mailbox.waitForSms(phoneNumber) + return mailbox.waitForSms(phoneNumber); } - return result - }) - } + return result; + }); + }; Client.prototype.smsStatus = function (country, clientIpAddress) { - return this.api.smsStatus(this.sessionToken, country, clientIpAddress) - } + return this.api.smsStatus(this.sessionToken, country, clientIpAddress); + }; Client.prototype.consumeSigninCode = function (code, metricsContext) { - return this.api.consumeSigninCode(code, metricsContext) - } + return this.api.consumeSigninCode(code, metricsContext); + }; - return Client -} + return Client; +}; diff --git a/test/e2e/push_tests.js b/test/e2e/push_tests.js index 158f1241..5d04d43e 100644 --- a/test/e2e/push_tests.js +++ b/test/e2e/push_tests.js @@ -2,41 +2,41 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const config = require('../../config').getProperties() -const { mockDB, mockLog } = require('../mocks') -const { PushManager } = require('../push_helper') +const { assert } = require('chai'); +const config = require('../../config').getProperties(); +const { mockDB, mockLog } = require('../mocks'); +const { PushManager } = require('../push_helper'); -const mockUid = 'foo' +const mockUid = 'foo'; describe('e2e/push', () => { - let pushManager + let pushManager; before(() => { pushManager = new PushManager({ server: 'wss://push.services.mozilla.com/', channelId: '9500b5e6-9954-40d5-8ac1-3920832e781e' - }) - }) + }); + }); it('sendPush sends notifications using a real push server', () => { return pushManager.getSubscription() .then(subscription => { - let count = 0 + let count = 0; const thisSpyLog = mockLog({ info (op, log) { if (log.name === 'push.account_verify.success') { - count++ + count++; } } - }) + }); - const push = require('../../lib/push')(thisSpyLog, mockDB(), config) + const push = require('../../lib/push')(thisSpyLog, mockDB(), config); const options = { data: Buffer.from('foodata') - } + }; return push.sendPush(mockUid, [ { id: '0f7aa00356e5416e82b3bef7bc409eef', @@ -51,9 +51,9 @@ describe('e2e/push', () => { } ], 'accountVerify', options) .then(() => { - assert.equal(thisSpyLog.error.callCount, 0, 'No errors should have been logged') - assert.equal(count, 1, 'log.info::push.account_verify.success was called once') - }) - }) - }) -}) + assert.equal(thisSpyLog.error.callCount, 0, 'No errors should have been logged'); + assert.equal(count, 1, 'log.info::push.account_verify.success was called once'); + }); + }); + }); +}); diff --git a/test/key_server_stub.js b/test/key_server_stub.js index d8a9cffe..9dcb37d7 100644 --- a/test/key_server_stub.js +++ b/test/key_server_stub.js @@ -2,6 +2,6 @@ * 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/. */ -'use strict' +'use strict'; -require('../bin/key_server.js') +require('../bin/key_server.js'); diff --git a/test/known-ip-location.js b/test/known-ip-location.js index ac04d658..4b443a69 100644 --- a/test/known-ip-location.js +++ b/test/known-ip-location.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/. */ -'use strict' +'use strict'; module.exports = { // This is a known IP address and its expected location. @@ -24,4 +24,4 @@ module.exports = { stateCode: 'CA', tz: 'America/Los_Angeles' } -} +}; diff --git a/test/lib/util.js b/test/lib/util.js index 18b11fe9..36ec9832 100644 --- a/test/lib/util.js +++ b/test/lib/util.js @@ -4,7 +4,7 @@ // Collection of utils for tests -'use strict' +'use strict'; const ORIGINAL_STDOUT_WRITE = process.stdout.write; @@ -18,19 +18,19 @@ function disableLogs() { return function(string, encoding, fd) { const args = Array.prototype.slice.call(arguments); if (args[0] && LOGS_REGEX.test(args[0])) { - args[0] = '' + args[0] = ''; } - ORIGINAL_STDOUT_WRITE.apply(process.stdout, args) + ORIGINAL_STDOUT_WRITE.apply(process.stdout, args); }; }()); } function restoreStdoutWrite() { - process.stdout.write = ORIGINAL_STDOUT_WRITE + process.stdout.write = ORIGINAL_STDOUT_WRITE; } module.exports = { disableLogs, restoreStdoutWrite -} +}; diff --git a/test/local/authMethods.js b/test/local/authMethods.js index 6a3babb5..8857b8da 100644 --- a/test/local/authMethods.js +++ b/test/local/authMethods.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; -const mocks = require('../mocks') -const P = require('../../lib/promise') -const error = require('../../lib/error') +const mocks = require('../mocks'); +const P = require('../../lib/promise'); +const error = require('../../lib/error'); -const authMethods = require('../../lib/authMethods') +const authMethods = require('../../lib/authMethods'); const MOCK_ACCOUNT = { uid: 'abcdef123456' -} +}; describe('availableAuthenticationMethods', () => { - let mockDb + let mockDb; beforeEach(() => { - mockDb = mocks.mockDB() - }) + mockDb = mocks.mockDB(); + }); it('returns [`pwd`,`email`] for non-TOTP-enabled accounts', () => { - mockDb.totpToken = sinon.spy(() => { return P.reject(error.totpTokenNotFound()) }) + mockDb.totpToken = sinon.spy(() => { return P.reject(error.totpTokenNotFound()); }); return authMethods.availableAuthenticationMethods(mockDb, MOCK_ACCOUNT).then(amr => { - assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid) - assert.deepEqual(Array.from(amr).sort(), ['email', 'pwd']) - }) - }) + assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid); + assert.deepEqual(Array.from(amr).sort(), ['email', 'pwd']); + }); + }); it('returns [`pwd`,`email`,`otp`] for TOTP-enabled accounts', () => { mockDb.totpToken = sinon.spy(() => { @@ -38,13 +38,13 @@ describe('availableAuthenticationMethods', () => { verified: true, enabled: true, sharedSecret: 'secret!', - }) - }) + }); + }); return authMethods.availableAuthenticationMethods(mockDb, MOCK_ACCOUNT).then(amr => { - assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid) - assert.deepEqual(Array.from(amr).sort(), ['email', 'otp', 'pwd']) - }) - }) + assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid); + assert.deepEqual(Array.from(amr).sort(), ['email', 'otp', 'pwd']); + }); + }); it('returns [`pwd`,`email`] when TOTP token is not yet enabled', () => { mockDb.totpToken = sinon.spy(() => { @@ -52,75 +52,75 @@ describe('availableAuthenticationMethods', () => { verified: true, enabled: false, sharedSecret: 'secret!', - }) - }) + }); + }); return authMethods.availableAuthenticationMethods(mockDb, MOCK_ACCOUNT).then(amr => { - assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid) - assert.deepEqual(Array.from(amr).sort(), ['email', 'pwd']) - }) - }) + assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid); + assert.deepEqual(Array.from(amr).sort(), ['email', 'pwd']); + }); + }); it('rethrows unexpected DB errors', () => { - mockDb.totpToken = sinon.spy(() => { return P.reject(error.serviceUnavailable()) }) + mockDb.totpToken = sinon.spy(() => { return P.reject(error.serviceUnavailable()); }); return authMethods.availableAuthenticationMethods(mockDb, MOCK_ACCOUNT).then( - () => { assert.fail('error should have been re-thrown') }, + () => { assert.fail('error should have been re-thrown'); }, (err) => { - assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid) - assert.equal(err.errno, error.ERRNO.SERVER_BUSY) + assert.calledWithExactly(mockDb.totpToken, MOCK_ACCOUNT.uid); + assert.equal(err.errno, error.ERRNO.SERVER_BUSY); } - ) - }) -}) + ); + }); +}); describe('verificationMethodToAMR', () => { it('maps `email` to `email`', () => { - assert.equal(authMethods.verificationMethodToAMR('email'), 'email') - }) + assert.equal(authMethods.verificationMethodToAMR('email'), 'email'); + }); it('maps `email-captcha` to `email`', () => { - assert.equal(authMethods.verificationMethodToAMR('email-captcha'), 'email') - }) + assert.equal(authMethods.verificationMethodToAMR('email-captcha'), 'email'); + }); it('maps `email-2fa` to `email`', () => { - assert.equal(authMethods.verificationMethodToAMR('email-2fa'), 'email') - }) + assert.equal(authMethods.verificationMethodToAMR('email-2fa'), 'email'); + }); it('maps `totp-2fa` to `otp`', () => { - assert.equal(authMethods.verificationMethodToAMR('totp-2fa'), 'otp') - }) + assert.equal(authMethods.verificationMethodToAMR('totp-2fa'), 'otp'); + }); it('maps `recovery-code` to `otp`', () => { - assert.equal(authMethods.verificationMethodToAMR('recovery-code'), 'otp') - }) + assert.equal(authMethods.verificationMethodToAMR('recovery-code'), 'otp'); + }); it('throws when given an unknown verification method', () => { - assert.throws(() => { authMethods.verificationMethodToAMR('email-gotcha') }, /unknown verificationMethod/) - }) -}) + assert.throws(() => { authMethods.verificationMethodToAMR('email-gotcha'); }, /unknown verificationMethod/); + }); +}); describe('maximumAssuranceLevel', () => { it('returns 0 when no authentication methods are used', () => { - assert.equal(authMethods.maximumAssuranceLevel([]), 0) - assert.equal(authMethods.maximumAssuranceLevel(new Set()), 0) - }) + assert.equal(authMethods.maximumAssuranceLevel([]), 0); + assert.equal(authMethods.maximumAssuranceLevel(new Set()), 0); + }); it('returns 1 when only `pwd` auth is used', () => { - assert.equal(authMethods.maximumAssuranceLevel(['pwd']), 1) - }) + assert.equal(authMethods.maximumAssuranceLevel(['pwd']), 1); + }); it('returns 1 when only `email` auth is used', () => { - assert.equal(authMethods.maximumAssuranceLevel(['email']), 1) - }) + assert.equal(authMethods.maximumAssuranceLevel(['email']), 1); + }); it('returns 1 when only `otp` auth is used', () => { - assert.equal(authMethods.maximumAssuranceLevel(['otp']), 1) - }) + assert.equal(authMethods.maximumAssuranceLevel(['otp']), 1); + }); it('returns 1 when only things-you-know auth mechanisms are used', () => { - assert.equal(authMethods.maximumAssuranceLevel(['email', 'pwd']), 1) - }) + assert.equal(authMethods.maximumAssuranceLevel(['email', 'pwd']), 1); + }); it('returns 2 when both `pwd` and `otp` methods are used', () => { - assert.equal(authMethods.maximumAssuranceLevel(['pwd', 'otp']), 2) - }) -}) + assert.equal(authMethods.maximumAssuranceLevel(['pwd', 'otp']), 2); + }); +}); diff --git a/test/local/backendService.js b/test/local/backendService.js index 15dfb84b..ff2e6d45 100644 --- a/test/local/backendService.js +++ b/test/local/backendService.js @@ -2,27 +2,27 @@ * 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/. */ -'use strict' +'use strict'; -const nock = require('nock') -const Joi = require('joi') -const { assert } = require('chai') -const { mockLog } = require('../mocks') +const nock = require('nock'); +const Joi = require('joi'); +const { assert } = require('chai'); +const { mockLog } = require('../mocks'); -const error = require('../../lib/error') -const createBackendServiceAPI = require('../../lib/backendService') +const error = require('../../lib/error'); +const createBackendServiceAPI = require('../../lib/backendService'); -const mockConfig = {} +const mockConfig = {}; -const mockServiceURL = 'http://mock.service' -const mockService = nock(mockServiceURL) +const mockServiceURL = 'http://mock.service'; +const mockService = nock(mockServiceURL); describe('createBackendServiceAPI', () => { - let Service, api, log + let Service, api, log; beforeEach(() => { - log = mockLog() + log = mockLog(); Service = createBackendServiceAPI(log, mockConfig, 'mock-service', { testSimpleGet: { @@ -78,259 +78,259 @@ describe('createBackendServiceAPI', () => { } } - }) - api = new Service(mockServiceURL) - }) + }); + api = new Service(mockServiceURL); + }); afterEach(() => { - assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test') - }) + assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test'); + }); it('can make a simple GET request and return the response', async () => { mockService.get('/test_get/one/two') .reply(200, { hello: 'world' - }) - const resp = await api.testSimpleGet('one', 'two') + }); + const resp = await api.testSimpleGet('one', 'two'); assert.deepEqual(resp, { hello: 'world' - }) - }) + }); + }); it('can make a simple POST request and return the response', async () => { mockService.post('/test_post/abc') .reply(200, { hello: 'world' - }) - const resp = await api.testSimplePost('abc', { foo: 'bar' }) + }); + const resp = await api.testSimplePost('abc', { foo: 'bar' }); assert.deepEqual(resp, { hello: 'world' - }) - }) + }); + }); it('requires that a body be provided for POST requests', async () => { try { - await api.testSimplePost('abc') - assert.fail('should have thrown') + await api.testSimplePost('abc'); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.message, 'mock-service.testSimplePost must be called with 2 arguments (1 given)') + assert.equal(err.message, 'mock-service.testSimplePost must be called with 2 arguments (1 given)'); } - }) + }); it('validates the request body', async () => { try { - await api.testSimplePost('abc', { foo: 123 }) - assert.fail('should have thrown') + await api.testSimplePost('abc', { foo: 123 }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(err.output.payload.op, 'mock-service.testSimplePost') + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(err.output.payload.op, 'mock-service.testSimplePost'); assert.deepEqual(err.output.payload.data, { location: 'request', value: { foo: 123 } - }) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testSimplePost') - assert.equal(log.error.getCall(0).args[1].error, 'request schema validation failed') - assert.ok(/"foo" must be a string/.test(log.error.getCall(0).args[1].message)) + }); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testSimplePost'); + assert.equal(log.error.getCall(0).args[1].error, 'request schema validation failed'); + assert.ok(/"foo" must be a string/.test(log.error.getCall(0).args[1].message)); } - }) + }); it('validates path parameters', async () => { try { - await api.testGetWithValidation('ABC', '123', {}) - assert.fail('should have thrown') + await api.testGetWithValidation('ABC', '123', {}); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(err.output.payload.op, 'mock-service.testGetWithValidation') + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(err.output.payload.op, 'mock-service.testGetWithValidation'); assert.deepEqual(err.output.payload.data, { location: 'params', value: { first: 'ABC', second: '123' } - }) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation') - assert.equal(log.error.getCall(0).args[1].error, 'params schema validation failed') - assert.ok(/fails to match the required pattern/.test(log.error.getCall(0).args[1].message)) + }); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation'); + assert.equal(log.error.getCall(0).args[1].error, 'params schema validation failed'); + assert.ok(/fails to match the required pattern/.test(log.error.getCall(0).args[1].message)); } - log.error.resetHistory() + log.error.resetHistory(); try { - await api.testGetWithValidation('abc', 123, {}) - assert.fail('should have thrown') + await api.testGetWithValidation('abc', 123, {}); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation') - assert.equal(log.error.getCall(0).args[1].error, 'params schema validation failed') - assert.ok(/"second" must be a string/.test(log.error.getCall(0).args[1].message)) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation'); + assert.equal(log.error.getCall(0).args[1].error, 'params schema validation failed'); + assert.ok(/"second" must be a string/.test(log.error.getCall(0).args[1].message)); } - }) + }); it('rejects unsafe path parameters', async () => { try { - await api.testSimpleGet('abc\n', '123') - assert.fail('should have thrown') + await api.testSimpleGet('abc\n', '123'); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'safeUrl.unsafe') - assert.equal(log.error.getCall(0).args[1].key, 'first') + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'safeUrl.unsafe'); + assert.equal(log.error.getCall(0).args[1].key, 'first'); } - }) + }); it('validates query paramters', async () => { try { - await api.testGetWithValidation('abc', '123', { foo: 123 }) - assert.fail('should have thrown') + await api.testGetWithValidation('abc', '123', { foo: 123 }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(err.output.payload.op, 'mock-service.testGetWithValidation') + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(err.output.payload.op, 'mock-service.testGetWithValidation'); assert.deepEqual(err.output.payload.data, { location: 'query', value: { foo: 123 } - }) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation') - assert.equal(log.error.getCall(0).args[1].error, 'query schema validation failed') - assert.ok(/"foo" must be a string/.test(log.error.getCall(0).args[1].message)) + }); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testGetWithValidation'); + assert.equal(log.error.getCall(0).args[1].error, 'query schema validation failed'); + assert.ok(/"foo" must be a string/.test(log.error.getCall(0).args[1].message)); } - }) + }); it('rejects unsafe query parameters', async () => { try { - await api.testGetWithValidation('abc', '123', { foo: '123\n' }) - assert.fail('should have thrown') + await api.testGetWithValidation('abc', '123', { foo: '123\n' }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'safeUrl.unsafe') - assert.equal(log.error.getCall(0).args[1].key, 'foo') + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'safeUrl.unsafe'); + assert.equal(log.error.getCall(0).args[1].key, 'foo'); } - }) + }); it('requires that query parameters be present if schema is declared', async () => { try { - await api.testGetWithValidation('abc', '123') - assert.fail('should have thrown') + await api.testGetWithValidation('abc', '123'); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.message, 'mock-service.testGetWithValidation must be called with 3 arguments (2 given)') + assert.equal(err.message, 'mock-service.testGetWithValidation must be called with 3 arguments (2 given)'); } - }) + }); it('validates response body', async () => { - let requestBody + let requestBody; mockService.post('/test_post/abc', body => { - requestBody = body - return true + requestBody = body; + return true; }) .query({ bar: 'baz' }) .reply(200, { status: 200, message: 'ok' - }) - const resp = await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }) + }); + const resp = await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }); assert.deepEqual(requestBody, { foo: 'bar' - }) + }); assert.deepEqual(resp, { status: 200, message: 'ok' - }) + }); mockService.post('/test_post/abc', () => true) .query({ bar: 'baz' }) .reply(200, { status: 'whoops', message: 'whoops' - }) + }); try { - await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }) - assert.fail('should have thrown') + await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation') - assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed') - assert.ok(/"status" must be a number/.test(log.error.getCall(0).args[1].message)) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation'); + assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed'); + assert.ok(/"status" must be a number/.test(log.error.getCall(0).args[1].message)); } - }) + }); it('strips unknown keys from response body', async () => { - let requestBody + let requestBody; mockService.post('/test_post/abc', body => { - requestBody = body - return true + requestBody = body; + return true; }) .query({ bar: 'baz' }) .reply(200, { status: 200, message: 'ok', something: 'extra' - }) - const resp = await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }) + }); + const resp = await api.testPostWithValidation('abc', { bar: 'baz' }, { foo: 'bar' }); assert.deepEqual(requestBody, { foo: 'bar' - }) + }); assert.deepEqual(resp, { status: 200, message: 'ok' - }) - }) + }); + }); it('re-throws 400-level errors returned by the service', async () => { mockService.post('/test_post/abc', body => true) .reply(400, { message: 'invalid frobble', - }) + }); try { - await api.testPostWithValidation('abc', {}, { foo: 'bar'}) - assert.fail('should have thrown') + await api.testPostWithValidation('abc', {}, { foo: 'bar'}); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.statusCode, 400) - assert.equal(err.message, 'invalid frobble') + assert.equal(err.statusCode, 400); + assert.equal(err.message, 'invalid frobble'); } - }) + }); it('logs 500-level errors and returns backendServiceFailure', async () => { mockService.post('/test_post/abc', body => true) .reply(500, { message: 'invalid frobble', - }) + }); try { - await api.testPostWithValidation('abc', {}, { foo: 'bar'}) - assert.fail('should have thrown') + await api.testPostWithValidation('abc', {}, { foo: 'bar'}); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation.1') - assert.deepEqual(log.error.getCall(0).args[1].params, { id: 'abc' }) - assert.deepEqual(log.error.getCall(0).args[1].query, {}) - assert.deepEqual(log.error.getCall(0).args[1].payload, { foo: 'bar' }) - assert.deepEqual(log.error.getCall(0).args[1].err.message, 'invalid frobble') + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation.1'); + assert.deepEqual(log.error.getCall(0).args[1].params, { id: 'abc' }); + assert.deepEqual(log.error.getCall(0).args[1].query, {}); + assert.deepEqual(log.error.getCall(0).args[1].payload, { foo: 'bar' }); + assert.deepEqual(log.error.getCall(0).args[1].err.message, 'invalid frobble'); } - }) + }); it('logs connection errors and returns backendServiceFailure', async () => { mockService.post('/test_post/abc', body => true) - .replyWithError('ruh-roh!') + .replyWithError('ruh-roh!'); try { - await api.testPostWithValidation('abc', {}, { foo: 'bar'}) - assert.fail('should have thrown') + await api.testPostWithValidation('abc', {}, { foo: 'bar'}); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation.1') - assert.deepEqual(log.error.getCall(0).args[1].params, { id: 'abc' }) - assert.deepEqual(log.error.getCall(0).args[1].query, {}) - assert.deepEqual(log.error.getCall(0).args[1].payload, { foo: 'bar' }) - assert.ok(log.error.getCall(0).args[1].err.message.indexOf('ruh-roh!') >= 0) + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'mock-service.testPostWithValidation.1'); + assert.deepEqual(log.error.getCall(0).args[1].params, { id: 'abc' }); + assert.deepEqual(log.error.getCall(0).args[1].query, {}); + assert.deepEqual(log.error.getCall(0).args[1].payload, { foo: 'bar' }); + assert.ok(log.error.getCall(0).args[1].err.message.indexOf('ruh-roh!') >= 0); } - }) + }); -}) +}); diff --git a/test/local/bounces.js b/test/local/bounces.js index 52a79012..5390ced6 100644 --- a/test/local/bounces.js +++ b/test/local/bounces.js @@ -2,37 +2,37 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const config = require(`${ROOT_DIR}/config`).getProperties() -const createBounces = require(`${ROOT_DIR}/lib/bounces`) -const error = require(`${ROOT_DIR}/lib/error`) -const P = require('bluebird') -const sinon = require('sinon') +const { assert } = require('chai'); +const config = require(`${ROOT_DIR}/config`).getProperties(); +const createBounces = require(`${ROOT_DIR}/lib/bounces`); +const error = require(`${ROOT_DIR}/lib/error`); +const P = require('bluebird'); +const sinon = require('sinon'); -const EMAIL = Math.random() + '@example.test' -const BOUNCE_TYPE_HARD = 1 -const BOUNCE_TYPE_COMPLAINT = 3 +const EMAIL = Math.random() + '@example.test'; +const BOUNCE_TYPE_HARD = 1; +const BOUNCE_TYPE_COMPLAINT = 3; -const NOW = Date.now() +const NOW = Date.now(); describe('bounces', () => { it('succeeds if bounces not over limit', () => { const db = { emailBounces: sinon.spy(() => P.resolve([])) - } + }; return createBounces(config, db).check(EMAIL) .then(() => { - assert.equal(db.emailBounces.callCount, 1) - }) - }) + assert.equal(db.emailBounces.callCount, 1); + }); + }); it('error if complaints over limit', () => { - const conf = Object.assign({}, config) + const conf = Object.assign({}, config); conf.smtp = { bounces: { enabled: true, @@ -40,7 +40,7 @@ describe('bounces', () => { 0: Infinity } } - } + }; const db = { emailBounces: sinon.spy(() => P.resolve([ { @@ -48,19 +48,19 @@ describe('bounces', () => { createdAt: NOW } ])) - } + }; return createBounces(conf, db).check(EMAIL) .then( () => assert(false), e => { - assert.equal(db.emailBounces.callCount, 1) - assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT) + assert.equal(db.emailBounces.callCount, 1); + assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT); } - ) - }) + ); + }); it('error if hard bounces over limit', () => { - const conf = Object.assign({}, config) + const conf = Object.assign({}, config); conf.smtp = { bounces: { enabled: true, @@ -69,8 +69,8 @@ describe('bounces', () => { 1: 5000 } } - } - const DATE = Date.now() - 1000 + }; + const DATE = Date.now() - 1000; const db = { emailBounces: sinon.spy(() => P.resolve([ { @@ -82,20 +82,20 @@ describe('bounces', () => { createdAt: DATE - 1000 } ])) - } + }; return createBounces(conf, db).check(EMAIL) .then( () => assert(false), e => { - assert.equal(db.emailBounces.callCount, 1) - assert.equal(e.errno, error.ERRNO.BOUNCE_HARD) - assert.equal(e.output.payload.bouncedAt, DATE) + assert.equal(db.emailBounces.callCount, 1); + assert.equal(e.errno, error.ERRNO.BOUNCE_HARD); + assert.equal(e.output.payload.bouncedAt, DATE); } - ) - }) + ); + }); it('does not error if not enough bounces in duration', () => { - const conf = Object.assign({}, config) + const conf = Object.assign({}, config); conf.smtp = { bounces: { enabled: true, @@ -104,7 +104,7 @@ describe('bounces', () => { 1: 50000 } } - } + }; const db = { emailBounces: sinon.spy(() => P.resolve([ { @@ -112,15 +112,15 @@ describe('bounces', () => { createdAt: Date.now() - 20000 } ])) - } + }; return createBounces(conf, db).check(EMAIL) .then(() => { - assert.equal(db.emailBounces.callCount, 1) - }) - }) + assert.equal(db.emailBounces.callCount, 1); + }); + }); it('does not error if not enough complaints in duration', () => { - const conf = Object.assign({}, config) + const conf = Object.assign({}, config); conf.smtp = { bounces: { enabled: true, @@ -129,7 +129,7 @@ describe('bounces', () => { 1: 50000 } } - } + }; const db = { emailBounces: sinon.spy(() => P.resolve([ { @@ -137,22 +137,22 @@ describe('bounces', () => { createdAt: Date.now() - 20000 } ])) - } + }; return createBounces(conf, db).check(EMAIL) .then(() => { - assert.equal(db.emailBounces.callCount, 1) - }) - }) + assert.equal(db.emailBounces.callCount, 1); + }); + }); describe('disabled', () => { it('does not call bounces.check if disabled', () => { - const conf = Object.assign({}, config) + const conf = Object.assign({}, config); conf.smtp = { bounces: { enabled: false } - } + }; const db = { emailBounces: sinon.spy(() => P.resolve([ { @@ -160,12 +160,12 @@ describe('bounces', () => { createdAt: Date.now() - 20000 } ])) - } - assert.equal(db.emailBounces.callCount, 0) + }; + assert.equal(db.emailBounces.callCount, 0); return createBounces(conf, db).check(EMAIL) .then(() => { - assert.equal(db.emailBounces.callCount, 0) - }) - }) - }) -}) + assert.equal(db.emailBounces.callCount, 0); + }); + }); + }); +}); diff --git a/test/local/cache.js b/test/local/cache.js index fa9ff8de..c07f4294 100644 --- a/test/local/cache.js +++ b/test/local/cache.js @@ -2,224 +2,224 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const crypto = require('crypto') -const Memcached = require('memcached') -const mocks = require('../mocks') -const P = require(`${ROOT_DIR}/lib/promise`) -const sinon = require('sinon') +const { assert } = require('chai'); +const crypto = require('crypto'); +const Memcached = require('memcached'); +const mocks = require('../mocks'); +const P = require(`${ROOT_DIR}/lib/promise`); +const sinon = require('sinon'); -const modulePath = `${ROOT_DIR}/lib/cache` +const modulePath = `${ROOT_DIR}/lib/cache`; describe('cache:', () => { - let sandbox, log, cache, token, digest + let sandbox, log, cache, token, digest; beforeEach(() => { - sandbox = sinon.createSandbox() - log = mocks.mockLog() + sandbox = sinon.createSandbox(); + log = mocks.mockLog(); cache = require(modulePath)(log, { memcached: { address: '127.0.0.1:1121', idle: 500, lifetime: 30 } - }, 'wibble') + }, 'wibble'); token = { uid: Buffer.alloc(32, 'cd'), id: 'deadbeef' - } - const hash = crypto.createHash('sha256') - hash.update(token.uid) - hash.update(token.id) - digest = hash.digest('base64') - }) + }; + const hash = crypto.createHash('sha256'); + hash.update(token.uid); + hash.update(token.id); + digest = hash.digest('base64'); + }); - afterEach(() => sandbox.restore()) + afterEach(() => sandbox.restore()); it('exports the correct interface', () => { - assert.ok(cache) - assert.equal(typeof cache, 'object') - assert.equal(Object.keys(cache).length, 3) - assert.equal(typeof cache.add, 'function') - assert.equal(cache.add.length, 2) - assert.equal(typeof cache.del, 'function') - assert.equal(cache.del.length, 1) - assert.equal(typeof cache.get, 'function') - assert.equal(cache.get.length, 1) - }) + assert.ok(cache); + assert.equal(typeof cache, 'object'); + assert.equal(Object.keys(cache).length, 3); + assert.equal(typeof cache.add, 'function'); + assert.equal(cache.add.length, 2); + assert.equal(typeof cache.del, 'function'); + assert.equal(cache.del.length, 1); + assert.equal(typeof cache.get, 'function'); + assert.equal(cache.get.length, 1); + }); describe('memcached resolves:', () => { beforeEach(() => { - sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.resolve()) - sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.resolve()) - sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.resolve('mock get result')) - }) + sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.resolve()); + sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.resolve()); + sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.resolve('mock get result')); + }); describe('add:', () => { beforeEach(() => { - return cache.add(digest, 'wibble') - }) + return cache.add(digest, 'wibble'); + }); it('calls memcached.addAsync correctly', () => { - assert.equal(Memcached.prototype.addAsync.callCount, 1) - const args = Memcached.prototype.addAsync.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], digest) - assert.equal(args[1], 'wibble') - assert.equal(args[2], 30) + assert.equal(Memcached.prototype.addAsync.callCount, 1); + const args = Memcached.prototype.addAsync.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], digest); + assert.equal(args[1], 'wibble'); + assert.equal(args[2], 30); - assert.equal(Memcached.prototype.delAsync.callCount, 0) - assert.equal(Memcached.prototype.getAsync.callCount, 0) - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(Memcached.prototype.delAsync.callCount, 0); + assert.equal(Memcached.prototype.getAsync.callCount, 0); + assert.equal(log.error.callCount, 0); + }); + }); describe('del:', () => { beforeEach(() => { - return cache.del(digest) - }) + return cache.del(digest); + }); it('calls memcached.delAsync correctly', () => { - assert.equal(Memcached.prototype.delAsync.callCount, 1) - const args = Memcached.prototype.delAsync.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], digest) + assert.equal(Memcached.prototype.delAsync.callCount, 1); + const args = Memcached.prototype.delAsync.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], digest); - assert.equal(Memcached.prototype.addAsync.callCount, 0) - assert.equal(Memcached.prototype.getAsync.callCount, 0) - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(Memcached.prototype.addAsync.callCount, 0); + assert.equal(Memcached.prototype.getAsync.callCount, 0); + assert.equal(log.error.callCount, 0); + }); + }); describe('get:', () => { - let result + let result; beforeEach(() => { return cache.get(digest) - .then(r => result = r) - }) + .then(r => result = r); + }); it('returns the correct result', () => { - assert.equal(result, 'mock get result') - }) + assert.equal(result, 'mock get result'); + }); it('calls memcached.getAsync correctly', () => { - assert.equal(Memcached.prototype.getAsync.callCount, 1) - const args = Memcached.prototype.getAsync.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], digest) + assert.equal(Memcached.prototype.getAsync.callCount, 1); + const args = Memcached.prototype.getAsync.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], digest); - assert.equal(Memcached.prototype.addAsync.callCount, 0) - assert.equal(Memcached.prototype.delAsync.callCount, 0) - assert.equal(log.error.callCount, 0) - }) - }) - }) + assert.equal(Memcached.prototype.addAsync.callCount, 0); + assert.equal(Memcached.prototype.delAsync.callCount, 0); + assert.equal(log.error.callCount, 0); + }); + }); + }); describe('memcached rejects:', () => { beforeEach(() => { - sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.reject('foo')) - sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.reject('bar')) - sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.reject('baz')) - }) + sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.reject('foo')); + sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.reject('bar')); + sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.reject('baz')); + }); describe('add:', () => { - let error + let error; beforeEach(() => { return cache.add(digest, 'wibble') - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('propagates the error', () => { - assert.equal(error, 'foo') - }) - }) + assert.equal(error, 'foo'); + }); + }); describe('del:', () => { - let error + let error; beforeEach(() => { return cache.del(digest) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('propagates the error', () => { - assert.equal(error, 'bar') - }) - }) + assert.equal(error, 'bar'); + }); + }); describe('get:', () => { - let error + let error; beforeEach(() => { return cache.get(digest) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('propagates the error', () => { - assert.equal(error, 'baz') - }) - }) - }) -}) + assert.equal(error, 'baz'); + }); + }); + }); +}); describe('null cache:', () => { - let sandbox, log, cache, token + let sandbox, log, cache, token; beforeEach(() => { - sandbox = sinon.createSandbox() - log = mocks.mockLog() + sandbox = sinon.createSandbox(); + log = mocks.mockLog(); cache = require(modulePath)(log, { memcached: { address: 'none', idle: 500, lifetime: 30 } - }, 'wibble') + }, 'wibble'); token = { uid: Buffer.alloc(32, 'cd'), id: 'deadbeef' - } - sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.resolve()) - sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.resolve()) - sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.resolve()) - }) + }; + sandbox.stub(Memcached.prototype, 'addAsync').callsFake(() => P.resolve()); + sandbox.stub(Memcached.prototype, 'delAsync').callsFake(() => P.resolve()); + sandbox.stub(Memcached.prototype, 'getAsync').callsFake(() => P.resolve()); + }); - afterEach(() => sandbox.restore()) + afterEach(() => sandbox.restore()); describe('add:', () => { beforeEach(() => { - return cache.add(token, {}) - }) + return cache.add(token, {}); + }); it('did not call memcached.addAsync', () => { - assert.equal(Memcached.prototype.addAsync.callCount, 0) - }) - }) + assert.equal(Memcached.prototype.addAsync.callCount, 0); + }); + }); describe('del:', () => { beforeEach(() => { - return cache.del(token) - }) + return cache.del(token); + }); it('did not call memcached.delAsync', () => { - assert.equal(Memcached.prototype.delAsync.callCount, 0) - }) - }) + assert.equal(Memcached.prototype.delAsync.callCount, 0); + }); + }); describe('get:', () => { beforeEach(() => { - return cache.get(token) - }) + return cache.get(token); + }); it('did not call memcached.getAsync', () => { - assert.equal(Memcached.prototype.getAsync.callCount, 0) - }) - }) -}) + assert.equal(Memcached.prototype.getAsync.callCount, 0); + }); + }); +}); diff --git a/test/local/config/index.js b/test/local/config/index.js index 1a7b6df9..3bac6b9e 100644 --- a/test/local/config/index.js +++ b/test/local/config/index.js @@ -1,46 +1,46 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const proxyquire = require('proxyquire') +const { assert } = require('chai'); +const proxyquire = require('proxyquire'); -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; describe('Config', () => { describe('NODE_ENV=prod', () => { let originalEnv; function mockEnv(key, value) { - originalEnv[key] = process.env[key] - process.env[key] = value + originalEnv[key] = process.env[key]; + process.env[key] = value; } beforeEach(() => { - originalEnv = {} - mockEnv('NODE_ENV', 'prod') - }) + originalEnv = {}; + mockEnv('NODE_ENV', 'prod'); + }); afterEach(() => { for (const key in originalEnv) { - process.env[key] = originalEnv[key] + process.env[key] = originalEnv[key]; } - }) + }); it('errors when secret settings have their default values', () => { assert.throws(() => { - proxyquire(`${ROOT_DIR}/config`, {}) - }, /Config \'[a-zA-Z.]+\' must be set in production/) - }) + proxyquire(`${ROOT_DIR}/config`, {}); + }, /Config \'[a-zA-Z.]+\' must be set in production/); + }); it('succeeds when secret settings have all been configured', () => { - mockEnv('PUSHBOX_KEY', 'production secret here') - mockEnv('FLOW_ID_KEY', 'production secret here') - mockEnv('OAUTH_SERVER_SECRET_KEY', 'production secret here') + mockEnv('PUSHBOX_KEY', 'production secret here'); + mockEnv('FLOW_ID_KEY', 'production secret here'); + mockEnv('OAUTH_SERVER_SECRET_KEY', 'production secret here'); assert.doesNotThrow(() => { - proxyquire(`${ROOT_DIR}/config`, {}) - }) - }) - }) -}) + proxyquire(`${ROOT_DIR}/config`, {}); + }); + }); + }); +}); diff --git a/test/local/crypto/butil.js b/test/local/crypto/butil.js index c1252e59..bcca2607 100644 --- a/test/local/crypto/butil.js +++ b/test/local/crypto/butil.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -const assert = require('assert') -const butil = require('../../../lib/crypto/butil') +const assert = require('assert'); +const butil = require('../../../lib/crypto/butil'); describe('butil', () => { @@ -14,20 +14,20 @@ describe('butil', () => { it( 'returns false if lengths are different', () => { - assert.equal(butil.buffersAreEqual(Buffer.alloc(2), Buffer.alloc(4)), false) + assert.equal(butil.buffersAreEqual(Buffer.alloc(2), Buffer.alloc(4)), false); } - ) + ); it( 'returns true if buffers have same bytes', () => { - const b1 = Buffer.from('abcd', 'hex') - const b2 = Buffer.from('abcd', 'hex') - assert.equal(butil.buffersAreEqual(b1, b2), true) + const b1 = Buffer.from('abcd', 'hex'); + const b2 = Buffer.from('abcd', 'hex'); + assert.equal(butil.buffersAreEqual(b1, b2), true); } - ) + ); - }) + }); describe('.xorBuffers', () => { @@ -35,20 +35,20 @@ describe('butil', () => { 'throws an Error if lengths are different', () => { assert.throws(() => { - butil.xorBuffers(Buffer.alloc(2), Buffer.alloc(4)) - }) + butil.xorBuffers(Buffer.alloc(2), Buffer.alloc(4)); + }); } - ) + ); it( 'should return a Buffer with bits ORed', () => { - const b1 = Buffer.from('e5', 'hex') - const b2 = Buffer.from('5e', 'hex') - assert.deepEqual(butil.xorBuffers(b1, b2), Buffer.from('bb', 'hex')) + const b1 = Buffer.from('e5', 'hex'); + const b2 = Buffer.from('5e', 'hex'); + assert.deepEqual(butil.xorBuffers(b1, b2), Buffer.from('bb', 'hex')); } - ) + ); - }) + }); -}) +}); diff --git a/test/local/crypto/hkdf.js b/test/local/crypto/hkdf.js index 502b0ee1..823125c8 100644 --- a/test/local/crypto/hkdf.js +++ b/test/local/crypto/hkdf.js @@ -2,32 +2,32 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const hkdf = require('../../../lib/crypto/hkdf') +const { assert } = require('chai'); +const hkdf = require('../../../lib/crypto/hkdf'); describe('hkdf', () => { it( 'should extract', () => { - let stretchedPw = 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c' - stretchedPw = Buffer.from (stretchedPw, 'hex') - const info = 'mainKDF' - const salt = Buffer.from ('00f000000000000000000000000000000000000000000000000000000000034d', 'hex') - const lengthHkdf = 2 * 32 + let stretchedPw = 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c'; + stretchedPw = Buffer.from (stretchedPw, 'hex'); + const info = 'mainKDF'; + const salt = Buffer.from ('00f000000000000000000000000000000000000000000000000000000000034d', 'hex'); + const lengthHkdf = 2 * 32; return hkdf(stretchedPw, info, salt, lengthHkdf) .then( function (hkdfResult) { - const hkdfStr = hkdfResult.toString('hex') + const hkdfStr = hkdfResult.toString('hex'); - assert.equal(hkdfStr.substring(0, 64), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d') - assert.equal(hkdfStr.substring(64, 128), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a') - assert.equal(salt.toString('hex'), '00f000000000000000000000000000000000000000000000000000000000034d') + assert.equal(hkdfStr.substring(0, 64), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d'); + assert.equal(hkdfStr.substring(64, 128), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a'); + assert.equal(salt.toString('hex'), '00f000000000000000000000000000000000000000000000000000000000034d'); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/crypto/pbkdf2.js b/test/local/crypto/pbkdf2.js index 3d628cfc..5a796c42 100644 --- a/test/local/crypto/pbkdf2.js +++ b/test/local/crypto/pbkdf2.js @@ -2,61 +2,61 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const pbkdf2 = require('../../../lib/crypto/pbkdf2') -const ITERATIONS = 20000 -const LENGTH = 32 +const { assert } = require('chai'); +const pbkdf2 = require('../../../lib/crypto/pbkdf2'); +const ITERATIONS = 20000; +const LENGTH = 32; describe('pbkdf2', () => { it( 'pbkdf2 derive', () => { - var salt = Buffer.from('identity.mozilla.com/picl/v1/first-PBKDF:andré@example.org') - var password = Buffer.from('pässwörd') + var salt = Buffer.from('identity.mozilla.com/picl/v1/first-PBKDF:andré@example.org'); + var password = Buffer.from('pässwörd'); return pbkdf2.derive(password, salt, ITERATIONS, LENGTH) .then( function (K1) { - assert.equal(K1.toString('hex'), 'f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd') + assert.equal(K1.toString('hex'), 'f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd'); } - ) + ); } - ) + ); it( 'pbkdf2 derive long input', () => { - var email = Buffer.from('ijqmkkafer3xsj5rzoq+msnxsacvkmqxabtsvxvj@some-test-domain-with-a-long-name-example.org') - var password = Buffer.from('mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ') - var salt = Buffer.from('identity.mozilla.com/picl/v1/first-PBKDF:' + email) + var email = Buffer.from('ijqmkkafer3xsj5rzoq+msnxsacvkmqxabtsvxvj@some-test-domain-with-a-long-name-example.org'); + var password = Buffer.from('mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ'); + var salt = Buffer.from('identity.mozilla.com/picl/v1/first-PBKDF:' + email); return pbkdf2.derive(password, salt, ITERATIONS, LENGTH) .then( function (K1) { - assert.equal(K1.toString('hex'), '5f99c22dfac713b6d73094604a05082e6d345f8a00d4947e57105733f51216eb') + assert.equal(K1.toString('hex'), '5f99c22dfac713b6d73094604a05082e6d345f8a00d4947e57105733f51216eb'); } - ) + ); } - ) + ); it( 'pbkdf2 derive bit array', () => { - var salt = Buffer.from('identity.mozilla.com/picl/v1/second-PBKDF:andré@example.org') - var K2 = '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5' - var passwordString = 'pässwörd' + var salt = Buffer.from('identity.mozilla.com/picl/v1/second-PBKDF:andré@example.org'); + var K2 = '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5'; + var passwordString = 'pässwörd'; var password = Buffer.concat([ Buffer.from(K2, 'hex'), Buffer.from(passwordString) - ]) + ]); return pbkdf2.derive(password, salt, ITERATIONS, LENGTH) .then( function (K1) { - assert.equal(K1.toString('hex'), 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c') + assert.equal(K1.toString('hex'), 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c'); } - ) + ); } - ) + ); -}) +}); diff --git a/test/local/crypto/random.js b/test/local/crypto/random.js index c5b9b214..053ef6e5 100644 --- a/test/local/crypto/random.js +++ b/test/local/crypto/random.js @@ -2,116 +2,116 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const random = require('../../../lib/crypto/random') -const base10 = random.base10 -const base32 = random.base32 +const random = require('../../../lib/crypto/random'); +const base10 = random.base10; +const base32 = random.base32; describe('random', () => { it('should generate random bytes', () => { return random(16) .then(bytes => { - assert(Buffer.isBuffer(bytes)) - assert.equal(bytes.length, 16) + assert(Buffer.isBuffer(bytes)); + assert.equal(bytes.length, 16); - return random(32) + return random(32); }) .then(bytes => { - assert(Buffer.isBuffer(bytes)) - assert.equal(bytes.length, 32) - }) - }) + assert(Buffer.isBuffer(bytes)); + assert.equal(bytes.length, 32); + }); + }); it('should generate several random bytes buffers', () => { return random(16, 8) .then(bufs => { - assert.equal(bufs.length, 2) + assert.equal(bufs.length, 2); - const a = bufs[0] - const b = bufs[1] + const a = bufs[0]; + const b = bufs[1]; - assert(Buffer.isBuffer(a)) - assert.equal(a.length, 16) + assert(Buffer.isBuffer(a)); + assert.equal(a.length, 16); - assert(Buffer.isBuffer(b)) - assert.equal(b.length, 8) - }) - }) + assert(Buffer.isBuffer(b)); + assert.equal(b.length, 8); + }); + }); describe('hex', () => { it('should generate a random hex string', () => { return random.hex(16) .then(str => { - assert.equal(typeof str, 'string') - assert(/^[0-9a-f]+$/g.test(str)) - assert.equal(str.length, 32) + assert.equal(typeof str, 'string'); + assert(/^[0-9a-f]+$/g.test(str)); + assert.equal(str.length, 32); - return random.hex(32) + return random.hex(32); }) .then(str => { - assert.equal(typeof str, 'string') - assert.equal(str.length, 64) - }) - }) + assert.equal(typeof str, 'string'); + assert.equal(str.length, 64); + }); + }); it('should generate several random hex strings', () => { return random.hex(16, 8) .then(strs => { - assert.equal(strs.length, 2) + assert.equal(strs.length, 2); - const a = strs[0] - const b = strs[1] + const a = strs[0]; + const b = strs[1]; - assert.equal(typeof a, 'string') - assert(/^[0-9a-f]+$/g.test(a)) - assert.equal(a.length, 32) + assert.equal(typeof a, 'string'); + assert(/^[0-9a-f]+$/g.test(a)); + assert.equal(a.length, 32); - assert.equal(typeof b, 'string') - assert(/^[0-9a-f]+$/g.test(b)) - assert.equal(b.length, 16) - }) - }) - }) + assert.equal(typeof b, 'string'); + assert(/^[0-9a-f]+$/g.test(b)); + assert.equal(b.length, 16); + }); + }); + }); describe('base32', () => { it('takes 1 integer argument, returns a function', () => { - assert.equal(typeof base32, 'function') - assert.equal(base32.length, 1) - const gen = base32(10) - assert.equal(typeof gen, 'function') - assert.equal(gen.length, 0) - }) + assert.equal(typeof base32, 'function'); + assert.equal(base32.length, 1); + const gen = base32(10); + assert.equal(typeof gen, 'function'); + assert.equal(gen.length, 0); + }); it('should have correct output', () => { return base32(10)().then(code => { - assert.equal(code.length, 10, 'matches length') - assert.ok(/^[0-9A-Z]+$/.test(code), 'no lowercase letters') - assert.equal(code.indexOf('I'), -1, 'no I') - assert.equal(code.indexOf('L'), -1, 'no L') - assert.equal(code.indexOf('O'), -1, 'no O') - assert.equal(code.indexOf('U'), -1, 'no U') - }) - }) - }) + assert.equal(code.length, 10, 'matches length'); + assert.ok(/^[0-9A-Z]+$/.test(code), 'no lowercase letters'); + assert.equal(code.indexOf('I'), -1, 'no I'); + assert.equal(code.indexOf('L'), -1, 'no L'); + assert.equal(code.indexOf('O'), -1, 'no O'); + assert.equal(code.indexOf('U'), -1, 'no U'); + }); + }); + }); describe('base10', () => { it('takes 1 integer argument, returns a function', () => { - assert.equal(typeof base10, 'function') - assert.equal(base10.length, 1) - const gen = base10(10) - assert.equal(typeof gen, 'function') - assert.equal(gen.length, 0) - }) + assert.equal(typeof base10, 'function'); + assert.equal(base10.length, 1); + const gen = base10(10); + assert.equal(typeof gen, 'function'); + assert.equal(gen.length, 0); + }); it('should have correct output', () => { return base10(10)().then(code => { - assert.equal(code.length, 10, 'matches length') - assert.ok(/^[0-9]+$/.test(code), 'only digits') - }) - }) - }) -}) + assert.equal(code.length, 10, 'matches length'); + assert.ok(/^[0-9]+$/.test(code), 'only digits'); + }); + }); + }); +}); diff --git a/test/local/crypto/scrypt.js b/test/local/crypto/scrypt.js index aa0185d1..a0ba9060 100644 --- a/test/local/crypto/scrypt.js +++ b/test/local/crypto/scrypt.js @@ -2,57 +2,57 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var promise = require('../../../lib/promise') -var config = { scrypt: { maxPending: 5 } } +const { assert } = require('chai'); +var promise = require('../../../lib/promise'); +var config = { scrypt: { maxPending: 5 } }; var log = { buffer: [], - warn: function(obj){ log.buffer.push(obj) }, -} + warn: function(obj){ log.buffer.push(obj); }, +}; -var scrypt = require('../../../lib/crypto/scrypt')(log, config) +var scrypt = require('../../../lib/crypto/scrypt')(log, config); describe('scrypt', () => { it( 'scrypt basic', () => { - var K1 = Buffer.from('f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd', 'hex') - var salt = Buffer.from('identity.mozilla.com/picl/v1/scrypt') + var K1 = Buffer.from('f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd', 'hex'); + var salt = Buffer.from('identity.mozilla.com/picl/v1/scrypt'); return scrypt.hash(K1, salt, 65536, 8, 1, 32) .then( function (K2) { - assert.equal(K2, '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5') + assert.equal(K2, '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5'); } - ) + ); } - ) + ); it( 'scrypt enforces maximum number of pending requests', () => { - var K1 = Buffer.from('f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd', 'hex') - var salt = Buffer.from('identity.mozilla.com/picl/v1/scrypt') + var K1 = Buffer.from('f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd', 'hex'); + var salt = Buffer.from('identity.mozilla.com/picl/v1/scrypt'); // Check the we're using the lower maxPending setting from config. - assert.equal(scrypt.maxPending, 5, 'maxPending is correctly set from config') + assert.equal(scrypt.maxPending, 5, 'maxPending is correctly set from config'); // Send many concurrent requests. // Not yielding the event loop ensures they will pile up quickly. - var promises = [] + var promises = []; for (var i = 0; i < 10; i++) { - promises.push(scrypt.hash(K1, salt, 65536, 8, 1, 32)) + promises.push(scrypt.hash(K1, salt, 65536, 8, 1, 32)); } return promise.all(promises).then( function () { - assert(false, 'too many pending scrypt hashes were allowed') + assert(false, 'too many pending scrypt hashes were allowed'); }, function (err) { - assert.equal(err.message, 'too many pending scrypt hashes') - assert.equal(scrypt.numPendingHWM, 6, 'HWM should be maxPending+1') - assert.equal(log.buffer[0], 'scrypt.maxPendingExceeded') + assert.equal(err.message, 'too many pending scrypt hashes'); + assert.equal(scrypt.numPendingHWM, 6, 'HWM should be maxPending+1'); + assert.equal(log.buffer[0], 'scrypt.maxPendingExceeded'); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/customs.js b/test/local/customs.js index 3497e3e0..ff537246 100644 --- a/test/local/customs.js +++ b/test/local/customs.js @@ -2,85 +2,85 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') +const { assert } = require('chai'); const log = { trace: () => {}, activityEvent: () => {}, flowEvent: () => {}, error() {} -} -const mocks = require('../mocks') -const error = require(`${ROOT_DIR}/lib/error.js`) -const P = require(`${ROOT_DIR}/lib/promise.js`) -var nock = require('nock') +}; +const mocks = require('../mocks'); +const error = require(`${ROOT_DIR}/lib/error.js`); +const P = require(`${ROOT_DIR}/lib/promise.js`); +var nock = require('nock'); -const Customs = require(`${ROOT_DIR}/lib/customs.js`)(log, error) +const Customs = require(`${ROOT_DIR}/lib/customs.js`)(log, error); -var CUSTOMS_URL_REAL = 'http://localhost:7000' -var CUSTOMS_URL_MISSING = 'http://localhost:7001' +var CUSTOMS_URL_REAL = 'http://localhost:7000'; +var CUSTOMS_URL_MISSING = 'http://localhost:7001'; -var customsNoUrl -var customsWithUrl -var customsInvalidUrl +var customsNoUrl; +var customsWithUrl; +var customsInvalidUrl; var customsServer = nock(CUSTOMS_URL_REAL) .defaultReplyHeaders({ 'Content-Type': 'application/json' - }) + }); describe('Customs', () => { it( "can create a customs object with url as 'none'", () => { - customsNoUrl = new Customs('none') + customsNoUrl = new Customs('none'); - assert.ok(customsNoUrl, 'got a customs object with a none url') + assert.ok(customsNoUrl, 'got a customs object with a none url'); - var request = newRequest() - var ip = request.app.clientAddress - var email = newEmail() - var action = newAction() + var request = newRequest(); + var ip = request.app.clientAddress; + var email = newEmail(); + var action = newAction(); return customsNoUrl.check(request, email, action) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds') + assert.equal(result, undefined, 'Nothing is returned when /check succeeds'); }) .then(function() { - return customsNoUrl.flag(ip, { email: email, uid: '12345' }) + return customsNoUrl.flag(ip, { email: email, uid: '12345' }); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds') + assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds'); }) .then(function() { - return customsNoUrl.reset(email) + return customsNoUrl.reset(email); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /passwordReset succeeds') + assert.equal(result, undefined, 'Nothing is returned when /passwordReset succeeds'); }) .then(() => { - return customsNoUrl.checkIpOnly(request, action) + return customsNoUrl.checkIpOnly(request, action); }) .then(result => { - assert.equal(result, undefined, 'Nothing is returned when /checkIpOnly succeeds') - }) + assert.equal(result, undefined, 'Nothing is returned when /checkIpOnly succeeds'); + }); } - ) + ); it( 'can create a customs object with a url', () => { - customsWithUrl = new Customs(CUSTOMS_URL_REAL) + customsWithUrl = new Customs(CUSTOMS_URL_REAL); - assert.ok(customsWithUrl, 'got a customs object with a valid url') + assert.ok(customsWithUrl, 'got a customs object with a valid url'); - var request = newRequest() - var ip = request.app.clientAddress - var email = newEmail() - var action = newAction() + var request = newRequest(); + var ip = request.app.clientAddress; + var email = newEmail(); + var action = newAction(); // Mock a check that does not get blocked. customsServer.post('/check', function (body) { @@ -91,15 +91,15 @@ describe('Customs', () => { headers: request.headers, query: request.query, payload: request.payload, - }, 'first call to /check had expected request params') - return true + }, 'first call to /check had expected request params'); + return true; }).reply(200, { block: false, retryAfter: 0 - }) + }); return customsWithUrl.check(request, email, action) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds') + assert.equal(result, undefined, 'Nothing is returned when /check succeeds'); }) .then(function() { // Mock a report of a failed login attempt @@ -108,26 +108,26 @@ describe('Customs', () => { ip: ip, email: email, errno: error.ERRNO.UNEXPECTED_ERROR - }, 'first call to /failedLoginAttempt had expected request params') - return true - }).reply(200, {}) - return customsWithUrl.flag(ip, { email: email, uid: '12345' }) + }, 'first call to /failedLoginAttempt had expected request params'); + return true; + }).reply(200, {}); + return customsWithUrl.flag(ip, { email: email, uid: '12345' }); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds') + assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds'); }) .then(function() { // Mock a report of a password reset. customsServer.post('/passwordReset', function (body) { assert.deepEqual(body, { email: email, - }, 'first call to /passwordReset had expected request params') - return true - }).reply(200, {}) - return customsWithUrl.reset(email) + }, 'first call to /passwordReset had expected request params'); + return true; + }).reply(200, {}); + return customsWithUrl.reset(email); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /passwordReset succeeds') + assert.equal(result, undefined, 'Nothing is returned when /passwordReset succeeds'); }) .then(function() { // Mock a check that does get blocked, with a retryAfter. @@ -139,23 +139,23 @@ describe('Customs', () => { headers: request.headers, query: request.query, payload: request.payload, - }, 'second call to /check had expected request params') - return true + }, 'second call to /check had expected request params'); + return true; }).reply(200, { block: true, retryAfter: 10001 - }) - return customsWithUrl.check(request, email, action) + }); + return customsWithUrl.check(request, email, action); }) .then(function(result) { - assert(false, 'This should have failed the check since it should be blocked') + assert(false, 'This should have failed the check since it should be blocked'); }, function(err) { - assert.equal(err.errno, error.ERRNO.THROTTLED, 'Error number is correct') - assert.equal(err.message, 'Client has sent too many requests', 'Error message is correct') - assert.ok(err.isBoom, 'The error causes a boom') - assert.equal(err.output.statusCode, 429, 'Status Code is correct') - assert.equal(err.output.payload.retryAfter, 10001, 'retryAfter is correct') - assert.equal(err.output.headers['retry-after'], 10001, 'retryAfter header is correct') + assert.equal(err.errno, error.ERRNO.THROTTLED, 'Error number is correct'); + assert.equal(err.message, 'Client has sent too many requests', 'Error message is correct'); + assert.ok(err.isBoom, 'The error causes a boom'); + assert.equal(err.output.statusCode, 429, 'Status Code is correct'); + assert.equal(err.output.payload.retryAfter, 10001, 'retryAfter is correct'); + assert.equal(err.output.headers['retry-after'], 10001, 'retryAfter header is correct'); }) .then(function() { // Mock a report of a failed login attempt that does trigger lockout. @@ -164,21 +164,21 @@ describe('Customs', () => { ip: ip, email: email, errno: error.ERRNO.INCORRECT_PASSWORD - }, 'second call to /failedLoginAttempt had expected request params') - return true - }).reply(200, { }) + }, 'second call to /failedLoginAttempt had expected request params'); + return true; + }).reply(200, { }); return customsWithUrl.flag(ip, { email: email, errno: error.ERRNO.INCORRECT_PASSWORD - }) + }); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds') + assert.equal(result, undefined, 'Nothing is returned when /failedLoginAttempt succeeds'); }) .then(function() { // Mock a check that does get blocked, with no retryAfter. - request.headers['user-agent'] = 'test passing through headers' - request.payload['foo'] = 'bar' + request.headers['user-agent'] = 'test passing through headers'; + request.payload['foo'] = 'bar'; customsServer.post('/check', function (body) { assert.deepEqual(body, { ip: ip, @@ -187,84 +187,84 @@ describe('Customs', () => { headers: request.headers, query: request.query, payload: request.payload, - }, 'third call to /check had expected request params') - return true + }, 'third call to /check had expected request params'); + return true; }).reply(200, { block: true - }) - return customsWithUrl.check(request, email, action) + }); + return customsWithUrl.check(request, email, action); }) .then(function(result) { - assert(false, 'This should have failed the check since it should be blocked') + assert(false, 'This should have failed the check since it should be blocked'); }, function(err) { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'Error number is correct') - assert.equal(err.message, 'The request was blocked for security reasons', 'Error message is correct') - assert.ok(err.isBoom, 'The error causes a boom') - assert.equal(err.output.statusCode, 400, 'Status Code is correct') - assert(! err.output.payload.retryAfter, 'retryAfter field is not present') - assert(! err.output.headers['retry-after'], 'retryAfter header is not present') + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'Error number is correct'); + assert.equal(err.message, 'The request was blocked for security reasons', 'Error message is correct'); + assert.ok(err.isBoom, 'The error causes a boom'); + assert.equal(err.output.statusCode, 400, 'Status Code is correct'); + assert(! err.output.payload.retryAfter, 'retryAfter field is not present'); + assert(! err.output.headers['retry-after'], 'retryAfter header is not present'); }) .then(() => { customsServer.post('/checkIpOnly', function (body) { assert.deepEqual(body, { ip: ip, action: action - }, 'first call to /check had expected request params') - return true + }, 'first call to /check had expected request params'); + return true; }).reply(200, { block: false, retryAfter: 0 - }) - return customsWithUrl.checkIpOnly(request, action) + }); + return customsWithUrl.checkIpOnly(request, action); }) .then(result => { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds') - }) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds'); + }); } - ) + ); it( 'failed closed when creating a customs object with non-existant customs service', () => { - customsInvalidUrl = new Customs(CUSTOMS_URL_MISSING) + customsInvalidUrl = new Customs(CUSTOMS_URL_MISSING); - assert.ok(customsInvalidUrl, 'got a customs object with a non-existant service url') + assert.ok(customsInvalidUrl, 'got a customs object with a non-existant service url'); - var request = newRequest() - var ip = request.app.clientAddress - var email = newEmail() - var action = newAction() + var request = newRequest(); + var ip = request.app.clientAddress; + var email = newEmail(); + var action = newAction(); return P.all([ customsInvalidUrl.check(request, email, action) .then(assert.fail, err => { - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /check') + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /check'); }), customsInvalidUrl.flag(ip, { email: email, uid: '12345' }) .then(assert.fail, err => { - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /flag') + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /flag'); }), customsInvalidUrl.reset(email) .then(assert.fail, err => { - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /passwordReset') + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE, 'an error is returned from /passwordReset'); }) - ]) + ]); } - ) + ); it( 'can rate limit checkAccountStatus /check', () => { - customsWithUrl = new Customs(CUSTOMS_URL_REAL) + customsWithUrl = new Customs(CUSTOMS_URL_REAL); - assert.ok(customsWithUrl, 'can rate limit checkAccountStatus /check') + assert.ok(customsWithUrl, 'can rate limit checkAccountStatus /check'); - var request = newRequest() - var ip = request.app.clientAddress - var email = newEmail() - var action = 'accountStatusCheck' + var request = newRequest(); + var ip = request.app.clientAddress; + var email = newEmail(); + var action = 'accountStatusCheck'; function checkRequestBody (body) { assert.deepEqual(body, { @@ -274,8 +274,8 @@ describe('Customs', () => { headers: request.headers, query: request.query, payload: request.payload, - }, 'call to /check had expected request params') - return true + }, 'call to /check had expected request params'); + return true; } customsServer @@ -284,62 +284,62 @@ describe('Customs', () => { .post('/check', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') .post('/check', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') .post('/check', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') - .post('/check', checkRequestBody).reply(200, '{"block":true,"retryAfter":10001}') + .post('/check', checkRequestBody).reply(200, '{"block":true,"retryAfter":10001}'); return customsWithUrl.check(request, email, action) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 1') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 1'); + return customsWithUrl.check(request, email, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 2') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 2'); + return customsWithUrl.check(request, email, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 3') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 3'); + return customsWithUrl.check(request, email, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 4') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 4'); + return customsWithUrl.check(request, email, action); }) .then(function() { // request is blocked - return customsWithUrl.check(request, email, action) + return customsWithUrl.check(request, email, action); }) .then(function() { - assert(false, 'This should have failed the check since it should be blocked') + assert(false, 'This should have failed the check since it should be blocked'); }, function(error) { - assert.equal(error.errno, 114, 'Error number is correct') - assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct') - assert.ok(error.isBoom, 'The error causes a boom') - assert.equal(error.output.statusCode, 429, 'Status Code is correct') - assert.equal(error.output.payload.retryAfter, 10001, 'retryAfter is correct') - assert.equal(error.output.payload.retryAfterLocalized, 'in 3 hours', 'retryAfterLocalized is correct') - assert.equal(error.output.headers['retry-after'], 10001, 'retryAfter header is correct') - }) + assert.equal(error.errno, 114, 'Error number is correct'); + assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct'); + assert.ok(error.isBoom, 'The error causes a boom'); + assert.equal(error.output.statusCode, 429, 'Status Code is correct'); + assert.equal(error.output.payload.retryAfter, 10001, 'retryAfter is correct'); + assert.equal(error.output.payload.retryAfterLocalized, 'in 3 hours', 'retryAfterLocalized is correct'); + assert.equal(error.output.headers['retry-after'], 10001, 'retryAfter header is correct'); + }); } - ) + ); it( 'can rate limit devicesNotify /checkAuthenticated', () => { - customsWithUrl = new Customs(CUSTOMS_URL_REAL) + customsWithUrl = new Customs(CUSTOMS_URL_REAL); - assert.ok(customsWithUrl, 'can rate limit /checkAuthenticated') + assert.ok(customsWithUrl, 'can rate limit /checkAuthenticated'); - var request = newRequest() - var action = 'devicesNotify' - var ip = request.app.clientAddress - var uid = 'foo' + var request = newRequest(); + var action = 'devicesNotify'; + var ip = request.app.clientAddress; + var uid = 'foo'; function checkRequestBody (body) { assert.deepEqual(body, { action: action, ip: ip, uid: uid, - }, 'call to /checkAuthenticated had expected request params') - return true + }, 'call to /checkAuthenticated had expected request params'); + return true; } customsServer @@ -348,50 +348,50 @@ describe('Customs', () => { .post('/checkAuthenticated', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') .post('/checkAuthenticated', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') .post('/checkAuthenticated', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') - .post('/checkAuthenticated', checkRequestBody).reply(200, '{"block":true,"retryAfter":10001}') + .post('/checkAuthenticated', checkRequestBody).reply(200, '{"block":true,"retryAfter":10001}'); return customsWithUrl.checkAuthenticated(request, uid, action) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 1') - return customsWithUrl.checkAuthenticated(request, uid, action) + assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 1'); + return customsWithUrl.checkAuthenticated(request, uid, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 2') - return customsWithUrl.checkAuthenticated(request, uid, action) + assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 2'); + return customsWithUrl.checkAuthenticated(request, uid, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 3') - return customsWithUrl.checkAuthenticated(request, uid, action) + assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 3'); + return customsWithUrl.checkAuthenticated(request, uid, action); }) .then(function(result) { - assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 4') - return customsWithUrl.checkAuthenticated(request, uid, action) + assert.equal(result, undefined, 'Nothing is returned when /checkAuthenticated succeeds - 4'); + return customsWithUrl.checkAuthenticated(request, uid, action); }) .then(function() { // request is blocked - return customsWithUrl.checkAuthenticated(request, uid, action) + return customsWithUrl.checkAuthenticated(request, uid, action); }) .then(function() { - assert(false, 'This should have failed the check since it should be blocked') + assert(false, 'This should have failed the check since it should be blocked'); }, function(error) { - assert.equal(error.errno, 114, 'Error number is correct') - assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct') - assert.ok(error.isBoom, 'The error causes a boom') - assert.equal(error.output.statusCode, 429, 'Status Code is correct') - assert.equal(error.output.payload.retryAfter, 10001, 'retryAfter is correct') - assert.equal(error.output.headers['retry-after'], 10001, 'retryAfter header is correct') - }) + assert.equal(error.errno, 114, 'Error number is correct'); + assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct'); + assert.ok(error.isBoom, 'The error causes a boom'); + assert.equal(error.output.statusCode, 429, 'Status Code is correct'); + assert.equal(error.output.payload.retryAfter, 10001, 'retryAfter is correct'); + assert.equal(error.output.headers['retry-after'], 10001, 'retryAfter header is correct'); + }); } - ) + ); it('can rate limit verifyTotpCode /check', () => { - const request = newRequest() - const action = 'verifyTotpCode' - const email = 'test@email.com' - const ip = request.app.clientAddress + const request = newRequest(); + const action = 'verifyTotpCode'; + const email = 'test@email.com'; + const ip = request.app.clientAddress; - customsWithUrl = new Customs(CUSTOMS_URL_REAL) - assert.ok(customsWithUrl, 'can rate limit ') + customsWithUrl = new Customs(CUSTOMS_URL_REAL); + assert.ok(customsWithUrl, 'can rate limit '); function checkRequestBody(body) { assert.deepEqual(body, { @@ -401,48 +401,48 @@ describe('Customs', () => { headers: request.headers, query: request.query, payload: request.payload, - }, 'call to /check had expected request params') - return true + }, 'call to /check had expected request params'); + return true; } customsServer .post('/check', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') .post('/check', checkRequestBody).reply(200, '{"block":false,"retryAfter":0}') - .post('/check', checkRequestBody).reply(200, '{"block":true,"retryAfter":30}') + .post('/check', checkRequestBody).reply(200, '{"block":true,"retryAfter":30}'); return customsWithUrl.check(request, email, action) .then((result) => { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 1') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 1'); + return customsWithUrl.check(request, email, action); }) .then((result) => { - assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 2') - return customsWithUrl.check(request, email, action) + assert.equal(result, undefined, 'Nothing is returned when /check succeeds - 2'); + return customsWithUrl.check(request, email, action); }) .then(assert.fail, (error) => { - assert.equal(error.errno, 114, 'Error number is correct') - assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct') - assert.ok(error.isBoom, 'The error causes a boom') - assert.equal(error.output.statusCode, 429, 'Status Code is correct') - assert.equal(error.output.payload.retryAfter, 30, 'retryAfter is correct') - assert.equal(error.output.headers['retry-after'], 30, 'retryAfter header is correct') - }) - }) + assert.equal(error.errno, 114, 'Error number is correct'); + assert.equal(error.message, 'Client has sent too many requests', 'Error message is correct'); + assert.ok(error.isBoom, 'The error causes a boom'); + assert.equal(error.output.statusCode, 429, 'Status Code is correct'); + assert.equal(error.output.payload.retryAfter, 30, 'retryAfter is correct'); + assert.equal(error.output.headers['retry-after'], 30, 'retryAfter header is correct'); + }); + }); it( 'can scrub customs request object', () => { - customsWithUrl = new Customs(CUSTOMS_URL_REAL) + customsWithUrl = new Customs(CUSTOMS_URL_REAL); - assert.ok(customsWithUrl, 'got a customs object with a valid url') + assert.ok(customsWithUrl, 'got a customs object with a valid url'); - var request = newRequest() - request.payload.authPW = 'asdfasdfadsf' - request.payload.oldAuthPW = '012301230123' - request.payload.notThePW = 'plaintext' - var ip = request.app.clientAddress - var email = newEmail() - var action = newAction() + var request = newRequest(); + request.payload.authPW = 'asdfasdfadsf'; + request.payload.oldAuthPW = '012301230123'; + request.payload.notThePW = 'plaintext'; + var ip = request.app.clientAddress; + var email = newEmail(); + var action = newAction(); customsServer.post('/check', function (body) { assert.deepEqual(body, { @@ -454,24 +454,24 @@ describe('Customs', () => { payload: { notThePW: 'plaintext' } - }, 'should not have password fields in payload') - return true + }, 'should not have password fields in payload'); + return true; }).reply(200, { block: false, retryAfter: 0 - }) + }); return customsWithUrl.check(request, email, action) .then(function (result) { - assert.equal(result, undefined, 'nothing is returned when /check succeeds - 1') - }) + assert.equal(result, undefined, 'nothing is returned when /check succeeds - 1'); + }); } - ) + ); -}) +}); function newEmail() { - return Math.random().toString().substr(2) + '@example.com' + return Math.random().toString().substr(2) + '@example.com'; } function newIp() { @@ -480,7 +480,7 @@ function newIp() { '' + Math.floor(Math.random() * 256), '' + Math.floor(Math.random() * 256), '' + Math.floor(Math.random() * 256), - ].join('.') + ].join('.'); } function newRequest() { @@ -489,7 +489,7 @@ function newRequest() { headers: {}, query: {}, payload: {} - }) + }); } @@ -499,7 +499,7 @@ function newAction() { 'recoveryEmailResendCode', 'passwordForgotSendCode', 'passwordForgotResendCode' - ] + ]; - return EMAIL_ACTIONS[Math.floor(Math.random() * EMAIL_ACTIONS.length)] + return EMAIL_ACTIONS[Math.floor(Math.random() * EMAIL_ACTIONS.length)]; } diff --git a/test/local/db.js b/test/local/db.js index 28180f08..df0acb4b 100644 --- a/test/local/db.js +++ b/test/local/db.js @@ -2,274 +2,274 @@ * 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/. */ -'use strict' +'use strict'; -const LIB_DIR = '../../lib' +const LIB_DIR = '../../lib'; -const { assert } = require('chai') -const mocks = require('../mocks') -const P = require(`${LIB_DIR}/promise`) -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const { assert } = require('chai'); +const mocks = require('../mocks'); +const P = require(`${LIB_DIR}/promise`); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); describe('db, session tokens expire:', () => { const tokenLifetimes = { sessionTokenWithoutDevice: 2419200000 - } + }; - let results, pool, log, tokens, db + let results, pool, log, tokens, db; beforeEach(() => { - results = {} + results = {}; pool = { get: sinon.spy(() => P.resolve(results.pool)), post: sinon.spy(() => P.resolve()), put: sinon.spy(() => P.resolve()) - } - log = mocks.mockLog() - tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }) + }; + log = mocks.mockLog(); + tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }); const DB = proxyquire(`${LIB_DIR}/db`, { - './pool': function () { return pool } - })({ tokenLifetimes, tokenPruning: {}, redis: {} }, log, tokens, {}) + './pool': function () { return pool; } + })({ tokenLifetimes, tokenPruning: {}, redis: {} }, log, tokens, {}); return DB.connect({}) - .then(result => db = result) - }) + .then(result => db = result); + }); describe('sessions:', () => { - let sessions + let sessions; beforeEach(() => { - const now = Date.now() + const now = Date.now(); results.pool = [ { createdAt: now, tokenId: 'foo' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice - 1, tokenId: 'bar' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice + 1000, tokenId: 'baz' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice - 1, tokenId: 'qux', deviceId: 'wibble' } - ] + ]; return db.sessions() - .then(result => sessions = result) - }) + .then(result => sessions = result); + }); it('returned the correct result', () => { - assert(Array.isArray(sessions)) - assert.equal(sessions.length, 3) - assert.equal(sessions[0].id, 'foo') - assert.equal(sessions[1].id, 'baz') - assert.equal(sessions[2].id, 'qux') - }) - }) -}) + assert(Array.isArray(sessions)); + assert.equal(sessions.length, 3); + assert.equal(sessions[0].id, 'foo'); + assert.equal(sessions[1].id, 'baz'); + assert.equal(sessions[2].id, 'qux'); + }); + }); +}); describe('db, session tokens do not expire:', () => { const tokenLifetimes = { sessionTokenWithoutDevice: 0 - } + }; - let results, pool, log, tokens, db + let results, pool, log, tokens, db; beforeEach(() => { - results = {} + results = {}; pool = { get: sinon.spy(() => P.resolve(results.pool)), post: sinon.spy(() => P.resolve()), put: sinon.spy(() => P.resolve()) - } - log = mocks.mockLog() - tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }) + }; + log = mocks.mockLog(); + tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }); const DB = proxyquire(`${LIB_DIR}/db`, { - './pool': function () { return pool } - })({ tokenLifetimes, tokenPruning: {}, redis: {} }, log, tokens, {}) + './pool': function () { return pool; } + })({ tokenLifetimes, tokenPruning: {}, redis: {} }, log, tokens, {}); return DB.connect({}) - .then(result => db = result) - }) + .then(result => db = result); + }); describe('sessions:', () => { - let sessions + let sessions; beforeEach(() => { - const now = Date.now() + const now = Date.now(); results.pool = [ { createdAt: now, tokenId: 'foo' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice - 1, tokenId: 'bar' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice + 1000, tokenId: 'baz' }, { createdAt: now - tokenLifetimes.sessionTokenWithoutDevice - 1, tokenId: 'qux', deviceId: 'wibble' } - ] + ]; return db.sessions() - .then(result => sessions = result) - }) + .then(result => sessions = result); + }); it('returned the correct result', () => { - assert.equal(sessions.length, 4) - assert.equal(sessions[0].id, 'foo') - assert.equal(sessions[1].id, 'bar') - assert.equal(sessions[2].id, 'baz') - assert.equal(sessions[3].id, 'qux') - }) - }) -}) + assert.equal(sessions.length, 4); + assert.equal(sessions[0].id, 'foo'); + assert.equal(sessions[1].id, 'bar'); + assert.equal(sessions[2].id, 'baz'); + assert.equal(sessions[3].id, 'qux'); + }); + }); +}); describe('db with redis disabled:', () => { const tokenLifetimes = { sessionTokenWithoutDevice: 2419200000 - } + }; - let results, pool, log, tokens, db + let results, pool, log, tokens, db; beforeEach(() => { - results = {} + results = {}; pool = { get: sinon.spy(() => P.resolve(results.pool)), post: sinon.spy(() => P.resolve()), del: sinon.spy(() => P.resolve()), put: sinon.spy(() => P.resolve()) - } - log = mocks.mockLog() - tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }) + }; + log = mocks.mockLog(); + tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }); const DB = proxyquire(`${LIB_DIR}/db`, { - './pool': function () { return pool }, + './pool': function () { return pool; }, './redis': () => {} - })({ redis: {}, tokenLifetimes, tokenPruning: {} }, log, tokens, {}) + })({ redis: {}, tokenLifetimes, tokenPruning: {} }, log, tokens, {}); return DB.connect({}) - .then(result => db = result) - }) + .then(result => db = result); + }); it('db.sessions succeeds without a redis instance', () => { - results.pool = [] + results.pool = []; return db.sessions('fakeUid') .then(result => { - assert.equal(pool.get.callCount, 1) - const args = pool.get.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'fakeUid' }) - assert.deepEqual(result, []) - }) - }) + assert.equal(pool.get.callCount, 1); + const args = pool.get.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'fakeUid' }); + assert.deepEqual(result, []); + }); + }); it('db.devices succeeds without a redis instance', () => { - results.pool = [] + results.pool = []; return db.devices('fakeUid') .then(result => { - assert.equal(pool.get.callCount, 1) - const args = pool.get.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'fakeUid' }) - assert.deepEqual(result, []) - }) - }) + assert.equal(pool.get.callCount, 1); + const args = pool.get.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'fakeUid' }); + assert.deepEqual(result, []); + }); + }); it('db.device succeeds without a redis instance', () => { - results.pool = { id: 'fakeDeviceId' } + results.pool = { id: 'fakeDeviceId' }; return db.device('fakeUid', 'fakeDeviceId') .then(result => { - assert.equal(pool.get.callCount, 1) - const args = pool.get.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'fakeUid', deviceId: 'fakeDeviceId' }) - assert.equal(result.id, 'fakeDeviceId') - }) - }) + assert.equal(pool.get.callCount, 1); + const args = pool.get.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'fakeUid', deviceId: 'fakeDeviceId' }); + assert.equal(result.id, 'fakeDeviceId'); + }); + }); it('db.deleteAccount succeeds without a redis instance', () => { return db.deleteAccount({ uid: 'fakeUid' }) .then(() => { - assert.equal(pool.del.callCount, 1) - const args = pool.del.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'fakeUid' }) - }) - }) + assert.equal(pool.del.callCount, 1); + const args = pool.del.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'fakeUid' }); + }); + }); it('db.deleteSessionToken succeeds without a redis instance', () => { return db.deleteSessionToken({ id: 'foo', uid: 'bar'}) .then(() => { - assert.equal(pool.del.callCount, 1) - const args = pool.del.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { id: 'foo' }) - }) - }) + assert.equal(pool.del.callCount, 1); + const args = pool.del.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { id: 'foo' }); + }); + }); it('db.deleteDevice succeeds without a redis instance', () => { - pool.del = sinon.spy(() => P.resolve({})) + pool.del = sinon.spy(() => P.resolve({})); return db.deleteDevice('foo', 'bar') .then(() => { - assert.equal(pool.del.callCount, 1) - const args = pool.del.args[0] - assert.equal(args.length, 2) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'foo', deviceId: 'bar' }) - }) - }) + assert.equal(pool.del.callCount, 1); + const args = pool.del.args[0]; + assert.equal(args.length, 2); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'foo', deviceId: 'bar' }); + }); + }); it('db.resetAccount succeeds without a redis instance', () => { - const start = Date.now() + const start = Date.now(); return db.resetAccount({ uid: 'fakeUid' }, {}) .then(() => { - const end = Date.now() - assert.equal(pool.post.callCount, 1) - const args = pool.post.args[0] - assert.equal(args.length, 3) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.deepEqual(args[1], { uid: 'fakeUid' }) - assert.equal(Object.keys(args[2]).length, 1) - assert.ok(args[2].verifierSetAt >= start) - assert.ok(args[2].verifierSetAt <= end) - }) - }) + const end = Date.now(); + assert.equal(pool.post.callCount, 1); + const args = pool.post.args[0]; + assert.equal(args.length, 3); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.deepEqual(args[1], { uid: 'fakeUid' }); + assert.equal(Object.keys(args[2]).length, 1); + assert.ok(args[2].verifierSetAt >= start); + assert.ok(args[2].verifierSetAt <= end); + }); + }); it('db.touchSessionToken succeeds without a redis instance', () => { return db.touchSessionToken({ id: 'foo', uid: 'bar' }) .then(() => { - assert.equal(pool.get.callCount, 0) - assert.equal(pool.post.callCount, 0) - }) - }) + assert.equal(pool.get.callCount, 0); + assert.equal(pool.post.callCount, 0); + }); + }); it('db.pruneSessionTokens succeeds without a redis instance', () => { return db.pruneSessionTokens('foo', [ { id: 'bar', createdAt: 1 } ]) .then(() => { - assert.equal(pool.get.callCount, 0) - assert.equal(pool.post.callCount, 0) - }) - }) + assert.equal(pool.get.callCount, 0); + assert.equal(pool.post.callCount, 0); + }); + }); it('db.createSessionToken succeeds without a redis instance', () => { return db.createSessionToken({ uid: 'foo' }) .then(() => { - assert.equal(pool.put.callCount, 1) - const args = pool.put.args[0] - assert.equal(args.length, 3) - assert.equal(typeof args[0].render, 'function') - assert.equal(args[0].constructor.name, 'SafeUrl') - assert.ok(args[1].id) - assert.equal(args[2].tokenId, args[1].id) - assert.equal(args[2].uid, 'foo') - }) - }) -}) + assert.equal(pool.put.callCount, 1); + const args = pool.put.args[0]; + assert.equal(args.length, 3); + assert.equal(typeof args[0].render, 'function'); + assert.equal(args[0].constructor.name, 'SafeUrl'); + assert.ok(args[1].id); + assert.equal(args[2].tokenId, args[1].id); + assert.equal(args[2].uid, 'foo'); + }); + }); +}); describe('redis enabled, token-pruning enabled:', () => { const tokenLifetimes = { sessionTokenWithoutDevice: 2419200000 - } + }; const tokenPruning = { enabled: true, maxAge: 1000 * 60 * 60 * 24 * 72 - } + }; - let pool, redis, log, tokens, db + let pool, redis, log, tokens, db; beforeEach(() => { pool = { @@ -277,25 +277,25 @@ describe('redis enabled, token-pruning enabled:', () => { post: sinon.spy(() => P.resolve()), del: sinon.spy(() => P.resolve()), put: sinon.spy(() => P.resolve()) - } + }; redis = { get: sinon.spy(() => P.resolve('{}')), set: sinon.spy(() => P.resolve()), del: sinon.spy(() => P.resolve()), update: sinon.spy(() => P.resolve()) - } - log = mocks.mockLog() - tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }) + }; + log = mocks.mockLog(); + tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }); const DB = proxyquire(`${LIB_DIR}/db`, { - './pool': function () { return pool }, + './pool': function () { return pool; }, './redis': (...args) => { - assert.equal(args.length, 2, 'redisPool was passed two arguments') - assert.equal(args[0].foo, 'bar', 'redisPool was passed config') - assert.equal(args[0].baz, 'qux', 'redisPool was passed session token config') - assert.equal(args[0].prefix, 'wibble', 'redisPool was passed session token prefix') - assert.equal(args[0].blee, undefined, 'redisPool was not passed email service config') - assert.equal(args[1], log, 'redisPool was passed log') - return redis + assert.equal(args.length, 2, 'redisPool was passed two arguments'); + assert.equal(args[0].foo, 'bar', 'redisPool was passed config'); + assert.equal(args[0].baz, 'qux', 'redisPool was passed session token config'); + assert.equal(args[0].prefix, 'wibble', 'redisPool was passed session token prefix'); + assert.equal(args[0].blee, undefined, 'redisPool was not passed email service config'); + assert.equal(args[1], log, 'redisPool was passed log'); + return redis; } })({ tokenLifetimes, @@ -316,136 +316,136 @@ describe('redis enabled, token-pruning enabled:', () => { sampleRate: 1, earliestSaneTimestamp: 1 } - }, log, tokens, {}) + }, log, tokens, {}); return DB.connect({}) - .then(result => db = result) - }) + .then(result => db = result); + }); it('should not call redis or the db in db.devices if uid is falsey', () => { return db.devices('') .then( result => assert.equal(result, 'db.devices should reject with error.unknownAccount'), err => { - assert.equal(pool.get.callCount, 0) - assert.equal(redis.get.callCount, 0) - assert.equal(err.errno, 102) - assert.equal(err.message, 'Unknown account') + assert.equal(pool.get.callCount, 0); + assert.equal(redis.get.callCount, 0); + assert.equal(err.errno, 102); + assert.equal(err.message, 'Unknown account'); } - ) - }) + ); + }); it('should call redis and the db in db.devices if uid is not falsey', () => { return db.devices('wibble') .then(() => { - assert.equal(pool.get.callCount, 1) - assert.equal(redis.get.callCount, 1) - assert.equal(redis.get.args[0].length, 1) - assert.equal(redis.get.args[0][0], 'wibble') - }) - }) + assert.equal(pool.get.callCount, 1); + assert.equal(redis.get.callCount, 1); + assert.equal(redis.get.args[0].length, 1); + assert.equal(redis.get.args[0][0], 'wibble'); + }); + }); it('should call redis and the db in db.device if uid is not falsey', () => { return db.device('wibble', 'wobble') .then(() => { - assert.equal(pool.get.callCount, 1) - assert.equal(redis.get.callCount, 1) - assert.equal(redis.get.args[0].length, 1) - assert.equal(redis.get.args[0][0], 'wibble') - }) - }) + assert.equal(pool.get.callCount, 1); + assert.equal(redis.get.callCount, 1); + assert.equal(redis.get.args[0].length, 1); + assert.equal(redis.get.args[0][0], 'wibble'); + }); + }); it('should call redis.get in db.sessions', () => { return db.sessions('wibble') .then(() => { - assert.equal(redis.get.callCount, 1) - assert.equal(redis.get.args[0].length, 1) - assert.equal(redis.get.args[0][0], 'wibble') + assert.equal(redis.get.callCount, 1); + assert.equal(redis.get.args[0].length, 1); + assert.equal(redis.get.args[0][0], 'wibble'); - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); it('should call redis.del in db.deleteAccount', () => { return db.deleteAccount({ uid: 'wibble' }) .then(() => { - assert.equal(redis.del.callCount, 1) - assert.equal(redis.del.args[0].length, 1) - assert.equal(redis.del.args[0][0], 'wibble') - }) - }) + assert.equal(redis.del.callCount, 1); + assert.equal(redis.del.args[0].length, 1); + assert.equal(redis.del.args[0][0], 'wibble'); + }); + }); it('should call redis.del in db.resetAccount', () => { return db.resetAccount({ uid: 'wibble' }, {}) .then(() => { - assert.equal(redis.del.callCount, 1) - assert.equal(redis.del.args[0].length, 1) - assert.equal(redis.del.args[0][0], 'wibble') - }) - }) + assert.equal(redis.del.callCount, 1); + assert.equal(redis.del.args[0].length, 1); + assert.equal(redis.del.args[0][0], 'wibble'); + }); + }); it('should call redis.update in db.touchSessionToken', () => { return db.touchSessionToken({ id: 'wibble', uid: 'blee' }) .then(() => { - assert.equal(redis.update.callCount, 1) - assert.equal(redis.update.args[0].length, 2) - assert.equal(redis.update.args[0][0], 'blee') - assert.equal(typeof redis.update.args[0][1], 'function') - }) - }) + assert.equal(redis.update.callCount, 1); + assert.equal(redis.update.args[0].length, 2); + assert.equal(redis.update.args[0][0], 'blee'); + assert.equal(typeof redis.update.args[0][1], 'function'); + }); + }); it('should call redis.update in db.pruneSessionTokens', () => { - const createdAt = Date.now() - tokenPruning.maxAge - 1 + const createdAt = Date.now() - tokenPruning.maxAge - 1; return db.pruneSessionTokens('foo', [ { id: 'bar', createdAt }, { id: 'baz', createdAt } ]) .then(() => { - assert.equal(redis.update.callCount, 1) - assert.equal(redis.update.args[0].length, 2) - assert.equal(redis.update.args[0][0], 'foo') - assert.equal(typeof redis.update.args[0][1], 'function') - }) - }) + assert.equal(redis.update.callCount, 1); + assert.equal(redis.update.args[0].length, 2); + assert.equal(redis.update.args[0][0], 'foo'); + assert.equal(typeof redis.update.args[0][1], 'function'); + }); + }); it('should not call redis.update for unexpired tokens in db.pruneSessionTokens', () => { - const createdAt = Date.now() - tokenPruning.maxAge + 1000 + const createdAt = Date.now() - tokenPruning.maxAge + 1000; return db.pruneSessionTokens('foo', [ { id: 'bar', createdAt }, { id: 'baz', createdAt } ]) - .then(() => assert.equal(redis.update.callCount, 0)) - }) + .then(() => assert.equal(redis.update.callCount, 0)); + }); it('should call redis.update in db.deleteSessionToken', () => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) .then(() => { - assert.equal(redis.update.callCount, 1) - assert.equal(redis.update.args[0].length, 2) - assert.equal(redis.update.args[0][0], 'blee') - assert.equal(typeof redis.update.args[0][1], 'function') - }) - }) + assert.equal(redis.update.callCount, 1); + assert.equal(redis.update.args[0].length, 2); + assert.equal(redis.update.args[0][0], 'blee'); + assert.equal(typeof redis.update.args[0][1], 'function'); + }); + }); it('should call redis.update in db.deleteDevice', () => { - pool.del = sinon.spy(() => P.resolve({})) + pool.del = sinon.spy(() => P.resolve({})); return db.deleteDevice('wibble', 'blee') .then(() => { - assert.equal(redis.update.callCount, 1) - assert.equal(redis.update.args[0].length, 2) - assert.equal(redis.update.args[0][0], 'wibble') - assert.equal(typeof redis.update.args[0][1], 'function') - }) - }) + assert.equal(redis.update.callCount, 1); + assert.equal(redis.update.args[0].length, 2); + assert.equal(redis.update.args[0][0], 'wibble'); + assert.equal(typeof redis.update.args[0][1], 'function'); + }); + }); it('should call redis.update in db.createSessionToken', () => { return db.createSessionToken({ uid: 'wibble' }) .then(() => { - assert.equal(redis.update.callCount, 1) - assert.equal(redis.update.args[0].length, 2) - assert.equal(redis.update.args[0][0], 'wibble') - assert.equal(typeof redis.update.args[0][1], 'function') - }) - }) + assert.equal(redis.update.callCount, 1); + assert.equal(redis.update.args[0].length, 2); + assert.equal(redis.update.args[0][0], 'wibble'); + assert.equal(typeof redis.update.args[0][1], 'function'); + }); + }); it('db.devices handles old-format and new-format token objects from redis', () => { const oldFormat = { @@ -463,16 +463,16 @@ describe('redis enabled, token-pruning enabled:', () => { country: 'United Kingdom', countryCode: 'GB' } - } + }; const newFormat = [ 1, [ 'Mountain View', 'California', 'CA', 'United States', 'US' ], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' - ] - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ oldFormat, newFormat }))) + ]; + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ oldFormat, newFormat }))); pool.get = sinon.spy(() => P.resolve([ { id: 'device-id', sessionTokenId: 'oldFormat' }, { id: 'device-id', sessionTokenId: 'newFormat' } - ])) + ])); return db.devices('wibble') .then(result => assert.deepEqual(result, [ { @@ -527,8 +527,8 @@ describe('redis enabled, token-pruning enabled:', () => { countryCode: 'US' } } - ])) - }) + ])); + }); it('db.sessions handles old-format and new-format token objects from redis', () => { const oldFormat = { @@ -546,16 +546,16 @@ describe('redis enabled, token-pruning enabled:', () => { country: 'United States', countryCode: 'US' } - } + }; const newFormat = [ 42, [ 'Bournemouth', 'England', 'EN', 'United Kingdom', 'GB' ], 'Firefox', '59', 'Mac OS X', '10.11' - ] - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ oldFormat, newFormat }))) + ]; + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ oldFormat, newFormat }))); pool.get = sinon.spy(() => P.resolve([ { tokenId: 'oldFormat', deviceId: 'device-id' }, { tokenId: 'newFormat', deviceId: 'device-id' } - ])) + ])); return db.sessions('wibble') .then(result => assert.deepEqual(result, [ { @@ -594,8 +594,8 @@ describe('redis enabled, token-pruning enabled:', () => { countryCode: 'GB' } } - ])) - }) + ])); + }); it('db.touchSessionToken handles old-format and new-format token objects from redis', () => { return db.touchSessionToken({ @@ -618,9 +618,9 @@ describe('redis enabled, token-pruning enabled:', () => { } }) .then(() => { - assert.equal(redis.update.callCount, 1) - const getUpdatedValue = redis.update.args[0][1] - assert.equal(typeof getUpdatedValue, 'function') + assert.equal(redis.update.callCount, 1); + const getUpdatedValue = redis.update.args[0][1]; + assert.equal(typeof getUpdatedValue, 'function'); const result = getUpdatedValue(JSON.stringify({ oldFormat: { @@ -640,7 +640,7 @@ describe('redis enabled, token-pruning enabled:', () => { } }, newFormat: [ 2, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - })) + })); assert.deepEqual(JSON.parse(result), { wibble: [ 42, [ 'Bournemouth', 'England', 'EN', 'United Kingdom', 'GB'], @@ -654,20 +654,20 @@ describe('redis enabled, token-pruning enabled:', () => { 2, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - }) - }) - }) + }); + }); + }); it('db.pruneSessionTokens handles old-format and new-format token objects from redis', () => { - const expiryPoint = Date.now() - tokenPruning.maxAge + const expiryPoint = Date.now() - tokenPruning.maxAge; return db.pruneSessionTokens('blee', [ { id: 'unexpired', createdAt: expiryPoint + 1000 }, { id: 'expired', createdAt: expiryPoint } ]) .then(() => { - assert.equal(redis.update.callCount, 1) - const getUpdatedValue = redis.update.args[0][1] - assert.equal(typeof getUpdatedValue, 'function') + assert.equal(redis.update.callCount, 1); + const getUpdatedValue = redis.update.args[0][1]; + assert.equal(typeof getUpdatedValue, 'function'); const result = getUpdatedValue(JSON.stringify({ unexpired: [ 0, [], 'foo', 'bar', 'baz', 'qux' ], @@ -689,7 +689,7 @@ describe('redis enabled, token-pruning enabled:', () => { } }, newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - })) + })); assert.deepEqual(JSON.parse(result), { unexpired: [ 0, [], 'foo', 'bar', 'baz', 'qux' ], oldFormat: [ @@ -697,16 +697,16 @@ describe('redis enabled, token-pruning enabled:', () => { 'Firefox', '59', 'Mac OS X', '10.11' ], newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - }) - }) - }) + }); + }); + }); it('db.deleteSessionToken handles old-format and new-format token objects from redis', () => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) .then(() => { - assert.equal(redis.update.callCount, 1) - const getUpdatedValue = redis.update.args[0][1] - assert.equal(typeof getUpdatedValue, 'function') + assert.equal(redis.update.callCount, 1); + const getUpdatedValue = redis.update.args[0][1]; + assert.equal(typeof getUpdatedValue, 'function'); const result = getUpdatedValue(JSON.stringify({ wibble: [ 1, [], 'foo', 'bar', 'baz', 'qux' ], @@ -727,24 +727,24 @@ describe('redis enabled, token-pruning enabled:', () => { } }, newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - })) + })); assert.deepEqual(JSON.parse(result), { oldFormat: [ 2, [ 'Mountain View', 'California', 'CA', 'United States', 'US' ], 'Firefox', '59', 'Mac OS X', '10.11' ], newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - }) - }) - }) + }); + }); + }); it('db.deleteDevice handles old-format and new-format token objects from redis', () => { - pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })) + pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })); return db.deleteDevice('wibble', 'blee') .then(() => { - assert.equal(redis.update.callCount, 1) - const getUpdatedValue = redis.update.args[0][1] - assert.equal(typeof getUpdatedValue, 'function') + assert.equal(redis.update.callCount, 1); + const getUpdatedValue = redis.update.args[0][1]; + assert.equal(typeof getUpdatedValue, 'function'); const result = getUpdatedValue(JSON.stringify({ wibble: [ 0, [], 'foo', 'bar', 'baz', 'qux' ], @@ -766,7 +766,7 @@ describe('redis enabled, token-pruning enabled:', () => { } }, newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - })) + })); assert.deepEqual(JSON.parse(result), { wibble: [ 0, [], 'foo', 'bar', 'baz', 'qux' ], oldFormat: [ @@ -774,17 +774,17 @@ describe('redis enabled, token-pruning enabled:', () => { 'Firefox', '59', 'Mac OS X', '10.11' ], newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - }) - }) - }) + }); + }); + }); it('db.createSessionToken handles old-format and new-format token objects from redis', () => { - tokens.SessionToken.create = () => P.resolve({ id: 'wibble' }) + tokens.SessionToken.create = () => P.resolve({ id: 'wibble' }); return db.createSessionToken({ uid: 'blee' }) .then(() => { - assert.equal(redis.update.callCount, 1) - const getUpdatedValue = redis.update.args[0][1] - assert.equal(typeof getUpdatedValue, 'function') + assert.equal(redis.update.callCount, 1); + const getUpdatedValue = redis.update.args[0][1]; + assert.equal(typeof getUpdatedValue, 'function'); const result = getUpdatedValue(JSON.stringify({ wibble: [ 0, [], 'foo', 'bar', 'baz', 'qux' ], @@ -805,400 +805,400 @@ describe('redis enabled, token-pruning enabled:', () => { } }, newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - })) + })); assert.deepEqual(JSON.parse(result), { oldFormat: [ 2, [ 'Mountain View', 'California', 'CA', 'United States', 'US' ], 'Firefox', '59', 'Mac OS X', '10.11' ], newFormat: [ 3, [], 'Firefox Focus', '4.0.1', 'Android', '8.1', 'mobile' ] - }) - }) - }) + }); + }); + }); describe('redis.get rejects:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.reject({ message: 'mock redis.get error' })) - }) + redis.get = sinon.spy(() => P.reject({ message: 'mock redis.get error' })); + }); it('should log the error in db.sessions', () => { return db.sessions('wibble') .then(() => { - assert.equal(redis.get.callCount, 1) - assert.equal(redis.del.callCount, 0) + assert.equal(redis.get.callCount, 1); + assert.equal(redis.del.callCount, 0); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'redis.get.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'redis.get.error'); assert.deepEqual(log.error.args[0][1], { key: 'wibble', err: 'mock redis.get error' - }) - }) - }) + }); + }); + }); it('should log the error in db.devices', () => { return db.devices('wibble') .then(() => { - assert.equal(redis.get.callCount, 1) - assert.equal(redis.del.callCount, 0) + assert.equal(redis.get.callCount, 1); + assert.equal(redis.del.callCount, 0); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'redis.get.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'redis.get.error'); assert.deepEqual(log.error.args[0][1], { key: 'wibble', err: 'mock redis.get error' - }) - }) - }) + }); + }); + }); it('should log the error in db.device', () => { return db.device('wibble', 'wobble') .then(() => { - assert.equal(redis.get.callCount, 1) - assert.equal(redis.del.callCount, 0) + assert.equal(redis.get.callCount, 1); + assert.equal(redis.del.callCount, 0); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'redis.get.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'redis.get.error'); assert.deepEqual(log.error.args[0][1], { key: 'wibble', err: 'mock redis.get error' - }) - }) - }) - }) + }); + }); + }); + }); describe('redis.get returns invalid JSON:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve('{"wibble":nonsense}')) - }) + redis.get = sinon.spy(() => P.resolve('{"wibble":nonsense}')); + }); it('should log the error in db.sessions', () => { return db.sessions('wibble') .then(result => { - assert.deepEqual(result, []) + assert.deepEqual(result, []); - assert.equal(redis.get.callCount, 1) + assert.equal(redis.get.callCount, 1); - assert.equal(redis.del.callCount, 1) - assert.equal(redis.del.args[0].length, 1) - assert.equal(redis.del.args[0][0], 'wibble') + assert.equal(redis.del.callCount, 1); + assert.equal(redis.del.args[0].length, 1); + assert.equal(redis.del.args[0][0], 'wibble'); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error'); assert.deepEqual(log.error.args[0][1], { err: 'Unexpected token o in JSON at position 11' - }) - }) - }) + }); + }); + }); it('should log the error in db.devices', () => { return db.devices('wibble') .then(result => { - assert.deepEqual(result, []) + assert.deepEqual(result, []); - assert.equal(redis.get.callCount, 1) - assert.equal(redis.del.callCount, 1) + assert.equal(redis.get.callCount, 1); + assert.equal(redis.del.callCount, 1); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error'); assert.deepEqual(log.error.args[0][1], { err: 'Unexpected token o in JSON at position 11' - }) - }) - }) + }); + }); + }); it('should log the error in db.device', () => { return db.device('wibble', 'wobble') .then(() => { - assert.equal(redis.get.callCount, 1) - assert.equal(redis.del.callCount, 1) + assert.equal(redis.get.callCount, 1); + assert.equal(redis.del.callCount, 1); - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'db.unpackTokensFromRedis.error'); assert.deepEqual(log.error.args[0][1], { err: 'Unexpected token o in JSON at position 11' - }) - }) - }) - }) + }); + }); + }); + }); describe('redis.del rejects:', () => { beforeEach(() => { - redis.del = sinon.spy(() => P.reject({ message: 'mock redis.del error' })) - }) + redis.del = sinon.spy(() => P.reject({ message: 'mock redis.del error' })); + }); it('db.deleteAccount should reject', () => { return db.deleteAccount({ uid: 'wibble' }) .then( () => assert.equal(false, 'db.deleteAccount should have rejected'), error => assert.equal(error.message, 'mock redis.del error') - ) - }) + ); + }); it('db.resetAccount should reject', () => { return db.resetAccount({ uid: 'wibble' }, {}) .then( () => assert.equal(false, 'db.resetAccount should have rejected'), error => assert.equal(error.message, 'mock redis.del error') - ) - }) - }) + ); + }); + }); describe('redis.update rejects:', () => { beforeEach(() => { - redis.update = sinon.spy(() => P.reject({ message: 'mock redis.update error' })) - }) + redis.update = sinon.spy(() => P.reject({ message: 'mock redis.update error' })); + }); it('db.touchSessionToken should reject', () => { return db.touchSessionToken({ id: 'wibble', uid: 'blee' }, {}) .then( () => assert.equal(false, 'db.touchSessionToken should have rejected'), error => assert.equal(error.message, 'mock redis.update error') - ) - }) + ); + }); it('db.pruneSessionTokens should reject', () => { return db.pruneSessionTokens('wibble', [ { id: 'blee', createdAt: 1 } ]) .then( () => assert.equal(false, 'db.pruneSessionTokens should have rejected'), error => assert.equal(error.message, 'mock redis.update error') - ) - }) + ); + }); it('db.deleteSessionToken should reject', () => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) .then( () => assert.equal(false, 'db.deleteSessionToken should have rejected'), error => assert.equal(error.message, 'mock redis.update error') - ) - }) + ); + }); it('db.deleteDevice should reject', () => { - pool.del = sinon.spy(() => P.resolve({})) + pool.del = sinon.spy(() => P.resolve({})); return db.deleteDevice('wibble', 'blee') .then( () => assert.equal(false, 'db.deleteDevice should have rejected'), error => assert.equal(error.message, 'mock redis.update error') - ) - }) + ); + }); it('db.createSessionToken should not reject', () => { - return db.createSessionToken({ uid: 'wibble' }) - }) - }) + return db.createSessionToken({ uid: 'wibble' }); + }); + }); describe('deleteSessionToken reads falsey value from redis:', () => { - let result + let result; beforeEach(() => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) - .then(() => result = redis.update.args[0][1]()) - }) + .then(() => result = redis.update.args[0][1]()); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('deleteSessionToken reads empty object from redis:', () => { - let result + let result; beforeEach(() => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) - .then(() => result = redis.update.args[0][1]('{"wibble":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"wibble":{}}')); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('deleteSessionToken reads populated object from redis:', () => { - let result + let result; beforeEach(() => { return db.deleteSessionToken({ id: 'wibble', uid: 'blee' }) - .then(() => result = redis.update.args[0][1]('{"frang":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"frang":{}}')); + }); it('returned object', () => { - assert.equal(result, '{"frang":[]}') - }) - }) + assert.equal(result, '{"frang":[]}'); + }); + }); describe('deleteDevice reads falsey value from redis:', () => { - let result + let result; beforeEach(() => { - pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })) + pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })); return db.deleteDevice('wibble', 'blee') - .then(() => result = redis.update.args[0][1]()) - }) + .then(() => result = redis.update.args[0][1]()); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('deleteDevice reads empty object from redis:', () => { - let result + let result; beforeEach(() => { - pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })) + pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })); return db.deleteDevice('wibble', 'blee') - .then(() => result = redis.update.args[0][1]('{"mngh":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"mngh":{}}')); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('deleteDevice reads populated object from redis:', () => { - let result + let result; beforeEach(() => { - pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })) + pool.del = sinon.spy(() => P.resolve({ sessionTokenId: 'mngh' })); return db.deleteDevice('wibble', 'blee') - .then(() => result = redis.update.args[0][1]('{"frang":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"frang":{}}')); + }); it('returned object', () => { - assert.equal(result, '{"frang":[]}') - }) - }) + assert.equal(result, '{"frang":[]}'); + }); + }); describe('createSessionToken reads falsey value from redis:', () => { - let result + let result; beforeEach(() => { return db.createSessionToken({ uid: 'wibble' }) - .then(() => result = redis.update.args[0][1]()) - }) + .then(() => result = redis.update.args[0][1]()); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('createSessionToken reads empty object from redis:', () => { - let result + let result; beforeEach(() => { - tokens.SessionToken.create = () => P.resolve({ id: 'wibble' }) + tokens.SessionToken.create = () => P.resolve({ id: 'wibble' }); return db.createSessionToken({ uid: 'blee' }) - .then(() => result = redis.update.args[0][1]('{"wibble":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"wibble":{}}')); + }); it('returned undefined', () => { - assert.equal(result, undefined) - }) - }) + assert.equal(result, undefined); + }); + }); describe('createSessionToken reads populated object from redis:', () => { - let result + let result; beforeEach(() => { return db.createSessionToken({ uid: 'wibble' }) - .then(() => result = redis.update.args[0][1]('{"frang":{}}')) - }) + .then(() => result = redis.update.args[0][1]('{"frang":{}}')); + }); it('returned object', () => { - assert.equal(result, '{"frang":[]}') - }) - }) + assert.equal(result, '{"frang":[]}'); + }); + }); describe('mock db.pruneSessionTokens:', () => { beforeEach(() => { - db.pruneSessionTokens = sinon.spy(() => P.resolve()) - }) + db.pruneSessionTokens = sinon.spy(() => P.resolve()); + }); describe('return expired tokens from pool.get:', () => { beforeEach(() => { - const expiryPoint = Date.now() - tokenLifetimes.sessionTokenWithoutDevice + const expiryPoint = Date.now() - tokenLifetimes.sessionTokenWithoutDevice; pool.get = sinon.spy(() => P.resolve([ { tokenId: 'unexpired', createdAt: expiryPoint + 1000 }, { tokenId: 'expired1', createdAt: expiryPoint - 1 }, { tokenId: 'expired2', createdAt: 1 } - ])) - }) + ])); + }); it('should call pruneSessionTokens in db.sessions', () => { return db.sessions('foo') .then(result => { - assert.equal(result.length, 1) - assert.equal(result[0].id, 'unexpired') + assert.equal(result.length, 1); + assert.equal(result[0].id, 'unexpired'); - assert.equal(db.pruneSessionTokens.callCount, 1) - const args = db.pruneSessionTokens.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'foo') - assert.ok(Array.isArray(args[1])) - assert.equal(args[1].length, 2) - assert.equal(args[1][0].id, 'expired1') - assert.equal(args[1][1].id, 'expired2') - }) - }) - }) + assert.equal(db.pruneSessionTokens.callCount, 1); + const args = db.pruneSessionTokens.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'foo'); + assert.ok(Array.isArray(args[1])); + assert.equal(args[1].length, 2); + assert.equal(args[1][0].id, 'expired1'); + assert.equal(args[1][1].id, 'expired2'); + }); + }); + }); describe('return unexpired tokens from pool.get:', () => { beforeEach(() => { - const expiryPoint = Date.now() - tokenLifetimes.sessionTokenWithoutDevice + const expiryPoint = Date.now() - tokenLifetimes.sessionTokenWithoutDevice; pool.get = sinon.spy(() => P.resolve([ { tokenId: 'unexpired1', createdAt: expiryPoint + 1000 }, { tokenId: 'unexpired2', createdAt: expiryPoint + 100000 }, { tokenId: 'unexpired3', createdAt: expiryPoint + 10000000 } - ])) - }) + ])); + }); it('should not call pruneSessionTokens in db.sessions', () => { return db.sessions('foo') .then(result => { - assert.equal(result.length, 3) - assert.equal(db.pruneSessionTokens.callCount, 0) - }) - }) - }) - }) -}) + assert.equal(result.length, 3); + assert.equal(db.pruneSessionTokens.callCount, 0); + }); + }); + }); + }); +}); describe('redis enabled, token-pruning disabled:', () => { const tokenLifetimes = { sessionTokenWithoutDevice: 2419200000 - } + }; - let pool, redis, log, tokens, db + let pool, redis, log, tokens, db; beforeEach(() => { pool = { get: sinon.spy(() => P.resolve([])), post: sinon.spy(() => P.resolve()), del: sinon.spy(() => P.resolve()) - } + }; redis = { get: sinon.spy(() => P.resolve('{}')), set: sinon.spy(() => P.resolve()), del: sinon.spy(() => P.resolve()), update: sinon.spy(() => P.resolve()) - } - log = mocks.mockLog() - tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }) + }; + log = mocks.mockLog(); + tokens = require(`${LIB_DIR}/tokens`)(log, { tokenLifetimes }); const DB = proxyquire(`${LIB_DIR}/db`, { - './pool': function () { return pool }, + './pool': function () { return pool; }, './redis': (...args) => { - assert.equal(args.length, 2, 'redisPool was passed two arguments') - assert.equal(args[0].foo, 'bar', 'redisPool was passed config') - assert.equal(args[0].baz, 'qux', 'redisPool was passed session token config') - assert.equal(args[0].prefix, 'wibble', 'redisPool was passed session token prefix') - assert.equal(args[0].blee, undefined, 'redisPool was not passed email service config') - assert.equal(args[1], log, 'redisPool was passed log') - return redis + assert.equal(args.length, 2, 'redisPool was passed two arguments'); + assert.equal(args[0].foo, 'bar', 'redisPool was passed config'); + assert.equal(args[0].baz, 'qux', 'redisPool was passed session token config'); + assert.equal(args[0].prefix, 'wibble', 'redisPool was passed session token prefix'); + assert.equal(args[0].blee, undefined, 'redisPool was not passed email service config'); + assert.equal(args[1], log, 'redisPool was passed log'); + return redis; } })({ tokenLifetimes, @@ -1221,16 +1221,16 @@ describe('redis enabled, token-pruning disabled:', () => { sampleRate: 1, earliestSaneTimestamp: 1 } - }, log, tokens, {}) + }, log, tokens, {}); return DB.connect({}) - .then(result => db = result) - }) + .then(result => db = result); + }); it('should not call redis.update in db.pruneSessionTokens', () => { return db.pruneSessionTokens('wibble', [ { id: 'blee', createdAt: 1 } ]) - .then(() => assert.equal(redis.update.callCount, 0)) - }) -}) + .then(() => assert.equal(redis.update.callCount, 0)); + }); +}); diff --git a/test/local/devices.js b/test/local/devices.js index ba6dd050..4c20c895 100644 --- a/test/local/devices.js +++ b/test/local/devices.js @@ -2,77 +2,77 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const mocks = require('../mocks') -const uuid = require('uuid') +const { assert } = require('chai'); +const crypto = require('crypto'); +const mocks = require('../mocks'); +const uuid = require('uuid'); -const MODULE_PATH = '../../lib/devices' +const MODULE_PATH = '../../lib/devices'; describe('lib/devices:', () => { it('should export the correct interface', () => { - assert.equal(typeof require(MODULE_PATH), 'function') - assert.equal(require(MODULE_PATH).length, 3) - assert.equal(typeof require(MODULE_PATH).schema, 'object') - assert.notEqual(require(MODULE_PATH).schema, null) - }) + assert.equal(typeof require(MODULE_PATH), 'function'); + assert.equal(require(MODULE_PATH).length, 3); + assert.equal(typeof require(MODULE_PATH).schema, 'object'); + assert.notEqual(require(MODULE_PATH).schema, null); + }); describe('instantiate:', () => { - let log, deviceCreatedAt, deviceId, device, db, push, devices + let log, deviceCreatedAt, deviceId, device, db, push, devices; beforeEach(() => { - log = mocks.mockLog() - deviceCreatedAt = Date.now() - deviceId = crypto.randomBytes(16).toString('hex') + log = mocks.mockLog(); + deviceCreatedAt = Date.now(); + deviceId = crypto.randomBytes(16).toString('hex'); device = { name: 'foo', type: 'bar' - } + }; db = mocks.mockDB({ device: device, deviceCreatedAt: deviceCreatedAt, deviceId: deviceId - }) - push = mocks.mockPush() - devices = require(MODULE_PATH)(log, db, push) - }) + }); + push = mocks.mockPush(); + devices = require(MODULE_PATH)(log, db, push); + }); it('returns the expected interface', () => { - assert.equal(typeof devices, 'object') - assert.equal(Object.keys(devices).length, 3) + assert.equal(typeof devices, 'object'); + assert.equal(Object.keys(devices).length, 3); - assert.equal(typeof devices.isSpuriousUpdate, 'function') - assert.equal(devices.isSpuriousUpdate.length, 2) + assert.equal(typeof devices.isSpuriousUpdate, 'function'); + assert.equal(devices.isSpuriousUpdate.length, 2); - assert.equal(typeof devices.upsert, 'function') - assert.equal(devices.upsert.length, 3) + assert.equal(typeof devices.upsert, 'function'); + assert.equal(devices.upsert.length, 3); - assert.equal(typeof devices.synthesizeName, 'function') - assert.equal(devices.synthesizeName.length, 1) - }) + assert.equal(typeof devices.synthesizeName, 'function'); + assert.equal(devices.synthesizeName.length, 1); + }); describe('isSpuriousUpdate:', () => { it('returns false when token has no device record', () => { - assert.strictEqual(devices.isSpuriousUpdate({}, {}), false) - }) + assert.strictEqual(devices.isSpuriousUpdate({}, {}), false); + }); it('returns false when token has different device id', () => { assert.strictEqual(devices.isSpuriousUpdate({ id: 'foo' }, { deviceId: 'bar' - }), false) - }) + }), false); + }); it('returns true when ids match', () => { assert.strictEqual(devices.isSpuriousUpdate({ id: 'foo' }, { deviceId: 'foo' - }), true) - }) + }), true); + }); it('returns false when token has different device name', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -81,8 +81,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceName: 'bar' - }), false) - }) + }), false); + }); it('returns true when ids and names match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -91,8 +91,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceName: 'foo' - }), true) - }) + }), true); + }); it('returns false when token has different device type', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -101,8 +101,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceType: 'bar' - }), false) - }) + }), false); + }); it('returns true when ids and types match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -111,8 +111,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceType: 'foo' - }), true) - }) + }), true); + }); it('returns false when token has different device callback URL', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -121,8 +121,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceCallbackURL: 'bar' - }), false) - }) + }), false); + }); it('returns true when ids and callback URLs match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -131,8 +131,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceCallbackURL: 'foo' - }), true) - }) + }), true); + }); it('returns false when token has different device callback public key', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -141,8 +141,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceCallbackPublicKey: 'bar' - }), false) - }) + }), false); + }); it('returns true when ids and callback public keys match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -151,8 +151,8 @@ describe('lib/devices:', () => { }, { deviceId: 'foo', deviceCallbackPublicKey: 'foo' - }), true) - }) + }), true); + }); it('returns false when payload has different available commands', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -166,8 +166,8 @@ describe('lib/devices:', () => { deviceAvailableCommands: { foo: 'bar' } - }), false) - }) + }), false); + }); it('returns false when token has different device available commands', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -181,8 +181,8 @@ describe('lib/devices:', () => { foo: 'bar', baz: 'qux' } - }), false) - }) + }), false); + }); it('returns true when ids and available commands match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -195,8 +195,8 @@ describe('lib/devices:', () => { deviceAvailableCommands: { foo: 'bar' } - }), true) - }) + }), true); + }); it('returns true when all properties match', () => { assert.strictEqual(devices.isSpuriousUpdate({ @@ -219,24 +219,24 @@ describe('lib/devices:', () => { frop: 'punv', thib: 'blap' } - }), true) - }) - }) + }), true); + }); + }); describe('upsert:', () => { - var request, credentials + var request, credentials; beforeEach(() => { request = mocks.mockRequest({ log: log - }) + }); credentials = { id: crypto.randomBytes(16).toString('hex'), uid: uuid.v4('binary').toString('hex'), tokenVerified: true - } - }) + }; + }); it('should create', () => { return devices.upsert(request, credentials, device) @@ -246,19 +246,19 @@ describe('lib/devices:', () => { name: device.name, type: device.type, createdAt: deviceCreatedAt - }, 'result was correct') + }, 'result was correct'); - assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called') + assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called'); - assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once') - var args = db.createDevice.args[0] - assert.equal(args.length, 2, 'db.createDevice was passed two arguments') - assert.deepEqual(args[0], credentials.uid, 'first argument was uid') - assert.equal(args[1], device, 'second argument was device') + assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once'); + var args = db.createDevice.args[0]; + assert.equal(args.length, 2, 'db.createDevice was passed two arguments'); + assert.deepEqual(args[0], credentials.uid, 'first argument was uid'); + assert.equal(args[1], device, 'second argument was device'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.created', @@ -268,95 +268,95 @@ describe('lib/devices:', () => { uid: credentials.uid, device_id: deviceId, is_placeholder: false - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(log.info.callCount, 0, 'log.info was not called') + assert.equal(log.info.callCount, 0, 'log.info was not called'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once') - args = log.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'device:create', 'first argument was event name') - assert.equal(args[1], request, 'second argument was request object') + assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once'); + args = log.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'device:create', 'first argument was event name'); + assert.equal(args[1], request, 'second argument was request object'); assert.deepEqual(args[2], { uid: credentials.uid, id: deviceId, type: device.type, timestamp: deviceCreatedAt, isPlaceholder: false - }, 'third argument was event data') + }, 'third argument was event data'); - assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once') - args = push.notifyDeviceConnected.args[0] - assert.equal(args.length, 3, 'push.notifyDeviceConnected was passed three arguments') - assert.equal(args[0], credentials.uid, 'first argument was uid') - assert.ok(Array.isArray(args[1]), 'second argument was devices array') - assert.equal(args[2], device.name, 'third argument was device name') - }) - }) + assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once'); + args = push.notifyDeviceConnected.args[0]; + assert.equal(args.length, 3, 'push.notifyDeviceConnected was passed three arguments'); + assert.equal(args[0], credentials.uid, 'first argument was uid'); + assert.ok(Array.isArray(args[1]), 'second argument was devices array'); + assert.equal(args[2], device.name, 'third argument was device name'); + }); + }); it('should not call notifyDeviceConnected with unverified token', () => { - credentials.tokenVerified = false - device.name = 'device with an unverified sessionToken' + credentials.tokenVerified = false; + device.name = 'device with an unverified sessionToken'; return devices.upsert(request, credentials, device) .then(function () { - assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called') - credentials.tokenVerified = true - }) - }) + assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called'); + credentials.tokenVerified = true; + }); + }); it('should create placeholders', () => { - delete device.name + delete device.name; return devices.upsert(request, credentials, { uaBrowser: 'Firefox' }) .then(function (result) { - assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called') - assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once') + assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called'); + assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(log.activityEvent.args[0][0].is_placeholder, true, 'is_placeholder was correct') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(log.activityEvent.args[0][0].is_placeholder, true, 'is_placeholder was correct'); - assert.equal(log.info.callCount, 1, 'log.info was called once') - assert.equal(log.info.args[0].length, 2) - assert.equal(log.info.args[0][0], 'device:createPlaceholder') + assert.equal(log.info.callCount, 1, 'log.info was called once'); + assert.equal(log.info.args[0].length, 2); + assert.equal(log.info.args[0][0], 'device:createPlaceholder'); assert.deepEqual(log.info.args[0][1], { uid: credentials.uid, id: result.id - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once') - assert.equal(log.notifyAttachedServices.args[0][2].isPlaceholder, true, 'isPlaceholder was correct') + assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once'); + assert.equal(log.notifyAttachedServices.args[0][2].isPlaceholder, true, 'isPlaceholder was correct'); - assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once') - assert.equal(push.notifyDeviceConnected.args[0][0], credentials.uid, 'uid was correct') - assert.equal(push.notifyDeviceConnected.args[0][2], 'Firefox', 'device name was included') + assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once'); + assert.equal(push.notifyDeviceConnected.args[0][0], credentials.uid, 'uid was correct'); + assert.equal(push.notifyDeviceConnected.args[0][2], 'Firefox', 'device name was included'); - }) - }) + }); + }); it('should update', () => { var deviceInfo = { id: deviceId, name: device.name, type: device.type - } + }; return devices.upsert(request, credentials, deviceInfo) .then(function (result) { - assert.equal(result, deviceInfo, 'result was correct') + assert.equal(result, deviceInfo, 'result was correct'); - assert.equal(db.createDevice.callCount, 0, 'db.createDevice was not called') + assert.equal(db.createDevice.callCount, 0, 'db.createDevice was not called'); - assert.equal(db.updateDevice.callCount, 1, 'db.updateDevice was called once') - var args = db.updateDevice.args[0] - assert.equal(args.length, 2, 'db.createDevice was passed two arguments') - assert.deepEqual(args[0], credentials.uid, 'first argument was uid') + assert.equal(db.updateDevice.callCount, 1, 'db.updateDevice was called once'); + var args = db.updateDevice.args[0]; + assert.equal(args.length, 2, 'db.createDevice was passed two arguments'); + assert.deepEqual(args[0], credentials.uid, 'first argument was uid'); assert.deepEqual(args[1], { id: deviceId, name: device.name, type: device.type - }, 'device info was unmodified') + }, 'device info was unmodified'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.updated', @@ -366,31 +366,31 @@ describe('lib/devices:', () => { uid: credentials.uid, device_id: deviceId, is_placeholder: false - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(log.info.callCount, 0, 'log.info was not called') + assert.equal(log.info.callCount, 0, 'log.info was not called'); - assert.equal(log.notifyAttachedServices.callCount, 0, 'log.notifyAttachedServices was not called') + assert.equal(log.notifyAttachedServices.callCount, 0, 'log.notifyAttachedServices was not called'); - assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called') - }) - }) - }) + assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called'); + }); + }); + }); describe('upsert with refreshToken:', () => { - let request, credentials + let request, credentials; beforeEach(() => { request = mocks.mockRequest({ log: log - }) + }); credentials = { refreshTokenId: crypto.randomBytes(16).toString('hex'), uid: uuid.v4('binary').toString('hex'), tokenVerified: true - } - }) + }; + }); it('should create', () => { return devices.upsert(request, credentials, device) @@ -400,19 +400,19 @@ describe('lib/devices:', () => { name: device.name, type: device.type, createdAt: deviceCreatedAt - }, 'result was correct') + }, 'result was correct'); - assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called') + assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called'); - assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once') - let args = db.createDevice.args[0] - assert.equal(args.length, 2, 'db.createDevice was passed two arguments') - assert.deepEqual(args[0], credentials.uid, 'first argument was uid') - assert.equal(args[1], device, 'second argument was device') + assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once'); + let args = db.createDevice.args[0]; + assert.equal(args.length, 2, 'db.createDevice was passed two arguments'); + assert.deepEqual(args[0], credentials.uid, 'first argument was uid'); + assert.equal(args[1], device, 'second argument was device'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.created', @@ -422,85 +422,85 @@ describe('lib/devices:', () => { uid: credentials.uid, device_id: deviceId, is_placeholder: false - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(log.info.callCount, 0, 'log.info was not called') + assert.equal(log.info.callCount, 0, 'log.info was not called'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once') - args = log.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'device:create', 'first argument was event name') - assert.equal(args[1], request, 'second argument was request object') + assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once'); + args = log.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'device:create', 'first argument was event name'); + assert.equal(args[1], request, 'second argument was request object'); assert.deepEqual(args[2], { uid: credentials.uid, id: deviceId, type: device.type, timestamp: deviceCreatedAt, isPlaceholder: false - }, 'third argument was event data') + }, 'third argument was event data'); - assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once') - args = push.notifyDeviceConnected.args[0] - assert.equal(args.length, 3, 'push.notifyDeviceConnected was passed three arguments') - assert.equal(args[0], credentials.uid, 'first argument was uid') - assert.ok(Array.isArray(args[1]), 'second argument was devices array') - assert.equal(args[2], device.name, 'third argument was device name') - }) - }) + assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once'); + args = push.notifyDeviceConnected.args[0]; + assert.equal(args.length, 3, 'push.notifyDeviceConnected was passed three arguments'); + assert.equal(args[0], credentials.uid, 'first argument was uid'); + assert.ok(Array.isArray(args[1]), 'second argument was devices array'); + assert.equal(args[2], device.name, 'third argument was device name'); + }); + }); it('should create placeholders', () => { - delete device.name + delete device.name; return devices.upsert(request, credentials, { uaBrowser: 'Firefox' }) .then(function (result) { - assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called') - assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once') + assert.equal(db.updateDevice.callCount, 0, 'db.updateDevice was not called'); + assert.equal(db.createDevice.callCount, 1, 'db.createDevice was called once'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(log.activityEvent.args[0][0].is_placeholder, true, 'is_placeholder was correct') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(log.activityEvent.args[0][0].is_placeholder, true, 'is_placeholder was correct'); - assert.equal(log.info.callCount, 1, 'log.info was called once') - assert.equal(log.info.args[0].length, 2) - assert.equal(log.info.args[0][0], 'device:createPlaceholder') + assert.equal(log.info.callCount, 1, 'log.info was called once'); + assert.equal(log.info.args[0].length, 2); + assert.equal(log.info.args[0][0], 'device:createPlaceholder'); assert.deepEqual(log.info.args[0][1], { uid: credentials.uid, id: result.id - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once') - assert.equal(log.notifyAttachedServices.args[0][2].isPlaceholder, true, 'isPlaceholder was correct') + assert.equal(log.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once'); + assert.equal(log.notifyAttachedServices.args[0][2].isPlaceholder, true, 'isPlaceholder was correct'); - assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once') - assert.equal(push.notifyDeviceConnected.args[0][0], credentials.uid, 'uid was correct') - assert.equal(push.notifyDeviceConnected.args[0][2], 'Firefox', 'device name was included') + assert.equal(push.notifyDeviceConnected.callCount, 1, 'push.notifyDeviceConnected was called once'); + assert.equal(push.notifyDeviceConnected.args[0][0], credentials.uid, 'uid was correct'); + assert.equal(push.notifyDeviceConnected.args[0][2], 'Firefox', 'device name was included'); - }) - }) + }); + }); it('should update', () => { var deviceInfo = { id: deviceId, name: device.name, type: device.type - } + }; return devices.upsert(request, credentials, deviceInfo) .then(function (result) { - assert.equal(result, deviceInfo, 'result was correct') + assert.equal(result, deviceInfo, 'result was correct'); - assert.equal(db.createDevice.callCount, 0, 'db.createDevice was not called') + assert.equal(db.createDevice.callCount, 0, 'db.createDevice was not called'); - assert.equal(db.updateDevice.callCount, 1, 'db.updateDevice was called once') - var args = db.updateDevice.args[0] - assert.equal(args.length, 2, 'db.createDevice was passed two arguments') - assert.deepEqual(args[0], credentials.uid, 'first argument was uid') + assert.equal(db.updateDevice.callCount, 1, 'db.updateDevice was called once'); + var args = db.updateDevice.args[0]; + assert.equal(args.length, 2, 'db.createDevice was passed two arguments'); + assert.deepEqual(args[0], credentials.uid, 'first argument was uid'); assert.deepEqual(args[1], { id: deviceId, name: device.name, type: device.type - }, 'device info was unmodified') + }, 'device info was unmodified'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.updated', @@ -510,16 +510,16 @@ describe('lib/devices:', () => { uid: credentials.uid, device_id: deviceId, is_placeholder: false - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(log.info.callCount, 0, 'log.info was not called') + assert.equal(log.info.callCount, 0, 'log.info was not called'); - assert.equal(log.notifyAttachedServices.callCount, 0, 'log.notifyAttachedServices was not called') + assert.equal(log.notifyAttachedServices.callCount, 0, 'log.notifyAttachedServices was not called'); - assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called') - }) - }) - }) + assert.equal(push.notifyDeviceConnected.callCount, 0, 'push.notifyDeviceConnected was not called'); + }); + }); + }); it('should synthesizeName', () => { assert.equal(devices.synthesizeName({ @@ -528,76 +528,76 @@ describe('lib/devices:', () => { uaOS: 'baz', uaOSVersion: 'qux', uaFormFactor: 'wibble' - }), 'foo bar, wibble', 'result is correct when all ua properties are set') + }), 'foo bar, wibble', 'result is correct when all ua properties are set'); assert.equal(devices.synthesizeName({ uaBrowserVersion: 'foo.foo', uaOS: 'bar', uaOSVersion: 'baz', uaFormFactor: 'wibble' - }), 'wibble', 'result is correct when uaBrowser property is missing') + }), 'wibble', 'result is correct when uaBrowser property is missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'foo', uaOS: 'bar', uaOSVersion: 'baz', uaFormFactor: 'wibble' - }), 'foo, wibble', 'result is correct when uaBrowserVersion property is missing') + }), 'foo, wibble', 'result is correct when uaBrowserVersion property is missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'foo', uaBrowserVersion: 'bar.bar', uaOSVersion: 'baz', uaFormFactor: 'wibble' - }), 'foo bar, wibble', 'result is correct when uaOS property is missing') + }), 'foo bar, wibble', 'result is correct when uaOS property is missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'foo', uaBrowserVersion: 'bar.bar', uaOS: 'baz', uaFormFactor: 'wibble' - }), 'foo bar, wibble', 'result is correct when uaOSVersion property is missing') + }), 'foo bar, wibble', 'result is correct when uaOSVersion property is missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'foo', uaBrowserVersion: 'bar.bar', uaOS: 'baz', uaOSVersion: 'qux' - }), 'foo bar, baz qux', 'result is correct when uaFormFactor property is missing') + }), 'foo bar, baz qux', 'result is correct when uaFormFactor property is missing'); assert.equal(devices.synthesizeName({ uaOS: 'bar', uaFormFactor: 'wibble' - }), 'wibble', 'result is correct when uaBrowser and uaBrowserVersion properties are missing') + }), 'wibble', 'result is correct when uaBrowser and uaBrowserVersion properties are missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'wibble', uaBrowserVersion: 'blee.blee', uaOSVersion: 'qux' - }), 'wibble blee', 'result is correct when uaOS and uaFormFactor properties are missing') + }), 'wibble blee', 'result is correct when uaOS and uaFormFactor properties are missing'); assert.equal(devices.synthesizeName({ uaBrowser: 'foo', uaBrowserVersion: 'bar.bar', uaOS: 'baz' - }), 'foo bar, baz', 'result is correct when uaOSVersion and uaFormFactor properties are missing') + }), 'foo bar, baz', 'result is correct when uaOSVersion and uaFormFactor properties are missing'); assert.equal(devices.synthesizeName({ uaOS: 'foo' - }), 'foo', 'result is correct when only uaOS property is present') + }), 'foo', 'result is correct when only uaOS property is present'); assert.equal(devices.synthesizeName({ uaFormFactor: 'bar' - }), 'bar', 'result is correct when only uaFormFactor property is present') + }), 'bar', 'result is correct when only uaFormFactor property is present'); assert.equal(devices.synthesizeName({ uaOS: 'foo', uaOSVersion: 'bar' - }), 'foo bar', 'result is correct when only uaOS and uaOSVersion properties are present') + }), 'foo bar', 'result is correct when only uaOS and uaOSVersion properties are present'); assert.equal(devices.synthesizeName({ uaOSVersion: 'foo' - }), '', 'result defaults to the empty string') - }) - }) -}) + }), '', 'result defaults to the empty string'); + }); + }); +}); diff --git a/test/local/email/bounce.js b/test/local/email/bounce.js index 3942982e..c80dfbe0 100644 --- a/test/local/email/bounce.js +++ b/test/local/email/bounce.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const bounces = require(`${ROOT_DIR}/lib/email/bounces`) -const error = require(`${ROOT_DIR}/lib/error`) -const { EventEmitter } = require('events') -const { mockLog } = require('../../mocks') -const P = require(`${ROOT_DIR}/lib/promise`) -const sinon = require('sinon') +const { assert } = require('chai'); +const bounces = require(`${ROOT_DIR}/lib/email/bounces`); +const error = require(`${ROOT_DIR}/lib/error`); +const { EventEmitter } = require('events'); +const { mockLog } = require('../../mocks'); +const P = require(`${ROOT_DIR}/lib/promise`); +const sinon = require('sinon'); -const mockBounceQueue = new EventEmitter() -mockBounceQueue.start = function start() {} +const mockBounceQueue = new EventEmitter(); +mockBounceQueue.start = function start() {}; function mockMessage(msg) { - msg.del = sinon.spy() - msg.headers = {} - return msg + msg.del = sinon.spy(); + msg.headers = {}; + return msg; } function mockedBounces(log, db) { - return bounces(log, error)(mockBounceQueue, db) + return bounces(log, error)(mockBounceQueue, db); } describe('bounce messages', () => { - let log, mockDB + let log, mockDB; beforeEach(() => { - log = mockLog() + log = mockLog(); mockDB = { createEmailBounce: sinon.spy(() =>P.resolve({})), accountRecord: sinon.spy((email) => { @@ -39,45 +39,45 @@ describe('bounce messages', () => { email: email, emailVerified: false, uid: '123456' - }) + }); }), deleteAccount: sinon.spy(() => P.resolve({})) - } - }) + }; + }); afterEach(() => { - mockBounceQueue.removeAllListeners() - }) + mockBounceQueue.removeAllListeners(); + }); it('should not log an error for headers', () => { return mockedBounces(log, {}) .handleBounce(mockMessage({ junk: 'message' })) - .then(() => assert.equal(log.error.callCount, 0)) - }) + .then(() => assert.equal(log.error.callCount, 0)); + }); it('should log an error for missing headers', () => { const message = mockMessage({ junk: 'message' - }) - message.headers = undefined + }); + message.headers = undefined; return mockedBounces(log, {}) .handleBounce(message) - .then(() => assert.equal(log.error.callCount, 1)) - }) + .then(() => assert.equal(log.error.callCount, 1)); + }); it('should ignore unknown message types', () => { return mockedBounces(log, {}).handleBounce(mockMessage({ junk: 'message' })).then(() => { - assert.equal(log.info.callCount, 0) - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 1) - assert.equal(log.warn.args[0][0], 'emailHeaders.keys') - }) - }) + assert.equal(log.info.callCount, 0); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 1); + assert.equal(log.warn.args[0][0], 'emailHeaders.keys'); + }); + }); it('should handle multiple recipients in turn', () => { - const bounceType = 'Permanent' + const bounceType = 'Permanent'; const mockMsg = mockMessage({ bounce: { bounceType: bounceType, @@ -86,22 +86,22 @@ describe('bounce messages', () => { {emailAddress: 'foobar@example.com'} ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.createEmailBounce.callCount, 2) - assert.equal(mockDB.accountRecord.callCount, 2) - assert.equal(mockDB.deleteAccount.callCount, 2) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.accountRecord.args[1][0], 'foobar@example.com') - assert.equal(log.info.callCount, 6) - assert.equal(log.info.args[5][0], 'accountDeleted') - assert.equal(log.info.args[5][1].email, 'foobar@example.com') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.createEmailBounce.callCount, 2); + assert.equal(mockDB.accountRecord.callCount, 2); + assert.equal(mockDB.deleteAccount.callCount, 2); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.accountRecord.args[1][0], 'foobar@example.com'); + assert.equal(log.info.callCount, 6); + assert.equal(log.info.args[5][0], 'accountDeleted'); + assert.equal(log.info.args[5][1].email, 'foobar@example.com'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should delete account registered with a Transient bounce', () => { - const bounceType = 'Transient' + const bounceType = 'Transient'; const mockMsg = mockMessage({ bounce: { bounceType: bounceType, @@ -117,25 +117,25 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.deleteAccount.callCount, 1, 'deletes the account') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.deleteAccount.callCount, 1, 'deletes the account'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should not delete account that bounces and is older than 6 hours', () => { - const SEVEN_HOURS_AGO = Date.now() - 1000 * 60 * 60 * 7 + const SEVEN_HOURS_AGO = Date.now() - 1000 * 60 * 60 * 7; mockDB.accountRecord = sinon.spy((email) => { return P.resolve({ createdAt: SEVEN_HOURS_AGO, uid: '123456', email: email, emailVerified: (email === 'verified@example.com') - }) - }) + }); + }); - const bounceType = 'Transient' + const bounceType = 'Transient'; const mockMsg = mockMessage({ bounce: { bounceType: bounceType, @@ -151,25 +151,25 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.deleteAccount.callCount, 0, 'does not delete the account') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.deleteAccount.callCount, 0, 'does not delete the account'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should delete account that bounces and is younger than 6 hours', () => { - const FOUR_HOURS_AGO = Date.now() - 1000 * 60 * 60 * 5 + const FOUR_HOURS_AGO = Date.now() - 1000 * 60 * 60 * 5; mockDB.accountRecord = sinon.spy((email) => { return P.resolve({ createdAt: FOUR_HOURS_AGO, uid: '123456', email: email, emailVerified: (email === 'verified@example.com') - }) - }) + }); + }); - const bounceType = 'Transient' + const bounceType = 'Transient'; const mockMsg = mockMessage({ bounce: { bounceType: bounceType, @@ -185,15 +185,15 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.deleteAccount.callCount, 1, 'delete the account') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.deleteAccount.callCount, 1, 'delete the account'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should delete accounts on login verification with a Transient bounce', () => { - const bounceType = 'Transient' + const bounceType = 'Transient'; const mockMsg = mockMessage({ bounce: { bounceType: bounceType, @@ -209,15 +209,15 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.deleteAccount.callCount, 1, 'deletes the account') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.deleteAccount.callCount, 1, 'deletes the account'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should treat complaints like bounces', () => { - const complaintType = 'abuse' + const complaintType = 'abuse'; return mockedBounces(log, mockDB).handleBounce(mockMessage({ complaint: { userAgent: 'AnyCompany Feedback Loop (V0.01)', @@ -228,22 +228,22 @@ describe('bounce messages', () => { ] } })).then(() => { - assert.equal(mockDB.createEmailBounce.callCount, 2) - assert.equal(mockDB.createEmailBounce.args[0][0].bounceType, 'Complaint') - assert.equal(mockDB.createEmailBounce.args[0][0].bounceSubType, complaintType) - assert.equal(mockDB.accountRecord.callCount, 2) - assert.equal(mockDB.deleteAccount.callCount, 2) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.accountRecord.args[1][0], 'foobar@example.com') - assert.equal(log.info.callCount, 6) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].domain, 'other') - assert.equal(log.info.args[0][1].type, 'bounced') - assert.equal(log.info.args[4][1].complaint, true) - assert.equal(log.info.args[4][1].complaintFeedbackType, complaintType) - assert.equal(log.info.args[4][1].complaintUserAgent, 'AnyCompany Feedback Loop (V0.01)') - }) - }) + assert.equal(mockDB.createEmailBounce.callCount, 2); + assert.equal(mockDB.createEmailBounce.args[0][0].bounceType, 'Complaint'); + assert.equal(mockDB.createEmailBounce.args[0][0].bounceSubType, complaintType); + assert.equal(mockDB.accountRecord.callCount, 2); + assert.equal(mockDB.deleteAccount.callCount, 2); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.accountRecord.args[1][0], 'foobar@example.com'); + assert.equal(log.info.callCount, 6); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].domain, 'other'); + assert.equal(log.info.args[0][1].type, 'bounced'); + assert.equal(log.info.args[4][1].complaint, true); + assert.equal(log.info.args[4][1].complaintFeedbackType, complaintType); + assert.equal(log.info.args[4][1].complaintUserAgent, 'AnyCompany Feedback Loop (V0.01)'); + }); + }); it('should not delete verified accounts on bounce', () => { mockDB.accountRecord = sinon.spy((email) => { @@ -252,8 +252,8 @@ describe('bounce messages', () => { uid: '123456', email: email, emailVerified: (email === 'verified@example.com') - }) - }) + }); + }); return mockedBounces(log, mockDB).handleBounce(mockMessage({ bounce: { @@ -265,28 +265,28 @@ describe('bounce messages', () => { ] } })).then(() => { - assert.equal(mockDB.accountRecord.callCount, 2) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.accountRecord.args[1][0], 'verified@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com') - assert.equal(log.info.callCount, 5) - assert.equal(log.info.args[1][0], 'handleBounce') - assert.equal(log.info.args[1][1].email, 'test@example.com') - assert.equal(log.info.args[1][1].domain, 'other') - assert.equal(log.info.args[1][1].status, '5.0.0') - assert.equal(log.info.args[1][1].action, 'failed') - assert.equal(log.info.args[1][1].diagnosticCode, 'smtp; 550 user unknown') - assert.equal(log.info.args[2][0], 'accountDeleted') - assert.equal(log.info.args[2][1].email, 'test@example.com') - assert.equal(log.info.args[4][0], 'handleBounce') - assert.equal(log.info.args[4][1].email, 'verified@example.com') - assert.equal(log.info.args[4][1].status, '4.0.0') - }) - }) + assert.equal(mockDB.accountRecord.callCount, 2); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.accountRecord.args[1][0], 'verified@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com'); + assert.equal(log.info.callCount, 5); + assert.equal(log.info.args[1][0], 'handleBounce'); + assert.equal(log.info.args[1][1].email, 'test@example.com'); + assert.equal(log.info.args[1][1].domain, 'other'); + assert.equal(log.info.args[1][1].status, '5.0.0'); + assert.equal(log.info.args[1][1].action, 'failed'); + assert.equal(log.info.args[1][1].diagnosticCode, 'smtp; 550 user unknown'); + assert.equal(log.info.args[2][0], 'accountDeleted'); + assert.equal(log.info.args[2][1].email, 'test@example.com'); + assert.equal(log.info.args[4][0], 'handleBounce'); + assert.equal(log.info.args[4][1].email, 'verified@example.com'); + assert.equal(log.info.args[4][1].status, '4.0.0'); + }); + }); it('should log errors when looking up the email record', () => { - mockDB.accountRecord = sinon.spy(() => P.reject(new error({}))) + mockDB.accountRecord = sinon.spy(() => P.reject(new error({}))); const mockMsg = mockMessage({ bounce: { bounceType: 'Permanent', @@ -294,22 +294,22 @@ describe('bounce messages', () => { {emailAddress: 'test@example.com'}, ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[1][0], 'handleBounce') - assert.equal(log.info.args[1][1].email, 'test@example.com') - assert.equal(log.error.callCount, 2) - assert.equal(log.error.args[1][0], 'databaseError') - assert.equal(log.error.args[1][1].email, 'test@example.com') - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[1][0], 'handleBounce'); + assert.equal(log.info.args[1][1].email, 'test@example.com'); + assert.equal(log.error.callCount, 2); + assert.equal(log.error.args[1][0], 'databaseError'); + assert.equal(log.error.args[1][1].email, 'test@example.com'); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should log errors when deleting the email record', () => { - mockDB.deleteAccount = sinon.spy(() => P.reject(new error.unknownAccount('test@example.com'))) + mockDB.deleteAccount = sinon.spy(() => P.reject(new error.unknownAccount('test@example.com'))); const mockMsg = mockMessage({ bounce: { bounceType: 'Permanent', @@ -317,36 +317,36 @@ describe('bounce messages', () => { {emailAddress: 'test@example.com'}, ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com') - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[1][0], 'handleBounce') - assert.equal(log.info.args[1][1].email, 'test@example.com') - assert.equal(log.error.callCount, 2) - assert.equal(log.error.args[1][0], 'databaseError') - assert.equal(log.error.args[1][1].email, 'test@example.com') - assert.equal(log.error.args[1][1].err.errno, error.ERRNO.ACCOUNT_UNKNOWN) - assert.equal(mockMsg.del.callCount, 1) - }) - }) + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com'); + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[1][0], 'handleBounce'); + assert.equal(log.info.args[1][1].email, 'test@example.com'); + assert.equal(log.error.callCount, 2); + assert.equal(log.error.args[1][0], 'databaseError'); + assert.equal(log.error.args[1][1].email, 'test@example.com'); + assert.equal(log.error.args[1][1].err.errno, error.ERRNO.ACCOUNT_UNKNOWN); + assert.equal(mockMsg.del.callCount, 1); + }); + }); it('should normalize quoted email addresses for lookup', () => { mockDB.accountRecord = sinon.spy((email) => { // Lookup only succeeds when using original, unquoted email addr. if (email !== 'test.@example.com') { - return P.reject(new error.unknownAccount(email)) + return P.reject(new error.unknownAccount(email)); } return P.resolve({ createdAt: Date.now(), uid: '123456', email: email, emailVerified: false - }) - }) + }); + }); return mockedBounces(log, mockDB).handleBounce(mockMessage({ bounce: { bounceType: 'Permanent', @@ -357,28 +357,28 @@ describe('bounce messages', () => { ] } })).then(() => { - assert.equal(mockDB.createEmailBounce.callCount, 1) - assert.equal(mockDB.createEmailBounce.args[0][0].email, 'test.@example.com') - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test.@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test.@example.com') - }) - }) + assert.equal(mockDB.createEmailBounce.callCount, 1); + assert.equal(mockDB.createEmailBounce.args[0][0].email, 'test.@example.com'); + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test.@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test.@example.com'); + }); + }); it('should handle multiple consecutive dots even if not quoted', () => { mockDB.accountRecord = sinon.spy((email) => { // Lookup only succeeds when using original, unquoted email addr. if (email !== 'test..me@example.com') { - return P.reject(new error.unknownAccount(email)) + return P.reject(new error.unknownAccount(email)); } return P.resolve({ createdAt: Date.now(), uid: '123456', email: email, emailVerified: false - }) - }) + }); + }); return mockedBounces(log, mockDB).handleBounce(mockMessage({ bounce: { @@ -390,17 +390,17 @@ describe('bounce messages', () => { ] } })).then(() => { - assert.equal(mockDB.createEmailBounce.callCount, 1) - assert.equal(mockDB.createEmailBounce.args[0][0].email, 'test..me@example.com') - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test..me@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test..me@example.com') - }) - }) + assert.equal(mockDB.createEmailBounce.callCount, 1); + assert.equal(mockDB.createEmailBounce.args[0][0].email, 'test..me@example.com'); + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test..me@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test..me@example.com'); + }); + }); it('should log a warning if it receives an unparseable email address', () => { - mockDB.accountRecord = sinon.spy(() => P.reject(new error.unknownAccount())) + mockDB.accountRecord = sinon.spy(() => P.reject(new error.unknownAccount())); return mockedBounces(log, mockDB).handleBounce(mockMessage({ bounce: { bounceType: 'Permanent', @@ -409,13 +409,13 @@ describe('bounce messages', () => { ] } })).then(() => { - assert.equal(mockDB.createEmailBounce.callCount, 0) - assert.equal(mockDB.accountRecord.callCount, 0) - assert.equal(mockDB.deleteAccount.callCount, 0) - assert.equal(log.warn.callCount, 2) - assert.equal(log.warn.args[1][0], 'handleBounce.addressParseFailure') - }) - }) + assert.equal(mockDB.createEmailBounce.callCount, 0); + assert.equal(mockDB.accountRecord.callCount, 0); + assert.equal(mockDB.deleteAccount.callCount, 0); + assert.equal(log.warn.callCount, 2); + assert.equal(log.warn.args[1][0], 'handleBounce.addressParseFailure'); + }); + }); it('should log email template name, language, and bounceType', () => { const mockMsg = mockMessage({ @@ -438,22 +438,22 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(() => { - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com') - assert.equal(log.info.callCount, 3) - assert.equal(log.info.args[1][0], 'handleBounce') - assert.equal(log.info.args[1][1].email, 'test@example.com') - assert.equal(log.info.args[1][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[1][1].bounceType, 'Permanent') - assert.equal(log.info.args[1][1].bounceSubType, 'General') - assert.equal(log.info.args[1][1].lang, 'db-LB') - }) - }) + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com'); + assert.equal(log.info.callCount, 3); + assert.equal(log.info.args[1][0], 'handleBounce'); + assert.equal(log.info.args[1][1].email, 'test@example.com'); + assert.equal(log.info.args[1][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[1][1].bounceType, 'Permanent'); + assert.equal(log.info.args[1][1].bounceSubType, 'General'); + assert.equal(log.info.args[1][1].lang, 'db-LB'); + }); + }); it('should emit flow metrics', () => { const mockMsg = mockMessage({ @@ -484,25 +484,25 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(function () { - assert.equal(mockDB.accountRecord.callCount, 1) - assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com') - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com') - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.bounced') - assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId') - assert.equal(log.flowEvent.args[0][0].flow_time > 0, true) - assert.equal(log.flowEvent.args[0][0].time > 0, true) - assert.equal(log.info.callCount, 3) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].type, 'bounced') - assert.equal(log.info.args[0][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[0][1].flow_id, 'someFlowId') - }) - }) + assert.equal(mockDB.accountRecord.callCount, 1); + assert.equal(mockDB.accountRecord.args[0][0], 'test@example.com'); + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com'); + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.bounced'); + assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId'); + assert.equal(log.flowEvent.args[0][0].flow_time > 0, true); + assert.equal(log.flowEvent.args[0][0].time > 0, true); + assert.equal(log.info.callCount, 3); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].type, 'bounced'); + assert.equal(log.info.args[0][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[0][1].flow_id, 'someFlowId'); + }); + }); it('should log email domain if popular one', () => { const mockMsg = mockMessage({ @@ -533,23 +533,23 @@ describe('bounce messages', () => { } ] } - }) + }); return mockedBounces(log, mockDB).handleBounce(mockMsg).then(function () { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.bounced') - assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId') - assert.equal(log.flowEvent.args[0][0].flow_time > 0, true) - assert.equal(log.flowEvent.args[0][0].time > 0, true) - assert.equal(log.info.callCount, 3) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].domain, 'aol.com') - assert.equal(log.info.args[0][1].type, 'bounced') - assert.equal(log.info.args[0][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[0][1].locale, 'en') - assert.equal(log.info.args[0][1].flow_id, 'someFlowId') - assert.equal(log.info.args[1][1].email, 'test@aol.com') - assert.equal(log.info.args[1][1].domain, 'aol.com') - }) - }) -}) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.bounced'); + assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId'); + assert.equal(log.flowEvent.args[0][0].flow_time > 0, true); + assert.equal(log.flowEvent.args[0][0].time > 0, true); + assert.equal(log.info.callCount, 3); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].domain, 'aol.com'); + assert.equal(log.info.args[0][1].type, 'bounced'); + assert.equal(log.info.args[0][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[0][1].locale, 'en'); + assert.equal(log.info.args[0][1].flow_id, 'someFlowId'); + assert.equal(log.info.args[1][1].email, 'test@aol.com'); + assert.equal(log.info.args[1][1].domain, 'aol.com'); + }); + }); +}); diff --git a/test/local/email/delivery.js b/test/local/email/delivery.js index f2cdb135..e99436e0 100644 --- a/test/local/email/delivery.js +++ b/test/local/email/delivery.js @@ -2,65 +2,65 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const EventEmitter = require('events').EventEmitter -const { mockLog } = require('../../mocks') -const sinon = require('sinon') -const delivery = require('../../../lib/email/delivery') +const EventEmitter = require('events').EventEmitter; +const { mockLog } = require('../../mocks'); +const sinon = require('sinon'); +const delivery = require('../../../lib/email/delivery'); -const mockDeliveryQueue = new EventEmitter() +const mockDeliveryQueue = new EventEmitter(); mockDeliveryQueue.start = function start() { -} +}; function mockMessage(msg) { - msg.del = sinon.spy() - msg.headers = {} - return msg + msg.del = sinon.spy(); + msg.headers = {}; + return msg; } function mockedDelivery(log) { - return delivery(log)(mockDeliveryQueue) + return delivery(log)(mockDeliveryQueue); } describe('delivery messages', () => { it('should not log an error for headers', () => { - const log = mockLog() + const log = mockLog(); return mockedDelivery(log) .handleDelivery(mockMessage({ junk: 'message' })) - .then(() => assert.equal(log.error.callCount, 0)) - }) + .then(() => assert.equal(log.error.callCount, 0)); + }); it('should log an error for missing headers', () => { - const log = mockLog() + const log = mockLog(); const message = mockMessage({ junk: 'message' - }) - message.headers = undefined + }); + message.headers = undefined; return mockedDelivery(log) .handleDelivery(message) - .then(() => assert.equal(log.error.callCount, 1)) - }) + .then(() => assert.equal(log.error.callCount, 1)); + }); it( 'should ignore unknown message types', () => { - const log = mockLog() + const log = mockLog(); return mockedDelivery(log).handleDelivery(mockMessage({ junk: 'message' })).then(function () { - assert.equal(log.warn.callCount, 1) - assert.equal(log.warn.args[0][0], 'emailHeaders.keys') - }) + assert.equal(log.warn.callCount, 1); + assert.equal(log.warn.args[0][0], 'emailHeaders.keys'); + }); } - ) + ); it( 'should log delivery', () => { - const log = mockLog() + const log = mockLog(); const mockMsg = mockMessage({ notificationType: 'Delivery', delivery: { @@ -79,26 +79,26 @@ describe('delivery messages', () => { } ] } - }) + }); return mockedDelivery(log).handleDelivery(mockMsg).then(function () { - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].domain, 'other') - assert.equal(log.info.args[0][1].type, 'delivered') - assert.equal(log.info.args[0][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[1][1].email, 'jane@example.com') - assert.equal(log.info.args[1][0], 'handleDelivery') - assert.equal(log.info.args[1][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[1][1].processingTimeMillis, 546) - }) + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].domain, 'other'); + assert.equal(log.info.args[0][1].type, 'delivered'); + assert.equal(log.info.args[0][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[1][1].email, 'jane@example.com'); + assert.equal(log.info.args[1][0], 'handleDelivery'); + assert.equal(log.info.args[1][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[1][1].processingTimeMillis, 546); + }); } - ) + ); it( 'should emit flow metrics', () => { - const log = mockLog() + const log = mockLog(); const mockMsg = mockMessage({ notificationType: 'Delivery', delivery: { @@ -129,30 +129,30 @@ describe('delivery messages', () => { } ] } - }) + }); return mockedDelivery(log).handleDelivery(mockMsg).then(function () { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.delivered') - assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId') - assert.equal(log.flowEvent.args[0][0].flow_time > 0, true) - assert.equal(log.flowEvent.args[0][0].time > 0, true) - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].domain, 'other') - assert.equal(log.info.args[0][1].type, 'delivered') - assert.equal(log.info.args[0][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[0][1].flow_id, 'someFlowId') - assert.equal(log.info.args[1][1].email, 'jane@example.com') - assert.equal(log.info.args[1][1].domain, 'other') - }) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.delivered'); + assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId'); + assert.equal(log.flowEvent.args[0][0].flow_time > 0, true); + assert.equal(log.flowEvent.args[0][0].time > 0, true); + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].domain, 'other'); + assert.equal(log.info.args[0][1].type, 'delivered'); + assert.equal(log.info.args[0][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[0][1].flow_id, 'someFlowId'); + assert.equal(log.info.args[1][1].email, 'jane@example.com'); + assert.equal(log.info.args[1][1].domain, 'other'); + }); } - ) + ); it( 'should log popular email domain', () => { - const log = mockLog() + const log = mockLog(); const mockMsg = mockMessage({ notificationType: 'Delivery', delivery: { @@ -183,24 +183,24 @@ describe('delivery messages', () => { } ] } - }) + }); return mockedDelivery(log).handleDelivery(mockMsg).then(function () { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.delivered') - assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId') - assert.equal(log.flowEvent.args[0][0].flow_time > 0, true) - assert.equal(log.flowEvent.args[0][0].time > 0, true) - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[0][0], 'emailEvent') - assert.equal(log.info.args[0][1].domain, 'aol.com') - assert.equal(log.info.args[0][1].type, 'delivered') - assert.equal(log.info.args[0][1].template, 'verifyLoginEmail') - assert.equal(log.info.args[0][1].locale, 'en') - assert.equal(log.info.args[0][1].flow_id, 'someFlowId') - assert.equal(log.info.args[1][1].email, 'jane@aol.com') - assert.equal(log.info.args[1][1].domain, 'aol.com') - }) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.flowEvent.args[0][0].event, 'email.verifyLoginEmail.delivered'); + assert.equal(log.flowEvent.args[0][0].flow_id, 'someFlowId'); + assert.equal(log.flowEvent.args[0][0].flow_time > 0, true); + assert.equal(log.flowEvent.args[0][0].time > 0, true); + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[0][0], 'emailEvent'); + assert.equal(log.info.args[0][1].domain, 'aol.com'); + assert.equal(log.info.args[0][1].type, 'delivered'); + assert.equal(log.info.args[0][1].template, 'verifyLoginEmail'); + assert.equal(log.info.args[0][1].locale, 'en'); + assert.equal(log.info.args[0][1].flow_id, 'someFlowId'); + assert.equal(log.info.args[1][1].email, 'jane@aol.com'); + assert.equal(log.info.args[1][1].domain, 'aol.com'); + }); } - ) -}) + ); +}); diff --git a/test/local/email/notifications.js b/test/local/email/notifications.js index d54e99bf..293c116c 100644 --- a/test/local/email/notifications.js +++ b/test/local/email/notifications.js @@ -2,60 +2,60 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const error = require(`${ROOT_DIR}/lib/error`) -const { mockLog } = require('../../mocks') -const notifications = require(`${ROOT_DIR}/lib/email/notifications`) -const P = require(`${ROOT_DIR}/lib/promise`) -const sinon = require('sinon') +const { assert } = require('chai'); +const error = require(`${ROOT_DIR}/lib/error`); +const { mockLog } = require('../../mocks'); +const notifications = require(`${ROOT_DIR}/lib/email/notifications`); +const P = require(`${ROOT_DIR}/lib/promise`); +const sinon = require('sinon'); -const SIX_HOURS = 1000 * 60 * 60 * 6 +const SIX_HOURS = 1000 * 60 * 60 * 6; describe('lib/email/notifications:', () => { - let now, del, log, queue, emailRecord, db + let now, del, log, queue, emailRecord, db; beforeEach(() => { - now = Date.now() - sinon.stub(Date, 'now').callsFake(() => now) - del = sinon.spy() - log = mockLog() + now = Date.now(); + sinon.stub(Date, 'now').callsFake(() => now); + del = sinon.spy(); + log = mockLog(); queue = { start: sinon.spy(), on: sinon.spy() - } + }; emailRecord = { emailVerified: false, createdAt: now - SIX_HOURS - 1 - } + }; db = { accountRecord: sinon.spy(() => P.resolve(emailRecord)), deleteAccount: sinon.spy(() => P.resolve()) - } - notifications(log, error)(queue, db) - }) + }; + notifications(log, error)(queue, db); + }); afterEach(() => { - Date.now.restore() - }) + Date.now.restore(); + }); it('called queue.start', () => { - assert.equal(queue.start.callCount, 1) - assert.lengthOf(queue.start.args[0], 0) - }) + assert.equal(queue.start.callCount, 1); + assert.lengthOf(queue.start.args[0], 0); + }); it('called queue.on', () => { - assert.equal(queue.on.callCount, 1) + assert.equal(queue.on.callCount, 1); - const args = queue.on.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'data') - assert.isFunction(args[1]) - assert.lengthOf(args[1], 1) - }) + const args = queue.on.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'data'); + assert.isFunction(args[1]); + assert.lengthOf(args[1], 1); + }); describe('bounce message:', () => { beforeEach(() => { @@ -73,26 +73,26 @@ describe('lib/email/notifications:', () => { bounce: { bouncedRecipients: [ 'wibble@example.com' ] } - }) - }) + }); + }); it('logged a flow event', () => { - assert.equal(log.flowEvent.callCount, 1) - const args = log.flowEvent.args[0] - assert.lengthOf(args, 1) + assert.equal(log.flowEvent.callCount, 1); + const args = log.flowEvent.args[0]; + assert.lengthOf(args, 1); assert.deepEqual(args[0], { event: 'email.bar.bounced', flow_id: 'foo', flow_time: 1, time: now - }) - }) + }); + }); it('logged an email event', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailEvent') + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailEvent'); assert.deepEqual(args[1], { bounced: true, domain: 'other', @@ -101,27 +101,27 @@ describe('lib/email/notifications:', () => { template: 'bar', templateVersion: 'baz', type: 'bounced' - }) - }) + }); + }); it('did not delete the account', () => { - assert.equal(db.accountRecord.callCount, 1) - const args = db.accountRecord.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], 'wibble@example.com') + assert.equal(db.accountRecord.callCount, 1); + const args = db.accountRecord.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'wibble@example.com'); - assert.equal(db.deleteAccount.callCount, 0) - }) + assert.equal(db.deleteAccount.callCount, 0); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - assert.lengthOf(del.args[0], 0) - }) + assert.equal(del.callCount, 1); + assert.lengthOf(del.args[0], 0); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('complaint message, 2 recipients:', () => { beforeEach(() => { @@ -138,37 +138,37 @@ describe('lib/email/notifications:', () => { complaint: { complainedRecipients: [ 'foo@example.com', 'pmbooth@gmail.com' ] } - }) - }) + }); + }); it('logged 2 flow events', () => { - assert.equal(log.flowEvent.callCount, 2) + assert.equal(log.flowEvent.callCount, 2); - let args = log.flowEvent.args[0] - assert.lengthOf(args, 1) + let args = log.flowEvent.args[0]; + assert.lengthOf(args, 1); assert.deepEqual(args[0], { event: 'email.blee.bounced', flow_id: 'wibble', flow_time: 2, time: now - }) + }); - args = log.flowEvent.args[1] - assert.lengthOf(args, 1) + args = log.flowEvent.args[1]; + assert.lengthOf(args, 1); assert.deepEqual(args[0], { event: 'email.blee.bounced', flow_id: 'wibble', flow_time: 2, time: now - }) - }) + }); + }); it('logged 2 email events', () => { - assert.equal(log.info.callCount, 2) + assert.equal(log.info.callCount, 2); - let args = log.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailEvent') + let args = log.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailEvent'); assert.deepEqual(args[1], { complaint: true, domain: 'other', @@ -177,11 +177,11 @@ describe('lib/email/notifications:', () => { template: 'blee', templateVersion: '', type: 'bounced' - }) + }); - args = log.info.args[1] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailEvent') + args = log.info.args[1]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailEvent'); assert.deepEqual(args[1], { complaint: true, domain: 'gmail.com', @@ -190,35 +190,35 @@ describe('lib/email/notifications:', () => { template: 'blee', templateVersion: '', type: 'bounced' - }) - }) + }); + }); it('did not delete the accounts', () => { - assert.equal(db.accountRecord.callCount, 2) + assert.equal(db.accountRecord.callCount, 2); - let args = db.accountRecord.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], 'foo@example.com') + let args = db.accountRecord.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'foo@example.com'); - args = db.accountRecord.args[1] - assert.lengthOf(args, 1) - assert.equal(args[0], 'pmbooth@gmail.com') + args = db.accountRecord.args[1]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'pmbooth@gmail.com'); - assert.equal(db.deleteAccount.callCount, 0) - }) + assert.equal(db.deleteAccount.callCount, 0); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) + assert.equal(del.callCount, 1); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('bounce message, 2 recipients, new unverified account:', () => { beforeEach(() => { - emailRecord.createdAt += 1 + emailRecord.createdAt += 1; return queue.on.args[0][1]({ del, mail: { @@ -233,65 +233,65 @@ describe('lib/email/notifications:', () => { bounce: { bouncedRecipients: [ 'wibble@example.com', 'blee@example.com' ] } - }) - }) + }); + }); it('logged events', () => { - assert.equal(log.flowEvent.callCount, 2) + assert.equal(log.flowEvent.callCount, 2); - assert.equal(log.info.callCount, 4) + assert.equal(log.info.callCount, 4); - let args = log.info.args[2] - assert.lengthOf(args, 2) - assert.equal(args[0], 'accountDeleted') + let args = log.info.args[2]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'accountDeleted'); assert.deepEqual(args[1], { emailVerified: false, createdAt: emailRecord.createdAt - }) + }); - args = log.info.args[3] - assert.lengthOf(args, 2) - assert.equal(args[0], 'accountDeleted') + args = log.info.args[3]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'accountDeleted'); assert.deepEqual(args[1], { emailVerified: false, createdAt: emailRecord.createdAt - }) - }) + }); + }); it('deleted the accounts', () => { - assert.equal(db.accountRecord.callCount, 2) + assert.equal(db.accountRecord.callCount, 2); - let args = db.accountRecord.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], 'wibble@example.com') + let args = db.accountRecord.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'wibble@example.com'); - args = db.accountRecord.args[1] - assert.lengthOf(args, 1) - assert.equal(args[0], 'blee@example.com') + args = db.accountRecord.args[1]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'blee@example.com'); - assert.equal(db.deleteAccount.callCount, 2) + assert.equal(db.deleteAccount.callCount, 2); - args = db.deleteAccount.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], emailRecord) + args = db.deleteAccount.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], emailRecord); - args = db.deleteAccount.args[1] - assert.lengthOf(args, 1) - assert.equal(args[0], emailRecord) - }) + args = db.deleteAccount.args[1]; + assert.lengthOf(args, 1); + assert.equal(args[0], emailRecord); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) + assert.equal(del.callCount, 1); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('complaint message, new unverified account:', () => { beforeEach(() => { - emailRecord.createdAt += 1 + emailRecord.createdAt += 1; return queue.on.args[0][1]({ del, mail: { @@ -305,32 +305,32 @@ describe('lib/email/notifications:', () => { complaint: { complainedRecipients: [ 'foo@example.com' ] } - }) - }) + }); + }); it('logged events', () => { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.info.callCount, 2) - }) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.info.callCount, 2); + }); it('deleted the account', () => { - assert.equal(db.accountRecord.callCount, 1) - assert.equal(db.deleteAccount.callCount, 1) - }) + assert.equal(db.accountRecord.callCount, 1); + assert.equal(db.deleteAccount.callCount, 1); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) + assert.equal(del.callCount, 1); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('bounce message, new verified account:', () => { beforeEach(() => { - emailRecord.createdAt += 1 - emailRecord.emailVerified = true + emailRecord.createdAt += 1; + emailRecord.emailVerified = true; return queue.on.args[0][1]({ del, mail: { @@ -345,31 +345,31 @@ describe('lib/email/notifications:', () => { bounce: { bouncedRecipients: [ 'wibble@example.com' ] } - }) - }) + }); + }); it('logged events', () => { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.info.callCount, 1) - }) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.info.callCount, 1); + }); it('did not delete the account', () => { - assert.equal(db.accountRecord.callCount, 1) - assert.equal(db.deleteAccount.callCount, 0) - }) + assert.equal(db.accountRecord.callCount, 1); + assert.equal(db.deleteAccount.callCount, 0); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) + assert.equal(del.callCount, 1); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('delivery message, new unverified account:', () => { beforeEach(() => { - emailRecord.createdAt += 1 + emailRecord.createdAt += 1; return queue.on.args[0][1]({ del, mail: { @@ -384,26 +384,26 @@ describe('lib/email/notifications:', () => { delivery: { recipients: [ 'wibble@example.com' ] } - }) - }) + }); + }); it('logged a flow event', () => { - assert.equal(log.flowEvent.callCount, 1) - const args = log.flowEvent.args[0] - assert.lengthOf(args, 1) + assert.equal(log.flowEvent.callCount, 1); + const args = log.flowEvent.args[0]; + assert.lengthOf(args, 1); assert.deepEqual(args[0], { event: 'email.bar.delivered', flow_id: 'foo', flow_time: 1, time: now - }) - }) + }); + }); it('logged an email event', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailEvent') + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailEvent'); assert.deepEqual(args[1], { domain: 'other', flow_id: 'foo', @@ -411,22 +411,22 @@ describe('lib/email/notifications:', () => { template: 'bar', templateVersion: 'baz', type: 'delivered' - }) - }) + }); + }); it('did not delete the account', () => { - assert.equal(db.accountRecord.callCount, 0) - assert.equal(db.deleteAccount.callCount, 0) - }) + assert.equal(db.accountRecord.callCount, 0); + assert.equal(db.deleteAccount.callCount, 0); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) + assert.equal(del.callCount, 1); + }); it('did not log an error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('missing headers:', () => { beforeEach(() => { @@ -436,29 +436,29 @@ describe('lib/email/notifications:', () => { bounce: { bouncedRecipients: [ 'wibble@example.com' ] } - }) - }) + }); + }); it('logged an error', () => { - assert.isAtLeast(log.error.callCount, 1) + assert.isAtLeast(log.error.callCount, 1); - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailHeaders.missing') + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailHeaders.missing'); assert.deepEqual(args[1], { origin: 'notification' - }) - }) + }); + }); it('did not log a flow event', () => { - assert.equal(log.flowEvent.callCount, 0) - }) + assert.equal(log.flowEvent.callCount, 0); + }); it('logged an email event', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'emailEvent') + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'emailEvent'); assert.deepEqual(args[1], { bounced: true, domain: 'other', @@ -466,16 +466,16 @@ describe('lib/email/notifications:', () => { template: '', templateVersion: '', type: 'bounced' - }) - }) + }); + }); it('did not delete the account', () => { - assert.equal(db.accountRecord.callCount, 1) - assert.equal(db.deleteAccount.callCount, 0) - }) + assert.equal(db.accountRecord.callCount, 1); + assert.equal(db.deleteAccount.callCount, 0); + }); it('called message.del', () => { - assert.equal(del.callCount, 1) - }) - }) -}) + assert.equal(del.callCount, 1); + }); + }); +}); diff --git a/test/local/email/utils.js b/test/local/email/utils.js index 2ebb1b7e..5df4471a 100644 --- a/test/local/email/utils.js +++ b/test/local/email/utils.js @@ -2,23 +2,23 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const P = require(`${ROOT_DIR}/lib/promise`) -const { mockLog } = require('../../mocks') -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const { assert } = require('chai'); +const P = require(`${ROOT_DIR}/lib/promise`); +const { mockLog } = require('../../mocks'); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); -const amplitude = sinon.spy() +const amplitude = sinon.spy(); const emailHelpers = proxyquire(`${ROOT_DIR}/lib/email/utils/helpers`, { '../../metrics/amplitude': () => amplitude -}) +}); describe('email utils helpers', () => { - afterEach(() => amplitude.resetHistory()) + afterEach(() => amplitude.resetHistory()); describe('getHeaderValue', () => { @@ -30,11 +30,11 @@ describe('email utils helpers', () => { value: 'en-US' }] } - } + }; - const value = emailHelpers.getHeaderValue('Content-Language', message) - assert.equal(value, message.mail.headers[0].value) - }) + const value = emailHelpers.getHeaderValue('Content-Language', message); + assert.equal(value, message.mail.headers[0].value); + }); it('works with message.headers', () => { @@ -43,43 +43,43 @@ describe('email utils helpers', () => { name: 'content-language', value: 'ru' }] - } + }; - const value = emailHelpers.getHeaderValue('Content-Language', message) - assert.equal(value, message.headers[0].value) - }) + const value = emailHelpers.getHeaderValue('Content-Language', message); + assert.equal(value, message.headers[0].value); + }); - }) + }); describe('logEmailEventSent', () => { it('should check headers case-insensitively', () => { - const log = mockLog() + const log = mockLog(); const message = { email: 'user@example.domain', template: 'verifyEmail', headers: { 'cOnTeNt-LaNgUaGe': 'ru' } - } - emailHelpers.logEmailEventSent(log, message) - assert.equal(log.info.callCount, 1) - assert.equal(log.info.args[0][1].locale, 'ru') - }) + }; + emailHelpers.logEmailEventSent(log, message); + assert.equal(log.info.callCount, 1); + assert.equal(log.info.args[0][1].locale, 'ru'); + }); it('should log an event per CC email', () => { - const log = mockLog() + const log = mockLog(); const message = { email: 'user@example.domain', ccEmails: ['noreply@gmail.com', 'noreply@yahoo.com'], template: 'verifyEmail' - } - emailHelpers.logEmailEventSent(log, message) - assert.equal(log.info.callCount, 3) - assert.equal(log.info.args[0][1].domain, 'other') - assert.equal(log.info.args[1][1].domain, 'gmail.com') - assert.equal(log.info.args[2][1].domain, 'yahoo.com') - }) - }) + }; + emailHelpers.logEmailEventSent(log, message); + assert.equal(log.info.callCount, 3); + assert.equal(log.info.args[0][1].domain, 'other'); + assert.equal(log.info.args[1][1].domain, 'gmail.com'); + assert.equal(log.info.args[2][1].domain, 'yahoo.com'); + }); + }); it('logEmailEventSent should call amplitude correctly', () => { emailHelpers.logEmailEventSent(mockLog(), { @@ -97,11 +97,11 @@ describe('email utils helpers', () => { service: 'ddd', templateVersion: 'eee', uid: 'fff' - }) - assert.equal(amplitude.callCount, 1) - const args = amplitude.args[0] - assert.equal(args.length, 4) - assert.equal(args[0], 'email.verifyEmail.sent') + }); + assert.equal(amplitude.callCount, 1); + const args = amplitude.args[0]; + assert.equal(args.length, 4); + assert.equal(args[0], 'email.verifyEmail.sent'); assert.deepEqual(args[1], { app: { devices: P.resolve([]), @@ -114,7 +114,7 @@ describe('email utils helpers', () => { auth: {}, query: {}, payload: {} - }) + }); assert.deepEqual(args[2], { email_domain: 'other', email_service: 'fxa-email-service', @@ -122,12 +122,12 @@ describe('email utils helpers', () => { service: 'ddd', templateVersion: 'eee', uid: 'fff' - }) - assert.equal(args[3].device_id, 'bbb') - assert.equal(args[3].flow_id, 'ccc') - assert.equal(args[3].flowBeginTime, 42) - assert.ok(args[3].time > Date.now() - 1000) - }) + }); + assert.equal(args[3].device_id, 'bbb'); + assert.equal(args[3].flow_id, 'ccc'); + assert.equal(args[3].flowBeginTime, 42); + assert.ok(args[3].time > Date.now() - 1000); + }); it('logEmailEventFromMessage should call amplitude correctly', () => { emailHelpers.logEmailEventFromMessage(mockLog(), { @@ -145,11 +145,11 @@ describe('email utils helpers', () => { { name: 'X-Template-Version', value: 42 }, { name: 'X-Uid', value: 'e' } ] - }, 'bounced', 'gmail') - assert.equal(amplitude.callCount, 1) - const args = amplitude.args[0] - assert.equal(args.length, 4) - assert.equal(args[0], 'email.verifyLoginEmail.bounced') + }, 'bounced', 'gmail'); + assert.equal(amplitude.callCount, 1); + const args = amplitude.args[0]; + assert.equal(args.length, 4); + assert.equal(args[0], 'email.verifyLoginEmail.bounced'); assert.deepEqual(args[1], { app: { devices: P.resolve([]), @@ -162,7 +162,7 @@ describe('email utils helpers', () => { auth: {}, query: {}, payload: {} - }) + }); assert.deepEqual(args[2], { email_domain: 'gmail', email_sender: 'wibble', @@ -170,39 +170,39 @@ describe('email utils helpers', () => { service: 'd', templateVersion: 42, uid: 'e' - }) - assert.equal(args[3].device_id, 'b') - assert.equal(args[3].flow_id, 'c') - assert.equal(args[3].flowBeginTime, 1) - }) + }); + assert.equal(args[3].device_id, 'b'); + assert.equal(args[3].flow_id, 'c'); + assert.equal(args[3].flowBeginTime, 1); + }); describe('logErrorIfHeadersAreWeirdOrMissing', () => { - let log + let log; beforeEach(() => { - log = mockLog() - }) + log = mockLog(); + }); it('logs an error if message.mail is missing', () => { - emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, {}, 'wibble') - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'emailHeaders.missing') + emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, {}, 'wibble'); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'emailHeaders.missing'); assert.deepEqual(log.error.args[0][1], { origin: 'wibble' - }) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.warn.callCount, 0); + }); it('logs an error if message.mail.headers is missing', () => { - emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: {} }, 'blee') - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'emailHeaders.missing') + emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: {} }, 'blee'); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'emailHeaders.missing'); assert.deepEqual(log.error.args[0][1], { origin: 'blee' - }) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.warn.callCount, 0); + }); it('does not log an error/warning if message.mail.headers is object and deviceId is set', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { @@ -211,10 +211,10 @@ describe('email utils helpers', () => { 'X-Device-Id': 'foo' } } - }) - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 0); + }); it('does not log an error/warning if message.mail.headers is object and deviceId is set (lowercase)', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { @@ -223,10 +223,10 @@ describe('email utils helpers', () => { 'x-device-id': 'bar' } } - }) - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 0); + }); it('does not log an error/warning if message.mail.headers is object and uid is set', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { @@ -235,10 +235,10 @@ describe('email utils helpers', () => { 'X-Uid': 'foo' } } - }) - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 0); + }); it('does not log an error/warning if message.mail.headers is object and uid is set (lowercase)', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { @@ -247,10 +247,10 @@ describe('email utils helpers', () => { 'x-uid': 'bar' } } - }) - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 0); + }); it('logs a warning if message.mail.headers is object and deviceId and uid are missing', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { @@ -262,54 +262,54 @@ describe('email utils helpers', () => { 'X-Zzz': 'qux' } } - }, 'wibble') - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 1) - assert.equal(log.warn.args[0].length, 2) - assert.equal(log.warn.args[0][0], 'emailHeaders.keys') + }, 'wibble'); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 1); + assert.equal(log.warn.args[0].length, 2); + assert.equal(log.warn.args[0][0], 'emailHeaders.keys'); assert.deepEqual(log.warn.args[0][1], { keys: 'X-Template-Name,X-Xxx,X-Yyy,X-Zzz', template: 'foo', origin: 'wibble' - }) - }) + }); + }); it('logs a warning if message.headers is object and deviceId and uid are missing', () => { emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { headers: { 'x-template-name': 'wibble' } - }, 'blee') - assert.equal(log.error.callCount, 0) - assert.equal(log.warn.callCount, 1) - assert.equal(log.warn.args[0][0], 'emailHeaders.keys') + }, 'blee'); + assert.equal(log.error.callCount, 0); + assert.equal(log.warn.callCount, 1); + assert.equal(log.warn.args[0][0], 'emailHeaders.keys'); assert.deepEqual(log.warn.args[0][1], { keys: 'x-template-name', template: 'wibble', origin: 'blee' - }) - }) + }); + }); it('logs an error if message.mail.headers is non-object', () => { - emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: { headers: 'foo' } }, 'wibble') - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'emailHeaders.weird') + emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: { headers: 'foo' } }, 'wibble'); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'emailHeaders.weird'); assert.deepEqual(log.error.args[0][1], { type: 'string', origin: 'wibble' - }) - assert.equal(log.warn.callCount, 0) - }) + }); + assert.equal(log.warn.callCount, 0); + }); it('logs an error if message.headers is non-object', () => { - emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: {}, headers: 42 }, 'wibble') - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'emailHeaders.weird') + emailHelpers.logErrorIfHeadersAreWeirdOrMissing(log, { mail: {}, headers: 42 }, 'wibble'); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'emailHeaders.weird'); assert.deepEqual(log.error.args[0][1], { type: 'number', origin: 'wibble' - }) - assert.equal(log.warn.callCount, 0) - }) - }) -}) + }); + assert.equal(log.warn.callCount, 0); + }); + }); +}); diff --git a/test/local/error.js b/test/local/error.js index afb3076e..f070dc4e 100644 --- a/test/local/error.js +++ b/test/local/error.js @@ -2,36 +2,36 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const messages = require('joi/lib/language') -const AppError = require('../../lib/error') -const P = require('../../lib/promise') +const { assert } = require('chai'); +const messages = require('joi/lib/language'); +const AppError = require('../../lib/error'); +const P = require('../../lib/promise'); describe('AppErrors', () => { it( 'tightly-coupled joi message hack is okay', () => { - assert.equal(typeof messages.errors.any.required, 'string') - assert.notEqual(messages.errors.any.required, '') + assert.equal(typeof messages.errors.any.required, 'string'); + assert.notEqual(messages.errors.any.required, ''); } - ) + ); it( 'exported functions exist', () => { - assert.equal(typeof AppError, 'function') - assert.equal(AppError.length, 3) - assert.equal(typeof AppError.translate, 'function') - assert.lengthOf(AppError.translate, 2) - assert.equal(typeof AppError.invalidRequestParameter, 'function') - assert.equal(AppError.invalidRequestParameter.length, 1) - assert.equal(typeof AppError.missingRequestParameter, 'function') - assert.equal(AppError.missingRequestParameter.length, 1) + assert.equal(typeof AppError, 'function'); + assert.equal(AppError.length, 3); + assert.equal(typeof AppError.translate, 'function'); + assert.lengthOf(AppError.translate, 2); + assert.equal(typeof AppError.invalidRequestParameter, 'function'); + assert.equal(AppError.invalidRequestParameter.length, 1); + assert.equal(typeof AppError.missingRequestParameter, 'function'); + assert.equal(AppError.missingRequestParameter.length, 1); } - ) + ); it( 'should translate with missing required parameters', @@ -45,17 +45,17 @@ describe('AppErrors', () => { } } } - }) - assert.ok(result instanceof AppError, 'instanceof AppError') - assert.equal(result.errno, 108) - assert.equal(result.message, 'Missing parameter in request body: bar') - assert.equal(result.output.statusCode, 400) - assert.equal(result.output.payload.error, 'Bad Request') - assert.equal(result.output.payload.errno, result.errno) - assert.equal(result.output.payload.message, result.message) - assert.equal(result.output.payload.param, 'bar') + }); + assert.ok(result instanceof AppError, 'instanceof AppError'); + assert.equal(result.errno, 108); + assert.equal(result.message, 'Missing parameter in request body: bar'); + assert.equal(result.output.statusCode, 400); + assert.equal(result.output.payload.error, 'Bad Request'); + assert.equal(result.output.payload.errno, result.errno); + assert.equal(result.output.payload.message, result.message); + assert.equal(result.output.payload.param, 'bar'); } - ) + ); it( 'should translate with invalid parameter', @@ -66,63 +66,63 @@ describe('AppErrors', () => { validation: 'foo' } } - }) - assert.ok(result instanceof AppError, 'instanceof AppError') - assert.equal(result.errno, 107) - assert.equal(result.message, 'Invalid parameter in request body') - assert.equal(result.output.statusCode, 400) - assert.equal(result.output.payload.error, 'Bad Request') - assert.equal(result.output.payload.errno, result.errno) - assert.equal(result.output.payload.message, result.message) - assert.equal(result.output.payload.validation, 'foo') + }); + assert.ok(result instanceof AppError, 'instanceof AppError'); + assert.equal(result.errno, 107); + assert.equal(result.message, 'Invalid parameter in request body'); + assert.equal(result.output.statusCode, 400); + assert.equal(result.output.payload.error, 'Bad Request'); + assert.equal(result.output.payload.errno, result.errno); + assert.equal(result.output.payload.message, result.message); + assert.equal(result.output.payload.validation, 'foo'); } - ) + ); it( 'should translate with missing payload', () => { var result = AppError.translate(null, { output: {} - }) - assert.ok(result instanceof AppError, 'instanceof AppError') - assert.equal(result.errno, 999) - assert.equal(result.message, 'Unspecified error') - assert.equal(result.output.statusCode, 500) - assert.equal(result.output.payload.error, 'Internal Server Error') - assert.equal(result.output.payload.errno, result.errno) - assert.equal(result.output.payload.message, result.message) + }); + assert.ok(result instanceof AppError, 'instanceof AppError'); + assert.equal(result.errno, 999); + assert.equal(result.message, 'Unspecified error'); + assert.equal(result.output.statusCode, 500); + assert.equal(result.output.payload.error, 'Internal Server Error'); + assert.equal(result.output.payload.errno, result.errno); + assert.equal(result.output.payload.message, result.message); } - ) + ); it( 'tooManyRequests', () => { - var result = AppError.tooManyRequests(900, 'in 15 minutes') - assert.ok(result instanceof AppError, 'instanceof AppError') - assert.equal(result.errno, 114) - assert.equal(result.message, 'Client has sent too many requests') - assert.equal(result.output.statusCode, 429) - assert.equal(result.output.payload.error, 'Too Many Requests') - assert.equal(result.output.payload.retryAfter, 900) - assert.equal(result.output.payload.retryAfterLocalized, 'in 15 minutes') + var result = AppError.tooManyRequests(900, 'in 15 minutes'); + assert.ok(result instanceof AppError, 'instanceof AppError'); + assert.equal(result.errno, 114); + assert.equal(result.message, 'Client has sent too many requests'); + assert.equal(result.output.statusCode, 429); + assert.equal(result.output.payload.error, 'Too Many Requests'); + assert.equal(result.output.payload.retryAfter, 900); + assert.equal(result.output.payload.retryAfterLocalized, 'in 15 minutes'); - result = AppError.tooManyRequests(900) - assert.equal(result.output.payload.retryAfter, 900) - assert(! result.output.payload.retryAfterLocalized) + result = AppError.tooManyRequests(900); + assert.equal(result.output.payload.retryAfter, 900); + assert(! result.output.payload.retryAfterLocalized); } - ) + ); it('unexpectedError without request data', () => { - const err = AppError.unexpectedError() - assert.instanceOf(err, AppError) - assert.instanceOf(err, Error) - assert.equal(err.errno, 999) - assert.equal(err.message, 'Unspecified error') - assert.equal(err.output.statusCode, 500) - assert.equal(err.output.payload.error, 'Internal Server Error') - assert.isUndefined(err.output.payload.request) - }) + const err = AppError.unexpectedError(); + assert.instanceOf(err, AppError); + assert.instanceOf(err, Error); + assert.equal(err.errno, 999); + assert.equal(err.message, 'Unspecified error'); + assert.equal(err.output.statusCode, 500); + assert.equal(err.output.payload.error, 'Internal Server Error'); + assert.isUndefined(err.output.payload.request); + }); it('unexpectedError with request data', () => { const err = AppError.unexpectedError({ @@ -159,11 +159,11 @@ describe('AppErrors', () => { headers: { wibble: 'blee' } - }) - assert.equal(err.errno, 999) - assert.equal(err.message, 'Unspecified error') - assert.equal(err.output.statusCode, 500) - assert.equal(err.output.payload.error, 'Internal Server Error') + }); + assert.equal(err.errno, 999); + assert.equal(err.message, 'Unspecified error'); + assert.equal(err.output.statusCode, 500); + assert.equal(err.output.payload.error, 'Internal Server Error'); assert.deepEqual(err.output.payload.request, { acceptLanguage: 'en, fr', locale: 'en', @@ -185,8 +185,8 @@ describe('AppErrors', () => { headers: { wibble: 'blee' } - }) - }) + }); + }); const reasons = ['socket hang up', 'ECONNREFUSED']; reasons.forEach((reason) => { @@ -199,15 +199,15 @@ describe('AppErrors', () => { } }, reason - }) + }); - assert.ok(result instanceof AppError, 'instanceof AppError') - assert.equal(result.errno, 203) - assert.equal(result.message, 'A backend service request failed.') - assert.equal(result.output.statusCode, 500) - assert.equal(result.output.payload.error, 'Internal Server Error') - assert.equal(result.output.payload.errno, AppError.ERRNO.BACKEND_SERVICE_FAILURE) - assert.equal(result.output.payload.message, 'A backend service request failed.') - }) - }) -}) + assert.ok(result instanceof AppError, 'instanceof AppError'); + assert.equal(result.errno, 203); + assert.equal(result.message, 'A backend service request failed.'); + assert.equal(result.output.statusCode, 500); + assert.equal(result.output.payload.error, 'Internal Server Error'); + assert.equal(result.output.payload.errno, AppError.ERRNO.BACKEND_SERVICE_FAILURE); + assert.equal(result.output.payload.message, 'A backend service request failed.'); + }); + }); +}); diff --git a/test/local/features.js b/test/local/features.js index a6bc1f3a..0622a2de 100644 --- a/test/local/features.js +++ b/test/local/features.js @@ -2,179 +2,179 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') -const proxyquire = require('proxyquire') +const { assert } = require('chai'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); -let hashResult = Array(40).fill('0') +let hashResult = Array(40).fill('0'); const hash = { update: sinon.spy(), digest: sinon.spy(() => hashResult) -} +}; const crypto = { createHash: sinon.spy(() => hash) -} +}; const config = { lastAccessTimeUpdates: {}, signinConfirmation: {}, signinUnblock: {}, securityHistory: {} -} +}; -const MODULE_PATH = '../../lib/features' +const MODULE_PATH = '../../lib/features'; const features = proxyquire(MODULE_PATH, { crypto: crypto -})(config) +})(config); describe('features', () => { it( 'interface is correct', () => { - assert.equal(typeof require(MODULE_PATH).schema, 'object', 'features.schema is object') - assert.notEqual(require(MODULE_PATH).schema, null, 'features.schema is not null') + assert.equal(typeof require(MODULE_PATH).schema, 'object', 'features.schema is object'); + assert.notEqual(require(MODULE_PATH).schema, null, 'features.schema is not null'); - assert.equal(typeof features, 'object', 'object type should be exported') - assert.equal(Object.keys(features).length, 2, 'object should have correct number of properties') - assert.equal(typeof features.isSampledUser, 'function', 'isSampledUser should be function') - assert.equal(typeof features.isLastAccessTimeEnabledForUser, 'function', 'isLastAccessTimeEnabledForUser should be function') + assert.equal(typeof features, 'object', 'object type should be exported'); + assert.equal(Object.keys(features).length, 2, 'object should have correct number of properties'); + assert.equal(typeof features.isSampledUser, 'function', 'isSampledUser should be function'); + assert.equal(typeof features.isLastAccessTimeEnabledForUser, 'function', 'isLastAccessTimeEnabledForUser should be function'); - assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once on require') - let args = crypto.createHash.args[0] - assert.equal(args.length, 1, 'crypto.createHash should have been passed one argument') - assert.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1') + assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once on require'); + let args = crypto.createHash.args[0]; + assert.equal(args.length, 1, 'crypto.createHash should have been passed one argument'); + assert.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1'); - assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice on require') - args = hash.update.args[0] - assert.equal(args.length, 1, 'hash.update should have been passed one argument first time') - assert.equal(typeof args[0], 'string', 'hash.update data should have been a string first time') - args = hash.update.args[1] - assert.equal(args.length, 1, 'hash.update should have been passed one argument second time') - assert.equal(typeof args[0], 'string', 'hash.update data should have been a string second time') + assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice on require'); + args = hash.update.args[0]; + assert.equal(args.length, 1, 'hash.update should have been passed one argument first time'); + assert.equal(typeof args[0], 'string', 'hash.update data should have been a string first time'); + args = hash.update.args[1]; + assert.equal(args.length, 1, 'hash.update should have been passed one argument second time'); + assert.equal(typeof args[0], 'string', 'hash.update data should have been a string second time'); - assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once on require') - args = hash.digest.args[0] - assert.equal(args.length, 1, 'hash.digest should have been passed one argument') - assert.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex') + assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once on require'); + args = hash.digest.args[0]; + assert.equal(args.length, 1, 'hash.digest should have been passed one argument'); + assert.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex'); - crypto.createHash.resetHistory() - hash.update.resetHistory() - hash.digest.resetHistory() + crypto.createHash.resetHistory(); + hash.update.resetHistory(); + hash.digest.resetHistory(); } - ) + ); it( 'isSampledUser', () => { - let uid = Array(64).fill('f').join('') - let sampleRate = 1 - hashResult = Array(40).fill('f').join('') + let uid = Array(64).fill('f').join(''); + let sampleRate = 1; + hashResult = Array(40).fill('f').join(''); - assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should always return true if sample rate is 1') + assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should always return true if sample rate is 1'); - assert.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called') - assert.equal(hash.update.callCount, 0, 'hash.update should not have been called') - assert.equal(hash.digest.callCount, 0, 'hash.digest should not have been called') + assert.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called'); + assert.equal(hash.update.callCount, 0, 'hash.update should not have been called'); + assert.equal(hash.digest.callCount, 0, 'hash.digest should not have been called'); - sampleRate = 0 - hashResult = Array(40).fill('0').join('') + sampleRate = 0; + hashResult = Array(40).fill('0').join(''); - assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should always return false if sample rate is 0') + assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should always return false if sample rate is 0'); - assert.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called') - assert.equal(hash.update.callCount, 0, 'hash.update should not have been called') - assert.equal(hash.digest.callCount, 0, 'hash.digest should not have been called') + assert.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called'); + assert.equal(hash.update.callCount, 0, 'hash.update should not have been called'); + assert.equal(hash.digest.callCount, 0, 'hash.digest should not have been called'); - sampleRate = 0.05 + sampleRate = 0.05; // First 27 characters are ignored, last 13 are 0.04 * 0xfffffffffffff - hashResult = '0000000000000000000000000000a3d70a3d70a6' + hashResult = '0000000000000000000000000000a3d70a3d70a6'; - assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should return true if sample rate is greater than the extracted cohort value') + assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should return true if sample rate is greater than the extracted cohort value'); - assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once') - let args = crypto.createHash.args[0] - assert.equal(args.length, 1, 'crypto.createHash should have been passed one argument') - assert.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1') + assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once'); + let args = crypto.createHash.args[0]; + assert.equal(args.length, 1, 'crypto.createHash should have been passed one argument'); + assert.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1'); - assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice') - args = hash.update.args[0] - assert.equal(args.length, 1, 'hash.update should have been passed one argument first time') - assert.equal(args[0], uid.toString('hex'), 'hash.update data should have been stringified uid first time') - args = hash.update.args[1] - assert.equal(args.length, 1, 'hash.update should have been passed one argument second time') - assert.equal(args[0], 'foo', 'hash.update data should have been key second time') + assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice'); + args = hash.update.args[0]; + assert.equal(args.length, 1, 'hash.update should have been passed one argument first time'); + assert.equal(args[0], uid.toString('hex'), 'hash.update data should have been stringified uid first time'); + args = hash.update.args[1]; + assert.equal(args.length, 1, 'hash.update should have been passed one argument second time'); + assert.equal(args[0], 'foo', 'hash.update data should have been key second time'); - assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once') - args = hash.digest.args[0] - assert.equal(args.length, 1, 'hash.digest should have been passed one argument') - assert.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex') + assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once'); + args = hash.digest.args[0]; + assert.equal(args.length, 1, 'hash.digest should have been passed one argument'); + assert.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex'); - crypto.createHash.resetHistory() - hash.update.resetHistory() - hash.digest.resetHistory() + crypto.createHash.resetHistory(); + hash.update.resetHistory(); + hash.digest.resetHistory(); - sampleRate = 0.04 + sampleRate = 0.04; - assert.equal(features.isSampledUser(sampleRate, uid, 'bar'), false, 'should return false if sample rate is equal to the extracted cohort value') + assert.equal(features.isSampledUser(sampleRate, uid, 'bar'), false, 'should return false if sample rate is equal to the extracted cohort value'); - assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once') - assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice') - assert.equal(hash.update.args[0][0], uid.toString('hex'), 'hash.update data should have been stringified uid first time') - assert.equal(hash.update.args[1][0], 'bar', 'hash.update data should have been key second time') - assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once') + assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once'); + assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice'); + assert.equal(hash.update.args[0][0], uid.toString('hex'), 'hash.update data should have been stringified uid first time'); + assert.equal(hash.update.args[1][0], 'bar', 'hash.update data should have been key second time'); + assert.equal(hash.digest.callCount, 1, 'hash.digest should have been called once'); - crypto.createHash.resetHistory() - hash.update.resetHistory() - hash.digest.resetHistory() + crypto.createHash.resetHistory(); + hash.update.resetHistory(); + hash.digest.resetHistory(); - sampleRate = 0.03 + sampleRate = 0.03; - assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should return false if sample rate is less than the extracted cohort value') + assert.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should return false if sample rate is less than the extracted cohort value'); - crypto.createHash.resetHistory() - hash.update.resetHistory() - hash.digest.resetHistory() + crypto.createHash.resetHistory(); + hash.update.resetHistory(); + hash.digest.resetHistory(); - uid = Array(64).fill('7').join('') - sampleRate = 0.03 + uid = Array(64).fill('7').join(''); + sampleRate = 0.03; // First 27 characters are ignored, last 13 are 0.02 * 0xfffffffffffff - hashResult = '000000000000000000000000000051eb851eb852' + hashResult = '000000000000000000000000000051eb851eb852'; - assert.equal(features.isSampledUser(sampleRate, uid, 'wibble'), true, 'should return true if sample rate is greater than the extracted cohort value') + assert.equal(features.isSampledUser(sampleRate, uid, 'wibble'), true, 'should return true if sample rate is greater than the extracted cohort value'); - assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice') - assert.equal(hash.update.args[0][0], uid, 'hash.update data should have been stringified uid first time') - assert.equal(hash.update.args[1][0], 'wibble', 'hash.update data should have been key second time') + assert.equal(hash.update.callCount, 2, 'hash.update should have been called twice'); + assert.equal(hash.update.args[0][0], uid, 'hash.update data should have been stringified uid first time'); + assert.equal(hash.update.args[1][0], 'wibble', 'hash.update data should have been key second time'); - crypto.createHash.resetHistory() - hash.update.resetHistory() - hash.digest.resetHistory() + crypto.createHash.resetHistory(); + hash.update.resetHistory(); + hash.digest.resetHistory(); } - ) + ); it( 'isLastAccessTimeEnabledForUser', () => { - const uid = 'foo' - const email = 'bar@mozilla.com' + const uid = 'foo'; + const email = 'bar@mozilla.com'; // First 27 characters are ignored, last 13 are 0.02 * 0xfffffffffffff - hashResult = '000000000000000000000000000051eb851eb852' + hashResult = '000000000000000000000000000051eb851eb852'; - config.lastAccessTimeUpdates.enabled = true - config.lastAccessTimeUpdates.sampleRate = 0 + config.lastAccessTimeUpdates.enabled = true; + config.lastAccessTimeUpdates.sampleRate = 0; - config.lastAccessTimeUpdates.sampleRate = 0.03 - assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), true, 'should return true when sample rate matches') + config.lastAccessTimeUpdates.sampleRate = 0.03; + assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), true, 'should return true when sample rate matches'); - config.lastAccessTimeUpdates.sampleRate = 0.02 - assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when sample rate does not match') + config.lastAccessTimeUpdates.sampleRate = 0.02; + assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when sample rate does not match'); - config.lastAccessTimeUpdates.enabled = false - config.lastAccessTimeUpdates.sampleRate = 0.03 - assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when feature is disabled') + config.lastAccessTimeUpdates.enabled = false; + config.lastAccessTimeUpdates.sampleRate = 0.03; + assert.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when feature is disabled'); } - ) -}) + ); +}); diff --git a/test/local/geodb.js b/test/local/geodb.js index 3ccac5e7..5bfd336a 100644 --- a/test/local/geodb.js +++ b/test/local/geodb.js @@ -2,13 +2,13 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const knownIpLocation = require('../known-ip-location') -const proxyquire = require('proxyquire') -const mockLog = require('../mocks').mockLog -const modulePath = '../../lib/geodb' +const { assert } = require('chai'); +const knownIpLocation = require('../known-ip-location'); +const proxyquire = require('proxyquire'); +const mockLog = require('../mocks').mockLog; +const modulePath = '../../lib/geodb'; describe('geodb', () => { it( @@ -20,23 +20,23 @@ describe('geodb', () => { if (item === 'geodb') { return { enabled: true - } + }; } } } - } - const thisMockLog = mockLog({}) + }; + const thisMockLog = mockLog({}); - const getGeoData = proxyquire(modulePath, moduleMocks)(thisMockLog) - const geoData = getGeoData(knownIpLocation.ip) - assert.ok(knownIpLocation.location.city.has(geoData.location.city)) - assert.equal(geoData.location.country, knownIpLocation.location.country) - assert.equal(geoData.location.countryCode, knownIpLocation.location.countryCode) - assert.equal(geoData.timeZone, knownIpLocation.location.tz) - assert.equal(geoData.location.state, knownIpLocation.location.state) - assert.equal(geoData.location.stateCode, knownIpLocation.location.stateCode) + const getGeoData = proxyquire(modulePath, moduleMocks)(thisMockLog); + const geoData = getGeoData(knownIpLocation.ip); + assert.ok(knownIpLocation.location.city.has(geoData.location.city)); + assert.equal(geoData.location.country, knownIpLocation.location.country); + assert.equal(geoData.location.countryCode, knownIpLocation.location.countryCode); + assert.equal(geoData.timeZone, knownIpLocation.location.tz); + assert.equal(geoData.location.state, knownIpLocation.location.state); + assert.equal(geoData.location.stateCode, knownIpLocation.location.stateCode); } - ) + ); it( 'returns empty object data when disabled', @@ -47,16 +47,16 @@ describe('geodb', () => { if (item === 'geodb') { return { enabled: false - } + }; } } } - } - const thisMockLog = mockLog({}) + }; + const thisMockLog = mockLog({}); - const getGeoData = proxyquire(modulePath, moduleMocks)(thisMockLog) - const geoData = getGeoData('8.8.8.8') - assert.deepEqual(geoData, {}) + const getGeoData = proxyquire(modulePath, moduleMocks)(thisMockLog); + const geoData = getGeoData('8.8.8.8'); + assert.deepEqual(geoData, {}); } - ) -}) + ); +}); diff --git a/test/local/ip_profiling.js b/test/local/ip_profiling.js index 41bb7581..c5bcda9f 100644 --- a/test/local/ip_profiling.js +++ b/test/local/ip_profiling.js @@ -2,22 +2,22 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const getRoute = require('../routes_helpers').getRoute -const mocks = require('../mocks') -const P = require('../../lib/promise') -const proxyquire = require('proxyquire') -const uuid = require('uuid') +const { assert } = require('chai'); +const crypto = require('crypto'); +const getRoute = require('../routes_helpers').getRoute; +const mocks = require('../mocks'); +const P = require('../../lib/promise'); +const proxyquire = require('proxyquire'); +const uuid = require('uuid'); -const TEST_EMAIL = 'foo@gmail.com' -const MS_ONE_DAY = 1000 * 60 * 60 * 24 -const UID = uuid.v4('binary').toString('hex') +const TEST_EMAIL = 'foo@gmail.com'; +const MS_ONE_DAY = 1000 * 60 * 60 * 24; +const UID = uuid.v4('binary').toString('hex'); function makeRoutes (options = {}, requireMocks) { - const { db, mailer } = options + const { db, mailer } = options; const config = { securityHistory: { ipProfiling: { @@ -26,14 +26,14 @@ function makeRoutes (options = {}, requireMocks) { }, signinConfirmation: {}, smtp: {} - } - const log = mocks.mockLog() + }; + const log = mocks.mockLog(); const customs = { - check () { return P.resolve(true) }, + check () { return P.resolve(true); }, flag () {} - } - const signinUtils = require('../../lib/routes/utils/signin')(log, config, customs, db, mailer) - signinUtils.checkPassword = () => P.resolve(true) + }; + const signinUtils = require('../../lib/routes/utils/signin')(log, config, customs, db, mailer); + signinUtils.checkPassword = () => P.resolve(true); return proxyquire('../../lib/routes/account', requireMocks || {})( log, db, @@ -44,30 +44,30 @@ function makeRoutes (options = {}, requireMocks) { signinUtils, mocks.mockPush(), mocks.mockDevices() - ) + ); } function runTest(route, request, assertions) { return new P(function (resolve, reject) { try { - return route.handler(request).then(resolve, reject) + return route.handler(request).then(resolve, reject); } catch (err) { - reject(err) + reject(err); } }) - .then(assertions) + .then(assertions); } describe('IP Profiling', () => { - let route, accountRoutes, mockDB, mockMailer, mockRequest + let route, accountRoutes, mockDB, mockMailer, mockRequest; beforeEach(() => { mockDB = mocks.mockDB({ email: TEST_EMAIL, emailVerified: true, uid: UID - }) - mockMailer = mocks.mockMailer() + }); + mockMailer = mocks.mockMailer(); mockRequest = mocks.mockRequest({ payload: { authPW: crypto.randomBytes(32).toString('hex'), @@ -82,13 +82,13 @@ describe('IP Profiling', () => { query: { keys: 'true' } - }) + }); accountRoutes = makeRoutes({ db: mockDB, mailer: mockMailer - }) - route = getRoute(accountRoutes, '/account/login') - }) + }); + route = getRoute(accountRoutes, '/account/login'); + }); it( 'no previously verified session', @@ -100,15 +100,15 @@ describe('IP Profiling', () => { createdAt: Date.now(), verified: false } - ]) - } + ]); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session not verified') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session not verified'); + }); + }); it( 'previously verified session', @@ -120,15 +120,15 @@ describe('IP Profiling', () => { createdAt: Date.now(), verified: true } - ]) - } + ]); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called') - assert.equal(response.verified, true, 'session verified') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called'); + assert.equal(response.verified, true, 'session verified'); + }); + }); it( 'previously verified session more than a day', @@ -141,21 +141,21 @@ describe('IP Profiling', () => { createdAt: (Date.now() - MS_ONE_DAY * 2), // Created two days ago verified: true } - ]) - } + ]); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session verified') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session verified'); + }); + }); it( 'previously verified session with forced sign-in confirmation', () => { - var forceSigninEmail = 'forcedemail@mozilla.com' - mockRequest.payload.email = forceSigninEmail + var forceSigninEmail = 'forcedemail@mozilla.com'; + mockRequest.payload.email = forceSigninEmail; mockDB.accountRecord = function () { return P.resolve({ @@ -166,42 +166,42 @@ describe('IP Profiling', () => { primaryEmail: {normalizedEmail: forceSigninEmail, email: forceSigninEmail, isVerified: true, isPrimary: true}, kA: crypto.randomBytes(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: UID, wrapWrapKb: crypto.randomBytes(32) - }) - } + }); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session verified') - return runTest(route, mockRequest) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session verified'); + return runTest(route, mockRequest); }) .then(function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 2, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session verified') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 2, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session verified'); + }); + }); it( 'previously verified session with suspicious request', () => { - mockRequest.app.clientAddress = '63.245.221.32' - mockRequest.app.isSuspiciousRequest = true + mockRequest.app.clientAddress = '63.245.221.32'; + mockRequest.app.isSuspiciousRequest = true; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session verified') - return runTest(route, mockRequest) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session verified'); + return runTest(route, mockRequest); }) .then(function (response) { - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 2, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'session verified') - }) - }) -}) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 2, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'session verified'); + }); + }); +}); diff --git a/test/local/log.js b/test/local/log.js index 675b117f..b0a0be05 100644 --- a/test/local/log.js +++ b/test/local/log.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') -const proxyquire = require('proxyquire') +const { assert } = require('chai'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); -const { mockRequest, mockMetricsContext } = require('../mocks') +const { mockRequest, mockMetricsContext } = require('../mocks'); describe('log', () => { - let logger, mocks, log + let logger, mocks, log; beforeEach(() => { logger = { @@ -21,7 +21,7 @@ describe('log', () => { critical: sinon.spy(), warn: sinon.spy(), info: sinon.spy() - } + }; mocks = { '../config': { get (name) { @@ -29,66 +29,66 @@ describe('log', () => { case 'log': return { fmt: 'mozlog' - } + }; case 'oauth.clientIds': return { clientid: 'human readable name' - } + }; default: - throw new Error(`unexpected config get: ${name}`) + throw new Error(`unexpected config get: ${name}`); } } }, // These need to be `function` functions, not arrow functions, // otherwise proxyquire gets confused and errors out. - mozlog: sinon.spy(function () { return logger }), - './notifier': function () { return { send: sinon.spy() } } - } - mocks.mozlog.config = sinon.spy() + mozlog: sinon.spy(function () { return logger; }), + './notifier': function () { return { send: sinon.spy() }; } + }; + mocks.mozlog.config = sinon.spy(); log = proxyquire('../../lib/log', mocks)({ level: 'debug', name: 'test', stdout: { on: sinon.spy() } - }) - }) + }); + }); it( 'initialised correctly', () => { - assert.equal(mocks.mozlog.config.callCount, 1, 'mozlog.config was called once') - var args = mocks.mozlog.config.args[0] - assert.equal(args.length, 1, 'mozlog.config was passed one argument') - assert.equal(Object.keys(args[0]).length, 4, 'number of mozlog.config arguments was correct') - assert.equal(args[0].app, 'test', 'app property was correct') - assert.equal(args[0].level, 'debug', 'level property was correct') - assert.equal(args[0].stream, process.stderr, 'stream property was correct') + assert.equal(mocks.mozlog.config.callCount, 1, 'mozlog.config was called once'); + var args = mocks.mozlog.config.args[0]; + assert.equal(args.length, 1, 'mozlog.config was passed one argument'); + assert.equal(Object.keys(args[0]).length, 4, 'number of mozlog.config arguments was correct'); + assert.equal(args[0].app, 'test', 'app property was correct'); + assert.equal(args[0].level, 'debug', 'level property was correct'); + assert.equal(args[0].stream, process.stderr, 'stream property was correct'); - assert.equal(mocks.mozlog.callCount, 1, 'mozlog was called once') - assert.ok(mocks.mozlog.config.calledBefore(mocks.mozlog), 'mozlog was called after mozlog.config') - assert.equal(mocks.mozlog.args[0].length, 0, 'mozlog was passed no arguments') + assert.equal(mocks.mozlog.callCount, 1, 'mozlog was called once'); + assert.ok(mocks.mozlog.config.calledBefore(mocks.mozlog), 'mozlog was called after mozlog.config'); + assert.equal(mocks.mozlog.args[0].length, 0, 'mozlog was passed no arguments'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - assert.equal(logger.info.callCount, 0, 'logger.info was not called') + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); - assert.equal(typeof log.trace, 'function', 'log.trace method was exported') - assert.equal(typeof log.error, 'function', 'log.error method was exported') - assert.equal(typeof log.fatal, 'function', 'log.fatal method was exported') - assert.equal(typeof log.warn, 'function', 'log.warn method was exported') - assert.equal(typeof log.info, 'function', 'log.info method was exported') - assert.equal(typeof log.begin, 'function', 'log.begin method was exported') - assert.equal(typeof log.notifyAttachedServices, 'function', 'log.notifyAttachedServices method was exported') - assert.equal(typeof log.activityEvent, 'function', 'log.activityEvent method was exported') - assert.equal(log.activityEvent.length, 1, 'log.activityEvent expects 1 argument') - assert.equal(typeof log.flowEvent, 'function', 'log.flowEvent method was exported') - assert.equal(log.flowEvent.length, 1, 'log.flowEvent expects 1 argument') - assert.equal(typeof log.amplitudeEvent, 'function', 'log.amplitudeEvent method was exported') - assert.equal(log.amplitudeEvent.length, 1, 'log.amplitudeEvent expects 1 argument') - assert.equal(typeof log.summary, 'function', 'log.summary method was exported') + assert.equal(typeof log.trace, 'function', 'log.trace method was exported'); + assert.equal(typeof log.error, 'function', 'log.error method was exported'); + assert.equal(typeof log.fatal, 'function', 'log.fatal method was exported'); + assert.equal(typeof log.warn, 'function', 'log.warn method was exported'); + assert.equal(typeof log.info, 'function', 'log.info method was exported'); + assert.equal(typeof log.begin, 'function', 'log.begin method was exported'); + assert.equal(typeof log.notifyAttachedServices, 'function', 'log.notifyAttachedServices method was exported'); + assert.equal(typeof log.activityEvent, 'function', 'log.activityEvent method was exported'); + assert.equal(log.activityEvent.length, 1, 'log.activityEvent expects 1 argument'); + assert.equal(typeof log.flowEvent, 'function', 'log.flowEvent method was exported'); + assert.equal(log.flowEvent.length, 1, 'log.flowEvent expects 1 argument'); + assert.equal(typeof log.amplitudeEvent, 'function', 'log.amplitudeEvent method was exported'); + assert.equal(log.amplitudeEvent.length, 1, 'log.amplitudeEvent expects 1 argument'); + assert.equal(typeof log.summary, 'function', 'log.summary method was exported'); } - ) + ); it( '.activityEvent', @@ -96,91 +96,91 @@ describe('log', () => { log.activityEvent({ event: 'foo', uid: 'bar' - }) + }); - assert.equal(logger.info.callCount, 1, 'logger.info was called once') - const args = logger.info.args[0] - assert.equal(args.length, 2, 'logger.info was passed two arguments') - assert.equal(args[0], 'activityEvent', 'first argument was correct') + assert.equal(logger.info.callCount, 1, 'logger.info was called once'); + const args = logger.info.args[0]; + assert.equal(args.length, 2, 'logger.info was passed two arguments'); + assert.equal(args[0], 'activityEvent', 'first argument was correct'); assert.deepEqual(args[1], { event: 'foo', uid: 'bar' - }, 'second argument was event data') + }, 'second argument was event data'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.activityEvent with missing data', () => { - log.activityEvent() + log.activityEvent(); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'log.activityEvent', 'first argument was function name') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'log.activityEvent', 'first argument was function name'); assert.deepEqual(args[1], { data: undefined - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.activityEvent with missing uid', () => { log.activityEvent({ event: 'wibble' - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'log.activityEvent', 'first argument was function name') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'log.activityEvent', 'first argument was function name'); assert.deepEqual(args[1], { data: { event: 'wibble' } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.activityEvent with missing event', () => { log.activityEvent({ uid: 'wibble' - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'log.activityEvent', 'first argument was function name') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'log.activityEvent', 'first argument was function name'); assert.deepEqual(args[1], { data: { uid: 'wibble' } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.flowEvent', @@ -190,45 +190,45 @@ describe('log', () => { flow_id: 'blee', flow_time: 1000, time: 1483557217331 - }) + }); - assert.equal(logger.info.callCount, 1, 'logger.info was called once') - const args = logger.info.args[0] - assert.equal(args.length, 2, 'logger.info was passed two arguments') - assert.equal(args[0], 'flowEvent', 'first argument was correct') + assert.equal(logger.info.callCount, 1, 'logger.info was called once'); + const args = logger.info.args[0]; + assert.equal(args.length, 2, 'logger.info was passed two arguments'); + assert.equal(args[0], 'flowEvent', 'first argument was correct'); assert.deepEqual(args[1], { event: 'wibble', flow_id: 'blee', flow_time: 1000, time: 1483557217331 - }, 'second argument was event data') + }, 'second argument was event data'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); } - ) + ); it( '.flowEvent with missing data', () => { - log.flowEvent() + log.flowEvent(); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'flow.missingData', 'first argument was op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'flow.missingData', 'first argument was op'); assert.deepEqual(args[1], { data: undefined - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.flowEvent with missing event', @@ -237,26 +237,26 @@ describe('log', () => { flow_id: 'wibble', flow_time: 1000, time: 1483557217331 - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'flow.missingData', 'first argument was op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'flow.missingData', 'first argument was op'); assert.deepEqual(args[1], { data: { flow_id: 'wibble', flow_time: 1000, time: 1483557217331 } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.flowEvent with missing flow_id', @@ -265,26 +265,26 @@ describe('log', () => { event: 'wibble', flow_time: 1000, time: 1483557217331 - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'flow.missingData', 'first argument was op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'flow.missingData', 'first argument was op'); assert.deepEqual(args[1], { data: { event: 'wibble', flow_time: 1000, time: 1483557217331 } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.flowEvent with missing flow_time', @@ -293,26 +293,26 @@ describe('log', () => { event: 'wibble', flow_id: 'blee', time: 1483557217331 - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'flow.missingData', 'first argument was op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'flow.missingData', 'first argument was op'); assert.deepEqual(args[1], { data: { event: 'wibble', flow_id: 'blee', time: 1483557217331 } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it( '.flowEvent with missing time', @@ -321,151 +321,151 @@ describe('log', () => { event: 'wibble', flow_id: 'blee', flow_time: 1000 - }) + }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'flow.missingData', 'first argument was op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'flow.missingData', 'first argument was op'); assert.deepEqual(args[1], { data: { event: 'wibble', flow_id: 'blee', flow_time: 1000 } - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); } - ) + ); it('.amplitudeEvent', () => { - log.amplitudeEvent({ event_type: 'foo', device_id: 'bar', user_id: 'baz' }) + log.amplitudeEvent({ event_type: 'foo', device_id: 'bar', user_id: 'baz' }); - assert.equal(logger.info.callCount, 1, 'logger.info was called once') - const args = logger.info.args[0] - assert.equal(args.length, 2, 'logger.info was passed two arguments') - assert.equal(args[0], 'amplitudeEvent', 'first argument was correct') + assert.equal(logger.info.callCount, 1, 'logger.info was called once'); + const args = logger.info.args[0]; + assert.equal(args.length, 2, 'logger.info was passed two arguments'); + assert.equal(args[0], 'amplitudeEvent', 'first argument was correct'); assert.deepEqual(args[1], { event_type: 'foo', device_id: 'bar', user_id: 'baz' - }, 'second argument was event data') + }, 'second argument was event data'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it('.amplitudeEvent with missing data', () => { - log.amplitudeEvent() + log.amplitudeEvent(); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'amplitude.missingData', 'first argument was error op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'amplitude.missingData', 'first argument was error op'); assert.deepEqual(args[1], { data: undefined - }, 'second argument was correct') + }, 'second argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it('.amplitudeEvent with missing event_type', () => { - log.amplitudeEvent({ device_id: 'foo', user_id: 'bar' }) + log.amplitudeEvent({ device_id: 'foo', user_id: 'bar' }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'amplitude.missingData', 'first argument was error op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'amplitude.missingData', 'first argument was error op'); assert.deepEqual(args[1], { data: { device_id: 'foo', user_id: 'bar' } - }, 'second argument was correct') + }, 'second argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it('.amplitudeEvent with missing device_id and user_id', () => { - log.amplitudeEvent({ event_type: 'foo' }) + log.amplitudeEvent({ event_type: 'foo' }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - const args = logger.error.args[0] - assert.equal(args.length, 2, 'logger.error was passed two arguments') - assert.equal(args[0], 'amplitude.missingData', 'first argument was error op') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + const args = logger.error.args[0]; + assert.equal(args.length, 2, 'logger.error was passed two arguments'); + assert.equal(args[0], 'amplitude.missingData', 'first argument was error op'); assert.deepEqual(args[1], { data: { event_type: 'foo' } - }, 'second argument was correct') + }, 'second argument was correct'); - assert.equal(logger.info.callCount, 0, 'logger.info was not called') - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.info.callCount, 0, 'logger.info was not called'); + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it('.amplitudeEvent with missing device_id', () => { - log.amplitudeEvent({ event_type: 'wibble', user_id: 'blee' }) + log.amplitudeEvent({ event_type: 'wibble', user_id: 'blee' }); - assert.equal(logger.info.callCount, 1, 'logger.info was called once') - const args = logger.info.args[0] - assert.equal(args.length, 2, 'logger.info was passed two arguments') - assert.equal(args[0], 'amplitudeEvent', 'first argument was correct') + assert.equal(logger.info.callCount, 1, 'logger.info was called once'); + const args = logger.info.args[0]; + assert.equal(args.length, 2, 'logger.info was passed two arguments'); + assert.equal(args[0], 'amplitudeEvent', 'first argument was correct'); assert.deepEqual(args[1], { event_type: 'wibble', user_id: 'blee' - }, 'second argument was event data') + }, 'second argument was event data'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it('.amplitudeEvent with missing user_id', () => { - log.amplitudeEvent({ event_type: 'foo', device_id: 'bar' }) + log.amplitudeEvent({ event_type: 'foo', device_id: 'bar' }); - assert.equal(logger.info.callCount, 1, 'logger.info was called once') - const args = logger.info.args[0] - assert.equal(args.length, 2, 'logger.info was passed two arguments') - assert.equal(args[0], 'amplitudeEvent', 'first argument was correct') + assert.equal(logger.info.callCount, 1, 'logger.info was called once'); + const args = logger.info.args[0]; + assert.equal(args.length, 2, 'logger.info was passed two arguments'); + assert.equal(args[0], 'amplitudeEvent', 'first argument was correct'); assert.deepEqual(args[1], { event_type: 'foo', device_id: 'bar' - }, 'second argument was event data') + }, 'second argument was event data'); - assert.equal(logger.debug.callCount, 0, 'logger.debug was not called') - assert.equal(logger.error.callCount, 0, 'logger.error was not called') - assert.equal(logger.critical.callCount, 0, 'logger.critical was not called') - assert.equal(logger.warn.callCount, 0, 'logger.warn was not called') - }) + assert.equal(logger.debug.callCount, 0, 'logger.debug was not called'); + assert.equal(logger.error.callCount, 0, 'logger.error was not called'); + assert.equal(logger.critical.callCount, 0, 'logger.critical was not called'); + assert.equal(logger.warn.callCount, 0, 'logger.warn was not called'); + }); it( '.error removes PII from error objects', () => { - var err = new Error() - err.email = 'test@example.com' - log.error('unexpectedError', { err: err }) + var err = new Error(); + err.email = 'test@example.com'; + log.error('unexpectedError', { err: err }); - assert.equal(logger.error.callCount, 1, 'logger.error was called once') - var args = logger.error.args[0] - assert.equal(args[0], 'unexpectedError', 'logger.error received "op" value') - assert.lengthOf(Object.keys(args[1]), 2) - assert.equal(args[1].email, 'test@example.com', 'email is reported in top-level fields') - assert(! args[1].err.email, 'email should not be reported in error object') + assert.equal(logger.error.callCount, 1, 'logger.error was called once'); + var args = logger.error.args[0]; + assert.equal(args[0], 'unexpectedError', 'logger.error received "op" value'); + assert.lengthOf(Object.keys(args[1]), 2); + assert.equal(args[1].email, 'test@example.com', 'email is reported in top-level fields'); + assert(! args[1].err.email, 'email should not be reported in error object'); } - ) + ); it('.summary should log an info message and call request.emitRouteFlowEvent', () => { - const emitRouteFlowEvent = sinon.spy() + const emitRouteFlowEvent = sinon.spy(); log.summary({ app: { accountRecreated: false, @@ -503,16 +503,16 @@ describe('log', () => { source: { formattedPhoneNumber: 'garply' } - }) + }); - assert.equal(logger.info.callCount, 1) - assert.equal(logger.info.args[0][0], 'request.summary') - const line = logger.info.args[0][1] + assert.equal(logger.info.callCount, 1); + assert.equal(logger.info.args[0][0], 'request.summary'); + const line = logger.info.args[0][1]; // Because t is generated using Date.now and subtracting info.received, // it should be >= 0, but we don't know the exact value. - assert.isNumber(line.t) - assert.isTrue(line.t >= 0) + assert.isNumber(line.t); + assert.isTrue(line.t >= 0); // Compare only known values. delete line.t; @@ -534,15 +534,15 @@ describe('log', () => { method: 'get', email: 'quix', phoneNumber: 'garply', - }) + }); - assert.equal(emitRouteFlowEvent.callCount, 1) - assert.equal(emitRouteFlowEvent.args[0].length, 1) + assert.equal(emitRouteFlowEvent.callCount, 1); + assert.equal(emitRouteFlowEvent.args[0].length, 1); assert.deepEqual(emitRouteFlowEvent.args[0][0], { code: 200, errno: 109, statusCode: 201, source: { formattedPhoneNumber: 'garply' - }}) - assert.equal(logger.error.callCount, 0) - }) + }}); + assert.equal(logger.error.callCount, 0); + }); it('.summary with email in payload', () => { log.summary({ @@ -565,9 +565,9 @@ describe('log', () => { }, { code: 200, statusCode: 201 - }) + }); - assert.equal(logger.info.args[0][1].email, 'quix') + assert.equal(logger.info.args[0][1].email, 'quix'); }); it('.summary with email in query', () => { @@ -591,14 +591,14 @@ describe('log', () => { }, { code: 200, statusCode: 201 - }) + }); - assert.equal(logger.info.args[0][1].email, 'quix') + assert.equal(logger.info.args[0][1].email, 'quix'); }); it('.notifyAttachedServices should send a notification (with service=known clientid)', () => { - const now = Date.now() - const metricsContext = mockMetricsContext() + const now = Date.now(); + const metricsContext = mockMetricsContext(); const request = mockRequest({ log, metricsContext, @@ -615,12 +615,12 @@ describe('log', () => { utmTerm: 'utm term' } } - }) - sinon.stub(Date, 'now').callsFake(() => now) + }); + sinon.stub(Date, 'now').callsFake(() => now); return log.notifyAttachedServices('login', request, { service: 'clientid', ts: now}).then(() => { - assert.equal(metricsContext.gather.callCount, 1) - assert.equal(log.notifier.send.callCount, 1) - assert.equal(log.notifier.send.args[0].length, 1) + assert.equal(metricsContext.gather.callCount, 1); + assert.equal(log.notifier.send.callCount, 1); + assert.equal(log.notifier.send.args[0].length, 1); assert.deepEqual(log.notifier.send.args[0][0], { event: 'login', data: { @@ -641,15 +641,15 @@ describe('log', () => { utm_term: 'utm term' } } - }) + }); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.notifyAttachedServices should send a notification (with service=unknown clientid)', () => { - const now = Date.now() - const metricsContext = mockMetricsContext() + const now = Date.now(); + const metricsContext = mockMetricsContext(); const request = mockRequest({ log, metricsContext, @@ -666,12 +666,12 @@ describe('log', () => { utmTerm: 'utm term' } } - }) - sinon.stub(Date, 'now').callsFake(() => now) + }); + sinon.stub(Date, 'now').callsFake(() => now); return log.notifyAttachedServices('login', request, { service: 'unknown-clientid', ts: now}).then(() => { - assert.equal(metricsContext.gather.callCount, 1) - assert.equal(log.notifier.send.callCount, 1) - assert.equal(log.notifier.send.args[0].length, 1) + assert.equal(metricsContext.gather.callCount, 1); + assert.equal(log.notifier.send.callCount, 1); + assert.equal(log.notifier.send.args[0].length, 1); assert.deepEqual(log.notifier.send.args[0][0], { event: 'login', data: { @@ -692,15 +692,15 @@ describe('log', () => { utm_term: 'utm term' } } - }) + }); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.notifyAttachedServices should send a notification (with service=sync)', () => { - const now = Date.now() - const metricsContext = mockMetricsContext() + const now = Date.now(); + const metricsContext = mockMetricsContext(); const request = mockRequest({ log, metricsContext, @@ -717,12 +717,12 @@ describe('log', () => { utmTerm: 'utm term' } } - }) - sinon.stub(Date, 'now').callsFake(() => now) + }); + sinon.stub(Date, 'now').callsFake(() => now); return log.notifyAttachedServices('login', request, { service: 'sync', ts: now}).then(() => { - assert.equal(metricsContext.gather.callCount, 1) - assert.equal(log.notifier.send.callCount, 1) - assert.equal(log.notifier.send.args[0].length, 1) + assert.equal(metricsContext.gather.callCount, 1); + assert.equal(log.notifier.send.callCount, 1); + assert.equal(log.notifier.send.args[0].length, 1); assert.deepEqual(log.notifier.send.args[0][0], { event: 'login', data: { @@ -743,9 +743,9 @@ describe('log', () => { utm_term: 'utm term' } } - }) + }); }).finally(() => { - Date.now.restore() - }) - }) -}) + Date.now.restore(); + }); + }); +}); diff --git a/test/local/mailer_locales.js b/test/local/mailer_locales.js index 7267de22..91fe4183 100644 --- a/test/local/mailer_locales.js +++ b/test/local/mailer_locales.js @@ -2,50 +2,50 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const config = require(`${ROOT_DIR}/config/index`).getProperties() -const error = require(`${ROOT_DIR}/lib/error`) -const mocks = require('../mocks') +const { assert } = require('chai'); +const config = require(`${ROOT_DIR}/config/index`).getProperties(); +const error = require(`${ROOT_DIR}/lib/error`); +const mocks = require('../mocks'); const bounces = { check() { - return require(`${ROOT_DIR}/lib/promise`).resolve() + return require(`${ROOT_DIR}/lib/promise`).resolve(); } -} +}; -const log = mocks.mockLog() +const log = mocks.mockLog(); describe('mailer locales', () => { - let mailer + let mailer; before(() => { return require(`${ROOT_DIR}/lib/senders/translator`)(config.i18n.supportedLanguages, config.i18n.defaultLanguage) .then(translator => { - return require(`${ROOT_DIR}/lib/senders`)(log, config, error, bounces, translator) + return require(`${ROOT_DIR}/lib/senders`)(log, config, error, bounces, translator); }) .then(result => { - mailer = result.email - }) - }) + mailer = result.email; + }); + }); it( 'All configured supportedLanguages are available', () => { - var locales = config.i18n.supportedLanguages + var locales = config.i18n.supportedLanguages; locales.forEach(function(lang) { // sr-LATN is sr, but in Latin characters, not Cyrillic if (lang === 'sr-LATN') { - assert.equal('sr-Latn', mailer.translator(lang).language) + assert.equal('sr-Latn', mailer.translator(lang).language); } else { - assert.equal(lang, mailer.translator(lang).language) + assert.equal(lang, mailer.translator(lang).language); } - }) + }); } - ) + ); it( 'unsupported languages get default/fallback content', @@ -61,13 +61,13 @@ describe('mailer locales', () => { [ 'es-BO', 'es' ], [ 'fr-FR', 'fr' ], [ 'fr-CA', 'fr' ], - ] + ]; locales.forEach(function(lang) { - assert.equal(lang[1], mailer.translator(lang[0]).language) - }) + assert.equal(lang[1], mailer.translator(lang[0]).language); + }); } - ) + ); it( 'accept-language handled correctly', @@ -81,14 +81,14 @@ describe('mailer locales', () => { [ 'es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3', 'es-ES' ], [ 'sv-SE,sv;q=0.8,en-US;q=0.5,en;q=0.3', 'sv-SE' ], [ 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3', 'zh-CN' ] - ] + ]; locales.forEach(function(lang) { - assert.equal(lang[1], mailer.translator(lang[0]).language) - }) + assert.equal(lang[1], mailer.translator(lang[0]).language); + }); } - ) + ); - after(() => mailer.stop()) + after(() => mailer.stop()); -}) +}); diff --git a/test/local/metrics/amplitude.js b/test/local/metrics/amplitude.js index 6623efaf..c53e345e 100644 --- a/test/local/metrics/amplitude.js +++ b/test/local/metrics/amplitude.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const amplitudeModule = require('../../../lib/metrics/amplitude') -const mocks = require('../../mocks') +const { assert } = require('chai'); +const amplitudeModule = require('../../../lib/metrics/amplitude'); +const mocks = require('../../mocks'); -const DAY = 1000 * 60 * 60 * 24 -const WEEK = DAY * 7 -const MONTH = DAY * 28 +const DAY = 1000 * 60 * 60 * 24; +const WEEK = DAY * 7; +const MONTH = DAY * 28; describe('metrics/amplitude', () => { it('interface is correct', () => { - assert.equal(typeof amplitudeModule, 'function') - assert.equal(amplitudeModule.length, 2) - }) + assert.equal(typeof amplitudeModule, 'function'); + assert.equal(amplitudeModule.length, 2); + }); it('throws if log argument is missing', () => { - assert.throws(() => amplitudeModule(null, { oauth: { clientIds: {} } })) - }) + assert.throws(() => amplitudeModule(null, { oauth: { clientIds: {} } })); + }); it('throws if config argument is missing', () => { - assert.throws(() => amplitudeModule({}, { oauth: { clientIds: null } })) - }) + assert.throws(() => amplitudeModule({}, { oauth: { clientIds: null } })); + }); describe('instantiate', () => { - let log, amplitude + let log, amplitude; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); amplitude = amplitudeModule(log, { oauth: { clientIds: { @@ -38,59 +38,59 @@ describe('metrics/amplitude', () => { 1: 'pocket' } } - }) - }) + }); + }); it('interface is correct', () => { - assert.equal(typeof amplitude, 'function') - assert.equal(amplitude.length, 2) - }) + assert.equal(typeof amplitude, 'function'); + assert.equal(amplitude.length, 2); + }); describe('empty event argument', () => { beforeEach(() => { - return amplitude('', mocks.mockRequest({})) - }) + return amplitude('', mocks.mockRequest({})); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'amplitude.badArgument') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'amplitude.badArgument'); assert.deepEqual(log.error.args[0][1], { err: 'Bad argument', event: '', hasRequest: true - }) - }) + }); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('missing request argument', () => { beforeEach(() => { - return amplitude('foo') - }) + return amplitude('foo'); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0].length, 2) - assert.equal(log.error.args[0][0], 'amplitude.badArgument') + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0].length, 2); + assert.equal(log.error.args[0][0], 'amplitude.badArgument'); assert.deepEqual(log.error.args[0][1], { err: 'Bad argument', event: 'foo', hasRequest: false - }) - }) + }); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('account.confirmed', () => { beforeEach(() => { - const now = Date.now() + const now = Date.now(); return amplitude('account.confirmed', mocks.mockRequest({ uaBrowser: 'foo', uaBrowserVersion: 'bar', @@ -124,31 +124,31 @@ describe('metrics/amplitude', () => { flowBeginTime: 'kwop' } } - })) - }) + })); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args.length, 1) - assert.equal(args[0].device_id, 'juff') - assert.equal(args[0].user_id, 'blee') - assert.equal(args[0].event_type, 'fxa_login - email_confirmed') - assert.equal(args[0].session_id, 'kwop') - assert.equal(args[0].language, 'wibble') - assert.equal(args[0].country, 'United Kingdom') - assert.equal(args[0].region, 'England') - assert.equal(args[0].os_name, 'baz') - assert.equal(args[0].os_version, 'qux') - assert.equal(args[0].device_model, 'melm') + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0].device_id, 'juff'); + assert.equal(args[0].user_id, 'blee'); + assert.equal(args[0].event_type, 'fxa_login - email_confirmed'); + assert.equal(args[0].session_id, 'kwop'); + assert.equal(args[0].language, 'wibble'); + assert.equal(args[0].country, 'United Kingdom'); + assert.equal(args[0].region, 'England'); + assert.equal(args[0].os_name, 'baz'); + assert.equal(args[0].os_version, 'qux'); + assert.equal(args[0].device_model, 'melm'); assert.deepEqual(args[0].event_properties, { service: 'amo', oauth_client_id: '0' - }) + }); assert.deepEqual(args[0].user_properties, { flow_id: 'udge', sync_active_devices_day: 1, @@ -160,11 +160,11 @@ describe('metrics/amplitude', () => { '$append': { fxa_services_used: 'amo' } - }) - assert.ok(args[0].time > Date.now() - 1000) - assert.ok(/^[1-9][0-9]+$/.test(args[0].app_version)) - }) - }) + }); + assert.ok(args[0].time > Date.now() - 1000); + assert.ok(/^[1-9][0-9]+$/.test(args[0].app_version)); + }); + }); describe('account.created', () => { beforeEach(() => { @@ -185,33 +185,33 @@ describe('metrics/amplitude', () => { payload: { service: '1' } - }) + }); // mockRequest forces uaOS if it's not set - request.app.ua.os = '' - return amplitude('account.created', request) - }) + request.app.ua.os = ''; + return amplitude('account.created', request); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].device_id, undefined) - assert.equal(args[0].user_id, 'h') - assert.equal(args[0].event_type, 'fxa_reg - created') - assert.equal(args[0].session_id, undefined) - assert.equal(args[0].language, 'g') - assert.equal(args[0].country, 'United States') - assert.equal(args[0].region, 'California') - assert.equal(args[0].os_name, undefined) - assert.equal(args[0].os_version, undefined) - assert.equal(args[0].device_model, 'f') + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].device_id, undefined); + assert.equal(args[0].user_id, 'h'); + assert.equal(args[0].event_type, 'fxa_reg - created'); + assert.equal(args[0].session_id, undefined); + assert.equal(args[0].language, 'g'); + assert.equal(args[0].country, 'United States'); + assert.equal(args[0].region, 'California'); + assert.equal(args[0].os_name, undefined); + assert.equal(args[0].os_version, undefined); + assert.equal(args[0].device_model, 'f'); assert.deepEqual(args[0].event_properties, { service: 'pocket', oauth_client_id: '1' - }) + }); assert.deepEqual(args[0].user_properties, { sync_active_devices_day: 0, sync_active_devices_week: 0, @@ -222,9 +222,9 @@ describe('metrics/amplitude', () => { '$append': { fxa_services_used: 'pocket' } - }) - }) - }) + }); + }); + }); describe('account.login', () => { beforeEach(() => { @@ -234,28 +234,28 @@ describe('metrics/amplitude', () => { } }, { devices: {} - })) - }) + })); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_login - success') - assert.equal(args[0].event_properties.service, 'undefined_oauth') - assert.equal(args[0].event_properties.oauth_client_id, '2') + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_login - success'); + assert.equal(args[0].event_properties.service, 'undefined_oauth'); + assert.equal(args[0].event_properties.oauth_client_id, '2'); assert.deepEqual(args[0].user_properties['$append'], { fxa_services_used: 'undefined_oauth' - }) - assert.equal(args[0].user_properties.sync_active_devices_day, undefined) - assert.equal(args[0].user_properties.sync_active_devices_week, undefined) - assert.equal(args[0].user_properties.sync_active_devices_month, undefined) - assert.equal(args[0].user_properties.sync_device_count, undefined) - }) - }) + }); + assert.equal(args[0].user_properties.sync_active_devices_day, undefined); + assert.equal(args[0].user_properties.sync_active_devices_week, undefined); + assert.equal(args[0].user_properties.sync_active_devices_month, undefined); + assert.equal(args[0].user_properties.sync_device_count, undefined); + }); + }); describe('account.login.blocked', () => { beforeEach(() => { @@ -263,59 +263,59 @@ describe('metrics/amplitude', () => { payload: { service: 'sync' } - })) - }) + })); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_login - blocked') - assert.equal(args[0].event_properties.service, 'sync') - assert.equal(args[0].event_properties.oauth_client_id, undefined) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_login - blocked'); + assert.equal(args[0].event_properties.service, 'sync'); + assert.equal(args[0].event_properties.oauth_client_id, undefined); assert.deepEqual(args[0].user_properties['$append'], { fxa_services_used: 'sync' - }) - }) - }) + }); + }); + }); describe('account.login.confirmedUnblockCode', () => { beforeEach(() => { - return amplitude('account.login.confirmedUnblockCode', mocks.mockRequest({})) - }) + return amplitude('account.login.confirmedUnblockCode', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_login - unblock_success') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_login - unblock_success'); + }); + }); describe('account.reset', () => { beforeEach(() => { - return amplitude('account.reset', mocks.mockRequest({})) - }) + return amplitude('account.reset', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 2) - let args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_login - forgot_complete') - args = log.amplitudeEvent.args[1] - assert.equal(args[0].event_type, 'fxa_login - complete') - assert.isAbove(args[0].time, log.amplitudeEvent.args[0][0].time) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 2); + let args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_login - forgot_complete'); + args = log.amplitudeEvent.args[1]; + assert.equal(args[0].event_type, 'fxa_login - complete'); + assert.isAbove(args[0].time, log.amplitudeEvent.args[0][0].time); + }); + }); describe('account.signed', () => { beforeEach(() => { @@ -323,157 +323,157 @@ describe('metrics/amplitude', () => { payload: { service: 'content-server' } - })) - }) + })); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_activity - cert_signed') - assert.equal(args[0].event_properties.service, undefined) - assert.equal(args[0].event_properties.oauth_client_id, undefined) - assert.equal(args[0].user_properties['$append'], undefined) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_activity - cert_signed'); + assert.equal(args[0].event_properties.service, undefined); + assert.equal(args[0].event_properties.oauth_client_id, undefined); + assert.equal(args[0].user_properties['$append'], undefined); + }); + }); describe('account.verified', () => { beforeEach(() => { - return amplitude('account.verified', mocks.mockRequest({})) - }) + return amplitude('account.verified', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_reg - email_confirmed') - assert.equal(args[0].user_properties.newsletter_state, undefined) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_reg - email_confirmed'); + assert.equal(args[0].user_properties.newsletter_state, undefined); + }); + }); describe('account.verified, marketingOptIn=true', () => { beforeEach(() => { return amplitude('account.verified', mocks.mockRequest({}), { marketingOptIn: true - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_reg - email_confirmed') - assert.equal(args[0].user_properties.newsletter_state, 'subscribed') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_reg - email_confirmed'); + assert.equal(args[0].user_properties.newsletter_state, 'subscribed'); + }); + }); describe('account.verified, marketingOptIn=false', () => { beforeEach(() => { return amplitude('account.verified', mocks.mockRequest({}), { marketingOptIn: false - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_reg - email_confirmed') - assert.equal(args[0].user_properties.newsletter_state, 'unsubscribed') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_reg - email_confirmed'); + assert.equal(args[0].user_properties.newsletter_state, 'unsubscribed'); + }); + }); describe('flow.complete (sign-up)', () => { beforeEach(() => { return amplitude('flow.complete', mocks.mockRequest({}), {}, { flowType: 'registration' - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_reg - complete') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_reg - complete'); + }); + }); describe('flow.complete (sign-in)', () => { beforeEach(() => { return amplitude('flow.complete', mocks.mockRequest({}), {}, { flowType: 'login' - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_login - complete') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_login - complete'); + }); + }); describe('flow.complete (reset)', () => { beforeEach(() => { - return amplitude('flow.complete', mocks.mockRequest({})) - }) + return amplitude('flow.complete', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('sms.installFirefox.sent', () => { beforeEach(() => { - return amplitude('sms.installFirefox.sent', mocks.mockRequest({})) - }) + return amplitude('sms.installFirefox.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_sms - sent') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_sms - sent'); + }); + }); describe('device.created', () => { beforeEach(() => { - return amplitude('device.created', mocks.mockRequest({})) - }) + return amplitude('device.created', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('email.newDeviceLoginEmail.bounced', () => { beforeEach(() => { @@ -482,581 +482,581 @@ describe('metrics/amplitude', () => { email_sender: 'ses', email_service: 'fxa-email-service', templateVersion: 'wibble' - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'login') - assert.equal(args[0].event_properties.email_provider, 'gmail') - assert.equal(args[0].event_properties.email_sender, 'ses') - assert.equal(args[0].event_properties.email_service, 'fxa-email-service') - assert.equal(args[0].event_properties.email_template, 'newDeviceLoginEmail') - assert.equal(args[0].event_properties.email_version, 'wibble') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'login'); + assert.equal(args[0].event_properties.email_provider, 'gmail'); + assert.equal(args[0].event_properties.email_sender, 'ses'); + assert.equal(args[0].event_properties.email_service, 'fxa-email-service'); + assert.equal(args[0].event_properties.email_template, 'newDeviceLoginEmail'); + assert.equal(args[0].event_properties.email_version, 'wibble'); + }); + }); describe('email.newDeviceLoginEmail.sent', () => { beforeEach(() => { - return amplitude('email.newDeviceLoginEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.newDeviceLoginEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'login') - assert.equal(args[0].event_properties.email_provider, undefined) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'login'); + assert.equal(args[0].event_properties.email_provider, undefined); + }); + }); describe('email.passwordChangedEmail.bounced', () => { beforeEach(() => { - return amplitude('email.passwordChangedEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.passwordChangedEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'change_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'change_password'); + }); + }); describe('email.passwordChangedEmail.sent', () => { beforeEach(() => { - return amplitude('email.passwordChangedEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.passwordChangedEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'change_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'change_password'); + }); + }); describe('email.passwordResetEmail.bounced', () => { beforeEach(() => { - return amplitude('email.passwordResetEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.passwordResetEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.passwordResetEmail.sent', () => { beforeEach(() => { - return amplitude('email.passwordResetEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.passwordResetEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.passwordResetRequiredEmail.bounced', () => { beforeEach(() => { - return amplitude('email.passwordResetRequiredEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.passwordResetRequiredEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.passwordResetRequiredEmail.sent', () => { beforeEach(() => { - return amplitude('email.passwordResetRequiredEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.passwordResetRequiredEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.postRemoveSecondaryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.postRemoveSecondaryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.postRemoveSecondaryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.postRemoveSecondaryEmail.sent', () => { beforeEach(() => { - return amplitude('email.postRemoveSecondaryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.postRemoveSecondaryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.postChangePrimaryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.postChangePrimaryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.postChangePrimaryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'change_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'change_email'); + }); + }); describe('email.postChangePrimaryEmail.sent', () => { beforeEach(() => { - return amplitude('email.postChangePrimaryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.postChangePrimaryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'change_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'change_email'); + }); + }); describe('email.postVerifyEmail.bounced', () => { beforeEach(() => { - return amplitude('email.postVerifyEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.postVerifyEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.postVerifyEmail.sent', () => { beforeEach(() => { - return amplitude('email.postVerifyEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.postVerifyEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.postVerifySecondaryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.postVerifySecondaryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.postVerifySecondaryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.postVerifySecondaryEmail.sent', () => { beforeEach(() => { - return amplitude('email.postVerifySecondaryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.postVerifySecondaryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.recoveryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.recoveryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.recoveryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.recoveryEmail.sent', () => { beforeEach(() => { - return amplitude('email.recoveryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.recoveryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'reset_password') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'reset_password'); + }); + }); describe('email.unblockCode.bounced', () => { beforeEach(() => { - return amplitude('email.unblockCode.bounced', mocks.mockRequest({})) - }) + return amplitude('email.unblockCode.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'unblock') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'unblock'); + }); + }); describe('email.unblockCode.sent', () => { beforeEach(() => { - return amplitude('email.unblockCode.sent', mocks.mockRequest({})) - }) + return amplitude('email.unblockCode.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'unblock') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'unblock'); + }); + }); describe('email.verifyEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifyEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifyEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.verifyEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifyEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifyEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.verifyLoginEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifyLoginEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifyLoginEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'login') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'login'); + }); + }); describe('email.verifyLoginEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifyLoginEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifyLoginEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'login') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'login'); + }); + }); describe('email.verifyLoginCodeEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifyLoginCodeEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifyLoginCodeEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'login') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'login'); + }); + }); describe('email.verifyLoginCodeEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifyLoginCodeEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifyLoginCodeEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'login') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'login'); + }); + }); describe('email.verifyPrimaryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifyPrimaryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifyPrimaryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'verify') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'verify'); + }); + }); describe('email.verifyPrimaryEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifyPrimaryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifyPrimaryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'verify') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'verify'); + }); + }); describe('email.verifySyncEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifySyncEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifySyncEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.verifySyncEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifySyncEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifySyncEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'registration') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'registration'); + }); + }); describe('email.verifySecondaryEmail.bounced', () => { beforeEach(() => { - return amplitude('email.verifySecondaryEmail.bounced', mocks.mockRequest({})) - }) + return amplitude('email.verifySecondaryEmail.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - bounced') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - bounced'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.verifySecondaryEmail.sent', () => { beforeEach(() => { - return amplitude('email.verifySecondaryEmail.sent', mocks.mockRequest({})) - }) + return amplitude('email.verifySecondaryEmail.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('called log.amplitudeEvent correctly', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_email - sent') - assert.equal(args[0].event_properties.email_type, 'secondary_email') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_email - sent'); + assert.equal(args[0].event_properties.email_type, 'secondary_email'); + }); + }); describe('email.wibble.bounced', () => { beforeEach(() => { - return amplitude('email.wibble.bounced', mocks.mockRequest({})) - }) + return amplitude('email.wibble.bounced', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('email.wibble.sent', () => { beforeEach(() => { - return amplitude('email.wibble.sent', mocks.mockRequest({})) - }) + return amplitude('email.wibble.sent', mocks.mockRequest({})); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('did not call log.amplitudeEvent', () => { - assert.equal(log.amplitudeEvent.callCount, 0) - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0); + }); + }); describe('with data', () => { beforeEach(() => { @@ -1073,17 +1073,17 @@ describe('metrics/amplitude', () => { }), { service: 'zang', uid: 'frip' - }) - }) + }); + }); it('data properties were set', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].user_id, 'frip') - assert.equal(args[0].event_properties.service, 'undefined_oauth') - assert.equal(args[0].event_properties.oauth_client_id, 'zang') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].user_id, 'frip'); + assert.equal(args[0].event_properties.service, 'undefined_oauth'); + assert.equal(args[0].event_properties.oauth_client_id, 'zang'); + }); + }); describe('with metricsContext', () => { beforeEach(() => { @@ -1101,19 +1101,19 @@ describe('metrics/amplitude', () => { flowBeginTime: 'yerx', service: '0', time: 'wenf' - }) - }) + }); + }); it('metricsContext properties were set', () => { - assert.equal(log.amplitudeEvent.callCount, 1) - const args = log.amplitudeEvent.args[0] - assert.equal(args[0].device_id, 'plin') - assert.equal(args[0].event_properties.service, 'amo') - assert.equal(args[0].user_properties.flow_id, 'gorb') - assert.equal(args[0].user_properties['$append'].fxa_services_used, 'amo') - assert.equal(args[0].session_id, 'yerx') - assert.equal(args[0].time, 'wenf') - }) - }) - }) -}) + assert.equal(log.amplitudeEvent.callCount, 1); + const args = log.amplitudeEvent.args[0]; + assert.equal(args[0].device_id, 'plin'); + assert.equal(args[0].event_properties.service, 'amo'); + assert.equal(args[0].user_properties.flow_id, 'gorb'); + assert.equal(args[0].user_properties['$append'].fxa_services_used, 'amo'); + assert.equal(args[0].session_id, 'yerx'); + assert.equal(args[0].time, 'wenf'); + }); + }); + }); +}); diff --git a/test/local/metrics/context.js b/test/local/metrics/context.js index 4ddadb32..dda76389 100644 --- a/test/local/metrics/context.js +++ b/test/local/metrics/context.js @@ -2,96 +2,96 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const crypto = require('crypto') -const proxyquire = require('proxyquire') -const sinon = require('sinon') -const mocks = require('../../mocks') -const P = require(`${ROOT_DIR}/lib/promise`) +const { assert } = require('chai'); +const crypto = require('crypto'); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const mocks = require('../../mocks'); +const P = require(`${ROOT_DIR}/lib/promise`); -const modulePath = `${ROOT_DIR}/lib/metrics/context` -const metricsContextModule = require(modulePath) +const modulePath = `${ROOT_DIR}/lib/metrics/context`; +const metricsContextModule = require(modulePath); function hashToken (token) { - const hash = crypto.createHash('sha256') - hash.update(token.uid) - hash.update(token.id) + const hash = crypto.createHash('sha256'); + hash.update(token.uid); + hash.update(token.id); - return hash.digest('base64') + return hash.digest('base64'); } describe('metricsContext', () => { - let results, cache, cacheFactory, log, config, metricsContext + let results, cache, cacheFactory, log, config, metricsContext; beforeEach(() => { results = { del: P.resolve(), get: P.resolve(), set: P.resolve() - } + }; cache = { add: sinon.spy(() => results.add), del: sinon.spy(() => results.del), get: sinon.spy(() => results.get) - } - cacheFactory = sinon.spy(() => cache) - log = mocks.mockLog() - config = {} - metricsContext = proxyquire(modulePath, { '../cache': cacheFactory })(log, config) - }) + }; + cacheFactory = sinon.spy(() => cache); + log = mocks.mockLog(); + config = {}; + metricsContext = proxyquire(modulePath, { '../cache': cacheFactory })(log, config); + }); it('metricsContext interface is correct', () => { - assert.isFunction(metricsContextModule) - assert.isObject(metricsContextModule.schema) - assert.isNotNull(metricsContextModule.schema) + assert.isFunction(metricsContextModule); + assert.isObject(metricsContextModule.schema); + assert.isNotNull(metricsContextModule.schema); - assert.isObject(metricsContext) - assert.isNotNull(metricsContext) - assert.lengthOf(Object.keys(metricsContext), 7) + assert.isObject(metricsContext); + assert.isNotNull(metricsContext); + assert.lengthOf(Object.keys(metricsContext), 7); - assert.isFunction(metricsContext.stash) - assert.lengthOf(metricsContext.stash, 1) + assert.isFunction(metricsContext.stash); + assert.lengthOf(metricsContext.stash, 1); - assert.isFunction(metricsContext.get) - assert.lengthOf(metricsContext.get, 1) + assert.isFunction(metricsContext.get); + assert.lengthOf(metricsContext.get, 1); - assert.isFunction(metricsContext.gather) - assert.lengthOf(metricsContext.gather, 1) + assert.isFunction(metricsContext.gather); + assert.lengthOf(metricsContext.gather, 1); - assert.isFunction(metricsContext.propagate) - assert.lengthOf(metricsContext.propagate, 2) + assert.isFunction(metricsContext.propagate); + assert.lengthOf(metricsContext.propagate, 2); - assert.isFunction(metricsContext.clear) - assert.lengthOf(metricsContext.clear, 0) + assert.isFunction(metricsContext.clear); + assert.lengthOf(metricsContext.clear, 0); - assert.isFunction(metricsContext.validate) - assert.lengthOf(metricsContext.validate, 0) + assert.isFunction(metricsContext.validate); + assert.lengthOf(metricsContext.validate, 0); - assert.isFunction(metricsContext.setFlowCompleteSignal) - assert.lengthOf(metricsContext.setFlowCompleteSignal, 2) - }) + assert.isFunction(metricsContext.setFlowCompleteSignal); + assert.lengthOf(metricsContext.setFlowCompleteSignal, 2); + }); it('instantiated cache correctly', () => { - assert.equal(cacheFactory.callCount, 1) - const args = cacheFactory.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], log) - assert.equal(args[1], config) - assert.equal(args[2], 'fxa-metrics~') - }) + assert.equal(cacheFactory.callCount, 1); + const args = cacheFactory.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], log); + assert.equal(args[1], config); + assert.equal(args[2], 'fxa-metrics~'); + }); it( 'metricsContext.stash', () => { - results.add = P.resolve('wibble') + results.add = P.resolve('wibble'); const token = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; return metricsContext.stash.call({ payload: { metricsContext: { @@ -101,31 +101,31 @@ describe('metricsContext', () => { }, query: {} }, token).then(result => { - assert.equal(result, 'wibble', 'result is correct') + assert.equal(result, 'wibble', 'result is correct'); - assert.equal(cache.add.callCount, 1, 'cache.add was called once') - assert.equal(cache.add.args[0].length, 2, 'cache.add was passed two arguments') - assert.equal(cache.add.args[0][0], hashToken(token), 'first argument was correct') + assert.equal(cache.add.callCount, 1, 'cache.add was called once'); + assert.equal(cache.add.args[0].length, 2, 'cache.add was passed two arguments'); + assert.equal(cache.add.args[0][0], hashToken(token), 'first argument was correct'); assert.deepEqual(cache.add.args[0][1], { foo: 'bar', service: 'baz' - }, 'second argument was correct') + }, 'second argument was correct'); - assert.equal(cache.get.callCount, 0, 'cache.get was not called') - assert.equal(log.warn.callCount, 0, 'log.warn was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.equal(cache.get.callCount, 0, 'cache.get was not called'); + assert.equal(log.warn.callCount, 0, 'log.warn was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it( 'metricsContext.stash with clashing data', () => { - results.add = P.reject('wibble') + results.add = P.reject('wibble'); const token = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; return metricsContext.stash.call({ payload: { metricsContext: { @@ -135,22 +135,22 @@ describe('metricsContext', () => { }, query: {} }, token).then(result => { - assert.strictEqual(result, undefined, 'result is undefined') - assert.equal(cache.add.callCount, 1, 'cache.add was called once') - assert.equal(log.warn.callCount, 1, 'log.warn was called once') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.strictEqual(result, undefined, 'result is undefined'); + assert.equal(cache.add.callCount, 1, 'cache.add was called once'); + assert.equal(log.warn.callCount, 1, 'log.warn was called once'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it( 'metricsContext.stash with service query param', () => { - results.add = P.resolve('wibble') + results.add = P.resolve('wibble'); const token = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; return metricsContext.stash.call({ payload: { metricsContext: { @@ -161,13 +161,13 @@ describe('metricsContext', () => { service: 'qux' } }, token).then(result => { - assert.equal(cache.add.callCount, 1, 'cache.add was called once') - assert.equal(cache.add.args[0][1].service, 'qux', 'service property was correct') + assert.equal(cache.add.callCount, 1, 'cache.add was called once'); + assert.equal(cache.add.args[0][1].service, 'qux', 'service property was correct'); - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it( 'metricsContext.stash with bad token', @@ -182,20 +182,20 @@ describe('metricsContext', () => { }, { id: 'foo' }).then(result => { - assert.equal(result, undefined, 'result is undefined') + assert.equal(result, undefined, 'result is undefined'); - assert.equal(log.error.callCount, 1, 'log.error was called once') - assert.equal(log.error.args[0].length, 2, 'log.error was passed one argument') - assert.equal(log.error.args[0][0], 'metricsContext.stash', 'op property was correct') - assert.equal(log.error.args[0][1].err.message, 'Invalid token', 'err.message property was correct') - assert.strictEqual(log.error.args[0][1].hasToken, true, 'hasToken property was correct') - assert.strictEqual(log.error.args[0][1].hasId, true, 'hasId property was correct') - assert.strictEqual(log.error.args[0][1].hasUid, false, 'hasUid property was correct') + assert.equal(log.error.callCount, 1, 'log.error was called once'); + assert.equal(log.error.args[0].length, 2, 'log.error was passed one argument'); + assert.equal(log.error.args[0][0], 'metricsContext.stash', 'op property was correct'); + assert.equal(log.error.args[0][1].err.message, 'Invalid token', 'err.message property was correct'); + assert.strictEqual(log.error.args[0][1].hasToken, true, 'hasToken property was correct'); + assert.strictEqual(log.error.args[0][1].hasId, true, 'hasId property was correct'); + assert.strictEqual(log.error.args[0][1].hasUid, false, 'hasUid property was correct'); - assert.equal(cache.add.callCount, 0, 'cache.add was not called') - }) + assert.equal(cache.add.callCount, 0, 'cache.add was not called'); + }); } - ) + ); it( 'metricsContext.stash without metadata', @@ -207,19 +207,19 @@ describe('metricsContext', () => { uid: Array(64).fill('c').join(''), id: 'foo' }).then(result => { - assert.equal(result, undefined, 'result is undefined') + assert.equal(result, undefined, 'result is undefined'); - assert.equal(cache.add.callCount, 0, 'cache.add was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.equal(cache.add.callCount, 0, 'cache.add was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it('metricsContext.get with payload', async () => { results.get = P.resolve({ flowId: 'not this flow id', flowBeginTime: 0 - }) + }); const result = await metricsContext.get({ payload: { @@ -228,22 +228,22 @@ describe('metricsContext', () => { flowBeginTime: 42 } } - }) + }); assert.deepEqual(result, { flowId: 'mock flow id', flowBeginTime: 42 - }) + }); - assert.equal(cache.get.callCount, 0) - assert.equal(log.error.callCount, 0) - }) + assert.equal(cache.get.callCount, 0); + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with payload', async () => { results.get = P.resolve({ flowId: 'not this flow id', flowBeginTime: 0 - }) + }); const result = await metricsContext.get({ payload: { metricsContext: { @@ -251,77 +251,77 @@ describe('metricsContext', () => { flowBeginTime: 42 } } - }) + }); - assert.isObject(result) + assert.isObject(result); assert.deepEqual(result, { flowId: 'mock flow id', flowBeginTime: 42 - }) + }); - assert.equal(cache.get.callCount, 0) - assert.equal(log.error.callCount, 0) - }) + assert.equal(cache.get.callCount, 0); + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with token', async () => { results.get = P.resolve({ flowId: 'flowId', flowBeginTime: 1977 - }) + }); const token = { uid: Array(64).fill('7').join(''), id: 'wibble' - } + }; const result = await metricsContext.get({ auth: { credentials: token } - }) + }); assert.deepEqual(result, { flowId: 'flowId', flowBeginTime: 1977 - }) + }); - assert.equal(cache.get.callCount, 1) - assert.lengthOf(cache.get.args[0], 1) - assert.equal(cache.get.args[0][0], hashToken(token)) + assert.equal(cache.get.callCount, 1); + assert.lengthOf(cache.get.args[0], 1); + assert.equal(cache.get.args[0][0], hashToken(token)); - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with fake token', async () => { results.get = P.resolve({ flowId: 'flowId', flowBeginTime: 1977 - }) + }); - const uid = Array(64).fill('7').join('') - const id = 'wibble' + const uid = Array(64).fill('7').join(''); + const id = 'wibble'; - const token = { uid, id } + const token = { uid, id }; const result = await metricsContext.get({ payload: { uid, code: id } - }) + }); assert.deepEqual(result, { flowId: 'flowId', flowBeginTime: 1977 - }) + }); - assert.equal(cache.get.callCount, 1) - assert.lengthOf(cache.get.args[0], 1) - assert.equal(cache.get.args[0][0], hashToken(token)) - assert.deepEqual(cache.get.args[0][0], hashToken({ uid, id })) + assert.equal(cache.get.callCount, 1); + assert.lengthOf(cache.get.args[0], 1); + assert.equal(cache.get.args[0][0], hashToken(token)); + assert.deepEqual(cache.get.args[0][0], hashToken({ uid, id })); - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with bad token', async () => { const result = await metricsContext.get({ @@ -330,34 +330,34 @@ describe('metricsContext', () => { uid: Array(64).fill('c').join('') } } - }) + }); - assert.deepEqual(result, {}) + assert.deepEqual(result, {}); - assert.equal(log.error.callCount, 1) - assert.lengthOf(log.error.args[0], 2) - assert.equal(log.error.args[0][0], 'metricsContext.get') - assert.equal(log.error.args[0][1].err.message, 'Invalid token') - assert.strictEqual(log.error.args[0][1].hasToken, true) - assert.strictEqual(log.error.args[0][1].hasId, false) - assert.strictEqual(log.error.args[0][1].hasUid, true) - }) + assert.equal(log.error.callCount, 1); + assert.lengthOf(log.error.args[0], 2); + assert.equal(log.error.args[0][0], 'metricsContext.get'); + assert.equal(log.error.args[0][1].err.message, 'Invalid token'); + assert.strictEqual(log.error.args[0][1].hasToken, true); + assert.strictEqual(log.error.args[0][1].hasId, false); + assert.strictEqual(log.error.args[0][1].hasUid, true); + }); it('metricsContext.get with no token and no payload', async () => { const result = await metricsContext.get({ auth: {} - }) + }); - assert.deepEqual(result, {}) + assert.deepEqual(result, {}); - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with token and payload', async () => { results.get = P.resolve({ flowId: 'foo', flowBeginTime: 1977 - }) + }); const result = await metricsContext.get({ auth: { @@ -372,19 +372,19 @@ describe('metricsContext', () => { flowBeginTime: 42 } } - }) + }); assert.deepEqual(result, { flowId: 'baz', flowBeginTime: 42 - }) + }); - assert.equal(cache.get.callCount, 0) - assert.equal(log.error.callCount, 0) - }) + assert.equal(cache.get.callCount, 0); + assert.equal(log.error.callCount, 0); + }); it('metricsContext.get with cache.get error', async () => { - results.get = P.reject('foo') + results.get = P.reject('foo'); const result = await metricsContext.get({ auth: { credentials: { @@ -392,20 +392,20 @@ describe('metricsContext', () => { id: 'bar' } } - }) + }); - assert.deepEqual(result, {}) + assert.deepEqual(result, {}); - assert.equal(cache.get.callCount, 1) + assert.equal(cache.get.callCount, 1); - assert.equal(log.error.callCount, 1) - assert.lengthOf(log.error.args[0], 2) - assert.equal(log.error.args[0][0], 'metricsContext.get') - assert.equal(log.error.args[0][1].err, 'foo') - assert.strictEqual(log.error.args[0][1].hasToken, true) - assert.strictEqual(log.error.args[0][1].hasId, true) - assert.strictEqual(log.error.args[0][1].hasUid, true) - }) + assert.equal(log.error.callCount, 1); + assert.lengthOf(log.error.args[0], 2); + assert.equal(log.error.args[0][0], 'metricsContext.get'); + assert.equal(log.error.args[0][1].err, 'foo'); + assert.strictEqual(log.error.args[0][1].hasToken, true); + assert.strictEqual(log.error.args[0][1].hasId, true); + assert.strictEqual(log.error.args[0][1].hasUid, true); + }); it( 'metricsContext.gather with metadata', @@ -413,8 +413,8 @@ describe('metricsContext', () => { results.get = P.resolve({ flowId: 'not this flow id', flowBeginTime: 0 - }) - const time = Date.now() - 1 + }); + const time = Date.now() - 1; return metricsContext.gather.call({ app: { metricsContext: P.resolve({ @@ -436,30 +436,30 @@ describe('metricsContext', () => { }) } }, {}).then(function (result) { - assert.equal(typeof result, 'object', 'result is object') - assert.notEqual(result, null, 'result is not null') - assert.equal(Object.keys(result).length, 14, 'result has 14 properties') - assert.ok(result.time > time, 'result.time seems correct') - assert.equal(result.device_id, 'mock device id', 'result.device_id is correct') - assert.equal(result.entrypoint, 'mock entry point') - assert.equal(result.flow_id, 'mock flow id', 'result.flow_id is correct') - assert.ok(result.flow_time > 0, 'result.flow_time is greater than zero') - assert.ok(result.flow_time < time, 'result.flow_time is less than the current time') - assert.equal(result.flowBeginTime, time, 'result.flowBeginTime is correct') - assert.equal(result.flowCompleteSignal, 'mock flow complete signal', 'result.flowCompleteSignal is correct') - assert.equal(result.flowType, 'mock flow type', 'result.flowType is correct') - assert.equal(result.service, 'mock service', 'result.service is correct') - assert.equal(result.utm_campaign, 'mock utm_campaign', 'result.utm_campaign is correct') - assert.equal(result.utm_content, 'mock utm_content', 'result.utm_content is correct') - assert.equal(result.utm_medium, 'mock utm_medium', 'result.utm_medium is correct') - assert.equal(result.utm_source, 'mock utm_source', 'result.utm_source is correct') - assert.equal(result.utm_term, 'mock utm_term', 'result.utm_term is correct') + assert.equal(typeof result, 'object', 'result is object'); + assert.notEqual(result, null, 'result is not null'); + assert.equal(Object.keys(result).length, 14, 'result has 14 properties'); + assert.ok(result.time > time, 'result.time seems correct'); + assert.equal(result.device_id, 'mock device id', 'result.device_id is correct'); + assert.equal(result.entrypoint, 'mock entry point'); + assert.equal(result.flow_id, 'mock flow id', 'result.flow_id is correct'); + assert.ok(result.flow_time > 0, 'result.flow_time is greater than zero'); + assert.ok(result.flow_time < time, 'result.flow_time is less than the current time'); + assert.equal(result.flowBeginTime, time, 'result.flowBeginTime is correct'); + assert.equal(result.flowCompleteSignal, 'mock flow complete signal', 'result.flowCompleteSignal is correct'); + assert.equal(result.flowType, 'mock flow type', 'result.flowType is correct'); + assert.equal(result.service, 'mock service', 'result.service is correct'); + assert.equal(result.utm_campaign, 'mock utm_campaign', 'result.utm_campaign is correct'); + assert.equal(result.utm_content, 'mock utm_content', 'result.utm_content is correct'); + assert.equal(result.utm_medium, 'mock utm_medium', 'result.utm_medium is correct'); + assert.equal(result.utm_source, 'mock utm_source', 'result.utm_source is correct'); + assert.equal(result.utm_term, 'mock utm_term', 'result.utm_term is correct'); - assert.equal(cache.get.callCount, 0, 'cache.get was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.equal(cache.get.callCount, 0, 'cache.get was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it( 'metricsContext.gather with DNT header', @@ -488,18 +488,18 @@ describe('metricsContext', () => { }) } }, {}).then(function (result) { - assert.equal(Object.keys(result).length, 8, 'result has 8 properties') - assert.isUndefined(result.entrypoint) - assert.equal(result.utm_campaign, undefined, 'result.utm_campaign is undefined') - assert.equal(result.utm_content, undefined, 'result.utm_content is undefined') - assert.equal(result.utm_medium, undefined, 'result.utm_medium is undefined') - assert.equal(result.utm_source, undefined, 'result.utm_source is undefined') - assert.equal(result.utm_term, undefined, 'result.utm_term is undefined') + assert.equal(Object.keys(result).length, 8, 'result has 8 properties'); + assert.isUndefined(result.entrypoint); + assert.equal(result.utm_campaign, undefined, 'result.utm_campaign is undefined'); + assert.equal(result.utm_content, undefined, 'result.utm_content is undefined'); + assert.equal(result.utm_medium, undefined, 'result.utm_medium is undefined'); + assert.equal(result.utm_source, undefined, 'result.utm_source is undefined'); + assert.equal(result.utm_term, undefined, 'result.utm_term is undefined'); - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); } - ) + ); it( 'metricsContext.gather with bad flowBeginTime', @@ -511,87 +511,87 @@ describe('metricsContext', () => { }) } }, {}).then(function (result) { - assert.equal(typeof result, 'object', 'result is object') - assert.notEqual(result, null, 'result is not null') - assert.strictEqual(result.flow_time, 0, 'result.time is zero') + assert.equal(typeof result, 'object', 'result is object'); + assert.notEqual(result, null, 'result is not null'); + assert.strictEqual(result.flow_time, 0, 'result.time is zero'); - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.error.callCount, 0, 'log.error was not called'); - }) + }); } - ) + ); it('metricsContext.propagate', () => { - results.get = P.resolve('wibble') - results.add = P.resolve() + results.get = P.resolve('wibble'); + results.add = P.resolve(); const oldToken = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; const newToken = { uid: Array(64).fill('d').join(''), id: 'bar' - } + }; return metricsContext.propagate(oldToken, newToken) .then(() => { - assert.equal(cache.get.callCount, 1) - let args = cache.get.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], hashToken(oldToken)) + assert.equal(cache.get.callCount, 1); + let args = cache.get.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], hashToken(oldToken)); - assert.equal(cache.add.callCount, 1) - args = cache.add.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], hashToken(newToken)) - assert.equal(args[1], 'wibble') + assert.equal(cache.add.callCount, 1); + args = cache.add.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], hashToken(newToken)); + assert.equal(args[1], 'wibble'); - assert.equal(cache.del.callCount, 0) - assert.equal(log.warn.callCount, 0) - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(cache.del.callCount, 0); + assert.equal(log.warn.callCount, 0); + assert.equal(log.error.callCount, 0); + }); + }); it('metricsContext.propagate with clashing data', () => { - results.get = P.resolve('wibble') - results.add = P.reject('blee') + results.get = P.resolve('wibble'); + results.add = P.reject('blee'); const oldToken = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; const newToken = { uid: Array(64).fill('d').join(''), id: 'bar' - } + }; return metricsContext.propagate(oldToken, newToken) .then(() => { - assert.equal(cache.get.callCount, 1) - assert.equal(cache.add.callCount, 1) - assert.equal(log.warn.callCount, 1) - assert.equal(cache.del.callCount, 0) - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(cache.get.callCount, 1); + assert.equal(cache.add.callCount, 1); + assert.equal(log.warn.callCount, 1); + assert.equal(cache.del.callCount, 0); + assert.equal(log.error.callCount, 0); + }); + }); it('metricsContext.propagate with get error', () => { - results.get = P.reject('wibble') - results.add = P.resolve() + results.get = P.reject('wibble'); + results.add = P.resolve(); const oldToken = { uid: Array(64).fill('c').join(''), id: 'foo' - } + }; const newToken = { uid: Array(64).fill('d').join(''), id: 'bar' - } + }; return metricsContext.propagate(oldToken, newToken) .then(() => { - assert.equal(cache.get.callCount, 1) - assert.equal(log.error.callCount, 1) - assert.equal(cache.add.callCount, 0) - assert.equal(log.warn.callCount, 0) - assert.equal(cache.del.callCount, 0) - }) - }) + assert.equal(cache.get.callCount, 1); + assert.equal(log.error.callCount, 1); + assert.equal(cache.add.callCount, 0); + assert.equal(log.warn.callCount, 0); + assert.equal(cache.del.callCount, 0); + }); + }); it( 'metricsContext.clear with token', @@ -599,46 +599,46 @@ describe('metricsContext', () => { const token = { uid: Array(64).fill('7').join(''), id: 'wibble' - } + }; return metricsContext.clear.call({ auth: { credentials: token } }).then(() => { - assert.equal(cache.del.callCount, 1, 'cache.del was called once') - assert.equal(cache.del.args[0].length, 1, 'cache.del was passed one argument') - assert.equal(cache.del.args[0][0], hashToken(token), 'cache.del argument was correct') - }) + assert.equal(cache.del.callCount, 1, 'cache.del was called once'); + assert.equal(cache.del.args[0].length, 1, 'cache.del was passed one argument'); + assert.equal(cache.del.args[0][0], hashToken(token), 'cache.del argument was correct'); + }); } - ) + ); it( 'metricsContext.clear with fake token', () => { - const uid = Array(64).fill('6').join('') - const id = 'blee' + const uid = Array(64).fill('6').join(''); + const id = 'blee'; return metricsContext.clear.call({ payload: { uid: uid, code: id } }).then(() => { - assert.equal(cache.del.callCount, 1, 'cache.del was called once') - assert.equal(cache.del.args[0].length, 1, 'cache.del was passed one argument') - assert.deepEqual(cache.del.args[0][0], hashToken({ uid, id }), 'cache.del argument was correct') - }) + assert.equal(cache.del.callCount, 1, 'cache.del was called once'); + assert.equal(cache.del.args[0].length, 1, 'cache.del was passed one argument'); + assert.deepEqual(cache.del.args[0][0], hashToken({ uid, id }), 'cache.del argument was correct'); + }); } - ) + ); it( 'metricsContext.clear with no token', () => { return metricsContext.clear.call({}).then(() => { - assert.equal(cache.del.callCount, 0, 'cache.del was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }).catch(err => assert.fail(err)) + assert.equal(cache.del.callCount, 0, 'cache.del was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }).catch(err => assert.fail(err)); } - ) + ); it( 'metricsContext.clear with memcached error', @@ -646,8 +646,8 @@ describe('metricsContext', () => { const token = { uid: Array(64).fill('7').join(''), id: 'wibble' - } - results.del = P.reject(new Error('blee')) + }; + results.del = P.reject(new Error('blee')); return metricsContext.clear.call({ auth: { credentials: token @@ -655,28 +655,28 @@ describe('metricsContext', () => { }) .then(() => assert.fail('call to metricsContext.clear should have failed')) .catch(err => { - assert.equal(err.message, 'blee', 'metricsContext.clear should have rejected with memcached error') - assert.equal(cache.del.callCount, 1, 'cache.del was called once') - }) + assert.equal(err.message, 'blee', 'metricsContext.clear should have rejected with memcached error'); + assert.equal(cache.del.callCount, 1, 'cache.del was called once'); + }); } - ) + ); it( 'metricsContext.validate with valid data', () => { - const flowBeginTime = 1451566800000 - const flowId = '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f' + const flowBeginTime = 1451566800000; + const flowId = '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f'; sinon.stub(Date, 'now').callsFake(function() { - return flowBeginTime + 59999 - }) - const mockLog = mocks.mockLog() + return flowBeginTime + 59999; + }); + const mockLog = mocks.mockLog(); const mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'S3CR37' } - } + }; const mockRequest = { headers: { 'user-agent': 'test-agent' @@ -687,66 +687,66 @@ describe('metricsContext', () => { flowBeginTime } } - } + }; - const metricsContext = require(modulePath)(mockLog, mockConfig) - const result = metricsContext.validate.call(mockRequest) + const metricsContext = require(modulePath)(mockLog, mockConfig); + const result = metricsContext.validate.call(mockRequest); - assert.strictEqual(result, true, 'result was true') - assert.equal(mockRequest.payload.metricsContext.flowId, '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f', 'valid flow data was not removed') - assert.equal(mockLog.warn.callCount, 0, 'log.warn was not called') - assert.equal(mockLog.info.callCount, 1, 'log.info was called once') - assert.lengthOf(mockLog.info.args[0], 2) - assert.equal(mockLog.info.args[0][0], 'metrics.context.validate') + assert.strictEqual(result, true, 'result was true'); + assert.equal(mockRequest.payload.metricsContext.flowId, '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f', 'valid flow data was not removed'); + assert.equal(mockLog.warn.callCount, 0, 'log.warn was not called'); + assert.equal(mockLog.info.callCount, 1, 'log.info was called once'); + assert.lengthOf(mockLog.info.args[0], 2); + assert.equal(mockLog.info.args[0][0], 'metrics.context.validate'); assert.deepEqual(mockLog.info.args[0][1], { valid: true - }, 'log.info was passed correct argument') + }, 'log.info was passed correct argument'); - Date.now.restore() + Date.now.restore(); } - ) + ); it( 'metricsContext.validate with missing payload', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' } - } + }; - var metricsContext = require('../../../lib/metrics/context')(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require('../../../lib/metrics/context')(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'missing payload' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with missing data bundle', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' @@ -755,32 +755,32 @@ describe('metricsContext', () => { email: 'test@example.com' // note that 'metricsContext' key is absent } - } + }; - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'missing context' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with missing flowId', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' @@ -790,33 +790,33 @@ describe('metricsContext', () => { flowBeginTime: Date.now() - 1 } } - } + }; - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowBeginTime, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowBeginTime, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'missing flowId' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with missing flowBeginTime', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' @@ -826,33 +826,33 @@ describe('metricsContext', () => { flowId: 'f1031df1031df1031df1031df1031df1031df1031df1031df1031df1031df103' } } - } + }; - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'missing flowBeginTime' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with flowBeginTime that is too old', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' @@ -863,33 +863,33 @@ describe('metricsContext', () => { flowBeginTime: Date.now() - mockConfig.metrics.flow_id_expiry - 1 } } - } + }; - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'expired flowBeginTime' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with an invalid flow signature', () => { - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'test' } - } + }; var mockRequest = { headers: { 'user-agent': 'test-agent' @@ -900,36 +900,36 @@ describe('metricsContext', () => { flowBeginTime: Date.now() - 1 } } - } + }; - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'invalid signature' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with flow signature from different key', () => { - var expectedTime = 1451566800000 - var expectedSalt = '4d6f7a696c6c6146697265666f782121' - var expectedHmac = '2a204a6d26b009b26b3116f643d84c6f' - var mockLog = mocks.mockLog() + var expectedTime = 1451566800000; + var expectedSalt = '4d6f7a696c6c6146697265666f782121'; + var expectedHmac = '2a204a6d26b009b26b3116f643d84c6f'; + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'ThisIsTheWrongKey' } - } + }; var mockRequest = { headers: { 'user-agent': 'Firefox' @@ -940,43 +940,43 @@ describe('metricsContext', () => { flowBeginTime: expectedTime } } - } + }; sinon.stub(Date, 'now').callsFake(function() { - return expectedTime + 20000 - }) + return expectedTime + 20000; + }); try { - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); } finally { - Date.now.restore() + Date.now.restore(); } - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'invalid signature' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with flow signature from different timestamp', () => { - var expectedTime = 1451566800000 - var expectedSalt = '4d6f7a696c6c6146697265666f782121' - var expectedHmac = '2a204a6d26b009b26b3116f643d84c6f' - var mockLog = mocks.mockLog() + var expectedTime = 1451566800000; + var expectedSalt = '4d6f7a696c6c6146697265666f782121'; + var expectedHmac = '2a204a6d26b009b26b3116f643d84c6f'; + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'S3CR37' } - } + }; var mockRequest = { headers: { 'user-agent': 'Firefox' @@ -987,46 +987,46 @@ describe('metricsContext', () => { flowBeginTime: expectedTime - 1 } } - } + }; sinon.stub(Date, 'now').callsFake(function() { - return expectedTime + 20000 - }) + return expectedTime + 20000; + }); try { - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); } finally { - Date.now.restore() + Date.now.restore(); } - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'invalid signature' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it( 'metricsContext.validate with flow signature including user agent', () => { - var expectedTime = 1451566800000 + var expectedTime = 1451566800000; // This is the correct signature for the *old* recipe, where we used // to include the user agent string in the hash. The test is expected // to fail because we don't support that recipe any more. - var expectedSalt = '4d6f7a696c6c6146697265666f782121' - var expectedHmac = 'c89d56556d22039fbbf54d34e0baf206' - var mockLog = mocks.mockLog() + var expectedSalt = '4d6f7a696c6c6146697265666f782121'; + var expectedHmac = 'c89d56556d22039fbbf54d34e0baf206'; + var mockLog = mocks.mockLog(); var mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'S3CR37' } - } + }; var mockRequest = { headers: { 'user-agent': 'Firefox' @@ -1037,41 +1037,41 @@ describe('metricsContext', () => { flowBeginTime: expectedTime } } - } + }; sinon.stub(Date, 'now').callsFake(function() { - return expectedTime + 20000 - }) + return expectedTime + 20000; + }); try { - var metricsContext = require(modulePath)(mockLog, mockConfig) - var valid = metricsContext.validate.call(mockRequest) + var metricsContext = require(modulePath)(mockLog, mockConfig); + var valid = metricsContext.validate.call(mockRequest); } finally { - Date.now.restore() + Date.now.restore(); } - assert(! valid, 'the data is treated as invalid') - assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed') - assert.equal(mockLog.info.callCount, 0, 'log.info was not called') - assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once') + assert(! valid, 'the data is treated as invalid'); + assert(! mockRequest.payload.metricsContext.flowId, 'the invalid flow data was removed'); + assert.equal(mockLog.info.callCount, 0, 'log.info was not called'); + assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once'); assert.ok(mockLog.warn.calledWithExactly('metrics.context.validate', { valid: false, reason: 'invalid signature' - }), 'log.warn was called with the expected log data') + }), 'log.warn was called with the expected log data'); } - ) + ); it('metricsContext.validate with flow signature compared without user agent', () => { - const flowBeginTime = 1451566800000 - const flowId = '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f' - sinon.stub(Date, 'now').callsFake(() => flowBeginTime + 59999) - const mockLog = mocks.mockLog() + const flowBeginTime = 1451566800000; + const flowId = '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f'; + sinon.stub(Date, 'now').callsFake(() => flowBeginTime + 59999); + const mockLog = mocks.mockLog(); const mockConfig = { memcached: {}, metrics: { flow_id_expiry: 60000, flow_id_key: 'S3CR37' } - } + }; const mockRequest = { headers: { 'user-agent': 'some other user agent' @@ -1082,18 +1082,18 @@ describe('metricsContext', () => { flowBeginTime } } - } + }; - const metricsContext = require(modulePath)(mockLog, mockConfig) - const result = metricsContext.validate.call(mockRequest) + const metricsContext = require(modulePath)(mockLog, mockConfig); + const result = metricsContext.validate.call(mockRequest); - assert.strictEqual(result, true, 'validate returned true') - assert.equal(mockRequest.payload.metricsContext.flowId, '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f', 'valid flow data was not removed') - assert.equal(mockLog.warn.callCount, 0, 'log.warn was not called') - assert.equal(mockLog.info.callCount, 1, 'log.info was called once') + assert.strictEqual(result, true, 'validate returned true'); + assert.equal(mockRequest.payload.metricsContext.flowId, '1234567890abcdef1234567890abcdef06146f1d05e7ae215885a4e45b66ff1f', 'valid flow data was not removed'); + assert.equal(mockLog.warn.callCount, 0, 'log.warn was not called'); + assert.equal(mockLog.info.callCount, 1, 'log.info was called once'); - Date.now.restore() - }) + Date.now.restore(); + }); it( 'setFlowCompleteSignal', @@ -1102,24 +1102,24 @@ describe('metricsContext', () => { payload: { metricsContext: {} } - } - metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee') + }; + metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee'); assert.deepEqual(request.payload.metricsContext, { flowCompleteSignal: 'wibble', flowType: 'blee' - }, 'flowCompleteSignal and flowType were set correctly') + }, 'flowCompleteSignal and flowType were set correctly'); } - ) + ); it( 'setFlowCompleteSignal without metricsContext', () => { const request = { payload: {} - } - metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee') - assert.deepEqual(request.payload, {}, 'flowCompleteSignal and flowType were not set') + }; + metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee'); + assert.deepEqual(request.payload, {}, 'flowCompleteSignal and flowType were not set'); } - ) + ); -}) +}); diff --git a/test/local/metrics/events.js b/test/local/metrics/events.js index da2c638a..b39db366 100644 --- a/test/local/metrics/events.js +++ b/test/local/metrics/events.js @@ -2,70 +2,70 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') +const { assert } = require('chai'); +const sinon = require('sinon'); const log = { activityEvent: sinon.spy(), amplitudeEvent: sinon.spy(), error: sinon.spy(), flowEvent: sinon.spy() -} +}; const events = require('../../../lib/metrics/events')(log, { oauth: { clientIds: {} } -}) -const mocks = require('../../mocks') -const P = require('../../../lib/promise') +}); +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); describe('metrics/events', () => { afterEach(() => { - log.activityEvent.resetHistory() - log.amplitudeEvent.resetHistory() - log.error.resetHistory() - log.flowEvent.resetHistory() - }) + log.activityEvent.resetHistory(); + log.amplitudeEvent.resetHistory(); + log.error.resetHistory(); + log.flowEvent.resetHistory(); + }); it('interface is correct', () => { - assert.equal(typeof events, 'object', 'events is object') - assert.notEqual(events, null, 'events is not null') - assert.equal(Object.keys(events).length, 2, 'events has 2 properties') + assert.equal(typeof events, 'object', 'events is object'); + assert.notEqual(events, null, 'events is not null'); + assert.equal(Object.keys(events).length, 2, 'events has 2 properties'); - assert.equal(typeof events.emit, 'function', 'events.emit is function') - assert.equal(events.emit.length, 2, 'events.emit expects 2 arguments') + assert.equal(typeof events.emit, 'function', 'events.emit is function'); + assert.equal(events.emit.length, 2, 'events.emit expects 2 arguments'); - assert.equal(typeof events.emitRouteFlowEvent, 'function', 'events.emitRouteFlowEvent is function') - assert.equal(events.emitRouteFlowEvent.length, 1, 'events.emitRouteFlowEvent expects 1 argument') + assert.equal(typeof events.emitRouteFlowEvent, 'function', 'events.emitRouteFlowEvent is function'); + assert.equal(events.emitRouteFlowEvent.length, 1, 'events.emitRouteFlowEvent expects 1 argument'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - }) + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + }); it('.emit with missing event', () => { - const metricsContext = mocks.mockMetricsContext() - const request = mocks.mockRequest({ metricsContext }) + const metricsContext = mocks.mockMetricsContext(); + const request = mocks.mockRequest({ metricsContext }); return events.emit.call(request, '', {}) .then(() => { - assert.equal(log.error.callCount, 1, 'log.error was called once') - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'metricsEvents.emit') + assert.equal(log.error.callCount, 1, 'log.error was called once'); + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'metricsEvents.emit'); assert.deepEqual(args[1], { missingEvent: true - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - }) - }) + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + }); + }); it('.emit with activity event', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { 'user-agent': 'foo' @@ -74,15 +74,15 @@ describe('metrics/events', () => { query: { service: 'bar' } - }) + }); const data = { uid: 'baz' - } + }; return events.emit.call(request, 'device.created', data) .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - let args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + let args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.created', @@ -90,79 +90,79 @@ describe('metrics/events', () => { userAgent: 'foo', service: 'bar', uid: 'baz' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') - args = metricsContext.gather.args[0] - assert.equal(args.length, 1, 'metricsContext.gather was passed one argument') - assert.deepEqual(args[0], {}, 'metricsContext.gather was passed an empty object') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); + args = metricsContext.gather.args[0]; + assert.equal(args.length, 1, 'metricsContext.gather was passed one argument'); + assert.deepEqual(args[0], {}, 'metricsContext.gather was passed an empty object'); - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emit with activity event and missing data', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, payload: { service: 'bar' } - }) + }); return events.emit.call(request, 'device.created') .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - const args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + const args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.created', region: 'California', userAgent: 'test user-agent', service: 'bar' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emit with activity event and missing uid', () => { - const metricsContext = mocks.mockMetricsContext() - const request = mocks.mockRequest({ metricsContext }) + const metricsContext = mocks.mockMetricsContext(); + const request = mocks.mockRequest({ metricsContext }); return events.emit.call(request, 'device.created', {}) .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - const args = log.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + const args = log.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.created', region: 'California', service: undefined, userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emit with flow event', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ credentials: { uid: 'deadbeef' @@ -182,19 +182,19 @@ describe('metrics/events', () => { }, service: 'baz' } - }) + }); return events.emit.call(request, 'email.verification.sent') .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') - let args = metricsContext.gather.args[0] - assert.equal(args.length, 1, 'metricsContext.gather was passed one argument') - assert.equal(args[0].event, 'email.verification.sent', 'metricsContext.gather was passed event') - assert.equal(args[0].locale, request.app.locale, 'metricsContext.gather was passed locale') - assert.equal(args[0].userAgent, request.headers['user-agent'], 'metricsContext.gather was passed user agent') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); + let args = metricsContext.gather.args[0]; + assert.equal(args.length, 1, 'metricsContext.gather was passed one argument'); + assert.equal(args[0].event, 'email.verification.sent', 'metricsContext.gather was passed event'); + assert.equal(args[0].locale, request.app.locale, 'metricsContext.gather was passed locale'); + assert.equal(args[0].userAgent, request.headers['user-agent'], 'metricsContext.gather was passed user agent'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'email.verification.sent', @@ -214,21 +214,21 @@ describe('metrics/events', () => { utm_medium: 'utm medium', utm_source: 'utm source', utm_term: 'utm term' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event and no session token', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = { app: { devices: P.resolve(), @@ -255,14 +255,14 @@ describe('metrics/events', () => { flowCompleteSignal: 'account.signed' } } - } + }; return events.emit.call(request, 'email.verification.sent') .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - const args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + const args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United Kingdom', event: 'email.verification.sent', @@ -275,21 +275,21 @@ describe('metrics/events', () => { region: 'Dorset', time, userAgent: 'foo' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event and string uid', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -303,14 +303,14 @@ describe('metrics/events', () => { flowCompleteSignal: 'account.signed' } } - }) + }); return events.emit.call(request, 'email.verification.sent', { uid: 'deadbeef' }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - const args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + const args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'email.verification.sent', @@ -324,21 +324,21 @@ describe('metrics/events', () => { time, uid: 'deadbeef', userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event and buffer uid', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -352,14 +352,14 @@ describe('metrics/events', () => { flowCompleteSignal: 'account.signed' } } - }) + }); return events.emit.call(request, 'email.verification.sent', { uid: 'deadbeef' }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - const args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + const args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'email.verification.sent', @@ -373,21 +373,21 @@ describe('metrics/events', () => { time, uid: 'deadbeef', userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event and null uid', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -401,14 +401,14 @@ describe('metrics/events', () => { flowCompleteSignal: 'account.signed' } } - }) + }); return events.emit.call(request, 'email.verification.sent', { uid: null }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - const args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + const args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'email.verification.sent', @@ -421,21 +421,21 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event that matches complete signal', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -451,12 +451,12 @@ describe('metrics/events', () => { flowType: 'registration' } } - }) + }); return events.emit.call(request, 'email.verification.sent', { locale: 'baz', uid: 'qux' }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 2, 'log.flowEvent was called twice') + assert.equal(log.flowEvent.callCount, 2, 'log.flowEvent was called twice'); assert.deepEqual(log.flowEvent.args[0][0], { country: 'United States', event: 'email.verification.sent', @@ -470,7 +470,7 @@ describe('metrics/events', () => { time, uid: 'qux', userAgent: 'test user-agent' - }, 'argument was event data first time') + }, 'argument was event data first time'); assert.deepEqual(log.flowEvent.args[1][0], { country: 'United States', event: 'flow.complete', @@ -484,24 +484,24 @@ describe('metrics/events', () => { time, uid: 'qux', userAgent: 'test user-agent' - }, 'argument was complete event data second time') + }, 'argument was complete event data second time'); - assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once') - assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument') - assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_reg - complete', 'log.amplitudeEvent was passed correct event_type') + assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once'); + assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument'); + assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_reg - complete', 'log.amplitudeEvent was passed correct event_type'); - assert.equal(metricsContext.clear.callCount, 1, 'metricsContext.clear was called once') - assert.equal(metricsContext.clear.args[0].length, 0, 'metricsContext.clear was passed no arguments') + assert.equal(metricsContext.clear.callCount, 1, 'metricsContext.clear was called once'); + assert.equal(metricsContext.clear.args[0].length, 0, 'metricsContext.clear was passed no arguments'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with flow event and missing headers', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = { app: { devices: P.resolve(), @@ -516,29 +516,29 @@ describe('metrics/events', () => { flowBeginTime: Date.now() - 1 } } - } + }; return events.emit.call(request, 'email.verification.sent') .then(() => { - assert.equal(log.error.callCount, 1, 'log.error was called once') - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'metricsEvents.emitFlowEvent') + assert.equal(log.error.callCount, 1, 'log.error was called once'); + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'metricsEvents.emitFlowEvent'); assert.deepEqual(args[1], { event: 'email.verification.sent', badRequest: true - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - }) - }) + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + }); + }); it('.emit with flow event and missing flowId', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, payload: { @@ -546,29 +546,29 @@ describe('metrics/events', () => { flowBeginTime: Date.now() - 1 } } - }) + }); return events.emit.call(request, 'email.verification.sent') .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.error.callCount, 1, 'log.error was called once') - assert.equal(log.error.args[0][0], 'metricsEvents.emitFlowEvent') + assert.equal(log.error.callCount, 1, 'log.error was called once'); + assert.equal(log.error.args[0][0], 'metricsEvents.emitFlowEvent'); assert.deepEqual(log.error.args[0][1], { event: 'email.verification.sent', missingFlowId: true - }, 'argument was correct') + }, 'argument was correct'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - }) - }) + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + }); + }); it('.emit with hybrid activity/flow event', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -581,13 +581,13 @@ describe('metrics/events', () => { flowBeginTime: time - 42 } } - }) + }); const data = { uid: 'baz' - } + }; return events.emit.call(request, 'account.keyfetch', data) .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); assert.deepEqual(log.activityEvent.args[0][0], { country: 'United States', event: 'account.keyfetch', @@ -595,11 +595,11 @@ describe('metrics/events', () => { userAgent: 'test user-agent', service: undefined, uid: 'baz' - }, 'activity event data was correct') + }, 'activity event data was correct'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); assert.deepEqual(log.flowEvent.args[0][0], { country: 'United States', time, @@ -613,18 +613,18 @@ describe('metrics/events', () => { region: 'California', uid: 'baz', userAgent: 'test user-agent' - }, 'flow event data was correct') + }, 'flow event data was correct'); - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emit with optional flow event and missing flowId', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, payload: { @@ -632,50 +632,50 @@ describe('metrics/events', () => { flowBeginTime: Date.now() - 1 } } - }) + }); const data = { uid: 'bar' - } + }; return events.emit.call(request, 'account.keyfetch', data) .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emit with content-server account.signed event', () => { - const flowBeginTime = Date.now() - 1 + const flowBeginTime = Date.now() - 1; const metricsContext = mocks.mockMetricsContext({ gather: sinon.spy(() => ({ device_id: 'foo', flow_id: 'bar', flowBeginTime })) - }) + }); const request = mocks.mockRequest({ metricsContext, query: { service: 'content-server' } - }) + }); const data = { uid: 'baz' - } + }; return events.emit.call(request, 'account.signed', data) .then(() => { - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); - assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once') - assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument') - assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_activity - cert_signed', 'log.amplitudeEvent was passed correct event_type') - assert.equal(log.amplitudeEvent.args[0][0].device_id, 'foo', 'log.amplitudeEvent was passed correct device_id') - assert.equal(log.amplitudeEvent.args[0][0].session_id, flowBeginTime, 'log.amplitudeEvent was passed correct session_id') - assert.deepEqual(log.amplitudeEvent.args[0][0].event_properties, {}, 'log.amplitudeEvent was passed correct event properties') + assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once'); + assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument'); + assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_activity - cert_signed', 'log.amplitudeEvent was passed correct event_type'); + assert.equal(log.amplitudeEvent.args[0][0].device_id, 'foo', 'log.amplitudeEvent was passed correct device_id'); + assert.equal(log.amplitudeEvent.args[0][0].session_id, flowBeginTime, 'log.amplitudeEvent was passed correct session_id'); + assert.deepEqual(log.amplitudeEvent.args[0][0].event_properties, {}, 'log.amplitudeEvent was passed correct event properties'); assert.deepEqual(log.amplitudeEvent.args[0][0].user_properties, { flow_id: 'bar', sync_active_devices_day: 0, @@ -684,18 +684,18 @@ describe('metrics/events', () => { sync_device_count: 0, ua_browser: request.app.ua.browser, ua_version: request.app.ua.browserVersion - }, 'log.amplitudeEvent was passed correct user properties') + }, 'log.amplitudeEvent was passed correct user properties'); - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emit with sync account.signed event', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, payload: { @@ -707,27 +707,27 @@ describe('metrics/events', () => { query: { service: 'sync' } - }) + }); const data = { uid: 'baz' - } + }; return events.emit.call(request, 'account.signed', data) .then(() => { - assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once') - assert.equal(log.amplitudeEvent.args[0][0].event_properties.service, 'sync', 'log.amplitudeEvent was passed correct service') + assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once'); + assert.equal(log.amplitudeEvent.args[0][0].event_properties.service, 'sync', 'log.amplitudeEvent was passed correct service'); - assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emitRouteFlowEvent with matching route and response.statusCode', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -742,15 +742,15 @@ describe('metrics/events', () => { } }, received: time - 42 - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 200 }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 2, 'log.flowEvent was called twice') + assert.equal(log.flowEvent.callCount, 2, 'log.flowEvent was called twice'); - let args = log.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument first time') + let args = log.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument first time'); assert.deepEqual(args[0], { country: 'United States', event: 'route./account/create.200', @@ -763,10 +763,10 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was route summary event data') + }, 'argument was route summary event data'); - args = log.flowEvent.args[1] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument second time') + args = log.flowEvent.args[1]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument second time'); assert.deepEqual(args[0], { country: 'United States', event: 'route.performance./account/create', @@ -779,20 +779,20 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was performance event data') + }, 'argument was performance event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emitRouteFlowEvent with matching route and response.output.statusCode', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -806,12 +806,12 @@ describe('metrics/events', () => { flowBeginTime: time - 1000 } } - }) + }); return events.emitRouteFlowEvent.call(request, { output: { statusCode: 399 } }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); assert.deepEqual(log.flowEvent.args[0][0], { country: 'United States', event: 'route./account/login.399', @@ -824,20 +824,20 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emitRouteFlowEvent with matching route and 400 statusCode', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -851,12 +851,12 @@ describe('metrics/events', () => { flowBeginTime: time - 1000 } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 400 }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); assert.deepEqual(log.flowEvent.args[0][0], { country: 'United States', event: 'route./recovery_email/resend_code.400.999', @@ -869,20 +869,20 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emitRouteFlowEvent with matching route and 404 statusCode', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -896,23 +896,23 @@ describe('metrics/events', () => { flowBeginTime: time - 1000 } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 404 }) .then(() => { - assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) - }) + Date.now.restore(); + }); + }); it('.emitRouteFlowEvent with matching route and 400 statusCode with errno', () => { - const time = Date.now() - sinon.stub(Date, 'now').callsFake(() => time) - const metricsContext = mocks.mockMetricsContext() + const time = Date.now(); + sinon.stub(Date, 'now').callsFake(() => time); + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ headers: { dnt: '1', @@ -926,12 +926,12 @@ describe('metrics/events', () => { flowBeginTime: time - 1000 } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 400, errno: 42 }) .then(() => { - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); assert.deepEqual(log.flowEvent.args[0][0], { country: 'United States', event: 'route./account/destroy.400.42', @@ -944,14 +944,14 @@ describe('metrics/events', () => { region: 'California', time, userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); }).finally(() => { - Date.now.restore() - }) + Date.now.restore(); + }); }) ;[ @@ -963,7 +963,7 @@ describe('metrics/events', () => { '/recovery_email/status', '/recoveryKey/0123456789abcdef0123456789ABCDEF' ].forEach(route => it(`.emitRouteFlowEvent with ${route}`, () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, path: `/v1${route}`, @@ -973,19 +973,19 @@ describe('metrics/events', () => { flowBeginTime: Date.now() - 1000 } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 200 }) .then(() => { - assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - })) + assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + })); it('.emitRouteFlowEvent with matching route and invalid metrics context', () => { - const metricsContext = mocks.mockMetricsContext({ validate: sinon.spy(() => false) }) + const metricsContext = mocks.mockMetricsContext({ validate: sinon.spy(() => false) }); const request = mocks.mockRequest({ metricsContext, path: '/v1/account/destroy', @@ -995,22 +995,22 @@ describe('metrics/events', () => { flowBeginTime: Date.now() } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 400, errno: 107 }) .then(() => { - assert.equal(metricsContext.validate.callCount, 1, 'metricsContext.validate was called once') - assert.equal(metricsContext.validate.args[0].length, 0, 'metricsContext.validate was passed no arguments') + assert.equal(metricsContext.validate.callCount, 1, 'metricsContext.validate was called once'); + assert.equal(metricsContext.validate.args[0].length, 0, 'metricsContext.validate was passed no arguments'); - assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called') - assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called') - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called'); + assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called'); + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('.emitRouteFlowEvent with missing parameter error but valid metrics context', () => { - const metricsContext = mocks.mockMetricsContext() + const metricsContext = mocks.mockMetricsContext(); const request = mocks.mockRequest({ metricsContext, path: '/v1/account/destroy', @@ -1020,16 +1020,16 @@ describe('metrics/events', () => { flowBeginTime: Date.now() } } - }) + }); return events.emitRouteFlowEvent.call(request, { statusCode: 400, errno: 107 }) .then(() => { - assert.equal(metricsContext.validate.callCount, 1, 'metricsContext.validate was called once') - assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once') - assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once') + assert.equal(metricsContext.validate.callCount, 1, 'metricsContext.validate was called once'); + assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once'); + assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once'); - assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called') - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) -}) + assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called'); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); +}); diff --git a/test/local/mock-sns.js b/test/local/mock-sns.js index 71b2dbff..7c4d143f 100644 --- a/test/local/mock-sns.js +++ b/test/local/mock-sns.js @@ -2,61 +2,61 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); // noPreserveCache is needed to prevent the mock mailer from // being used for all future tests that include mock-nexmo. -const proxyquire = require('proxyquire').noPreserveCache() -const sinon = require('sinon') -const config = require('../../config').getProperties() +const proxyquire = require('proxyquire').noPreserveCache(); +const sinon = require('sinon'); +const config = require('../../config').getProperties(); describe('mock-sns', () => { - let mailer - let mockSNS + let mailer; + let mockSNS; const MockSNS = proxyquire('../../test/mock-sns', { nodemailer: { createTransport: () => mailer } - }) + }); before(() => { mailer = { sendMail: sinon.spy((config, callback) => callback()) - } - mockSNS = new MockSNS(null, config) - }) + }; + mockSNS = new MockSNS(null, config); + }); afterEach(() => { - mailer.sendMail.resetHistory() - }) + mailer.sendMail.resetHistory(); + }); it('constructor creates an instance', () => { - assert.ok(mockSNS) - }) + assert.ok(mockSNS); + }); describe('message.sendSms', () => { - let result + let result; beforeEach(() => { return mockSNS.publish({ PhoneNumber: '+019999999999', Message: 'message' - }).promise().then(r => result = r) - }) + }).promise().then(r => result = r); + }); it('returns message id', () => { - assert.deepEqual(result, { MessageId: 'fake message id' }) - }) + assert.deepEqual(result, { MessageId: 'fake message id' }); + }); it('calls mailer.sendMail correctly', () => { - assert.equal(mailer.sendMail.callCount, 1) - const sendConfig = mailer.sendMail.args[0][0] - assert.equal(sendConfig.from, config.smtp.sender) - assert.equal(sendConfig.to, 'sms.+019999999999@restmail.net') - assert.equal(sendConfig.subject, 'MockSNS.publish') - assert.equal(sendConfig.text, 'message') - }) - }) -}) + assert.equal(mailer.sendMail.callCount, 1); + const sendConfig = mailer.sendMail.args[0][0]; + assert.equal(sendConfig.from, config.smtp.sender); + assert.equal(sendConfig.to, 'sms.+019999999999@restmail.net'); + assert.equal(sendConfig.subject, 'MockSNS.publish'); + assert.equal(sendConfig.text, 'message'); + }); + }); +}); diff --git a/test/local/notifier.js b/test/local/notifier.js index 8df9e6c2..0f84e6a2 100644 --- a/test/local/notifier.js +++ b/test/local/notifier.js @@ -2,52 +2,52 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const proxyquire = require('proxyquire') -const { assert } = require('chai') -const sinon = require('sinon') +const proxyquire = require('proxyquire'); +const { assert } = require('chai'); +const sinon = require('sinon'); describe('notifier', () => { const log = { error: sinon.spy(), trace: sinon.spy() - } + }; beforeEach(() => { - log.error.resetHistory() - log.trace.resetHistory() - }) + log.error.resetHistory(); + log.trace.resetHistory(); + }); describe('with sns configuration', () => { - let config, notifier + let config, notifier; beforeEach(() => { config = { get: (key) => { if (key === 'snsTopicArn') { - return 'arn:aws:sns:us-west-2:927034868275:foo' + return 'arn:aws:sns:us-west-2:927034868275:foo'; } } - } + }; notifier = proxyquire(`${ROOT_DIR}/lib/notifier`, { '../config': config - })(log) + })(log); notifier.__sns.publish = sinon.spy((event, cb) => { - cb(null, event) - }) - }) + cb(null, event); + }); + }); it('publishes a correctly-formatted message', () => { notifier.send({ event: 'stuff' - }) + }); - assert.equal(log.trace.args[0][0], 'Notifier.publish') + assert.equal(log.trace.args[0][0], 'Notifier.publish'); assert.deepEqual(log.trace.args[0][1], { data: { TopicArn: 'arn:aws:sns:us-west-2:927034868275:foo', @@ -60,9 +60,9 @@ describe('notifier', () => { } }, success: true - }) - assert.equal(log.error.called, false) - }) + }); + assert.equal(log.error.called, false); + }); it('flattens additional data into the message body', () => { notifier.send({ @@ -71,9 +71,9 @@ describe('notifier', () => { cool: 'stuff', more: 'stuff' } - }) + }); - assert.equal(log.trace.args[0][0], 'Notifier.publish') + assert.equal(log.trace.args[0][0], 'Notifier.publish'); assert.deepEqual(log.trace.args[0][1], { data: { TopicArn: 'arn:aws:sns:us-west-2:927034868275:foo', @@ -86,9 +86,9 @@ describe('notifier', () => { } }, success: true - }) - assert.equal(log.error.called, false) - }) + }); + assert.equal(log.error.called, false); + }); it('includes email domain in message attributes', () => { notifier.send({ @@ -96,9 +96,9 @@ describe('notifier', () => { data: { email: 'testme@example.com' } - }) + }); - assert.equal(log.trace.args[0][0], 'Notifier.publish') + assert.equal(log.trace.args[0][0], 'Notifier.publish'); assert.deepEqual(log.trace.args[0][1], { data: { TopicArn: 'arn:aws:sns:us-west-2:927034868275:foo', @@ -115,37 +115,37 @@ describe('notifier', () => { } }, success: true - }) - assert.equal(log.error.called, false) - }) - }) + }); + assert.equal(log.error.called, false); + }); + }); it('works with disabled configuration', () => { const config = { get: (key) => { if (key === 'snsTopicArn') { - return 'disabled' + return 'disabled'; } } - } + }; const notifier = proxyquire(`${ROOT_DIR}/lib/notifier`, { '../config': config - })(log) + })(log); notifier.send({ event: 'stuff' }, () => { - assert.equal(log.trace.args[0][0], 'Notifier.publish') + assert.equal(log.trace.args[0][0], 'Notifier.publish'); assert.deepEqual(log.trace.args[0][1], { data: { disabled: true }, success: true - }) - assert.equal(log.trace.args[0][1].data.disabled, true) - assert.equal(log.error.called, false) - }) + }); + assert.equal(log.trace.args[0][1].data.disabled, true); + assert.equal(log.error.called, false); + }); - }) + }); -}) +}); diff --git a/test/local/oauthdb.js b/test/local/oauthdb.js index 1841df2f..203a55b6 100644 --- a/test/local/oauthdb.js +++ b/test/local/oauthdb.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const nock = require('nock') -const JWT = require('jsonwebtoken') +const { assert } = require('chai'); +const nock = require('nock'); +const JWT = require('jsonwebtoken'); -const P = require('../../lib/promise') -const oauthdbModule = require('../../lib/oauthdb') -const error = require('../../lib/error') -const { mockLog } = require('../mocks') +const P = require('../../lib/promise'); +const oauthdbModule = require('../../lib/oauthdb'); +const error = require('../../lib/error'); +const { mockLog } = require('../mocks'); const mockConfig = { publicUrl: 'https://accounts.example.com', @@ -20,16 +20,16 @@ const mockConfig = { secretKey: 'secret-key-oh-secret-key', }, domain: 'accounts.example.com' -} +}; -const MOCK_UID = 'ABCDEF' -const MOCK_CLIENT_ID = '0123456789ABCDEF' +const MOCK_UID = 'ABCDEF'; +const MOCK_CLIENT_ID = '0123456789ABCDEF'; const MOCK_CLIENT_INFO = { id: MOCK_CLIENT_ID, name: 'mock client', trusted: false, redirect_uri: 'http://mock.client.com/redirect' -} +}; const LOCKBOX_CLIENT_INFO = { id: 'e7ce535d93522896', @@ -37,52 +37,52 @@ const LOCKBOX_CLIENT_INFO = { trusted: true, image_uri: '', redirect_uri: 'https://lockbox.firefox.com/fxa/android-redirect.html' -} +}; const mockOAuthServer = nock(mockConfig.oauth.url).defaultReplyHeaders({ 'Content-Type': 'application/json' -}) +}); function verifyJWT(token) { return new P((resolve, reject) => { JWT.verify(token, mockConfig.oauth.secretKey, { algorithm: 'HS256' }, (err, claims) => { if (err) { - reject(err) + reject(err); } else { - resolve(claims) + resolve(claims); } - }) - }) + }); + }); } describe('oauthdb', () => { - let oauthdb + let oauthdb; afterEach(async () => { - assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test') + assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test'); if (oauthdb) { - await oauthdb.close() + await oauthdb.close(); } - }) + }); describe('getClientInfo', () => { it('gets client info', async () => { mockOAuthServer.get(`/v1/client/${MOCK_CLIENT_ID}`) - .reply(200, MOCK_CLIENT_INFO) - oauthdb = oauthdbModule(mockLog(), mockConfig) - const info = await oauthdb.getClientInfo(MOCK_CLIENT_ID) - assert.deepEqual(info, MOCK_CLIENT_INFO) - }) + .reply(200, MOCK_CLIENT_INFO); + oauthdb = oauthdbModule(mockLog(), mockConfig); + const info = await oauthdb.getClientInfo(MOCK_CLIENT_ID); + assert.deepEqual(info, MOCK_CLIENT_INFO); + }); it('gets client info for a real client that previous triggered validation errors', async () => { mockOAuthServer.get(`/v1/client/${LOCKBOX_CLIENT_INFO.id}`) - .reply(200, LOCKBOX_CLIENT_INFO) - oauthdb = oauthdbModule(mockLog(), mockConfig) - const info = await oauthdb.getClientInfo(LOCKBOX_CLIENT_INFO.id) - assert.deepEqual(info, LOCKBOX_CLIENT_INFO) - }) + .reply(200, LOCKBOX_CLIENT_INFO); + oauthdb = oauthdbModule(mockLog(), mockConfig); + const info = await oauthdb.getClientInfo(LOCKBOX_CLIENT_INFO.id); + assert.deepEqual(info, LOCKBOX_CLIENT_INFO); + }); it('returns correct error for unknown client_id', async () => { mockOAuthServer.get(`/v1/client/${MOCK_CLIENT_ID}`) @@ -91,26 +91,26 @@ describe('oauthdb', () => { errno: 101, message: 'Unknown client', clientId: MOCK_CLIENT_ID - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); try { - await oauthdb.getClientInfo(MOCK_CLIENT_ID) - assert.fail('should have thrown') + await oauthdb.getClientInfo(MOCK_CLIENT_ID); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.UNKNOWN_CLIENT_ID) - assert.equal(err.output.payload.clientId, MOCK_CLIENT_ID) + assert.equal(err.errno, error.ERRNO.UNKNOWN_CLIENT_ID); + assert.equal(err.output.payload.clientId, MOCK_CLIENT_ID); } - }) + }); it('validates its input parameters', async () => { - oauthdb = oauthdbModule(mockLog(), mockConfig) + oauthdb = oauthdbModule(mockLog(), mockConfig); try { - await oauthdb.getClientInfo('invalid-client-id') - assert.fail('should have thrown') + await oauthdb.getClientInfo('invalid-client-id'); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); } - }) + }); it('validates the response data', async () => { mockOAuthServer.get(`/v1/client/${MOCK_CLIENT_ID}`) @@ -119,21 +119,21 @@ describe('oauthdb', () => { name: 'mock client', trusted: false, redirect_uri: 42 // invalid! - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); try { - await oauthdb.getClientInfo(MOCK_CLIENT_ID) - assert.fail('should have thrown') + await oauthdb.getClientInfo(MOCK_CLIENT_ID); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); } - }) - }) + }); + }); describe('getScopedKeyData', () => { - const ZEROS = Buffer.alloc(32).toString('hex') - const MOCK_SCOPES = 'mock-scope another-scope' + const ZEROS = Buffer.alloc(32).toString('hex'); + const MOCK_SCOPES = 'mock-scope another-scope'; const MOCK_CREDENTIALS = { uid: MOCK_UID, verifierSetAt: 12345, @@ -143,34 +143,34 @@ describe('oauthdb', () => { tokenVerified: true, authenticationMethods: ['pwd'], authenticatorAssuranceLevel: 1 - } + }; it('gets scoped key data, authenticating with a JWT', async () => { - let requestBody + let requestBody; mockOAuthServer.post('/v1/key-data', body => { - requestBody = body - return true + requestBody = body; + return true; }).reply(200, { 'mock-scope': { identifier: 'mock-scope', keyRotationSecret: ZEROS, keyRotationTimestamp: 0, } - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); const keyData = await oauthdb.getScopedKeyData(MOCK_CREDENTIALS, { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.equal(requestBody.client_id, MOCK_CLIENT_ID) - assert.equal(requestBody.scope, MOCK_SCOPES) - const claims = await verifyJWT(requestBody.assertion) + }); + assert.equal(requestBody.client_id, MOCK_CLIENT_ID); + assert.equal(requestBody.scope, MOCK_SCOPES); + const claims = await verifyJWT(requestBody.assertion); // We don't know the exact `iat` timestamp for the JWT. - assert.ok(claims.iat <= Date.now() / 1000) - assert.ok(claims.iat >= Date.now() / 1000 - 10) - assert.equal(claims.exp, claims.iat + 60) - delete claims.iat - delete claims.exp + assert.ok(claims.iat <= Date.now() / 1000); + assert.ok(claims.iat >= Date.now() / 1000 - 10); + assert.equal(claims.exp, claims.iat + 60); + delete claims.iat; + delete claims.exp; assert.deepEqual(claims, { aud: 'https://oauth.server.com', 'fxa-aal': 1, @@ -181,16 +181,16 @@ describe('oauthdb', () => { 'fxa-verifiedEmail': MOCK_CREDENTIALS.email, iss: 'accounts.example.com', sub: MOCK_UID - }) + }); assert.deepEqual(keyData, { 'mock-scope': { identifier: 'mock-scope', keyRotationSecret: ZEROS, keyRotationTimestamp: 0, } - }) + }); - }) + }); it('returns correct error for unknown client_id', async () => { mockOAuthServer.post('/v1/key-data', body => true) @@ -199,19 +199,19 @@ describe('oauthdb', () => { errno: 101, message: 'Unknown client', clientId: MOCK_CLIENT_ID - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(MOCK_CREDENTIALS, { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.UNKNOWN_CLIENT_ID) - assert.equal(err.output.payload.clientId, MOCK_CLIENT_ID) + assert.equal(err.errno, error.ERRNO.UNKNOWN_CLIENT_ID); + assert.equal(err.output.payload.clientId, MOCK_CLIENT_ID); } - }) + }); it('returns correct error for stale auth-at', async () => { mockOAuthServer.post('/v1/key-data', body => true) @@ -220,32 +220,32 @@ describe('oauthdb', () => { errno: 119, message: 'Stale auth timestamp', authAt: 7 - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(MOCK_CREDENTIALS, { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.STALE_AUTH_AT) - assert.equal(err.output.payload.authAt, 7) + assert.equal(err.errno, error.ERRNO.STALE_AUTH_AT); + assert.equal(err.output.payload.authAt, 7); } - }) + }); it('validates its input parameters', async () => { - oauthdb = oauthdbModule(mockLog(), mockConfig) + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(MOCK_CREDENTIALS, { client_id: MOCK_CLIENT_ID, scope: 'invalid!scope#' - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); } - }) + }); it('validates the response data', async () => { mockOAuthServer.post('/v1/key-data', body => true) @@ -255,36 +255,36 @@ describe('oauthdb', () => { keyRotationSecret: 42, // invalid! keyRotationTimestamp: 0, } - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(MOCK_CREDENTIALS, { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); } - }) + }); it('requires a verified account', async () => { - oauthdb = oauthdbModule(mockLog(), mockConfig) + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(Object.assign({}, MOCK_CREDENTIALS, { emailVerified: false }), { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.ACCOUNT_UNVERIFIED) + assert.equal(err.errno, error.ERRNO.ACCOUNT_UNVERIFIED); } - }) + }); it('enforces session verification', async () => { - oauthdb = oauthdbModule(mockLog(), mockConfig) + oauthdb = oauthdbModule(mockLog(), mockConfig); try { await oauthdb.getScopedKeyData(Object.assign({}, MOCK_CREDENTIALS, { mustVerify: true, @@ -292,13 +292,13 @@ describe('oauthdb', () => { }), { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.fail('should have thrown') + }); + assert.fail('should have thrown'); } catch (err) { - assert.equal(err.errno, error.ERRNO.SESSION_UNVERIFIED) + assert.equal(err.errno, error.ERRNO.SESSION_UNVERIFIED); } - }) + }); - }) + }); -}) +}); diff --git a/test/local/oauthdb/check-refresh-token.js b/test/local/oauthdb/check-refresh-token.js index 8a9bc69a..16093816 100644 --- a/test/local/oauthdb/check-refresh-token.js +++ b/test/local/oauthdb/check-refresh-token.js @@ -2,18 +2,18 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const nock = require('nock') -const oauthdbModule = require('../../../lib/oauthdb') -const { mockLog } = require('../../mocks') +const { assert } = require('chai'); +const nock = require('nock'); +const oauthdbModule = require('../../../lib/oauthdb'); +const { mockLog } = require('../../mocks'); -const ISSUER = 'api.accounts.firefox.com' -const LAST_USED_AT = new Date().getTime() -const MOCK_UID = 'ABCDEF' -const MOCK_CLIENT_ID = '0123456789ABCDEF' -const JWT_IAT = Date.now() +const ISSUER = 'api.accounts.firefox.com'; +const LAST_USED_AT = new Date().getTime(); +const MOCK_UID = 'ABCDEF'; +const MOCK_CLIENT_ID = '0123456789ABCDEF'; +const JWT_IAT = Date.now(); const mockConfig = { publicUrl: 'https://accounts.example.com', oauth: { @@ -21,20 +21,20 @@ const mockConfig = { secretKey: 'secret-key-oh-secret-key', }, domain: 'accounts.example.com' -} +}; const mockOAuthServer = nock(mockConfig.oauth.url).defaultReplyHeaders({ 'Content-Type': 'application/json' -}) +}); describe('oauthdb/checkRefreshToken', () => { - let oauthdb + let oauthdb; afterEach(async () => { - assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test') + assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test'); if (oauthdb) { - await oauthdb.close() + await oauthdb.close(); } - }) + }); it('can return a token', async () => { mockOAuthServer.post('/v1/introspect', body => true) @@ -48,10 +48,10 @@ describe('oauthdb/checkRefreshToken', () => { sub: MOCK_UID, iss: ISSUER, 'fxa-lastUsedAt': LAST_USED_AT - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) - const accessToken = await oauthdb.checkRefreshToken('DEADBEEFDEADBEEFDEADBEEFDEADBEEF') + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); + const accessToken = await oauthdb.checkRefreshToken('DEADBEEFDEADBEEFDEADBEEFDEADBEEF'); - assert.ok(accessToken) - }) -}) + assert.ok(accessToken); + }); +}); diff --git a/test/local/oauthdb/revoke-refresh-token.js b/test/local/oauthdb/revoke-refresh-token.js index 989f98d2..ed95cf82 100644 --- a/test/local/oauthdb/revoke-refresh-token.js +++ b/test/local/oauthdb/revoke-refresh-token.js @@ -2,12 +2,12 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const nock = require('nock') -const oauthdbModule = require('../../../lib/oauthdb') -const { mockLog } = require('../../mocks') +const { assert } = require('chai'); +const nock = require('nock'); +const oauthdbModule = require('../../../lib/oauthdb'); +const { mockLog } = require('../../mocks'); const mockConfig = { publicUrl: 'https://accounts.example.com', @@ -16,28 +16,28 @@ const mockConfig = { secretKey: 'secret-key-oh-secret-key', }, domain: 'accounts.example.com' -} +}; const mockOAuthServer = nock(mockConfig.oauth.url).defaultReplyHeaders({ 'Content-Type': 'application/json' -}) +}); describe('oauthdb/revokeRefreshToken', () => { - let oauthdb + let oauthdb; afterEach(async () => { - assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test') + assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test'); if (oauthdb) { - await oauthdb.close() + await oauthdb.close(); } - }) + }); it('can return a token', async () => { mockOAuthServer.post('/v1/destroy', body => true) .reply(200, { - }) - oauthdb = oauthdbModule(mockLog(), mockConfig) - const resp = await oauthdb.revokeRefreshTokenById('DEADBEEFDEADBEEFDEADBEEFDEADBEEF') + }); + oauthdb = oauthdbModule(mockLog(), mockConfig); + const resp = await oauthdb.revokeRefreshTokenById('DEADBEEFDEADBEEFDEADBEEFDEADBEEF'); - assert.ok(resp) - }) -}) + assert.ok(resp); + }); +}); diff --git a/test/local/password.js b/test/local/password.js index 4394076a..cd379c6a 100644 --- a/test/local/password.js +++ b/test/local/password.js @@ -2,88 +2,88 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const log = {} -const config = {} -const Password = require('../../lib/crypto/password')(log, config) +const { assert } = require('chai'); +const log = {}; +const config = {}; +const Password = require('../../lib/crypto/password')(log, config); describe('Password', () => { it( 'password version zero', () => { - var pwd = Buffer.from('aaaaaaaaaaaaaaaa') - var salt = Buffer.from('bbbbbbbbbbbbbbbb') - var p1 = new Password(pwd, salt, 0) - assert.equal(p1.version, 0, 'should be using version zero') - var p2 = new Password(pwd, salt, 0) - assert.equal(p2.version, 0, 'should be using version zero') + var pwd = Buffer.from('aaaaaaaaaaaaaaaa'); + var salt = Buffer.from('bbbbbbbbbbbbbbbb'); + var p1 = new Password(pwd, salt, 0); + assert.equal(p1.version, 0, 'should be using version zero'); + var p2 = new Password(pwd, salt, 0); + assert.equal(p2.version, 0, 'should be using version zero'); return p1.verifyHash() .then( function (hash) { - return p2.matches(hash) + return p2.matches(hash); } ) .then( function (matched) { - assert.ok(matched, 'identical passwords should match') + assert.ok(matched, 'identical passwords should match'); } - ) + ); } - ) + ); it( 'password version one', () => { - var pwd = Buffer.from('aaaaaaaaaaaaaaaa') - var salt = Buffer.from('bbbbbbbbbbbbbbbb') - var p1 = new Password(pwd, salt, 1) - assert.equal(p1.version, 1, 'should be using version one') - var p2 = new Password(pwd, salt, 1) - assert.equal(p2.version, 1, 'should be using version one') + var pwd = Buffer.from('aaaaaaaaaaaaaaaa'); + var salt = Buffer.from('bbbbbbbbbbbbbbbb'); + var p1 = new Password(pwd, salt, 1); + assert.equal(p1.version, 1, 'should be using version one'); + var p2 = new Password(pwd, salt, 1); + assert.equal(p2.version, 1, 'should be using version one'); return p1.verifyHash() .then( function (hash) { - return p2.matches(hash) + return p2.matches(hash); } ) .then( function (matched) { - assert.ok(matched, 'identical passwords should match') + assert.ok(matched, 'identical passwords should match'); } - ) + ); } - ) + ); it( 'passwords of different versions should not match', () => { - var pwd = Buffer.from('aaaaaaaaaaaaaaaa') - var salt = Buffer.from('bbbbbbbbbbbbbbbb') - var p1 = new Password(pwd, salt, 0) - var p2 = new Password(pwd, salt, 1) + var pwd = Buffer.from('aaaaaaaaaaaaaaaa'); + var salt = Buffer.from('bbbbbbbbbbbbbbbb'); + var p1 = new Password(pwd, salt, 0); + var p2 = new Password(pwd, salt, 1); return p1.verifyHash() .then( function (hash) { - return p2.matches(hash) + return p2.matches(hash); } ) .then( function (matched) { - assert.ok(! matched, 'passwords should not match') + assert.ok(! matched, 'passwords should not match'); } - ) + ); } - ) + ); it( 'scrypt queue stats can be reported', () => { - var stat = Password.stat() - assert.equal(stat.stat, 'scrypt') - assert.ok(stat.hasOwnProperty('numPending')) - assert.ok(stat.hasOwnProperty('numPendingHWM')) + var stat = Password.stat(); + assert.equal(stat.stat, 'scrypt'); + assert.ok(stat.hasOwnProperty('numPending')); + assert.ok(stat.hasOwnProperty('numPendingHWM')); } - ) -}) + ); +}); diff --git a/test/local/pool.js b/test/local/pool.js index fc9ef97d..3b551af9 100644 --- a/test/local/pool.js +++ b/test/local/pool.js @@ -2,320 +2,320 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const mocks = require('../mocks') -const sinon = require('sinon') -const proxyquire = require('proxyquire') +const { assert } = require('chai'); +const mocks = require('../mocks'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; describe('Pool', () => { - let log, SafeUrl, Pool, poolee + let log, SafeUrl, Pool, poolee; beforeEach(() => { - log = mocks.mockLog() - SafeUrl = require(`${ROOT_DIR}/lib/safe-url`)(log) - poolee = sinon.createStubInstance(require('poolee')) + log = mocks.mockLog(); + SafeUrl = require(`${ROOT_DIR}/lib/safe-url`)(log); + poolee = sinon.createStubInstance(require('poolee')); Pool = proxyquire(`${ROOT_DIR}/lib/pool`, { poolee: function () { - return poolee + return poolee; } - }) - }) + }); + }); it( 'pool cannot be constructed with an unsupported protocol', () => { - assert.throws(() => new Pool('ftp://example.com/')) + assert.throws(() => new Pool('ftp://example.com/')); } - ) + ); it( 'pool.request with default options', () => { - var pool = new Pool('http://example.com/ignore/me') - pool.request(null, new SafeUrl('')) + var pool = new Pool('http://example.com/ignore/me'); + pool.request(null, new SafeUrl('')); - assert.equal(poolee.request.callCount, 1, 'poolee.request was called once') + assert.equal(poolee.request.callCount, 1, 'poolee.request was called once'); - var args = poolee.request.getCall(0).args - assert.equal(args.length, 2, 'poolee.request was passed two arguments') + var args = poolee.request.getCall(0).args; + assert.equal(args.length, 2, 'poolee.request was passed two arguments'); - var options = args[0] - assert.equal(typeof options, 'object', 'options is object') - assert.equal(Object.keys(options).length, 4, 'options has 4 properties') - assert.equal(options.method, 'GET', 'options.method is GET') - assert.equal(options.path, '', 'options.path is blank') - assert.equal(typeof options.headers, 'object', 'options.headers is object') - assert.equal(Object.keys(options.headers).length, 0, 'options.headers has zero properties') - assert.equal(options.data, undefined, 'options.data is undefined') + var options = args[0]; + assert.equal(typeof options, 'object', 'options is object'); + assert.equal(Object.keys(options).length, 4, 'options has 4 properties'); + assert.equal(options.method, 'GET', 'options.method is GET'); + assert.equal(options.path, '', 'options.path is blank'); + assert.equal(typeof options.headers, 'object', 'options.headers is object'); + assert.equal(Object.keys(options.headers).length, 0, 'options.headers has zero properties'); + assert.equal(options.data, undefined, 'options.data is undefined'); - var callback = args[1] - assert.equal(typeof callback, 'function', 'callback is function') + var callback = args[1]; + assert.equal(typeof callback, 'function', 'callback is function'); } - ) + ); it( 'pool.request with alternative options', () => { - var pool = new Pool('http://example.com/') - pool.request('POST', new SafeUrl('/:foo'), { foo: 'bar' }, { baz: 'qux' }, {'barbar': 'foofoo'}, { Authorization: 'Bearer 123abc' }) + var pool = new Pool('http://example.com/'); + pool.request('POST', new SafeUrl('/:foo'), { foo: 'bar' }, { baz: 'qux' }, {'barbar': 'foofoo'}, { Authorization: 'Bearer 123abc' }); - assert.equal(poolee.request.callCount, 1, 'poolee.request was called once') + assert.equal(poolee.request.callCount, 1, 'poolee.request was called once'); - var args = poolee.request.getCall(0).args - assert.equal(args.length, 2, 'poolee.request was passed two arguments') + var args = poolee.request.getCall(0).args; + assert.equal(args.length, 2, 'poolee.request was passed two arguments'); - var options = args[0] - assert.equal(typeof options, 'object', 'options is object') - assert.equal(Object.keys(options).length, 4, 'options has 4 properties') - assert.equal(options.method, 'POST', 'options.method is POST') - assert.equal(options.path, '/bar?baz=qux', 'options.path is /bar?baz=qux') - assert.equal(options.data, JSON.stringify({'barbar': 'foofoo'}), 'options.data is stringified object') - assert.equal(typeof options.headers, 'object', 'options.headers is object') - assert.equal(Object.keys(options.headers).length, 2, 'options.headers has 2 properties') - assert.equal(options.headers['Content-Type'], 'application/json', 'Content-Type header is application/json') - assert.equal(options.headers['Authorization'], 'Bearer 123abc', 'Authorization header is set') + var options = args[0]; + assert.equal(typeof options, 'object', 'options is object'); + assert.equal(Object.keys(options).length, 4, 'options has 4 properties'); + assert.equal(options.method, 'POST', 'options.method is POST'); + assert.equal(options.path, '/bar?baz=qux', 'options.path is /bar?baz=qux'); + assert.equal(options.data, JSON.stringify({'barbar': 'foofoo'}), 'options.data is stringified object'); + assert.equal(typeof options.headers, 'object', 'options.headers is object'); + assert.equal(Object.keys(options.headers).length, 2, 'options.headers has 2 properties'); + assert.equal(options.headers['Content-Type'], 'application/json', 'Content-Type header is application/json'); + assert.equal(options.headers['Authorization'], 'Bearer 123abc', 'Authorization header is set'); - var callback = args[1] - assert.equal(typeof callback, 'function', 'callback is function') + var callback = args[1]; + assert.equal(typeof callback, 'function', 'callback is function'); } - ) + ); it('pool.request with string path', () => { - const pool = new Pool('http://example.com/') + const pool = new Pool('http://example.com/'); pool.request(null, '/foo') .then( () => assert(false, 'request should have failed'), err => assert(err instanceof Error) - ) - }) + ); + }); it('pool.request with missing param', () => { - const pool = new Pool('http://example.com/') + const pool = new Pool('http://example.com/'); pool.request(null, new SafeUrl('/:foo'), {}) .then( () => assert(false, 'request should have failed'), err => assert(err instanceof Error) - ) - }) + ); + }); it( 'pool.request callback with error', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function () { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (error) { - assert.equal(typeof error, 'string', 'error is string') - assert.equal(error, 'foo', 'error is correct') - }) + assert.equal(typeof error, 'string', 'error is string'); + assert.equal(error, 'foo', 'error is correct'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback('foo') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback('foo'); + return p; } - ) + ); it( 'pool.request callback with HTTP error response', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function () { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (error) { - assert.ok(error instanceof Error, 'error is Error instance') - assert.equal(error.statusCode, 404, 'error.statusCode is 404') - assert.equal(error.message, 'wibble', 'error.message is correct') - }) + assert.ok(error instanceof Error, 'error is Error instance'); + assert.equal(error.statusCode, 404, 'error.statusCode is 404'); + assert.equal(error.message, 'wibble', 'error.message is correct'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback(null, { statusCode: 404 }, 'wibble') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback(null, { statusCode: 404 }, 'wibble'); + return p; } - ) + ); it( 'pool.request callback with HTTP error response and JSON body', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function () { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (error) { - assert.equal(error instanceof Error, true, 'error is an Error instance') - assert.equal(Object.keys(error).length, 2, 'error has two properties') - assert.equal(error.statusCode, 418, 'error.statusCode is 418') - assert.equal(error.foo, 'bar', 'other error data is correct') - }) + assert.equal(error instanceof Error, true, 'error is an Error instance'); + assert.equal(Object.keys(error).length, 2, 'error has two properties'); + assert.equal(error.statusCode, 418, 'error.statusCode is 418'); + assert.equal(error.foo, 'bar', 'other error data is correct'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback(null, { statusCode: 418 }, '{"foo":"bar"}') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback(null, { statusCode: 418 }, '{"foo":"bar"}'); + return p; } - ) + ); it( 'pool.request callback with HTTP success response and empty body', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function (result) { - assert.equal(result, undefined, 'result is undefined') - }) + assert.equal(result, undefined, 'result is undefined'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback(null, { statusCode: 200 }, '') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback(null, { statusCode: 200 }, ''); + return p; } - ) + ); it( 'pool.request callback with HTTP success response and valid JSON body', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function (result) { - assert.equal(typeof result, 'object', 'result is object') - assert.equal(Object.keys(result).length, 1, 'result has 1 property') - assert.equal(result.foo, 'bar', 'result data is correct') - }) + assert.equal(typeof result, 'object', 'result is object'); + assert.equal(Object.keys(result).length, 1, 'result has 1 property'); + assert.equal(result.foo, 'bar', 'result data is correct'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback(null, { statusCode: 200 }, '{"foo":"bar"}') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback(null, { statusCode: 200 }, '{"foo":"bar"}'); + return p; } - ) + ); it( 'pool.request callback with HTTP success response and invalid JSON body', () => { - var pool = new Pool('http://example.com/') + var pool = new Pool('http://example.com/'); const p = pool.request(null, new SafeUrl('')) .then(function () { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (error) { - assert.ok(error instanceof Error, 'error is Error instance') - assert.equal(error.statusCode, undefined, 'error.statusCode is undefined') - assert.equal(error.message, 'Invalid JSON', 'error.message is correct') - }) + assert.ok(error instanceof Error, 'error is Error instance'); + assert.equal(error.statusCode, undefined, 'error.statusCode is undefined'); + assert.equal(error.message, 'Invalid JSON', 'error.message is correct'); + }); - var args = poolee.request.getCall(0).args - var callback = args[1] - callback(null, { statusCode: 200 }, 'foo') - return p + var args = poolee.request.getCall(0).args; + var callback = args[1]; + callback(null, { statusCode: 200 }, 'foo'); + return p; } - ) + ); it( 'pool.get', () => { - var pool = new Pool('http://example.com/') - sinon.stub(pool, 'request').callsFake(function () {}) - pool.get('foo', 'bar') + var pool = new Pool('http://example.com/'); + sinon.stub(pool, 'request').callsFake(function () {}); + pool.get('foo', 'bar'); - assert.equal(pool.request.callCount, 1, 'pool.request was called once') + assert.equal(pool.request.callCount, 1, 'pool.request was called once'); - var args = pool.request.getCall(0).args - assert.equal(args.length, 6, 'pool.request was passed six arguments') - assert.equal(args[0], 'GET', 'first argument to pool.request was GET') - assert.equal(args[1], 'foo', 'second argument to pool.request was correct') - assert.equal(args[2], 'bar', 'third argument to pool.request was correct') - assert.deepEqual(args[3], {}, 'forth argument to pool.request was empty') - assert.equal(args[4], null, 'fifth argument to pool.request was null') - assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty') + var args = pool.request.getCall(0).args; + assert.equal(args.length, 6, 'pool.request was passed six arguments'); + assert.equal(args[0], 'GET', 'first argument to pool.request was GET'); + assert.equal(args[1], 'foo', 'second argument to pool.request was correct'); + assert.equal(args[2], 'bar', 'third argument to pool.request was correct'); + assert.deepEqual(args[3], {}, 'forth argument to pool.request was empty'); + assert.equal(args[4], null, 'fifth argument to pool.request was null'); + assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty'); } - ) + ); it( 'pool.put', () => { - var pool = new Pool('http://example.com/') - sinon.stub(pool, 'request').callsFake(function () {}) - pool.put('baz', 'qux', 'wibble') + var pool = new Pool('http://example.com/'); + sinon.stub(pool, 'request').callsFake(function () {}); + pool.put('baz', 'qux', 'wibble'); - assert.equal(pool.request.callCount, 1, 'pool.request was called once') + assert.equal(pool.request.callCount, 1, 'pool.request was called once'); - var args = pool.request.getCall(0).args - assert.equal(args.length, 6, 'pool.request was passed six arguments') - assert.equal(args[0], 'PUT', 'first argument to pool.request was PUT') - assert.equal(args[1], 'baz', 'second argument to pool.request was correct') - assert.equal(args[2], 'qux', 'third argument to pool.request was correct') - assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty') - assert.equal(args[4], 'wibble', 'fifth argument to pool.request was correct') - assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty') + var args = pool.request.getCall(0).args; + assert.equal(args.length, 6, 'pool.request was passed six arguments'); + assert.equal(args[0], 'PUT', 'first argument to pool.request was PUT'); + assert.equal(args[1], 'baz', 'second argument to pool.request was correct'); + assert.equal(args[2], 'qux', 'third argument to pool.request was correct'); + assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty'); + assert.equal(args[4], 'wibble', 'fifth argument to pool.request was correct'); + assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty'); } - ) + ); it( 'pool.post', () => { - var pool = new Pool('http://example.com/') - sinon.stub(pool, 'request').callsFake(function () {}) - pool.post('foo', 'bar', 'baz') + var pool = new Pool('http://example.com/'); + sinon.stub(pool, 'request').callsFake(function () {}); + pool.post('foo', 'bar', 'baz'); - assert.equal(pool.request.callCount, 1, 'pool.request was called once') + assert.equal(pool.request.callCount, 1, 'pool.request was called once'); - var args = pool.request.getCall(0).args - assert.equal(args.length, 6, 'pool.request was passed six arguments') - assert.equal(args[0], 'POST', 'first argument to pool.request was POST') - assert.equal(args[1], 'foo', 'second argument to pool.request was correct') - assert.equal(args[2], 'bar', 'third argument to pool.request was correct') - assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty') - assert.equal(args[4], 'baz', 'fifth argument to pool.request was correct') - assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty') + var args = pool.request.getCall(0).args; + assert.equal(args.length, 6, 'pool.request was passed six arguments'); + assert.equal(args[0], 'POST', 'first argument to pool.request was POST'); + assert.equal(args[1], 'foo', 'second argument to pool.request was correct'); + assert.equal(args[2], 'bar', 'third argument to pool.request was correct'); + assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty'); + assert.equal(args[4], 'baz', 'fifth argument to pool.request was correct'); + assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty'); } - ) + ); it( 'pool.post with query params and extra headers', () => { - var pool = new Pool('http://example.com/') - sinon.stub(pool, 'request').callsFake(function () {}) - pool.post('foo', 'bar', 'baz', {query: {bar: 'foo'}, headers: { foo: 'bar' }}) + var pool = new Pool('http://example.com/'); + sinon.stub(pool, 'request').callsFake(function () {}); + pool.post('foo', 'bar', 'baz', {query: {bar: 'foo'}, headers: { foo: 'bar' }}); - assert.equal(pool.request.callCount, 1, 'pool.request was called once') + assert.equal(pool.request.callCount, 1, 'pool.request was called once'); - var args = pool.request.getCall(0).args - assert.equal(args.length, 6, 'pool.request was passed six arguments') - assert.equal(args[0], 'POST', 'first argument to pool.request was POST') - assert.equal(args[1], 'foo', 'second argument to pool.request was correct') - assert.equal(args[2], 'bar', 'third argument to pool.request was correct') - assert.deepEqual(args[3], {bar: 'foo'}, 'fourth argument to pool.request was set') - assert.equal(args[4], 'baz', 'fifth argument to pool.request was correct') - assert.deepEqual(args[5], {foo: 'bar'}, 'sixth argument to pool.request was set') + var args = pool.request.getCall(0).args; + assert.equal(args.length, 6, 'pool.request was passed six arguments'); + assert.equal(args[0], 'POST', 'first argument to pool.request was POST'); + assert.equal(args[1], 'foo', 'second argument to pool.request was correct'); + assert.equal(args[2], 'bar', 'third argument to pool.request was correct'); + assert.deepEqual(args[3], {bar: 'foo'}, 'fourth argument to pool.request was set'); + assert.equal(args[4], 'baz', 'fifth argument to pool.request was correct'); + assert.deepEqual(args[5], {foo: 'bar'}, 'sixth argument to pool.request was set'); } - ) + ); it( 'pool.del', () => { - var pool = new Pool('http://example.com/') - sinon.stub(pool, 'request').callsFake(function () {}) - pool.del('foo', 'bar', 'baz') + var pool = new Pool('http://example.com/'); + sinon.stub(pool, 'request').callsFake(function () {}); + pool.del('foo', 'bar', 'baz'); - assert.equal(pool.request.callCount, 1, 'pool.request was called once') + assert.equal(pool.request.callCount, 1, 'pool.request was called once'); - var args = pool.request.getCall(0).args - assert.equal(args.length, 6, 'pool.request was passed six arguments') - assert.equal(args[0], 'DELETE', 'first argument to pool.request was POST') - assert.equal(args[1], 'foo', 'second argument to pool.request was correct') - assert.equal(args[2], 'bar', 'third argument was correct') - assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty') - assert.equal(args[4], 'baz', 'fifth argument was correct') - assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty') + var args = pool.request.getCall(0).args; + assert.equal(args.length, 6, 'pool.request was passed six arguments'); + assert.equal(args[0], 'DELETE', 'first argument to pool.request was POST'); + assert.equal(args[1], 'foo', 'second argument to pool.request was correct'); + assert.equal(args[2], 'bar', 'third argument was correct'); + assert.deepEqual(args[3], {}, 'fourth argument to pool.request was empty'); + assert.equal(args[4], 'baz', 'fifth argument was correct'); + assert.deepEqual(args[5], {}, 'sixth argument to pool.request was empty'); } - ) + ); -}) +}); diff --git a/test/local/profile/updates.js b/test/local/profile/updates.js index 85c384c5..a436ea40 100644 --- a/test/local/profile/updates.js +++ b/test/local/profile/updates.js @@ -2,69 +2,69 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const EventEmitter = require('events').EventEmitter -const sinon = require('sinon') -const { mockDB, mockLog } = require('../../mocks') -const profileUpdates = require('../../../lib/profile/updates') -const P = require('../../../lib/promise') +const EventEmitter = require('events').EventEmitter; +const sinon = require('sinon'); +const { mockDB, mockLog } = require('../../mocks'); +const profileUpdates = require('../../../lib/profile/updates'); +const P = require('../../../lib/promise'); -const mockDeliveryQueue = new EventEmitter() -mockDeliveryQueue.start = function start() {} +const mockDeliveryQueue = new EventEmitter(); +mockDeliveryQueue.start = function start() {}; function mockMessage(msg) { - msg.del = sinon.spy() - return msg + msg.del = sinon.spy(); + return msg; } -var pushShouldThrow = false +var pushShouldThrow = false; const mockPush = { notifyProfileUpdated: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') + assert.ok(typeof uid === 'string'); if (pushShouldThrow) { - throw new Error('oops') + throw new Error('oops'); } - return P.resolve() + return P.resolve(); }) -} +}; function mockProfileUpdates(log) { - return profileUpdates(log)(mockDeliveryQueue, mockPush, mockDB()) + return profileUpdates(log)(mockDeliveryQueue, mockPush, mockDB()); } describe('profile updates', () => { it( 'should log errors', () => { - pushShouldThrow = true - const log = mockLog() + pushShouldThrow = true; + const log = mockLog(); return mockProfileUpdates(log).handleProfileUpdated(mockMessage({ uid: 'bogusuid' })).then(() => { - assert.equal(mockPush.notifyProfileUpdated.callCount, 1) - assert.equal(log.error.callCount, 1) - pushShouldThrow = false - }) + assert.equal(mockPush.notifyProfileUpdated.callCount, 1); + assert.equal(log.error.callCount, 1); + pushShouldThrow = false; + }); } - ) + ); it( 'should send push notifications', () => { - const log = mockLog() - const uid = '1e2122ba' + const log = mockLog(); + const uid = '1e2122ba'; return mockProfileUpdates(log).handleProfileUpdated(mockMessage({ uid: uid })).then(function () { - assert.equal(log.error.callCount, 0) - assert.equal(mockPush.notifyProfileUpdated.callCount, 2) - var args = mockPush.notifyProfileUpdated.getCall(1).args - assert.equal(args[0], uid) - }) + assert.equal(log.error.callCount, 0); + assert.equal(mockPush.notifyProfileUpdated.callCount, 2); + var args = mockPush.notifyProfileUpdated.getCall(1).args; + assert.equal(args[0], uid); + }); } - ) -}) + ); +}); diff --git a/test/local/push.js b/test/local/push.js index a54510ba..4241b0eb 100644 --- a/test/local/push.js +++ b/test/local/push.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const proxyquire = require('proxyquire') -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } -const ajv = require('ajv')() -const fs = require('fs') -const path = require('path') +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; +const ajv = require('ajv')(); +const fs = require('fs'); +const path = require('path'); -const P = require(`${ROOT_DIR}/lib/promise`) -const mocks = require('../mocks') -const mockLog = mocks.mockLog -const mockUid = 'deadbeef' -const mockConfig = {} +const P = require(`${ROOT_DIR}/lib/promise`); +const mocks = require('../mocks'); +const mockLog = mocks.mockLog; +const mockUid = 'deadbeef'; +const mockConfig = {}; -const PUSH_PAYLOADS_SCHEMA_PATH = `${ROOT_DIR}/docs/pushpayloads.schema.json` -const TTL = '42' -const pushModulePath = `${ROOT_DIR}/lib/push` +const PUSH_PAYLOADS_SCHEMA_PATH = `${ROOT_DIR}/docs/pushpayloads.schema.json`; +const TTL = '42'; +const pushModulePath = `${ROOT_DIR}/lib/push`; describe('push', () => { - let mockDb, mockDevices + let mockDb, mockDevices; beforeEach(() => { - mockDb = mocks.mockDB() + mockDb = mocks.mockDB(); mockDevices = [ { 'id': '0f7aa00356e5416e82b3bef7bc409eef', @@ -67,8 +67,8 @@ describe('push', () => { 'pushAuthKey': 'w3b14Zjc-Afj2SDOLOyong==', 'pushEndpointExpired': false } - ] - }) + ]; + }); it( 'sendPush does not reject on empty device array', @@ -76,127 +76,127 @@ describe('push', () => { const thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.success') { - assert.fail('must not call push.success') + assert.fail('must not call push.success'); } } - }) - const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig) + }); + const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig); - return push.sendPush(mockUid, [], 'accountVerify') + return push.sendPush(mockUid, [], 'accountVerify'); } - ) + ); it( 'sendPush sends notifications with a TTL of 0', () => { - var successCalled = 0 + var successCalled = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.success') { // notification sent - successCalled++ + successCalled++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - assert.equal(options.TTL, '0', 'sends the proper ttl header') - return P.resolve() + assert.equal(options.TTL, '0', 'sends the proper ttl header'); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, mockDevices, 'accountVerify') .then(() => { - assert.equal(successCalled, 2) - }) + assert.equal(successCalled, 2); + }); } - ) + ); it( 'sendPush sends notifications with user-defined TTL', () => { - var successCalled = 0 + var successCalled = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.success') { // notification sent - successCalled++ + successCalled++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - assert.equal(options.TTL, TTL, 'sends the proper ttl header') - return P.resolve() + assert.equal(options.TTL, TTL, 'sends the proper ttl header'); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) - var options = { TTL: TTL } + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); + var options = { TTL: TTL }; return push.sendPush(mockUid, mockDevices, 'accountVerify', options) .then(() => { - assert.equal(successCalled, 2) - }) + assert.equal(successCalled, 2); + }); } - ) + ); it( 'sendPush sends data', () => { - var count = 0 - var data = { foo: 'bar' } + var count = 0; + var data = { foo: 'bar' }; var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - count++ - assert.ok(sub.keys.p256dh) - assert.ok(sub.keys.auth) - assert.deepEqual(payload, Buffer.from(JSON.stringify(data))) - return P.resolve() + count++; + assert.ok(sub.keys.p256dh); + assert.ok(sub.keys.auth); + assert.deepEqual(payload, Buffer.from(JSON.stringify(data))); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - var options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + var options = { data: data }; return push.sendPush(mockUid, mockDevices, 'accountVerify', options) .then(() => { - assert.equal(count, 2) - }) + assert.equal(count, 2); + }); } - ) + ); it( 'sendPush doesn\'t push to ios devices if it is triggered with an unsupported command', () => { - const data = Buffer.from(JSON.stringify({command: 'fxaccounts:non_existent_command'})) - const endPoints = [] + const data = Buffer.from(JSON.stringify({command: 'fxaccounts:non_existent_command'})); + const endPoints = []; const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - endPoints.push(sub.endpoint) - return P.resolve() + endPoints.push(sub.endpoint); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - const options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + const options = { data: data }; return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) .then(() => { - assert.equal(endPoints.length, 2) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - }) + assert.equal(endPoints.length, 2); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + }); } - ) + ); it( 'sendPush pushes to all ios devices if it is triggered with a "commands received" command', @@ -204,28 +204,28 @@ describe('push', () => { const data = { command: 'fxaccounts:command_received', data: { foo: 'bar' } - } - const endPoints = [] + }; + const endPoints = []; const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - endPoints.push(sub.endpoint) - return P.resolve() + endPoints.push(sub.endpoint); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - const options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + const options = { data: data }; return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) .then(() => { - assert.equal(endPoints.length, 3) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - assert.equal(endPoints[2], mockDevices[2].pushCallback) - }) + assert.equal(endPoints.length, 3); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + assert.equal(endPoints[2], mockDevices[2].pushCallback); + }); } - ) + ); it( 'sendPush pushes to all ios devices if it is triggered with a "collection changed" command', @@ -233,28 +233,28 @@ describe('push', () => { const data = { command: 'sync:collection_changed', data: { collection: 'clients' } - } - const endPoints = [] + }; + const endPoints = []; const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - endPoints.push(sub.endpoint) - return P.resolve() + endPoints.push(sub.endpoint); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - const options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + const options = { data: data }; return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) .then(() => { - assert.equal(endPoints.length, 3) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - assert.equal(endPoints[2], mockDevices[2].pushCallback) - }) + assert.equal(endPoints.length, 3); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + assert.equal(endPoints[2], mockDevices[2].pushCallback); + }); } - ) + ); it( 'sendPush does not push to ios devices if "collection changed" reason is "firstsync"', @@ -262,104 +262,104 @@ describe('push', () => { const data = { command: 'sync:collection_changed', data: { collection: 'clients', reason: 'firstsync' } - } - const endPoints = [] + }; + const endPoints = []; const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - endPoints.push(sub.endpoint) - return P.resolve() + endPoints.push(sub.endpoint); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - const options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + const options = { data: data }; return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) .then(() => { - assert.equal(endPoints.length, 2) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - }) + assert.equal(endPoints.length, 2); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + }); } - ) + ); it( 'sendPush pushes to ios >=10.0 devices if it is triggered with a "device connected" command', () => { - const data = {command: 'fxaccounts:device_connected'} - let endPoints = [] + const data = {command: 'fxaccounts:device_connected'}; + let endPoints = []; const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - endPoints.push(sub.endpoint) - return P.resolve() + endPoints.push(sub.endpoint); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - const options = { data: data } + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + const options = { data: data }; return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) .then(() => { - assert.equal(endPoints.length, 2) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) + assert.equal(endPoints.length, 2); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); // iOS not notified due to unknown browser version }).then(() => { - endPoints = [] - mockDevices[2].uaBrowserVersion = '8.2' - return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) + endPoints = []; + mockDevices[2].uaBrowserVersion = '8.2'; + return push.sendPush(mockUid, mockDevices, 'devicesNotify', options); }).then(() => { - assert.equal(endPoints.length, 2) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) + assert.equal(endPoints.length, 2); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); // iOS not notified due to unsupported browser version }).then(() => { - endPoints = [] - mockDevices[2].uaBrowserVersion = '10.0' - return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) + endPoints = []; + mockDevices[2].uaBrowserVersion = '10.0'; + return push.sendPush(mockUid, mockDevices, 'devicesNotify', options); }).then(() => { - assert.equal(endPoints.length, 3) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - assert.equal(endPoints[2], mockDevices[2].pushCallback) + assert.equal(endPoints.length, 3); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + assert.equal(endPoints[2], mockDevices[2].pushCallback); }).then(() => { - endPoints = [] - mockDevices[2].uaBrowserVersion = '10.1' - return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) + endPoints = []; + mockDevices[2].uaBrowserVersion = '10.1'; + return push.sendPush(mockUid, mockDevices, 'devicesNotify', options); }).then(() => { - assert.equal(endPoints.length, 3) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - assert.equal(endPoints[2], mockDevices[2].pushCallback) + assert.equal(endPoints.length, 3); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + assert.equal(endPoints[2], mockDevices[2].pushCallback); }).then(() => { - endPoints = [] - mockDevices[2].uaBrowserVersion = '11.2' - return push.sendPush(mockUid, mockDevices, 'devicesNotify', options) + endPoints = []; + mockDevices[2].uaBrowserVersion = '11.2'; + return push.sendPush(mockUid, mockDevices, 'devicesNotify', options); }).then(() => { - assert.equal(endPoints.length, 3) - assert.equal(endPoints[0], mockDevices[0].pushCallback) - assert.equal(endPoints[1], mockDevices[1].pushCallback) - assert.equal(endPoints[2], mockDevices[2].pushCallback) + assert.equal(endPoints.length, 3); + assert.equal(endPoints[0], mockDevices[0].pushCallback); + assert.equal(endPoints[1], mockDevices[1].pushCallback); + assert.equal(endPoints[2], mockDevices[2].pushCallback); }).finally(() => { - delete mockDevices[2].uaBrowserVersion - }) + delete mockDevices[2].uaBrowserVersion; + }); } - ) + ); it( 'push fails if data is present but both keys are not present', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.data_but_no_keys') { // data detected but device had no keys - count++ + count++; } } - }) + }); var devices = [{ 'id': 'foo', @@ -367,211 +367,211 @@ describe('push', () => { 'pushCallback': 'https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef', 'pushAuthKey': 'bogus', 'pushEndpointExpired': false - }] + }]; - const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig) - var options = { data: Buffer.from('foobar') } + const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig); + var options = { data: Buffer.from('foobar') }; return push.sendPush(mockUid, devices, 'accountVerify', options) .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'push catches devices with no push callback', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.no_push_callback') { // device had no push callback - count++ + count++; } } - }) + }); var devices = [{ 'id': 'foo', 'name': 'My Phone' - }] + }]; - const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig) + const push = require(pushModulePath)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, devices, 'accountVerify') .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'push reports errors when web-push fails', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.failed') { // web-push failed - count++ + count++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.reject(new Error('Failed')) + return P.reject(new Error('Failed')); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, [mockDevices[0]], 'accountVerify') .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'push logs an error when asked to send to more than 200 devices', () => { var thisMockLog = mockLog({ error: sinon.spy() - }) + }); - var devices = [] + var devices = []; for (var i = 0; i < 200; i++) { - devices.push(mockDevices[0]) + devices.push(mockDevices[0]); } var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - assert.equal(options.TTL, '0', 'sends the proper ttl header') - return P.resolve() + assert.equal(options.TTL, '0', 'sends the proper ttl header'); + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + }; + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, devices, 'accountVerify').then(function () { - assert.equal(thisMockLog.error.callCount, 0, 'log.error was not called') - devices.push(mockDevices[0]) - return push.sendPush(mockUid, devices, 'accountVerify') + assert.equal(thisMockLog.error.callCount, 0, 'log.error was not called'); + devices.push(mockDevices[0]); + return push.sendPush(mockUid, devices, 'accountVerify'); }).then(function () { - assert.equal(thisMockLog.error.callCount, 1, 'log.error was called') - var args = thisMockLog.error.getCall(0).args - assert.equal(args[0], 'push.sendPush') - assert.equal(args[1].err.message, 'Too many devices connected to account') - }) + assert.equal(thisMockLog.error.callCount, 1, 'log.error was called'); + var args = thisMockLog.error.getCall(0).args; + assert.equal(args[0], 'push.sendPush'); + assert.equal(args[1].err.message, 'Too many devices connected to account'); + }); } - ) + ); it( 'push resets device push data when push server responds with a 400 level error', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.reset_settings') { // web-push failed - assert.equal(mockDb.updateDevice.callCount, 1, 'db.updateDevice was called once') - var args = mockDb.updateDevice.args[0] - assert.equal(args.length, 2, 'db.updateDevice was passed two arguments') - assert.equal(args[1].sessionTokenId, null, 'sessionTokenId was null') - count++ + assert.equal(mockDb.updateDevice.callCount, 1, 'db.updateDevice was called once'); + var args = mockDb.updateDevice.args[0]; + assert.equal(args.length, 2, 'db.updateDevice was passed two arguments'); + assert.equal(args[1].sessionTokenId, null, 'sessionTokenId was null'); + count++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - var err = new Error('Failed') - err.statusCode = 410 - return P.reject(err) + var err = new Error('Failed'); + err.statusCode = 410; + return P.reject(err); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); // Careful, the argument gets modified in-place. - var device = JSON.parse(JSON.stringify(mockDevices[0])) + var device = JSON.parse(JSON.stringify(mockDevices[0])); return push.sendPush(mockUid, [device], 'accountVerify') .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'push resets device push data when a failure is caused by bad encryption keys', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.reset_settings') { // web-push failed - assert.equal(mockDb.updateDevice.callCount, 1, 'db.updateDevice was called once') - var args = mockDb.updateDevice.args[0] - assert.equal(args.length, 2, 'db.updateDevice was passed two arguments') - assert.equal(args[1].sessionTokenId, null, 'sessionTokenId argument was null') - count++ + assert.equal(mockDb.updateDevice.callCount, 1, 'db.updateDevice was called once'); + var args = mockDb.updateDevice.args[0]; + assert.equal(args.length, 2, 'db.updateDevice was passed two arguments'); + assert.equal(args[1].sessionTokenId, null, 'sessionTokenId argument was null'); + count++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - var err = new Error('Failed') - return P.reject(err) + var err = new Error('Failed'); + return P.reject(err); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); // Careful, the argument gets modified in-place. - var device = JSON.parse(JSON.stringify(mockDevices[0])) - device.pushPublicKey = 'E' + device.pushPublicKey.substring(1) // make the key invalid + var device = JSON.parse(JSON.stringify(mockDevices[0])); + device.pushPublicKey = 'E' + device.pushPublicKey.substring(1); // make the key invalid return push.sendPush(mockUid, [device], 'accountVerify') .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'push does not reset device push data after an unexpected failure', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.failed') { // web-push failed - assert.equal(mockDb.updateDevice.callCount, 0, 'db.updateDevice was not called') - count++ + assert.equal(mockDb.updateDevice.callCount, 0, 'db.updateDevice was not called'); + count++; } } - }) + }); var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - var err = new Error('Failed') - return P.reject(err) + var err = new Error('Failed'); + return P.reject(err); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, [mockDevices[0]], 'accountVerify') .then(() => { - assert.equal(count, 1) - }) + assert.equal(count, 1); + }); } - ) + ); it( 'notifyCommandReceived calls sendPush', @@ -579,19 +579,19 @@ describe('push', () => { const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); return push.notifyCommandReceived(mockUid, mockDevices[0], 'commandName', 'sendingDevice', 12, 'http://fetch.url', 42) .catch(err => { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(() => { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); assert.calledWithExactly(push.sendPush, mockUid, [mockDevices[0]], 'commandReceived', { data: { version: 1, @@ -604,14 +604,14 @@ describe('push', () => { } }, TTL: 42 - }) - const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - const schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, push.sendPush.getCall(0).args[3].data)) - push.sendPush.restore() - }) + }); + const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + const schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, push.sendPush.getCall(0).args[3].data)); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyDeviceConnected calls sendPush', @@ -619,39 +619,39 @@ describe('push', () => { const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') - var deviceName = 'My phone' + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); + var deviceName = 'My phone'; var expectedData = { version: 1, command: 'fxaccounts:device_connected', data: { deviceName: deviceName } - } + }; return push.notifyDeviceConnected(mockUid, mockDevices, deviceName) .catch(err => { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(() => { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.equal(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'deviceConnected') - const options = push.sendPush.getCall(0).args[3] - assert.deepEqual(options.data, expectedData) - const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - const schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, options.data)) - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.equal(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'deviceConnected'); + const options = push.sendPush.getCall(0).args[3]; + assert.deepEqual(options.data, expectedData); + const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + const schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, options.data)); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyDeviceDisconnected calls sendPush', @@ -659,40 +659,40 @@ describe('push', () => { const mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') - const idToDisconnect = mockDevices[0].id + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); + const idToDisconnect = mockDevices[0].id; const expectedData = { version: 1, command: 'fxaccounts:device_disconnected', data: { id: idToDisconnect } - } + }; return push.notifyDeviceDisconnected(mockUid, mockDevices, idToDisconnect) .catch(err => { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(() => { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.equal(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'deviceDisconnected') - const options = push.sendPush.getCall(0).args[3] - assert.deepEqual(options.data, expectedData) - const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - const schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, options.data)) - assert.ok(options.TTL, 'TTL should be set') - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.equal(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'deviceDisconnected'); + const options = push.sendPush.getCall(0).args[3]; + assert.deepEqual(options.data, expectedData); + const schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + const schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, options.data)); + assert.ok(options.TTL, 'TTL should be set'); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyPasswordChanged calls sendPush', @@ -700,34 +700,34 @@ describe('push', () => { var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); var expectedData = { version: 1, command: 'fxaccounts:password_changed' - } + }; return push.notifyPasswordChanged(mockUid, mockDevices).catch(function (err) { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(function() { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.equal(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'passwordChange') - var options = push.sendPush.getCall(0).args[3] - assert.deepEqual(options.data, expectedData) - var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - var schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, options.data)) - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.equal(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'passwordChange'); + var options = push.sendPush.getCall(0).args[3]; + assert.deepEqual(options.data, expectedData); + var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + var schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, options.data)); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyPasswordReset calls sendPush', @@ -735,34 +735,34 @@ describe('push', () => { var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); var expectedData = { version: 1, command: 'fxaccounts:password_reset' - } + }; return push.notifyPasswordReset(mockUid, mockDevices).catch(function (err) { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(function() { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.equal(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'passwordReset') - var options = push.sendPush.getCall(0).args[3] - assert.deepEqual(options.data, expectedData) - var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - var schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, options.data)) - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.equal(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'passwordReset'); + var options = push.sendPush.getCall(0).args[3]; + assert.deepEqual(options.data, expectedData); + var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + var schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, options.data)); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyAccountUpdated calls sendPush', @@ -770,26 +770,26 @@ describe('push', () => { var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.stub(push, 'sendPush').callsFake(() => P.resolve()) + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.stub(push, 'sendPush').callsFake(() => P.resolve()); return push.notifyAccountUpdated(mockUid, mockDevices, 'deviceConnected').catch(function (err) { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(function() { - assert.ok(push.sendPush.calledOnce, 'push was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.deepEqual(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'deviceConnected') - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'push was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.deepEqual(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'deviceConnected'); + push.sendPush.restore(); + }); } - ) + ); it( 'notifyAccountDestroyed calls sendPush', @@ -797,98 +797,98 @@ describe('push', () => { var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - return P.resolve() + return P.resolve(); } } - } - const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig) - sinon.spy(push, 'sendPush') + }; + const push = proxyquire(pushModulePath, mocks)(mockLog(), mockDb, mockConfig); + sinon.spy(push, 'sendPush'); var expectedData = { version: 1, command: 'fxaccounts:account_destroyed', data: { uid: mockUid } - } + }; return push.notifyAccountDestroyed(mockUid, mockDevices).catch(function (err) { - assert.fail('must not throw') - throw err + assert.fail('must not throw'); + throw err; }) .then(function() { - assert.ok(push.sendPush.calledOnce, 'sendPush was called') - assert.equal(push.sendPush.getCall(0).args[0], mockUid) - assert.equal(push.sendPush.getCall(0).args[1], mockDevices) - assert.equal(push.sendPush.getCall(0).args[2], 'accountDestroyed') - var options = push.sendPush.getCall(0).args[3] - assert.deepEqual(options.data, expectedData) - var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH) - var schema = JSON.parse(fs.readFileSync(schemaPath)) - assert.ok(ajv.validate(schema, options.data)) - push.sendPush.restore() - }) + assert.ok(push.sendPush.calledOnce, 'sendPush was called'); + assert.equal(push.sendPush.getCall(0).args[0], mockUid); + assert.equal(push.sendPush.getCall(0).args[1], mockDevices); + assert.equal(push.sendPush.getCall(0).args[2], 'accountDestroyed'); + var options = push.sendPush.getCall(0).args[3]; + assert.deepEqual(options.data, expectedData); + var schemaPath = path.resolve(__dirname, PUSH_PAYLOADS_SCHEMA_PATH); + var schema = JSON.parse(fs.readFileSync(schemaPath)); + assert.ok(ajv.validate(schema, options.data)); + push.sendPush.restore(); + }); } - ) + ); it( 'sendPush includes VAPID identification if it is configured', () => { - let count = 0 + let count = 0; var thisMockLog = mockLog({ info: function (op, log) { if (log.name === 'push.account_verify.success') { - count++ + count++; } } - }) + }); var mockConfig = { publicUrl: 'https://example.com', vapidKeysFile: path.join(__dirname, '../config/mock-vapid-keys.json') - } + }; var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - assert.ok(options.vapidDetails, 'sends the VAPID params object') - assert.equal(options.vapidDetails.subject, mockConfig.publicUrl, 'sends the correct VAPID subject') - assert.equal(options.vapidDetails.privateKey, 'private', 'sends the correct VAPID privkey') - assert.equal(options.vapidDetails.publicKey, 'public', 'sends the correct VAPID pubkey') - return P.resolve() + assert.ok(options.vapidDetails, 'sends the VAPID params object'); + assert.equal(options.vapidDetails.subject, mockConfig.publicUrl, 'sends the correct VAPID subject'); + assert.equal(options.vapidDetails.privateKey, 'private', 'sends the correct VAPID privkey'); + assert.equal(options.vapidDetails.publicKey, 'public', 'sends the correct VAPID pubkey'); + return P.resolve(); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, mockDevices, 'accountVerify') .then(() => { - assert.equal(count, 2) - }) + assert.equal(count, 2); + }); } - ) + ); it( 'sendPush errors out cleanly if given an unknown reason argument', () => { - var thisMockLog = mockLog() - var mockConfig = {} + var thisMockLog = mockLog(); + var mockConfig = {}; var mocks = { 'web-push': { sendNotification: function (sub, payload, options) { - assert.fail('should not have called sendNotification') - return P.reject('Should not have called sendNotification') + assert.fail('should not have called sendNotification'); + return P.reject('Should not have called sendNotification'); } } - } + }; - const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig) + const push = proxyquire(pushModulePath, mocks)(thisMockLog, mockDb, mockConfig); return push.sendPush(mockUid, mockDevices, 'anUnknownReasonString').then( function () { - assert(false, 'calling sendPush should have failed') + assert(false, 'calling sendPush should have failed'); }, function (err) { - assert.equal(err, 'Unknown push reason: anUnknownReasonString') + assert.equal(err, 'Unknown push reason: anUnknownReasonString'); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/pushbox.js b/test/local/pushbox.js index a63b154a..6f28271e 100644 --- a/test/local/pushbox.js +++ b/test/local/pushbox.js @@ -2,13 +2,13 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const nock = require('nock') -const pushboxModule = require('../../lib/pushbox') -const error = require('../../lib/error') -const {mockLog} = require('../mocks') +const { assert } = require('chai'); +const nock = require('nock'); +const pushboxModule = require('../../lib/pushbox'); +const error = require('../../lib/error'); +const {mockLog} = require('../mocks'); const mockConfig = { publicUrl: 'https://accounts.example.com', @@ -18,22 +18,22 @@ const mockConfig = { key: 'foo', maxTTL: 123456000 } -} -const mockDeviceIds = ['AAAA11', 'BBBB22', 'CCCC33'] -const mockData = 'eyJmb28iOiAiYmFyIn0' -const mockUid = 'ABCDEF' +}; +const mockDeviceIds = ['AAAA11', 'BBBB22', 'CCCC33']; +const mockData = 'eyJmb28iOiAiYmFyIn0'; +const mockUid = 'ABCDEF'; const mockPushboxServer = nock(mockConfig.pushbox.url, { reqheaders: {Authorization: `FxA-Server-Key ${mockConfig.pushbox.key}`} }).defaultReplyHeaders({ 'Content-Type': 'application/json' -}) +}); describe('pushbox', () => { afterEach(() => { - assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test') - }) + assert.ok(nock.isDone(), 'there should be no pending request mocks at the end of a test'); + }); it( 'retrieve', @@ -49,8 +49,8 @@ describe('pushbox', () => { // This is { foo: "bar", bar: "bar" }, encoded. data: 'eyJmb28iOiJiYXIiLCAiYmFyIjogImJhciJ9' }] - }) - const pushbox = pushboxModule(mockLog(), mockConfig) + }); + const pushbox = pushboxModule(mockLog(), mockConfig); return pushbox.retrieve(mockUid, mockDeviceIds[0], 50, 10) .then(resp => { assert.deepEqual(resp, { @@ -60,10 +60,10 @@ describe('pushbox', () => { index: 15, data: { foo: 'bar', bar: 'bar' } }] - }) - }) + }); + }); } - ) + ); it( 'retrieve validates the pushbox server response', @@ -72,19 +72,19 @@ describe('pushbox', () => { .query({ limit: 50, index: 10 }) .reply(200, { 'bogus':'object' - }) - const log = mockLog() - const pushbox = pushboxModule(log, mockConfig) + }); + const log = mockLog(); + const pushbox = pushboxModule(log, mockConfig); return pushbox.retrieve(mockUid, mockDeviceIds[0], 50, 10) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'pushbox.retrieve') - assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed') - }) + assert.ok(err); + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'pushbox.retrieve'); + assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed'); + }); } - ) + ); it( 'retrieve throws on error response', @@ -94,83 +94,83 @@ describe('pushbox', () => { .reply(200, { 'error': 'lamentably, an error hath occurred', status: 1234 - }) - const log = mockLog() - const pushbox = pushboxModule(log, mockConfig) + }); + const log = mockLog(); + const pushbox = pushboxModule(log, mockConfig); return pushbox.retrieve(mockUid, mockDeviceIds[0], 50, 10) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'pushbox.retrieve') - assert.equal(log.error.getCall(0).args[1].error, 'lamentably, an error hath occurred') - assert.equal(log.error.getCall(0).args[1].status, 1234) - }) + assert.ok(err); + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'pushbox.retrieve'); + assert.equal(log.error.getCall(0).args[1].error, 'lamentably, an error hath occurred'); + assert.equal(log.error.getCall(0).args[1].status, 1234); + }); } - ) + ); it( 'store', () => { - let requestBody + let requestBody; mockPushboxServer.post(`/v1/store/${mockUid}/${mockDeviceIds[0]}`, body => { - requestBody = body - return true + requestBody = body; + return true; }) .reply(200, { status: 200, index: '12' - }) - const pushbox = pushboxModule(mockLog(), mockConfig) + }); + const pushbox = pushboxModule(mockLog(), mockConfig); return pushbox.store(mockUid, mockDeviceIds[0], { test: 'data' }) .then(({index}) => { - assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 123456}) - assert.equal(index, '12') - }) + assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 123456}); + assert.equal(index, '12'); + }); } - ) + ); it( 'store with custom ttl', () => { - let requestBody + let requestBody; mockPushboxServer.post(`/v1/store/${mockUid}/${mockDeviceIds[0]}`, body => { - requestBody = body - return true + requestBody = body; + return true; }) .reply(200, { status: 200, index: '12' - }) - const pushbox = pushboxModule(mockLog(), mockConfig) + }); + const pushbox = pushboxModule(mockLog(), mockConfig); return pushbox.store(mockUid, mockDeviceIds[0], { test: 'data' }, 42) .then(({index}) => { - assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 42}) - assert.equal(index, '12') - }) + assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 42}); + assert.equal(index, '12'); + }); } - ) + ); it( 'store caps ttl at configured maximum', () => { - let requestBody + let requestBody; mockPushboxServer.post(`/v1/store/${mockUid}/${mockDeviceIds[0]}`, body => { - requestBody = body - return true + requestBody = body; + return true; }) .reply(200, { status: 200, index: '12' - }) - const pushbox = pushboxModule(mockLog(), mockConfig) + }); + const pushbox = pushboxModule(mockLog(), mockConfig); return pushbox.store(mockUid, mockDeviceIds[0], { test: 'data' }, 999999999) .then(({index}) => { - assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 123456}) - assert.equal(index, '12') - }) + assert.deepEqual(requestBody, {data: 'eyJ0ZXN0IjoiZGF0YSJ9', ttl: 123456}); + assert.equal(index, '12'); + }); } - ) + ); it( 'store validates the pushbox server response', @@ -178,19 +178,19 @@ describe('pushbox', () => { mockPushboxServer.post(`/v1/store/${mockUid}/${mockDeviceIds[0]}`) .reply(200, { 'bogus':'object' - }) - const log = mockLog() - const pushbox = pushboxModule(log, mockConfig) + }); + const log = mockLog(); + const pushbox = pushboxModule(log, mockConfig); return pushbox.store(mockUid, mockDeviceIds[0], { test: 'data' }) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'pushbox.store') - assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed') - }) + assert.ok(err); + assert.equal(err.errno, error.ERRNO.INTERNAL_VALIDATION_ERROR); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'pushbox.store'); + assert.equal(log.error.getCall(0).args[1].error, 'response schema validation failed'); + }); } - ) + ); it( 'retrieve throws on error response', @@ -199,39 +199,39 @@ describe('pushbox', () => { .reply(200, { 'error': 'Alas, an error! I knew it, Horatio.', 'status': 789 - }) - const log = mockLog() - const pushbox = pushboxModule(log, mockConfig) + }); + const log = mockLog(); + const pushbox = pushboxModule(log, mockConfig); return pushbox.store(mockUid, mockDeviceIds[0], { test: 'data' }) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE) - assert.equal(log.error.callCount, 1, 'an error was logged') - assert.equal(log.error.getCall(0).args[0], 'pushbox.store') - assert.equal(log.error.getCall(0).args[1].error, 'Alas, an error! I knew it, Horatio.') - assert.equal(log.error.getCall(0).args[1].status, 789) - }) + assert.ok(err); + assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE); + assert.equal(log.error.callCount, 1, 'an error was logged'); + assert.equal(log.error.getCall(0).args[0], 'pushbox.store'); + assert.equal(log.error.getCall(0).args[1].error, 'Alas, an error! I knew it, Horatio.'); + assert.equal(log.error.getCall(0).args[1].status, 789); + }); } - ) + ); it( 'feature disabled', () => { const config = Object.assign({}, mockConfig, { pushbox: {enabled: false} - }) - const pushbox = pushboxModule(mockLog(), config) + }); + const pushbox = pushboxModule(mockLog(), config); return pushbox.store(mockUid, mockDeviceIds[0], 'sendtab', mockData) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.message, 'Feature not enabled') + assert.ok(err); + assert.equal(err.message, 'Feature not enabled'); }) .then(() => pushbox.retrieve(mockUid, mockDeviceIds[0], 50, 10)) .then(() => assert.ok(false, 'should not happen'), (err) => { - assert.ok(err) - assert.equal(err.message, 'Feature not enabled') - }) + assert.ok(err); + assert.equal(err.message, 'Feature not enabled'); + }); } - ) + ); -}) +}); diff --git a/test/local/redis.js b/test/local/redis.js index 4a043d10..3898592f 100644 --- a/test/local/redis.js +++ b/test/local/redis.js @@ -2,140 +2,140 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -'use strict' +'use strict'; -const LIB_DIR = '../../lib' +const LIB_DIR = '../../lib'; -const { assert } = require('chai') -const error = require(`${LIB_DIR}/error`) -const mocks = require('../mocks') -const P = require(`${LIB_DIR}/promise`) -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const { assert } = require('chai'); +const error = require(`${LIB_DIR}/error`); +const mocks = require('../mocks'); +const P = require(`${LIB_DIR}/promise`); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); describe('lib/redis:', () => { - let config, log, mockError, redis, fxaShared, wrapper + let config, log, mockError, redis, fxaShared, wrapper; beforeEach(() => { - config = {} - log = mocks.mockLog() + config = {}; + log = mocks.mockLog(); redis = { foo: sinon.spy(() => { if (mockError) { - return P.reject(mockError) + return P.reject(mockError); } - return P.resolve('bar') + return P.resolve('bar'); }), baz: sinon.spy(), wibble: 'blee', - } - fxaShared = sinon.spy(() => redis) - wrapper = proxyquire(`${LIB_DIR}/redis`, { 'fxa-shared/redis': fxaShared })(config, log) - }) + }; + fxaShared = sinon.spy(() => redis); + wrapper = proxyquire(`${LIB_DIR}/redis`, { 'fxa-shared/redis': fxaShared })(config, log); + }); it('returned the wrapped interface', () => { - assert.isObject(wrapper) - assert.notEqual(wrapper, redis) - assert.lengthOf(Object.keys(wrapper), 3) + assert.isObject(wrapper); + assert.notEqual(wrapper, redis); + assert.lengthOf(Object.keys(wrapper), 3); - assert.isFunction(wrapper.foo) - assert.notEqual(wrapper.foo, redis.foo) - assert.lengthOf(wrapper.foo, 0) + assert.isFunction(wrapper.foo); + assert.notEqual(wrapper.foo, redis.foo); + assert.lengthOf(wrapper.foo, 0); - assert.isFunction(wrapper.baz) - assert.notEqual(wrapper.baz, redis.baz) - assert.lengthOf(wrapper.baz, 0) + assert.isFunction(wrapper.baz); + assert.notEqual(wrapper.baz, redis.baz); + assert.lengthOf(wrapper.baz, 0); - assert.equal(wrapper.wibble, 'blee') - }) + assert.equal(wrapper.wibble, 'blee'); + }); it('called fxa-shared', () => { - assert.equal(fxaShared.callCount, 1) - const args = fxaShared.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], config) - assert.equal(args[1], log) - }) + assert.equal(fxaShared.callCount, 1); + const args = fxaShared.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], config); + assert.equal(args[1], log); + }); it('did not call any redis methods', () => { - assert.equal(redis.foo.callCount, 0) - assert.equal(redis.baz.callCount, 0) - }) + assert.equal(redis.foo.callCount, 0); + assert.equal(redis.baz.callCount, 0); + }); describe('successful method call:', () => { - let result + let result; beforeEach(async () => { - result = await wrapper.foo('mock arg 1', 'mock arg 2') - }) + result = await wrapper.foo('mock arg 1', 'mock arg 2'); + }); it('returned the expected result', () => { - assert.equal(result, 'bar') - }) + assert.equal(result, 'bar'); + }); it('called the underlying redis method', () => { - assert.equal(redis.foo.callCount, 1) - const args = redis.foo.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'mock arg 1') - assert.equal(args[1], 'mock arg 2') - }) + assert.equal(redis.foo.callCount, 1); + const args = redis.foo.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'mock arg 1'); + assert.equal(args[1], 'mock arg 2'); + }); it('did not call the other redis method', () => { - assert.equal(redis.baz.callCount, 0) - }) - }) + assert.equal(redis.baz.callCount, 0); + }); + }); describe('conflicting method call:', () => { - let result, err + let result, err; beforeEach(async () => { try { - mockError = new Error('redis.watch.conflict') - result = await wrapper.foo() + mockError = new Error('redis.watch.conflict'); + result = await wrapper.foo(); } catch (e) { - err = e + err = e; } - }) + }); it('rejected with 409 conflict', () => { - assert.isUndefined(result) - assert.isObject(err) - assert.equal(err.errno, error.ERRNO.REDIS_CONFLICT) - assert.equal(err.message, 'Redis WATCH detected a conflicting update') - assert.equal(err.output.statusCode, 409) - assert.equal(err.output.payload.error, 'Conflict') - }) + assert.isUndefined(result); + assert.isObject(err); + assert.equal(err.errno, error.ERRNO.REDIS_CONFLICT); + assert.equal(err.message, 'Redis WATCH detected a conflicting update'); + assert.equal(err.output.statusCode, 409); + assert.equal(err.output.payload.error, 'Conflict'); + }); it('called the underlying redis method', () => { - assert.equal(redis.foo.callCount, 1) - }) - }) + assert.equal(redis.foo.callCount, 1); + }); + }); describe('failing method call:', () => { - let result, err + let result, err; beforeEach(async () => { try { - mockError = new Error('wibble') - result = await wrapper.foo() + mockError = new Error('wibble'); + result = await wrapper.foo(); } catch (e) { - err = e + err = e; } - }) + }); it('rejected with 500 error', () => { - assert.isUndefined(result) - assert.isObject(err) - assert.equal(err.errno, error.ERRNO.UNEXPECTED_ERROR) - assert.equal(err.message, 'Unspecified error') - assert.equal(err.output.statusCode, 500) - assert.equal(err.output.payload.error, 'Internal Server Error') - }) + assert.isUndefined(result); + assert.isObject(err); + assert.equal(err.errno, error.ERRNO.UNEXPECTED_ERROR); + assert.equal(err.message, 'Unspecified error'); + assert.equal(err.output.statusCode, 500); + assert.equal(err.output.payload.error, 'Internal Server Error'); + }); it('called the underlying redis method', () => { - assert.equal(redis.foo.callCount, 1) - }) - }) -}) + assert.equal(redis.foo.callCount, 1); + }); + }); +}); diff --git a/test/local/routes/account.js b/test/local/routes/account.js index a9a7d007..2e8a8daa 100644 --- a/test/local/routes/account.js +++ b/test/local/routes/account.js @@ -2,55 +2,55 @@ * 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/. */ -'use strict' +'use strict'; -var sinon = require('sinon') +var sinon = require('sinon'); -const { assert } = require('chai') -var mocks = require('../../mocks') -var getRoute = require('../../routes_helpers').getRoute -var proxyquire = require('proxyquire') +const { assert } = require('chai'); +var mocks = require('../../mocks'); +var getRoute = require('../../routes_helpers').getRoute; +var proxyquire = require('proxyquire'); -var P = require('../../../lib/promise') -var uuid = require('uuid') -var crypto = require('crypto') -var error = require('../../../lib/error') -var log = require('../../../lib/log') +var P = require('../../../lib/promise'); +var uuid = require('uuid'); +var crypto = require('crypto'); +var error = require('../../../lib/error'); +var log = require('../../../lib/log'); -var TEST_EMAIL = 'foo@gmail.com' +var TEST_EMAIL = 'foo@gmail.com'; function hexString(bytes) { - return crypto.randomBytes(bytes).toString('hex') + return crypto.randomBytes(bytes).toString('hex'); } var makeRoutes = function (options = {}, requireMocks) { - const config = options.config || {} - config.verifierVersion = config.verifierVersion || 0 - config.smtp = config.smtp || {} + const config = options.config || {}; + config.verifierVersion = config.verifierVersion || 0; + config.smtp = config.smtp || {}; config.memcached = config.memcached || { address: 'none', idle: 500, lifetime: 30 - } - config.lastAccessTimeUpdates = {} - config.signinConfirmation = config.signinConfirmation || {} - config.signinConfirmation.tokenVerificationCode = config.signinConfirmation.tokenVerificationCode || {} - config.signinUnblock = config.signinUnblock || {} - config.secondaryEmail = config.secondaryEmail || {} + }; + config.lastAccessTimeUpdates = {}; + config.signinConfirmation = config.signinConfirmation || {}; + config.signinConfirmation.tokenVerificationCode = config.signinConfirmation.tokenVerificationCode || {}; + config.signinUnblock = config.signinUnblock || {}; + config.secondaryEmail = config.secondaryEmail || {}; - const log = options.log || mocks.mockLog() - const mailer = options.mailer || {} - const Password = options.Password || require('../../../lib/crypto/password')(log, config) - const db = options.db || mocks.mockDB() + const log = options.log || mocks.mockLog(); + const mailer = options.mailer || {}; + const Password = options.Password || require('../../../lib/crypto/password')(log, config); + const db = options.db || mocks.mockDB(); const customs = options.customs || { - check: () => { return P.resolve(true) } - } - const signinUtils = options.signinUtils || require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer) + check: () => { return P.resolve(true); } + }; + const signinUtils = options.signinUtils || require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer); if (options.checkPassword) { - signinUtils.checkPassword = options.checkPassword + signinUtils.checkPassword = options.checkPassword; } - const push = options.push || require('../../../lib/push')(log, db, {}) + const push = options.push || require('../../../lib/push')(log, db, {}); return proxyquire('../../../lib/routes/account', requireMocks || {})( log, db, @@ -60,28 +60,28 @@ var makeRoutes = function (options = {}, requireMocks) { customs, signinUtils, push - ) -} + ); +}; function runTest(route, request, assertions) { return new P(function (resolve, reject) { try { - return route.handler(request).then(resolve, reject) + return route.handler(request).then(resolve, reject); } catch (err) { - reject(err) + reject(err); } }) - .then(assertions) + .then(assertions); } describe('/account/reset', function () { let uid, mockLog, mockMetricsContext, mockRequest, keyFetchTokenId, sessionTokenId, - mockDB, mockCustoms, mockPush, accountRoutes, route, clientAddress, mailer + mockDB, mockCustoms, mockPush, accountRoutes, route, clientAddress, mailer; beforeEach(() => { - uid = uuid.v4('binary').toString('hex') - mockLog = mocks.mockLog() - mockMetricsContext = mocks.mockMetricsContext() + uid = uuid.v4('binary').toString('hex'); + mockLog = mocks.mockLog(); + mockMetricsContext = mocks.mockMetricsContext(); mockRequest = mocks.mockRequest({ credentials: { uid: uid @@ -103,9 +103,9 @@ describe('/account/reset', function () { uaBrowserVersion: '57', uaOS: 'Mac OS X', uaOSVersion: '10.11' - }) - keyFetchTokenId = hexString(16) - sessionTokenId = hexString(16) + }); + keyFetchTokenId = hexString(16); + sessionTokenId = hexString(16); mockDB = mocks.mockDB({ uid: uid, email: TEST_EMAIL, @@ -113,10 +113,10 @@ describe('/account/reset', function () { keyFetchTokenId: keyFetchTokenId, sessionTokenId: sessionTokenId, wrapWrapKb: hexString(32) - }) - mockCustoms = mocks.mockCustoms() - mockPush = mocks.mockPush() - mailer = mocks.mockMailer() + }); + mockCustoms = mocks.mockCustoms(); + mockPush = mocks.mockPush(); + mailer = mocks.mockMailer(); accountRoutes = makeRoutes({ config: { securityHistory: { @@ -128,73 +128,73 @@ describe('/account/reset', function () { log: mockLog, push: mockPush, mailer - }) - route = getRoute(accountRoutes, '/account/reset') + }); + route = getRoute(accountRoutes, '/account/reset'); - clientAddress = mockRequest.app.clientAddress - }) + clientAddress = mockRequest.app.clientAddress; + }); describe('reset account with recovery key', () => { - let res + let res; beforeEach(() => { - mockRequest.payload.wrapKb = hexString(32) - mockRequest.payload.recoveryKeyId = hexString(16) - return runTest(route, mockRequest, (result) => res = result) - }) + mockRequest.payload.wrapKb = hexString(32); + mockRequest.payload.recoveryKeyId = hexString(16); + return runTest(route, mockRequest, (result) => res = result); + }); it('should return response', () => { - assert.ok(res.sessionToken, 'return sessionToken') - assert.ok(res.keyFetchToken, 'return keyFetchToken') - }) + assert.ok(res.sessionToken, 'return sessionToken'); + assert.ok(res.keyFetchToken, 'return keyFetchToken'); + }); it('should have checked for recovery key', () => { - assert.equal(mockDB.getRecoveryKey.callCount, 1) - const args = mockDB.getRecoveryKey.args[0] - assert.equal(args.length, 2, 'db.getRecoveryKey passed correct number of args') - assert.equal(args[0], uid, 'uid passed') - assert.equal(args[1], mockRequest.payload.recoveryKeyId, 'recovery key id passed') - }) + assert.equal(mockDB.getRecoveryKey.callCount, 1); + const args = mockDB.getRecoveryKey.args[0]; + assert.equal(args.length, 2, 'db.getRecoveryKey passed correct number of args'); + assert.equal(args[0], uid, 'uid passed'); + assert.equal(args[1], mockRequest.payload.recoveryKeyId, 'recovery key id passed'); + }); it('should have reset account with recovery key', () => { - assert.equal(mockDB.resetAccount.callCount, 1) - assert.equal(mockDB.resetAccountTokens.callCount, 1) - assert.equal(mockDB.createKeyFetchToken.callCount, 1) - const args = mockDB.createKeyFetchToken.args[0] - assert.equal(args.length, 1, 'db.createKeyFetchToken passed correct number of args') - assert.equal(args[0].uid, uid, 'uid passed') - assert.equal(args[0].wrapKb, mockRequest.payload.wrapKb, 'wrapKb passed') - }) + assert.equal(mockDB.resetAccount.callCount, 1); + assert.equal(mockDB.resetAccountTokens.callCount, 1); + assert.equal(mockDB.createKeyFetchToken.callCount, 1); + const args = mockDB.createKeyFetchToken.args[0]; + assert.equal(args.length, 1, 'db.createKeyFetchToken passed correct number of args'); + assert.equal(args[0].uid, uid, 'uid passed'); + assert.equal(args[0].wrapKb, mockRequest.payload.wrapKb, 'wrapKb passed'); + }); it('should have deleted recovery key', () => { - assert.equal(mockDB.deleteRecoveryKey.callCount, 1) - const args = mockDB.deleteRecoveryKey.args[0] - assert.equal(args.length, 1, 'db.deleteRecoveryKey passed correct number of args') - assert.equal(args[0], uid, 'uid passed') - }) + assert.equal(mockDB.deleteRecoveryKey.callCount, 1); + const args = mockDB.deleteRecoveryKey.args[0]; + assert.equal(args.length, 1, 'db.deleteRecoveryKey passed correct number of args'); + assert.equal(args[0], uid, 'uid passed'); + }); it('called mailer.sendPasswordResetAccountRecoveryNotification correctly', () => { - assert.equal(mailer.sendPasswordResetAccountRecoveryNotification.callCount, 1) - const args = mailer.sendPasswordResetAccountRecoveryNotification.args[0] - assert.equal(args.length, 3) - assert.equal(args[0][0].email, TEST_EMAIL) - }) + assert.equal(mailer.sendPasswordResetAccountRecoveryNotification.callCount, 1); + const args = mailer.sendPasswordResetAccountRecoveryNotification.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0][0].email, TEST_EMAIL); + }); it('should have reset custom server', () => { - assert.equal(mockCustoms.reset.callCount, 1) - }) + assert.equal(mockCustoms.reset.callCount, 1); + }); it('should have recorded security event', () => { - assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called') - const securityEvent = mockDB.securityEvent.args[0][0] - assert.equal(securityEvent.uid, uid) - assert.equal(securityEvent.ipAddr, clientAddress) - assert.equal(securityEvent.name, 'account.reset') - }) + assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called'); + const securityEvent = mockDB.securityEvent.args[0][0]; + assert.equal(securityEvent.uid, uid); + assert.equal(securityEvent.ipAddr, clientAddress); + assert.equal(securityEvent.name, 'account.reset'); + }); it('should have emitted metrics', () => { - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - const args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + const args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.reset', @@ -202,75 +202,75 @@ describe('/account/reset', function () { service: undefined, userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockMetricsContext.validate.callCount, 0) - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 0) - assert.equal(mockMetricsContext.propagate.callCount, 2) - }) + assert.equal(mockMetricsContext.validate.callCount, 0); + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 0); + assert.equal(mockMetricsContext.propagate.callCount, 2); + }); it('should have created session', () => { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - const args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser') - assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version') - assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os') - assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version') - assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type') - assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor') - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + const args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser'); + assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version'); + assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os'); + assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version'); + assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type'); + assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor'); + }); + }); describe('reset account with totp', () => { - let res + let res; beforeEach(() => { mockDB.totpToken = sinon.spy(() => { return P.resolve({ verified: true, enabled: true - }) - }) - return runTest(route, mockRequest, (result) => res = result) - }) + }); + }); + return runTest(route, mockRequest, (result) => res = result); + }); it('should return response', () => { - assert.ok(res.sessionToken, 'return sessionToken') - assert.ok(res.keyFetchToken, 'return keyFetchToken') - assert.equal(res.verified, false, 'return verified false') - assert.equal(res.verificationMethod, 'totp-2fa', 'verification method set') - }) + assert.ok(res.sessionToken, 'return sessionToken'); + assert.ok(res.keyFetchToken, 'return keyFetchToken'); + assert.equal(res.verified, false, 'return verified false'); + assert.equal(res.verificationMethod, 'totp-2fa', 'verification method set'); + }); it('should have created unverified sessionToken', () => { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - const args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.ok(args[0].tokenVerificationId, 'tokenVerificationId is set') - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + const args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.ok(args[0].tokenVerificationId, 'tokenVerificationId is set'); + }); it('should have created unverified keyFetchToken', () => { - assert.equal(mockDB.createKeyFetchToken.callCount, 1, 'db.createKeyFetchToken was called once') - const args = mockDB.createKeyFetchToken.args[0] - assert.equal(args.length, 1, 'db.createKeyFetchToken was passed one argument') - assert.ok(args[0].tokenVerificationId, 'tokenVerificationId is set') - }) - }) + assert.equal(mockDB.createKeyFetchToken.callCount, 1, 'db.createKeyFetchToken was called once'); + const args = mockDB.createKeyFetchToken.args[0]; + assert.equal(args.length, 1, 'db.createKeyFetchToken was passed one argument'); + assert.ok(args[0].tokenVerificationId, 'tokenVerificationId is set'); + }); + }); it('should reset account', () => { return runTest(route, mockRequest, function (res) { - assert.equal(mockDB.resetAccount.callCount, 1) - assert.equal(mockDB.resetAccountTokens.callCount, 1) + assert.equal(mockDB.resetAccount.callCount, 1); + assert.equal(mockDB.resetAccountTokens.callCount, 1); - assert.equal(mockPush.notifyPasswordReset.callCount, 1) - assert.deepEqual(mockPush.notifyPasswordReset.firstCall.args[0], uid) + assert.equal(mockPush.notifyPasswordReset.callCount, 1); + assert.deepEqual(mockPush.notifyPasswordReset.firstCall.args[0], uid); - assert.equal(mockDB.account.callCount, 1) - assert.equal(mockCustoms.reset.callCount, 1) + assert.equal(mockDB.account.callCount, 1); + assert.equal(mockCustoms.reset.callCount, 1); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - var args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + var args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.reset', @@ -278,59 +278,59 @@ describe('/account/reset', function () { service: undefined, userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called') - var securityEvent = mockDB.securityEvent.args[0][0] - assert.equal(securityEvent.uid, uid) - assert.equal(securityEvent.ipAddr, clientAddress) - assert.equal(securityEvent.name, 'account.reset') + assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called'); + var securityEvent = mockDB.securityEvent.args[0][0]; + assert.equal(securityEvent.uid, uid); + assert.equal(securityEvent.ipAddr, clientAddress); + assert.equal(securityEvent.name, 'account.reset'); - assert.equal(mockMetricsContext.validate.callCount, 0) - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 0) + assert.equal(mockMetricsContext.validate.callCount, 0); + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 0); - assert.equal(mockMetricsContext.propagate.callCount, 2) + assert.equal(mockMetricsContext.propagate.callCount, 2); - args = mockMetricsContext.propagate.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0].uid, uid) - assert.equal(args[1].uid, uid) - assert.equal(args[1].id, sessionTokenId) + args = mockMetricsContext.propagate.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0].uid, uid); + assert.equal(args[1].uid, uid); + assert.equal(args[1].id, sessionTokenId); - args = mockMetricsContext.propagate.args[1] - assert.lengthOf(args, 2) - assert.equal(args[0].uid, uid) - assert.equal(args[1].uid, uid) - assert.equal(args[1].id, keyFetchTokenId) + args = mockMetricsContext.propagate.args[1]; + assert.lengthOf(args, 2); + assert.equal(args[0].uid, uid); + assert.equal(args[1].uid, uid); + assert.equal(args[1].id, keyFetchTokenId); - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.equal(args[0].tokenVerificationId, null, 'tokenVerificationId is not set') - assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser') - assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version') - assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os') - assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version') - assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type') - assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor') - }) - }) -}) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.equal(args[0].tokenVerificationId, null, 'tokenVerificationId is not set'); + assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser'); + assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version'); + assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os'); + assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version'); + assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type'); + assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor'); + }); + }); +}); describe('/account/create', () => { function setup() { - const mockLog = log('ERROR', 'test') + const mockLog = log('ERROR', 'test'); mockLog.activityEvent = sinon.spy(() => { - return P.resolve() - }) + return P.resolve(); + }); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) - mockLog.error = sinon.spy() - mockLog.notifier.send = sinon.spy() + return P.resolve(); + }); + mockLog.error = sinon.spy(); + mockLog.notifier.send = sinon.spy(); - const mockMetricsContext = mocks.mockMetricsContext() + const mockMetricsContext = mocks.mockMetricsContext(); const mockRequest = mocks.mockRequest({ locale: 'en-GB', log: mockLog, @@ -360,12 +360,12 @@ describe('/account/create', () => { uaOSVersion: '11', uaDeviceType: 'tablet', uaFormFactor: 'iPad' - }) - const clientAddress = mockRequest.app.clientAddress - const emailCode = hexString(16) - const keyFetchTokenId = hexString(16) - const sessionTokenId = hexString(16) - const uid = uuid.v4('binary').toString('hex') + }); + const clientAddress = mockRequest.app.clientAddress; + const emailCode = hexString(16); + const keyFetchTokenId = hexString(16); + const sessionTokenId = hexString(16); + const uid = uuid.v4('binary').toString('hex'); const mockDB = mocks.mockDB({ email: TEST_EMAIL, emailCode: emailCode, @@ -381,9 +381,9 @@ describe('/account/create', () => { wrapWrapKb: 'wibble' }, { emailRecord: new error.unknownAccount() - }) - const mockMailer = mocks.mockMailer() - const mockPush = mocks.mockPush() + }); + const mockMailer = mocks.mockMailer(); + const mockPush = mocks.mockPush(); const accountRoutes = makeRoutes({ config: { securityHistory: { @@ -396,16 +396,16 @@ describe('/account/create', () => { Password: function () { return { unwrap: function () { - return P.resolve('wibble') + return P.resolve('wibble'); }, verifyHash: function () { - return P.resolve('wibble') + return P.resolve('wibble'); } - } + }; }, push: mockPush - }) - const route = getRoute(accountRoutes, '/account/create') + }); + const route = getRoute(accountRoutes, '/account/create'); return { clientAddress, @@ -419,45 +419,45 @@ describe('/account/create', () => { route, sessionTokenId, uid - } + }; } it('should create a sync account', () => { - const mocked = setup() - const clientAddress = mocked.clientAddress - const emailCode = mocked.emailCode - const keyFetchTokenId = mocked.keyFetchTokenId - const mockDB = mocked.mockDB - const mockLog = mocked.mockLog - const mockMailer = mocked.mockMailer - const mockMetricsContext = mocked.mockMetricsContext - const mockRequest = mocked.mockRequest - const route = mocked.route - const sessionTokenId = mocked.sessionTokenId - const uid = mocked.uid + const mocked = setup(); + const clientAddress = mocked.clientAddress; + const emailCode = mocked.emailCode; + const keyFetchTokenId = mocked.keyFetchTokenId; + const mockDB = mocked.mockDB; + const mockLog = mocked.mockLog; + const mockMailer = mocked.mockMailer; + const mockMetricsContext = mocked.mockMetricsContext; + const mockRequest = mocked.mockRequest; + const route = mocked.route; + const sessionTokenId = mocked.sessionTokenId; + const uid = mocked.uid; - const now = Date.now() - sinon.stub(Date, 'now').callsFake(() => now) + const now = Date.now(); + sinon.stub(Date, 'now').callsFake(() => now); return runTest(route, mockRequest, () => { - assert.equal(mockDB.createAccount.callCount, 1, 'createAccount was called') + assert.equal(mockDB.createAccount.callCount, 1, 'createAccount was called'); - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - let args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.equal(args[0].uaBrowser, 'Firefox Mobile', 'db.createSessionToken was passed correct browser') - assert.equal(args[0].uaBrowserVersion, '9', 'db.createSessionToken was passed correct browser version') - assert.equal(args[0].uaOS, 'iOS', 'db.createSessionToken was passed correct os') - assert.equal(args[0].uaOSVersion, '11', 'db.createSessionToken was passed correct os version') - assert.equal(args[0].uaDeviceType, 'tablet', 'db.createSessionToken was passed correct device type') - assert.equal(args[0].uaFormFactor, 'iPad', 'db.createSessionToken was passed correct form factor') + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + let args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.equal(args[0].uaBrowser, 'Firefox Mobile', 'db.createSessionToken was passed correct browser'); + assert.equal(args[0].uaBrowserVersion, '9', 'db.createSessionToken was passed correct browser version'); + assert.equal(args[0].uaOS, 'iOS', 'db.createSessionToken was passed correct os'); + assert.equal(args[0].uaOSVersion, '11', 'db.createSessionToken was passed correct os version'); + assert.equal(args[0].uaDeviceType, 'tablet', 'db.createSessionToken was passed correct device type'); + assert.equal(args[0].uaFormFactor, 'iPad', 'db.createSessionToken was passed correct form factor'); - assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged') - var eventData = mockLog.notifier.send.getCall(0).args[0] - assert.equal(eventData.event, 'login', 'it was a login event') - assert.equal(eventData.data.service, 'sync', 'it was for sync') - assert.equal(eventData.data.email, TEST_EMAIL, 'it was for the correct email') - assert.ok(eventData.data.ts, 'timestamp of event set') + assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged'); + var eventData = mockLog.notifier.send.getCall(0).args[0]; + assert.equal(eventData.event, 'login', 'it was a login event'); + assert.equal(eventData.data.service, 'sync', 'it was for sync'); + assert.equal(eventData.data.email, TEST_EMAIL, 'it was for the correct email'); + assert.ok(eventData.data.ts, 'timestamp of event set'); assert.deepEqual(eventData.data.metricsContext, { entrypoint: 'blee', flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime, @@ -471,11 +471,11 @@ describe('/account/create', () => { utm_medium: 'utm medium', utm_source: 'utm source', utm_term: 'utm term' - }, 'it contained the correct metrics context metadata') + }, 'it contained the correct metrics context metadata'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.created', @@ -483,11 +483,11 @@ describe('/account/create', () => { service: 'sync', userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent was called once') - args = mockLog.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument') + assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent was called once'); + args = mockLog.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', entrypoint: 'blee', @@ -507,85 +507,85 @@ describe('/account/create', () => { utm_medium: 'utm medium', utm_source: 'utm source', utm_term: 'utm term' - }, 'flow event data was correct') + }, 'flow event data was correct'); - assert.equal(mockMetricsContext.validate.callCount, 1, 'metricsContext.validate was called') - assert.equal(mockMetricsContext.validate.args[0].length, 0, 'validate was called without arguments') + assert.equal(mockMetricsContext.validate.callCount, 1, 'metricsContext.validate was called'); + assert.equal(mockMetricsContext.validate.args[0].length, 0, 'validate was called without arguments'); - assert.equal(mockMetricsContext.stash.callCount, 3, 'metricsContext.stash was called three times') + assert.equal(mockMetricsContext.stash.callCount, 3, 'metricsContext.stash was called three times'); - args = mockMetricsContext.stash.args[0] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument first time') - assert.deepEqual(args[0].id, sessionTokenId, 'argument was session token') - assert.deepEqual(args[0].uid, uid, 'sessionToken.uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[0], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[0]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument first time'); + assert.deepEqual(args[0].id, sessionTokenId, 'argument was session token'); + assert.deepEqual(args[0].uid, uid, 'sessionToken.uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[0], mockRequest, 'this was request'); - args = mockMetricsContext.stash.args[1] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument second time') - assert.equal(args[0].id, emailCode, 'argument was synthesized token') - assert.deepEqual(args[0].uid, uid, 'token.uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[1]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument second time'); + assert.equal(args[0].id, emailCode, 'argument was synthesized token'); + assert.deepEqual(args[0].uid, uid, 'token.uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request'); - args = mockMetricsContext.stash.args[2] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument third time') - assert.deepEqual(args[0].id, keyFetchTokenId, 'argument was key fetch token') - assert.deepEqual(args[0].uid, uid, 'keyFetchToken.uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[2], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[2]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument third time'); + assert.deepEqual(args[0].id, keyFetchTokenId, 'argument was key fetch token'); + assert.deepEqual(args[0].uid, uid, 'keyFetchToken.uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[2], mockRequest, 'this was request'); - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once') - args = mockMetricsContext.setFlowCompleteSignal.args[0] - assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments') - assert.equal(args[0], 'account.signed', 'first argument was event name') - assert.equal(args[1], 'registration', 'second argument was flow type') + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once'); + args = mockMetricsContext.setFlowCompleteSignal.args[0]; + assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments'); + assert.equal(args[0], 'account.signed', 'first argument was event name'); + assert.equal(args[1], 'registration', 'second argument was flow type'); - var securityEvent = mockDB.securityEvent - assert.equal(securityEvent.callCount, 1, 'db.securityEvent is called') - securityEvent = securityEvent.args[0][0] - assert.equal(securityEvent.name, 'account.create') - assert.equal(securityEvent.uid, uid) - assert.equal(securityEvent.ipAddr, clientAddress) + var securityEvent = mockDB.securityEvent; + assert.equal(securityEvent.callCount, 1, 'db.securityEvent is called'); + securityEvent = securityEvent.args[0][0]; + assert.equal(securityEvent.name, 'account.create'); + assert.equal(securityEvent.uid, uid); + assert.equal(securityEvent.ipAddr, clientAddress); - assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called') - args = mockMailer.sendVerifyCode.args[0] - assert.equal(args[2].location.city, 'Mountain View') - assert.equal(args[2].location.country, 'United States') - assert.equal(args[2].uaBrowser, 'Firefox Mobile') - assert.equal(args[2].uaBrowserVersion, '9') - assert.equal(args[2].uaOS, 'iOS') - assert.equal(args[2].uaOSVersion, '11') - assert.strictEqual(args[2].uaDeviceType, 'tablet') - assert.equal(args[2].deviceId, mockRequest.payload.metricsContext.deviceId) - assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId) - assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime) - assert.equal(args[2].service, 'sync') - assert.equal(args[2].uid, uid) + assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called'); + args = mockMailer.sendVerifyCode.args[0]; + assert.equal(args[2].location.city, 'Mountain View'); + assert.equal(args[2].location.country, 'United States'); + assert.equal(args[2].uaBrowser, 'Firefox Mobile'); + assert.equal(args[2].uaBrowserVersion, '9'); + assert.equal(args[2].uaOS, 'iOS'); + assert.equal(args[2].uaOSVersion, '11'); + assert.strictEqual(args[2].uaDeviceType, 'tablet'); + assert.equal(args[2].deviceId, mockRequest.payload.metricsContext.deviceId); + assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId); + assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime); + assert.equal(args[2].service, 'sync'); + assert.equal(args[2].uid, uid); - assert.equal(mockLog.error.callCount, 0) - }).finally(() => Date.now.restore()) - }) + assert.equal(mockLog.error.callCount, 0); + }).finally(() => Date.now.restore()); + }); it('should create a non-sync account', () => { - const mocked = setup() - const mockLog = mocked.mockLog - const mockMailer = mocked.mockMailer - const mockRequest = mocked.mockRequest - const route = mocked.route - const uid = mocked.uid + const mocked = setup(); + const mockLog = mocked.mockLog; + const mockMailer = mocked.mockMailer; + const mockRequest = mocked.mockRequest; + const route = mocked.route; + const uid = mocked.uid; - const now = Date.now() - sinon.stub(Date, 'now').callsFake(() => now) + const now = Date.now(); + sinon.stub(Date, 'now').callsFake(() => now); - mockRequest.payload.service = 'foo' + mockRequest.payload.service = 'foo'; return runTest(route, mockRequest, () => { - assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged') - var eventData = mockLog.notifier.send.getCall(0).args[0] - assert.equal(eventData.event, 'login', 'it was a login event') - assert.equal(eventData.data.service, 'foo', 'it was for the expected service') + assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged'); + var eventData = mockLog.notifier.send.getCall(0).args[0]; + assert.equal(eventData.event, 'login', 'it was a login event'); + assert.equal(eventData.data.service, 'foo', 'it was for the expected service'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - let args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + let args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.created', @@ -593,47 +593,47 @@ describe('/account/create', () => { service: 'foo', userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called') - args = mockMailer.sendVerifyCode.args[0] - assert.equal(args[2].service, 'foo') - assert.equal(mockLog.error.callCount, 0) - }).finally(() => Date.now.restore()) - }) + assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called'); + args = mockMailer.sendVerifyCode.args[0]; + assert.equal(args[2].service, 'foo'); + assert.equal(mockLog.error.callCount, 0); + }).finally(() => Date.now.restore()); + }); it('should return an error if email fails to send', () => { - const mocked = setup() - const mockMailer = mocked.mockMailer - const mockRequest = mocked.mockRequest - const route = mocked.route + const mocked = setup(); + const mockMailer = mocked.mockMailer; + const mockRequest = mocked.mockRequest; + const route = mocked.route; - mockMailer.sendVerifyCode = sinon.spy(() => P.reject()) + mockMailer.sendVerifyCode = sinon.spy(() => P.reject()); return runTest(route, mockRequest).then(assert.fail, (err) => { - assert.equal(err.message, 'Failed to send email') - assert.equal(err.output.payload.code, 422) - assert.equal(err.output.payload.errno, 151) - assert.equal(err.output.payload.error, 'Unprocessable Entity') - }) - }) + assert.equal(err.message, 'Failed to send email'); + assert.equal(err.output.payload.code, 422); + assert.equal(err.output.payload.errno, 151); + assert.equal(err.output.payload.error, 'Unprocessable Entity'); + }); + }); it('should return a bounce error if send fails with one', () => { - const mocked = setup() - const mockMailer = mocked.mockMailer - const mockRequest = mocked.mockRequest - const route = mocked.route + const mocked = setup(); + const mockMailer = mocked.mockMailer; + const mockRequest = mocked.mockRequest; + const route = mocked.route; - mockMailer.sendVerifyCode = sinon.spy(() => P.reject(error.emailBouncedHard(42))) + mockMailer.sendVerifyCode = sinon.spy(() => P.reject(error.emailBouncedHard(42))); return runTest(route, mockRequest).then(assert.fail, (err) => { - assert.equal(err.message, 'Email account hard bounced') - assert.equal(err.output.payload.code, 400) - assert.equal(err.output.payload.errno, 134) - assert.equal(err.output.payload.error, 'Bad Request') - }) - }) -}) + assert.equal(err.message, 'Email account hard bounced'); + assert.equal(err.output.payload.code, 400); + assert.equal(err.output.payload.errno, 134); + assert.equal(err.output.payload.error, 'Bad Request'); + }); + }); +}); describe('/account/login', function () { var config = { @@ -648,16 +648,16 @@ describe('/account/login', function () { signinUnblock: { codeLifetime: 1000 } - } - const mockLog = log('ERROR', 'test') + }; + const mockLog = log('ERROR', 'test'); mockLog.activityEvent = sinon.spy(() => { - return P.resolve() - }) + return P.resolve(); + }); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) - mockLog.notifier.send = sinon.spy() - const mockMetricsContext = mocks.mockMetricsContext() + return P.resolve(); + }); + mockLog.notifier.send = sinon.spy(); + const mockMetricsContext = mocks.mockMetricsContext(); const mockRequest = mocks.mockRequest({ log: mockLog, @@ -691,7 +691,7 @@ describe('/account/login', function () { uaOS: 'Android', uaOSVersion: '6', uaDeviceType: 'mobile' - }) + }); const mockRequestNoKeys = mocks.mockRequest({ log: mockLog, metricsContext: mockMetricsContext, @@ -708,7 +708,7 @@ describe('/account/login', function () { } }, query: {} - }) + }); const mockRequestWithUnblockCode = mocks.mockRequest({ log: mockLog, payload: { @@ -723,10 +723,10 @@ describe('/account/login', function () { flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103' } } - }) - var keyFetchTokenId = hexString(16) - var sessionTokenId = hexString(16) - var uid = uuid.v4('binary').toString('hex') + }); + var keyFetchTokenId = hexString(16); + var sessionTokenId = hexString(16); + var uid = uuid.v4('binary').toString('hex'); var mockDB = mocks.mockDB({ email: TEST_EMAIL, emailVerified: true, @@ -738,16 +738,16 @@ describe('/account/login', function () { uaOSVersion: '6', uaDeviceType: 'mobile', uid: uid - }) - var mockMailer = mocks.mockMailer() - var mockPush = mocks.mockPush() + }); + var mockMailer = mocks.mockMailer(); + var mockPush = mocks.mockPush(); var mockCustoms = { check: () => P.resolve(), flag: () => P.resolve() - } + }; var accountRoutes = makeRoutes({ checkPassword: function () { - return P.resolve(true) + return P.resolve(true); }, config: config, customs: mockCustoms, @@ -755,56 +755,56 @@ describe('/account/login', function () { log: mockLog, mailer: mockMailer, push: mockPush - }) - var route = getRoute(accountRoutes, '/account/login') + }); + var route = getRoute(accountRoutes, '/account/login'); - const defaultEmailRecord = mockDB.emailRecord - const defaultEmailAccountRecord = mockDB.accountRecord + const defaultEmailRecord = mockDB.emailRecord; + const defaultEmailAccountRecord = mockDB.accountRecord; afterEach(() => { - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - mockMailer.sendNewDeviceLoginNotification = sinon.spy(() => P.resolve([])) - mockMailer.sendVerifyLoginEmail = sinon.spy(() => P.resolve()) - mockMailer.sendVerifyCode.resetHistory() - mockDB.createSessionToken.resetHistory() - mockDB.sessions.resetHistory() - mockMetricsContext.stash.resetHistory() - mockMetricsContext.validate.resetHistory() - mockMetricsContext.setFlowCompleteSignal.resetHistory() - mockDB.emailRecord = defaultEmailRecord - mockDB.emailRecord.resetHistory() - mockDB.accountRecord = defaultEmailAccountRecord - mockDB.accountRecord.resetHistory() - mockDB.getSecondaryEmail = sinon.spy(() => P.reject(error.unknownSecondaryEmail())) - mockDB.getSecondaryEmail.resetHistory() - mockRequest.payload.email = TEST_EMAIL - mockRequest.payload.verificationMethod = undefined - }) + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + mockMailer.sendNewDeviceLoginNotification = sinon.spy(() => P.resolve([])); + mockMailer.sendVerifyLoginEmail = sinon.spy(() => P.resolve()); + mockMailer.sendVerifyCode.resetHistory(); + mockDB.createSessionToken.resetHistory(); + mockDB.sessions.resetHistory(); + mockMetricsContext.stash.resetHistory(); + mockMetricsContext.validate.resetHistory(); + mockMetricsContext.setFlowCompleteSignal.resetHistory(); + mockDB.emailRecord = defaultEmailRecord; + mockDB.emailRecord.resetHistory(); + mockDB.accountRecord = defaultEmailAccountRecord; + mockDB.accountRecord.resetHistory(); + mockDB.getSecondaryEmail = sinon.spy(() => P.reject(error.unknownSecondaryEmail())); + mockDB.getSecondaryEmail.resetHistory(); + mockRequest.payload.email = TEST_EMAIL; + mockRequest.payload.verificationMethod = undefined; + }); it('emits the correct series of calls and events', function () { - const now = Date.now() - sinon.stub(Date, 'now').callsFake(() => now) + const now = Date.now(); + sinon.stub(Date, 'now').callsFake(() => now); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.accountRecord.callCount, 1, 'db.accountRecord was called') + assert.equal(mockDB.accountRecord.callCount, 1, 'db.accountRecord was called'); - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - let args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser') - assert.equal(args[0].uaBrowserVersion, '50', 'db.createSessionToken was passed correct browser version') - assert.equal(args[0].uaOS, 'Android', 'db.createSessionToken was passed correct os') - assert.equal(args[0].uaOSVersion, '6', 'db.createSessionToken was passed correct os version') - assert.equal(args[0].uaDeviceType, 'mobile', 'db.createSessionToken was passed correct device type') - assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor') + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + let args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser'); + assert.equal(args[0].uaBrowserVersion, '50', 'db.createSessionToken was passed correct browser version'); + assert.equal(args[0].uaOS, 'Android', 'db.createSessionToken was passed correct os'); + assert.equal(args[0].uaOSVersion, '6', 'db.createSessionToken was passed correct os version'); + assert.equal(args[0].uaDeviceType, 'mobile', 'db.createSessionToken was passed correct device type'); + assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor'); - assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged') - const eventData = mockLog.notifier.send.getCall(0).args[0] - assert.equal(eventData.event, 'login', 'it was a login event') - assert.equal(eventData.data.service, 'sync', 'it was for sync') - assert.equal(eventData.data.email, TEST_EMAIL, 'it was for the correct email') - assert.ok(eventData.data.ts, 'timestamp of event set') + assert.equal(mockLog.notifier.send.callCount, 1, 'an sqs event was logged'); + const eventData = mockLog.notifier.send.getCall(0).args[0]; + assert.equal(eventData.event, 'login', 'it was a login event'); + assert.equal(eventData.data.service, 'sync', 'it was for sync'); + assert.equal(eventData.data.email, TEST_EMAIL, 'it was for the correct email'); + assert.ok(eventData.data.ts, 'timestamp of event set'); assert.deepEqual(eventData.data.metricsContext, { time: now, flow_id: mockRequest.payload.metricsContext.flowId, @@ -812,11 +812,11 @@ describe('/account/login', function () { flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime, flowCompleteSignal: 'account.signed', flowType: undefined - }, 'metrics context was correct') + }, 'metrics context was correct'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.login', @@ -824,11 +824,11 @@ describe('/account/login', function () { service: 'sync', userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice') - args = mockLog.flowEvent.args[0] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument first time') + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice'); + args = mockLog.flowEvent.args[0]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument first time'); assert.deepEqual(args[0], { country: 'United States', event: 'account.login', @@ -842,9 +842,9 @@ describe('/account/login', function () { time: now, uid: uid, userAgent: 'test user-agent' - }, 'first flow event was correct') - args = mockLog.flowEvent.args[1] - assert.equal(args.length, 1, 'log.flowEvent was passed one argument second time') + }, 'first flow event was correct'); + args = mockLog.flowEvent.args[1]; + assert.equal(args.length, 1, 'log.flowEvent was passed one argument second time'); assert.deepEqual(args[0], { country: 'United States', event: 'email.confirmation.sent', @@ -857,63 +857,63 @@ describe('/account/login', function () { region: 'California', time: now, userAgent: 'test user-agent' - }, 'second flow event was correct') + }, 'second flow event was correct'); - assert.equal(mockMetricsContext.validate.callCount, 1, 'metricsContext.validate was called') - assert.equal(mockMetricsContext.validate.args[0].length, 0, 'validate was called without arguments') + assert.equal(mockMetricsContext.validate.callCount, 1, 'metricsContext.validate was called'); + assert.equal(mockMetricsContext.validate.args[0].length, 0, 'validate was called without arguments'); - assert.equal(mockMetricsContext.stash.callCount, 3, 'metricsContext.stash was called three times') + assert.equal(mockMetricsContext.stash.callCount, 3, 'metricsContext.stash was called three times'); - args = mockMetricsContext.stash.args[0] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument first time') - assert.deepEqual(args[0].id, sessionTokenId, 'argument was session token') - assert.deepEqual(args[0].uid, uid, 'sessionToken.uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[0], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[0]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument first time'); + assert.deepEqual(args[0].id, sessionTokenId, 'argument was session token'); + assert.deepEqual(args[0].uid, uid, 'sessionToken.uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[0], mockRequest, 'this was request'); - args = mockMetricsContext.stash.args[1] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument second time') - assert.ok(/^[0-9a-f]{32}$/.test(args[0].id), 'argument was synthesized token verification id') - assert.deepEqual(args[0].uid, uid, 'tokenVerificationId uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[1]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument second time'); + assert.ok(/^[0-9a-f]{32}$/.test(args[0].id), 'argument was synthesized token verification id'); + assert.deepEqual(args[0].uid, uid, 'tokenVerificationId uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request'); - args = mockMetricsContext.stash.args[2] - assert.equal(args.length, 1, 'metricsContext.stash was passed one argument third time') - assert.deepEqual(args[0].id, keyFetchTokenId, 'argument was key fetch token') - assert.deepEqual(args[0].uid, uid, 'keyFetchToken.uid was correct') - assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request') + args = mockMetricsContext.stash.args[2]; + assert.equal(args.length, 1, 'metricsContext.stash was passed one argument third time'); + assert.deepEqual(args[0].id, keyFetchTokenId, 'argument was key fetch token'); + assert.deepEqual(args[0].uid, uid, 'keyFetchToken.uid was correct'); + assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request'); - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once') - args = mockMetricsContext.setFlowCompleteSignal.args[0] - assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments') - assert.equal(args[0], 'account.signed', 'argument was event name') - assert.equal(args[1], 'login', 'second argument was flow type') + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once'); + args = mockMetricsContext.setFlowCompleteSignal.args[0]; + assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments'); + assert.equal(args[0], 'account.signed', 'argument was event name'); + assert.equal(args[1], 'login', 'second argument was flow type'); - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - args = mockMailer.sendVerifyLoginEmail.args[0] - assert.equal(args[2].location.city, 'Mountain View') - assert.equal(args[2].location.country, 'United States') - assert.equal(args[2].timeZone, 'America/Los_Angeles') - assert.equal(args[2].uaBrowser, 'Firefox') - assert.equal(args[2].uaBrowserVersion, '50') - assert.equal(args[2].uaOS, 'Android') - assert.equal(args[2].uaOSVersion, '6') - assert.equal(args[2].uaDeviceType, 'mobile') - assert.equal(args[2].deviceId, mockRequest.payload.metricsContext.deviceId) - assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId) - assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime) - assert.equal(args[2].service, 'sync') - assert.equal(args[2].uid, uid) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + args = mockMailer.sendVerifyLoginEmail.args[0]; + assert.equal(args[2].location.city, 'Mountain View'); + assert.equal(args[2].location.country, 'United States'); + assert.equal(args[2].timeZone, 'America/Los_Angeles'); + assert.equal(args[2].uaBrowser, 'Firefox'); + assert.equal(args[2].uaBrowserVersion, '50'); + assert.equal(args[2].uaOS, 'Android'); + assert.equal(args[2].uaOSVersion, '6'); + assert.equal(args[2].uaDeviceType, 'mobile'); + assert.equal(args[2].deviceId, mockRequest.payload.metricsContext.deviceId); + assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId); + assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime); + assert.equal(args[2].service, 'sync'); + assert.equal(args[2].uid, uid); - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.ok(! response.verified, 'response indicates account is not verified') - assert.equal(response.verificationMethod, 'email', 'verificationMethod is email') - assert.equal(response.verificationReason, 'login', 'verificationReason is login') - }).finally(() => Date.now.restore()) - }) + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.ok(! response.verified, 'response indicates account is not verified'); + assert.equal(response.verificationMethod, 'email', 'verificationMethod is email'); + assert.equal(response.verificationReason, 'login', 'verificationReason is login'); + }).finally(() => Date.now.restore()); + }); describe('sign-in unverified account', function () { it('sends email code', function () { - var emailCode = hexString(16) + var emailCode = hexString(16); mockDB.accountRecord = function () { return P.resolve({ authSalt: hexString(32), @@ -924,34 +924,34 @@ describe('/account/login', function () { primaryEmail: {normalizedEmail: TEST_EMAIL.toLowerCase(), email: TEST_EMAIL, isVerified: false, isPrimary: true}, kA: hexString(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: uid, wrapWrapKb: hexString(32) - }) - } + }); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called') + assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called'); // Verify that the email code was sent - var verifyCallArgs = mockMailer.sendVerifyCode.getCall(0).args - assert.notEqual(verifyCallArgs[1], emailCode, 'mailer.sendVerifyCode was called with a fresh verification code') - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login', 'first event was login') - assert.equal(mockLog.flowEvent.args[1][0].event, 'email.verification.sent', 'second event was sent') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(response.verified, false, 'response indicates account is unverified') - assert.equal(response.verificationMethod, 'email', 'verificationMethod is email') - assert.equal(response.verificationReason, 'signup', 'verificationReason is signup') - }) - }) - }) + var verifyCallArgs = mockMailer.sendVerifyCode.getCall(0).args; + assert.notEqual(verifyCallArgs[1], emailCode, 'mailer.sendVerifyCode was called with a fresh verification code'); + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login', 'first event was login'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'email.verification.sent', 'second event was sent'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(response.verified, false, 'response indicates account is unverified'); + assert.equal(response.verificationMethod, 'email', 'verificationMethod is email'); + assert.equal(response.verificationReason, 'signup', 'verificationReason is signup'); + }); + }); + }); describe('sign-in confirmation', function () { before(() => { - config.signinConfirmation.forcedEmailAddresses = /.+@mozilla\.com$/ + config.signinConfirmation.forcedEmailAddresses = /.+@mozilla\.com$/; mockDB.accountRecord = function () { return P.resolve({ @@ -962,35 +962,35 @@ describe('/account/login', function () { primaryEmail: {normalizedEmail: TEST_EMAIL.toLowerCase(), email: TEST_EMAIL, isVerified: true, isPrimary: true}, kA: hexString(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: uid, wrapWrapKb: hexString(32) - }) - } - }) + }); + }; + }); it('is enabled by default', function () { return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use') - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') - assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.ok(! response.verified, 'response indicates account is not verified') - assert.equal(response.verificationMethod, 'email', 'verificationMethod is email') - assert.equal(response.verificationReason, 'login', 'verificationReason is login') + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use'); + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); + assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.ok(! response.verified, 'response indicates account is not verified'); + assert.equal(response.verificationMethod, 'email', 'verificationMethod is email'); + assert.equal(response.verificationReason, 'login', 'verificationReason is login'); - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.city, 'Mountain View') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.country, 'United States') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].timeZone, 'America/Los_Angeles') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.city, 'Mountain View'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.country, 'United States'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].timeZone, 'America/Los_Angeles'); + }); + }); it('does not require verification when keys are not requested', function () { - const email = 'test@mozilla.com' + const email = 'test@mozilla.com'; mockDB.accountRecord = function () { return P.resolve({ authSalt: hexString(32), @@ -1000,35 +1000,35 @@ describe('/account/login', function () { primaryEmail: {normalizedEmail: email.toLowerCase(), email: email, isVerified: true, isPrimary: true}, kA: hexString(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: uid, wrapWrapKb: hexString(32) - }) - } + }); + }; return runTest(route, mockRequestNoKeys, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(! tokenData.mustVerify, 'sessionToken does not have to be verified') - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(! tokenData.mustVerify, 'sessionToken does not have to be verified'); + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); // Note that *neither* email is sent in this case, // since it can't have been a new device connection. - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once') - assert.deepEqual(mockMetricsContext.setFlowCompleteSignal.args[0][0], 'account.login', 'argument was event name') + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once'); + assert.deepEqual(mockMetricsContext.setFlowCompleteSignal.args[0][0], 'account.login', 'argument was event name'); - assert.ok(response.verified, 'response indicates account is verified') - assert.ok(! response.verificationMethod, 'verificationMethod doesn\'t exist') - assert.ok(! response.verificationReason, 'verificationReason doesn\'t exist') - }) - }) + assert.ok(response.verified, 'response indicates account is verified'); + assert.ok(! response.verificationMethod, 'verificationMethod doesn\'t exist'); + assert.ok(! response.verificationReason, 'verificationReason doesn\'t exist'); + }); + }); it('unverified account gets account confirmation email', function () { - const email = 'test@mozilla.com' - mockRequest.payload.email = email + const email = 'test@mozilla.com'; + mockRequest.payload.email = email; mockDB.accountRecord = function () { return P.resolve({ authSalt: hexString(32), @@ -1038,46 +1038,46 @@ describe('/account/login', function () { primaryEmail: {normalizedEmail: email.toLowerCase(), email: email, isVerified: false, isPrimary: true}, kA: hexString(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: uid, wrapWrapKb: hexString(32) - }) - } + }); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use') - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') - assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.ok(! response.verified, 'response indicates account is not verified') - assert.equal(response.verificationMethod, 'email', 'verificationMethod is email') - assert.equal(response.verificationReason, 'signup', 'verificationReason is signup') - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use'); + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); + assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.ok(! response.verified, 'response indicates account is not verified'); + assert.equal(response.verificationMethod, 'email', 'verificationMethod is email'); + assert.equal(response.verificationReason, 'signup', 'verificationReason is signup'); + }); + }); it('should return an error if email fails to send', () => { - mockMailer.sendVerifyLoginEmail = sinon.spy(() => P.reject()) + mockMailer.sendVerifyLoginEmail = sinon.spy(() => P.reject()); return runTest(route, mockRequest).then(assert.fail, (err) => { - assert.equal(err.message, 'Failed to send email') - assert.equal(err.output.payload.code, 500) - assert.equal(err.output.payload.errno, 151) - assert.equal(err.output.payload.error, 'Internal Server Error') - }) - }) + assert.equal(err.message, 'Failed to send email'); + assert.equal(err.output.payload.code, 500); + assert.equal(err.output.payload.errno, 151); + assert.equal(err.output.payload.error, 'Internal Server Error'); + }); + }); describe('skip for new accounts', function () { function setup(enabled, accountCreatedSince) { config.signinConfirmation.skipForNewAccounts = { enabled: enabled, maxAge: 5 - } + }; - const email = mockRequest.payload.email + const email = mockRequest.payload.email; mockDB.accountRecord = function () { return P.resolve({ @@ -1089,16 +1089,16 @@ describe('/account/login', function () { primaryEmail: {normalizedEmail: email.toLowerCase(), email: email, isVerified: true, isPrimary: true}, kA: hexString(32), lastAuthAt: function () { - return Date.now() + return Date.now(); }, uid: uid, wrapWrapKb: hexString(32) - }) - } + }); + }; var accountRoutes = makeRoutes({ checkPassword: function () { - return P.resolve(true) + return P.resolve(true); }, config: config, customs: mockCustoms, @@ -1106,88 +1106,88 @@ describe('/account/login', function () { log: mockLog, mailer: mockMailer, push: mockPush - }) + }); - route = getRoute(accountRoutes, '/account/login') + route = getRoute(accountRoutes, '/account/login'); } it('is disabled', function () { - setup(false) + setup(false); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use') - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') - assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.ok(! response.verified, 'response indicates account is not verified') - assert.equal(response.verificationMethod, 'email', 'verificationMethod is email') - assert.equal(response.verificationReason, 'login', 'verificationReason is login') + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(tokenData.mustVerify, 'sessionToken must be verified before use'); + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); + assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.ok(! response.verified, 'response indicates account is not verified'); + assert.equal(response.verificationMethod, 'email', 'verificationMethod is email'); + assert.equal(response.verificationReason, 'login', 'verificationReason is login'); - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.city, 'Mountain View') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.country, 'United States') - assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].timeZone, 'America/Los_Angeles') - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.city, 'Mountain View'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.country, 'United States'); + assert.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].timeZone, 'America/Los_Angeles'); + }); + }); it('skip sign-in confirmation on recently created account', function () { - setup(true, 0) + setup(true, 0); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.equal(tokenData.tokenVerificationId, null, 'sessionToken was created verified') - assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called') - assert.ok(response.verified, 'response indicates account is verified') - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.equal(tokenData.tokenVerificationId, null, 'sessionToken was created verified'); + assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called'); + assert.ok(response.verified, 'response indicates account is verified'); + }); + }); it('do not error if new device login notification is blocked', function () { - setup(true, 0) + setup(true, 0); - mockMailer.sendNewDeviceLoginNotification = sinon.spy(() => P.reject(error.emailBouncedHard())) + mockMailer.sendNewDeviceLoginNotification = sinon.spy(() => P.reject(error.emailBouncedHard())); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.equal(tokenData.tokenVerificationId, null, 'sessionToken was created verified') - assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].deviceId, mockRequest.payload.metricsContext.deviceId) - assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowId, mockRequest.payload.metricsContext.flowId) - assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime) - assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].service, 'sync') - assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].uid, uid) - assert.ok(response.verified, 'response indicates account is verified') - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.equal(tokenData.tokenVerificationId, null, 'sessionToken was created verified'); + assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].deviceId, mockRequest.payload.metricsContext.deviceId); + assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowId, mockRequest.payload.metricsContext.flowId); + assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime); + assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].service, 'sync'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].uid, uid); + assert.ok(response.verified, 'response indicates account is verified'); + }); + }); it('don\'t skip sign-in confirmation on older account', function () { - setup(true, 10) + setup(true, 10); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - var tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.ok(! response.verified, 'response indicates account is unverified') - }) - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + var tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.ok(! response.verified, 'response indicates account is unverified'); + }); + }); + }); describe('skip for emails', function () { function setup(email) { - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts = {enabled: false} - config.signinConfirmation.skipForEmailAddresses = ['skip@confirmation.com', 'other@email.com'] + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts = {enabled: false}; + config.signinConfirmation.skipForEmailAddresses = ['skip@confirmation.com', 'other@email.com']; - mockRequest.payload.email = email + mockRequest.payload.email = email; mockDB.accountRecord = () => { return P.resolve({ @@ -1200,8 +1200,8 @@ describe('/account/login', function () { lastAuthAt: () => Date.now(), uid, wrapWrapKb: hexString(32) - }) - } + }); + }; const accountRoutes = makeRoutes({ checkPassword: () => P.resolve(true), @@ -1211,306 +1211,306 @@ describe('/account/login', function () { log: mockLog, mailer: mockMailer, push: mockPush - }) + }); - route = getRoute(accountRoutes, '/account/login') + route = getRoute(accountRoutes, '/account/login'); } it('should not skip sign-in confirmation for specified email', () => { - setup('not@skip.com') + setup('not@skip.com'); return runTest(route, mockRequest, (response) => { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - const tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called') - assert.ok(! response.verified, 'response indicates account is unverified') - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + const tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called'); + assert.ok(! response.verified, 'response indicates account is unverified'); + }); + }); it('should skip sign-in confirmation for specified email', () => { - setup('skip@confirmation.com') + setup('skip@confirmation.com'); return runTest(route, mockRequest, (response) => { - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called') - const tokenData = mockDB.createSessionToken.getCall(0).args[0] - assert.ok(! tokenData.tokenVerificationId, 'sessionToken was created verified') - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called') - assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called') - assert.ok(response.verified, 'response indicates account is verified') - }) - }) - }) - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called'); + const tokenData = mockDB.createSessionToken.getCall(0).args[0]; + assert.ok(! tokenData.tokenVerificationId, 'sessionToken was created verified'); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called'); + assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called'); + assert.ok(response.verified, 'response indicates account is verified'); + }); + }); + }); + }); it('creating too many sessions causes an error to be logged', function () { - const oldSessions = mockDB.sessions + const oldSessions = mockDB.sessions; mockDB.sessions = sinon.spy(function () { - return P.resolve(new Array(200)) - }) - mockLog.error = sinon.spy() - mockRequest.app.clientAddress = '63.245.221.32' + return P.resolve(new Array(200)); + }); + mockLog.error = sinon.spy(); + mockRequest.app.clientAddress = '63.245.221.32'; return runTest(route, mockRequest, function () { - assert.equal(mockLog.error.callCount, 0, 'log.error was not called') + assert.equal(mockLog.error.callCount, 0, 'log.error was not called'); }).then(function () { mockDB.sessions = sinon.spy(function () { - return P.resolve(new Array(201)) - }) - mockLog.error.resetHistory() + return P.resolve(new Array(201)); + }); + mockLog.error.resetHistory(); return runTest(route, mockRequest, function () { - assert.equal(mockLog.error.callCount, 1, 'log.error was called') - assert.equal(mockLog.error.firstCall.args[0], 'Account.login') - assert.equal(mockLog.error.firstCall.args[1].numSessions, 201) - mockDB.sessions = oldSessions - }) - }) - }) + assert.equal(mockLog.error.callCount, 1, 'log.error was called'); + assert.equal(mockLog.error.firstCall.args[0], 'Account.login'); + assert.equal(mockLog.error.firstCall.args[1].numSessions, 201); + mockDB.sessions = oldSessions; + }); + }); + }); describe('checks security history', function () { - var record - var clientAddress = mockRequest.app.clientAddress + var record; + var clientAddress = mockRequest.app.clientAddress; beforeEach(() => { mockLog.info = sinon.spy(function (op, arg) { if (op.indexOf('Account.history') === 0) { - record = arg + record = arg; } - }) - }) + }); + }); it('with a seen ip address', function () { - record = undefined - var securityQuery + record = undefined; + var securityQuery; mockDB.securityEvents = sinon.spy(function (arg) { - securityQuery = arg + securityQuery = arg; return P.resolve([ { name: 'account.login', createdAt: Date.now(), verified: true } - ]) - }) + ]); + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called') - assert.equal(securityQuery.uid, uid) - assert.equal(securityQuery.ipAddr, clientAddress) + assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called'); + assert.equal(securityQuery.uid, uid); + assert.equal(securityQuery.ipAddr, clientAddress); - assert.equal(!! record, true, 'log.info was called for Account.history') - assert.equal(mockLog.info.args[0][0], 'Account.history.verified') - assert.equal(record.uid, uid) - assert.equal(record.events, 1) - assert.equal(record.recency, 'day') - }) - }) + assert.equal(!! record, true, 'log.info was called for Account.history'); + assert.equal(mockLog.info.args[0][0], 'Account.history.verified'); + assert.equal(record.uid, uid); + assert.equal(record.events, 1); + assert.equal(record.recency, 'day'); + }); + }); it('with a seen, unverified ip address', function () { - record = undefined - var securityQuery + record = undefined; + var securityQuery; mockDB.securityEvents = sinon.spy(function (arg) { - securityQuery = arg + securityQuery = arg; return P.resolve([ { name: 'account.login', createdAt: Date.now(), verified: false } - ]) - }) + ]); + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called') - assert.equal(securityQuery.uid, uid) - assert.equal(securityQuery.ipAddr, clientAddress) + assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called'); + assert.equal(securityQuery.uid, uid); + assert.equal(securityQuery.ipAddr, clientAddress); - assert.equal(!! record, true, 'log.info was called for Account.history') - assert.equal(mockLog.info.args[0][0], 'Account.history.unverified') - assert.equal(record.uid, uid) - assert.equal(record.events, 1) - }) - }) + assert.equal(!! record, true, 'log.info was called for Account.history'); + assert.equal(mockLog.info.args[0][0], 'Account.history.unverified'); + assert.equal(record.uid, uid); + assert.equal(record.events, 1); + }); + }); it('with a new ip address', function () { - record = undefined + record = undefined; - var securityQuery + var securityQuery; mockDB.securityEvents = sinon.spy(function (arg) { - securityQuery = arg - return P.resolve([]) - }) + securityQuery = arg; + return P.resolve([]); + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called') - assert.equal(securityQuery.uid, uid) - assert.equal(securityQuery.ipAddr, clientAddress) + assert.equal(mockDB.securityEvents.callCount, 1, 'db.securityEvents was called'); + assert.equal(securityQuery.uid, uid); + assert.equal(securityQuery.ipAddr, clientAddress); - assert.equal(record, undefined, 'log.info was not called for Account.history.verified') - }) - }) - }) + assert.equal(record, undefined, 'log.info was not called for Account.history.verified'); + }); + }); + }); it('records security event', function () { - var clientAddress = mockRequest.app.clientAddress - var securityQuery + var clientAddress = mockRequest.app.clientAddress; + var securityQuery; mockDB.securityEvent = sinon.spy(function (arg) { - securityQuery = arg - return P.resolve() - }) + securityQuery = arg; + return P.resolve(); + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called') - assert.equal(securityQuery.uid, uid) - assert.equal(securityQuery.ipAddr, clientAddress) - assert.equal(securityQuery.name, 'account.login') - }) - }) + assert.equal(mockDB.securityEvent.callCount, 1, 'db.securityEvent was called'); + assert.equal(securityQuery.uid, uid); + assert.equal(securityQuery.ipAddr, clientAddress); + assert.equal(securityQuery.name, 'account.login'); + }); + }); describe('blocked by customs', () => { describe('can unblock', () => { - const oldCheck = mockCustoms.check + const oldCheck = mockCustoms.check; before(() => { - mockCustoms.check = () => P.reject(error.requestBlocked(true)) - }) + mockCustoms.check = () => P.reject(error.requestBlocked(true)); + }); beforeEach(() => { - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - }) + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + }); after(() => { - mockCustoms.check = oldCheck - }) + mockCustoms.check = oldCheck; + }); describe('signin unblock enabled', () => { before(() => { - mockLog.flowEvent.resetHistory() - }) + mockLog.flowEvent.resetHistory(); + }); it('without unblock code', () => { return runTest(route, mockRequest).then(() => assert.ok(false), err => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned') - assert.equal(err.output.statusCode, 400, 'correct status code is returned') - assert.equal(err.output.payload.verificationMethod, 'email-captcha') - assert.equal(err.output.payload.verificationReason, 'login') - assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once') - assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.blocked', 'first event is blocked') - mockLog.flowEvent.resetHistory() - }) - }) + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned'); + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); + assert.equal(err.output.payload.verificationMethod, 'email-captcha'); + assert.equal(err.output.payload.verificationReason, 'login'); + assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.blocked', 'first event is blocked'); + mockLog.flowEvent.resetHistory(); + }); + }); describe('with unblock code', () => { it('invalid code', () => { - mockDB.consumeUnblockCode = () => P.reject(error.invalidUnblockCode()) + mockDB.consumeUnblockCode = () => P.reject(error.invalidUnblockCode()); return runTest(route, mockRequestWithUnblockCode).then(() => assert.ok(false), err => { - assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'correct errno is returned') - assert.equal(err.output.statusCode, 400, 'correct status code is returned') - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent called twice') - assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.invalidUnblockCode', 'second event is invalid') + assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'correct errno is returned'); + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent called twice'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.invalidUnblockCode', 'second event is invalid'); - mockLog.flowEvent.resetHistory() - }) - }) + mockLog.flowEvent.resetHistory(); + }); + }); it('expired code', () => { // test 5 seconds old, to reduce flakiness of test - mockDB.consumeUnblockCode = () => P.resolve({ createdAt: Date.now() - (config.signinUnblock.codeLifetime + 5000) }) + mockDB.consumeUnblockCode = () => P.resolve({ createdAt: Date.now() - (config.signinUnblock.codeLifetime + 5000) }); return runTest(route, mockRequestWithUnblockCode).then(() => assert.ok(false), err => { - assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'correct errno is returned') - assert.equal(err.output.statusCode, 400, 'correct status code is returned') + assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'correct errno is returned'); + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent called twice') - assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.invalidUnblockCode', 'second event is invalid') + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent called twice'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.invalidUnblockCode', 'second event is invalid'); - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - }) - }) + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + }); + }); it('unknown account', () => { - mockDB.accountRecord = () => P.reject(new error.unknownAccount()) - mockDB.emailRecord = () => P.reject(new error.unknownAccount()) + mockDB.accountRecord = () => P.reject(new error.unknownAccount()); + mockDB.emailRecord = () => P.reject(new error.unknownAccount()); return runTest(route, mockRequestWithUnblockCode).then(() => assert(false), err => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED) - assert.equal(err.output.statusCode, 400) - }) - }) + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED); + assert.equal(err.output.statusCode, 400); + }); + }); it('valid code', () => { - mockDB.consumeUnblockCode = () => P.resolve({ createdAt: Date.now() }) + mockDB.consumeUnblockCode = () => P.resolve({ createdAt: Date.now() }); return runTest(route, mockRequestWithUnblockCode, (res) => { - assert.equal(mockLog.flowEvent.callCount, 4) - assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.blocked', 'first event was account.login.blocked') - assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.confirmedUnblockCode', 'second event was account.login.confirmedUnblockCode') - assert.equal(mockLog.flowEvent.args[2][0].event, 'account.login', 'third event was account.login') - assert.equal(mockLog.flowEvent.args[3][0].event, 'flow.complete', 'fourth event was flow.complete') - }) - }) - }) - }) - }) + assert.equal(mockLog.flowEvent.callCount, 4); + assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.blocked', 'first event was account.login.blocked'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'account.login.confirmedUnblockCode', 'second event was account.login.confirmedUnblockCode'); + assert.equal(mockLog.flowEvent.args[2][0].event, 'account.login', 'third event was account.login'); + assert.equal(mockLog.flowEvent.args[3][0].event, 'flow.complete', 'fourth event was flow.complete'); + }); + }); + }); + }); + }); describe('cannot unblock', () => { - const oldCheck = mockCustoms.check + const oldCheck = mockCustoms.check; before(() => { - mockCustoms.check = () => P.reject(error.requestBlocked(false)) - }) + mockCustoms.check = () => P.reject(error.requestBlocked(false)); + }); after(() => { - mockCustoms.check = oldCheck - }) + mockCustoms.check = oldCheck; + }); it('without an unblock code', () => { return runTest(route, mockRequest).then(() => assert.ok(false), err => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned') - assert.equal(err.output.statusCode, 400, 'correct status code is returned') - assert.equal(err.output.payload.verificationMethod, undefined, 'no verificationMethod') - assert.equal(err.output.payload.verificationReason, undefined, 'no verificationReason') - }) - }) + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned'); + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); + assert.equal(err.output.payload.verificationMethod, undefined, 'no verificationMethod'); + assert.equal(err.output.payload.verificationReason, undefined, 'no verificationReason'); + }); + }); it('with unblock code', () => { return runTest(route, mockRequestWithUnblockCode).then(() => assert.ok(false), err => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned') - assert.equal(err.output.statusCode, 400, 'correct status code is returned') - assert.equal(err.output.payload.verificationMethod, undefined, 'no verificationMethod') - assert.equal(err.output.payload.verificationReason, undefined, 'no verificationReason') - }) - }) - }) - }) + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'correct errno is returned'); + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); + assert.equal(err.output.payload.verificationMethod, undefined, 'no verificationMethod'); + assert.equal(err.output.payload.verificationReason, undefined, 'no verificationReason'); + }); + }); + }); + }); it('fails login with non primary email', function () { - const email = 'foo@mail.com' + const email = 'foo@mail.com'; mockDB.accountRecord = sinon.spy(function () { return P.resolve({ primaryEmail: {normalizedEmail: email.toLowerCase(), email: email, isVerified: true, isPrimary: false}, - }) - }) + }); + }); return runTest(route, mockRequest).then(() => assert.ok(false), (err) => { - assert.equal(mockDB.accountRecord.callCount, 1, 'db.accountRecord was called') - assert.equal(err.errno, 142, 'correct errno called') - }) - }) + assert.equal(mockDB.accountRecord.callCount, 1, 'db.accountRecord was called'); + assert.equal(err.errno, 142, 'correct errno called'); + }); + }); it('fails login when requesting TOTP verificationMethod and TOTP not setup', function () { mockDB.totpToken = sinon.spy(function () { return P.resolve({ verified: true, enabled: false - }) - }) - mockRequest.payload.verificationMethod = 'totp-2fa' + }); + }); + mockRequest.payload.verificationMethod = 'totp-2fa'; return runTest(route, mockRequest).then(() => assert.ok(false), (err) => { - assert.equal(mockDB.totpToken.callCount, 1, 'db.totpToken was called') - assert.equal(err.errno, 160, 'correct errno called') - }) - }) -}) + assert.equal(mockDB.totpToken.callCount, 1, 'db.totpToken was called'); + assert.equal(err.errno, 160, 'correct errno called'); + }); + }); +}); describe('/account/keys', function () { - var keyFetchTokenId = hexString(16) - var uid = uuid.v4('binary').toString('hex') - const mockLog = mocks.mockLog() + var keyFetchTokenId = hexString(16); + var uid = uuid.v4('binary').toString('hex'); + const mockLog = mocks.mockLog(); const mockRequest = mocks.mockRequest({ credentials: { emailVerified: true, @@ -1521,26 +1521,26 @@ describe('/account/keys', function () { uid: uid }, log: mockLog - }) - var mockDB = mocks.mockDB() + }); + var mockDB = mocks.mockDB(); var accountRoutes = makeRoutes({ db: mockDB, log: mockLog - }) - var route = getRoute(accountRoutes, '/account/keys') + }); + var route = getRoute(accountRoutes, '/account/keys'); it('verified token', function () { return runTest(route, mockRequest, function (response) { - assert.deepEqual(response, {bundle: mockRequest.auth.credentials.keyBundle}, 'response was correct') + assert.deepEqual(response, {bundle: mockRequest.auth.credentials.keyBundle}, 'response was correct'); - assert.equal(mockDB.deleteKeyFetchToken.callCount, 1, 'db.deleteKeyFetchToken was called once') - var args = mockDB.deleteKeyFetchToken.args[0] - assert.equal(args.length, 1, 'db.deleteKeyFetchToken was passed one argument') - assert.equal(args[0], mockRequest.auth.credentials, 'db.deleteKeyFetchToken was passed key fetch token') + assert.equal(mockDB.deleteKeyFetchToken.callCount, 1, 'db.deleteKeyFetchToken was called once'); + var args = mockDB.deleteKeyFetchToken.args[0]; + assert.equal(args.length, 1, 'db.deleteKeyFetchToken was passed one argument'); + assert.equal(args[0], mockRequest.auth.credentials, 'db.deleteKeyFetchToken was passed key fetch token'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.keyfetch', @@ -1548,47 +1548,47 @@ describe('/account/keys', function () { service: undefined, userAgent: 'test user-agent', uid: uid - }, 'event data was correct') + }, 'event data was correct'); }) .then(function () { - mockLog.activityEvent.resetHistory() - mockDB.deleteKeyFetchToken.resetHistory() - }) - }) + mockLog.activityEvent.resetHistory(); + mockDB.deleteKeyFetchToken.resetHistory(); + }); + }); it('unverified token', function () { - mockRequest.auth.credentials.tokenVerificationId = hexString(16) - mockRequest.auth.credentials.tokenVerified = false + mockRequest.auth.credentials.tokenVerificationId = hexString(16); + mockRequest.auth.credentials.tokenVerified = false; return runTest(route, mockRequest).then(() => assert.ok(false), response => { - assert.equal(response.errno, 104, 'correct errno for unverified account') - assert.equal(response.message, 'Unverified account', 'correct error message') + assert.equal(response.errno, 104, 'correct errno for unverified account'); + assert.equal(response.message, 'Unverified account', 'correct error message'); }) .then(function () { - mockLog.activityEvent.resetHistory() - }) - }) -}) + mockLog.activityEvent.resetHistory(); + }); + }); +}); describe('/account/destroy', function () { it('should delete the account', () => { - var email = 'foo@example.com' - var uid = uuid.v4('binary').toString('hex') + var email = 'foo@example.com'; + var uid = uuid.v4('binary').toString('hex'); var mockDB = mocks.mockDB({ email: email, uid: uid - }) - const mockLog = mocks.mockLog() + }); + const mockLog = mocks.mockLog(); const mockRequest = mocks.mockRequest({ log: mockLog, payload: { email: email, authPW: new Array(65).join('f') } - }) - const mockPush = mocks.mockPush() + }); + const mockPush = mocks.mockPush(); var accountRoutes = makeRoutes({ checkPassword: function () { - return P.resolve(true) + return P.resolve(true); }, config: { domain: 'wibble' @@ -1596,43 +1596,43 @@ describe('/account/destroy', function () { db: mockDB, log: mockLog, push: mockPush - }) - var route = getRoute(accountRoutes, '/account/destroy') + }); + var route = getRoute(accountRoutes, '/account/destroy'); return runTest(route, mockRequest, function () { - assert.equal(mockDB.accountRecord.callCount, 1, 'db.emailRecord was called once') - var args = mockDB.accountRecord.args[0] - assert.equal(args.length, 2, 'db.emailRecord was passed two arguments') - assert.equal(args[0], email, 'first argument was email address') - assert.equal(args[1], true, 'second argument was customs.check result') + assert.equal(mockDB.accountRecord.callCount, 1, 'db.emailRecord was called once'); + var args = mockDB.accountRecord.args[0]; + assert.equal(args.length, 2, 'db.emailRecord was passed two arguments'); + assert.equal(args[0], email, 'first argument was email address'); + assert.equal(args[1], true, 'second argument was customs.check result'); - assert.equal(mockDB.deleteAccount.callCount, 1, 'db.deleteAccount was called once') - args = mockDB.deleteAccount.args[0] - assert.equal(args.length, 1, 'db.deleteAccount was passed one argument') - assert.equal(args[0].email, email, 'db.deleteAccount was passed email record') - assert.deepEqual(args[0].uid, uid, 'email record had correct uid') + assert.equal(mockDB.deleteAccount.callCount, 1, 'db.deleteAccount was called once'); + args = mockDB.deleteAccount.args[0]; + assert.equal(args.length, 1, 'db.deleteAccount was passed one argument'); + assert.equal(args[0].email, email, 'db.deleteAccount was passed email record'); + assert.deepEqual(args[0].uid, uid, 'email record had correct uid'); - assert.equal(mockLog.info.callCount, 1) - args = mockLog.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'accountDeleted.byRequest') - assert.equal(args[1].email, email) - assert.equal(args[1].uid, uid) + assert.equal(mockLog.info.callCount, 1); + args = mockLog.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'accountDeleted.byRequest'); + assert.equal(args[1].email, email); + assert.equal(args[1].uid, uid); - assert.equal(mockPush.notifyAccountDestroyed.callCount, 1) - assert.equal(mockPush.notifyAccountDestroyed.firstCall.args[0], uid) + assert.equal(mockPush.notifyAccountDestroyed.callCount, 1); + assert.equal(mockPush.notifyAccountDestroyed.firstCall.args[0], uid); - assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once') - args = mockLog.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'delete', 'first argument was event name') - assert.equal(args[1], mockRequest, 'second argument was request object') - assert.equal(args[2].uid, uid, 'third argument was event data with a uid') - assert.equal(args[2].iss, 'wibble', 'third argument was event data with an issuer field') + assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'log.notifyAttachedServices was called once'); + args = mockLog.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'delete', 'first argument was event name'); + assert.equal(args[1], mockRequest, 'second argument was request object'); + assert.equal(args[2].uid, uid, 'third argument was event data with a uid'); + assert.equal(args[2].iss, 'wibble', 'third argument was event data with an issuer field'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.deleted', @@ -1640,8 +1640,8 @@ describe('/account/destroy', function () { service: undefined, userAgent: 'test user-agent', uid: uid - }, 'event data was correct') - }) - }) -}) + }, 'event data was correct'); + }); + }); +}); diff --git a/test/local/routes/devices-and-sessions.js b/test/local/routes/devices-and-sessions.js index 34903ed3..6a458f2c 100644 --- a/test/local/routes/devices-and-sessions.js +++ b/test/local/routes/devices-and-sessions.js @@ -2,74 +2,74 @@ * 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/. */ -'use strict' +'use strict'; -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } -const crypto = require('crypto') -const error = require('../../../lib/error') -const getRoute = require('../../routes_helpers').getRoute -const isA = require('joi') -const mocks = require('../../mocks') -const moment = require('fxa-shared/node_modules/moment') // Ensure consistency with production code -const P = require('../../../lib/promise') -const proxyquire = require('proxyquire') -const uuid = require('uuid') -const OAuthError = require('../../../fxa-oauth-server/lib/error') +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; +const crypto = require('crypto'); +const error = require('../../../lib/error'); +const getRoute = require('../../routes_helpers').getRoute; +const isA = require('joi'); +const mocks = require('../../mocks'); +const moment = require('fxa-shared/node_modules/moment'); // Ensure consistency with production code +const P = require('../../../lib/promise'); +const proxyquire = require('proxyquire'); +const uuid = require('uuid'); +const OAuthError = require('../../../fxa-oauth-server/lib/error'); -const EARLIEST_SANE_TIMESTAMP = 31536000000 +const EARLIEST_SANE_TIMESTAMP = 31536000000; function makeRoutes (options = {}, requireMocks) { - const config = options.config || {} - config.smtp = config.smtp || {} + const config = options.config || {}; + config.smtp = config.smtp || {}; config.memcached = config.memcached || { address: '127.0.0.1:1121', idle: 500, lifetime: 30 - } + }; config.i18n = { supportedLanguages: [ 'en', 'fr' ], defaultLanguage: 'en' - } + }; config.push = { allowedServerRegex: /^https:\/\/updates\.push\.services\.mozilla\.com(\/.*)?$/ - } + }; config.lastAccessTimeUpdates = { earliestSaneTimestamp: EARLIEST_SANE_TIMESTAMP - } - config.publicUrl = 'https://public.url' + }; + config.publicUrl = 'https://public.url'; - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() - const oauthdb = options.oauthdb || mocks.mockOAuthDB(log, config) + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); + const oauthdb = options.oauthdb || mocks.mockOAuthDB(log, config); const customs = options.customs || { - check: function () { return P.resolve(true) } - } - const push = options.push || require('../../../lib/push')(log, db, {}) - const pushbox = options.pushbox || mocks.mockPushbox() + check: function () { return P.resolve(true); } + }; + const push = options.push || require('../../../lib/push')(log, db, {}); + const pushbox = options.pushbox || mocks.mockPushbox(); return proxyquire('../../../lib/routes/devices-and-sessions', requireMocks || {})( log, db, config, customs, push, pushbox, options.devices || require('../../../lib/devices')(log, db, push), oauthdb - ) + ); } function runTest(route, request, onSuccess, onError) { return route.handler(request) .then(onSuccess, onError) - .catch(onError) + .catch(onError); } function hexString (bytes) { - return crypto.randomBytes(bytes).toString('hex') + return crypto.randomBytes(bytes).toString('hex'); } describe('/account/device', function () { - var config = {} - var uid = uuid.v4('binary').toString('hex') - var deviceId = crypto.randomBytes(16).toString('hex') - var mockDeviceName = 'my awesome device 🍓🔥' + var config = {}; + var uid = uuid.v4('binary').toString('hex'); + var deviceId = crypto.randomBytes(16).toString('hex'); + var mockDeviceName = 'my awesome device 🍓🔥'; var mockRequest = mocks.mockRequest({ credentials: { deviceCallbackPublicKey: '', @@ -85,28 +85,28 @@ describe('/account/device', function () { id: deviceId.toString('hex'), name: mockDeviceName } - }) - const devicesData = {} - var mockDevices = mocks.mockDevices(devicesData) - var mockLog = mocks.mockLog() + }); + const devicesData = {}; + var mockDevices = mocks.mockDevices(devicesData); + var mockLog = mocks.mockLog(); var accountRoutes = makeRoutes({ config: config, devices: mockDevices, log: mockLog - }) - var route = getRoute(accountRoutes, '/account/device') + }); + var route = getRoute(accountRoutes, '/account/device'); it('identical data', function () { - devicesData.spurious = true + devicesData.spurious = true; return runTest(route, mockRequest, function (response) { - assert.equal(mockDevices.isSpuriousUpdate.callCount, 1) - const args = mockDevices.isSpuriousUpdate.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], mockRequest.payload) - const creds = mockRequest.auth.credentials - assert.equal(args[1], creds) + assert.equal(mockDevices.isSpuriousUpdate.callCount, 1); + const args = mockDevices.isSpuriousUpdate.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], mockRequest.payload); + const creds = mockRequest.auth.credentials; + assert.equal(args[1], creds); - assert.equal(mockDevices.upsert.callCount, 0) + assert.equal(mockDevices.upsert.callCount, 0); // Make sure the shape of the response is the same as if // the update wasn't spurious. assert.deepEqual(response, { @@ -118,88 +118,88 @@ describe('/account/device', function () { pushEndpointExpired: creds.deviceCallbackIsExpired, pushPublicKey: creds.deviceCallbackPublicKey, type: creds.deviceType, - }) + }); }) .then(function () { - mockDevices.isSpuriousUpdate.resetHistory() - mockDevices.upsert.resetHistory() - }) - }) + mockDevices.isSpuriousUpdate.resetHistory(); + mockDevices.upsert.resetHistory(); + }); + }); it('different data', function () { - devicesData.spurious = false - mockRequest.auth.credentials.deviceId = crypto.randomBytes(16).toString('hex') - var payload = mockRequest.payload - payload.name = 'my even awesomer device' - payload.type = 'phone' - payload.pushCallback = 'https://push.services.mozilla.com/123456' - payload.pushPublicKey = mocks.MOCK_PUSH_KEY + devicesData.spurious = false; + mockRequest.auth.credentials.deviceId = crypto.randomBytes(16).toString('hex'); + var payload = mockRequest.payload; + payload.name = 'my even awesomer device'; + payload.type = 'phone'; + payload.pushCallback = 'https://push.services.mozilla.com/123456'; + payload.pushPublicKey = mocks.MOCK_PUSH_KEY; return runTest(route, mockRequest, function (response) { - assert.equal(mockDevices.isSpuriousUpdate.callCount, 1) - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - var args = mockDevices.upsert.args[0] - assert.equal(args.length, 3, 'devices.upsert was passed three arguments') - assert.equal(args[0], mockRequest, 'first argument was request object') - assert.deepEqual(args[1].id, mockRequest.auth.credentials.id, 'second argument was session token') - assert.deepEqual(args[1].uid, uid, 'sessionToken.uid was correct') - assert.deepEqual(args[2], mockRequest.payload, 'third argument was payload') + assert.equal(mockDevices.isSpuriousUpdate.callCount, 1); + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + var args = mockDevices.upsert.args[0]; + assert.equal(args.length, 3, 'devices.upsert was passed three arguments'); + assert.equal(args[0], mockRequest, 'first argument was request object'); + assert.deepEqual(args[1].id, mockRequest.auth.credentials.id, 'second argument was session token'); + assert.deepEqual(args[1].uid, uid, 'sessionToken.uid was correct'); + assert.deepEqual(args[2], mockRequest.payload, 'third argument was payload'); }) .then(function () { - mockDevices.isSpuriousUpdate.resetHistory() - mockDevices.upsert.resetHistory() - }) - }) + mockDevices.isSpuriousUpdate.resetHistory(); + mockDevices.upsert.resetHistory(); + }); + }); it('with no id in payload', function () { - devicesData.spurious = false - mockRequest.payload.id = undefined + devicesData.spurious = false; + mockRequest.payload.id = undefined; return runTest(route, mockRequest, function (response) { - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - var args = mockDevices.upsert.args[0] - assert.equal(args[2].id, mockRequest.auth.credentials.deviceId.toString('hex'), 'payload.id defaulted to credentials.deviceId') + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + var args = mockDevices.upsert.args[0]; + assert.equal(args[2].id, mockRequest.auth.credentials.deviceId.toString('hex'), 'payload.id defaulted to credentials.deviceId'); }) .then(function () { - mockDevices.isSpuriousUpdate.resetHistory() - mockDevices.upsert.resetHistory() - }) - }) + mockDevices.isSpuriousUpdate.resetHistory(); + mockDevices.upsert.resetHistory(); + }); + }); it('device updates disabled', function () { - config.deviceUpdatesEnabled = false + config.deviceUpdatesEnabled = false; return runTest(route, mockRequest, function () { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }) .then(() => assert.ok(false), function (err) { - assert.equal(err.output.statusCode, 503, 'correct status code is returned') - assert.equal(err.errno, error.ERRNO.FEATURE_NOT_ENABLED, 'correct errno is returned') - delete config.deviceUpdatesEnabled - }) - }) + assert.equal(err.output.statusCode, 503, 'correct status code is returned'); + assert.equal(err.errno, error.ERRNO.FEATURE_NOT_ENABLED, 'correct errno is returned'); + delete config.deviceUpdatesEnabled; + }); + }); it('pushbox feature disabled', function () { - config.pushbox = { enabled: false } + config.pushbox = { enabled: false }; mockRequest.payload.availableCommands = { 'test': 'command' - } + }; return runTest(route, mockRequest, function () { - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - var args = mockDevices.upsert.args[0] - assert.deepEqual(args[2].availableCommands, {}, 'availableCommands are ignored when pushbox is disabled') + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + var args = mockDevices.upsert.args[0]; + assert.deepEqual(args[2].availableCommands, {}, 'availableCommands are ignored when pushbox is disabled'); }) .then(function () { - mockDevices.isSpuriousUpdate.resetHistory() - mockDevices.upsert.resetHistory() - delete config.pushbox - }) - }) + mockDevices.isSpuriousUpdate.resetHistory(); + mockDevices.upsert.resetHistory(); + delete config.pushbox; + }); + }); it('removes the push endpoint expired flag on callback URL update', function () { - var mockDevices = mocks.mockDevices() - var route = getRoute(makeRoutes({devices: mockDevices}), '/account/device') + var mockDevices = mocks.mockDevices(); + var route = getRoute(makeRoutes({devices: mockDevices}), '/account/device'); var mockRequest = mocks.mockRequest({ credentials: { @@ -216,17 +216,17 @@ describe('/account/device', function () { id: deviceId.toString('hex'), pushCallback: 'https://updates.push.services.mozilla.com/update/d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c75' } - }) + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDevices.upsert.callCount, 1, 'mockDevices.upsert was called') - assert.equal(mockDevices.upsert.args[0][2].pushEndpointExpired, false, 'pushEndpointExpired is updated to false') - }) - }) + assert.equal(mockDevices.upsert.callCount, 1, 'mockDevices.upsert was called'); + assert.equal(mockDevices.upsert.args[0][2].pushEndpointExpired, false, 'pushEndpointExpired is updated to false'); + }); + }); it('should not remove the push endpoint expired flag on any other property update', function () { - var mockDevices = mocks.mockDevices() - var route = getRoute(makeRoutes({devices: mockDevices}), '/account/device') + var mockDevices = mocks.mockDevices(); + var route = getRoute(makeRoutes({devices: mockDevices}), '/account/device'); var mockRequest = mocks.mockRequest({ credentials: { @@ -243,20 +243,20 @@ describe('/account/device', function () { id: deviceId.toString('hex'), name: 'beep beep' } - }) + }); return runTest(route, mockRequest, function (response) { - assert.equal(mockDevices.upsert.callCount, 1, 'mockDevices.upsert was called') - assert.equal(mockDevices.upsert.args[0][2].pushEndpointExpired, undefined, 'pushEndpointExpired is not updated') - }) - }) -}) + assert.equal(mockDevices.upsert.callCount, 1, 'mockDevices.upsert was called'); + assert.equal(mockDevices.upsert.args[0][2].pushEndpointExpired, undefined, 'pushEndpointExpired is not updated'); + }); + }); +}); describe('/account/devices/notify', function () { - var config = {} - var uid = uuid.v4('binary').toString('hex') - var deviceId = crypto.randomBytes(16).toString('hex') - var mockLog = mocks.mockLog() + var config = {}; + var uid = uuid.v4('binary').toString('hex'); + var deviceId = crypto.randomBytes(16).toString('hex'); + var mockLog = mocks.mockLog(); var mockRequest = mocks.mockRequest({ log: mockLog, devices: [ @@ -273,22 +273,22 @@ describe('/account/devices/notify', function () { uid: uid, deviceId: deviceId } - }) + }); var pushPayload = { version: 1, command: 'sync:collection_changed', data: { collections: ['clients'] } - } - var mockPush = mocks.mockPush() - var mockCustoms = mocks.mockCustoms() + }; + var mockPush = mocks.mockPush(); + var mockCustoms = mocks.mockCustoms(); var accountRoutes = makeRoutes({ config: config, customs: mockCustoms, push: mockPush - }) - var route = getRoute(accountRoutes, '/account/devices/notify') + }); + var route = getRoute(accountRoutes, '/account/devices/notify'); it('bad payload', function () { mockRequest.payload = { @@ -296,15 +296,15 @@ describe('/account/devices/notify', function () { payload: { bogus: 'payload' } - } + }; return runTest(route, mockRequest, function () { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }) .then(() => assert(false), function (err) { - assert.equal(mockPush.sendPush.callCount, 0, 'mockPush.sendPush was not called') - assert.equal(err.errno, 107, 'Correct errno for invalid push payload') - }) - }) + assert.equal(mockPush.sendPush.callCount, 0, 'mockPush.sendPush was not called'); + assert.equal(err.errno, 107, 'Correct errno for invalid push payload'); + }); + }); it('all devices', function () { mockRequest.payload = { @@ -312,89 +312,89 @@ describe('/account/devices/notify', function () { excluded: ['bogusid'], TTL: 60, payload: pushPayload - } + }; // We don't wait on sendPush in the request handler, that's why // we have to wait on it manually by spying. - var sendPushPromise = P.defer() + var sendPushPromise = P.defer(); mockPush.sendPush = sinon.spy(function () { - sendPushPromise.resolve() - return P.resolve() - }) + sendPushPromise.resolve(); + return P.resolve(); + }); return runTest(route, mockRequest, function (response) { return sendPushPromise.promise.then(function () { - assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once') - assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once') - var args = mockPush.sendPush.args[0] - assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments') - assert.equal(args[0], uid, 'first argument was the device uid') - assert.ok(Array.isArray(args[1]), 'second argument was devices array') - assert.equal(args[2], 'devicesNotify', 'second argument was the devicesNotify reason') + assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once'); + assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once'); + var args = mockPush.sendPush.args[0]; + assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments'); + assert.equal(args[0], uid, 'first argument was the device uid'); + assert.ok(Array.isArray(args[1]), 'second argument was devices array'); + assert.equal(args[2], 'devicesNotify', 'second argument was the devicesNotify reason'); assert.deepEqual(args[3], { data: pushPayload, TTL: 60 - }, 'third argument was the push options') - }) - }) - }) + }, 'third argument was the push options'); + }); + }); + }); it('extra push payload properties are rejected', function () { - var extraPropsPayload = JSON.parse(JSON.stringify(pushPayload)) - extraPropsPayload.extra = true - extraPropsPayload.data.extra = true + var extraPropsPayload = JSON.parse(JSON.stringify(pushPayload)); + extraPropsPayload.extra = true; + extraPropsPayload.data.extra = true; mockRequest.payload = { to: 'all', excluded: ['bogusid'], TTL: 60, payload: extraPropsPayload - } + }; // We don't wait on sendPush in the request handler, that's why // we have to wait on it manually by spying. - var sendPushPromise = P.defer() + var sendPushPromise = P.defer(); mockPush.sendPush = sinon.spy(function () { - sendPushPromise.resolve() - return Promise.resolve() - }) + sendPushPromise.resolve(); + return Promise.resolve(); + }); return runTest(route, mockRequest, function () { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }) .then(() => assert.ok(false), function (err) { - assert.equal(err.output.statusCode, 400, 'correct status code is returned') - assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'correct errno is returned') - }) - }) + assert.equal(err.output.statusCode, 400, 'correct status code is returned'); + assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'correct errno is returned'); + }); + }); it('specific devices', function () { - mockCustoms.checkAuthenticated.resetHistory() - mockLog.activityEvent.resetHistory() - mockLog.error.resetHistory() + mockCustoms.checkAuthenticated.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockLog.error.resetHistory(); mockRequest.payload = { to: ['bogusid1', 'bogusid2'], TTL: 60, payload: pushPayload - } + }; // We don't wait on sendPush in the request handler, that's why // we have to wait on it manually by spying. - var sendPushPromise = P.defer() + var sendPushPromise = P.defer(); mockPush.sendPush = sinon.spy(function () { - sendPushPromise.resolve() - return P.resolve() - }) + sendPushPromise.resolve(); + return P.resolve(); + }); return runTest(route, mockRequest, function (response) { return sendPushPromise.promise.then(function () { - assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once') - assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once') - var args = mockPush.sendPush.args[0] - assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments') - assert.equal(args[0], uid, 'first argument was the device uid') - assert.ok(Array.isArray(args[1]), 'second argument was devices array') - assert.equal(args[2], 'devicesNotify', 'third argument was the devicesNotify reason') + assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once'); + assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once'); + var args = mockPush.sendPush.args[0]; + assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments'); + assert.equal(args[0], uid, 'first argument was the device uid'); + assert.ok(Array.isArray(args[1]), 'second argument was devices array'); + assert.equal(args[2], 'devicesNotify', 'third argument was the devicesNotify reason'); assert.deepEqual(args[3], { data: pushPayload, TTL: 60 - }, 'fourth argument was the push options') - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + }, 'fourth argument was the push options'); + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'sync.sentTabToDevice', @@ -403,16 +403,16 @@ describe('/account/devices/notify', function () { userAgent: 'test user-agent', uid: uid.toString('hex'), device_id: deviceId.toString('hex') - }, 'event data was correct') - assert.equal(mockLog.error.callCount, 0, 'log.error was not called') - }) - }) - }) + }, 'event data was correct'); + assert.equal(mockLog.error.callCount, 0, 'log.error was not called'); + }); + }); + }); it('does not log activity event for non-send-tab-related notifications', function () { - mockPush.sendPush.resetHistory() - mockLog.activityEvent.resetHistory() - mockLog.error.resetHistory() + mockPush.sendPush.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockLog.error.resetHistory(); mockRequest.payload = { to: ['bogusid1', 'bogusid2'], TTL: 60, @@ -420,31 +420,31 @@ describe('/account/devices/notify', function () { version: 1, command: 'fxaccounts:password_reset' } - } + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once') - assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(mockLog.error.callCount, 0, 'log.error was not called') - }) - }) + assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once'); + assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(mockLog.error.callCount, 0, 'log.error was not called'); + }); + }); it('device driven notifications disabled', function () { - config.deviceNotificationsEnabled = false + config.deviceNotificationsEnabled = false; mockRequest.payload = { to: 'all', excluded: ['bogusid'], TTL: 60, payload: pushPayload - } + }; return runTest(route, mockRequest, function () { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }) .then(() => assert.ok(false), function (err) { - assert.equal(err.output.statusCode, 503, 'correct status code is returned') - assert.equal(err.errno, error.ERRNO.FEATURE_NOT_ENABLED, 'correct errno is returned') - }) - }) + assert.equal(err.output.statusCode, 503, 'correct status code is returned'); + assert.equal(err.errno, error.ERRNO.FEATURE_NOT_ENABLED, 'correct errno is returned'); + }); + }); it('throws error if customs blocked the request', function () { mockRequest.payload = { @@ -452,116 +452,116 @@ describe('/account/devices/notify', function () { excluded: ['bogusid'], TTL: 60, payload: pushPayload - } - config.deviceNotificationsEnabled = true + }; + config.deviceNotificationsEnabled = true; const mockCustoms = mocks.mockCustoms({ checkAuthenticated: error.tooManyRequests(1) - }) - route = getRoute(makeRoutes({customs: mockCustoms}), '/account/devices/notify') + }); + route = getRoute(makeRoutes({customs: mockCustoms}), '/account/devices/notify'); return runTest(route, mockRequest, function (response) { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }) .then(() => assert(false), function (err) { - assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once') - assert.equal(err.message, 'Client has sent too many requests') - }) - }) + assert.equal(mockCustoms.checkAuthenticated.callCount, 1, 'mockCustoms.checkAuthenticated was called once'); + assert.equal(err.message, 'Client has sent too many requests'); + }); + }); it('logs error if no devices found', () => { mockRequest.payload = { to: ['bogusid1', 'bogusid2'], TTL: 60, payload: pushPayload - } + }; - var mockLog = mocks.mockLog() + var mockLog = mocks.mockLog(); var mockPush = mocks.mockPush({ sendPush: () => P.reject('devices empty') - }) + }); var mockCustoms = { checkAuthenticated: () => P.resolve() - } + }; route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush - }), '/account/devices/notify') + }), '/account/devices/notify'); return runTest(route, mockRequest, function (response) { - assert.equal(JSON.stringify(response), '{}', 'response should not throw push errors') - }) - }) + assert.equal(JSON.stringify(response), '{}', 'response should not throw push errors'); + }); + }); it('can send account verification message with empty payload', () => { mockRequest.payload = { to: 'all', _endpointAction: 'accountVerify', payload: {} - } - const sendPushPromise = P.defer() + }; + const sendPushPromise = P.defer(); mockPush.sendPush = sinon.spy(() => { - sendPushPromise.resolve() - return P.resolve() - }) + sendPushPromise.resolve(); + return P.resolve(); + }); const mockCustoms = { checkAuthenticated: () => P.resolve() - } + }; route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush - }), '/account/devices/notify') + }), '/account/devices/notify'); return runTest(route, mockRequest, () => { return sendPushPromise.promise.then(() => { - assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once') - const args = mockPush.sendPush.args[0] - assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments') - assert.equal(args[0], uid, 'first argument was the device uid') - assert.ok(Array.isArray(args[1]), 'second argument was devices array') - assert.equal(args[2], 'accountVerify', 'second argument was the accountVerify reason') + assert.equal(mockPush.sendPush.callCount, 1, 'mockPush.sendPush was called once'); + const args = mockPush.sendPush.args[0]; + assert.equal(args.length, 4, 'mockPush.sendPush was passed four arguments'); + assert.equal(args[0], uid, 'first argument was the device uid'); + assert.ok(Array.isArray(args[1]), 'second argument was devices array'); + assert.equal(args[2], 'accountVerify', 'second argument was the accountVerify reason'); assert.deepEqual(args[3], { data: {} - }, 'third argument was the push options') - }) - }) - }) + }, 'third argument was the push options'); + }); + }); + }); it('reject account verification message with non-empty payload', () => { mockRequest.payload = { to: 'all', _endpointAction: 'accountVerify', payload: pushPayload - } + }; route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush - }), '/account/devices/notify') + }), '/account/devices/notify'); return runTest(route, mockRequest).then(() => { - assert.fail('should not have succeed') + assert.fail('should not have succeed'); }, (err) => { - assert.equal(err.errno, 107, 'invalid parameter in request body') - }) - }) -}) + assert.equal(err.errno, 107, 'invalid parameter in request body'); + }); + }); +}); describe('/account/device/commands', function () { - const uid = uuid.v4('binary').toString('hex') - const deviceId = crypto.randomBytes(16).toString('hex') - const mockLog = mocks.mockLog() + const uid = uuid.v4('binary').toString('hex'); + const deviceId = crypto.randomBytes(16).toString('hex'); + const mockLog = mocks.mockLog(); const mockRequest = mocks.mockRequest({ log: mockLog, credentials: { uid: uid, deviceId: deviceId } - }) - const mockCustoms = mocks.mockCustoms() + }); + const mockCustoms = mocks.mockCustoms(); it('retrieves messages using the pushbox service', () => { const mockResponse = { @@ -571,76 +571,76 @@ describe('/account/device/commands', function () { { index: 3, data: { number: 'three' } }, { index: 4, data: { number: 'four'} } ] - } - const mockPushbox = mocks.mockPushbox() - mockPushbox.retrieve = sinon.spy(() => P.resolve(mockResponse)) + }; + const mockPushbox = mocks.mockPushbox(); + mockPushbox.retrieve = sinon.spy(() => P.resolve(mockResponse)); mockRequest.query = { index: 2 - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, pushbox: mockPushbox - }), '/account/device/commands') + }), '/account/device/commands'); - mockRequest.query = isA.validate(mockRequest.query, route.options.validate.query).value - assert.ok(mockRequest.query) + mockRequest.query = isA.validate(mockRequest.query, route.options.validate.query).value; + assert.ok(mockRequest.query); return runTest(route, mockRequest).then(response => { - assert.equal(mockPushbox.retrieve.callCount, 1, 'pushbox was called') - assert.calledWithExactly(mockPushbox.retrieve, uid, deviceId, 100, 2) - assert.deepEqual(response, mockResponse) - }) - }) + assert.equal(mockPushbox.retrieve.callCount, 1, 'pushbox was called'); + assert.calledWithExactly(mockPushbox.retrieve, uid, deviceId, 100, 2); + assert.deepEqual(response, mockResponse); + }); + }); it('accepts a custom limit parameter', () => { - const mockPushbox = mocks.mockPushbox() + const mockPushbox = mocks.mockPushbox(); mockRequest.query = { index: 2, limit: 12 - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, pushbox: mockPushbox - }), '/account/device/commands') + }), '/account/device/commands'); return runTest(route, mockRequest).then(() => { - assert.equal(mockPushbox.retrieve.callCount, 1, 'pushbox was called') - assert.calledWithExactly(mockPushbox.retrieve, uid, deviceId, 12, 2) - }) - }) + assert.equal(mockPushbox.retrieve.callCount, 1, 'pushbox was called'); + assert.calledWithExactly(mockPushbox.retrieve, uid, deviceId, 12, 2); + }); + }); it('relays errors from the pushbox service', () => { const mockPushbox = mocks.mockPushbox({ retrieve() { - const error = new Error() - error.message = 'Boom!' - error.statusCode = 500 - return Promise.reject(error) + const error = new Error(); + error.message = 'Boom!'; + error.statusCode = 500; + return Promise.reject(error); } - }) + }); mockRequest.query = { index: 2 - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, pushbox: mockPushbox - }), '/account/device/commands') + }), '/account/device/commands'); return runTest(route, mockRequest).then(() => { - assert.ok(false, 'should not go here') + assert.ok(false, 'should not go here'); }, (err) => { - assert.equal(err.message, 'Boom!') - assert.equal(err.statusCode, 500) - }) - }) -}) + assert.equal(err.message, 'Boom!'); + assert.equal(err.statusCode, 500); + }); + }); +}); describe('/account/devices/invoke_command', function () { - const uid = uuid.v4('binary').toString('hex') - const command = 'bogusCommandName' + const uid = uuid.v4('binary').toString('hex'); + const command = 'bogusCommandName'; const mockDevices = [ { id: 'bogusid1', @@ -654,57 +654,57 @@ describe('/account/devices/invoke_command', function () { id: 'bogusid2', type: 'desktop', } - ] - let mockLog, mockDB, mockRequest, mockPush, mockCustoms + ]; + let mockLog, mockDB, mockRequest, mockPush, mockCustoms; beforeEach(() => { - mockLog = mocks.mockLog() + mockLog = mocks.mockLog(); mockDB = mocks.mockDB({ devices: mockDevices - }) + }); mockRequest = mocks.mockRequest({ log: mockLog, credentials: { uid: uid, deviceId: 'bogusid2' } - }) - mockPush = mocks.mockPush() - mockCustoms = mocks.mockCustoms() - }) + }); + mockPush = mocks.mockPush(); + mockCustoms = mocks.mockCustoms(); + }); it('stores commands using the pushbox service and sends a notification', () => { const mockPushbox = mocks.mockPushbox({ store: sinon.spy(async () => ({ index: 15 })) - }) - const target = 'bogusid1' - const sender = 'bogusid2' - const payload = { 'bogus': 'payload' } + }); + const target = 'bogusid1'; + const sender = 'bogusid2'; + const payload = { 'bogus': 'payload' }; mockRequest.payload = { target, command, payload - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush, pushbox: mockPushbox, db: mockDB - }), '/account/devices/invoke_command') + }), '/account/devices/invoke_command'); return runTest(route, mockRequest).then(() => { - assert.equal(mockDB.device.callCount, 1, 'device record was fetched') - assert.calledWithExactly(mockDB.device, uid, target) + assert.equal(mockDB.device.callCount, 1, 'device record was fetched'); + assert.calledWithExactly(mockDB.device, uid, target); - assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called') + assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called'); assert.calledWithExactly(mockPushbox.store, uid, target, { command, payload, sender, - }, undefined) + }, undefined); - assert.equal(mockPush.notifyCommandReceived.callCount, 1, 'notifyCommandReceived was called') + assert.equal(mockPush.notifyCommandReceived.callCount, 1, 'notifyCommandReceived was called'); assert.calledWithExactly(mockPush.notifyCommandReceived, uid, mockDevices[0], @@ -713,41 +713,41 @@ describe('/account/devices/invoke_command', function () { 15, 'https://public.url/v1/account/device/commands?index=15&limit=1', undefined - ) - }) - }) + ); + }); + }); it('uses a default TTL for send-tab commands with no TTL specified', () => { const THIRTY_DAYS_IN_SECS = 30 * 24 * 3600; - const commandSendTab = 'https://identity.mozilla.com/cmd/open-uri' + const commandSendTab = 'https://identity.mozilla.com/cmd/open-uri'; const mockPushbox = mocks.mockPushbox({ store: sinon.spy(async () => ({ index: 15 })) - }) - const target = 'bogusid1' - const sender = 'bogusid2' - const payload = { 'bogus': 'payload' } + }); + const target = 'bogusid1'; + const sender = 'bogusid2'; + const payload = { 'bogus': 'payload' }; mockRequest.payload = { target, command: commandSendTab, payload - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush, pushbox: mockPushbox, db: mockDB - }), '/account/devices/invoke_command') + }), '/account/devices/invoke_command'); return runTest(route, mockRequest).then(() => { - assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called') + assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called'); assert.calledWithExactly(mockPushbox.store, uid, target, { command: commandSendTab, payload, sender - }, THIRTY_DAYS_IN_SECS) + }, THIRTY_DAYS_IN_SECS); - assert.equal(mockPush.notifyCommandReceived.callCount, 1, 'notifyCommandReceived was called') + assert.equal(mockPush.notifyCommandReceived.callCount, 1, 'notifyCommandReceived was called'); assert.calledWithExactly(mockPush.notifyCommandReceived, uid, mockDevices[0], @@ -756,116 +756,116 @@ describe('/account/devices/invoke_command', function () { 15, 'https://public.url/v1/account/device/commands?index=15&limit=1', THIRTY_DAYS_IN_SECS - ) - }) - }) + ); + }); + }); it('rejects if sending to an unknown device', () => { - const mockPushbox = mocks.mockPushbox() - const target = 'unknowndevice' - const payload = { 'bogus': 'payload' } + const mockPushbox = mocks.mockPushbox(); + const target = 'unknowndevice'; + const payload = { 'bogus': 'payload' }; mockRequest.payload = { target, command, payload - } - mockDB.device = sinon.spy(() => P.reject(error.unknownDevice())) + }; + mockDB.device = sinon.spy(() => P.reject(error.unknownDevice())); const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush, pushbox: mockPushbox, db: mockDB - }), '/account/devices/invoke_command') + }), '/account/devices/invoke_command'); return runTest(route, mockRequest, () => { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }, (err) => { - assert.equal(err.errno, 123, 'Unknown device') - assert.equal(mockPushbox.store.callCount, 0, 'pushbox was not called') - assert.equal(mockPush.notifyCommandReceived.callCount, 0, 'notifyMessageReceived was not called') - }) - }) + assert.equal(err.errno, 123, 'Unknown device'); + assert.equal(mockPushbox.store.callCount, 0, 'pushbox was not called'); + assert.equal(mockPush.notifyCommandReceived.callCount, 0, 'notifyMessageReceived was not called'); + }); + }); it('rejects if invoking an unavailable command', () => { - const mockPushbox = mocks.mockPushbox() - const target = 'bogusid1' - const payload = { 'bogus': 'payload' } + const mockPushbox = mocks.mockPushbox(); + const target = 'bogusid1'; + const payload = { 'bogus': 'payload' }; mockRequest.payload = { target, command: 'nonexistentCommandName', payload - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush, pushbox: mockPushbox, db: mockDB - }), '/account/devices/invoke_command') + }), '/account/devices/invoke_command'); return runTest(route, mockRequest, () => { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }, (err) => { - assert.equal(err.errno, 157, 'unavailable device command') - assert.equal(mockPushbox.store.callCount, 0, 'pushbox was not called') + assert.equal(err.errno, 157, 'unavailable device command'); + assert.equal(mockPushbox.store.callCount, 0, 'pushbox was not called'); assert.equal(mockPush.notifyCommandReceived.callCount, 0, - 'notifyMessageReceived was not called') - }) - }) + 'notifyMessageReceived was not called'); + }); + }); it('relays errors from the pushbox service', () => { const mockPushbox = mocks.mockPushbox({ store: sinon.spy(() => { - const error = new Error() - error.message = 'Boom!' - error.statusCode = 500 - return Promise.reject(error) + const error = new Error(); + error.message = 'Boom!'; + error.statusCode = 500; + return Promise.reject(error); }) - }) - const target = 'bogusid1' - const payload = { 'bogus': 'payload' } + }); + const target = 'bogusid1'; + const payload = { 'bogus': 'payload' }; mockRequest.payload = { target, command, payload - } + }; const route = getRoute(makeRoutes({ customs: mockCustoms, log: mockLog, push: mockPush, pushbox: mockPushbox, db: mockDB - }), '/account/devices/invoke_command') + }), '/account/devices/invoke_command'); return runTest(route, mockRequest, () => { - assert(false, 'should have thrown') + assert(false, 'should have thrown'); }, (err) => { - assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called') - assert.equal(err.message, 'Boom!') - assert.equal(err.statusCode, 500) + assert.equal(mockPushbox.store.callCount, 1, 'pushbox was called'); + assert.equal(err.message, 'Boom!'); + assert.equal(err.statusCode, 500); assert.equal(mockPush.notifyCommandReceived.callCount, 0, - 'notifyMessageReceived was not called') - }) - }) -}) + 'notifyMessageReceived was not called'); + }); + }); +}); describe('/account/device/destroy', function () { - let uid - let deviceId - let deviceId2 - let mockLog - let mockDB - let mockPush + let uid; + let deviceId; + let deviceId2; + let mockLog; + let mockDB; + let mockPush; beforeEach(() => { - uid = uuid.v4('binary').toString('hex') - deviceId = crypto.randomBytes(16).toString('hex') - deviceId2 = crypto.randomBytes(16).toString('hex') - mockLog = mocks.mockLog() - mockDB = mocks.mockDB() - mockPush = mocks.mockPush() - }) + uid = uuid.v4('binary').toString('hex'); + deviceId = crypto.randomBytes(16).toString('hex'); + deviceId2 = crypto.randomBytes(16).toString('hex'); + mockLog = mocks.mockLog(); + mockDB = mocks.mockDB(); + mockPush = mocks.mockPush(); + }); it('should work', () => { var mockRequest = mocks.mockRequest({ @@ -877,25 +877,25 @@ describe('/account/device/destroy', function () { payload: { id: deviceId } - }) + }); var accountRoutes = makeRoutes({ db: mockDB, log: mockLog, push: mockPush - }) - var route = getRoute(accountRoutes, '/account/device/destroy') + }); + var route = getRoute(accountRoutes, '/account/device/destroy'); return runTest(route, mockRequest, function () { - assert.equal(mockDB.deleteDevice.callCount, 1) - assert.ok(mockDB.deleteDevice.calledBefore(mockPush.notifyDeviceDisconnected)) - assert.equal(mockPush.notifyDeviceDisconnected.callCount, 1) - assert.equal(mockPush.notifyDeviceDisconnected.firstCall.args[0], mockRequest.auth.credentials.uid) - assert.deepEqual(mockPush.notifyDeviceDisconnected.firstCall.args[1], [deviceId, deviceId2]) - assert.equal(mockPush.notifyDeviceDisconnected.firstCall.args[2], deviceId) + assert.equal(mockDB.deleteDevice.callCount, 1); + assert.ok(mockDB.deleteDevice.calledBefore(mockPush.notifyDeviceDisconnected)); + assert.equal(mockPush.notifyDeviceDisconnected.callCount, 1); + assert.equal(mockPush.notifyDeviceDisconnected.firstCall.args[0], mockRequest.auth.credentials.uid); + assert.deepEqual(mockPush.notifyDeviceDisconnected.firstCall.args[1], [deviceId, deviceId2]); + assert.equal(mockPush.notifyDeviceDisconnected.firstCall.args[2], deviceId); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - var args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + var args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'device.deleted', @@ -904,27 +904,27 @@ describe('/account/device/destroy', function () { userAgent: 'test user-agent', uid: uid.toString('hex'), device_id: deviceId - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockLog.notifyAttachedServices.callCount, 1) - args = mockLog.notifyAttachedServices.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], 'device:delete') - assert.equal(args[1], mockRequest) - var details = args[2] - assert.equal(details.uid, uid) - assert.equal(details.id, deviceId) - assert.ok(Date.now() - details.timestamp < 100) - }) - }) + assert.equal(mockLog.notifyAttachedServices.callCount, 1); + args = mockLog.notifyAttachedServices.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], 'device:delete'); + assert.equal(args[1], mockRequest); + var details = args[2]; + assert.equal(details.uid, uid); + assert.equal(details.id, deviceId); + assert.ok(Date.now() - details.timestamp < 100); + }); + }); describe('refreshToken revocation', () => { - let refreshTokenId - let mockDevices - let mockRequest + let refreshTokenId; + let mockDevices; + let mockRequest; beforeEach(() => { - refreshTokenId = '40f61392cf69b0be709fbd3122d0726bb32247b476b2a28451345e7a5555cec7' + refreshTokenId = '40f61392cf69b0be709fbd3122d0726bb32247b476b2a28451345e7a5555cec7'; mockDevices = [ { id: 'bogusid1', @@ -939,10 +939,10 @@ describe('/account/device/destroy', function () { id: 'bogusid2', type: 'desktop', } - ] + ]; mockDB = mocks.mockDB({ devices: mockDevices - }) + }); mockRequest = mocks.mockRequest({ credentials: { uid, @@ -953,67 +953,67 @@ describe('/account/device/destroy', function () { payload: { id: mockDevices[0].id } - }) - }) + }); + }); it('revokes refreshTokens', () => { const mockOAuthDb = mocks.mockOAuthDB({ revokeRefreshTokenById: sinon.spy(async () => { - return {} + return {}; }) - }) + }); const accountRoutes = makeRoutes({ db: mockDB, oauthdb: mockOAuthDb, log: mockLog, push: mockPush - }) - const route = getRoute(accountRoutes, '/account/device/destroy') + }); + const route = getRoute(accountRoutes, '/account/device/destroy'); return runTest(route, mockRequest, function () { - assert.equal(mockDB.deleteDevice.callCount, 1) - assert.isFalse(mockLog.error.calledOnceWith('deviceDestroy.revokeRefreshToken.error')) - assert.isTrue(mockOAuthDb.revokeRefreshTokenById.calledOnceWith(refreshTokenId)) - assert.equal(mockLog.notifyAttachedServices.callCount, 1) - }) - }) + assert.equal(mockDB.deleteDevice.callCount, 1); + assert.isFalse(mockLog.error.calledOnceWith('deviceDestroy.revokeRefreshToken.error')); + assert.isTrue(mockOAuthDb.revokeRefreshTokenById.calledOnceWith(refreshTokenId)); + assert.equal(mockLog.notifyAttachedServices.callCount, 1); + }); + }); it('catches err on refreshToken delete', () => { const mockOAuthDb = mocks.mockOAuthDB({ revokeRefreshTokenById: sinon.spy(async () => { throw OAuthError.invalidToken(); }) - }) + }); const accountRoutes = makeRoutes({ db: mockDB, oauthdb: mockOAuthDb, log: mockLog, push: mockPush - }) - const route = getRoute(accountRoutes, '/account/device/destroy') + }); + const route = getRoute(accountRoutes, '/account/device/destroy'); return runTest(route, mockRequest, function () { - assert.equal(mockDB.deleteDevice.callCount, 1) - assert.isTrue(mockOAuthDb.revokeRefreshTokenById.calledOnceWith(refreshTokenId)) - assert.isTrue(mockLog.error.calledOnceWith('deviceDestroy.revokeRefreshTokenById.error')) - assert.equal(mockLog.notifyAttachedServices.callCount, 1) - }) - }) - }) + assert.equal(mockDB.deleteDevice.callCount, 1); + assert.isTrue(mockOAuthDb.revokeRefreshTokenById.calledOnceWith(refreshTokenId)); + assert.isTrue(mockLog.error.calledOnceWith('deviceDestroy.revokeRefreshTokenById.error')); + assert.equal(mockLog.notifyAttachedServices.callCount, 1); + }); + }); + }); -}) +}); describe('/account/devices', () => { it('should return the devices list (translated)', () => { const credentials = { uid: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex') - } + }; const unnamedDevice = { sessionToken: crypto.randomBytes(16).toString('hex'), lastAccessTime: EARLIEST_SANE_TIMESTAMP - } + }; const mockRequest = mocks.mockRequest({ acceptLanguage: 'en;q=0.5, fr;q=0.51', credentials, @@ -1045,75 +1045,75 @@ describe('/account/devices', () => { unnamedDevice ], payload: {} - }) - const mockDB = mocks.mockDB() - const mockDevices = mocks.mockDevices() - const log = mocks.mockLog() + }); + const mockDB = mocks.mockDB(); + const mockDevices = mocks.mockDevices(); + const log = mocks.mockLog(); const accountRoutes = makeRoutes({ db: mockDB, devices: mockDevices, log - }) - const route = getRoute(accountRoutes, '/account/devices') + }); + const route = getRoute(accountRoutes, '/account/devices'); return runTest(route, mockRequest, response => { - const now = Date.now() + const now = Date.now(); - assert.ok(Array.isArray(response), 'response is array') - assert.equal(response.length, 4, 'response contains 4 items') + assert.ok(Array.isArray(response), 'response is array'); + assert.equal(response.length, 4, 'response contains 4 items'); - assert.equal(response[0].name, 'current session') - assert.equal(response[0].type, 'mobile') - assert.equal(response[0].sessionToken, undefined) - assert.equal(response[0].isCurrentDevice, true) - assert.ok(response[0].lastAccessTime > now - 10000 && response[0].lastAccessTime <= now) - assert.equal(response[0].lastAccessTimeFormatted, 'il y a quelques secondes') - assert.equal(response[0].approximateLastAccessTime, undefined) - assert.equal(response[0].approximateLastAccessTimeFormatted, undefined) - assert.deepEqual(response[0].location, {}) + assert.equal(response[0].name, 'current session'); + assert.equal(response[0].type, 'mobile'); + assert.equal(response[0].sessionToken, undefined); + assert.equal(response[0].isCurrentDevice, true); + assert.ok(response[0].lastAccessTime > now - 10000 && response[0].lastAccessTime <= now); + assert.equal(response[0].lastAccessTimeFormatted, 'il y a quelques secondes'); + assert.equal(response[0].approximateLastAccessTime, undefined); + assert.equal(response[0].approximateLastAccessTimeFormatted, undefined); + assert.deepEqual(response[0].location, {}); - assert.equal(response[1].name, 'has no type') - assert.equal(response[1].type, 'desktop') - assert.equal(response[1].sessionToken, undefined) - assert.equal(response[1].isCurrentDevice, false) - assert.equal(response[1].lastAccessTime, 1) - assert.equal(response[1].lastAccessTimeFormatted, moment(1).locale('fr').fromNow()) - assert.equal(response[1].approximateLastAccessTime, EARLIEST_SANE_TIMESTAMP) - assert.equal(response[1].approximateLastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()) - assert.deepEqual(response[1].location, {}) + assert.equal(response[1].name, 'has no type'); + assert.equal(response[1].type, 'desktop'); + assert.equal(response[1].sessionToken, undefined); + assert.equal(response[1].isCurrentDevice, false); + assert.equal(response[1].lastAccessTime, 1); + assert.equal(response[1].lastAccessTimeFormatted, moment(1).locale('fr').fromNow()); + assert.equal(response[1].approximateLastAccessTime, EARLIEST_SANE_TIMESTAMP); + assert.equal(response[1].approximateLastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()); + assert.deepEqual(response[1].location, {}); - assert.equal(response[2].name, 'has device type') - assert.equal(response[2].type, 'wibble') - assert.equal(response[2].isCurrentDevice, false) - assert.equal(response[2].lastAccessTime, EARLIEST_SANE_TIMESTAMP - 1) - assert.equal(response[2].lastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP - 1).locale('fr').fromNow()) - assert.equal(response[2].approximateLastAccessTime, EARLIEST_SANE_TIMESTAMP) - assert.equal(response[2].approximateLastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()) + assert.equal(response[2].name, 'has device type'); + assert.equal(response[2].type, 'wibble'); + assert.equal(response[2].isCurrentDevice, false); + assert.equal(response[2].lastAccessTime, EARLIEST_SANE_TIMESTAMP - 1); + assert.equal(response[2].lastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP - 1).locale('fr').fromNow()); + assert.equal(response[2].approximateLastAccessTime, EARLIEST_SANE_TIMESTAMP); + assert.equal(response[2].approximateLastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()); assert.deepEqual(response[2].location, { country: 'Royaume-Uni' - }) + }); - assert.equal(response[3].name, null) - assert.equal(response[3].lastAccessTime, EARLIEST_SANE_TIMESTAMP) - assert.equal(response[3].lastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()) - assert.equal(response[3].approximateLastAccessTime, undefined) - assert.equal(response[3].approximateLastAccessTimeFormatted, undefined) + assert.equal(response[3].name, null); + assert.equal(response[3].lastAccessTime, EARLIEST_SANE_TIMESTAMP); + assert.equal(response[3].lastAccessTimeFormatted, moment(EARLIEST_SANE_TIMESTAMP).locale('fr').fromNow()); + assert.equal(response[3].approximateLastAccessTime, undefined); + assert.equal(response[3].approximateLastAccessTimeFormatted, undefined); - assert.equal(log.error.callCount, 0, 'log.error was not called') + assert.equal(log.error.callCount, 0, 'log.error was not called'); - assert.equal(mockDB.devices.callCount, 0, 'db.devices was not called') + assert.equal(mockDB.devices.callCount, 0, 'db.devices was not called'); - assert.equal(mockDevices.synthesizeName.callCount, 1, 'mockDevices.synthesizeName was called once') - assert.equal(mockDevices.synthesizeName.args[0].length, 1, 'mockDevices.synthesizeName was passed one argument') - assert.equal(mockDevices.synthesizeName.args[0][0], unnamedDevice, 'mockDevices.synthesizeName was passed unnamed device') - }) - }) + assert.equal(mockDevices.synthesizeName.callCount, 1, 'mockDevices.synthesizeName was called once'); + assert.equal(mockDevices.synthesizeName.args[0].length, 1, 'mockDevices.synthesizeName was passed one argument'); + assert.equal(mockDevices.synthesizeName.args[0][0], unnamedDevice, 'mockDevices.synthesizeName was passed unnamed device'); + }); + }); it('should return the devices list (not translated)', () => { const credentials = { uid: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex') - } + }; const request = mocks.mockRequest({ acceptLanguage: 'en-US,en;q=0.5', credentials, @@ -1132,28 +1132,28 @@ describe('/account/devices', () => { } ], payload: {} - }) - const db = mocks.mockDB() - const devices = mocks.mockDevices() - const log = mocks.mockLog() - const accountRoutes = makeRoutes({ db, devices, log }) - const route = getRoute(accountRoutes, '/account/devices') + }); + const db = mocks.mockDB(); + const devices = mocks.mockDevices(); + const log = mocks.mockLog(); + const accountRoutes = makeRoutes({ db, devices, log }); + const route = getRoute(accountRoutes, '/account/devices'); return runTest(route, request, response => { - assert.equal(response.length, 1) - assert.equal(response[0].name, 'wibble') + assert.equal(response.length, 1); + assert.equal(response[0].name, 'wibble'); assert.deepEqual(response[0].location, { city: 'Bournemouth', country: 'United Kingdom', state: 'England', stateCode: 'EN' - }) - assert.equal(log.error.callCount, 0, 'log.error was not called') - }) - }) + }); + assert.equal(log.error.callCount, 0, 'log.error was not called'); + }); + }); it('should allow returning a lastAccessTime of 0', () => { - const route = getRoute(makeRoutes({}), '/account/devices') + const route = getRoute(makeRoutes({}), '/account/devices'); const res = [ { id: crypto.randomBytes(16).toString('hex'), @@ -1163,12 +1163,12 @@ describe('/account/devices', () => { type: 'test', pushEndpointExpired: false } - ] - isA.assert(res, route.options.response.schema) - }) + ]; + isA.assert(res, route.options.response.schema); + }); it('should allow returning approximateLastAccessTime', () => { - const route = getRoute(makeRoutes({}), '/account/devices') + const route = getRoute(makeRoutes({}), '/account/devices'); isA.assert([{ id: crypto.randomBytes(16).toString('hex'), isCurrentDevice: true, @@ -1178,11 +1178,11 @@ describe('/account/devices', () => { name: 'test', type: 'test', pushEndpointExpired: false - }], route.options.response.schema) - }) + }], route.options.response.schema); + }); it('should not allow returning approximateLastAccessTime < EARLIEST_SANE_TIMESTAMP', () => { - const route = getRoute(makeRoutes({}), '/account/devices') + const route = getRoute(makeRoutes({}), '/account/devices'); assert.throws(() => isA.assert([{ id: crypto.randomBytes(16).toString('hex'), isCurrentDevice: true, @@ -1192,14 +1192,14 @@ describe('/account/devices', () => { name: 'test', type: 'test', pushEndpointExpired: false - }], route.config.response.schema)) - }) -}) + }], route.config.response.schema)); + }); +}); describe('/account/sessions', () => { - const now = Date.now() - const times = [ now, now + 1, now + 2, now + 3, now + 4, now + 5, now + 6, now + 7, now + 8 ] - const tokenIds = [ 'foo', 'bar', 'baz', 'qux' ] + const now = Date.now(); + const times = [ now, now + 1, now + 2, now + 3, now + 4, now + 5, now + 6, now + 7, now + 8 ]; + const tokenIds = [ 'foo', 'bar', 'baz', 'qux' ]; const sessions = [ { id: tokenIds[0], uid: 'qux', createdAt: times[0], lastAccessTime: times[1], @@ -1245,9 +1245,9 @@ describe('/account/sessions', () => { deviceCallbackIsExpired: false, location: null } - ] - const db = mocks.mockDB({ sessions }) - const accountRoutes = makeRoutes({ db }) + ]; + const db = mocks.mockDB({ sessions }); + const accountRoutes = makeRoutes({ db }); const request = mocks.mockRequest({ acceptLanguage: 'xx', credentials: { @@ -1255,14 +1255,14 @@ describe('/account/sessions', () => { uid: hexString(16) }, payload: {} - }) + }); it('should list account sessions', () => { - const route = getRoute(accountRoutes, '/account/sessions') + const route = getRoute(accountRoutes, '/account/sessions'); return runTest(route, request, result => { - assert.ok(Array.isArray(result)) - assert.equal(result.length, 4) + assert.ok(Array.isArray(result)); + assert.equal(result.length, 4); assert.deepEqual(result, [ { deviceId: null, @@ -1358,7 +1358,7 @@ describe('/account/sessions', () => { userAgent: '', location: {} } - ]) - }) - }) -}) + ]); + }); + }); +}); diff --git a/test/local/routes/emails.js b/test/local/routes/emails.js index 07b3a2a2..f98a4c00 100644 --- a/test/local/routes/emails.js +++ b/test/local/routes/emails.js @@ -2,56 +2,56 @@ * 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/. */ -'use strict' +'use strict'; -const sinon = require('sinon') +const sinon = require('sinon'); -const { assert } = require('chai') -const crypto = require('crypto') -const error = require('../../../lib/error') -const getRoute = require('../../routes_helpers').getRoute -const knownIpLocation = require('../../known-ip-location') -const mocks = require('../../mocks') -const P = require('../../../lib/promise') -const proxyquire = require('proxyquire') -const uuid = require('uuid') +const { assert } = require('chai'); +const crypto = require('crypto'); +const error = require('../../../lib/error'); +const getRoute = require('../../routes_helpers').getRoute; +const knownIpLocation = require('../../known-ip-location'); +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); +const proxyquire = require('proxyquire'); +const uuid = require('uuid'); -const TEST_EMAIL = 'foo@gmail.com' -const TEST_EMAIL_ADDITIONAL = 'foo2@gmail.com' -const TEST_EMAIL_INVALID = 'example@dotless-domain' -const MS_IN_DAY = 1000 * 60 * 60 * 24 +const TEST_EMAIL = 'foo@gmail.com'; +const TEST_EMAIL_ADDITIONAL = 'foo2@gmail.com'; +const TEST_EMAIL_INVALID = 'example@dotless-domain'; +const MS_IN_DAY = 1000 * 60 * 60 * 24; // This is slightly less than 2 months ago, regardless of which // months are in question (I'm looking at you, February...) -const MS_IN_ALMOST_TWO_MONTHS = MS_IN_DAY * 58 +const MS_IN_ALMOST_TWO_MONTHS = MS_IN_DAY * 58; var makeRoutes = function (options = {}, requireMocks) { - var config = options.config || {} - config.verifierVersion = config.verifierVersion || 0 - config.smtp = config.smtp || {} + var config = options.config || {}; + config.verifierVersion = config.verifierVersion || 0; + config.smtp = config.smtp || {}; config.memcached = config.memcached || { address: 'none', idle: 500, lifetime: 30 - } + }; config.i18n = { supportedLanguages: ['en'], defaultLanguage: 'en' - } - config.lastAccessTimeUpdates = {} - config.signinConfirmation = config.signinConfirmation || {} - config.signinUnblock = config.signinUnblock || {} - config.secondaryEmail = config.secondaryEmail || {} + }; + config.lastAccessTimeUpdates = {}; + config.signinConfirmation = config.signinConfirmation || {}; + config.signinUnblock = config.signinUnblock || {}; + config.secondaryEmail = config.secondaryEmail || {}; config.push = { allowedServerRegex: /^https:\/\/updates\.push\.services\.mozilla\.com(\/.*)?$/ - } + }; - var log = options.log || mocks.mockLog() - var db = options.db || mocks.mockDB() + var log = options.log || mocks.mockLog(); + var db = options.db || mocks.mockDB(); var customs = options.customs || { - check: function () { return P.resolve(true) } - } - var push = options.push || require('../../../lib/push')(log, db, {}) + check: function () { return P.resolve(true); } + }; + var push = options.push || require('../../../lib/push')(log, db, {}); return proxyquire('../../../lib/routes/emails', requireMocks || {})( log, db, @@ -59,133 +59,133 @@ var makeRoutes = function (options = {}, requireMocks) { config, customs, push - ) -} + ); +}; function runTest (route, request, assertions) { return route.handler(request) - .then(assertions) + .then(assertions); } describe('/recovery_email/status', function () { - var config = {} - var mockDB = mocks.mockDB() - var pushCalled + var config = {}; + var mockDB = mocks.mockDB(); + var pushCalled; var mockLog = mocks.mockLog({ info: sinon.spy((op, data) => { if (data.name === 'recovery_email_reason.push') { - pushCalled = true + pushCalled = true; } }) - }) + }); var accountRoutes = makeRoutes({ config: config, db: mockDB, log: mockLog - }) - var route = getRoute(accountRoutes, '/recovery_email/status') + }); + var route = getRoute(accountRoutes, '/recovery_email/status'); var mockRequest = mocks.mockRequest({ credentials: { uid: uuid.v4('binary').toString('hex'), email: TEST_EMAIL } - }) + }); describe('invalid email', function () { - var mockRequest + var mockRequest; beforeEach(() => { mockRequest = mocks.mockRequest({ credentials: { email: TEST_EMAIL_INVALID } - }) - }) + }); + }); it('unverified account', function () { - mockRequest.auth.credentials.emailVerified = false + mockRequest.auth.credentials.emailVerified = false; return runTest(route, mockRequest).then(() => assert.ok(false), function (response) { - assert.equal(mockDB.deleteAccount.callCount, 1) - assert.equal(mockDB.deleteAccount.firstCall.args[0].email, TEST_EMAIL_INVALID) - assert.equal(response.errno, error.ERRNO.INVALID_TOKEN) + assert.equal(mockDB.deleteAccount.callCount, 1); + assert.equal(mockDB.deleteAccount.firstCall.args[0].email, TEST_EMAIL_INVALID); + assert.equal(response.errno, error.ERRNO.INVALID_TOKEN); - assert.equal(mockLog.info.callCount, 2) - const args = mockLog.info.args[1] - assert.equal(args.length, 2) - assert.equal(args[0], 'accountDeleted.invalidEmailAddress') + assert.equal(mockLog.info.callCount, 2); + const args = mockLog.info.args[1]; + assert.equal(args.length, 2); + assert.equal(args[0], 'accountDeleted.invalidEmailAddress'); assert.deepEqual(args[1], { email: TEST_EMAIL_INVALID, emailVerified: false - }) + }); }) .then(function () { - mockDB.deleteAccount.resetHistory() - }) - }) + mockDB.deleteAccount.resetHistory(); + }); + }); it('unverified account - stale session token', () => { const log = { info: sinon.spy(), begin: sinon.spy() - } - const db = mocks.mockDB() - config.emailStatusPollingTimeout = MS_IN_ALMOST_TWO_MONTHS + }; + const db = mocks.mockDB(); + config.emailStatusPollingTimeout = MS_IN_ALMOST_TWO_MONTHS; const routes = makeRoutes({ config, db, log - }) + }); mockRequest = mocks.mockRequest({ credentials: { email: TEST_EMAIL_INVALID } - }) - const route = getRoute(routes, '/recovery_email/status') + }); + const route = getRoute(routes, '/recovery_email/status'); - const date = new Date() - date.setMonth(date.getMonth() - 2) + const date = new Date(); + date.setMonth(date.getMonth() - 2); - mockRequest.auth.credentials.createdAt = date.getTime() - mockRequest.auth.credentials.hello = 'mytest' - mockRequest.auth.credentials.emailVerified = false - mockRequest.auth.credentials.uaBrowser = 'Firefox' - mockRequest.auth.credentials.uaBrowserVersion = '57' + mockRequest.auth.credentials.createdAt = date.getTime(); + mockRequest.auth.credentials.hello = 'mytest'; + mockRequest.auth.credentials.emailVerified = false; + mockRequest.auth.credentials.uaBrowser = 'Firefox'; + mockRequest.auth.credentials.uaBrowserVersion = '57'; return runTest(route, mockRequest).then(() => assert.ok(false), function (response) { - const args = log.info.firstCall.args - assert.equal(args[0], 'recovery_email.status.stale') - assert.equal(args[1].email, TEST_EMAIL_INVALID) - assert.equal(args[1].createdAt, date.getTime()) - assert.equal(args[1].browser, 'Firefox 57') + const args = log.info.firstCall.args; + assert.equal(args[0], 'recovery_email.status.stale'); + assert.equal(args[1].email, TEST_EMAIL_INVALID); + assert.equal(args[1].createdAt, date.getTime()); + assert.equal(args[1].browser, 'Firefox 57'); }) .then(function () { - mockDB.deleteAccount.resetHistory() - }) - }) + mockDB.deleteAccount.resetHistory(); + }); + }); it('verified account', function () { - mockRequest.auth.credentials.uid = uuid.v4('binary').toString('hex') - mockRequest.auth.credentials.emailVerified = true - mockRequest.auth.credentials.tokenVerified = true + mockRequest.auth.credentials.uid = uuid.v4('binary').toString('hex'); + mockRequest.auth.credentials.emailVerified = true; + mockRequest.auth.credentials.tokenVerified = true; return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.deleteAccount.callCount, 0) + assert.equal(mockDB.deleteAccount.callCount, 0); assert.deepEqual(response, { email: TEST_EMAIL_INVALID, verified: true, emailVerified: true, sessionVerified: true - }) - }) - }) - }) + }); + }); + }); + }); it('valid email, verified account', function () { - pushCalled = false + pushCalled = false; var mockRequest = mocks.mockRequest({ credentials: { uid: uuid.v4('binary').toString('hex'), @@ -196,23 +196,23 @@ describe('/recovery_email/status', function () { query: { reason: 'push' } - }) + }); return runTest(route, mockRequest, function (response) { - assert.equal(pushCalled, true) + assert.equal(pushCalled, true); assert.deepEqual(response, { email: TEST_EMAIL, verified: true, emailVerified: true, sessionVerified: true - }) - }) - }) + }); + }); + }); it('verified account, verified session', function () { - mockRequest.auth.credentials.emailVerified = true - mockRequest.auth.credentials.tokenVerified = true + mockRequest.auth.credentials.emailVerified = true; + mockRequest.auth.credentials.tokenVerified = true; return runTest(route, mockRequest, function (response) { assert.deepEqual(response, { @@ -220,14 +220,14 @@ describe('/recovery_email/status', function () { verified: true, sessionVerified: true, emailVerified: true - }) - }) - }) + }); + }); + }); it('verified account, unverified session, must verify session', function () { - mockRequest.auth.credentials.emailVerified = true - mockRequest.auth.credentials.tokenVerified = false - mockRequest.auth.credentials.mustVerify = true + mockRequest.auth.credentials.emailVerified = true; + mockRequest.auth.credentials.tokenVerified = false; + mockRequest.auth.credentials.mustVerify = true; return runTest(route, mockRequest, function (response) { assert.deepEqual(response, { @@ -235,14 +235,14 @@ describe('/recovery_email/status', function () { verified: false, sessionVerified: false, emailVerified: true - }) - }) - }) + }); + }); + }); it('verified account, unverified session, neednt verify session', function () { - mockRequest.auth.credentials.emailVerified = true - mockRequest.auth.credentials.tokenVerified = false - mockRequest.auth.credentials.mustVerify = false + mockRequest.auth.credentials.emailVerified = true; + mockRequest.auth.credentials.tokenVerified = false; + mockRequest.auth.credentials.mustVerify = false; return runTest(route, mockRequest, function (response) { assert.deepEqual(response, { @@ -250,28 +250,28 @@ describe('/recovery_email/status', function () { verified: true, sessionVerified: false, emailVerified: true - }) - }) - }) -}) + }); + }); + }); +}); describe('/recovery_email/resend_code', () => { - const config = {} - const secondEmailCode = crypto.randomBytes(16) - const mockDB = mocks.mockDB({secondEmailCode: secondEmailCode}) - const mockLog = mocks.mockLog() + const config = {}; + const secondEmailCode = crypto.randomBytes(16); + const mockDB = mocks.mockDB({secondEmailCode: secondEmailCode}); + const mockLog = mocks.mockLog(); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) - const mockMailer = mocks.mockMailer() - const mockMetricsContext = mocks.mockMetricsContext() + return P.resolve(); + }); + const mockMailer = mocks.mockMailer(); + const mockMetricsContext = mocks.mockMetricsContext(); const accountRoutes = makeRoutes({ config: config, db: mockDB, log: mockLog, mailer: mockMailer - }) - const route = getRoute(accountRoutes, '/recovery_email/resend_code') + }); + const route = getRoute(accountRoutes, '/recovery_email/resend_code'); it('verification', () => { const mockRequest = mocks.mockRequest({ @@ -296,34 +296,34 @@ describe('/recovery_email/resend_code', () => { flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103' } } - }) + }); return runTest(route, mockRequest, response => { - assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once') - assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verification.resent') + assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verification.resent'); - assert.equal(mockMailer.sendVerifyCode.callCount, 1) - const args = mockMailer.sendVerifyCode.args[0] - assert.equal(args[2].uaBrowser, 'Firefox') - assert.equal(args[2].uaBrowserVersion, '52') - assert.equal(args[2].uaOS, 'Mac OS X') - assert.equal(args[2].uaOSVersion, '10.10') - assert.ok(knownIpLocation.location.city.has(args[2].location.city)) - assert.equal(args[2].location.country, knownIpLocation.location.country) - assert.equal(args[2].ip, knownIpLocation.ip) - assert.equal(args[2].timeZone, knownIpLocation.location.tz) - assert.strictEqual(args[2].uaDeviceType, undefined) - assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId) - assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId) - assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime) - assert.equal(args[2].service, mockRequest.payload.service) - assert.equal(args[2].uid, mockRequest.auth.credentials.uid) + assert.equal(mockMailer.sendVerifyCode.callCount, 1); + const args = mockMailer.sendVerifyCode.args[0]; + assert.equal(args[2].uaBrowser, 'Firefox'); + assert.equal(args[2].uaBrowserVersion, '52'); + assert.equal(args[2].uaOS, 'Mac OS X'); + assert.equal(args[2].uaOSVersion, '10.10'); + assert.ok(knownIpLocation.location.city.has(args[2].location.city)); + assert.equal(args[2].location.country, knownIpLocation.location.country); + assert.equal(args[2].ip, knownIpLocation.ip); + assert.equal(args[2].timeZone, knownIpLocation.location.tz); + assert.strictEqual(args[2].uaDeviceType, undefined); + assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId); + assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId); + assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime); + assert.equal(args[2].service, mockRequest.payload.service); + assert.equal(args[2].uid, mockRequest.auth.credentials.uid); }) .then(() => { - mockMailer.sendVerifyCode.resetHistory() - mockLog.flowEvent.resetHistory() - }) - }) + mockMailer.sendVerifyCode.resetHistory(); + mockLog.flowEvent.resetHistory(); + }); + }); it('verification additional email', () => { const mockRequest = mocks.mockRequest({ @@ -347,26 +347,26 @@ describe('/recovery_email/resend_code', () => { payload: { email: 'secondEmail@email.com' } - }) + }); return runTest(route, mockRequest, response => { - assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowId, mockMetricsContext.flowId) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowBeginTime, mockMetricsContext.flowBeginTime) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].service, 'foo') - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid) + assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowId, mockMetricsContext.flowId); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowBeginTime, mockMetricsContext.flowBeginTime); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].service, 'foo'); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid); - assert.equal(mockMailer.sendVerifyCode.callCount, 0) - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0) - const args = mockMailer.sendVerifySecondaryEmail.getCall(0).args - assert.equal(args[2].code, secondEmailCode, 'email code set') + assert.equal(mockMailer.sendVerifyCode.callCount, 0); + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0); + const args = mockMailer.sendVerifySecondaryEmail.getCall(0).args; + assert.equal(args[2].code, secondEmailCode, 'email code set'); }) .then(() => { - mockMailer.sendVerifySecondaryEmail.resetHistory() - mockLog.flowEvent.resetHistory() - }) - }) + mockMailer.sendVerifySecondaryEmail.resetHistory(); + mockLog.flowEvent.resetHistory(); + }); + }); it('confirmation', () => { const mockRequest = mocks.mockRequest({ @@ -392,33 +392,33 @@ describe('/recovery_email/resend_code', () => { flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103' } } - }) - mockLog.flowEvent.resetHistory() + }); + mockLog.flowEvent.resetHistory(); return runTest(route, mockRequest, response => { - assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once') - assert.equal(mockLog.flowEvent.args[0][0].event, 'email.confirmation.resent') + assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'email.confirmation.resent'); - assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1) - const args = mockMailer.sendVerifyLoginEmail.args[0] - assert.equal(args[2].uaBrowser, 'Firefox') - assert.equal(args[2].uaBrowserVersion, '50') - assert.equal(args[2].uaOS, 'Android') - assert.equal(args[2].uaOSVersion, '6') - assert.strictEqual(args[2].uaDeviceType, 'tablet') - assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId) - assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId) - assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime) - assert.equal(args[2].service, mockRequest.payload.service) - assert.equal(args[2].uid, mockRequest.auth.credentials.uid) - }) - }) + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1); + const args = mockMailer.sendVerifyLoginEmail.args[0]; + assert.equal(args[2].uaBrowser, 'Firefox'); + assert.equal(args[2].uaBrowserVersion, '50'); + assert.equal(args[2].uaOS, 'Android'); + assert.equal(args[2].uaOSVersion, '6'); + assert.strictEqual(args[2].uaDeviceType, 'tablet'); + assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId); + assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId); + assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime); + assert.equal(args[2].service, mockRequest.payload.service); + assert.equal(args[2].uid, mockRequest.auth.credentials.uid); + }); + }); -}) +}); describe('/recovery_email/verify_code', function () { - const uid = uuid.v4('binary').toString('hex') - const mockLog = mocks.mockLog() + const uid = uuid.v4('binary').toString('hex'); + const mockLog = mocks.mockLog(); const mockRequest = mocks.mockRequest({ log: mockLog, metricsContext: mocks.mockMetricsContext({ @@ -428,7 +428,7 @@ describe('/recovery_email/verify_code', function () { flow_time: 10000, flow_id: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103', time: Date.now() - 10000 - })) + })); } }), query: {}, @@ -437,7 +437,7 @@ describe('/recovery_email/verify_code', function () { service: 'sync', uid: uid } - }) + }); var dbData = { email: TEST_EMAIL, emailCode: Buffer.from(mockRequest.payload.code, 'hex'), @@ -445,17 +445,17 @@ describe('/recovery_email/verify_code', function () { secondEmail: 'test@email.com', secondEmailCode: crypto.randomBytes(16).toString('hex'), uid: uid - } + }; var dbErrors = { verifyTokens: error.invalidVerificationCode({}) - } - var mockDB = mocks.mockDB(dbData, dbErrors) - var mockMailer = mocks.mockMailer() - const mockPush = mocks.mockPush() - var mockCustoms = mocks.mockCustoms() + }; + var mockDB = mocks.mockDB(dbData, dbErrors); + var mockMailer = mocks.mockMailer(); + const mockPush = mocks.mockPush(); + var mockCustoms = mocks.mockCustoms(); var accountRoutes = makeRoutes({ checkPassword: function () { - return P.resolve(true) + return P.resolve(true); }, config: {}, customs: mockCustoms, @@ -463,30 +463,30 @@ describe('/recovery_email/verify_code', function () { log: mockLog, mailer: mockMailer, push: mockPush - }) - var route = getRoute(accountRoutes, '/recovery_email/verify_code') + }); + var route = getRoute(accountRoutes, '/recovery_email/verify_code'); describe('verifyTokens rejects with INVALID_VERIFICATION_CODE', function () { it('without a reminder payload', function () { return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.verifyTokens.callCount, 1, 'calls verifyTokens') - assert.equal(mockDB.verifyEmail.callCount, 1, 'calls verifyEmail') - assert.equal(mockCustoms.check.callCount, 1, 'calls customs.check') + assert.equal(mockDB.verifyTokens.callCount, 1, 'calls verifyTokens'); + assert.equal(mockDB.verifyEmail.callCount, 1, 'calls verifyEmail'); + assert.equal(mockCustoms.check.callCount, 1, 'calls customs.check'); - assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'logs verified') - let args = mockLog.notifyAttachedServices.args[0] - assert.equal(args[0], 'verified') - assert.equal(args[2].uid, uid) - assert.equal(args[2].marketingOptIn, undefined) - assert.equal(args[2].service, 'sync') + assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'logs verified'); + let args = mockLog.notifyAttachedServices.args[0]; + assert.equal(args[0], 'verified'); + assert.equal(args[2].uid, uid); + assert.equal(args[2].marketingOptIn, undefined); + assert.equal(args[2].service, 'sync'); - assert.equal(mockMailer.sendPostVerifyEmail.callCount, 1, 'sendPostVerifyEmail was called once') - assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].service, mockRequest.payload.service) - assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].uid, uid) + assert.equal(mockMailer.sendPostVerifyEmail.callCount, 1, 'sendPostVerifyEmail was called once'); + assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].service, mockRequest.payload.service); + assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].uid, uid); - assert.equal(mockLog.activityEvent.callCount, 1, 'activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.verified', @@ -495,113 +495,113 @@ describe('/recovery_email/verify_code', function () { service: 'sync', uid: uid.toString('hex'), userAgent: 'test user-agent' - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockLog.amplitudeEvent.callCount, 1, 'amplitudeEvent was called once') - args = mockLog.amplitudeEvent.args[0] - assert.equal(args[0].event_type, 'fxa_reg - email_confirmed', 'first call to amplitudeEvent was email_confirmed event') - assert.equal(args[0].user_properties.newsletter_state, 'unsubscribed', 'newsletter_state was correct') + assert.equal(mockLog.amplitudeEvent.callCount, 1, 'amplitudeEvent was called once'); + args = mockLog.amplitudeEvent.args[0]; + assert.equal(args[0].event_type, 'fxa_reg - email_confirmed', 'first call to amplitudeEvent was email_confirmed event'); + assert.equal(args[0].user_properties.newsletter_state, 'unsubscribed', 'newsletter_state was correct'); - assert.equal(mockLog.flowEvent.callCount, 2, 'flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verify_code.clicked', 'first event was email.verify_code.clicked') - assert.equal(mockLog.flowEvent.args[1][0].event, 'account.verified', 'second event was event account.verified') + assert.equal(mockLog.flowEvent.callCount, 2, 'flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verify_code.clicked', 'first event was email.verify_code.clicked'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'account.verified', 'second event was event account.verified'); - assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once') - args = mockPush.notifyAccountUpdated.args[0] - assert.equal(args.length, 3, 'mockPush.notifyAccountUpdated should have been passed three arguments') - assert.equal(args[0].toString('hex'), uid, 'first argument should have been uid') - assert.ok(Array.isArray(args[1]), 'second argument should have been devices array') - assert.equal(args[2], 'accountVerify', 'third argument should have been reason') + assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once'); + args = mockPush.notifyAccountUpdated.args[0]; + assert.equal(args.length, 3, 'mockPush.notifyAccountUpdated should have been passed three arguments'); + assert.equal(args[0].toString('hex'), uid, 'first argument should have been uid'); + assert.ok(Array.isArray(args[1]), 'second argument should have been devices array'); + assert.equal(args[2], 'accountVerify', 'third argument should have been reason'); - assert.equal(JSON.stringify(response), '{}') + assert.equal(JSON.stringify(response), '{}'); }) .then(function () { - mockDB.verifyTokens.resetHistory() - mockDB.verifyEmail.resetHistory() - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - mockLog.notifyAttachedServices.resetHistory() - mockMailer.sendPostVerifyEmail.resetHistory() - mockPush.notifyAccountUpdated.resetHistory() - }) - }) + mockDB.verifyTokens.resetHistory(); + mockDB.verifyEmail.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + mockLog.notifyAttachedServices.resetHistory(); + mockMailer.sendPostVerifyEmail.resetHistory(); + mockPush.notifyAccountUpdated.resetHistory(); + }); + }); it('with marketingOptIn', () => { - mockRequest.payload.marketingOptIn = true + mockRequest.payload.marketingOptIn = true; return runTest(route, mockRequest, function (response) { - assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'logs verified') - let args = mockLog.notifyAttachedServices.args[0] - assert.equal(args[0], 'verified') - assert.equal(args[2].uid, uid) - assert.equal(args[2].marketingOptIn, true) - assert.equal(args[2].service, 'sync') + assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'logs verified'); + let args = mockLog.notifyAttachedServices.args[0]; + assert.equal(args[0], 'verified'); + assert.equal(args[2].uid, uid); + assert.equal(args[2].marketingOptIn, true); + assert.equal(args[2].service, 'sync'); - assert.equal(mockLog.amplitudeEvent.callCount, 2, 'amplitudeEvent was called twice') - args = mockLog.amplitudeEvent.args[1] - assert.equal(args[0].event_type, 'fxa_reg - email_confirmed', 'second call to amplitudeEvent was email_confirmed event') - assert.equal(args[0].user_properties.newsletter_state, 'subscribed', 'newsletter_state was correct') + assert.equal(mockLog.amplitudeEvent.callCount, 2, 'amplitudeEvent was called twice'); + args = mockLog.amplitudeEvent.args[1]; + assert.equal(args[0].event_type, 'fxa_reg - email_confirmed', 'second call to amplitudeEvent was email_confirmed event'); + assert.equal(args[0].user_properties.newsletter_state, 'subscribed', 'newsletter_state was correct'); - assert.equal(JSON.stringify(response), '{}') + assert.equal(JSON.stringify(response), '{}'); }) .then(function () { - delete mockRequest.payload.marketingOptIn - mockDB.verifyTokens.resetHistory() - mockDB.verifyEmail.resetHistory() - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - mockLog.notifyAttachedServices.resetHistory() - mockMailer.sendPostVerifyEmail.resetHistory() - mockPush.notifyAccountUpdated.resetHistory() - }) - }) + delete mockRequest.payload.marketingOptIn; + mockDB.verifyTokens.resetHistory(); + mockDB.verifyEmail.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + mockLog.notifyAttachedServices.resetHistory(); + mockMailer.sendPostVerifyEmail.resetHistory(); + mockPush.notifyAccountUpdated.resetHistory(); + }); + }); it('with a reminder payload', function () { - mockRequest.payload.reminder = 'second' + mockRequest.payload.reminder = 'second'; return runTest(route, mockRequest, function (response) { - assert.equal(mockLog.activityEvent.callCount, 1, 'activityEvent was called once') + assert.equal(mockLog.activityEvent.callCount, 1, 'activityEvent was called once'); - assert.equal(mockLog.flowEvent.callCount, 2, 'flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verify_code.clicked', 'first event was email.verify_code.clicked') - assert.equal(mockLog.flowEvent.args[1][0].event, 'account.verified', 'second event was account.verified') + assert.equal(mockLog.flowEvent.callCount, 2, 'flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'email.verify_code.clicked', 'first event was email.verify_code.clicked'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'account.verified', 'second event was account.verified'); - assert.equal(mockMailer.sendPostVerifyEmail.callCount, 1, 'sendPostVerifyEmail was called once') - assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once') + assert.equal(mockMailer.sendPostVerifyEmail.callCount, 1, 'sendPostVerifyEmail was called once'); + assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once'); - assert.equal(JSON.stringify(response), '{}') + assert.equal(JSON.stringify(response), '{}'); }) .then(function () { - mockDB.verifyTokens.resetHistory() - mockDB.verifyEmail.resetHistory() - mockLog.activityEvent.resetHistory() - mockLog.flowEvent.resetHistory() - mockLog.notifyAttachedServices.resetHistory() - mockMailer.sendPostVerifyEmail.resetHistory() - mockPush.notifyAccountUpdated.resetHistory() - }) - }) - }) + mockDB.verifyTokens.resetHistory(); + mockDB.verifyEmail.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockLog.flowEvent.resetHistory(); + mockLog.notifyAttachedServices.resetHistory(); + mockMailer.sendPostVerifyEmail.resetHistory(); + mockPush.notifyAccountUpdated.resetHistory(); + }); + }); + }); describe('verifyTokens resolves', function () { before(() => { - dbData.emailVerified = true - dbErrors.verifyTokens = undefined - }) + dbData.emailVerified = true; + dbErrors.verifyTokens = undefined; + }); it('email verification', function () { return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens') - assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail') - assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices') - assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(mockPush.notifyAccountUpdated.callCount, 0, 'mockPush.notifyAccountUpdated should not have been called') - assert.equal(mockPush.notifyDeviceConnected.callCount, 0, 'mockPush.notifyDeviceConnected should not have been called (no devices)') + assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens'); + assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail'); + assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices'); + assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(mockPush.notifyAccountUpdated.callCount, 0, 'mockPush.notifyAccountUpdated should not have been called'); + assert.equal(mockPush.notifyDeviceConnected.callCount, 0, 'mockPush.notifyDeviceConnected should not have been called (no devices)'); }) .then(function () { - mockDB.verifyTokens.resetHistory() - }) - }) + mockDB.verifyTokens.resetHistory(); + }); + }); it('email verification with associated device', function () { mockDB.deviceFromTokenVerificationId = function (uid, tokenVerificationId) { @@ -609,32 +609,32 @@ describe('/recovery_email/verify_code', function () { name: 'my device', id: '123456789', type: 'desktop' - }) - } + }); + }; return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens') - assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail') - assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices') - assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called') - assert.equal(mockPush.notifyAccountUpdated.callCount, 0, 'mockPush.notifyAccountUpdated should not have been called') - assert.equal(mockPush.notifyDeviceConnected.callCount, 1, 'mockPush.notifyDeviceConnected should have been called') + assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens'); + assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail'); + assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices'); + assert.equal(mockLog.activityEvent.callCount, 0, 'log.activityEvent was not called'); + assert.equal(mockPush.notifyAccountUpdated.callCount, 0, 'mockPush.notifyAccountUpdated should not have been called'); + assert.equal(mockPush.notifyDeviceConnected.callCount, 1, 'mockPush.notifyDeviceConnected should have been called'); }) .then(function () { - mockDB.verifyTokens.resetHistory() - }) - }) + mockDB.verifyTokens.resetHistory(); + }); + }); it('sign-in confirmation', function () { - dbData.emailCode = crypto.randomBytes(16) + dbData.emailCode = crypto.randomBytes(16); return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens') - assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail') - assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices') + assert.equal(mockDB.verifyTokens.callCount, 1, 'call db.verifyTokens'); + assert.equal(mockDB.verifyEmail.callCount, 0, 'does not call db.verifyEmail'); + assert.equal(mockLog.notifyAttachedServices.callCount, 0, 'does not call log.notifyAttachedServices'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - var args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + var args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.confirmed', @@ -642,60 +642,60 @@ describe('/recovery_email/verify_code', function () { service: 'sync', userAgent: 'test user-agent', uid: uid.toString('hex') - }, 'event data was correct') + }, 'event data was correct'); - assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once') - args = mockPush.notifyAccountUpdated.args[0] - assert.equal(args.length, 3, 'mockPush.notifyAccountUpdated should have been passed three arguments') - assert.equal(args[0].toString('hex'), uid, 'first argument should have been uid') - assert.ok(Array.isArray(args[1]), 'second argument should have been devices array') - assert.equal(args[2], 'accountConfirm', 'third argument should have been reason') + assert.equal(mockPush.notifyAccountUpdated.callCount, 1, 'mockPush.notifyAccountUpdated should have been called once'); + args = mockPush.notifyAccountUpdated.args[0]; + assert.equal(args.length, 3, 'mockPush.notifyAccountUpdated should have been passed three arguments'); + assert.equal(args[0].toString('hex'), uid, 'first argument should have been uid'); + assert.ok(Array.isArray(args[1]), 'second argument should have been devices array'); + assert.equal(args[2], 'accountConfirm', 'third argument should have been reason'); }) .then(function () { - mockDB.verifyTokens.resetHistory() - mockLog.activityEvent.resetHistory() - mockPush.notifyAccountUpdated.resetHistory() - }) - }) + mockDB.verifyTokens.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockPush.notifyAccountUpdated.resetHistory(); + }); + }); it('secondary email verification', function () { - dbData.emailCode = crypto.randomBytes(16).toString('hex') - mockRequest.payload.code = dbData.secondEmailCode.toString('hex') - mockRequest.payload.type = 'secondary' - mockRequest.payload.verifiedEmail = dbData.secondEmail + dbData.emailCode = crypto.randomBytes(16).toString('hex'); + mockRequest.payload.code = dbData.secondEmailCode.toString('hex'); + mockRequest.payload.type = 'secondary'; + mockRequest.payload.verifiedEmail = dbData.secondEmail; return runTest(route, mockRequest, function (response) { - assert.equal(mockDB.verifyEmail.callCount, 1, 'call db.verifyEmail') - var args = mockDB.verifyEmail.args[0] - assert.equal(args.length, 2, 'mockDB.verifyEmail was passed correct arguments') - assert.equal(args[0].email, dbData.email, 'correct account primary email was passed') - assert.equal(args[1].toString('hex'), dbData.secondEmailCode.toString('hex'), 'correct email code was passed') + assert.equal(mockDB.verifyEmail.callCount, 1, 'call db.verifyEmail'); + var args = mockDB.verifyEmail.args[0]; + assert.equal(args.length, 2, 'mockDB.verifyEmail was passed correct arguments'); + assert.equal(args[0].email, dbData.email, 'correct account primary email was passed'); + assert.equal(args[1].toString('hex'), dbData.secondEmailCode.toString('hex'), 'correct email code was passed'); - assert.equal(mockMailer.sendPostVerifySecondaryEmail.callCount, 1, 'call mailer.sendPostVerifySecondaryEmail') - args = mockMailer.sendPostVerifySecondaryEmail.args[0] - assert.equal(args.length, 3, 'mockMailer.sendPostVerifySecondaryEmail was passed correct arguments') - assert.equal(args[1].email, dbData.email, 'correct account primary email was passed') - assert.equal(args[2].secondaryEmail, dbData.secondEmail, 'correct secondary email was passed') - assert.equal(args[2].service, mockRequest.payload.service) - assert.equal(args[2].uid, uid) + assert.equal(mockMailer.sendPostVerifySecondaryEmail.callCount, 1, 'call mailer.sendPostVerifySecondaryEmail'); + args = mockMailer.sendPostVerifySecondaryEmail.args[0]; + assert.equal(args.length, 3, 'mockMailer.sendPostVerifySecondaryEmail was passed correct arguments'); + assert.equal(args[1].email, dbData.email, 'correct account primary email was passed'); + assert.equal(args[2].secondaryEmail, dbData.secondEmail, 'correct secondary email was passed'); + assert.equal(args[2].service, mockRequest.payload.service); + assert.equal(args[2].uid, uid); }) .then(function () { - mockDB.verifyEmail.resetHistory() - mockLog.activityEvent.resetHistory() - mockMailer.sendPostVerifySecondaryEmail.resetHistory() - mockPush.notifyAccountUpdated.resetHistory() - }) - }) - }) -}) + mockDB.verifyEmail.resetHistory(); + mockLog.activityEvent.resetHistory(); + mockMailer.sendPostVerifySecondaryEmail.resetHistory(); + mockPush.notifyAccountUpdated.resetHistory(); + }); + }); + }); +}); describe('/recovery_email', () => { - const uid = uuid.v4('binary').toString('hex') - const mockLog = mocks.mockLog() - let dbData, accountRoutes, mockDB, mockRequest, route - const mockMailer = mocks.mockMailer() - const mockPush = mocks.mockPush() - const mockCustoms = mocks.mockCustoms() + const uid = uuid.v4('binary').toString('hex'); + const mockLog = mocks.mockLog(); + let dbData, accountRoutes, mockDB, mockRequest, route; + const mockMailer = mocks.mockMailer(); + const mockPush = mocks.mockPush(); + const mockCustoms = mocks.mockCustoms(); beforeEach(() => { mockRequest = mocks.mockRequest({ @@ -710,16 +710,16 @@ describe('/recovery_email', () => { payload: { email: TEST_EMAIL_ADDITIONAL } - }) + }); dbData = { email: TEST_EMAIL, uid: uid, secondEmail: TEST_EMAIL_ADDITIONAL - } - mockDB = mocks.mockDB(dbData) + }; + mockDB = mocks.mockDB(dbData); accountRoutes = makeRoutes({ checkPassword: function () { - return P.resolve(true) + return P.resolve(true); }, config: { secondaryEmail: { @@ -731,53 +731,53 @@ describe('/recovery_email', () => { log: mockLog, mailer: mockMailer, push: mockPush - }) - }) + }); + }); describe('/recovery_email', () => { beforeEach(() => { mockDB.getSecondaryEmail = sinon.spy(() => { - return P.reject(error.unknownSecondaryEmail()) - }) - }) + return P.reject(error.unknownSecondaryEmail()); + }); + }); it('should create email on account', () => { - route = getRoute(accountRoutes, '/recovery_email') + route = getRoute(accountRoutes, '/recovery_email'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail') - assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call db.sendVerifySecondaryEmail') - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid) + assert.ok(response); + assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail'); + assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call db.sendVerifySecondaryEmail'); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid); }) .then(function () { - mockDB.createEmail.resetHistory() - mockMailer.sendVerifySecondaryEmail.resetHistory() - }) - }) + mockDB.createEmail.resetHistory(); + mockMailer.sendVerifySecondaryEmail.resetHistory(); + }); + }); it('should fail with unverified primary email', () => { - route = getRoute(accountRoutes, '/recovery_email') - mockRequest.auth.credentials.emailVerified = false + route = getRoute(accountRoutes, '/recovery_email'); + mockRequest.auth.credentials.emailVerified = false; return runTest(route, mockRequest).then( () => assert.fail('Should have failed adding secondary email with unverified primary email'), err => assert.equal(err.errno, 104, 'unverified account')) .then(function () { - mockDB.createEmail.resetHistory() - }) - }) + mockDB.createEmail.resetHistory(); + }); + }); it('should fail when adding secondary email that is same as primary', () => { - route = getRoute(accountRoutes, '/recovery_email') - mockRequest.payload.email = TEST_EMAIL + route = getRoute(accountRoutes, '/recovery_email'); + mockRequest.payload.email = TEST_EMAIL; return runTest(route, mockRequest).then( () => assert.fail('Should have failed when adding secondary email that is same as primary'), err => assert.equal(err.errno, 139, 'cannot add secondary email, same as primary')) .then(function () { - mockDB.createEmail.resetHistory() - }) - }) + mockDB.createEmail.resetHistory(); + }); + }); it('creates secondary email if another user unverified primary more than day old, deletes unverified account', () => { mockDB.getSecondaryEmail = sinon.spy(() => { @@ -787,30 +787,30 @@ describe('/recovery_email', () => { normalizedEmail: TEST_EMAIL, createdAt: Date.now() - MS_IN_DAY, uid: crypto.randomBytes(16) - }) - }) - route = getRoute(accountRoutes, '/recovery_email') - mockRequest.payload.email = TEST_EMAIL_ADDITIONAL + }); + }); + route = getRoute(accountRoutes, '/recovery_email'); + mockRequest.payload.email = TEST_EMAIL_ADDITIONAL; return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.deleteAccount.callCount, 1, 'call db.deleteAccount') - assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail') - let args = mockDB.createEmail.getCall(0).args - assert.equal(args[1].email, TEST_EMAIL_ADDITIONAL, 'call db.createEmail with correct email') - assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call mailer.sendVerifySecondaryEmail') + assert.ok(response); + assert.equal(mockDB.deleteAccount.callCount, 1, 'call db.deleteAccount'); + assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail'); + let args = mockDB.createEmail.getCall(0).args; + assert.equal(args[1].email, TEST_EMAIL_ADDITIONAL, 'call db.createEmail with correct email'); + assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call mailer.sendVerifySecondaryEmail'); - assert.equal(mockLog.info.callCount, 1) - args = mockLog.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'accountDeleted.unverifiedSecondaryEmail') - assert.equal(args[1].normalizedEmail, TEST_EMAIL) + assert.equal(mockLog.info.callCount, 1); + args = mockLog.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'accountDeleted.unverifiedSecondaryEmail'); + assert.equal(args[1].normalizedEmail, TEST_EMAIL); }) .then(function () { - mockDB.deleteAccount.resetHistory() - mockDB.createEmail.resetHistory() - mockMailer.sendVerifySecondaryEmail.resetHistory() - }) - }) + mockDB.deleteAccount.resetHistory(); + mockDB.createEmail.resetHistory(); + mockMailer.sendVerifySecondaryEmail.resetHistory(); + }); + }); it('fails create email if another user unverified primary less than day old', () => { mockDB.getSecondaryEmail = sinon.spy(() => { @@ -820,80 +820,80 @@ describe('/recovery_email', () => { normalizedEmail: TEST_EMAIL, createdAt: Date.now(), uid: crypto.randomBytes(16) - }) - }) - route = getRoute(accountRoutes, '/recovery_email') - mockRequest.payload.email = TEST_EMAIL_ADDITIONAL + }); + }); + route = getRoute(accountRoutes, '/recovery_email'); + mockRequest.payload.email = TEST_EMAIL_ADDITIONAL; return runTest(route, mockRequest).then( () => assert.fail('Should have failed when creating email'), - err => assert.equal(err.errno, 141, 'cannot add secondary email, newly created primary account')) - }) + err => assert.equal(err.errno, 141, 'cannot add secondary email, newly created primary account')); + }); it('deletes secondary email if there was an error sending verification email', () => { - route = getRoute(accountRoutes, '/recovery_email') + route = getRoute(accountRoutes, '/recovery_email'); mockMailer.sendVerifySecondaryEmail = sinon.spy(() => { - return P.reject(new Error('failed to send')) - }) + return P.reject(new Error('failed to send')); + }); return runTest(route, mockRequest, () => { - assert.fail('should have failed') + assert.fail('should have failed'); }) .catch((err) => { - assert.equal(err.errno, 151, 'failed to send email error') - assert.equal(err.output.payload.code, 422) - assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail') - assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail') - assert.equal(mockDB.deleteEmail.args[0][0], mockRequest.auth.credentials.uid, 'correct uid passed') - assert.equal(mockDB.deleteEmail.args[0][1], TEST_EMAIL_ADDITIONAL, 'correct email passed') - assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call db.sendVerifySecondaryEmail') - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId) - assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid) + assert.equal(err.errno, 151, 'failed to send email error'); + assert.equal(err.output.payload.code, 422); + assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail'); + assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail'); + assert.equal(mockDB.deleteEmail.args[0][0], mockRequest.auth.credentials.uid, 'correct uid passed'); + assert.equal(mockDB.deleteEmail.args[0][1], TEST_EMAIL_ADDITIONAL, 'correct email passed'); + assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call db.sendVerifySecondaryEmail'); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId); + assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid); }) .then(() => { - mockDB.createEmail.resetHistory() - mockDB.deleteEmail.resetHistory() - mockMailer.sendVerifySecondaryEmail.resetHistory() - }) - }) - }) + mockDB.createEmail.resetHistory(); + mockDB.deleteEmail.resetHistory(); + mockMailer.sendVerifySecondaryEmail.resetHistory(); + }); + }); + }); describe('/recovery_emails', () => { it('should get all account emails', () => { - route = getRoute(accountRoutes, '/recovery_emails') + route = getRoute(accountRoutes, '/recovery_emails'); return runTest(route, mockRequest, (response) => { - assert.equal(response.length, 1, 'should return account email') - assert.equal(response[0].email, dbData.email, 'should return users email') - assert.equal(mockDB.account.callCount, 1, 'call db.account') + assert.equal(response.length, 1, 'should return account email'); + assert.equal(response[0].email, dbData.email, 'should return users email'); + assert.equal(mockDB.account.callCount, 1, 'call db.account'); }) .then(function () { - mockDB.accountEmails.resetHistory() - }) - }) - }) + mockDB.accountEmails.resetHistory(); + }); + }); + }); describe('/recovery_email/destroy', () => { it('should delete email from account ', () => { - route = getRoute(accountRoutes, '/recovery_email/destroy') + route = getRoute(accountRoutes, '/recovery_email/destroy'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail') - }) - }) + assert.ok(response); + assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail'); + }); + }); it('should reset outstanding tokens on the account ', () => { - route = getRoute(accountRoutes, '/recovery_email/destroy') + route = getRoute(accountRoutes, '/recovery_email/destroy'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.resetAccountTokens.callCount, 1, 'call db.resetAccountTokens') - }) - }) + assert.ok(response); + assert.equal(mockDB.resetAccountTokens.callCount, 1, 'call db.resetAccountTokens'); + }); + }); it('should send secondary email post delete notification, if email is verified', () => { - const tempEmail = 'anotherEmail@one.com' + const tempEmail = 'anotherEmail@one.com'; mockRequest.payload = { email: tempEmail - } + }; mockDB.account = sinon.spy(() => { return P.resolve({ uid: mockRequest.auth.credentials.uid, @@ -901,21 +901,21 @@ describe('/recovery_email', () => { isPrimary: false, emails: [{normalizedEmail: TEST_EMAIL, email: TEST_EMAIL, isVerified: true, isPrimary: true}, {normalizedEmail: tempEmail.toLowerCase(), email: tempEmail, isVerified: true, isPrimary: false}], - }) - }) - route = getRoute(accountRoutes, '/recovery_email/destroy') + }); + }); + route = getRoute(accountRoutes, '/recovery_email/destroy'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail') - assert.equal(mockMailer.sendPostRemoveSecondaryEmail.callCount, 1, 'call mailer.sendVerifySecondaryEmail') - }) - }) + assert.ok(response); + assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail'); + assert.equal(mockMailer.sendPostRemoveSecondaryEmail.callCount, 1, 'call mailer.sendVerifySecondaryEmail'); + }); + }); it('shouldn\'t send secondary email post delete notification, if email is unverified', () => { - const tempEmail = 'anotherEmail@one.com' + const tempEmail = 'anotherEmail@one.com'; mockRequest.payload = { email: tempEmail - } + }; mockDB.account = sinon.spy(() => { return P.resolve({ uid: mockRequest.auth.credentials.uid, @@ -923,21 +923,21 @@ describe('/recovery_email', () => { isPrimary: false, emails: [{normalizedEmail: TEST_EMAIL, email: TEST_EMAIL, isVerified: true, isPrimary: true}, {normalizedEmail: tempEmail, email: tempEmail, isVerified: false, isPrimary: false}], - }) - }) - route = getRoute(accountRoutes, '/recovery_email/destroy') + }); + }); + route = getRoute(accountRoutes, '/recovery_email/destroy'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail') - assert.equal(mockMailer.sendPostRemoveSecondaryEmail.callCount, 0, 'shouldn\'t call mailer.sendVerifySecondaryEmail') - }) - }) + assert.ok(response); + assert.equal(mockDB.deleteEmail.callCount, 1, 'call db.deleteEmail'); + assert.equal(mockMailer.sendPostRemoveSecondaryEmail.callCount, 0, 'shouldn\'t call mailer.sendVerifySecondaryEmail'); + }); + }); afterEach(() => { - mockDB.deleteEmail.resetHistory() - mockMailer.sendPostRemoveSecondaryEmail.resetHistory() - }) - }) + mockDB.deleteEmail.resetHistory(); + mockMailer.sendPostRemoveSecondaryEmail.resetHistory(); + }); + }); describe('/recovery_email/set_primary', () => { @@ -947,30 +947,30 @@ describe('/recovery_email', () => { uid: mockRequest.auth.credentials.uid, isVerified: true, isPrimary: false - }) - }) + }); + }); - route = getRoute(accountRoutes, '/recovery_email/set_primary') + route = getRoute(accountRoutes, '/recovery_email/set_primary'); return runTest(route, mockRequest, (response) => { - assert.ok(response) - assert.equal(mockDB.setPrimaryEmail.callCount, 1, 'call db.setPrimaryEmail') - assert.equal(mockPush.notifyProfileUpdated.callCount, 1, 'call db.notifyProfileUpdated') - assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'call db.notifyAttachedServices') - assert.equal(mockMailer.sendPostChangePrimaryEmail.callCount, 1, 'call db.sendPostChangePrimaryEmail') + assert.ok(response); + assert.equal(mockDB.setPrimaryEmail.callCount, 1, 'call db.setPrimaryEmail'); + assert.equal(mockPush.notifyProfileUpdated.callCount, 1, 'call db.notifyProfileUpdated'); + assert.equal(mockLog.notifyAttachedServices.callCount, 1, 'call db.notifyAttachedServices'); + assert.equal(mockMailer.sendPostChangePrimaryEmail.callCount, 1, 'call db.sendPostChangePrimaryEmail'); - const args = mockLog.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'primaryEmailChanged', 'first argument was event name') - assert.equal(args[1], mockRequest, 'second argument was request object') - assert.equal(args[2].uid, mockRequest.auth.credentials.uid, 'third argument was event data with a uid') - assert.equal(args[2].email, TEST_EMAIL_ADDITIONAL, 'third argument was event data with new email') + const args = mockLog.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'primaryEmailChanged', 'first argument was event name'); + assert.equal(args[1], mockRequest, 'second argument was request object'); + assert.equal(args[2].uid, mockRequest.auth.credentials.uid, 'third argument was event data with a uid'); + assert.equal(args[2].email, TEST_EMAIL_ADDITIONAL, 'third argument was event data with new email'); }) .then(function () { - mockDB.setPrimaryEmail.resetHistory() - mockPush.notifyProfileUpdated.resetHistory() - mockMailer.sendPostChangePrimaryEmail.resetHistory() - }) - }) + mockDB.setPrimaryEmail.resetHistory(); + mockPush.notifyProfileUpdated.resetHistory(); + mockMailer.sendPostChangePrimaryEmail.resetHistory(); + }); + }); it('should fail when setting email to email user does not own', () => { mockDB.getSecondaryEmail = sinon.spy(() => { @@ -978,14 +978,14 @@ describe('/recovery_email', () => { uid: uuid.v4('binary').toString('hex'), isVerified: true, isPrimary: false - }) - }) + }); + }); - route = getRoute(accountRoutes, '/recovery_email/set_primary') + route = getRoute(accountRoutes, '/recovery_email/set_primary'); return runTest(route, mockRequest).then( () => assert.fail('should have errored'), - err => assert.equal(err.errno, 148, 'correct errno changing email to non account email')) - }) + err => assert.equal(err.errno, 148, 'correct errno changing email to non account email')); + }); it('should fail when setting email is unverified', () => { mockDB.getSecondaryEmail = sinon.spy(() => { @@ -993,13 +993,13 @@ describe('/recovery_email', () => { uid: mockRequest.auth.credentials.uid, isVerified: false, isPrimary: false - }) - }) + }); + }); - route = getRoute(accountRoutes, '/recovery_email/set_primary') + route = getRoute(accountRoutes, '/recovery_email/set_primary'); return runTest(route, mockRequest).then( () => assert.fail('should have errored'), - err => assert.equal(err.errno, 147, 'correct errno changing email to unverified email')) - }) - }) -}) + err => assert.equal(err.errno, 147, 'correct errno changing email to unverified email')); + }); + }); +}); diff --git a/test/local/routes/oauth.js b/test/local/routes/oauth.js index 283a8029..f959f64a 100644 --- a/test/local/routes/oauth.js +++ b/test/local/routes/oauth.js @@ -2,104 +2,104 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } -const uuid = require('uuid') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; +const uuid = require('uuid'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); -const MOCK_CLIENT_ID = '01234567890ABCDEF' -const MOCK_SCOPES = 'mock-scope another-scope' +const MOCK_CLIENT_ID = '01234567890ABCDEF'; +const MOCK_SCOPES = 'mock-scope another-scope'; describe('/oauth/ routes', () => { - let mockOAuthDB, mockLog, sessionToken + let mockOAuthDB, mockLog, sessionToken; async function loadAndCallRoute(path, request) { - const routes = require('../../../lib/routes/oauth')(mockLog, {}, mockOAuthDB) - const response = await getRoute(routes, path).handler(request) + const routes = require('../../../lib/routes/oauth')(mockLog, {}, mockOAuthDB); + const response = await getRoute(routes, path).handler(request); if (response instanceof Error) { - throw response + throw response; } - return response + return response; } beforeEach(() => { - mockLog = mocks.mockLog() - }) + mockLog = mocks.mockLog(); + }); describe('/oauth/client/{client_id}', () => { it('calls oauthdb.getClientInfo', async () => { mockOAuthDB = mocks.mockOAuthDB({ getClientInfo: sinon.spy(async () => { - return { id: MOCK_CLIENT_ID, name: 'mock client' } + return { id: MOCK_CLIENT_ID, name: 'mock client' }; }) - }) + }); const mockRequest = mocks.mockRequest({ params: { client_id: MOCK_CLIENT_ID } - }) - const resp = await loadAndCallRoute('/oauth/client/{client_id}', mockRequest) - assert.calledOnce(mockOAuthDB.getClientInfo) - assert.calledWithExactly(mockOAuthDB.getClientInfo, MOCK_CLIENT_ID) - assert.equal(resp.id, MOCK_CLIENT_ID) - assert.equal(resp.name, 'mock client') - }) + }); + const resp = await loadAndCallRoute('/oauth/client/{client_id}', mockRequest); + assert.calledOnce(mockOAuthDB.getClientInfo); + assert.calledWithExactly(mockOAuthDB.getClientInfo, MOCK_CLIENT_ID); + assert.equal(resp.id, MOCK_CLIENT_ID); + assert.equal(resp.name, 'mock client'); + }); - }) + }); describe('/account/scoped-key-data', () => { it('calls oauthdb.getScopedKeyData', async () => { mockOAuthDB = mocks.mockOAuthDB({ getScopedKeyData: sinon.spy(async () => { - return { key: 'data' } + return { key: 'data' }; }) - }) - const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog) + }); + const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog); const SessionToken = require(`${ROOT_DIR}/lib/tokens/session_token`)(mockLog, Token, { tokenLifetimes: { sessionTokenWithoutDevice: 2419200000 } - }) + }); sessionToken = await SessionToken.create({ uid: uuid.v4('binary').toString('hex'), email: 'foo@example.com', emailVerified: true, - }) + }); const mockRequest = mocks.mockRequest({ credentials: sessionToken, payload: { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES } - }) - const resp = await loadAndCallRoute('/account/scoped-key-data', mockRequest) - assert.calledOnce(mockOAuthDB.getScopedKeyData) + }); + const resp = await loadAndCallRoute('/account/scoped-key-data', mockRequest); + assert.calledOnce(mockOAuthDB.getScopedKeyData); assert.calledWithExactly(mockOAuthDB.getScopedKeyData, sessionToken, { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES - }) - assert.deepEqual(resp, { key: 'data' }) - }) + }); + assert.deepEqual(resp, { key: 'data' }); + }); it('rejects an `assertion` parameter in the request payload', async () => { - const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog) + const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog); const SessionToken = require(`${ROOT_DIR}/lib/tokens/session_token`)(mockLog, Token, { tokenLifetimes: { sessionTokenWithoutDevice: 2419200000 } - }) + }); sessionToken = await SessionToken.create({ uid: uuid.v4('binary').toString('hex'), email: 'foo@example.com', emailVerified: true, - }) + }); const mockRequest = mocks.mockRequest({ credentials: sessionToken, payload: { @@ -107,11 +107,11 @@ describe('/oauth/ routes', () => { client_id: MOCK_CLIENT_ID, scope: MOCK_SCOPES } - }) - const resp = await loadAndCallRoute('/account/scoped-key-data', mockRequest) - assert.deepEqual(resp, { key: 'data' }) - }) + }); + const resp = await loadAndCallRoute('/account/scoped-key-data', mockRequest); + assert.deepEqual(resp, { key: 'data' }); + }); - }) + }); -}) +}); diff --git a/test/local/routes/password.js b/test/local/routes/password.js index 4b784255..e555eb2c 100644 --- a/test/local/routes/password.js +++ b/test/local/routes/password.js @@ -2,34 +2,34 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var mocks = require('../../mocks') -var getRoute = require('../../routes_helpers').getRoute +const { assert } = require('chai'); +var mocks = require('../../mocks'); +var getRoute = require('../../routes_helpers').getRoute; -var P = require('../../../lib/promise') -var uuid = require('uuid') -var crypto = require('crypto') -var error = require('../../../lib/error') -const sinon = require('sinon') -const log = require('../../../lib/log') +var P = require('../../../lib/promise'); +var uuid = require('uuid'); +var crypto = require('crypto'); +var error = require('../../../lib/error'); +const sinon = require('sinon'); +const log = require('../../../lib/log'); -var TEST_EMAIL = 'foo@gmail.com' +var TEST_EMAIL = 'foo@gmail.com'; function makeRoutes(options = {}) { const config = options.config || { verifierVersion: 0, smtp: {} - } - const log = options.log || mocks.mockLog() - const db = options.db || {} - const mailer = options.mailer || {} - const Password = require('../../../lib/crypto/password')(log, config) - const customs = options.customs || {} - const signinUtils = require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer) - config.secondaryEmail = config.secondaryEmail || {} + }; + const log = options.log || mocks.mockLog(); + const db = options.db || {}; + const mailer = options.mailer || {}; + const Password = require('../../../lib/crypto/password')(log, config); + const customs = options.customs || {}; + const signinUtils = require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer); + config.secondaryEmail = config.secondaryEmail || {}; return require('../../../lib/routes/password')( log, db, @@ -41,26 +41,26 @@ function makeRoutes(options = {}) { signinUtils, options.push || {}, config - ) + ); } function runRoute(routes, name, request) { - return getRoute(routes, name).handler(request) + return getRoute(routes, name).handler(request); } describe('/password', () => { it('/forgot/send_code', () => { - const mockCustoms = mocks.mockCustoms() - const uid = uuid.v4('binary').toString('hex') - const passwordForgotTokenId = crypto.randomBytes(16).toString('hex') + const mockCustoms = mocks.mockCustoms(); + const uid = uuid.v4('binary').toString('hex'); + const passwordForgotTokenId = crypto.randomBytes(16).toString('hex'); const mockDB = mocks.mockDB({ email: TEST_EMAIL, passCode: 'foo', passwordForgotTokenId, uid - }) - const mockMailer = mocks.mockMailer() - const mockMetricsContext = mocks.mockMetricsContext() + }); + const mockMailer = mocks.mockMailer(); + const mockMetricsContext = mocks.mockMetricsContext(); const mockLog = log('ERROR', 'test', { stdout: { on: sinon.spy(), @@ -70,17 +70,17 @@ describe('/password', () => { on: sinon.spy(), write: sinon.spy() } - }) + }); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) + return P.resolve(); + }); const passwordRoutes = makeRoutes({ customs: mockCustoms, db: mockDB, mailer : mockMailer, metricsContext: mockMetricsContext, log: mockLog - }) + }); const mockRequest = mocks.mockRequest({ log: mockLog, @@ -94,52 +94,52 @@ describe('/password', () => { }, query: {}, metricsContext: mockMetricsContext - }) + }); return runRoute(passwordRoutes, '/password/forgot/send_code', mockRequest) .then(response => { - assert.equal(mockDB.accountRecord.callCount, 1, 'db.emailRecord was called once') + assert.equal(mockDB.accountRecord.callCount, 1, 'db.emailRecord was called once'); - assert.equal(mockDB.createPasswordForgotToken.callCount, 1, 'db.createPasswordForgotToken was called once') - let args = mockDB.createPasswordForgotToken.args[0] - assert.equal(args.length, 1, 'db.createPasswordForgotToken was passed one argument') - assert.deepEqual(args[0].uid, uid, 'db.createPasswordForgotToken was passed the correct uid') - assert.equal(args[0].createdAt, undefined, 'db.createPasswordForgotToken was not passed a createdAt timestamp') + assert.equal(mockDB.createPasswordForgotToken.callCount, 1, 'db.createPasswordForgotToken was called once'); + let args = mockDB.createPasswordForgotToken.args[0]; + assert.equal(args.length, 1, 'db.createPasswordForgotToken was passed one argument'); + assert.deepEqual(args[0].uid, uid, 'db.createPasswordForgotToken was passed the correct uid'); + assert.equal(args[0].createdAt, undefined, 'db.createPasswordForgotToken was not passed a createdAt timestamp'); - assert.equal(mockRequest.validateMetricsContext.callCount, 1, 'validateMetricsContext was called') + assert.equal(mockRequest.validateMetricsContext.callCount, 1, 'validateMetricsContext was called'); - assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1) - args = mockMetricsContext.setFlowCompleteSignal.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0], 'account.reset') + assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1); + args = mockMetricsContext.setFlowCompleteSignal.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0], 'account.reset'); - assert.equal(mockMetricsContext.stash.callCount, 1) - args = mockMetricsContext.stash.args[0] - assert.lengthOf(args, 1) - assert.equal(args[0].id, passwordForgotTokenId) - assert.equal(args[0].uid, uid) + assert.equal(mockMetricsContext.stash.callCount, 1); + args = mockMetricsContext.stash.args[0]; + assert.lengthOf(args, 1); + assert.equal(args[0].id, passwordForgotTokenId); + assert.equal(args[0].uid, uid); - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.send_code.start', 'password.forgot.send_code.start event was logged') - assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.send_code.completed', 'password.forgot.send_code.completed event was logged') + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.send_code.start', 'password.forgot.send_code.start event was logged'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.send_code.completed', 'password.forgot.send_code.completed event was logged'); - assert.equal(mockMailer.sendRecoveryCode.callCount, 1, 'mailer.sendRecoveryCode was called once') - args = mockMailer.sendRecoveryCode.args[0] - assert.equal(args[2].location.city, 'Mountain View') - assert.equal(args[2].location.country, 'United States') - assert.equal(args[2].timeZone, 'America/Los_Angeles') - assert.equal(args[2].uid, uid) - assert.equal(args[2].deviceId, 'wibble') - }) - }) + assert.equal(mockMailer.sendRecoveryCode.callCount, 1, 'mailer.sendRecoveryCode was called once'); + args = mockMailer.sendRecoveryCode.args[0]; + assert.equal(args[2].location.city, 'Mountain View'); + assert.equal(args[2].location.country, 'United States'); + assert.equal(args[2].timeZone, 'America/Los_Angeles'); + assert.equal(args[2].uid, uid); + assert.equal(args[2].deviceId, 'wibble'); + }); + }); it( '/forgot/resend_code', () => { - var mockCustoms = mocks.mockCustoms() - var uid = uuid.v4('binary').toString('hex') - var mockDB = mocks.mockDB() - var mockMailer = mocks.mockMailer() - var mockMetricsContext = mocks.mockMetricsContext() + var mockCustoms = mocks.mockCustoms(); + var uid = uuid.v4('binary').toString('hex'); + var mockDB = mocks.mockDB(); + var mockMailer = mocks.mockMailer(); + var mockMetricsContext = mocks.mockMetricsContext(); var mockLog = log('ERROR', 'test', { stdout: { on: sinon.spy(), @@ -149,24 +149,24 @@ describe('/password', () => { on: sinon.spy(), write: sinon.spy() } - }) + }); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) + return P.resolve(); + }); var passwordRoutes = makeRoutes({ customs: mockCustoms, db: mockDB, mailer : mockMailer, metricsContext: mockMetricsContext, log: mockLog - }) + }); var mockRequest = mocks.mockRequest({ credentials: { data: crypto.randomBytes(16).toString('hex'), email: TEST_EMAIL, passCode: Buffer.from('abcdef', 'hex'), - ttl: function () { return 17 }, + ttl: function () { return 17; }, uid: uid }, log: mockLog, @@ -180,39 +180,39 @@ describe('/password', () => { } }, query: {} - }) + }); return runRoute(passwordRoutes, '/password/forgot/resend_code', mockRequest) .then(function(response) { - assert.equal(mockMailer.sendRecoveryCode.callCount, 1, 'mailer.sendRecoveryCode was called once') - assert.equal(mockMailer.sendRecoveryCode.args[0][2].uid, uid) - assert.equal(mockMailer.sendRecoveryCode.args[0][2].deviceId, 'wibble') + assert.equal(mockMailer.sendRecoveryCode.callCount, 1, 'mailer.sendRecoveryCode was called once'); + assert.equal(mockMailer.sendRecoveryCode.args[0][2].uid, uid); + assert.equal(mockMailer.sendRecoveryCode.args[0][2].deviceId, 'wibble'); - assert.equal(mockRequest.validateMetricsContext.callCount, 0) - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.resend_code.start', 'password.forgot.resend_code.start event was logged') - assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.resend_code.completed', 'password.forgot.resend_code.completed event was logged') - }) + assert.equal(mockRequest.validateMetricsContext.callCount, 0); + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.resend_code.start', 'password.forgot.resend_code.start event was logged'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.resend_code.completed', 'password.forgot.resend_code.completed event was logged'); + }); } - ) + ); it('/forgot/verify_code', () => { - const mockCustoms = mocks.mockCustoms() - const uid = uuid.v4('binary').toString('hex') + const mockCustoms = mocks.mockCustoms(); + const uid = uuid.v4('binary').toString('hex'); const accountResetToken = { data: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex'), uid - } - const passwordForgotTokenId = crypto.randomBytes(16).toString('hex') + }; + const passwordForgotTokenId = crypto.randomBytes(16).toString('hex'); const mockDB = mocks.mockDB({ accountResetToken: accountResetToken, email: TEST_EMAIL, passCode: 'abcdef', passwordForgotTokenId, uid - }) - const mockMailer = mocks.mockMailer() - const mockMetricsContext = mocks.mockMetricsContext() + }); + const mockMailer = mocks.mockMailer(); + const mockMetricsContext = mocks.mockMetricsContext(); const mockLog = log('ERROR', 'test', { stdout: { on: sinon.spy(), @@ -222,16 +222,16 @@ describe('/password', () => { on: sinon.spy(), write: sinon.spy() } - }) + }); mockLog.flowEvent = sinon.spy(() => { - return P.resolve() - }) + return P.resolve(); + }); const passwordRoutes = makeRoutes({ customs: mockCustoms, db: mockDB, mailer: mockMailer, metricsContext: mockMetricsContext - }) + }); const mockRequest = mocks.mockRequest({ log: mockLog, @@ -239,7 +239,7 @@ describe('/password', () => { email: TEST_EMAIL, id: passwordForgotTokenId, passCode: Buffer.from('abcdef', 'hex'), - ttl: function () { return 17 }, + ttl: function () { return 17; }, uid }, metricsContext: mockMetricsContext, @@ -252,56 +252,56 @@ describe('/password', () => { } }, query: {} - }) + }); return runRoute(passwordRoutes, '/password/forgot/verify_code', mockRequest) .then(response => { - assert.deepEqual(Object.keys(response), ['accountResetToken'], 'an accountResetToken was returned') - assert.equal(response.accountResetToken, accountResetToken.data.toString('hex'), 'correct accountResetToken was returned') + assert.deepEqual(Object.keys(response), ['accountResetToken'], 'an accountResetToken was returned'); + assert.equal(response.accountResetToken, accountResetToken.data.toString('hex'), 'correct accountResetToken was returned'); - assert.equal(mockCustoms.check.callCount, 1, 'customs.check was called once') + assert.equal(mockCustoms.check.callCount, 1, 'customs.check was called once'); - assert.equal(mockDB.forgotPasswordVerified.callCount, 1, 'db.passwordForgotVerified was called once') - let args = mockDB.forgotPasswordVerified.args[0] - assert.equal(args.length, 1, 'db.passwordForgotVerified was passed one argument') - assert.deepEqual(args[0].uid, uid, 'db.forgotPasswordVerified was passed the correct token') + assert.equal(mockDB.forgotPasswordVerified.callCount, 1, 'db.passwordForgotVerified was called once'); + let args = mockDB.forgotPasswordVerified.args[0]; + assert.equal(args.length, 1, 'db.passwordForgotVerified was passed one argument'); + assert.deepEqual(args[0].uid, uid, 'db.forgotPasswordVerified was passed the correct token'); - assert.equal(mockRequest.validateMetricsContext.callCount, 0) - assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice') - assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.verify_code.start', 'password.forgot.verify_code.start event was logged') - assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.verify_code.completed', 'password.forgot.verify_code.completed event was logged') + assert.equal(mockRequest.validateMetricsContext.callCount, 0); + assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.verify_code.start', 'password.forgot.verify_code.start event was logged'); + assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.verify_code.completed', 'password.forgot.verify_code.completed event was logged'); - assert.equal(mockMetricsContext.propagate.callCount, 1) - args = mockMetricsContext.propagate.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0].id, passwordForgotTokenId) - assert.equal(args[0].uid, uid) - assert.equal(args[1].id, accountResetToken.id) - assert.equal(args[1].uid, uid) + assert.equal(mockMetricsContext.propagate.callCount, 1); + args = mockMetricsContext.propagate.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0].id, passwordForgotTokenId); + assert.equal(args[0].uid, uid); + assert.equal(args[1].id, accountResetToken.id); + assert.equal(args[1].uid, uid); - assert.equal(mockMailer.sendPasswordResetNotification.callCount, 1, 'mailer.sendPasswordResetNotification was called once') - assert.equal(mockMailer.sendPasswordResetNotification.args[0][2].uid, uid, 'mailer.sendPasswordResetNotification was passed uid') - assert.equal(mockMailer.sendPasswordResetNotification.args[0][2].deviceId, 'wibble') - }) + assert.equal(mockMailer.sendPasswordResetNotification.callCount, 1, 'mailer.sendPasswordResetNotification was called once'); + assert.equal(mockMailer.sendPasswordResetNotification.args[0][2].uid, uid, 'mailer.sendPasswordResetNotification was passed uid'); + assert.equal(mockMailer.sendPasswordResetNotification.args[0][2].deviceId, 'wibble'); + }); } - ) + ); describe('/change/finish', () => { it( 'smoke', () => { - var uid = uuid.v4('binary').toString('hex') + var uid = uuid.v4('binary').toString('hex'); var devices = [ { uid: uid, id: crypto.randomBytes(16) }, { uid: uid, id: crypto.randomBytes(16) } - ] + ]; var mockDB = mocks.mockDB({ email: TEST_EMAIL, uid, devices - }) - var mockPush = mocks.mockPush() - var mockMailer = mocks.mockMailer() - var mockLog = mocks.mockLog() + }); + var mockPush = mocks.mockPush(); + var mockMailer = mocks.mockMailer(); + var mockLog = mocks.mockLog(); var mockRequest = mocks.mockRequest({ credentials: { uid: uid @@ -320,34 +320,34 @@ describe('/password', () => { uaBrowserVersion: '57', uaOS: 'Mac OS X', uaOSVersion: '10.11' - }) + }); var passwordRoutes = makeRoutes({ db: mockDB, push: mockPush, mailer: mockMailer, log: mockLog - }) + }); return runRoute(passwordRoutes, '/password/change/finish', mockRequest) .then(function(response) { - assert.equal(mockDB.deletePasswordChangeToken.callCount, 1) - assert.equal(mockDB.resetAccount.callCount, 1) + assert.equal(mockDB.deletePasswordChangeToken.callCount, 1); + assert.equal(mockDB.resetAccount.callCount, 1); - assert.equal(mockPush.notifyPasswordChanged.callCount, 1, 'sent a push notification of the change') - assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[0], uid, 'notified the correct uid') - assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[1], [devices[1]], 'notified only the second device') + assert.equal(mockPush.notifyPasswordChanged.callCount, 1, 'sent a push notification of the change'); + assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[0], uid, 'notified the correct uid'); + assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[1], [devices[1]], 'notified only the second device'); - assert.equal(mockDB.account.callCount, 1) - assert.equal(mockMailer.sendPasswordChangedNotification.callCount, 1) - assert.equal(mockMailer.sendPasswordChangedNotification.firstCall.args[1].email, TEST_EMAIL) - assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.city, 'Mountain View') - assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.country, 'United States') - assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].timeZone, 'America/Los_Angeles') - assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].uid, uid) + assert.equal(mockDB.account.callCount, 1); + assert.equal(mockMailer.sendPasswordChangedNotification.callCount, 1); + assert.equal(mockMailer.sendPasswordChangedNotification.firstCall.args[1].email, TEST_EMAIL); + assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.city, 'Mountain View'); + assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.country, 'United States'); + assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].timeZone, 'America/Los_Angeles'); + assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].uid, uid); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - var args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + var args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.changedPassword', @@ -355,36 +355,36 @@ describe('/password', () => { service: undefined, uid: uid.toString('hex'), userAgent: 'test user-agent' - }, 'argument was event data') + }, 'argument was event data'); - assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - args = mockDB.createSessionToken.args[0] - assert.equal(args.length, 1, 'db.createSessionToken was passed one argument') - assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser') - assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version') - assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os') - assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version') - assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type') - assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor') - }) + assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + args = mockDB.createSessionToken.args[0]; + assert.equal(args.length, 1, 'db.createSessionToken was passed one argument'); + assert.equal(args[0].uaBrowser, 'Firefox', 'db.createSessionToken was passed correct browser'); + assert.equal(args[0].uaBrowserVersion, '57', 'db.createSessionToken was passed correct browser version'); + assert.equal(args[0].uaOS, 'Mac OS X', 'db.createSessionToken was passed correct os'); + assert.equal(args[0].uaOSVersion, '10.11', 'db.createSessionToken was passed correct os version'); + assert.equal(args[0].uaDeviceType, null, 'db.createSessionToken was passed correct device type'); + assert.equal(args[0].uaFormFactor, null, 'db.createSessionToken was passed correct form factor'); + }); } - ) + ); it( 'succeeds even if notification blocked', () => { - var uid = uuid.v4('binary').toString('hex') + var uid = uuid.v4('binary').toString('hex'); var mockDB = mocks.mockDB({ email: TEST_EMAIL, uid: uid - }) - var mockPush = mocks.mockPush() + }); + var mockPush = mocks.mockPush(); var mockMailer = { sendPasswordChangedNotification: sinon.spy(() => { - return P.reject(error.emailBouncedHard()) + return P.reject(error.emailBouncedHard()); }) - } - var mockLog = mocks.mockLog() + }; + var mockLog = mocks.mockLog(); var mockRequest = mocks.mockRequest({ credentials: { uid: uid @@ -398,7 +398,7 @@ describe('/password', () => { keys: 'true' }, log: mockLog - }) + }); var passwordRoutes = makeRoutes({ config: { domain: 'wibble', @@ -408,29 +408,29 @@ describe('/password', () => { push: mockPush, mailer: mockMailer, log: mockLog - }) + }); return runRoute(passwordRoutes, '/password/change/finish', mockRequest) .then(function(response) { - assert.equal(mockDB.deletePasswordChangeToken.callCount, 1) - assert.equal(mockDB.resetAccount.callCount, 1) + assert.equal(mockDB.deletePasswordChangeToken.callCount, 1); + assert.equal(mockDB.resetAccount.callCount, 1); - assert.equal(mockPush.notifyPasswordChanged.callCount, 1) - assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[0], uid) + assert.equal(mockPush.notifyPasswordChanged.callCount, 1); + assert.deepEqual(mockPush.notifyPasswordChanged.firstCall.args[0], uid); - const notifyArgs = mockLog.notifyAttachedServices.args[0] - assert.equal(notifyArgs.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(notifyArgs[0], 'passwordChange', 'first argument was event name') - assert.equal(notifyArgs[1], mockRequest, 'second argument was request object') - assert.equal(notifyArgs[2].uid, uid, 'third argument was event data with a uid') - assert.equal(notifyArgs[2].iss, 'wibble', 'third argument was event data with an issuer field') + const notifyArgs = mockLog.notifyAttachedServices.args[0]; + assert.equal(notifyArgs.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(notifyArgs[0], 'passwordChange', 'first argument was event name'); + assert.equal(notifyArgs[1], mockRequest, 'second argument was request object'); + assert.equal(notifyArgs[2].uid, uid, 'third argument was event data with a uid'); + assert.equal(notifyArgs[2].iss, 'wibble', 'third argument was event data with an issuer field'); - assert.equal(mockDB.account.callCount, 1) - assert.equal(mockMailer.sendPasswordChangedNotification.callCount, 1) + assert.equal(mockDB.account.callCount, 1); + assert.equal(mockMailer.sendPasswordChangedNotification.callCount, 1); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - var args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + var args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', event: 'account.changedPassword', @@ -438,10 +438,10 @@ describe('/password', () => { service: undefined, uid: uid.toString('hex'), userAgent: 'test user-agent' - }, 'argument was event data') - }) + }, 'argument was event data'); + }); } - ) - }) -}) + ); + }); +}); diff --git a/test/local/routes/recovery-codes.js b/test/local/routes/recovery-codes.js index 7a896eb5..861d91d9 100644 --- a/test/local/routes/recovery-codes.js +++ b/test/local/routes/recovery-codes.js @@ -2,39 +2,39 @@ * 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/. */ -'use strict' +'use strict'; -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const error = require('../../../lib/error') -const P = require('../../../lib/promise') +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const error = require('../../../lib/error'); +const P = require('../../../lib/promise'); -let log, db, customs, routes, route, request, requestOptions, mailer -const TEST_EMAIL = 'test@email.com' -const UID = 'uid' +let log, db, customs, routes, route, request, requestOptions, mailer; +const TEST_EMAIL = 'test@email.com'; +const UID = 'uid'; function runTest(routePath, requestOptions) { - const config = { recoveryCodes: {} } - routes = require('../../../lib/routes/recovery-codes')(log, db, config, customs, mailer) - route = getRoute(routes, routePath) - request = mocks.mockRequest(requestOptions) - request.emitMetricsEvent = sinon.spy(() => P.resolve({})) + const config = { recoveryCodes: {} }; + routes = require('../../../lib/routes/recovery-codes')(log, db, config, customs, mailer); + route = getRoute(routes, routePath); + request = mocks.mockRequest(requestOptions); + request.emitMetricsEvent = sinon.spy(() => P.resolve({})); - return route.handler(request) + return route.handler(request); } describe('recovery codes', () => { beforeEach(() => { - log = mocks.mockLog() - customs = mocks.mockCustoms() - mailer = mocks.mockMailer() + log = mocks.mockLog(); + customs = mocks.mockCustoms(); + mailer = mocks.mockMailer(); db = mocks.mockDB({ uid: UID, email: TEST_EMAIL - }) + }); requestOptions = { metricsContext: mocks.mockMetricsContext(), credentials: { @@ -48,48 +48,48 @@ describe('recovery codes', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' } } - } - }) + }; + }); describe('/recoveryCodes', () => { it('should replace recovery codes in TOTP session', () => { - requestOptions.credentials.authenticatorAssuranceLevel = 2 + requestOptions.credentials.authenticatorAssuranceLevel = 2; return runTest('/recoveryCodes', requestOptions) .then((res) => { - assert.equal(res.recoveryCodes.length, 2, 'correct default code count') + assert.equal(res.recoveryCodes.length, 2, 'correct default code count'); - assert.equal(db.replaceRecoveryCodes.callCount, 1) - let args = db.replaceRecoveryCodes.args[0] - assert.equal(args[0], UID, 'called with uid') - assert.equal(args[1], 8, 'called with recovery code count') + assert.equal(db.replaceRecoveryCodes.callCount, 1); + let args = db.replaceRecoveryCodes.args[0]; + assert.equal(args[0], UID, 'called with uid'); + assert.equal(args[1], 8, 'called with recovery code count'); - assert.equal(log.info.callCount, 1) - args = log.info.args[0] - assert.equal(args[0], 'account.recoveryCode.replaced') - assert.equal(args[1].uid, UID) - }) - }) + assert.equal(log.info.callCount, 1); + args = log.info.args[0]; + assert.equal(args[0], 'account.recoveryCode.replaced'); + assert.equal(args[1].uid, UID); + }); + }); it('should\'t replace codes in non-TOTP verified session', () => { - requestOptions.credentials.authenticatorAssuranceLevel = 1 + requestOptions.credentials.authenticatorAssuranceLevel = 1; return runTest('/recoveryCodes', requestOptions) .then(assert.fail, (err) => { - assert.equal(err.errno, error.ERRNO.SESSION_UNVERIFIED) - }) - }) - }) + assert.equal(err.errno, error.ERRNO.SESSION_UNVERIFIED); + }); + }); + }); describe('/session/verify/recoveryCode', () => { it('should rate-limit attempts to use a recovery code via customs', () => { - requestOptions.payload.code = '1234567890' + requestOptions.payload.code = '1234567890'; db.consumeRecoveryCode = sinon.spy(code => { - throw error.recoveryCodeNotFound() - }) + throw error.recoveryCodeNotFound(); + }); return runTest('/session/verify/recoveryCode', requestOptions) .then(assert.fail, err => { - assert.equal(err.errno, error.ERRNO.RECOVERY_CODE_NOT_FOUND) - assert.calledWithExactly(customs.check, request, TEST_EMAIL, 'verifyRecoveryCode') - }) - }) - }) -}) + assert.equal(err.errno, error.ERRNO.RECOVERY_CODE_NOT_FOUND); + assert.calledWithExactly(customs.check, request, TEST_EMAIL, 'verifyRecoveryCode'); + }); + }); + }); +}); diff --git a/test/local/routes/recovery-keys.js b/test/local/routes/recovery-keys.js index 2a71c4fa..f63260ee 100644 --- a/test/local/routes/recovery-keys.js +++ b/test/local/routes/recovery-keys.js @@ -2,20 +2,20 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const P = require('../../../lib/promise') -const sinon = require('sinon') -const errors = require('../../../lib/error') +const { assert } = require('chai'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); +const sinon = require('sinon'); +const errors = require('../../../lib/error'); -let log, db, customs, mailer, routes, route, request, response -const email = 'test@email.com' -const recoveryKeyId = '000000' -const recoveryData = '11111111111' -const uid = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +let log, db, customs, mailer, routes, route, request, response; +const email = 'test@email.com'; +const recoveryKeyId = '000000'; +const recoveryData = '11111111111'; +const uid = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; describe('POST /recoveryKey', () => { describe('should create recovery key', () => { @@ -24,65 +24,65 @@ describe('POST /recoveryKey', () => { credentials: {uid, email}, log, payload: {recoveryKeyId, recoveryData} - } - return setup({db: {email}}, {}, '/recoveryKey', requestOptions).then(r => response = r) - }) + }; + return setup({db: {email}}, {}, '/recoveryKey', requestOptions).then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, {}) - }) + assert.deepEqual(response, {}); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'createRecoveryKey') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'createRecoveryKey'); + assert.equal(args[1], request); + }); it('called db.createRecoveryKey correctly', () => { - assert.equal(db.createRecoveryKey.callCount, 1) - const args = db.createRecoveryKey.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], uid) - assert.equal(args[1], recoveryKeyId) - assert.equal(args[2], recoveryData) - }) + assert.equal(db.createRecoveryKey.callCount, 1); + const args = db.createRecoveryKey.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], uid); + assert.equal(args[1], recoveryKeyId); + assert.equal(args[2], recoveryData); + }); it('called log.info correctly', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'account.recoveryKey.created') - }) + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'account.recoveryKey.created'); + }); it('called request.emitMetricsEvent correctly', () => { - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - const args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'recoveryKey.created', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], uid, 'called emitMetricsEvent with correct event') - }) + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + const args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'recoveryKey.created', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], uid, 'called emitMetricsEvent with correct event'); + }); it('called mailer.sendPostAddAccountRecoveryNotification correctly', () => { - assert.equal(mailer.sendPostAddAccountRecoveryNotification.callCount, 1) - const args = mailer.sendPostAddAccountRecoveryNotification.args[0] - assert.equal(args.length, 3) - assert.equal(args[0][0].email, email) - }) - }) + assert.equal(mailer.sendPostAddAccountRecoveryNotification.callCount, 1); + const args = mailer.sendPostAddAccountRecoveryNotification.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0][0].email, email); + }); + }); describe('should fail for unverified session', () => { it('returned the correct response', () => { const requestOptions = { credentials: {uid, email, tokenVerificationId: '1232311'}, - } + }; return setup({db: {}}, {}, '/recoveryKey', requestOptions) .then(assert.fail, (err) => { - assert.deepEqual(err.errno, errors.ERRNO.SESSION_UNVERIFIED, 'returns unverified session error') - }) - }) - }) -}) + assert.deepEqual(err.errno, errors.ERRNO.SESSION_UNVERIFIED, 'returns unverified session error'); + }); + }); + }); +}); describe('GET /recoveryKey/{recoveryKeyId}', () => { describe('should get recovery key', () => { @@ -91,40 +91,40 @@ describe('GET /recoveryKey/{recoveryKeyId}', () => { credentials: {uid, email}, params: {recoveryKeyId}, log - } + }; return setup({db: {recoveryData, recoveryKeyId}}, {}, '/recoveryKey/{recoveryKeyId}', requestOptions) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response.recoveryData, recoveryData, 'return recovery data') - }) + assert.deepEqual(response.recoveryData, recoveryData, 'return recovery data'); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'getRecoveryKey') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'getRecoveryKey'); + assert.equal(args[1], request); + }); it('called customs.checkAuthenticated correctly', () => { - assert.equal(customs.checkAuthenticated.callCount, 1) - const args = customs.checkAuthenticated.args[0] - assert.equal(args.length, 3) - assert.deepEqual(args[0], request) - assert.equal(args[1], uid) - assert.equal(args[2], 'getRecoveryKey') - }) + assert.equal(customs.checkAuthenticated.callCount, 1); + const args = customs.checkAuthenticated.args[0]; + assert.equal(args.length, 3); + assert.deepEqual(args[0], request); + assert.equal(args[1], uid); + assert.equal(args[2], 'getRecoveryKey'); + }); it('called db.getRecoveryKey correctly', () => { - assert.equal(db.getRecoveryKey.callCount, 1) - const args = db.getRecoveryKey.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], uid) - assert.equal(args[1], recoveryKeyId) - }) - }) + assert.equal(db.getRecoveryKey.callCount, 1); + const args = db.getRecoveryKey.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], uid); + assert.equal(args[1], recoveryKeyId); + }); + }); describe('fails to return recovery data with recoveryKeyId mismatch', () => { beforeEach(() => { @@ -132,16 +132,16 @@ describe('GET /recoveryKey/{recoveryKeyId}', () => { credentials: {uid, email}, params: {recoveryKeyId}, log - } + }; return setup({db: {recoveryData, recoveryKeyIdInvalid: true}}, {}, '/recoveryKey/{recoveryKeyId}', requestOptions) - .then(assert.fail, (err) => response = err) - }) + .then(assert.fail, (err) => response = err); + }); it('returned the correct response', () => { - assert.deepEqual(response.errno, errors.ERRNO.RECOVERY_KEY_INVALID, 'correct invalid recovery key errno') - }) - }) -}) + assert.deepEqual(response.errno, errors.ERRNO.RECOVERY_KEY_INVALID, 'correct invalid recovery key errno'); + }); + }); +}); describe('POST /recoveryKey/exists', () => { describe('should check if recovery key exists using sessionToken', () => { @@ -149,69 +149,69 @@ describe('POST /recoveryKey/exists', () => { const requestOptions = { credentials: {uid, email}, log - } + }; return setup({db: {recoveryData}}, {}, '/recoveryKey/exists', requestOptions) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.equal(response.exists, true, 'exists ') - }) + assert.equal(response.exists, true, 'exists '); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'recoveryKeyExists') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'recoveryKeyExists'); + assert.equal(args[1], request); + }); it('called db.recoveryKeyExists correctly', () => { - assert.equal(db.recoveryKeyExists.callCount, 1) - const args = db.recoveryKeyExists.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], uid) - }) - }) + assert.equal(db.recoveryKeyExists.callCount, 1); + const args = db.recoveryKeyExists.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], uid); + }); + }); describe('should check if recovery key exists using email', () => { beforeEach(() => { const requestOptions = { payload: {email}, log - } + }; return setup({db: {uid, email, recoveryData}}, {}, '/recoveryKey/exists', requestOptions) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response.exists, true, 'exists ') - }) + assert.deepEqual(response.exists, true, 'exists '); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'recoveryKeyExists') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'recoveryKeyExists'); + assert.equal(args[1], request); + }); it('called customs.check correctly', () => { - assert.equal(customs.check.callCount, 1) - const args = customs.check.args[0] - assert.equal(args.length, 3) - assert.equal(args[1], email) - assert.equal(args[2], 'recoveryKeyExists') - }) + assert.equal(customs.check.callCount, 1); + const args = customs.check.args[0]; + assert.equal(args.length, 3); + assert.equal(args[1], email); + assert.equal(args[2], 'recoveryKeyExists'); + }); it('called db.recoveryKeyExists correctly', () => { - assert.equal(db.recoveryKeyExists.callCount, 1) - const args = db.recoveryKeyExists.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], uid) - }) - }) -}) + assert.equal(db.recoveryKeyExists.callCount, 1); + const args = db.recoveryKeyExists.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], uid); + }); + }); +}); describe('DELETE /recoveryKey', () => { describe('should delete recovery key', () => { @@ -220,37 +220,37 @@ describe('DELETE /recoveryKey', () => { method: 'DELETE', credentials: {uid, email}, log - } + }; return setup({db: {recoveryData, email}}, {}, '/recoveryKey', requestOptions) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.ok(response, 'empty response ') - }) + assert.ok(response, 'empty response '); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'recoveryKeyDelete') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'recoveryKeyDelete'); + assert.equal(args[1], request); + }); it('called db.deleteRecoveryKey correctly', () => { - assert.equal(db.deleteRecoveryKey.callCount, 1) - const args = db.deleteRecoveryKey.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], uid) - }) + assert.equal(db.deleteRecoveryKey.callCount, 1); + const args = db.deleteRecoveryKey.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], uid); + }); it('called mailer.sendPostRemoveAccountRecoveryNotification correctly', () => { - assert.equal(mailer.sendPostRemoveAccountRecoveryNotification.callCount, 1) - const args = mailer.sendPostRemoveAccountRecoveryNotification.args[0] - assert.equal(args.length, 3) - assert.equal(args[0][0].email, email) - }) - }) + assert.equal(mailer.sendPostRemoveAccountRecoveryNotification.callCount, 1); + const args = mailer.sendPostRemoveAccountRecoveryNotification.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0][0].email, email); + }); + }); describe('should fail for unverified session', () => { beforeEach(() => { @@ -258,42 +258,42 @@ describe('DELETE /recoveryKey', () => { method: 'DELETE', credentials: {uid, email, tokenVerificationId: 'unverified'}, log - } + }; return setup({db: {recoveryData}}, {}, '/recoveryKey', requestOptions) - .then(assert.fail, (err) => response = err) - }) + .then(assert.fail, (err) => response = err); + }); it('returned the correct response', () => { - assert.equal(response.errno, errors.ERRNO.SESSION_UNVERIFIED, 'unverified session') - }) - }) -}) + assert.equal(response.errno, errors.ERRNO.SESSION_UNVERIFIED, 'unverified session'); + }); + }); +}); function setup(results, errors, path, requestOptions) { - results = results || {} - errors = errors || {} + results = results || {}; + errors = errors || {}; - log = mocks.mockLog() - db = mocks.mockDB(results.db, errors.db) - customs = mocks.mockCustoms(errors.customs) - mailer = mocks.mockMailer() - routes = makeRoutes({log, db, customs, mailer}) - route = getRoute(routes, path, requestOptions.method) - request = mocks.mockRequest(requestOptions) - request.emitMetricsEvent = sinon.spy(() => P.resolve({})) - return runTest(route, request) + log = mocks.mockLog(); + db = mocks.mockDB(results.db, errors.db); + customs = mocks.mockCustoms(errors.customs); + mailer = mocks.mockMailer(); + routes = makeRoutes({log, db, customs, mailer}); + route = getRoute(routes, path, requestOptions.method); + request = mocks.mockRequest(requestOptions); + request.emitMetricsEvent = sinon.spy(() => P.resolve({})); + return runTest(route, request); } function makeRoutes(options = {}) { - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() - const customs = options.customs || mocks.mockCustoms() - const config = options.config || {signinConfirmation: {}} - const Password = require('../../../lib/crypto/password')(log, config) - const mailer = options.mailer || mocks.mockMailer() - return require('../../../lib/routes/recovery-key')(log, db, Password, config.verifierVersion, customs, mailer) + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); + const customs = options.customs || mocks.mockCustoms(); + const config = options.config || {signinConfirmation: {}}; + const Password = require('../../../lib/crypto/password')(log, config); + const mailer = options.mailer || mocks.mockMailer(); + return require('../../../lib/routes/recovery-key')(log, db, Password, config.verifierVersion, customs, mailer); } function runTest(route, request) { - return route.handler(request) + return route.handler(request); } diff --git a/test/local/routes/request_helper.js b/test/local/routes/request_helper.js index f765e1ff..daf0f3b2 100644 --- a/test/local/routes/request_helper.js +++ b/test/local/routes/request_helper.js @@ -2,28 +2,28 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const requestHelper = require('../../../lib/routes/utils/request_helper') +const { assert } = require('chai'); +const requestHelper = require('../../../lib/routes/utils/request_helper'); describe('requestHelper', () => { it( 'interface is correct', () => { - assert.equal(typeof requestHelper, 'object', 'object type should be exported') - assert.equal(Object.keys(requestHelper).length, 1, 'object should have one properties') - assert.equal(typeof requestHelper.wantsKeys, 'function', 'wantsKeys should be function') + assert.equal(typeof requestHelper, 'object', 'object type should be exported'); + assert.equal(Object.keys(requestHelper).length, 1, 'object should have one properties'); + assert.equal(typeof requestHelper.wantsKeys, 'function', 'wantsKeys should be function'); } - ) + ); it( 'wantsKeys', () => { - assert.equal(!! requestHelper.wantsKeys({}), false, 'should return falsey if request.query is not set') - assert.equal(requestHelper.wantsKeys({ query: {} }), false, 'should return false if query.keys is not set') - assert.equal(requestHelper.wantsKeys({ query: { keys: false } }), false, 'should return false if query.keys is false') - assert.equal(requestHelper.wantsKeys({ query: { keys: true } }), true, 'should return true if keys is true') + assert.equal(!! requestHelper.wantsKeys({}), false, 'should return falsey if request.query is not set'); + assert.equal(requestHelper.wantsKeys({ query: {} }), false, 'should return false if query.keys is not set'); + assert.equal(requestHelper.wantsKeys({ query: { keys: false } }), false, 'should return false if query.keys is false'); + assert.equal(requestHelper.wantsKeys({ query: { keys: true } }), true, 'should return true if keys is true'); } - ) -}) + ); +}); diff --git a/test/local/routes/session.js b/test/local/routes/session.js index 1ed913f9..12bc3023 100644 --- a/test/local/routes/session.js +++ b/test/local/routes/session.js @@ -2,88 +2,88 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const getRoute = require('../../routes_helpers').getRoute -const knownIpLocation = require('../../known-ip-location') -const mocks = require('../../mocks') -const P = require('../../../lib/promise') -const error = require('../../../lib/error') -const sinon = require('sinon') +const { assert } = require('chai'); +const crypto = require('crypto'); +const getRoute = require('../../routes_helpers').getRoute; +const knownIpLocation = require('../../known-ip-location'); +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); +const error = require('../../../lib/error'); +const sinon = require('sinon'); function makeRoutes (options = {}) { - const config = options.config || {} - config.smtp = config.smtp || {} - const db = options.db || mocks.mockDB() - const log = options.log || mocks.mockLog() - const mailer = options.mailer || {} - const Password = options.Password || require('../../../lib/crypto/password')(log, config) + const config = options.config || {}; + config.smtp = config.smtp || {}; + const db = options.db || mocks.mockDB(); + const log = options.log || mocks.mockLog(); + const mailer = options.mailer || {}; + const Password = options.Password || require('../../../lib/crypto/password')(log, config); const customs = options.customs || { - check: () => { return P.resolve(true) } - } - const signinUtils = options.signinUtils || require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer) + check: () => { return P.resolve(true); } + }; + const signinUtils = options.signinUtils || require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer); if (options.checkPassword) { - signinUtils.checkPassword = options.checkPassword + signinUtils.checkPassword = options.checkPassword; } - return require('../../../lib/routes/session')(log, db, Password, config, signinUtils) + return require('../../../lib/routes/session')(log, db, Password, config, signinUtils); } function runTest (route, request) { - return route.handler(request) + return route.handler(request); } function hexString(bytes) { - return crypto.randomBytes(bytes).toString('hex') + return crypto.randomBytes(bytes).toString('hex'); } describe('/session/status', () => { - const log = mocks.mockLog() - const config = {} - const routes = makeRoutes({ log, config }) - const route = getRoute(routes, '/session/status') + const log = mocks.mockLog(); + const config = {}; + const routes = makeRoutes({ log, config }); + const route = getRoute(routes, '/session/status'); const request = mocks.mockRequest({ credentials: { email: 'foo@example.org', state: 'unverified', uid: 'foo' } - }) + }); it('returns status correctly', () => { return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 2) - assert.equal(res.uid, 'foo') - assert.equal(res.state, 'unverified') - }) - }) + assert.equal(Object.keys(res).length, 2); + assert.equal(res.uid, 'foo'); + assert.equal(res.state, 'unverified'); + }); + }); -}) +}); describe('/session/reauth', () => { - const TEST_EMAIL = 'foo@example.com' - const TEST_UID = 'abcdef123456' - const TEST_AUTHPW = hexString(32) + const TEST_EMAIL = 'foo@example.com'; + const TEST_UID = 'abcdef123456'; + const TEST_AUTHPW = hexString(32); - let log, config, customs, db, mailer, signinUtils, routes, route, request, SessionToken + let log, config, customs, db, mailer, signinUtils, routes, route, request, SessionToken; beforeEach(() => { - log = mocks.mockLog() - config = {} + log = mocks.mockLog(); + config = {}; customs = { - check: () => { return P.resolve(true) } - } + check: () => { return P.resolve(true); } + }; db = mocks.mockDB({ email: TEST_EMAIL, uid: TEST_UID - }) - mailer = mocks.mockMailer() - signinUtils = require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer) - SessionToken = require('../../../lib/tokens/index')(log, config).SessionToken - routes = makeRoutes({ log, config, customs, db, mailer, signinUtils }) - route = getRoute(routes, '/session/reauth') + }); + mailer = mocks.mockMailer(); + signinUtils = require('../../../lib/routes/utils/signin')(log, config, customs, db, mailer); + SessionToken = require('../../../lib/tokens/index')(log, config).SessionToken; + routes = makeRoutes({ log, config, customs, db, mailer, signinUtils }); + route = getRoute(routes, '/session/reauth'); request = mocks.mockRequest({ log: log, payload: { @@ -110,224 +110,224 @@ describe('/session/reauth', () => { uaOSVersion: '6', uaDeviceType: 'mobile', uaFormFactor: 'trapezoid' - }) + }); return SessionToken.fromHex(hexString(16), { email: TEST_EMAIL, uid: TEST_UID, createdAt: 12345678 }).then(sessionToken => { - request.auth.credentials = sessionToken - }) - }) + request.auth.credentials = sessionToken; + }); + }); it('emits the correct series of calls', () => { - signinUtils.checkEmailAddress = sinon.spy(() => P.resolve(true)) - signinUtils.checkPassword = sinon.spy(() => P.resolve(true)) - signinUtils.checkCustomsAndLoadAccount = sinon.spy(() => P.props({ accountRecord: db.accountRecord(TEST_EMAIL) })) - signinUtils.sendSigninNotifications = sinon.spy(() => P.resolve()) - signinUtils.createKeyFetchToken = sinon.spy(() => P.resolve( { data: 'KEYFETCHTOKEN' })) - signinUtils.getSessionVerificationStatus = sinon.spy(() => ({ verified: true })) - const testNow = Math.floor(Date.now() / 1000) + signinUtils.checkEmailAddress = sinon.spy(() => P.resolve(true)); + signinUtils.checkPassword = sinon.spy(() => P.resolve(true)); + signinUtils.checkCustomsAndLoadAccount = sinon.spy(() => P.props({ accountRecord: db.accountRecord(TEST_EMAIL) })); + signinUtils.sendSigninNotifications = sinon.spy(() => P.resolve()); + signinUtils.createKeyFetchToken = sinon.spy(() => P.resolve( { data: 'KEYFETCHTOKEN' })); + signinUtils.getSessionVerificationStatus = sinon.spy(() => ({ verified: true })); + const testNow = Math.floor(Date.now() / 1000); return runTest(route, request).then((res) => { - assert.equal(signinUtils.checkCustomsAndLoadAccount.callCount, 1, 'checkCustomsAndLoadAccount was called') - let args = signinUtils.checkCustomsAndLoadAccount.args[0] - assert.equal(args.length, 2, 'checkCustomsAndLoadAccount was called with correct number of arguments') - assert.equal(args[0], request, 'checkCustomsAndLoadAccount was called with request as first argument') - assert.equal(args[1], TEST_EMAIL, 'checkCustomsAndLoadAccount was called with email as second argument') + assert.equal(signinUtils.checkCustomsAndLoadAccount.callCount, 1, 'checkCustomsAndLoadAccount was called'); + let args = signinUtils.checkCustomsAndLoadAccount.args[0]; + assert.equal(args.length, 2, 'checkCustomsAndLoadAccount was called with correct number of arguments'); + assert.equal(args[0], request, 'checkCustomsAndLoadAccount was called with request as first argument'); + assert.equal(args[1], TEST_EMAIL, 'checkCustomsAndLoadAccount was called with email as second argument'); - assert.equal(db.accountRecord.callCount, 1, 'db.accountRecord was called') - args = db.accountRecord.args[0] - assert.equal(args.length, 1, 'db.accountRecord was called with correct number of arguments') - assert.equal(args[0], TEST_EMAIL, 'db.accountRecord was called with email as first argument') + assert.equal(db.accountRecord.callCount, 1, 'db.accountRecord was called'); + args = db.accountRecord.args[0]; + assert.equal(args.length, 1, 'db.accountRecord was called with correct number of arguments'); + assert.equal(args[0], TEST_EMAIL, 'db.accountRecord was called with email as first argument'); - assert.equal(signinUtils.checkEmailAddress.callCount, 1, 'checkEmaiLAddress was called') - args = signinUtils.checkEmailAddress.args[0] - assert.equal(args.length, 3, 'checkEmailAddress was called with correct number of arguments') - assert.equal(args[0].uid, TEST_UID, 'checkEmailAddress was called with account record as first argument') - assert.equal(args[1], TEST_EMAIL, 'checkEmaiLAddress was called with email as second argument') - assert.equal(args[2], undefined, 'checkEmaiLAddress was called with undefined originalLoginEmail as third argument') + assert.equal(signinUtils.checkEmailAddress.callCount, 1, 'checkEmaiLAddress was called'); + args = signinUtils.checkEmailAddress.args[0]; + assert.equal(args.length, 3, 'checkEmailAddress was called with correct number of arguments'); + assert.equal(args[0].uid, TEST_UID, 'checkEmailAddress was called with account record as first argument'); + assert.equal(args[1], TEST_EMAIL, 'checkEmaiLAddress was called with email as second argument'); + assert.equal(args[2], undefined, 'checkEmaiLAddress was called with undefined originalLoginEmail as third argument'); - assert.equal(signinUtils.checkPassword.callCount, 1, 'checkPassword was called') - args = signinUtils.checkPassword.args[0] - assert.equal(args.length, 3, 'checkPassword was called with correct number of arguments') - assert.equal(args[0].uid, TEST_UID, 'checkPassword was called with account record as first argument') - assert.equal(args[1].authPW.toString('hex'), TEST_AUTHPW, 'checkPassword was called with Password object as second argument') - assert.equal(args[2], knownIpLocation.ip, 'checkPassword was called with mock ip address as third argument') + assert.equal(signinUtils.checkPassword.callCount, 1, 'checkPassword was called'); + args = signinUtils.checkPassword.args[0]; + assert.equal(args.length, 3, 'checkPassword was called with correct number of arguments'); + assert.equal(args[0].uid, TEST_UID, 'checkPassword was called with account record as first argument'); + assert.equal(args[1].authPW.toString('hex'), TEST_AUTHPW, 'checkPassword was called with Password object as second argument'); + assert.equal(args[2], knownIpLocation.ip, 'checkPassword was called with mock ip address as third argument'); - assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called') - args = db.updateSessionToken.args[0] - assert.equal(args.length, 1, 'db.updateSessionToken was called with correct number of arguments') - assert.equal(args[0], request.auth.credentials, 'db.updateSessionToken was called with sessionToken as first argument') + assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called'); + args = db.updateSessionToken.args[0]; + assert.equal(args.length, 1, 'db.updateSessionToken was called with correct number of arguments'); + assert.equal(args[0], request.auth.credentials, 'db.updateSessionToken was called with sessionToken as first argument'); - assert.equal(signinUtils.sendSigninNotifications.callCount, 1, 'sendSigninNotifications was called') - args = signinUtils.sendSigninNotifications.args[0] - assert.equal(args.length, 4, 'sendSigninNotifications was called with correct number of arguments') - assert.equal(args[0], request, 'sendSigninNotifications was called with request as first argument') - assert.equal(args[1].uid, TEST_UID, 'sendSigninNotifications was called with account record as second argument') - assert.equal(args[2], request.auth.credentials, 'sendSigninNotifications was called with sessionToken as third argument') - assert.equal(args[3], undefined, 'sendSigninNotifications was called with undefined verificationMethod as third argument') + assert.equal(signinUtils.sendSigninNotifications.callCount, 1, 'sendSigninNotifications was called'); + args = signinUtils.sendSigninNotifications.args[0]; + assert.equal(args.length, 4, 'sendSigninNotifications was called with correct number of arguments'); + assert.equal(args[0], request, 'sendSigninNotifications was called with request as first argument'); + assert.equal(args[1].uid, TEST_UID, 'sendSigninNotifications was called with account record as second argument'); + assert.equal(args[2], request.auth.credentials, 'sendSigninNotifications was called with sessionToken as third argument'); + assert.equal(args[3], undefined, 'sendSigninNotifications was called with undefined verificationMethod as third argument'); - assert.equal(signinUtils.createKeyFetchToken.callCount, 1, 'createKeyFetchToken was called') - args = signinUtils.createKeyFetchToken.args[0] - assert.equal(args.length, 4, 'createKeyFetchToken was called with correct number of arguments') - assert.equal(args[0], request, 'createKeyFetchToken was called with request as first argument') - assert.equal(args[1].uid, TEST_UID, 'createKeyFetchToken was called with account record as second argument') - assert.equal(args[2].authPW.toString('hex'), TEST_AUTHPW, 'createKeyFetchToken was called with Password object as third argument') - assert.equal(args[3], request.auth.credentials, 'createKeyFetchToken was called with sessionToken as fourth argument') + assert.equal(signinUtils.createKeyFetchToken.callCount, 1, 'createKeyFetchToken was called'); + args = signinUtils.createKeyFetchToken.args[0]; + assert.equal(args.length, 4, 'createKeyFetchToken was called with correct number of arguments'); + assert.equal(args[0], request, 'createKeyFetchToken was called with request as first argument'); + assert.equal(args[1].uid, TEST_UID, 'createKeyFetchToken was called with account record as second argument'); + assert.equal(args[2].authPW.toString('hex'), TEST_AUTHPW, 'createKeyFetchToken was called with Password object as third argument'); + assert.equal(args[3], request.auth.credentials, 'createKeyFetchToken was called with sessionToken as fourth argument'); - assert.equal(signinUtils.getSessionVerificationStatus.callCount, 1, 'getSessionVerificationStatus was called') - args = signinUtils.getSessionVerificationStatus.args[0] - assert.equal(args.length, 2, 'getSessionVerificationStatus was called with correct number of arguments') - assert.equal(args[0], request.auth.credentials, 'getSessionVerificationStatus was called with sessionToken as first argument') - assert.equal(args[1], undefined, 'getSessionVerificationStatus was called with undefined verificationMethod as first argument') + assert.equal(signinUtils.getSessionVerificationStatus.callCount, 1, 'getSessionVerificationStatus was called'); + args = signinUtils.getSessionVerificationStatus.args[0]; + assert.equal(args.length, 2, 'getSessionVerificationStatus was called with correct number of arguments'); + assert.equal(args[0], request.auth.credentials, 'getSessionVerificationStatus was called with sessionToken as first argument'); + assert.equal(args[1], undefined, 'getSessionVerificationStatus was called with undefined verificationMethod as first argument'); - assert.equal(Object.keys(res).length, 4, 'response object had correct number of keys') - assert.equal(res.uid, TEST_UID, 'response object contained correct uid') - assert.ok(res.authAt >= testNow, 'response object contained an updated authAt timestamp') - assert.equal(res.keyFetchToken, 'KEYFETCHTOKEN', 'response object contained the keyFetchToken') - assert.equal(res.verified, true, 'response object indicated correct verification status') - }) - }) + assert.equal(Object.keys(res).length, 4, 'response object had correct number of keys'); + assert.equal(res.uid, TEST_UID, 'response object contained correct uid'); + assert.ok(res.authAt >= testNow, 'response object contained an updated authAt timestamp'); + assert.equal(res.keyFetchToken, 'KEYFETCHTOKEN', 'response object contained the keyFetchToken'); + assert.equal(res.verified, true, 'response object indicated correct verification status'); + }); + }); it('correctly updates sessionToken details', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) - const testNow = Date.now() - const testNowSeconds = Math.floor(Date.now() / 1000) + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); + const testNow = Date.now(); + const testNowSeconds = Math.floor(Date.now() / 1000); - assert.ok(! request.auth.credentials.authAt, 'sessionToken starts off with no authAt') - assert.ok(request.auth.credentials.lastAuthAt() < testNowSeconds , 'sessionToken starts off with low lastAuthAt') - assert.ok(! request.auth.credentials.uaBrowser, 'sessionToken starts off with no uaBrowser') - assert.ok(! request.auth.credentials.uaBrowserVersion, 'sessionToken starts off with no uaBrowserVersion') - assert.ok(! request.auth.credentials.uaOS, 'sessionToken starts off with no uaOS') - assert.ok(! request.auth.credentials.uaOSVersion, 'sessionToken starts off with no uaOSVersion') - assert.ok(! request.auth.credentials.uaDeviceType, 'sessionToken starts off with no uaDeviceType') - assert.ok(! request.auth.credentials.uaFormFactor, 'sessionToken starts off with no uaFormFactor') + assert.ok(! request.auth.credentials.authAt, 'sessionToken starts off with no authAt'); + assert.ok(request.auth.credentials.lastAuthAt() < testNowSeconds , 'sessionToken starts off with low lastAuthAt'); + assert.ok(! request.auth.credentials.uaBrowser, 'sessionToken starts off with no uaBrowser'); + assert.ok(! request.auth.credentials.uaBrowserVersion, 'sessionToken starts off with no uaBrowserVersion'); + assert.ok(! request.auth.credentials.uaOS, 'sessionToken starts off with no uaOS'); + assert.ok(! request.auth.credentials.uaOSVersion, 'sessionToken starts off with no uaOSVersion'); + assert.ok(! request.auth.credentials.uaDeviceType, 'sessionToken starts off with no uaDeviceType'); + assert.ok(! request.auth.credentials.uaFormFactor, 'sessionToken starts off with no uaFormFactor'); return runTest(route, request).then((res) => { - assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called') - const sessionToken = db.updateSessionToken.args[0][0] - assert.ok(sessionToken.authAt >= testNow, 'sessionToken has updated authAt timestamp') - assert.ok(sessionToken.lastAuthAt() >= testNowSeconds , 'sessionToken has udpated lastAuthAt') - assert.equal(sessionToken.uaBrowser, 'Firefox', 'sessionToken has updated uaBrowser') - assert.equal(sessionToken.uaBrowserVersion, '50', 'sessionToken has updated uaBrowserVersion') - assert.equal(sessionToken.uaOS, 'Android', 'sessionToken has updated uaOS') - assert.equal(sessionToken.uaOSVersion, '6', 'sessionToken has updated uaOSVersion') - assert.equal(sessionToken.uaDeviceType, 'mobile', 'sessionToken has updated uaDeviceType') - assert.equal(sessionToken.uaFormFactor, 'trapezoid', 'sessionToken has updated uaFormFactor') - }) - }) + assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called'); + const sessionToken = db.updateSessionToken.args[0][0]; + assert.ok(sessionToken.authAt >= testNow, 'sessionToken has updated authAt timestamp'); + assert.ok(sessionToken.lastAuthAt() >= testNowSeconds , 'sessionToken has udpated lastAuthAt'); + assert.equal(sessionToken.uaBrowser, 'Firefox', 'sessionToken has updated uaBrowser'); + assert.equal(sessionToken.uaBrowserVersion, '50', 'sessionToken has updated uaBrowserVersion'); + assert.equal(sessionToken.uaOS, 'Android', 'sessionToken has updated uaOS'); + assert.equal(sessionToken.uaOSVersion, '6', 'sessionToken has updated uaOSVersion'); + assert.equal(sessionToken.uaDeviceType, 'mobile', 'sessionToken has updated uaDeviceType'); + assert.equal(sessionToken.uaFormFactor, 'trapezoid', 'sessionToken has updated uaFormFactor'); + }); + }); it('correctly updates to mustVerify=true when requesting keys', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); - assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false') + assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false'); return runTest(route, request).then((res) => { - assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called') - const sessionToken = db.updateSessionToken.args[0][0] - assert.ok(sessionToken.mustVerify, 'sessionToken has updated to mustVerify=true') - }) - }) + assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called'); + const sessionToken = db.updateSessionToken.args[0][0]; + assert.ok(sessionToken.mustVerify, 'sessionToken has updated to mustVerify=true'); + }); + }); it('correctly updates to mustVerify=true when explicit verificationMethod is requested in payload', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); - assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false') + assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false'); - request.payload.verificationMethod = 'email-2fa' + request.payload.verificationMethod = 'email-2fa'; return runTest(route, request).then((res) => { - assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called') - const sessionToken = db.updateSessionToken.args[0][0] - assert.ok(sessionToken.mustVerify, 'sessionToken has updated to mustVerify=true') - }) - }) + assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called'); + const sessionToken = db.updateSessionToken.args[0][0]; + assert.ok(sessionToken.mustVerify, 'sessionToken has updated to mustVerify=true'); + }); + }); it('leaves mustVerify=false when not requesting keys', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) - request.query.keys = false + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); + request.query.keys = false; - assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false') + assert.ok(! request.auth.credentials.mustVerify, 'sessionToken starts off with mustVerify=false'); return runTest(route, request).then((res) => { - assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called') - const sessionToken = db.updateSessionToken.args[0][0] - assert.ok(! sessionToken.mustVerify, 'sessionToken still has mustVerify=false') - }) - }) + assert.equal(db.updateSessionToken.callCount, 1, 'db.updateSessionToken was called'); + const sessionToken = db.updateSessionToken.args[0][0]; + assert.ok(! sessionToken.mustVerify, 'sessionToken still has mustVerify=false'); + }); + }); it('does not return a keyFetchToken when not requesting keys', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) - signinUtils.createKeyFetchToken = sinon.spy(() => { assert.fail('should not be called') }) - request.query.keys = false + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); + signinUtils.createKeyFetchToken = sinon.spy(() => { assert.fail('should not be called'); }); + request.query.keys = false; return runTest(route, request).then((res) => { - assert.equal(signinUtils.createKeyFetchToken.callCount, 0, 'createKeyFetchToken was not called') - assert.ok(! res.keyFetchToken, 'response object did not contain a keyFetchToken') - }) - }) + assert.equal(signinUtils.createKeyFetchToken.callCount, 0, 'createKeyFetchToken was not called'); + assert.ok(! res.keyFetchToken, 'response object did not contain a keyFetchToken'); + }); + }); it('correctly rejects incorrect passwords', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(false) }) + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(false); }); return runTest(route, request).then( - (res) => { assert.fail('request should have been rejected') }, + (res) => { assert.fail('request should have been rejected'); }, (err) => { - assert.equal(signinUtils.checkPassword.callCount, 1, 'checkPassword was called') - assert.equal(err.errno, error.ERRNO.INCORRECT_PASSWORD, 'the errno was correct') + assert.equal(signinUtils.checkPassword.callCount, 1, 'checkPassword was called'); + assert.equal(err.errno, error.ERRNO.INCORRECT_PASSWORD, 'the errno was correct'); } - ) - }) + ); + }); it('reflects the requested verificationMethod in the response body', () => { - signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true) }) - request.auth.credentials.emailVerified = true - request.auth.credentials.tokenVerified = false - request.auth.credentials.tokenVerificationId = 'myCoolId' - request.auth.credentials.tokenVerificationCode = 'myAwesomerCode' - request.auth.credentials.tokenVerificationCodeExpiresAt = Date.now() + 10000 - request.payload.verificationMethod = 'email-2fa' + signinUtils.checkPassword = sinon.spy(() => { return P.resolve(true); }); + request.auth.credentials.emailVerified = true; + request.auth.credentials.tokenVerified = false; + request.auth.credentials.tokenVerificationId = 'myCoolId'; + request.auth.credentials.tokenVerificationCode = 'myAwesomerCode'; + request.auth.credentials.tokenVerificationCodeExpiresAt = Date.now() + 10000; + request.payload.verificationMethod = 'email-2fa'; return runTest(route, request).then((res) => { - assert.ok(res) - assert.equal(res.verified, false, 'result reports the session as unverified') - assert.equal(res.verificationReason, 'login', 'result reports the verificationReason as "login"') - assert.equal(res.verificationMethod, 'email-2fa', 'result reports the verificationReason as requested') - }) - }) -}) + assert.ok(res); + assert.equal(res.verified, false, 'result reports the session as unverified'); + assert.equal(res.verificationReason, 'login', 'result reports the verificationReason as "login"'); + assert.equal(res.verificationMethod, 'email-2fa', 'result reports the verificationReason as requested'); + }); + }); +}); describe('/session/destroy', () => { - let route - let request - let log - let db + let route; + let request; + let log; + let db; beforeEach(() => { - db = mocks.mockDB() - log = mocks.mockLog() - const config = {} - const routes = makeRoutes({ log, config, db}) - route = getRoute(routes, '/session/destroy') + db = mocks.mockDB(); + log = mocks.mockLog(); + const config = {}; + const routes = makeRoutes({ log, config, db}); + route = getRoute(routes, '/session/destroy'); request = mocks.mockRequest({ credentials: { email: 'foo@example.org', uid: 'foo' }, log: log - }) - }) + }); + }); it('responds correctly when session is destroyed', () => { return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 0) - }) - }) + assert.equal(Object.keys(res).length, 0); + }); + }); it('responds correctly when custom session is destroyed', () => { db.sessionToken = sinon.spy(function () { return P.resolve({ uid: 'foo' - }) - }) + }); + }); request = mocks.mockRequest({ credentials: { email: 'foo@example.org', @@ -337,19 +337,19 @@ describe('/session/destroy', () => { payload: { customSessionToken: 'foo' } - }) + }); return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 0) - }) - }) + assert.equal(Object.keys(res).length, 0); + }); + }); it('throws on invalid session token', () => { db.sessionToken = sinon.spy(function () { return P.resolve({ uid: 'diff-user' - }) - }) + }); + }); request = mocks.mockRequest({ credentials: { email: 'foo@example.org', @@ -359,28 +359,28 @@ describe('/session/destroy', () => { payload: { customSessionToken: 'foo' } - }) + }); return runTest(route, request).then(assert, (err) => { - assert.equal(err.message, 'Invalid session token') - }) - }) + assert.equal(err.message, 'Invalid session token'); + }); + }); -}) +}); describe('/session/duplicate', () => { - let route - let request - let log - let db + let route; + let request; + let log; + let db; beforeEach(() => { db = mocks.mockDB({ - }) - log = mocks.mockLog() - const config = {} - const routes = makeRoutes({log, config, db}) - route = getRoute(routes, '/session/duplicate') + }); + log = mocks.mockLog(); + const config = {}; + const routes = makeRoutes({log, config, db}); + route = getRoute(routes, '/session/duplicate'); request = mocks.mockRequest({ credentials: { uid: 'foo', @@ -405,90 +405,90 @@ describe('/session/duplicate', () => { uaOSVersion: '7', uaDeviceType: 'desktop', uaFormFactor: 'womble' - }) - }) + }); + }); it('correctly duplicates a session token', () => { return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 4, 'response has correct number of keys') - assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid') - assert.ok(res.sessionToken, 'response includes a sessionToken') - assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp') - assert.equal(res.verified, true, 'response includes correctly-copied verification flag') + assert.equal(Object.keys(res).length, 4, 'response has correct number of keys'); + assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid'); + assert.ok(res.sessionToken, 'response includes a sessionToken'); + assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp'); + assert.equal(res.verified, true, 'response includes correctly-copied verification flag'); - assert.equal(db.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - const sessionTokenOptions = db.createSessionToken.args[0][0] - assert.equal(Object.keys(sessionTokenOptions).length, 14, 'was called with correct number of options') - assert.equal(sessionTokenOptions.uid, 'foo', 'db.createSessionToken called with correct uid') - assert.equal(sessionTokenOptions.createdAt, 234567, 'db.createSessionToken called with correct createdAt') - assert.equal(sessionTokenOptions.email, 'foo@example.org', 'db.createSessionToken called with correct email') - assert.equal(sessionTokenOptions.emailCode, 'abcdef', 'db.createSessionToken called with correct emailCode') - assert.equal(sessionTokenOptions.emailVerified, true, 'db.createSessionToken called with correct emailverified') - assert.equal(sessionTokenOptions.verifierSetAt, 123456, 'db.createSessionToken called with correct verifierSetAt') - assert.equal(sessionTokenOptions.locale, 'en-AU', 'db.createSessionToken called with correct locale') - assert.ok(! sessionTokenOptions.mustVerify, 'db.createSessionToken called with falsy mustVerify') - assert.equal(sessionTokenOptions.tokenVerificationId, undefined, 'db.createSessionToken called with correct tokenVerificationId') - assert.equal(sessionTokenOptions.tokenVerificationCode, undefined, 'db.createSessionToken called with correct tokenVerificationCode') - assert.equal(sessionTokenOptions.tokenVerificationCodeExpiresAt, undefined, 'db.createSessionToken called with correct tokenVerificationCodeExpiresAt') - assert.equal(sessionTokenOptions.uaBrowser, 'Chrome', 'db.createSessionToken called with correct uaBrowser') - assert.equal(sessionTokenOptions.uaBrowserVersion, '12', 'db.createSessionToken called with correct uaBrowserVersion') - assert.equal(sessionTokenOptions.uaOS, 'iOS', 'db.createSessionToken called with correct uaOS') - assert.equal(sessionTokenOptions.uaOSVersion, '7', 'db.createSessionToken called with correct uaOSVersion') - assert.equal(sessionTokenOptions.uaDeviceType, 'desktop', 'db.createSessionToken called with correct uaDeviceType') - assert.equal(sessionTokenOptions.uaFormFactor, 'womble', 'db.createSessionToken called with correct uaFormFactor') - }) - }) + assert.equal(db.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + const sessionTokenOptions = db.createSessionToken.args[0][0]; + assert.equal(Object.keys(sessionTokenOptions).length, 14, 'was called with correct number of options'); + assert.equal(sessionTokenOptions.uid, 'foo', 'db.createSessionToken called with correct uid'); + assert.equal(sessionTokenOptions.createdAt, 234567, 'db.createSessionToken called with correct createdAt'); + assert.equal(sessionTokenOptions.email, 'foo@example.org', 'db.createSessionToken called with correct email'); + assert.equal(sessionTokenOptions.emailCode, 'abcdef', 'db.createSessionToken called with correct emailCode'); + assert.equal(sessionTokenOptions.emailVerified, true, 'db.createSessionToken called with correct emailverified'); + assert.equal(sessionTokenOptions.verifierSetAt, 123456, 'db.createSessionToken called with correct verifierSetAt'); + assert.equal(sessionTokenOptions.locale, 'en-AU', 'db.createSessionToken called with correct locale'); + assert.ok(! sessionTokenOptions.mustVerify, 'db.createSessionToken called with falsy mustVerify'); + assert.equal(sessionTokenOptions.tokenVerificationId, undefined, 'db.createSessionToken called with correct tokenVerificationId'); + assert.equal(sessionTokenOptions.tokenVerificationCode, undefined, 'db.createSessionToken called with correct tokenVerificationCode'); + assert.equal(sessionTokenOptions.tokenVerificationCodeExpiresAt, undefined, 'db.createSessionToken called with correct tokenVerificationCodeExpiresAt'); + assert.equal(sessionTokenOptions.uaBrowser, 'Chrome', 'db.createSessionToken called with correct uaBrowser'); + assert.equal(sessionTokenOptions.uaBrowserVersion, '12', 'db.createSessionToken called with correct uaBrowserVersion'); + assert.equal(sessionTokenOptions.uaOS, 'iOS', 'db.createSessionToken called with correct uaOS'); + assert.equal(sessionTokenOptions.uaOSVersion, '7', 'db.createSessionToken called with correct uaOSVersion'); + assert.equal(sessionTokenOptions.uaDeviceType, 'desktop', 'db.createSessionToken called with correct uaDeviceType'); + assert.equal(sessionTokenOptions.uaFormFactor, 'womble', 'db.createSessionToken called with correct uaFormFactor'); + }); + }); it('correctly generates new codes for unverified sessions', () => { - request.auth.credentials.tokenVerified = false - request.auth.credentials.tokenVerificationId = 'myCoolId' - request.auth.credentials.tokenVerificationCode = 'myAwesomerCode' - request.auth.credentials.tokenVerificationCodeExpiresAt = Date.now() + 10000 + request.auth.credentials.tokenVerified = false; + request.auth.credentials.tokenVerificationId = 'myCoolId'; + request.auth.credentials.tokenVerificationCode = 'myAwesomerCode'; + request.auth.credentials.tokenVerificationCodeExpiresAt = Date.now() + 10000; return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 6, 'response has correct number of keys') - assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid') - assert.ok(res.sessionToken, 'response includes a sessionToken') - assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp') - assert.equal(res.verified, false, 'response includes correctly-copied verification flag') - assert.equal(res.verificationMethod, 'email', 'response includes correct verification method') - assert.equal(res.verificationReason, 'login', 'response includes correct verification reason') + assert.equal(Object.keys(res).length, 6, 'response has correct number of keys'); + assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid'); + assert.ok(res.sessionToken, 'response includes a sessionToken'); + assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp'); + assert.equal(res.verified, false, 'response includes correctly-copied verification flag'); + assert.equal(res.verificationMethod, 'email', 'response includes correct verification method'); + assert.equal(res.verificationReason, 'login', 'response includes correct verification reason'); - assert.equal(db.createSessionToken.callCount, 1, 'db.createSessionToken was called once') - const sessionTokenOptions = db.createSessionToken.args[0][0] - assert.equal(Object.keys(sessionTokenOptions).length, 17, 'was called with correct number of options') - assert.equal(sessionTokenOptions.uid, 'foo', 'db.createSessionToken called with correct uid') - assert.equal(sessionTokenOptions.createdAt, 234567, 'db.createSessionToken called with correct createdAt') - assert.equal(sessionTokenOptions.email, 'foo@example.org', 'db.createSessionToken called with correct email') - assert.equal(sessionTokenOptions.emailCode, 'abcdef', 'db.createSessionToken called with correct emailCode') - assert.equal(sessionTokenOptions.emailVerified, true, 'db.createSessionToken called with correct emailverified') - assert.equal(sessionTokenOptions.verifierSetAt, 123456, 'db.createSessionToken called with correct verifierSetAt') - assert.equal(sessionTokenOptions.locale, 'en-AU', 'db.createSessionToken called with correct locale') - assert.ok(! sessionTokenOptions.mustVerify, 'db.createSessionToken called with falsy mustVerify') - assert.ok(sessionTokenOptions.tokenVerificationId, 'db.createSessionToken called with a truthy tokenVerificationId') - assert.notEqual(sessionTokenOptions.tokenVerificationId, 'myCoolId', 'db.createSessionToken called with a new tokenVerificationId') - assert.ok(sessionTokenOptions.tokenVerificationCode, 'db.createSessionToken called with a truthy tokenVerificationCode') - assert.notEqual(sessionTokenOptions.tokenVerificationCode, 'myAwesomerCode', 'db.createSessionToken called with a new tokenVerificationCode') - assert.equal(sessionTokenOptions.tokenVerificationCodeExpiresAt, 0, 'db.createSessionToken called with correct tokenVerificationCodeExpiresAt') - assert.equal(sessionTokenOptions.uaBrowser, 'Chrome', 'db.createSessionToken called with correct uaBrowser') - assert.equal(sessionTokenOptions.uaBrowserVersion, '12', 'db.createSessionToken called with correct uaBrowserVersion') - assert.equal(sessionTokenOptions.uaOS, 'iOS', 'db.createSessionToken called with correct uaOS') - assert.equal(sessionTokenOptions.uaOSVersion, '7', 'db.createSessionToken called with correct uaOSVersion') - assert.equal(sessionTokenOptions.uaDeviceType, 'desktop', 'db.createSessionToken called with correct uaDeviceType') - assert.equal(sessionTokenOptions.uaFormFactor, 'womble', 'db.createSessionToken called with correct uaFormFactor') - }) - }) + assert.equal(db.createSessionToken.callCount, 1, 'db.createSessionToken was called once'); + const sessionTokenOptions = db.createSessionToken.args[0][0]; + assert.equal(Object.keys(sessionTokenOptions).length, 17, 'was called with correct number of options'); + assert.equal(sessionTokenOptions.uid, 'foo', 'db.createSessionToken called with correct uid'); + assert.equal(sessionTokenOptions.createdAt, 234567, 'db.createSessionToken called with correct createdAt'); + assert.equal(sessionTokenOptions.email, 'foo@example.org', 'db.createSessionToken called with correct email'); + assert.equal(sessionTokenOptions.emailCode, 'abcdef', 'db.createSessionToken called with correct emailCode'); + assert.equal(sessionTokenOptions.emailVerified, true, 'db.createSessionToken called with correct emailverified'); + assert.equal(sessionTokenOptions.verifierSetAt, 123456, 'db.createSessionToken called with correct verifierSetAt'); + assert.equal(sessionTokenOptions.locale, 'en-AU', 'db.createSessionToken called with correct locale'); + assert.ok(! sessionTokenOptions.mustVerify, 'db.createSessionToken called with falsy mustVerify'); + assert.ok(sessionTokenOptions.tokenVerificationId, 'db.createSessionToken called with a truthy tokenVerificationId'); + assert.notEqual(sessionTokenOptions.tokenVerificationId, 'myCoolId', 'db.createSessionToken called with a new tokenVerificationId'); + assert.ok(sessionTokenOptions.tokenVerificationCode, 'db.createSessionToken called with a truthy tokenVerificationCode'); + assert.notEqual(sessionTokenOptions.tokenVerificationCode, 'myAwesomerCode', 'db.createSessionToken called with a new tokenVerificationCode'); + assert.equal(sessionTokenOptions.tokenVerificationCodeExpiresAt, 0, 'db.createSessionToken called with correct tokenVerificationCodeExpiresAt'); + assert.equal(sessionTokenOptions.uaBrowser, 'Chrome', 'db.createSessionToken called with correct uaBrowser'); + assert.equal(sessionTokenOptions.uaBrowserVersion, '12', 'db.createSessionToken called with correct uaBrowserVersion'); + assert.equal(sessionTokenOptions.uaOS, 'iOS', 'db.createSessionToken called with correct uaOS'); + assert.equal(sessionTokenOptions.uaOSVersion, '7', 'db.createSessionToken called with correct uaOSVersion'); + assert.equal(sessionTokenOptions.uaDeviceType, 'desktop', 'db.createSessionToken called with correct uaDeviceType'); + assert.equal(sessionTokenOptions.uaFormFactor, 'womble', 'db.createSessionToken called with correct uaFormFactor'); + }); + }); it('correctly reports verification reason for unverified emails', () => { - request.auth.credentials.emailVerified = false + request.auth.credentials.emailVerified = false; return runTest(route, request).then((res) => { - assert.equal(Object.keys(res).length, 6, 'response has correct number of keys') - assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid') - assert.ok(res.sessionToken, 'response includes a sessionToken') - assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp') - assert.equal(res.verified, false, 'response includes correctly-copied verification flag') - assert.equal(res.verificationMethod, 'email', 'response includes correct verification method') - assert.equal(res.verificationReason, 'signup', 'response includes correct verification reason') - }) - }) + assert.equal(Object.keys(res).length, 6, 'response has correct number of keys'); + assert.equal(res.uid, request.auth.credentials.uid, 'response includes correctly-copied uid'); + assert.ok(res.sessionToken, 'response includes a sessionToken'); + assert.equal(res.authAt, request.auth.credentials.createdAt, 'response includes correctly-copied auth timestamp'); + assert.equal(res.verified, false, 'response includes correctly-copied verification flag'); + assert.equal(res.verificationMethod, 'email', 'response includes correct verification method'); + assert.equal(res.verificationReason, 'signup', 'response includes correct verification reason'); + }); + }); -}) +}); diff --git a/test/local/routes/sign.js b/test/local/routes/sign.js index 4b4d7d91..a9ac7fba 100644 --- a/test/local/routes/sign.js +++ b/test/local/routes/sign.js @@ -2,34 +2,34 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const crypto = require('crypto') -const uuid = require('uuid') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const P = require(`${ROOT_DIR}/lib/promise`) -const error = require(`${ROOT_DIR}/lib/error`) +const { assert } = require('chai'); +const crypto = require('crypto'); +const uuid = require('uuid'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const P = require(`${ROOT_DIR}/lib/promise`); +const error = require(`${ROOT_DIR}/lib/error`); describe('/certificate/sign', () => { - let db, deviceId, mockDevices, mockLog, sessionToken, mockRequest + let db, deviceId, mockDevices, mockLog, sessionToken, mockRequest; beforeEach(() => { - db = mocks.mockDB() - deviceId = crypto.randomBytes(16).toString('hex') + db = mocks.mockDB(); + deviceId = crypto.randomBytes(16).toString('hex'); mockDevices = mocks.mockDevices({ deviceId: deviceId - }) - mockLog = mocks.mockLog() - const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog) + }); + mockLog = mocks.mockLog(); + const Token = require(`${ROOT_DIR}/lib/tokens/token`)(mockLog); const SessionToken = require(`${ROOT_DIR}/lib/tokens/session_token`)(mockLog, Token, { tokenLifetimes: { sessionTokenWithoutDevice: 2419200000 } - }) + }); return SessionToken.create({ uid: uuid.v4('binary').toString('hex'), email: 'foo@example.com', @@ -43,8 +43,8 @@ describe('/certificate/sign', () => { uaFormFactor: 'qux' }) .then(result => { - assert.equal(result.lastAccessTime, undefined, 'lastAccessTime is not set') - sessionToken = result + assert.equal(result.lastAccessTime, undefined, 'lastAccessTime is not set'); + sessionToken = result; mockRequest = mocks.mockRequest({ credentials: sessionToken, headers: { @@ -64,9 +64,9 @@ describe('/certificate/sign', () => { } }, query: {} - }) - }) - }) + }); + }); + }); it('without service', function () { return runTest({ @@ -74,36 +74,36 @@ describe('/certificate/sign', () => { devices: mockDevices, log: mockLog }, mockRequest, function () { - assert.equal(db.touchSessionToken.callCount, 1, 'db.touchSessionToken was called once') - let args = db.touchSessionToken.args[0] - assert.equal(args.length, 2, 'db.touchSessionToken was passed two arguments') - assert.equal(args[0].uid, sessionToken.uid, 'first argument uid property was correct') - assert.equal(args[0].email, sessionToken.email, 'first argument email property was correct') - assert.equal(args[0].uaBrowser, 'Firefox', 'first argument uaBrowser property was correct') - assert.equal(args[0].uaBrowserVersion, '55', 'first argument uaBrowserVersion property was correct') - assert.equal(args[0].uaOS, 'Windows', 'first argument uaOS property was correct') - assert.equal(args[0].uaOSVersion, '10', 'first argument uaOSVersion property was correct') - assert.equal(args[0].uaDeviceType, null, 'first argument uaDeviceType property was null') - assert.equal(args[0].uaFormFactor, null, 'first argument uaFormFactor property was null') - assert.ok(args[0].lastAccessTime, 'lastAccessTime is set') - assert.ok(args[0].lastAccessTime > args[0].createdAt, 'lastAccessTime is updated') - assert.equal(args[1], mockRequest.app.geo, 'second argument was geo data') + assert.equal(db.touchSessionToken.callCount, 1, 'db.touchSessionToken was called once'); + let args = db.touchSessionToken.args[0]; + assert.equal(args.length, 2, 'db.touchSessionToken was passed two arguments'); + assert.equal(args[0].uid, sessionToken.uid, 'first argument uid property was correct'); + assert.equal(args[0].email, sessionToken.email, 'first argument email property was correct'); + assert.equal(args[0].uaBrowser, 'Firefox', 'first argument uaBrowser property was correct'); + assert.equal(args[0].uaBrowserVersion, '55', 'first argument uaBrowserVersion property was correct'); + assert.equal(args[0].uaOS, 'Windows', 'first argument uaOS property was correct'); + assert.equal(args[0].uaOSVersion, '10', 'first argument uaOSVersion property was correct'); + assert.equal(args[0].uaDeviceType, null, 'first argument uaDeviceType property was null'); + assert.equal(args[0].uaFormFactor, null, 'first argument uaFormFactor property was null'); + assert.ok(args[0].lastAccessTime, 'lastAccessTime is set'); + assert.ok(args[0].lastAccessTime > args[0].createdAt, 'lastAccessTime is updated'); + assert.equal(args[1], mockRequest.app.geo, 'second argument was geo data'); - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - args = mockDevices.upsert.args[0] - assert.equal(args.length, 3, 'devices.upsert was passed one argument') - assert.equal(args[0], mockRequest, 'first argument was request object') - assert.equal(args[1], mockRequest.auth.credentials, 'second argument was sessionToken') + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + args = mockDevices.upsert.args[0]; + assert.equal(args.length, 3, 'devices.upsert was passed one argument'); + assert.equal(args[0], mockRequest, 'first argument was request object'); + assert.equal(args[1], mockRequest.auth.credentials, 'second argument was sessionToken'); assert.deepEqual(args[2], { uaBrowser: 'Firefox', uaBrowserVersion: '55', uaOS: 'Windows', uaOSVersion: '10', - }, 'third argument was UA info') + }, 'third argument was UA info'); - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - args = mockLog.activityEvent.args[0] - assert.equal(args.length, 1, 'log.activityEvent was passed one argument') + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + args = mockLog.activityEvent.args[0]; + assert.equal(args.length, 1, 'log.activityEvent was passed one argument'); assert.deepEqual(args[0], { country: 'United States', device_id: deviceId.toString('hex'), @@ -112,118 +112,118 @@ describe('/certificate/sign', () => { service: undefined, uid: mockRequest.auth.credentials.uid.toString('hex'), userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0' - }, 'argument was event data') - }) - }) + }, 'argument was event data'); + }); + }); it('with service=sync', () => { - mockRequest.query.service = 'sync' + mockRequest.query.service = 'sync'; return runTest({ devices: mockDevices, log: mockLog }, mockRequest, function () { - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - }) - }) + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + }); + }); it('with service=foo', () => { - mockRequest.query.service = 'foo' + mockRequest.query.service = 'foo'; return runTest({ devices: mockDevices, log: mockLog }, mockRequest, function () { - assert.equal(mockDevices.upsert.callCount, 0, 'devices.upsert was not called') - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(mockLog.activityEvent.args[0][0].device_id, undefined, 'device_id was undefined') - }) - }) + assert.equal(mockDevices.upsert.callCount, 0, 'devices.upsert was not called'); + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(mockLog.activityEvent.args[0][0].device_id, undefined, 'device_id was undefined'); + }); + }); it('with deviceId', () => { - mockRequest.query.service = 'sync' - mockRequest.auth.credentials.deviceId = crypto.randomBytes(16).toString('hex') + mockRequest.query.service = 'sync'; + mockRequest.auth.credentials.deviceId = crypto.randomBytes(16).toString('hex'); return runTest({ devices: mockDevices, log: mockLog }, mockRequest, function () { - assert.equal(mockDevices.upsert.callCount, 0, 'devices.upsert was not called') - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(mockLog.activityEvent.args[0][0].device_id, mockRequest.auth.credentials.deviceId.toString('hex'), 'device_id was correct') - }) - }) + assert.equal(mockDevices.upsert.callCount, 0, 'devices.upsert was not called'); + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(mockLog.activityEvent.args[0][0].device_id, mockRequest.auth.credentials.deviceId.toString('hex'), 'device_id was correct'); + }); + }); it('with concurrent registration of a device record', () => { - mockRequest.query.service = 'sync' - const conflictingDeviceId = crypto.randomBytes(16).toString('hex') + mockRequest.query.service = 'sync'; + const conflictingDeviceId = crypto.randomBytes(16).toString('hex'); mockDevices = mocks.mockDevices({}, { upsert: error.deviceSessionConflict(conflictingDeviceId) - }) + }); return runTest({ devices: mockDevices, log: mockLog }, mockRequest, () => { - assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once') - assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once') - assert.equal(mockLog.activityEvent.args[0][0].device_id, conflictingDeviceId, 'device_id was correct') - }) - }) + assert.equal(mockDevices.upsert.callCount, 1, 'devices.upsert was called once'); + assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once'); + assert.equal(mockLog.activityEvent.args[0][0].device_id, conflictingDeviceId, 'device_id was correct'); + }); + }); it('with session that requires verification', () => { - mockRequest.query.service = 'foo' - mockRequest.auth.credentials.mustVerify = true - mockRequest.auth.credentials.tokenVerified = false + mockRequest.query.service = 'foo'; + mockRequest.auth.credentials.mustVerify = true; + mockRequest.auth.credentials.tokenVerified = false; return runTest({ log: mockLog }, mockRequest, (r) => { - assert.fail('should have errored') + assert.fail('should have errored'); }, (e) => { - assert.equal(e.errno, 138, 'failed due to unverified session') - }) - }) + assert.equal(e.errno, 138, 'failed due to unverified session'); + }); + }); it('with unverified session that does not require verification', () => { - mockRequest.query.service = 'foo' - mockRequest.auth.credentials.mustVerify = false - mockRequest.auth.credentials.tokenVerified = false + mockRequest.query.service = 'foo'; + mockRequest.auth.credentials.mustVerify = false; + mockRequest.auth.credentials.tokenVerified = false; return runTest({ log: mockLog }, mockRequest, (res) => { - assert.ok(res) - }) - }) + assert.ok(res); + }); + }); function runTest (options, request, onSuccess, onError) { return new P(function (resolve, reject) { try { - const response = getRoute(makeRoutes(options), '/certificate/sign').handler(request) + const response = getRoute(makeRoutes(options), '/certificate/sign').handler(request); if (response instanceof Error) { - reject(response) + reject(response); } else { - resolve(response) + resolve(response); } } catch (e) { - reject(e) + reject(e); } }) .then(onSuccess) - .catch(onError) + .catch(onError); } function makeRoutes (options = {}) { - var log = options.log || mocks.mockLog() + var log = options.log || mocks.mockLog(); return require('../../../lib/routes/sign')( log, options.signer || { sign: function () { - return P.resolve({}) + return P.resolve({}); } }, options.db || { @@ -232,7 +232,7 @@ describe('/certificate/sign', () => { }, options.domain || 'wibble', options.devices - ) + ); } -}) +}); diff --git a/test/local/routes/signin-codes.js b/test/local/routes/signin-codes.js index 5dea33e9..5d289eee 100644 --- a/test/local/routes/signin-codes.js +++ b/test/local/routes/signin-codes.js @@ -2,160 +2,160 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') +const { assert } = require('chai'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); describe('/signinCodes/consume:', () => { - let log, db, customs, routes, route, request, response + let log, db, customs, routes, route, request, response; describe('success, db does not return flowId:', () => { - beforeEach(() => setup({ db: { email: 'foo@bar' } }).then(r => response = r)) + beforeEach(() => setup({ db: { email: 'foo@bar' } }).then(r => response = r)); it('returned the correct response', () => { - assert.deepEqual(response, { email: 'foo@bar' }) - }) + assert.deepEqual(response, { email: 'foo@bar' }); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'signinCodes.consume') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'signinCodes.consume'); + assert.equal(args[1], request); + }); it('called request.validateMetricsContext correctly', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - const args = request.validateMetricsContext.args[0] - assert.equal(args.length, 0) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + const args = request.validateMetricsContext.args[0]; + assert.equal(args.length, 0); + }); it('called customs.checkIpOnly correctly', () => { - assert.equal(customs.checkIpOnly.callCount, 1) - const args = customs.checkIpOnly.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], request) - assert.equal(args[1], 'consumeSigninCode') - }) + assert.equal(customs.checkIpOnly.callCount, 1); + const args = customs.checkIpOnly.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], request); + assert.equal(args[1], 'consumeSigninCode'); + }); it('called db.consumeSigninCode correctly', () => { - assert.equal(db.consumeSigninCode.callCount, 1) - const args = db.consumeSigninCode.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], 'fbefff7dfd') - }) + assert.equal(db.consumeSigninCode.callCount, 1); + const args = db.consumeSigninCode.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], 'fbefff7dfd'); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 1) + assert.equal(log.flowEvent.callCount, 1); - const args = log.flowEvent.args[0] - assert.equal(args.length, 1) - assert.equal(args[0].event, 'signinCode.consumed') - assert.equal(args[0].flow_id, request.payload.metricsContext.flowId) - }) - }) + const args = log.flowEvent.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0].event, 'signinCode.consumed'); + assert.equal(args[0].flow_id, request.payload.metricsContext.flowId); + }); + }); describe('success, db returns flowId:', () => { - beforeEach(() => setup({ db: { email: 'foo@bar', flowId: 'baz' } }).then(r => response = r)) + beforeEach(() => setup({ db: { email: 'foo@bar', flowId: 'baz' } }).then(r => response = r)); it('returned the correct response', () => { - assert.deepEqual(response, { email: 'foo@bar' }) - }) + assert.deepEqual(response, { email: 'foo@bar' }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called customs.checkIpOnly once', () => { - assert.equal(customs.checkIpOnly.callCount, 1) - }) + assert.equal(customs.checkIpOnly.callCount, 1); + }); it('called db.consumeSigninCode once', () => { - assert.equal(db.consumeSigninCode.callCount, 1) - }) + assert.equal(db.consumeSigninCode.callCount, 1); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) + assert.equal(log.flowEvent.callCount, 2); - let args = log.flowEvent.args[0] - assert.equal(args.length, 1) - assert.equal(args[0].event, 'signinCode.consumed') - assert.equal(args[0].flow_id, request.payload.metricsContext.flowId) + let args = log.flowEvent.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0].event, 'signinCode.consumed'); + assert.equal(args[0].flow_id, request.payload.metricsContext.flowId); - args = log.flowEvent.args[1] - assert.equal(args.length, 1) - assert.equal(args[0].event, 'flow.continued.baz') - assert.equal(args[0].flow_id, request.payload.metricsContext.flowId) - }) - }) + args = log.flowEvent.args[1]; + assert.equal(args.length, 1); + assert.equal(args[0].event, 'flow.continued.baz'); + assert.equal(args[0].flow_id, request.payload.metricsContext.flowId); + }); + }); describe('db error:', () => { beforeEach(() => setup(null, { db: { consumeSigninCode: new Error('foo') } }) - .catch((err) => { assert(err.message, 'foo') })) + .catch((err) => { assert(err.message, 'foo'); })); it('called log.begin', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called customs.checkIpOnly', () => { - assert.equal(customs.checkIpOnly.callCount, 1) - }) + assert.equal(customs.checkIpOnly.callCount, 1); + }); it('called db.consumeSigninCode', () => { - assert.equal(db.consumeSigninCode.callCount, 1) - }) + assert.equal(db.consumeSigninCode.callCount, 1); + }); it('did not call log.flowEvent', () => { - assert.equal(log.flowEvent.callCount, 0) - }) - }) + assert.equal(log.flowEvent.callCount, 0); + }); + }); describe('customs error:', () => { beforeEach(() => setup(null, { customs: { checkIpOnly: new Error('foo') } }) - .catch((err) => { assert(err.message, 'foo') }) - ) + .catch((err) => { assert(err.message, 'foo'); }) + ); it('called log.begin', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called customs.checkIpOnly', () => { - assert.equal(customs.checkIpOnly.callCount, 1) - }) + assert.equal(customs.checkIpOnly.callCount, 1); + }); it('did not call db.consumeSigninCode', () => { - assert.equal(db.consumeSigninCode.callCount, 0) - }) + assert.equal(db.consumeSigninCode.callCount, 0); + }); it('did not call log.flowEvent', () => { - assert.equal(log.flowEvent.callCount, 0) - }) - }) + assert.equal(log.flowEvent.callCount, 0); + }); + }); function setup (results, errors) { - results = results || {} - errors = errors || {} + results = results || {}; + errors = errors || {}; - log = mocks.mockLog() - db = mocks.mockDB(results.db, errors.db) - customs = mocks.mockCustoms(errors.customs) - routes = makeRoutes({ log, db, customs }) - route = getRoute(routes, '/signinCodes/consume') + log = mocks.mockLog(); + db = mocks.mockDB(results.db, errors.db); + customs = mocks.mockCustoms(errors.customs); + routes = makeRoutes({ log, db, customs }); + route = getRoute(routes, '/signinCodes/consume'); request = mocks.mockRequest({ log: log, payload: { @@ -165,18 +165,18 @@ describe('/signinCodes/consume:', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' } } - }) - return runTest(route, request) + }); + return runTest(route, request); } -}) +}); function makeRoutes (options = {}) { - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() - const customs = options.customs || mocks.mockCustoms() - return require('../../../lib/routes/signin-codes')(log, db, customs) + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); + const customs = options.customs || mocks.mockCustoms(); + return require('../../../lib/routes/signin-codes')(log, db, customs); } function runTest (route, request) { - return route.handler(request) + return route.handler(request); } diff --git a/test/local/routes/sms.js b/test/local/routes/sms.js index 6e7c6dac..b7b5eeda 100644 --- a/test/local/routes/sms.js +++ b/test/local/routes/sms.js @@ -2,44 +2,44 @@ * 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/. */ -'use strict' +'use strict'; -const AppError = require('../../../lib/error') -const { assert } = require('chai') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const P = require('../../../lib/promise') -const sinon = require('sinon') +const AppError = require('../../../lib/error'); +const { assert } = require('chai'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); +const sinon = require('sinon'); function makeRoutes (options = {}, dependencies) { - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() - return require('../../../lib/routes/sms')(log, db, options.config, mocks.mockCustoms(), options.sms) + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); + return require('../../../lib/routes/sms')(log, db, options.config, mocks.mockCustoms(), options.sms); } function runTest (route, request) { - return route.handler(request) + return route.handler(request); } describe('/sms with the signinCodes feature included in the payload', () => { - let log, signinCode, db, config, sms, routes, route, request, response + let log, signinCode, db, config, sms, routes, route, request, response; beforeEach(() => { - log = mocks.mockLog() - signinCode = Buffer.from('++//ff0=', 'base64') - db = mocks.mockDB({ signinCode }) + log = mocks.mockLog(); + signinCode = Buffer.from('++//ff0=', 'base64'); + db = mocks.mockDB({ signinCode }); config = { sms: { enabled: true, countryCodes: [ 'AT', 'CA', 'DE', 'GB', 'US' ], isStatusGeoEnabled: true } - } + }; sms = { send: sinon.spy(() => P.resolve()) - } - routes = makeRoutes({ log, db, config, sms }) - route = getRoute(routes, '/sms') + }; + routes = makeRoutes({ log, db, config, sms }); + route = getRoute(routes, '/sms'); request = mocks.mockRequest({ credentials: { email: 'foo@example.org', @@ -54,366 +54,366 @@ describe('/sms with the signinCodes feature included in the payload', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' } } - }) - }) + }); + }); describe('sms.send succeeds', () => { beforeEach(() => { - sms.send = sinon.spy(() => P.resolve()) - }) + sms.send = sinon.spy(() => P.resolve()); + }); describe('USA phone number', () => { beforeEach(() => { - request.payload.phoneNumber = '+18885083401' + request.payload.phoneNumber = '+18885083401'; return runTest(route, request) - .then((_response) => response = _response) - }) + .then((_response) => response = _response); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'sms.send') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'sms.send'); + assert.equal(args[1], request); + }); it('called request.validateMetricsContext correctly', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - const args = request.validateMetricsContext.args[0] - assert.equal(args.length, 0) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + const args = request.validateMetricsContext.args[0]; + assert.equal(args.length, 0); + }); it('called db.createSigninCode correctly', () => { - assert.equal(db.createSigninCode.callCount, 1) - const args = db.createSigninCode.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'bar') - assert.equal(args[1], request.payload.metricsContext.flowId) - }) + assert.equal(db.createSigninCode.callCount, 1); + const args = db.createSigninCode.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'bar'); + assert.equal(args[1], request.payload.metricsContext.flowId); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - const args = sms.send.args[0] - assert.equal(args.length, 4) - assert.equal(args[0], '+18885083401') - assert.equal(args[1], 'installFirefox') - assert.equal(args[2], 'en-US') - assert.equal(args[3], signinCode) - }) + assert.equal(sms.send.callCount, 1); + const args = sms.send.args[0]; + assert.equal(args.length, 4); + assert.equal(args[0], '+18885083401'); + assert.equal(args[1], 'installFirefox'); + assert.equal(args[2], 'en-US'); + assert.equal(args[3], signinCode); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) + assert.equal(log.flowEvent.callCount, 2); - let args = log.flowEvent.args[0] - assert.equal(args.length, 1) - assert.equal(args[0].event, 'sms.region.US') - assert.equal(args[0].flow_id, request.payload.metricsContext.flowId) + let args = log.flowEvent.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0].event, 'sms.region.US'); + assert.equal(args[0].flow_id, request.payload.metricsContext.flowId); - args = log.flowEvent.args[1] - assert.equal(args.length, 1) - assert.equal(args[0].event, 'sms.installFirefox.sent') - assert.equal(args[0].flow_id, request.payload.metricsContext.flowId) - }) + args = log.flowEvent.args[1]; + assert.equal(args.length, 1); + assert.equal(args[0].event, 'sms.installFirefox.sent'); + assert.equal(args[0].flow_id, request.payload.metricsContext.flowId); + }); it('returns a formattedPhoneNumber', () => { - assert.equal(response.formattedPhoneNumber, '888-508-3401') - }) - }) + assert.equal(response.formattedPhoneNumber, '888-508-3401'); + }); + }); describe('Canada phone number', () => { beforeEach(() => { - request.payload.phoneNumber = '+14168483114' + request.payload.phoneNumber = '+14168483114'; return runTest(route, request) - .then((_response) => response = _response) - }) + .then((_response) => response = _response); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called db.createSigninCode once', () => { - assert.equal(db.createSigninCode.callCount, 1) - }) + assert.equal(db.createSigninCode.callCount, 1); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - const args = sms.send.args[0] - assert.equal(args[0], '+14168483114') - }) + assert.equal(sms.send.callCount, 1); + const args = sms.send.args[0]; + assert.equal(args[0], '+14168483114'); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) - assert.equal(log.flowEvent.args[0][0].event, 'sms.region.CA') - }) + assert.equal(log.flowEvent.callCount, 2); + assert.equal(log.flowEvent.args[0][0].event, 'sms.region.CA'); + }); it('returns a formattedPhoneNumber', () => { - assert.equal(response.formattedPhoneNumber, '416-848-3114') - }) - }) + assert.equal(response.formattedPhoneNumber, '416-848-3114'); + }); + }); describe('UK phone number', () => { beforeEach(() => { - request.payload.phoneNumber = '+442078553000' + request.payload.phoneNumber = '+442078553000'; return runTest(route, request) - .then((_response) => response = _response) - }) + .then((_response) => response = _response); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called db.createSigninCode once', () => { - assert.equal(db.createSigninCode.callCount, 1) - }) + assert.equal(db.createSigninCode.callCount, 1); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - const args = sms.send.args[0] - assert.equal(args[0], '+442078553000') - }) + assert.equal(sms.send.callCount, 1); + const args = sms.send.args[0]; + assert.equal(args[0], '+442078553000'); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) - assert.equal(log.flowEvent.args[0][0].event, 'sms.region.GB') - }) + assert.equal(log.flowEvent.callCount, 2); + assert.equal(log.flowEvent.args[0][0].event, 'sms.region.GB'); + }); it('returns a formattedPhoneNumber', () => { - assert.equal(response.formattedPhoneNumber, '20 7855 3000') - }) - }) + assert.equal(response.formattedPhoneNumber, '20 7855 3000'); + }); + }); describe('AT phone number', () => { beforeEach(() => { - request.payload.phoneNumber = '+43676641643' + request.payload.phoneNumber = '+43676641643'; return runTest(route, request) - .then((_response) => response = _response) - }) + .then((_response) => response = _response); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - const args = sms.send.args[0] - assert.equal(args[0], '+43676641643') - }) + assert.equal(sms.send.callCount, 1); + const args = sms.send.args[0]; + assert.equal(args[0], '+43676641643'); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) - assert.equal(log.flowEvent.args[0][0].event, 'sms.region.AT') - }) + assert.equal(log.flowEvent.callCount, 2); + assert.equal(log.flowEvent.args[0][0].event, 'sms.region.AT'); + }); it('returns a formattedPhoneNumber', () => { - assert.equal(response.formattedPhoneNumber, '676 641643') - }) - }) + assert.equal(response.formattedPhoneNumber, '676 641643'); + }); + }); describe('DE phone number', () => { beforeEach(() => { - request.payload.phoneNumber = '+49015153563252' + request.payload.phoneNumber = '+49015153563252'; return runTest(route, request) - .then((_response) => response = _response) - }) + .then((_response) => response = _response); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - const args = sms.send.args[0] - assert.equal(args[0], '+49015153563252') - }) + assert.equal(sms.send.callCount, 1); + const args = sms.send.args[0]; + assert.equal(args[0], '+49015153563252'); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 2) - assert.equal(log.flowEvent.args[0][0].event, 'sms.region.DE') - }) + assert.equal(log.flowEvent.callCount, 2); + assert.equal(log.flowEvent.args[0][0].event, 'sms.region.DE'); + }); it('returns a formattedPhoneNumber', () => { - assert.equal(response.formattedPhoneNumber, '1515 3563252') - }) - }) + assert.equal(response.formattedPhoneNumber, '1515 3563252'); + }); + }); describe('invalid phone number', () => { - let err + let err; beforeEach(() => { - request.payload.phoneNumber = '+15551234567' + request.payload.phoneNumber = '+15551234567'; return runTest(route, request) .catch(e => { - err = e - }) - }) + err = e; + }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('did not call db.createSigninCode', () => { - assert.equal(db.createSigninCode.callCount, 0) - }) + assert.equal(db.createSigninCode.callCount, 0); + }); it('did not call sms.send', () => { - assert.equal(sms.send.callCount, 0) - }) + assert.equal(sms.send.callCount, 0); + }); it('did not call log.flowEvent', () => { - assert.equal(log.flowEvent.callCount, 0) - }) + assert.equal(log.flowEvent.callCount, 0); + }); it('threw the correct error data', () => { - assert.ok(err instanceof AppError) - assert.equal(err.errno, AppError.ERRNO.INVALID_PHONE_NUMBER) - assert.equal(err.message, 'Invalid phone number') - }) - }) + assert.ok(err instanceof AppError); + assert.equal(err.errno, AppError.ERRNO.INVALID_PHONE_NUMBER); + assert.equal(err.message, 'Invalid phone number'); + }); + }); describe('invalid region', () => { - let err + let err; beforeEach(() => { - request.payload.phoneNumber = '+886287861100' + request.payload.phoneNumber = '+886287861100'; return runTest(route, request) .catch(e => { - err = e - }) - }) + err = e; + }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('did not call db.createSigninCode', () => { - assert.equal(db.createSigninCode.callCount, 0) - }) + assert.equal(db.createSigninCode.callCount, 0); + }); it('did not call sms.send', () => { - assert.equal(sms.send.callCount, 0) - }) + assert.equal(sms.send.callCount, 0); + }); it('called log.flowEvent correctly', () => { - assert.equal(log.flowEvent.callCount, 1) - assert.equal(log.flowEvent.args[0][0].event, 'sms.region.TW') - }) + assert.equal(log.flowEvent.callCount, 1); + assert.equal(log.flowEvent.args[0][0].event, 'sms.region.TW'); + }); it('threw the correct error data', () => { - assert.ok(err instanceof AppError) - assert.equal(err.errno, AppError.ERRNO.INVALID_REGION) - assert.equal(err.message, 'Invalid region') - assert.equal(err.output.payload.region, 'TW') - }) - }) + assert.ok(err instanceof AppError); + assert.equal(err.errno, AppError.ERRNO.INVALID_REGION); + assert.equal(err.message, 'Invalid region'); + assert.equal(err.output.payload.region, 'TW'); + }); + }); describe('too-short phone number', () => { - let err + let err; beforeEach(() => { - request.payload.phoneNumber = '+18' + request.payload.phoneNumber = '+18'; return runTest(route, request) .catch(e => { - err = e - }) - }) + err = e; + }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('did not call db.createSigninCode', () => { - assert.equal(db.createSigninCode.callCount, 0) - }) + assert.equal(db.createSigninCode.callCount, 0); + }); it('did not call sms.send', () => { - assert.equal(sms.send.callCount, 0) - }) + assert.equal(sms.send.callCount, 0); + }); it('did not call log.flowEvent', () => { - assert.equal(log.flowEvent.callCount, 0) - }) + assert.equal(log.flowEvent.callCount, 0); + }); it('threw the correct error data', () => { - assert.instanceOf(err, AppError) - assert.equal(err.errno, AppError.ERRNO.INVALID_PHONE_NUMBER) - assert.equal(err.message, 'Invalid phone number') - }) - }) - }) + assert.instanceOf(err, AppError); + assert.equal(err.errno, AppError.ERRNO.INVALID_PHONE_NUMBER); + assert.equal(err.message, 'Invalid phone number'); + }); + }); + }); describe('sms.send fails', () => { - let err + let err; beforeEach(() => { - sms.send = sinon.spy(() => P.reject(AppError.messageRejected('wibble', 7))) - request.payload.phoneNumber = '+18885083401' + sms.send = sinon.spy(() => P.reject(AppError.messageRejected('wibble', 7))); + request.payload.phoneNumber = '+18885083401'; return runTest(route, request) .catch(e => { - err = e - }) - }) + err = e; + }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext once', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('called db.createSigninCode once', () => { - assert.equal(db.createSigninCode.callCount, 1) - }) + assert.equal(db.createSigninCode.callCount, 1); + }); it('called sms.send once', () => { - assert.equal(sms.send.callCount, 1) - }) + assert.equal(sms.send.callCount, 1); + }); it('called log.flowEvent once', () => { - assert.equal(log.flowEvent.callCount, 1) - }) + assert.equal(log.flowEvent.callCount, 1); + }); it('threw the correct error data', () => { - assert.ok(err instanceof AppError) - assert.equal(err.errno, AppError.ERRNO.MESSAGE_REJECTED) - assert.equal(err.message, 'Message rejected') - assert.equal(err.output.payload.reason, 'wibble') - assert.equal(err.output.payload.reasonCode, 7) - }) - }) -}) + assert.ok(err instanceof AppError); + assert.equal(err.errno, AppError.ERRNO.MESSAGE_REJECTED); + assert.equal(err.message, 'Message rejected'); + assert.equal(err.output.payload.reason, 'wibble'); + assert.equal(err.output.payload.reasonCode, 7); + }); + }); +}); describe('/sms without the signinCodes feature included in the payload', () => { - let log, signinCode, db, config, sms, routes, route, request + let log, signinCode, db, config, sms, routes, route, request; beforeEach(() => { - log = mocks.mockLog() - signinCode = Buffer.from('++//ff0=', 'base64') - db = mocks.mockDB({ signinCode }) + log = mocks.mockLog(); + signinCode = Buffer.from('++//ff0=', 'base64'); + db = mocks.mockDB({ signinCode }); config = { sms: { enabled: true, countryCodes: [ 'CA', 'GB', 'US' ], isStatusGeoEnabled: true } - } + }; sms = { send: sinon.spy(() => P.resolve()) - } - routes = makeRoutes({ log, db, config, sms }) - route = getRoute(routes, '/sms') + }; + routes = makeRoutes({ log, db, config, sms }); + route = getRoute(routes, '/sms'); request = mocks.mockRequest({ credentials: { email: 'foo@example.org', @@ -428,58 +428,58 @@ describe('/sms without the signinCodes feature included in the payload', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' } } - }) - sms.send = sinon.spy(() => P.resolve()) - return runTest(route, request) - }) + }); + sms.send = sinon.spy(() => P.resolve()); + return runTest(route, request); + }); it('called log.begin', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called request.validateMetricsContext', () => { - assert.equal(request.validateMetricsContext.callCount, 1) - }) + assert.equal(request.validateMetricsContext.callCount, 1); + }); it('did not call db.createSigninCode', () => { - assert.equal(db.createSigninCode.callCount, 0) - }) + assert.equal(db.createSigninCode.callCount, 0); + }); it('called sms.send correctly', () => { - assert.equal(sms.send.callCount, 1) - assert.equal(sms.send.args[0][4], undefined) - }) + assert.equal(sms.send.callCount, 1); + assert.equal(sms.send.args[0][4], undefined); + }); it('called log.flowEvent', () => { - assert.equal(log.flowEvent.callCount, 2) - }) -}) + assert.equal(log.flowEvent.callCount, 2); + }); +}); describe('/sms disabled', () => { - let log, config, routes + let log, config, routes; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); config = { sms: { enabled: false, isStatusGeoEnabled: true } - } - routes = makeRoutes({ log, config }) - }) + }; + routes = makeRoutes({ log, config }); + }); it('routes was empty array', () => { - assert.ok(Array.isArray(routes)) - assert.equal(routes.length, 0) - }) -}) + assert.ok(Array.isArray(routes)); + assert.equal(routes.length, 0); + }); +}); describe('/sms/status', () => { - let log, config, sms, routes, route + let log, config, sms, routes, route; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); config = { sms: { apiRegion: 'us-east-1', @@ -488,16 +488,16 @@ describe('/sms/status', () => { isStatusGeoEnabled: true }, smtp: {} - } + }; sms = { isBudgetOk: () => true - } - routes = makeRoutes({ log, config, sms }) - route = getRoute(routes, '/sms/status') - }) + }; + routes = makeRoutes({ log, config, sms }); + route = getRoute(routes, '/sms/status'); + }); describe('country is US', () => { - let request, response + let request, response; beforeEach(() => { request = mocks.mockRequest({ @@ -505,30 +505,30 @@ describe('/sms/status', () => { email: 'foo@example.org' }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: true, country: 'US' }) - }) + assert.deepEqual(response, { ok: true, country: 'US' }); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'sms.status') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'sms.status'); + assert.equal(args[1], request); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('country is CA', () => { - let request, response + let request, response; beforeEach(() => { request = mocks.mockRequest({ @@ -541,54 +541,54 @@ describe('/sms/status', () => { } }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: false, country: 'CA' }) - }) + assert.deepEqual(response, { ok: false, country: 'CA' }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('spend is over budget', () => { - let request, response + let request, response; beforeEach(() => { - sms.isBudgetOk = () => false + sms.isBudgetOk = () => false; request = mocks.mockRequest({ credentials: { email: 'foo@example.org' }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: false, country: 'US' }) - }) + assert.deepEqual(response, { ok: false, country: 'US' }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('missing location', () => { - let request, response + let request, response; beforeEach(() => { request = mocks.mockRequest({ @@ -597,32 +597,32 @@ describe('/sms/status', () => { }, geo: {}, log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: false, country: undefined }) - }) + assert.deepEqual(response, { ok: false, country: undefined }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'sms.getGeoData') + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'sms.getGeoData'); assert.deepEqual(args[1], { err: 'missing location data' - }) - }) - }) + }); + }); + }); describe('missing country', () => { - let request, response + let request, response; beforeEach(() => { request = mocks.mockRequest({ @@ -633,48 +633,48 @@ describe('/sms/status', () => { location: {} }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: false, country: undefined }) - }) + assert.deepEqual(response, { ok: false, country: undefined }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'sms.getGeoData') + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'sms.getGeoData'); assert.deepEqual(args[1], { err: 'missing location data' - }) - }) - }) -}) + }); + }); + }); +}); describe('/sms/status with disabled geo-ip lookup', () => { - let log, config, sms, routes, route, request, response + let log, config, sms, routes, route, request, response; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); config = { sms: { enabled: true, countryCodes: [ 'US' ], isStatusGeoEnabled: false } - } + }; sms = { isBudgetOk: () => true - } - routes = makeRoutes({ log, config, sms }) - route = getRoute(routes, '/sms/status') + }; + routes = makeRoutes({ log, config, sms }); + route = getRoute(routes, '/sms/status'); request = mocks.mockRequest({ clientAddress: '127.0.0.1', credentials: { @@ -682,52 +682,52 @@ describe('/sms/status with disabled geo-ip lookup', () => { }, geo: {}, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: true, country: undefined }) - }) + assert.deepEqual(response, { ok: true, country: undefined }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called log.warn correctly', () => { - assert.equal(log.warn.callCount, 1) - const args = log.warn.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'sms.getGeoData') + assert.equal(log.warn.callCount, 1); + const args = log.warn.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'sms.getGeoData'); assert.deepEqual(args[1], { warning: 'skipping geolocation step' - }) - }) + }); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) -}) + assert.equal(log.error.callCount, 0); + }); +}); describe('/sms/status with query param and enabled geo-ip lookup', () => { - let log, config, sms, routes, route, request, response + let log, config, sms, routes, route, request, response; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); config = { sms: { enabled: true, countryCodes: [ 'RO' ], isStatusGeoEnabled: true } - } + }; sms = { isBudgetOk: () => true, send: sinon.spy(() => P.resolve()) - } - routes = makeRoutes({ log, config, sms }) - route = getRoute(routes, '/sms/status') + }; + routes = makeRoutes({ log, config, sms }); + route = getRoute(routes, '/sms/status'); request = mocks.mockRequest({ credentials: { email: 'foo@example.org' @@ -741,42 +741,42 @@ describe('/sms/status with query param and enabled geo-ip lookup', () => { country: 'RO' }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: true, country: 'RO' }) - }) + assert.deepEqual(response, { ok: true, country: 'RO' }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) -}) + assert.equal(log.error.callCount, 0); + }); +}); describe('/sms/status with query param and disabled geo-ip lookup', () => { - let log, config, sms, routes, route, request, response + let log, config, sms, routes, route, request, response; beforeEach(() => { - log = mocks.mockLog() + log = mocks.mockLog(); config = { sms: { enabled: true, countryCodes: [ 'GB' ], isStatusGeoEnabled: false } - } + }; sms = { isBudgetOk: () => true, send: sinon.spy(() => P.resolve()) - } - routes = makeRoutes({ log, config, sms }) - route = getRoute(routes, '/sms/status') + }; + routes = makeRoutes({ log, config, sms }); + route = getRoute(routes, '/sms/status'); request = mocks.mockRequest({ credentials: { email: 'foo@example.org' @@ -790,20 +790,20 @@ describe('/sms/status with query param and disabled geo-ip lookup', () => { country: 'GB' }, log: log - }) + }); return runTest(route, request) - .then(r => response = r) - }) + .then(r => response = r); + }); it('returned the correct response', () => { - assert.deepEqual(response, { ok: true, country: 'GB' }) - }) + assert.deepEqual(response, { ok: true, country: 'GB' }); + }); it('called log.begin once', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) -}) + assert.equal(log.error.callCount, 0); + }); +}); diff --git a/test/local/routes/token-codes.js b/test/local/routes/token-codes.js index 65aaa05b..34e918db 100644 --- a/test/local/routes/token-codes.js +++ b/test/local/routes/token-codes.js @@ -2,109 +2,109 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const errors = require('../../../lib/error') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') +const { assert } = require('chai'); +const errors = require('../../../lib/error'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); -let log, db, customs, routes, route, request, response -const TEST_EMAIL = 'test@email.com' +let log, db, customs, routes, route, request, response; +const TEST_EMAIL = 'test@email.com'; describe('/session/verify/token', () => { describe('should verify code', () => { - beforeEach(() => setup({db: {}}).then(r => response = r)) + beforeEach(() => setup({db: {}}).then(r => response = r)); it('returned the correct response', () => { - assert.deepEqual(response, {}) - }) + assert.deepEqual(response, {}); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'session.verify.token') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'session.verify.token'); + assert.equal(args[1], request); + }); it('called customs.check correctly', () => { - assert.equal(customs.check.callCount, 1) - const args = customs.check.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], request) - assert.equal(args[1], TEST_EMAIL) - assert.equal(args[2], 'verifyTokenCode') - }) + assert.equal(customs.check.callCount, 1); + const args = customs.check.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], request); + assert.equal(args[1], TEST_EMAIL); + assert.equal(args[2], 'verifyTokenCode'); + }); it('called db.verifyTokenCode correctly', () => { - assert.equal(db.verifyTokenCode.callCount, 1) - const args = db.verifyTokenCode.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'ASEFJK12') - }) + assert.equal(db.verifyTokenCode.callCount, 1); + const args = db.verifyTokenCode.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'ASEFJK12'); + }); it('called log.info correctly', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'account.token.code.verified') - }) - }) + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'account.token.code.verified'); + }); + }); describe('should not verify expired code', () => { beforeEach(() => { return setup(null, {db: {verifyTokenCode: errors.expiredTokenVerficationCode()}}).then(() => { - assert.fail('should not have verified') - }, (err) => response = err) - }) + assert.fail('should not have verified'); + }, (err) => response = err); + }); it('returned the correct error response', () => { - assert.equal(response.errno, 153, 'correct errno') - }) + assert.equal(response.errno, 153, 'correct errno'); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'session.verify.token') - assert.equal(args[1], request) - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'session.verify.token'); + assert.equal(args[1], request); + }); it('called customs.check correctly', () => { - assert.equal(customs.check.callCount, 1) - const args = customs.check.args[0] - assert.equal(args.length, 3) - assert.equal(args[0], request) - assert.equal(args[1], TEST_EMAIL) - assert.equal(args[2], 'verifyTokenCode') - }) + assert.equal(customs.check.callCount, 1); + const args = customs.check.args[0]; + assert.equal(args.length, 3); + assert.equal(args[0], request); + assert.equal(args[1], TEST_EMAIL); + assert.equal(args[2], 'verifyTokenCode'); + }); it('called db.verifyTokenCode correctly', () => { - assert.equal(db.verifyTokenCode.callCount, 1) - const args = db.verifyTokenCode.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'ASEFJK12') - }) + assert.equal(db.verifyTokenCode.callCount, 1); + const args = db.verifyTokenCode.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'ASEFJK12'); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'account.token.code.expired') - }) - }) -}) + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'account.token.code.expired'); + }); + }); +}); function setup(results, errors) { - results = results || {} - errors = errors || {} + results = results || {}; + errors = errors || {}; - log = mocks.mockLog() - db = mocks.mockDB(results.db, errors.db) - customs = mocks.mockCustoms(errors.customs) - routes = makeRoutes({log, db, customs}) - route = getRoute(routes, '/session/verify/token') + log = mocks.mockLog(); + db = mocks.mockDB(results.db, errors.db); + customs = mocks.mockCustoms(errors.customs); + routes = makeRoutes({log, db, customs}); + route = getRoute(routes, '/session/verify/token'); request = mocks.mockRequest({ credentials: { uid: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', @@ -114,18 +114,18 @@ function setup(results, errors) { payload: { code: 'ASEFJK12' } - }) - return runTest(route, request) + }); + return runTest(route, request); } function makeRoutes(options = {}) { - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() - const customs = options.customs || mocks.mockCustoms() - const config = options.config || {signinConfirmation: {}} - return require('../../../lib/routes/token-codes')(log, db, config, customs) + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); + const customs = options.customs || mocks.mockCustoms(); + const config = options.config || {signinConfirmation: {}}; + return require('../../../lib/routes/token-codes')(log, db, config, customs); } function runTest(route, request) { - return route.handler(request) + return route.handler(request); } diff --git a/test/local/routes/totp.js b/test/local/routes/totp.js index e87b375f..2b7beeda 100644 --- a/test/local/routes/totp.js +++ b/test/local/routes/totp.js @@ -2,19 +2,19 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const otplib = require('otplib') -const P = require('../../../lib/promise') -const sinon = require('sinon') +const { assert } = require('chai'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const otplib = require('otplib'); +const P = require('../../../lib/promise'); +const sinon = require('sinon'); -let log, db, customs, routes, route, request, requestOptions, mailer -const TEST_EMAIL = 'test@email.com' -const secret = 'KE3TGQTRNIYFO2KOPE4G6ULBOV2FQQTN' -const sessionId = 'id' +let log, db, customs, routes, route, request, requestOptions, mailer; +const TEST_EMAIL = 'test@email.com'; +const secret = 'KE3TGQTRNIYFO2KOPE4G6ULBOV2FQQTN'; +const sessionId = 'id'; describe('totp', () => { beforeEach(() => { @@ -33,248 +33,248 @@ describe('totp', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' } } - } - }) + }; + }); describe('/totp/create', () => { it('should create TOTP token', () => { return setup({db: {email: TEST_EMAIL}}, {}, '/totp/create', requestOptions) .then((response) => { - assert.ok(response.qrCodeUrl) - assert.ok(response.secret) - assert.equal(db.createTotpToken.callCount, 1, 'called create TOTP token') + assert.ok(response.qrCodeUrl); + assert.ok(response.secret); + assert.equal(db.createTotpToken.callCount, 1, 'called create TOTP token'); // emits correct metrics - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - const args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'totpToken.created', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event') - }) - }) + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + const args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'totpToken.created', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event'); + }); + }); it('should be disabled in unverified session', () => { - requestOptions.credentials.tokenVerificationId = 'notverified' + requestOptions.credentials.tokenVerificationId = 'notverified'; return setup({db: {email: TEST_EMAIL}}, {}, '/totp/create', requestOptions) .then(assert.fail, (err) => { - assert.deepEqual(err.errno, 138, 'unverified session error') - }) - }) - }) + assert.deepEqual(err.errno, 138, 'unverified session error'); + }); + }); + }); describe('/totp/destroy', () => { it('should delete TOTP token', () => { - requestOptions.credentials.authenticatorAssuranceLevel = 2 + requestOptions.credentials.authenticatorAssuranceLevel = 2; return setup({db: {email: TEST_EMAIL}}, {}, '/totp/destroy', requestOptions) .then((response) => { - assert.ok(response) - assert.equal(db.deleteTotpToken.callCount, 1, 'called delete TOTP token') + assert.ok(response); + assert.equal(db.deleteTotpToken.callCount, 1, 'called delete TOTP token'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'called notifyAttachedServices') - const args = log.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'profileDataChanged', 'first argument was event name') - assert.equal(args[1], request, 'second argument was request object') - assert.equal(args[2].uid, 'uid', 'third argument was event data with a uid') - }) - }) + assert.equal(log.notifyAttachedServices.callCount, 1, 'called notifyAttachedServices'); + const args = log.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'profileDataChanged', 'first argument was event name'); + assert.equal(args[1], request, 'second argument was request object'); + assert.equal(args[2].uid, 'uid', 'third argument was event data with a uid'); + }); + }); it('should not delete TOTP token in non-totp verified session', () => { - requestOptions.credentials.authenticatorAssuranceLevel = 1 + requestOptions.credentials.authenticatorAssuranceLevel = 1; return setup({db: {email: TEST_EMAIL}}, {}, '/totp/destroy', requestOptions) .then(assert.fail, (err) => { - assert.deepEqual(err.errno, 138, 'unverified session error') - assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices') - }) - }) + assert.deepEqual(err.errno, 138, 'unverified session error'); + assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices'); + }); + }); it('should be disabled in unverified session', () => { - requestOptions.credentials.tokenVerificationId = 'notverified' + requestOptions.credentials.tokenVerificationId = 'notverified'; return setup({db: {email: TEST_EMAIL}}, {}, '/totp/destroy', requestOptions) .then(assert.fail, (err) => { - assert.deepEqual(err.errno, 138, 'unverified session error') - assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices') - }) - }) - }) + assert.deepEqual(err.errno, 138, 'unverified session error'); + assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices'); + }); + }); + }); describe('/totp/exists', () => { it('should check for TOTP token', () => { return setup({db: {email: TEST_EMAIL}}, {}, '/totp/exists', requestOptions) .then((response) => { - assert.ok(response) - assert.equal(db.totpToken.callCount, 1, 'called get TOTP token') - }) - }) + assert.ok(response); + assert.equal(db.totpToken.callCount, 1, 'called get TOTP token'); + }); + }); it('should be disabled in unverified session', () => { - requestOptions.credentials.tokenVerificationId = 'notverified' + requestOptions.credentials.tokenVerificationId = 'notverified'; return setup({db: {email: TEST_EMAIL}}, {}, '/totp/exists', requestOptions) .then(assert.fail, (err) => { - assert.deepEqual(err.errno, 138, 'unverified session error') - }) - }) - }) + assert.deepEqual(err.errno, 138, 'unverified session error'); + }); + }); + }); describe('/session/verify/totp', () => { it('should enable and verify TOTP token', () => { - const authenticator = new otplib.authenticator.Authenticator() - authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}) + const authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}); requestOptions.payload = { code: authenticator.generate(secret) - } + }; return setup({db: {email: TEST_EMAIL}, totpTokenVerified: false, totpTokenEnabled: false}, {}, '/session/verify/totp', requestOptions) .then((response) => { - assert.equal(response.success, true, 'should be valid code') - assert.equal(db.totpToken.callCount, 1, 'called get TOTP token') - assert.equal(db.updateTotpToken.callCount, 1, 'update TOTP token') + assert.equal(response.success, true, 'should be valid code'); + assert.equal(db.totpToken.callCount, 1, 'called get TOTP token'); + assert.equal(db.updateTotpToken.callCount, 1, 'update TOTP token'); - assert.equal(log.notifyAttachedServices.callCount, 1, 'call notifyAttachedServices') - let args = log.notifyAttachedServices.args[0] - assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments') - assert.equal(args[0], 'profileDataChanged', 'first argument was event name') - assert.equal(args[1], request, 'second argument was request object') - assert.equal(args[2].uid, 'uid', 'third argument was event data with a uid') + assert.equal(log.notifyAttachedServices.callCount, 1, 'call notifyAttachedServices'); + let args = log.notifyAttachedServices.args[0]; + assert.equal(args.length, 3, 'log.notifyAttachedServices was passed three arguments'); + assert.equal(args[0], 'profileDataChanged', 'first argument was event name'); + assert.equal(args[1], request, 'second argument was request object'); + assert.equal(args[2].uid, 'uid', 'third argument was event data with a uid'); // verifies session - assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session') - args = db.verifyTokensWithMethod.args[0] - assert.equal(sessionId, args[0], 'called with correct session id') - assert.equal('totp-2fa', args[1], 'called with correct method') + assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session'); + args = db.verifyTokensWithMethod.args[0]; + assert.equal(sessionId, args[0], 'called with correct session id'); + assert.equal('totp-2fa', args[1], 'called with correct method'); // emits correct metrics - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event') + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event'); // correct emails sent - assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0) - assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 1) - }) - }) + assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0); + assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 1); + }); + }); it('should verify session with TOTP token - sync', () => { - const authenticator = new otplib.authenticator.Authenticator() - authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}) + const authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}); requestOptions.payload = { code: authenticator.generate(secret), service: 'sync' - } + }; return setup({db: {email: TEST_EMAIL}, totpTokenVerified: true, totpTokenEnabled: true}, {}, '/session/verify/totp', requestOptions) .then((response) => { - assert.equal(response.success, true, 'should be valid code') - assert.equal(db.totpToken.callCount, 1, 'called get TOTP token') - assert.equal(db.updateTotpToken.callCount, 0, 'did not call update TOTP token') + assert.equal(response.success, true, 'should be valid code'); + assert.equal(db.totpToken.callCount, 1, 'called get TOTP token'); + assert.equal(db.updateTotpToken.callCount, 0, 'did not call update TOTP token'); - assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices') + assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices'); // verifies session - assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session') - let args = db.verifyTokensWithMethod.args[0] - assert.equal(sessionId, args[0], 'called with correct session id') - assert.equal('totp-2fa', args[1], 'called with correct method') + assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session'); + let args = db.verifyTokensWithMethod.args[0]; + assert.equal(sessionId, args[0], 'called with correct session id'); + assert.equal('totp-2fa', args[1], 'called with correct method'); // emits correct metrics - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event') + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event'); // correct emails sent - assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 1) - assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0) - }) - }) + assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 1); + assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0); + }); + }); it('should verify session with TOTP token - non sync', () => { - const authenticator = new otplib.authenticator.Authenticator() - authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}) + const authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = Object.assign({}, otplib.authenticator.options, {secret}); requestOptions.payload = { code: authenticator.generate(secret), service: 'not sync' - } + }; return setup({db: {email: TEST_EMAIL}, totpTokenVerified: true, totpTokenEnabled: true}, {}, '/session/verify/totp', requestOptions) .then((response) => { - assert.equal(response.success, true, 'should be valid code') - assert.equal(db.totpToken.callCount, 1, 'called get TOTP token') - assert.equal(db.updateTotpToken.callCount, 0, 'did not call update TOTP token') + assert.equal(response.success, true, 'should be valid code'); + assert.equal(db.totpToken.callCount, 1, 'called get TOTP token'); + assert.equal(db.updateTotpToken.callCount, 0, 'did not call update TOTP token'); - assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices') + assert.equal(log.notifyAttachedServices.callCount, 0, 'did not call notifyAttachedServices'); // verifies session - assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session') - let args = db.verifyTokensWithMethod.args[0] - assert.equal(sessionId, args[0], 'called with correct session id') - assert.equal('totp-2fa', args[1], 'called with correct method') + assert.equal(db.verifyTokensWithMethod.callCount, 1, 'call verify session'); + let args = db.verifyTokensWithMethod.args[0]; + assert.equal(sessionId, args[0], 'called with correct session id'); + assert.equal('totp-2fa', args[1], 'called with correct method'); // emits correct metrics - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event') + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'totpToken.verified', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event'); // correct emails sent - assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0) - assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0) - }) - }) + assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0); + assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0); + }); + }); it('should return false for invalid TOTP code', () => { requestOptions.payload = { code: 'NOTVALID' - } + }; return setup({db: {email: TEST_EMAIL}}, {}, '/session/verify/totp', requestOptions) .then((response) => { - assert.equal(response.success, false, 'should be valid code') - assert.equal(db.totpToken.callCount, 1, 'called get TOTP token') + assert.equal(response.success, false, 'should be valid code'); + assert.equal(db.totpToken.callCount, 1, 'called get TOTP token'); // emits correct metrics - assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent') - const args = request.emitMetricsEvent.args[0] - assert.equal(args[0], 'totpToken.unverified', 'called emitMetricsEvent with correct event') - assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event') + assert.equal(request.emitMetricsEvent.callCount, 1, 'called emitMetricsEvent'); + const args = request.emitMetricsEvent.args[0]; + assert.equal(args[0], 'totpToken.unverified', 'called emitMetricsEvent with correct event'); + assert.equal(args[1]['uid'], 'uid', 'called emitMetricsEvent with correct event'); // correct emails sent - assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0) - assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0) - }) - }) - }) -}) + assert.equal(mailer.sendNewDeviceLoginNotification.callCount, 0); + assert.equal(mailer.sendPostAddTwoStepAuthNotification.callCount, 0); + }); + }); + }); +}); function setup(results, errors, routePath, requestOptions) { - results = results || {} - errors = errors || {} - log = mocks.mockLog() - customs = mocks.mockCustoms(errors.customs) - mailer = mocks.mockMailer() - db = mocks.mockDB(results.db, errors.db) + results = results || {}; + errors = errors || {}; + log = mocks.mockLog(); + customs = mocks.mockCustoms(errors.customs); + mailer = mocks.mockMailer(); + db = mocks.mockDB(results.db, errors.db); db.createTotpToken = sinon.spy(() => { return P.resolve({ qrCodeUrl: 'some base64 encoded png', sharedSecret: secret - }) - }) + }); + }); db.totpToken = sinon.spy(() => { return P.resolve({ verified: typeof results.totpTokenVerified === 'undefined' ? true : results.totpTokenVerified, enabled: typeof results.totpTokenEnabled === 'undefined' ? true : results.totpTokenEnabled, sharedSecret: secret - }) - }) - routes = makeRoutes({log, db, customs, mailer}) - route = getRoute(routes, routePath) - request = mocks.mockRequest(requestOptions) - request.emitMetricsEvent = sinon.spy(() => P.resolve({})) - return runTest(route, request) + }); + }); + routes = makeRoutes({log, db, customs, mailer}); + route = getRoute(routes, routePath); + request = mocks.mockRequest(requestOptions); + request.emitMetricsEvent = sinon.spy(() => P.resolve({})); + return runTest(route, request); } function makeRoutes(options = {}) { - const config = {step: 30} - const { log, db, customs, mailer } = options - return require('../../../lib/routes/totp')(log, db, mailer, customs, config) + const config = {step: 30}; + const { log, db, customs, mailer } = options; + return require('../../../lib/routes/totp')(log, db, mailer, customs, config); } function runTest(route, request) { - return route.handler(request) + return route.handler(request); } diff --git a/test/local/routes/unblock-codes.js b/test/local/routes/unblock-codes.js index bc5e2fcb..ef569abc 100644 --- a/test/local/routes/unblock-codes.js +++ b/test/local/routes/unblock-codes.js @@ -2,38 +2,38 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const getRoute = require('../../routes_helpers').getRoute -const mocks = require('../../mocks') -const P = require('../../../lib/promise') -const proxyquire = require('proxyquire') -const uuid = require('uuid') +const { assert } = require('chai'); +const getRoute = require('../../routes_helpers').getRoute; +const mocks = require('../../mocks'); +const P = require('../../../lib/promise'); +const proxyquire = require('proxyquire'); +const uuid = require('uuid'); function makeRoutes (options = {}, requireMocks) { - const config = options.config || {} - const log = options.log || mocks.mockLog() - const db = options.db || mocks.mockDB() + const config = options.config || {}; + const log = options.log || mocks.mockLog(); + const db = options.db || mocks.mockDB(); const customs = options.customs || { - check: function () { return P.resolve(true) } - } + check: function () { return P.resolve(true); } + }; return proxyquire('../../../lib/routes/unblock-codes', requireMocks || {})( log, db, options.mailer || {}, config.signinUnblock || {}, customs - ) + ); } function runTest (route, request, assertions) { return route.handler(request) - .then(assertions) + .then(assertions); } describe('/account/login/send_unblock_code', function () { - var uid = uuid.v4('binary').toString('hex') - var email = 'unblock@example.com' - const mockLog = mocks.mockLog() + var uid = uuid.v4('binary').toString('hex'); + var email = 'unblock@example.com'; + const mockLog = mocks.mockLog(); var mockRequest = mocks.mockRequest({ log: mockLog, payload: { @@ -43,92 +43,92 @@ describe('/account/login/send_unblock_code', function () { flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103' } } - }) - var mockMailer = mocks.mockMailer() + }); + var mockMailer = mocks.mockMailer(); var mockDb = mocks.mockDB({ uid: uid, email: email - }) + }); var config = { signinUnblock: {} - } + }; var accountRoutes = makeRoutes({ config: config, db: mockDb, log: mockLog, mailer: mockMailer - }) - var route = getRoute(accountRoutes, '/account/login/send_unblock_code') + }); + var route = getRoute(accountRoutes, '/account/login/send_unblock_code'); afterEach(function () { - mockDb.accountRecord.resetHistory() - mockDb.createUnblockCode.resetHistory() - mockMailer.sendUnblockCode.resetHistory() - }) + mockDb.accountRecord.resetHistory(); + mockDb.createUnblockCode.resetHistory(); + mockMailer.sendUnblockCode.resetHistory(); + }); it('signin unblock enabled', function () { return runTest(route, mockRequest, function (response) { - assert.ok(! (response instanceof Error), response.stack) - assert.deepEqual(response, {}, 'response has no keys') + assert.ok(! (response instanceof Error), response.stack); + assert.deepEqual(response, {}, 'response has no keys'); - assert.equal(mockDb.accountRecord.callCount, 1, 'db.accountRecord called') - assert.equal(mockDb.accountRecord.args[0][0], email) + assert.equal(mockDb.accountRecord.callCount, 1, 'db.accountRecord called'); + assert.equal(mockDb.accountRecord.args[0][0], email); - assert.equal(mockDb.createUnblockCode.callCount, 1, 'db.createUnblockCode called') - var dbArgs = mockDb.createUnblockCode.args[0] - assert.equal(dbArgs.length, 1) - assert.equal(dbArgs[0], uid) + assert.equal(mockDb.createUnblockCode.callCount, 1, 'db.createUnblockCode called'); + var dbArgs = mockDb.createUnblockCode.args[0]; + assert.equal(dbArgs.length, 1); + assert.equal(dbArgs[0], uid); - assert.equal(mockMailer.sendUnblockCode.callCount, 1, 'called mailer.sendUnblockCode') - var args = mockMailer.sendUnblockCode.args[0] - assert.equal(args.length, 3, 'mailer.sendUnblockCode called with 3 args') + assert.equal(mockMailer.sendUnblockCode.callCount, 1, 'called mailer.sendUnblockCode'); + var args = mockMailer.sendUnblockCode.args[0]; + assert.equal(args.length, 3, 'mailer.sendUnblockCode called with 3 args'); - assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent was called once') - assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.sentUnblockCode', 'event was account.login.sentUnblockCode') - mockLog.flowEvent.resetHistory() - }) - }) + assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent was called once'); + assert.equal(mockLog.flowEvent.args[0][0].event, 'account.login.sentUnblockCode', 'event was account.login.sentUnblockCode'); + mockLog.flowEvent.resetHistory(); + }); + }); it('uses normalized email address for feature flag', function () { - mockRequest.payload.email = 'UNBLOCK@example.com' + mockRequest.payload.email = 'UNBLOCK@example.com'; return runTest(route, mockRequest, function(response) { - assert.ok(! (response instanceof Error), response.stack) - assert.deepEqual(response, {}, 'response has no keys') + assert.ok(! (response instanceof Error), response.stack); + assert.deepEqual(response, {}, 'response has no keys'); - assert.equal(mockDb.accountRecord.callCount, 1, 'db.accountRecord called') - assert.equal(mockDb.accountRecord.args[0][0], mockRequest.payload.email) - assert.equal(mockDb.createUnblockCode.callCount, 1, 'db.createUnblockCode called') - assert.equal(mockMailer.sendUnblockCode.callCount, 1, 'called mailer.sendUnblockCode') - }) - }) -}) + assert.equal(mockDb.accountRecord.callCount, 1, 'db.accountRecord called'); + assert.equal(mockDb.accountRecord.args[0][0], mockRequest.payload.email); + assert.equal(mockDb.createUnblockCode.callCount, 1, 'db.createUnblockCode called'); + assert.equal(mockMailer.sendUnblockCode.callCount, 1, 'called mailer.sendUnblockCode'); + }); + }); +}); describe('/account/login/reject_unblock_code', function () { it('should consume the unblock code', () => { - var uid = uuid.v4('binary').toString('hex') - var unblockCode = 'A1B2C3D4' + var uid = uuid.v4('binary').toString('hex'); + var unblockCode = 'A1B2C3D4'; var mockRequest = mocks.mockRequest({ payload: { uid: uid, unblockCode: unblockCode } - }) - var mockDb = mocks.mockDB() + }); + var mockDb = mocks.mockDB(); var accountRoutes = makeRoutes({ db: mockDb - }) - var route = getRoute(accountRoutes, '/account/login/reject_unblock_code') + }); + var route = getRoute(accountRoutes, '/account/login/reject_unblock_code'); return runTest(route, mockRequest, function (response) { - assert.ok(! (response instanceof Error), response.stack) - assert.deepEqual(response, {}, 'response has no keys') + assert.ok(! (response instanceof Error), response.stack); + assert.deepEqual(response, {}, 'response has no keys'); - assert.equal(mockDb.consumeUnblockCode.callCount, 1, 'consumeUnblockCode is called') - var args = mockDb.consumeUnblockCode.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].toString('hex'), uid) - assert.equal(args[1], unblockCode) - }) - }) -}) + assert.equal(mockDb.consumeUnblockCode.callCount, 1, 'consumeUnblockCode is called'); + var args = mockDb.consumeUnblockCode.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].toString('hex'), uid); + assert.equal(args[1], unblockCode); + }); + }); +}); diff --git a/test/local/routes/utils/signin.js b/test/local/routes/utils/signin.js index 735709ef..5feb6523 100644 --- a/test/local/routes/utils/signin.js +++ b/test/local/routes/utils/signin.js @@ -2,97 +2,97 @@ * 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/. */ -'use strict' +'use strict'; -const sinon = require('sinon') -const assert = { ...sinon.assert, ...require('chai').assert } +const sinon = require('sinon'); +const assert = { ...sinon.assert, ...require('chai').assert }; -const P = require('../../../../lib/promise') -const mocks = require('../../../mocks') -const Password = require('../../../../lib/crypto/password')({}, {}) -const error = require('../../../../lib/error') -const butil = require('../../../../lib/crypto/butil') +const P = require('../../../../lib/promise'); +const mocks = require('../../../mocks'); +const Password = require('../../../../lib/crypto/password')({}, {}); +const error = require('../../../../lib/error'); +const butil = require('../../../../lib/crypto/butil'); -const CLIENT_ADDRESS = '10.0.0.1' -const TEST_EMAIL = 'test@example.com' -const TEST_UID = 'thisisauid' +const CLIENT_ADDRESS = '10.0.0.1'; +const TEST_EMAIL = 'test@example.com'; +const TEST_UID = 'thisisauid'; function makeSigninUtils(options) { - const log = options.log || mocks.mockLog() - const config = options.config || {} - const customs = options.customs || {} - const db = options.db || mocks.mockDB() - const mailer = options.mailer || {} - return require('../../../../lib/routes/utils/signin')(log, config, customs, db, mailer) + const log = options.log || mocks.mockLog(); + const config = options.config || {}; + const customs = options.customs || {}; + const db = options.db || mocks.mockDB(); + const mailer = options.mailer || {}; + return require('../../../../lib/routes/utils/signin')(log, config, customs, db, mailer); } describe('checkPassword', () => { - let customs, db, signinUtils + let customs, db, signinUtils; beforeEach(() => { - db = mocks.mockDB() + db = mocks.mockDB(); customs = { flag: sinon.spy(() => P.resolve({})) - } - signinUtils = makeSigninUtils({ db, customs }) - }) + }; + signinUtils = makeSigninUtils({ db, customs }); + }); it('should check with correct password', () => { - db.checkPassword = sinon.spy(uid => P.resolve(true)) - const authPW = Buffer.from('aaaaaaaaaaaaaaaa') + db.checkPassword = sinon.spy(uid => P.resolve(true)); + const authPW = Buffer.from('aaaaaaaaaaaaaaaa'); const accountRecord = { uid: TEST_UID, verifierVersion: 0, authSalt: Buffer.from('bbbbbbbbbbbbbbbb') - } - const password = new Password(authPW, accountRecord.authSalt, accountRecord.verifierVersion) + }; + const password = new Password(authPW, accountRecord.authSalt, accountRecord.verifierVersion); return password.verifyHash() .then(hash => { return signinUtils.checkPassword(accountRecord, password, CLIENT_ADDRESS) .then(match => { - assert.ok(match, 'password matches, checkPassword returns true') + assert.ok(match, 'password matches, checkPassword returns true'); - assert.calledOnce(db.checkPassword) - assert.calledWithExactly(db.checkPassword, TEST_UID, hash) + assert.calledOnce(db.checkPassword); + assert.calledWithExactly(db.checkPassword, TEST_UID, hash); - assert.notCalled(customs.flag) - }) - }) - }) + assert.notCalled(customs.flag); + }); + }); + }); it('should return false when check with incorrect password', () => { - db.checkPassword = sinon.spy(uid => P.resolve(false)) - const authPW = Buffer.from('aaaaaaaaaaaaaaaa') + db.checkPassword = sinon.spy(uid => P.resolve(false)); + const authPW = Buffer.from('aaaaaaaaaaaaaaaa'); const accountRecord = { uid: TEST_UID, email: TEST_EMAIL, verifierVersion: 0, authSalt: Buffer.from('bbbbbbbbbbbbbbbb') - } - const goodPassword = new Password(authPW, accountRecord.authSalt, accountRecord.verifierVersion) - const badAuthPW = Buffer.from('cccccccccccccccc') - const badPassword = new Password(badAuthPW, accountRecord.authSalt, accountRecord.verifierVersion) + }; + const goodPassword = new Password(authPW, accountRecord.authSalt, accountRecord.verifierVersion); + const badAuthPW = Buffer.from('cccccccccccccccc'); + const badPassword = new Password(badAuthPW, accountRecord.authSalt, accountRecord.verifierVersion); return P.all([goodPassword.verifyHash(), badPassword.verifyHash()]) .spread((goodHash, badHash) => { - assert.notEqual(goodHash, badHash, 'bad password actually has a different hash') + assert.notEqual(goodHash, badHash, 'bad password actually has a different hash'); return signinUtils.checkPassword(accountRecord, badPassword, CLIENT_ADDRESS) .then(match => { - assert.equal(!! match, false, 'password does not match, checkPassword returns false') + assert.equal(!! match, false, 'password does not match, checkPassword returns false'); - assert.calledOnce(db.checkPassword) - assert.calledWithExactly(db.checkPassword, TEST_UID, badHash) + assert.calledOnce(db.checkPassword); + assert.calledWithExactly(db.checkPassword, TEST_UID, badHash); - assert.calledOnce(customs.flag) + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.INCORRECT_PASSWORD - }) - }) - }) - }) + }); + }); + }); + }); it('should error when checking account whose password must be reset', () => { const accountRecord = { @@ -100,344 +100,344 @@ describe('checkPassword', () => { email: TEST_EMAIL, verifierVersion: 0, authSalt: butil.ONES - } - const incorrectAuthPW = Buffer.from('cccccccccccccccccccccccccccccccc') - const incorrectPassword = new Password(incorrectAuthPW, accountRecord.authSalt, accountRecord.verifierVersion) + }; + const incorrectAuthPW = Buffer.from('cccccccccccccccccccccccccccccccc'); + const incorrectPassword = new Password(incorrectAuthPW, accountRecord.authSalt, accountRecord.verifierVersion); return signinUtils.checkPassword(accountRecord, incorrectPassword, CLIENT_ADDRESS) .then( - (match) => { assert(false, 'password check should not have succeeded') }, + (match) => { assert(false, 'password check should not have succeeded'); }, (err) => { - assert.equal(err.errno, error.ERRNO.ACCOUNT_RESET, 'an ACCOUNT_RESET error was thrown') + assert.equal(err.errno, error.ERRNO.ACCOUNT_RESET, 'an ACCOUNT_RESET error was thrown'); - assert.notCalled(db.checkPassword) + assert.notCalled(db.checkPassword); - assert.calledOnce(customs.flag) + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.ACCOUNT_RESET - }) + }); } - ) - }) -}) + ); + }); +}); describe('checkEmailAddress', () => { - let accountRecord, checkEmailAddress + let accountRecord, checkEmailAddress; beforeEach(() => { accountRecord = { uid: 'testUid', primaryEmail: { normalizedEmail: 'primary@example.com' } - } - checkEmailAddress = makeSigninUtils({}).checkEmailAddress - }) + }; + checkEmailAddress = makeSigninUtils({}).checkEmailAddress; + }); it('should return true when email matches primary after normalization', () => { - assert.ok(checkEmailAddress(accountRecord, 'primary@example.com'), 'matches primary') - assert.ok(checkEmailAddress(accountRecord, 'PrIMArY@example.com'), 'matches primary when lowercased') - }) + assert.ok(checkEmailAddress(accountRecord, 'primary@example.com'), 'matches primary'); + assert.ok(checkEmailAddress(accountRecord, 'PrIMArY@example.com'), 'matches primary when lowercased'); + }); it('should throw when email does not match primary after normalization', () => { - assert.throws(() => checkEmailAddress(accountRecord, 'secondary@test.net'), 'Sign in with this email type is not currently supported') - assert.throws(() => checkEmailAddress(accountRecord, 'something@else.org'), 'Sign in with this email type is not currently supported') - }) + assert.throws(() => checkEmailAddress(accountRecord, 'secondary@test.net'), 'Sign in with this email type is not currently supported'); + assert.throws(() => checkEmailAddress(accountRecord, 'something@else.org'), 'Sign in with this email type is not currently supported'); + }); describe('with originalLoginEmail parameter', () => { it('should return true when originalLoginEmail matches primry after normalization', () => { - assert.ok(checkEmailAddress(accountRecord, 'other@email', 'primary@example.com'), 'matches primary') - assert.ok(checkEmailAddress(accountRecord, 'other@email', 'PrIMArY@example.com'), 'matches primary when lowercased') - }) + assert.ok(checkEmailAddress(accountRecord, 'other@email', 'primary@example.com'), 'matches primary'); + assert.ok(checkEmailAddress(accountRecord, 'other@email', 'PrIMArY@example.com'), 'matches primary when lowercased'); + }); it('should throw when originalLoginEmail does not match primary after normalization', () => { - assert.throws(() => checkEmailAddress(accountRecord, 'other@email', 'secondary@test.net'), 'Sign in with this email type is not currently supported') - assert.throws(() => checkEmailAddress(accountRecord, 'other@email', 'something@else.org'), 'Sign in with this email type is not currently supported') - }) - }) -}) + assert.throws(() => checkEmailAddress(accountRecord, 'other@email', 'secondary@test.net'), 'Sign in with this email type is not currently supported'); + assert.throws(() => checkEmailAddress(accountRecord, 'other@email', 'something@else.org'), 'Sign in with this email type is not currently supported'); + }); + }); +}); describe('checkCustomsAndLoadAccount', () => { - let config, customs, db, log, request, checkCustomsAndLoadAccount + let config, customs, db, log, request, checkCustomsAndLoadAccount; beforeEach(() => { db = mocks.mockDB({ uid: TEST_UID, email: TEST_EMAIL - }) - log = mocks.mockLog() + }); + log = mocks.mockLog(); customs = { check: sinon.spy(() => P.resolve()), flag: sinon.spy(() => P.resolve({})) - } + }; config = { signinUnblock: { forcedEmailAddresses: /^blockme.+$/, codeLifetime: 30000 } - } + }; request = mocks.mockRequest({ log, clientAddress: CLIENT_ADDRESS, payload: { } - }) - request.emitMetricsEvent = sinon.spy(() => P.resolve()) - checkCustomsAndLoadAccount = makeSigninUtils({log, config, db, customs}).checkCustomsAndLoadAccount - }) + }); + request.emitMetricsEvent = sinon.spy(() => P.resolve()); + checkCustomsAndLoadAccount = makeSigninUtils({log, config, db, customs}).checkCustomsAndLoadAccount; + }); it('should load the account record when customs allows the request', () => { return checkCustomsAndLoadAccount(request, TEST_EMAIL).then(res => { - assert.equal(res.didSigninUnblock, false, 'did not do signin unblock') - assert.ok(res.accountRecord, 'accountRecord was returned') - assert.equal(res.accountRecord.email, TEST_EMAIL, 'accountRecord has correct email') + assert.equal(res.didSigninUnblock, false, 'did not do signin unblock'); + assert.ok(res.accountRecord, 'accountRecord was returned'); + assert.equal(res.accountRecord.email, TEST_EMAIL, 'accountRecord has correct email'); - assert.calledOnce(customs.check) - assert.calledWithExactly(customs.check, request, TEST_EMAIL, 'accountLogin') + assert.calledOnce(customs.check); + assert.calledWithExactly(customs.check, request, TEST_EMAIL, 'accountLogin'); - assert.calledOnce(db.accountRecord) - assert.calledWithExactly(db.accountRecord, TEST_EMAIL) + assert.calledOnce(db.accountRecord); + assert.calledWithExactly(db.accountRecord, TEST_EMAIL); - assert.callOrder(customs.check, db.accountRecord) - }) - }) + assert.callOrder(customs.check, db.accountRecord); + }); + }); it('should throw non-customs errors directly back to the caller', () => { - customs.check = sinon.spy(() => { throw new Error('unexpected!') }) + customs.check = sinon.spy(() => { throw new Error('unexpected!'); }); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.message, 'unexpected!', 'the error was propagated to caller') - assert.calledOnce(customs.check) - assert.notCalled(db.accountRecord) - assert.notCalled(request.emitMetricsEvent) + assert.equal(err.message, 'unexpected!', 'the error was propagated to caller'); + assert.calledOnce(customs.check); + assert.notCalled(db.accountRecord); + assert.notCalled(request.emitMetricsEvent); } - ) - }) + ); + }); it('should re-throw customs errors when no unblock code is specified', () => { - const origErr = error.tooManyRequests() - customs.check = sinon.spy(() => P.reject(origErr)) + const origErr = error.tooManyRequests(); + customs.check = sinon.spy(() => P.reject(origErr)); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.deepEqual(err, origErr, 'the original error was propagated to caller') - assert.calledOnce(customs.check) - assert.notCalled(db.accountRecord) - assert.calledOnce(request.emitMetricsEvent) - assert.calledWithExactly(request.emitMetricsEvent, 'account.login.blocked') + assert.deepEqual(err, origErr, 'the original error was propagated to caller'); + assert.calledOnce(customs.check); + assert.notCalled(db.accountRecord); + assert.calledOnce(request.emitMetricsEvent); + assert.calledWithExactly(request.emitMetricsEvent, 'account.login.blocked'); } - ) - }) + ); + }); it('login attempts on an unknown account should be flagged with customs', () => { - db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())) + db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.ACCOUNT_UNKNOWN, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.calledOnce(customs.flag) + assert.equal(err.errno, error.ERRNO.ACCOUNT_UNKNOWN, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.ACCOUNT_UNKNOWN - }) + }); } - ) - }) + ); + }); it('login attempts on an unknown account should be flagged with customs', () => { - db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())) + db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.ACCOUNT_UNKNOWN, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.calledOnce(customs.flag) + assert.equal(err.errno, error.ERRNO.ACCOUNT_UNKNOWN, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.ACCOUNT_UNKNOWN - }) + }); } - ) - }) + ); + }); it('email addresses matching a configured regex get forcibly blocked', () => { - const email = 'blockme-' + TEST_EMAIL + const email = 'blockme-' + TEST_EMAIL; return checkCustomsAndLoadAccount(request, email).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'the correct error was thrown') - assert.equal(err.output.payload.verificationMethod, 'email-captcha', 'the error can be unblocked') + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'the correct error was thrown'); + assert.equal(err.output.payload.verificationMethod, 'email-captcha', 'the error can be unblocked'); - assert.notCalled(customs.check) - assert.notCalled(db.accountRecord) - assert.notCalled(customs.flag) + assert.notCalled(customs.check); + assert.notCalled(db.accountRecord); + assert.notCalled(customs.flag); - assert.calledOnce(request.emitMetricsEvent) - assert.calledWithExactly(request.emitMetricsEvent, 'account.login.blocked') + assert.calledOnce(request.emitMetricsEvent); + assert.calledWithExactly(request.emitMetricsEvent, 'account.login.blocked'); } - ) - }) + ); + }); it('a valid unblock code can bypass a customs block', () => { - customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, true))) - request.payload.unblockCode = 'VaLiD' - db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })) + customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, true))); + request.payload.unblockCode = 'VaLiD'; + db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then((res) => { - assert.equal(res.didSigninUnblock, true, 'did ignin unblock') - assert.ok(res.accountRecord, 'accountRecord was returned') - assert.equal(res.accountRecord.email, TEST_EMAIL, 'accountRecord has correct email') + assert.equal(res.didSigninUnblock, true, 'did ignin unblock'); + assert.ok(res.accountRecord, 'accountRecord was returned'); + assert.equal(res.accountRecord.email, TEST_EMAIL, 'accountRecord has correct email'); - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); - assert.calledOnce(db.consumeUnblockCode) - assert.calledWithExactly(db.consumeUnblockCode, TEST_UID, 'VALID') // unblockCode got uppercased + assert.calledOnce(db.consumeUnblockCode); + assert.calledWithExactly(db.consumeUnblockCode, TEST_UID, 'VALID'); // unblockCode got uppercased - assert.calledTwice(request.emitMetricsEvent) - assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked') - assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.confirmedUnblockCode') - }) - }) + assert.calledTwice(request.emitMetricsEvent); + assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked'); + assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.confirmedUnblockCode'); + }); + }); it('unblock codes are not checked for non-unblockable customs errors', () => { - customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, false))) - request.payload.unblockCode = 'VALID' - db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })) + customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, false))); + request.payload.unblockCode = 'VALID'; + db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.THROTTLED, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.notCalled(db.accountRecord) - assert.notCalled(db.consumeUnblockCode) - assert.notCalled(customs.flag) + assert.equal(err.errno, error.ERRNO.THROTTLED, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.notCalled(db.accountRecord); + assert.notCalled(db.consumeUnblockCode); + assert.notCalled(customs.flag); } - ) - }) + ); + }); it('unblock codes are not checked for non-customs errors', () => { - customs.check = sinon.spy(() => P.reject(error.serviceUnavailable())) - request.payload.unblockCode = 'VALID' - db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })) + customs.check = sinon.spy(() => P.reject(error.serviceUnavailable())); + request.payload.unblockCode = 'VALID'; + db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.SERVER_BUSY, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.notCalled(db.accountRecord) - assert.notCalled(db.consumeUnblockCode) - assert.notCalled(customs.flag) + assert.equal(err.errno, error.ERRNO.SERVER_BUSY, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.notCalled(db.accountRecord); + assert.notCalled(db.consumeUnblockCode); + assert.notCalled(customs.flag); } - ) - }) + ); + }); it('unblock codes are not checked when the account does not exist', () => { - customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, true))) - request.payload.unblockCode = 'VALID' - db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())) - db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })) + customs.check = sinon.spy(() => P.reject(error.tooManyRequests(60, null, true))); + request.payload.unblockCode = 'VALID'; + db.accountRecord = sinon.spy(() => P.reject(error.unknownAccount())); + db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() })); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.THROTTLED, 'the ACCOUNT_UNKNOWN error was hidden by the customs block') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.notCalled(db.consumeUnblockCode) - assert.calledOnce(customs.flag) + assert.equal(err.errno, error.ERRNO.THROTTLED, 'the ACCOUNT_UNKNOWN error was hidden by the customs block'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.notCalled(db.consumeUnblockCode); + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.ACCOUNT_UNKNOWN - }) + }); } - ) - }) + ); + }); it('invalid unblock codes are rejected and reported to customs', () => { - customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))) - request.payload.unblockCode = 'INVALID' - db.consumeUnblockCode = sinon.spy(() => P.reject(error.invalidUnblockCode())) + customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))); + request.payload.unblockCode = 'INVALID'; + db.consumeUnblockCode = sinon.spy(() => P.reject(error.invalidUnblockCode())); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.calledOnce(db.consumeUnblockCode) + assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.calledOnce(db.consumeUnblockCode); - assert.calledTwice(request.emitMetricsEvent) - assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked') - assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.invalidUnblockCode') + assert.calledTwice(request.emitMetricsEvent); + assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked'); + assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.invalidUnblockCode'); - assert.calledOnce(customs.flag) + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.INVALID_UNBLOCK_CODE - }) + }); } - ) - }) + ); + }); it('expired unblock codes are rejected as invalid', () => { - customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))) - request.payload.unblockCode = 'EXPIRED' - db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() - (config.signinUnblock.codeLifetime * 2) })) + customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))); + request.payload.unblockCode = 'EXPIRED'; + db.consumeUnblockCode = sinon.spy(() => P.resolve({ createdAt: Date.now() - (config.signinUnblock.codeLifetime * 2) })); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'the correct error was thrown') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.calledOnce(db.consumeUnblockCode) + assert.equal(err.errno, error.ERRNO.INVALID_UNBLOCK_CODE, 'the correct error was thrown'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.calledOnce(db.consumeUnblockCode); - assert.calledTwice(request.emitMetricsEvent) - assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked') - assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.invalidUnblockCode') + assert.calledTwice(request.emitMetricsEvent); + assert.calledWithExactly(request.emitMetricsEvent.getCall(0), 'account.login.blocked'); + assert.calledWithExactly(request.emitMetricsEvent.getCall(1), 'account.login.invalidUnblockCode'); - assert.calledOnce(log.info) - assert.calledWithMatch(log.info, 'Account.login.unblockCode.expired') + assert.calledOnce(log.info); + assert.calledWithMatch(log.info, 'Account.login.unblockCode.expired'); - assert.calledOnce(customs.flag) + assert.calledOnce(customs.flag); assert.calledWithExactly(customs.flag, CLIENT_ADDRESS, { email: TEST_EMAIL, errno: error.ERRNO.INVALID_UNBLOCK_CODE - }) + }); } - ) - }) + ); + }); it('unexpected errors when checking an unblock code, cause the original customs error to be rethrown', () => { - customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))) - request.payload.unblockCode = 'WHOOPSY' - db.consumeUnblockCode = sinon.spy(() => P.reject(error.serviceUnavailable())) + customs.check = sinon.spy(() => P.reject(error.requestBlocked(true))); + request.payload.unblockCode = 'WHOOPSY'; + db.consumeUnblockCode = sinon.spy(() => P.reject(error.serviceUnavailable())); return checkCustomsAndLoadAccount(request, TEST_EMAIL).then( - () => { assert.fail('should not succeed') }, + () => { assert.fail('should not succeed'); }, (err) => { - assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'the original customs error was re-thrown') - assert.calledOnce(customs.check) - assert.calledOnce(db.accountRecord) - assert.calledOnce(db.consumeUnblockCode) - assert.notCalled(customs.flag) + assert.equal(err.errno, error.ERRNO.REQUEST_BLOCKED, 'the original customs error was re-thrown'); + assert.calledOnce(customs.check); + assert.calledOnce(db.accountRecord); + assert.calledOnce(db.consumeUnblockCode); + assert.notCalled(customs.flag); } - ) - }) -}) + ); + }); +}); describe('sendSigninNotifications', () => { - let db, log, mailer, metricsContext, request, accountRecord, sessionToken, sendSigninNotifications + let db, log, mailer, metricsContext, request, accountRecord, sessionToken, sendSigninNotifications; beforeEach(() => { - db = mocks.mockDB() - log = mocks.mockLog() - mailer = mocks.mockMailer() - metricsContext = mocks.mockMetricsContext() + db = mocks.mockDB(); + log = mocks.mockLog(); + mailer = mocks.mockMailer(); + metricsContext = mocks.mockMetricsContext(); request = mocks.mockRequest({ log, metricsContext, @@ -470,7 +470,7 @@ describe('sendSigninNotifications', () => { uaOSVersion: '11', uaDeviceType: 'tablet', uaFormFactor: 'iPad' - }) + }); accountRecord = { uid: TEST_UID, primaryEmail: { @@ -478,28 +478,28 @@ describe('sendSigninNotifications', () => { isVerified: true }, emails: [{ email: TEST_EMAIL, isVerified: true, isPrimary: true }] - } + }; sessionToken = { id: 'SESSIONTOKEN', uid: TEST_UID, email: TEST_EMAIL, mustVerify: false - } - sendSigninNotifications = makeSigninUtils({log, db, mailer}).sendSigninNotifications - }) + }; + sendSigninNotifications = makeSigninUtils({log, db, mailer}).sendSigninNotifications; + }); it('emits correct notifications when no verifications are required', () => { return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login'); - assert.calledOnce(metricsContext.stash) - assert.calledWithExactly(metricsContext.stash, sessionToken) + assert.calledOnce(metricsContext.stash); + assert.calledWithExactly(metricsContext.stash, sessionToken); - assert.calledOnce(db.sessions) - assert.calledWithExactly(db.sessions, TEST_UID) + assert.calledOnce(db.sessions); + assert.calledWithExactly(db.sessions, TEST_UID); - assert.calledOnce(log.activityEvent) + assert.calledOnce(log.activityEvent); assert.calledWithExactly(log.activityEvent, { country: 'United States', event: 'account.login', @@ -507,50 +507,50 @@ describe('sendSigninNotifications', () => { service: 'testservice', userAgent: 'test user-agent', uid: TEST_UID - }) + }); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }); - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { deviceCount: 0, email: TEST_EMAIL, service: 'testservice', uid: TEST_UID, userAgent: 'test user-agent' - }) + }); - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginEmail) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginEmail); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); - assert.calledOnce(db.securityEvent) + assert.calledOnce(db.securityEvent); assert.calledWithExactly(db.securityEvent, { name: 'account.login', uid: TEST_UID, ipAddr: CLIENT_ADDRESS, tokenId: 'SESSIONTOKEN' - }) - }) - }) + }); + }); + }); describe('when when signing in with an unverified account', () => { beforeEach(() => { - accountRecord.primaryEmail.isVerified = false - accountRecord.primaryEmail.emailCode = 'emailVerifyCode' - }) + accountRecord.primaryEmail.isVerified = false; + accountRecord.primaryEmail.emailCode = 'emailVerifyCode'; + }); it('emits correct notifications when signing in with an unverified account, session verification not required', () => { return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login'); - assert.calledOnce(metricsContext.stash) + assert.calledOnce(metricsContext.stash); - assert.calledOnce(mailer.sendVerifyCode) + assert.calledOnce(mailer.sendVerifyCode); assert.calledWithExactly(mailer.sendVerifyCode, [], accountRecord, { acceptLanguage: 'en-US', code: 'emailVerifyCode', @@ -574,31 +574,31 @@ describe('sendSigninNotifications', () => { uaOS: 'iOS', uaOSVersion: '11', uaDeviceType: 'tablet' - }) + }); - assert.calledThrice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }) - assert.calledWithMatch(log.flowEvent.getCall(2), { event: 'email.verification.sent' }) - }) - }) + assert.calledThrice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }); + assert.calledWithMatch(log.flowEvent.getCall(2), { event: 'email.verification.sent' }); + }); + }); it('emits correct notifications when session verification required', () => { - sessionToken.tokenVerified = false - sessionToken.tokenVerificationId = 'tokenVerifyCode' - sessionToken.mustVerify = true + sessionToken.tokenVerified = false; + sessionToken.tokenVerificationId = 'tokenVerifyCode'; + sessionToken.mustVerify = true; return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.confirmed', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.confirmed', 'login'); - assert.calledTwice(metricsContext.stash) - assert.calledWithExactly(metricsContext.stash.getCall(0), sessionToken) + assert.calledTwice(metricsContext.stash); + assert.calledWithExactly(metricsContext.stash.getCall(0), sessionToken); assert.calledWithExactly(metricsContext.stash.getCall(1), { uid: TEST_UID, id: 'tokenVerifyCode' - }) + }); - assert.calledOnce(mailer.sendVerifyCode) + assert.calledOnce(mailer.sendVerifyCode); assert.calledWithExactly(mailer.sendVerifyCode, [], accountRecord, { acceptLanguage: 'en-US', code: 'tokenVerifyCode', // the token verification code is used if available @@ -622,82 +622,82 @@ describe('sendSigninNotifications', () => { uaOS: 'iOS', uaOSVersion: '11', uaDeviceType: 'tablet' - }) + }); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.verification.sent' }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.verification.sent' }); - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { deviceCount: 0, email: TEST_EMAIL, service: 'testservice', uid: TEST_UID, userAgent: 'test user-agent' - }) - }) - }) + }); + }); + }); afterEach(() => { - assert.calledOnce(db.sessions) - assert.calledOnce(log.activityEvent) + assert.calledOnce(db.sessions); + assert.calledOnce(log.activityEvent); - assert.notCalled(mailer.sendVerifyLoginEmail) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) + assert.notCalled(mailer.sendVerifyLoginEmail); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); - assert.calledOnce(db.securityEvent) - }) - }) + assert.calledOnce(db.securityEvent); + }); + }); describe('when signing in with a verified account, session already verified', () => { it('emits correct notifications, and sends no emails', () => { - sessionToken.tokenVerified = true - sessionToken.mustVerify = true + sessionToken.tokenVerified = true; + sessionToken.mustVerify = true; return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.login', 'login'); - assert.calledOnce(metricsContext.stash) - assert.calledOnce(db.sessions) - assert.calledOnce(log.activityEvent) - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(metricsContext.stash); + assert.calledOnce(db.sessions); + assert.calledOnce(log.activityEvent); + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { deviceCount: 0, email: TEST_EMAIL, service: 'testservice', uid: TEST_UID, userAgent: 'test user-agent' - }) + }); - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginEmail) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) - assert.notCalled(mailer.sendNewDeviceLoginNotification) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginEmail); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); + assert.notCalled(mailer.sendNewDeviceLoginNotification); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'flow.complete' }); - assert.calledOnce(db.securityEvent) - }) - }) - }) + assert.calledOnce(db.securityEvent); + }); + }); + }); describe('when signing in with a verified account, unverified session', () => { beforeEach(() => { - sessionToken.tokenVerified = false - sessionToken.tokenVerificationId = 'tokenVerifyCode' - sessionToken.tokenVerificationCode = 'tokenVerifyShortCode' - sessionToken.mustVerify = true - }) + sessionToken.tokenVerified = false; + sessionToken.tokenVerificationId = 'tokenVerifyCode'; + sessionToken.tokenVerificationCode = 'tokenVerifyShortCode'; + sessionToken.mustVerify = true; + }); it('emits correct notifications when verificationMethod is not specified', () => { return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) - assert.calledOnce(mailer.sendVerifyLoginEmail) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); + assert.calledOnce(mailer.sendVerifyLoginEmail); assert.calledWithExactly(mailer.sendVerifyLoginEmail, accountRecord.emails, accountRecord, { acceptLanguage: 'en-US', code: 'tokenVerifyCode', @@ -722,31 +722,31 @@ describe('sendSigninNotifications', () => { uaOSVersion: '11', uaDeviceType: 'tablet', uid: TEST_UID - }) + }); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.confirmation.sent' }) - }) - }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.confirmation.sent' }); + }); + }); it('emits correct notifications when verificationMethod=email', () => { return sendSigninNotifications(request, accountRecord, sessionToken, 'email').then(() => { - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) - assert.calledOnce(mailer.sendVerifyLoginEmail) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); + assert.calledOnce(mailer.sendVerifyLoginEmail); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.confirmation.sent' }) - }) - }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.confirmation.sent' }); + }); + }); it('emits correct notifications when verificationMethod=email-2fa', () => { return sendSigninNotifications(request, accountRecord, sessionToken, 'email-2fa').then(() => { - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginEmail) - assert.calledOnce(mailer.sendVerifyLoginCodeEmail) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginEmail); + assert.calledOnce(mailer.sendVerifyLoginCodeEmail); assert.calledWithExactly(mailer.sendVerifyLoginCodeEmail, accountRecord.emails, accountRecord, { acceptLanguage: 'en-US', code: 'tokenVerifyShortCode', @@ -771,248 +771,248 @@ describe('sendSigninNotifications', () => { uaOSVersion: '11', uaDeviceType: 'tablet', uid: TEST_UID - }) + }); - assert.calledTwice(log.flowEvent) - assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }) - assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.tokencode.sent' }) - }) - }) + assert.calledTwice(log.flowEvent); + assert.calledWithMatch(log.flowEvent.getCall(0), { event: 'account.login' }); + assert.calledWithMatch(log.flowEvent.getCall(1), { event: 'email.tokencode.sent' }); + }); + }); it('emits correct notifications when verificationMethod=email-captcha', () => { return sendSigninNotifications(request, accountRecord, sessionToken, 'email-captcha').then(() => { - assert.notCalled(mailer.sendVerifyCode) - assert.notCalled(mailer.sendVerifyLoginEmail) - assert.notCalled(mailer.sendVerifyLoginCodeEmail) + assert.notCalled(mailer.sendVerifyCode); + assert.notCalled(mailer.sendVerifyLoginEmail); + assert.notCalled(mailer.sendVerifyLoginCodeEmail); - assert.calledOnce(log.flowEvent) - assert.calledWithMatch(log.flowEvent, { event: 'account.login' }) - }) - }) + assert.calledOnce(log.flowEvent); + assert.calledWithMatch(log.flowEvent, { event: 'account.login' }); + }); + }); afterEach(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.confirmed', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.confirmed', 'login'); - assert.calledTwice(metricsContext.stash) - assert.calledWithExactly(metricsContext.stash.getCall(0), sessionToken) + assert.calledTwice(metricsContext.stash); + assert.calledWithExactly(metricsContext.stash.getCall(0), sessionToken); assert.calledWithExactly(metricsContext.stash.getCall(1), { uid: TEST_UID, id: 'tokenVerifyCode' - }) + }); - assert.calledOnce(db.sessions) - assert.calledOnce(log.activityEvent) - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(db.sessions); + assert.calledOnce(log.activityEvent); + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { deviceCount: 0, email: TEST_EMAIL, service: 'testservice', uid: TEST_UID, userAgent: 'test user-agent' - }) - assert.calledOnce(db.securityEvent) - }) - }) + }); + assert.calledOnce(db.securityEvent); + }); + }); describe('when signing in for another reason', () => { beforeEach(() => { - request.payload.reason = 'blee' - }) + request.payload.reason = 'blee'; + }); it('does not notify attached services of login', async () => { - await sendSigninNotifications(request, accountRecord, sessionToken, 'email-2fa') - assert.notCalled(log.notifyAttachedServices) - }) - }) + await sendSigninNotifications(request, accountRecord, sessionToken, 'email-2fa'); + assert.notCalled(log.notifyAttachedServices); + }); + }); describe('when signing in with service=sync', () => { beforeEach(() => { - request.payload.service = 'sync' - }) + request.payload.service = 'sync'; + }); it('emits correct notifications with one active session', () => { - db.sessions = sinon.spy(() => P.resolve([sessionToken])) + db.sessions = sinon.spy(() => P.resolve([sessionToken])); return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { service: 'sync', uid: TEST_UID, email: TEST_EMAIL, deviceCount: 1, userAgent: 'test user-agent' - }) - }) - }) + }); + }); + }); it('emits correct notifications with many active sessions', () => { - db.sessions = sinon.spy(() => P.resolve([{}, {}, {}, sessionToken])) + db.sessions = sinon.spy(() => P.resolve([{}, {}, {}, sessionToken])); return sendSigninNotifications(request, accountRecord, sessionToken, undefined).then(() => { - assert.calledOnce(log.notifyAttachedServices) + assert.calledOnce(log.notifyAttachedServices); assert.calledWithExactly(log.notifyAttachedServices, 'login', request, { service: 'sync', uid: TEST_UID, email: TEST_EMAIL, deviceCount: 4, userAgent: 'test user-agent' - }) - }) - }) + }); + }); + }); afterEach(() => { - assert.calledOnce(metricsContext.setFlowCompleteSignal) - assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.signed', 'login') + assert.calledOnce(metricsContext.setFlowCompleteSignal); + assert.calledWithExactly(metricsContext.setFlowCompleteSignal, 'account.signed', 'login'); - assert.calledOnce(metricsContext.stash) - assert.calledOnce(db.sessions) - assert.calledOnce(log.activityEvent) + assert.calledOnce(metricsContext.stash); + assert.calledOnce(db.sessions); + assert.calledOnce(log.activityEvent); - assert.calledOnce(log.flowEvent) - assert.calledWithMatch(log.flowEvent, { event: 'account.login' }) + assert.calledOnce(log.flowEvent); + assert.calledWithMatch(log.flowEvent, { event: 'account.login' }); - assert.calledOnce(db.securityEvent) - }) - }) -}) + assert.calledOnce(db.securityEvent); + }); + }); +}); describe('createKeyFetchToken', () => { - let password, db, metricsContext, request, accountRecord, sessionToken, createKeyFetchToken + let password, db, metricsContext, request, accountRecord, sessionToken, createKeyFetchToken; beforeEach(() => { - db = mocks.mockDB() + db = mocks.mockDB(); password = { unwrap: sinon.spy(() => P.resolve(Buffer.from('abcdef123456'))) - } - db.createKeyFetchToken = sinon.spy(() => P.resolve({ id: 'KEY_FETCH_TOKEN' })) - metricsContext = mocks.mockMetricsContext() + }; + db.createKeyFetchToken = sinon.spy(() => P.resolve({ id: 'KEY_FETCH_TOKEN' })); + metricsContext = mocks.mockMetricsContext(); request = mocks.mockRequest({ metricsContext - }) + }); accountRecord = { uid: TEST_UID, kA: Buffer.from('fedcba012345'), wrapWrapKb: Buffer.from('012345fedcba'), primaryEmail: { isVerified: true }, - } + }; sessionToken = { id: 'SESSIONTOKEN', uid: TEST_UID, email: TEST_EMAIL, tokenVerificationId: 'tokenVerifyCode' - } - createKeyFetchToken = makeSigninUtils({db}).createKeyFetchToken - }) + }; + createKeyFetchToken = makeSigninUtils({db}).createKeyFetchToken; + }); it('creates a keyFetchToken using unwrapped wrapKb', () => { return createKeyFetchToken(request, accountRecord, password, sessionToken).then((res) => { - assert.deepEqual(res, { id: 'KEY_FETCH_TOKEN' }, 'returned the keyFetchToken') + assert.deepEqual(res, { id: 'KEY_FETCH_TOKEN' }, 'returned the keyFetchToken'); - assert.calledOnce(password.unwrap) - assert.calledWithExactly(password.unwrap, accountRecord.wrapWrapKb) + assert.calledOnce(password.unwrap); + assert.calledWithExactly(password.unwrap, accountRecord.wrapWrapKb); - assert.calledOnce(db.createKeyFetchToken) + assert.calledOnce(db.createKeyFetchToken); assert.calledWithExactly(db.createKeyFetchToken, { uid: TEST_UID, kA: accountRecord.kA, wrapKb: Buffer.from('abcdef123456'), emailVerified: true, tokenVerificationId: 'tokenVerifyCode' - }) - }) - }) + }); + }); + }); it('stashes metricsContext on the keyFetchToken', () => { return createKeyFetchToken(request, accountRecord, password, sessionToken).then(() => { - assert.calledOnce(metricsContext.stash) - assert.calledOn(metricsContext.stash, request) - assert.calledWithExactly(metricsContext.stash, { id: 'KEY_FETCH_TOKEN' }) - }) - }) -}) + assert.calledOnce(metricsContext.stash); + assert.calledOn(metricsContext.stash, request); + assert.calledWithExactly(metricsContext.stash, { id: 'KEY_FETCH_TOKEN' }); + }); + }); +}); describe('getSessionVerificationStatus', () => { - let getSessionVerificationStatus + let getSessionVerificationStatus; beforeEach(() => { - getSessionVerificationStatus = makeSigninUtils({}).getSessionVerificationStatus - }) + getSessionVerificationStatus = makeSigninUtils({}).getSessionVerificationStatus; + }); it('correctly reports verified sessions as verified', () => { const sessionToken = { emailVerified: true, tokenVerified: true - } - const res = getSessionVerificationStatus(sessionToken) - assert.deepEqual(res, { verified: true }) - }) + }; + const res = getSessionVerificationStatus(sessionToken); + assert.deepEqual(res, { verified: true }); + }); it('correctly reports unverified sessions with mustVerify=false as verified', () => { const sessionToken = { emailVerified: true, tokenVerified: false, mustVerify: false - } - const res = getSessionVerificationStatus(sessionToken) - assert.deepEqual(res, { verified: true }) - }) + }; + const res = getSessionVerificationStatus(sessionToken); + assert.deepEqual(res, { verified: true }); + }); it('correctly reports unverified accounts as unverified', () => { const sessionToken = { emailVerified: false, tokenVerified: false, mustVerify: false - } - const res = getSessionVerificationStatus(sessionToken) + }; + const res = getSessionVerificationStatus(sessionToken); assert.deepEqual(res, { verified: false, verificationMethod: 'email', verificationReason: 'signup' - }) - }) + }); + }); it('correctly reports unverified sessions with mustVerify=true as unverified', () => { const sessionToken = { emailVerified: true, tokenVerified: false, mustVerify: true - } - const res = getSessionVerificationStatus(sessionToken) + }; + const res = getSessionVerificationStatus(sessionToken); assert.deepEqual(res, { verified: false, verificationMethod: 'email', verificationReason: 'login' - }) - }) + }); + }); it('correctly echos custom verificationMethod param for logins', () => { const sessionToken = { emailVerified: true, tokenVerified: false, mustVerify: true - } - const res = getSessionVerificationStatus(sessionToken, 'email-2fa') + }; + const res = getSessionVerificationStatus(sessionToken, 'email-2fa'); assert.deepEqual(res, { verified: false, verificationMethod: 'email-2fa', verificationReason: 'login' - }) - }) + }); + }); it('does not echo custom verificationMethod param for signups', () => { const sessionToken = { emailVerified: false, tokenVerified: false, mustVerify: true - } - const res = getSessionVerificationStatus(sessionToken, 'email-2fa') + }; + const res = getSessionVerificationStatus(sessionToken, 'email-2fa'); assert.deepEqual(res, { verified: false, verificationMethod: 'email', verificationReason: 'signup' - }) - }) -}) + }); + }); +}); diff --git a/test/local/routes/validators.js b/test/local/routes/validators.js index c6a768cb..c297bc98 100644 --- a/test/local/routes/validators.js +++ b/test/local/routes/validators.js @@ -2,210 +2,210 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const validators = require('../../../lib/routes/validators') +const validators = require('../../../lib/routes/validators'); describe('lib/routes/validators:', () => { it('isValidEmailAddress returns true for valid email addresses', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress('FOO@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress('42@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress('.+#$!%&|*/+-=?^_{}~`@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress('Δ٢@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress('🦀🧙@example.com'), true) - assert.strictEqual(validators.isValidEmailAddress(`${new Array(64).fill('a').join('')}@example.com`), true) - assert.strictEqual(validators.isValidEmailAddress('foo@EXAMPLE.com'), true) - assert.strictEqual(validators.isValidEmailAddress('foo@42.com'), true) - assert.strictEqual(validators.isValidEmailAddress('foo@ex-ample.com'), true) - assert.strictEqual(validators.isValidEmailAddress('foo@Δ٢.com'), true) - assert.strictEqual(validators.isValidEmailAddress('foo@ex🦀ample.com'), true) - assert.strictEqual(validators.isValidEmailAddress(`foo@${new Array(251).fill('a').join('')}.com`), true) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress('FOO@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress('42@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress('.+#$!%&|*/+-=?^_{}~`@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress('Δ٢@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress('🦀🧙@example.com'), true); + assert.strictEqual(validators.isValidEmailAddress(`${new Array(64).fill('a').join('')}@example.com`), true); + assert.strictEqual(validators.isValidEmailAddress('foo@EXAMPLE.com'), true); + assert.strictEqual(validators.isValidEmailAddress('foo@42.com'), true); + assert.strictEqual(validators.isValidEmailAddress('foo@ex-ample.com'), true); + assert.strictEqual(validators.isValidEmailAddress('foo@Δ٢.com'), true); + assert.strictEqual(validators.isValidEmailAddress('foo@ex🦀ample.com'), true); + assert.strictEqual(validators.isValidEmailAddress(`foo@${new Array(251).fill('a').join('')}.com`), true); + }); it('isValidEmailAddress returns false for undefined', () => { - assert.strictEqual(validators.isValidEmailAddress(), false) - }) + assert.strictEqual(validators.isValidEmailAddress(), false); + }); it('isValidEmailAddress returns false if the string has no @', () => { - assert.strictEqual(validators.isValidEmailAddress('fooexample.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('fooexample.com'), false); + }); it('isValidEmailAddress returns false if the string begins with @', () => { - assert.strictEqual(validators.isValidEmailAddress('@example.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('@example.com'), false); + }); it('isValidEmailAddress returns false if the string ends with @', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@'), false); + }); it('isValidEmailAddress returns false if the string contains multiple @', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@foo@example.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@foo@example.com'), false); + }); it('isValidEmailAddress returns false if the user part contains whitespace', () => { - assert.strictEqual(validators.isValidEmailAddress('foo @example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\x160@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\t@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\v@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\r@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\n@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\f@example.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo @example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\x160@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\t@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\v@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\r@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\n@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\f@example.com'), false); + }); it('isValidEmailAddress returns false if the user part contains other control characters', () => { - assert.strictEqual(validators.isValidEmailAddress('foo\0@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\b@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo\x128@example.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo\0@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\b@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo\x128@example.com'), false); + }); it('isValidEmailAddress returns false if the user part contains other disallowed characters', () => { - assert.strictEqual(validators.isValidEmailAddress('foo,@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo;@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo:@example.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo"@example.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo,@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo;@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo:@example.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo"@example.com'), false); + }); it('isValidEmailAddress returns false if the user part exceeds 64 characters', () => { - assert.strictEqual(validators.isValidEmailAddress(`${new Array(65).fill('a').join('')}@example.com`), false) - }) + assert.strictEqual(validators.isValidEmailAddress(`${new Array(65).fill('a').join('')}@example.com`), false); + }); it('isValidEmailAddress returns false if the domain part does not have a period', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example'), false); + }); it('isValidEmailAddress returns false if the domain part ends with a period', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example.com.'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example.com.'), false); + }); it('isValidEmailAddress returns false if the domain part ends with a hyphen', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example.com-'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example.com-'), false); + }); it('isValidEmailAddress returns false if the domain part follows a period with a hyphen', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example.-com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example.-com'), false); + }); it('isValidEmailAddress returns false if the domain part follows a hyphen with a period', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@example-.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@example-.com'), false); + }); it('isValidEmailAddress returns false if the domain part contains whitespace', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@ex ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\x160ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\tample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\vample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\rample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\nample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\fample.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@ex ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\x160ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\tample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\vample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\rample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\nample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\fample.com'), false); + }); it('isValidEmailAddress returns false if the domain part contains other control characters', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@ex\0ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@e\bxample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\x128ample.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@ex\0ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@e\bxample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\x128ample.com'), false); + }); it('isValidEmailAddress returns false if the domain part contains other disallowed characters', () => { - assert.strictEqual(validators.isValidEmailAddress('foo@ex+ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex_ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex#ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex$ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex!ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex~ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex,ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex;ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex:ample.com'), false) - assert.strictEqual(validators.isValidEmailAddress('foo@ex\'ample.com'), false) - }) + assert.strictEqual(validators.isValidEmailAddress('foo@ex+ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex_ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex#ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex$ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex!ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex~ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex,ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex;ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex:ample.com'), false); + assert.strictEqual(validators.isValidEmailAddress('foo@ex\'ample.com'), false); + }); it('isValidEmailAddress returns false if the domain part exceeds 255 characters', () => { - assert.strictEqual(validators.isValidEmailAddress(`foo@${new Array(252).fill('a').join('')}.com`), false) - }) + assert.strictEqual(validators.isValidEmailAddress(`foo@${new Array(252).fill('a').join('')}.com`), false); + }); describe('validators.redirectTo without base hostname:', () => { - const v = validators.redirectTo() + const v = validators.redirectTo(); it('accepts a well-formed https:// URL', function () { - const res = v.validate('https://example.com/path') - assert.ok(! res.error) - assert.equal(res.value, 'https://example.com/path') - }) + const res = v.validate('https://example.com/path'); + assert.ok(! res.error); + assert.equal(res.value, 'https://example.com/path'); + }); it('accepts a well-formed http:// URL', function () { - const res = v.validate('http://example.com/path') - assert.ok(! res.error) - assert.equal(res.value, 'http://example.com/path') - }) + const res = v.validate('http://example.com/path'); + assert.ok(! res.error); + assert.equal(res.value, 'http://example.com/path'); + }); it('rejects a non-URL string', function () { - const res = v.validate('not a url') - assert.ok(res.error) - assert.equal(res.value, 'not a url') - }) + const res = v.validate('not a url'); + assert.ok(res.error); + assert.equal(res.value, 'not a url'); + }); it('rejects a non-http(s) URL', function () { - const res = v.validate('mailto:test@example.com') - assert.ok(res.error) - assert.equal(res.value, 'mailto:test@example.com') - }) + const res = v.validate('mailto:test@example.com'); + assert.ok(res.error); + assert.equal(res.value, 'mailto:test@example.com'); + }); it('rejects tricksy quoted chars in the hostname', function () { - const res = v.validate('https://example.com%2Eevil.com') - assert.ok(res.error) - assert.equal(res.value, 'https://example.com%2Eevil.com') - }) - }) + const res = v.validate('https://example.com%2Eevil.com'); + assert.ok(res.error); + assert.equal(res.value, 'https://example.com%2Eevil.com'); + }); + }); describe('validators.redirectTo with a base hostname:', () => { - const v = validators.redirectTo('mozilla.com') + const v = validators.redirectTo('mozilla.com'); it('accepts a well-formed https:// URL at the base hostname', function () { - const res = v.validate('https://test.mozilla.com/path') - assert.ok(! res.error) - assert.equal(res.value, 'https://test.mozilla.com/path') - }) + const res = v.validate('https://test.mozilla.com/path'); + assert.ok(! res.error); + assert.equal(res.value, 'https://test.mozilla.com/path'); + }); it('accepts a well-formed http:// URL at the base hostname', function () { - const res = v.validate('http://test.mozilla.com/path') - assert.ok(! res.error) - assert.equal(res.value, 'http://test.mozilla.com/path') - }) + const res = v.validate('http://test.mozilla.com/path'); + assert.ok(! res.error); + assert.equal(res.value, 'http://test.mozilla.com/path'); + }); it('rejects a non-URL string', function () { - const res = v.validate('not a url') - assert.ok(res.error) - assert.equal(res.value, 'not a url') - }) + const res = v.validate('not a url'); + assert.ok(res.error); + assert.equal(res.value, 'not a url'); + }); it('rejects a non-http(s) URL at the base hostname', function () { - const res = v.validate('irc://irc.mozilla.com/#fxa') - assert.ok(res.error) - assert.equal(res.value, 'irc://irc.mozilla.com/#fxa') - }) + const res = v.validate('irc://irc.mozilla.com/#fxa'); + assert.ok(res.error); + assert.equal(res.value, 'irc://irc.mozilla.com/#fxa'); + }); it('rejects a well-formed https:// URL at a different hostname', function () { - const res = v.validate('https://test.example.com/path') - assert.ok(res.error) - assert.equal(res.value, 'https://test.example.com/path') - }) + const res = v.validate('https://test.example.com/path'); + assert.ok(res.error); + assert.equal(res.value, 'https://test.example.com/path'); + }); it('accepts a well-formed http:// URL at a different hostname', function () { - const res = v.validate('http://test.example.com/path') - assert.ok(res.error) - assert.equal(res.value, 'http://test.example.com/path') - }) + const res = v.validate('http://test.example.com/path'); + assert.ok(res.error); + assert.equal(res.value, 'http://test.example.com/path'); + }); it('rejects tricksy quoted chars in the hostname', function () { - let res = v.validate('https://evil.com%2Emozilla.com') - assert.ok(res.error) - assert.equal(res.value, 'https://evil.com%2Emozilla.com') + let res = v.validate('https://evil.com%2Emozilla.com'); + assert.ok(res.error); + assert.equal(res.value, 'https://evil.com%2Emozilla.com'); - res = v.validate('https://mozilla.com%2Eevil.com') - assert.ok(res.error) - assert.equal(res.value, 'https://mozilla.com%2Eevil.com') - }) - }) -}) + res = v.validate('https://mozilla.com%2Eevil.com'); + assert.ok(res.error); + assert.equal(res.value, 'https://mozilla.com%2Eevil.com'); + }); + }); +}); diff --git a/test/local/safe-url.js b/test/local/safe-url.js index e0b59b80..e5bfe022 100644 --- a/test/local/safe-url.js +++ b/test/local/safe-url.js @@ -2,179 +2,179 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const mocks = require('../mocks') +const { assert } = require('chai'); +const mocks = require('../mocks'); const SOME_DISALLOWED_GRAPHEMES = [ '\t', '\n', '\r', ' ', '"', '&', '\'', '/', ':', ';', '=', '?', '@', '\\', '~', 'ß', 'π', '💩' -] +]; describe('require:', () => { - let log, SafeUrl + let log, SafeUrl; beforeEach(() => { - log = mocks.mockLog() - SafeUrl = require('../../lib/safe-url')(log) - }) + log = mocks.mockLog(); + SafeUrl = require('../../lib/safe-url')(log); + }); it('returned the expected interface', () => { - assert.equal(typeof SafeUrl, 'function') - assert.equal(SafeUrl.length, 2) - }) + assert.equal(typeof SafeUrl, 'function'); + assert.equal(SafeUrl.length, 2); + }); describe('instantiate:', () => { - let safeUrl + let safeUrl; beforeEach(() => { - safeUrl = new SafeUrl('/foo/:bar', 'baz') - }) + safeUrl = new SafeUrl('/foo/:bar', 'baz'); + }); it('returned the expected interface', () => { - assert.equal(typeof safeUrl.render, 'function') - assert.equal(safeUrl.render.length, 0) - }) + assert.equal(typeof safeUrl.render, 'function'); + assert.equal(safeUrl.render.length, 0); + }); it('interpolates correctly', () => { - assert.equal(safeUrl.render({ bar: 'wibble' }), '/foo/wibble') - }) + assert.equal(safeUrl.render({ bar: 'wibble' }), '/foo/wibble'); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('logs an error and throws when param is missing', () => { - let threw = false + let threw = false; try { - safeUrl.render({}) + safeUrl.render({}); } catch (err) { - threw = true - assert.equal(err.output.payload.op, 'safeUrl.params.mismatch') + threw = true; + assert.equal(err.output.payload.op, 'safeUrl.params.mismatch'); assert.deepEqual(err.output.payload.data, { expected: [ 'bar' ], keys: [] - }) + }); } - assert.equal(threw, true) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.params.mismatch') + assert.equal(threw, true); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.params.mismatch'); assert.deepEqual(log.error.args[0][1], { keys: [], expected: [ 'bar' ], caller: 'baz' - }) - }) + }); + }); it('logs an error and throws when param has wrong key', () => { - assert.throws(() => safeUrl.render({ qux: 'wibble' })) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.params.unexpected') + assert.throws(() => safeUrl.render({ qux: 'wibble' })); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.params.unexpected'); assert.deepEqual(log.error.args[0][1], { key: 'qux', expected: [ 'bar' ], caller: 'baz' - }) - }) + }); + }); it('logs an error and throws when param is empty string', () => { - let threw = false + let threw = false; try { - safeUrl.render({ bar: '' }) + safeUrl.render({ bar: '' }); } catch (err) { - threw = true - assert.equal(err.output.payload.op, 'safeUrl.bad') + threw = true; + assert.equal(err.output.payload.op, 'safeUrl.bad'); assert.deepEqual(err.output.payload.data, { location: 'paramVal', key: 'bar', value: '' - }) + }); } - assert.equal(threw, true) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.bad') + assert.equal(threw, true); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.bad'); assert.deepEqual(log.error.args[0][1], { location: 'paramVal', key: 'bar', value: '', caller: 'baz' - }) - }) + }); + }); it('logs an error and throws when param is object', () => { - assert.throws(() => safeUrl.render({ bar: {} })) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.bad') + assert.throws(() => safeUrl.render({ bar: {} })); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.bad'); assert.deepEqual(log.error.args[0][1], { location: 'paramVal', key: 'bar', value: {}, caller: 'baz' - }) - }) + }); + }); SOME_DISALLOWED_GRAPHEMES.forEach(grapheme => { it(`logs an error and throws when param contains ${grapheme}`, () => { - assert.throws(() => safeUrl.render({ bar: `wibble${grapheme}` })) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.unsafe') + assert.throws(() => safeUrl.render({ bar: `wibble${grapheme}` })); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.unsafe'); assert.deepEqual(log.error.args[0][1], { location: 'paramVal', key: 'bar', value: `wibble${grapheme}`, caller: 'baz' - }) - }) - }) + }); + }); + }); it('logs an error and throws for bad query keys', () => { - let threw = false + let threw = false; try { - safeUrl.render({ bar: 'baz' }, {'💩': 'bar'}) + safeUrl.render({ bar: 'baz' }, {'💩': 'bar'}); } catch (err) { - threw = true - assert.equal(err.output.payload.op, 'safeUrl.unsafe') + threw = true; + assert.equal(err.output.payload.op, 'safeUrl.unsafe'); assert.deepEqual(err.output.payload.data, { location: 'queryKey', key: '💩', value: '💩' - }) + }); } - assert.equal(threw, true) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.unsafe') + assert.equal(threw, true); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.unsafe'); assert.deepEqual(log.error.args[0][1], { location: 'queryKey', key: '💩', value: '💩', caller: 'baz' - }) - }) + }); + }); it('logs an error and throws for bad query values', () => { - assert.throws(() => safeUrl.render({ bar: 'baz' }, {'bar': '💩'})) - assert.equal(log.error.callCount, 1) - assert.equal(log.error.args[0][0], 'safeUrl.unsafe') + assert.throws(() => safeUrl.render({ bar: 'baz' }, {'bar': '💩'})); + assert.equal(log.error.callCount, 1); + assert.equal(log.error.args[0][0], 'safeUrl.unsafe'); assert.deepEqual(log.error.args[0][1], { location: 'queryVal', key: 'bar', value: '💩', caller: 'baz' - }) - }) - }) + }); + }); + }); describe('instantiate with two params', () => { - let safeUrl + let safeUrl; beforeEach(() => { - safeUrl = new SafeUrl('/foo/:bar/:baz') - }) + safeUrl = new SafeUrl('/foo/:bar/:baz'); + }); it('interpolates correctly', () => { - assert.equal(safeUrl.render({ bar: 'wibble', baz: 'blee' }), '/foo/wibble/blee') - }) - }) -}) + assert.equal(safeUrl.render({ bar: 'wibble', baz: 'blee' }), '/foo/wibble/blee'); + }); + }); +}); diff --git a/test/local/scheme-refresh-token.js b/test/local/scheme-refresh-token.js index 616fe5d5..3a97c924 100644 --- a/test/local/scheme-refresh-token.js +++ b/test/local/scheme-refresh-token.js @@ -2,19 +2,19 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const schemeRefreshToken = require('../../lib/scheme-refresh-token') -const sinon = require('sinon') +const { assert } = require('chai'); +const schemeRefreshToken = require('../../lib/scheme-refresh-token'); +const sinon = require('sinon'); -const OAUTH_CLIENT_ID = '3c49430b43dfba77' -const OAUTH_CLIENT_NAME = 'Android Components Reference Browser' +const OAUTH_CLIENT_ID = '3c49430b43dfba77'; +const OAUTH_CLIENT_NAME = 'Android Components Reference Browser'; describe('lib/scheme-refresh-token', () => { - let db - let oauthdb - let response + let db; + let oauthdb; + let response; beforeEach(() => { db = { @@ -35,7 +35,7 @@ describe('lib/scheme-refresh-token', () => { lastAccessTimeFormatted: 'a few seconds ago' } ])) - } + }; oauthdb = { checkRefreshToken: sinon.spy(() => () => Promise.resolve({})), @@ -46,51 +46,51 @@ describe('lib/scheme-refresh-token', () => { image_uri: '', redirect_uri: `http://127.0.0.1:3030/oauth/success/${OAUTH_CLIENT_ID}` })), - } + }; response = { unauthenticated: sinon.spy(() => {}), authenticated: sinon.spy(() => {}) - } - }) + }; + }); it('handles bad authorization header', async () => { - const scheme = schemeRefreshToken() + const scheme = schemeRefreshToken(); try { await scheme().authenticate({ headers: { authorization: 'Bad Auth' } - }) + }); } catch (err) { - assert.equal(err.message, 'Invalid parameter in request body') + assert.equal(err.message, 'Invalid parameter in request body'); } - }) + }); it('handles bad refresh token format', async () => { - const scheme = schemeRefreshToken() + const scheme = schemeRefreshToken(); try { await scheme().authenticate({ headers: { authorization: 'Bearer Foo' } - }) + }); } catch (err) { - assert.equal(err.message, 'Invalid parameter in request body') + assert.equal(err.message, 'Invalid parameter in request body'); } - }) + }); it('works with a good authorization header', async () => { - const scheme = schemeRefreshToken(db, oauthdb) + const scheme = schemeRefreshToken(db, oauthdb); await scheme().authenticate({ headers: { authorization: 'Bearer B53DF2CE2BDB91820CB0A5D68201EF87D8D8A0DFC11829FB074B6426F537EE78' } - }, response) + }, response); - assert.isTrue(response.unauthenticated.calledOnce) - assert.isFalse(response.authenticated.calledOnce) - }) + assert.isTrue(response.unauthenticated.calledOnce); + assert.isFalse(response.authenticated.calledOnce); + }); it('authenticates with devices', async () => { oauthdb.checkRefreshToken = sinon.spy(() => Promise.resolve({ @@ -98,17 +98,17 @@ describe('lib/scheme-refresh-token', () => { scope: 'https://identity.mozilla.com/apps/oldsync', sub: '620203b5773b4c1d968e1fd4505a6885', jti: '40f61392cf69b0be709fbd3122d0726bb32247b476b2a28451345e7a5555cec7' - })) + })); - const scheme = schemeRefreshToken(db, oauthdb) + const scheme = schemeRefreshToken(db, oauthdb); await scheme().authenticate({ headers: { authorization: 'Bearer B53DF2CE2BDB91820CB0A5D68201EF87D8D8A0DFC11829FB074B6426F537EE78' } - }, response) + }, response); - assert.isFalse(response.unauthenticated.called) - assert.isTrue(response.authenticated.calledOnce) + assert.isFalse(response.unauthenticated.called); + assert.isTrue(response.authenticated.calledOnce); assert.deepEqual(response.authenticated.args[0][0].credentials, { uid: '620203b5773b4c1d968e1fd4505a6885', tokenVerified: true, @@ -129,8 +129,8 @@ describe('lib/scheme-refresh-token', () => { deviceCallbackPublicKey: undefined, deviceCallbackURL: undefined, deviceCreatedAt: undefined, - }) - }) + }); + }); it('requires an approved scope to authenticated', async () => { oauthdb.checkRefreshToken = sinon.spy(() => Promise.resolve({ @@ -138,17 +138,17 @@ describe('lib/scheme-refresh-token', () => { scope: 'profile', sub: '620203b5773b4c1d968e1fd4505a6885', jti: '40f61392cf69b0be709fbd3122d0726bb32247b476b2a28451345e7a5555cec7' - })) + })); - const scheme = schemeRefreshToken(db, oauthdb) + const scheme = schemeRefreshToken(db, oauthdb); await scheme().authenticate({ headers: { authorization: 'Bearer B53DF2CE2BDB91820CB0A5D68201EF87D8D8A0DFC11829FB074B6426F537EE78' } - }, response) + }, response); - assert.isTrue(response.unauthenticated.called) - assert.isFalse(response.authenticated.calledOnce) - }) + assert.isTrue(response.unauthenticated.called); + assert.isFalse(response.authenticated.calledOnce); + }); -}) +}); diff --git a/test/local/senders/email.js b/test/local/senders/email.js index 6ab2c70e..afeadf52 100644 --- a/test/local/senders/email.js +++ b/test/local/senders/email.js @@ -2,23 +2,23 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const cp = require('child_process') -const mocks = require('../../mocks') -const P = require('bluebird') -const path = require('path') -const proxyquire = require('proxyquire').noPreserveCache() -const sinon = require('sinon') +const { assert } = require('chai'); +const cp = require('child_process'); +const mocks = require('../../mocks'); +const P = require('bluebird'); +const path = require('path'); +const proxyquire = require('proxyquire').noPreserveCache(); +const sinon = require('sinon'); -cp.execAsync = P.promisify(cp.exec) +cp.execAsync = P.promisify(cp.exec); -const config = require(`${ROOT_DIR}/config`) +const config = require(`${ROOT_DIR}/config`); -const TEMPLATE_VERSIONS = require(`${ROOT_DIR}/lib/senders/templates/_versions.json`) +const TEMPLATE_VERSIONS = require(`${ROOT_DIR}/lib/senders/templates/_versions.json`); const messageTypes = [ 'lowRecoveryCodesEmail', @@ -43,7 +43,7 @@ const messageTypes = [ 'passwordResetAccountRecoveryEmail', 'postAddAccountRecoveryEmail', 'postRemoveAccountRecoveryEmail' -] +]; const typesContainSupportLinks = [ 'lowRecoveryCodesEmail', @@ -63,13 +63,13 @@ const typesContainSupportLinks = [ 'passwordResetAccountRecoveryEmail', 'postAddAccountRecoveryEmail', 'postRemoveAccountRecoveryEmail' -] +]; const typesContainPasswordResetLinks = [ 'passwordChangedEmail', 'passwordResetEmail', 'passwordResetRequiredEmail' -] +]; const typesContainPasswordChangeLinks = [ 'newDeviceLoginEmail', @@ -86,35 +86,35 @@ const typesContainPasswordChangeLinks = [ 'passwordResetAccountRecoveryEmail', 'postAddAccountRecoveryEmail', 'postRemoveAccountRecoveryEmail' -] +]; const typesContainUnblockCode = [ 'unblockCodeEmail' -] +]; const typesContainTokenCode = [ 'verifyLoginCodeEmail' -] +]; const typesContainRevokeAccountRecoveryLinks = [ 'postAddAccountRecoveryEmail' -] +]; const typesContainCreateAccountRecoveryLinks = [ 'passwordResetAccountRecoveryEmail' -] +]; const typesContainReportSignInLinks = [ 'unblockCodeEmail' -] +]; const typesContainAndroidStoreLinks = [ 'postVerifyEmail' -] +]; const typesContainIOSStoreLinks = [ 'postVerifyEmail' -] +]; const typesContainLocationData = [ 'newDeviceLoginEmail', @@ -132,11 +132,11 @@ const typesContainLocationData = [ 'passwordResetAccountRecoveryEmail', 'postRemoveTwoStepAuthenticationEmail', 'postRemoveAccountRecoveryEmail' -] +]; const typesContainPasswordManagerInfoLinks = [ 'passwordResetRequiredEmail', -] +]; const typesContainManageSettingsLinks = [ 'newDeviceLoginEmail', @@ -149,19 +149,19 @@ const typesContainManageSettingsLinks = [ 'postConsumeRecoveryCodeEmail', 'postRemoveTwoStepAuthenticationEmail', 'postRemoveAccountRecoveryEmail' -] +]; const typesContainRecoveryCodeLinks = [ 'lowRecoveryCodesEmail' -] +]; const typesPrependVerificationSubdomain = [ 'verifyEmail', 'verifyLoginEmail' -] +]; function includes(haystack, needle) { - return (haystack.indexOf(needle) > -1) + return (haystack.indexOf(needle) > -1); } function getLocationMessage (location) { @@ -171,68 +171,68 @@ function getLocationMessage (location) { location: location, service: 'sync', timeZone: 'America/Los_Angeles' - } + }; } function sesMessageTagsHeaderValue(templateName, serviceName) { - return `messageType=fxa-${templateName}, app=fxa, service=${serviceName}` + return `messageType=fxa-${templateName}, app=fxa, service=${serviceName}`; } function stubSendMail (stub, status) { return (emailConfig, callback) => { try { - stub(emailConfig) - return callback(null, status) + stub(emailConfig); + return callback(null, status); } catch (err) { - return callback(err, status) + return callback(err, status); } - } + }; } describe( 'lib/senders/email:', () => { - let mockLog, redis, mailer, oauthClientInfo + let mockLog, redis, mailer, oauthClientInfo; beforeEach(() => { return P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - mockLog = mocks.mockLog() + mockLog = mocks.mockLog(); oauthClientInfo = { fetch: sinon.spy(async (service) => { if (service === 'sync') { return { name: 'Firefox' - } + }; } else if (service === 'foo') { return { name: 'biz baz relier name' - } + }; } }) - } + }; redis = { get: sinon.spy(() => P.resolve()) - } + }; const Mailer = proxyquire(`${ROOT_DIR}/lib/senders/email`, { './oauth_client_info': () => oauthClientInfo, '../redis': () => redis - })(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, config.get('smtp')) - }) - }) + })(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, config.get('smtp')); + }); + }); - afterEach(() => mailer.stop()) + afterEach(() => mailer.stop()); it('mailer and emailService are not mocked', () => { - assert.isObject(mailer.mailer) - assert.isFunction(mailer.mailer.sendMail) - assert.isObject(mailer.emailService) - assert.isFunction(mailer.emailService.sendMail) - assert.notEqual(mailer.mailer, mailer.emailService) - }) + assert.isObject(mailer.mailer); + assert.isFunction(mailer.mailer.sendMail); + assert.isObject(mailer.emailService); + assert.isFunction(mailer.emailService.sendMail); + assert.notEqual(mailer.mailer, mailer.emailService); + }); messageTypes.forEach( function (type) { @@ -248,157 +248,157 @@ describe( type: 'secondary', flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', flowBeginTime: Date.now() - } + }; it( 'Contains template header for ' + type, function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(emailConfig.from, config.get('smtp.sender'), 'from header is correct') - assert.equal(emailConfig.sender, config.get('smtp.sender'), 'sender header is correct') - const templateName = emailConfig.headers['X-Template-Name'] - const templateVersion = emailConfig.headers['X-Template-Version'] + assert.equal(emailConfig.from, config.get('smtp.sender'), 'from header is correct'); + assert.equal(emailConfig.sender, config.get('smtp.sender'), 'sender header is correct'); + const templateName = emailConfig.headers['X-Template-Name']; + const templateVersion = emailConfig.headers['X-Template-Version']; if (type === 'verifyEmail') { // Handle special case for verify email - assert.equal(templateName, 'verifySyncEmail') + assert.equal(templateName, 'verifySyncEmail'); } else { - assert.equal(templateName, type) + assert.equal(templateName, type); } - assert.equal(templateVersion, TEMPLATE_VERSIONS[templateName], 'template version is correct') - }) - return mailer[type](message) + assert.equal(templateVersion, TEMPLATE_VERSIONS[templateName], 'template version is correct'); + }); + return mailer[type](message); } - ) + ); it(`Contains metrics headers for ${type}`, () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - const headers = emailConfig.headers - assert.equal(headers['X-Device-Id'], message.deviceId, 'device id header is correct') - assert.equal(headers['X-Flow-Id'], message.flowId, 'flow id header is correct') - assert.equal(headers['X-Flow-Begin-Time'], message.flowBeginTime, 'flow begin time header is correct') - assert.equal(headers['X-Service-Id'], message.service, 'service id header is correct') - assert.equal(headers['X-Uid'], message.uid, 'uid header is correct') - assert.equal(headers['X-Email-Service'], 'fxa-auth-server') - }) - return mailer[type](message) - }) + const headers = emailConfig.headers; + assert.equal(headers['X-Device-Id'], message.deviceId, 'device id header is correct'); + assert.equal(headers['X-Flow-Id'], message.flowId, 'flow id header is correct'); + assert.equal(headers['X-Flow-Begin-Time'], message.flowBeginTime, 'flow begin time header is correct'); + assert.equal(headers['X-Service-Id'], message.service, 'service id header is correct'); + assert.equal(headers['X-Uid'], message.uid, 'uid header is correct'); + assert.equal(headers['X-Email-Service'], 'fxa-auth-server'); + }); + return mailer[type](message); + }); it( 'test privacy link is in email template output for ' + type, function () { - var privacyLink = mailer.createPrivacyLink(type) + var privacyLink = mailer.createPrivacyLink(type); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, privacyLink)) - assert.ok(includes(emailConfig.text, privacyLink)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, privacyLink)); + assert.ok(includes(emailConfig.text, privacyLink)); + }); + return mailer[type](message); } - ) + ); if (type === 'verifySecondaryEmail') { it( 'contains correct type ' + type, function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.headers['X-Link'], 'type=secondary')) - assert.ok(includes(emailConfig.html, 'type=secondary')) - assert.ok(includes(emailConfig.text, 'type=secondary')) - assert.ok(! includes(emailConfig.headers['X-Link'], 'utm_source=email')) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.headers['X-Link'], 'type=secondary')); + assert.ok(includes(emailConfig.html, 'type=secondary')); + assert.ok(includes(emailConfig.text, 'type=secondary')); + assert.ok(! includes(emailConfig.headers['X-Link'], 'utm_source=email')); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } it( 'If sesConfigurationSet is not defined, then outgoing email does not contain X-SES* headers, for type ' + type, function () { - assert.ok('sesConfigurationSet' in mailer, 'configuration key exists') + assert.ok('sesConfigurationSet' in mailer, 'configuration key exists'); mailer.mailer.sendMail = stubSendMail(emailConfig => { - var sesConfigurationSetHeader = emailConfig.headers['X-SES-CONFIGURATION-SET'] - assert.ok(! sesConfigurationSetHeader) - var sesMessageTags = emailConfig.headers['X-SES-MESSAGE-TAGS'] - assert.ok(! sesMessageTags) - }) + var sesConfigurationSetHeader = emailConfig.headers['X-SES-CONFIGURATION-SET']; + assert.ok(! sesConfigurationSetHeader); + var sesMessageTags = emailConfig.headers['X-SES-MESSAGE-TAGS']; + assert.ok(! sesMessageTags); + }); - return mailer[type](message) + return mailer[type](message); } - ) + ); it( 'If sesConfigurationSet is defined, then outgoing email will contain X-SES* headers, for type ' + type, function () { - assert.ok('sesConfigurationSet' in mailer, 'configuration key exists') - var savedSesConfigurationSet = mailer.sesConfigurationSet - mailer.sesConfigurationSet = 'some-defined-value' + assert.ok('sesConfigurationSet' in mailer, 'configuration key exists'); + var savedSesConfigurationSet = mailer.sesConfigurationSet; + mailer.sesConfigurationSet = 'some-defined-value'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - var sesConfigurationSetHeader = emailConfig.headers['X-SES-CONFIGURATION-SET'] - assert.equal(sesConfigurationSetHeader, 'some-defined-value') + var sesConfigurationSetHeader = emailConfig.headers['X-SES-CONFIGURATION-SET']; + assert.equal(sesConfigurationSetHeader, 'some-defined-value'); - var sesMessageTags = emailConfig.headers['X-SES-MESSAGE-TAGS'] - var expectedSesMessageTags = sesMessageTagsHeaderValue(type, 'fxa-auth-server') - assert.equal(sesMessageTags, expectedSesMessageTags) + var sesMessageTags = emailConfig.headers['X-SES-MESSAGE-TAGS']; + var expectedSesMessageTags = sesMessageTagsHeaderValue(type, 'fxa-auth-server'); + assert.equal(sesMessageTags, expectedSesMessageTags); - mailer.sesConfigurationSet = savedSesConfigurationSet - }) + mailer.sesConfigurationSet = savedSesConfigurationSet; + }); - return mailer[type](message) + return mailer[type](message); } - ) + ); if (includes(typesContainSupportLinks, type)) { it( 'test support link is in email template output for ' + type, function () { - var supportTextLink = mailer.createSupportLink(type) + var supportTextLink = mailer.createSupportLink(type); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, supportTextLink)) - assert.ok(includes(emailConfig.text, supportTextLink)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, supportTextLink)); + assert.ok(includes(emailConfig.text, supportTextLink)); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainPasswordResetLinks, type)) { it( 'reset password link is in email template output for ' + type, function () { - var resetPasswordLink = mailer.createPasswordResetLink(message.email, type) + var resetPasswordLink = mailer.createPasswordResetLink(message.email, type); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, resetPasswordLink)) - assert.ok(includes(emailConfig.text, resetPasswordLink)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, resetPasswordLink)); + assert.ok(includes(emailConfig.text, resetPasswordLink)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainPasswordChangeLinks, type)) { it( 'password change link is in email template output for ' + type, function () { - var passwordChangeLink = mailer.createPasswordChangeLink(message.email, type) + var passwordChangeLink = mailer.createPasswordChangeLink(message.email, type); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, passwordChangeLink)) - assert.ok(includes(emailConfig.text, passwordChangeLink)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, passwordChangeLink)); + assert.ok(includes(emailConfig.text, passwordChangeLink)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainUnblockCode, type)) { @@ -406,24 +406,24 @@ describe( 'unblock code is in email template output for ' + type, function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, message.unblockCode)) - assert.ok(includes(emailConfig.text, message.unblockCode)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, message.unblockCode)); + assert.ok(includes(emailConfig.text, message.unblockCode)); + }); + return mailer[type](message); } - ) + ); } if (includes(typesPrependVerificationSubdomain, type)) { it(`can prepend verification subdomain for ${type}`, () => { - mailer.prependVerificationSubdomain.enabled = true - const subdomain = mailer.prependVerificationSubdomain.subdomain + mailer.prependVerificationSubdomain.enabled = true; + const subdomain = mailer.prependVerificationSubdomain.subdomain; mailer.mailer.sendMail = stubSendMail(emailConfig => { const link = emailConfig.headers['X-Link']; - assert.equal(link.indexOf(`http://${subdomain}.`), 0, 'link prepend with domain') - }) - return mailer[type](message) - }) + assert.equal(link.indexOf(`http://${subdomain}.`), 0, 'link prepend with domain'); + }); + return mailer[type](message); + }); } if (includes(typesContainTokenCode, type)) { @@ -431,12 +431,12 @@ describe( 'login code is in email template output for ' + type, function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, message.tokenCode)) - assert.ok(includes(emailConfig.text, message.tokenCode)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, message.tokenCode)); + assert.ok(includes(emailConfig.text, message.tokenCode)); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainReportSignInLinks, type)) { @@ -445,118 +445,118 @@ describe( function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { var reportSignInLink = - mailer.createReportSignInLink(type, message) - assert.ok(includes(emailConfig.html, reportSignInLink)) - assert.ok(includes(emailConfig.text, reportSignInLink)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) + mailer.createReportSignInLink(type, message); + assert.ok(includes(emailConfig.html, reportSignInLink)); + assert.ok(includes(emailConfig.text, reportSignInLink)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainRevokeAccountRecoveryLinks, type)) { it('revoke account recovery link is in email template output for ' + type, () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - const link = mailer.createRevokeAccountRecoveryLink(type, message) - assert.ok(includes(emailConfig.html, link)) - assert.ok(includes(emailConfig.text, link)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) - }) + const link = mailer.createRevokeAccountRecoveryLink(type, message); + assert.ok(includes(emailConfig.html, link)); + assert.ok(includes(emailConfig.text, link)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); + }); } if (includes(typesContainCreateAccountRecoveryLinks, type)) { it('create account recovery link is in email template output for ' + type, () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - const link = mailer._generateCreateAccountRecoveryLinks(message, type).link - assert.ok(includes(emailConfig.html, link)) - assert.ok(includes(emailConfig.text, link)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) - }) + const link = mailer._generateCreateAccountRecoveryLinks(message, type).link; + assert.ok(includes(emailConfig.html, link)); + assert.ok(includes(emailConfig.text, link)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); + }); } if (includes(typesContainAndroidStoreLinks, type)) { it( 'Android store link is in email template output for ' + type, function () { - var androidStoreLink = mailer.androidUrl + var androidStoreLink = mailer.androidUrl; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, androidStoreLink)) - assert.ok(includes(emailConfig.html, 'utm_source=email')) + assert.ok(includes(emailConfig.html, androidStoreLink)); + assert.ok(includes(emailConfig.html, 'utm_source=email')); // only the html email contains links to the store - }) - return mailer[type](message) + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainIOSStoreLinks, type)) { it( 'IOS store link is in email template output for ' + type, function () { - var iosStoreLink = mailer.iosUrl + var iosStoreLink = mailer.iosUrl; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, iosStoreLink)) - assert.ok(includes(emailConfig.html, 'utm_source=email')) + assert.ok(includes(emailConfig.html, iosStoreLink)); + assert.ok(includes(emailConfig.html, 'utm_source=email')); // only the html email contains links to the store - }) - return mailer[type](message) + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainPasswordManagerInfoLinks, type)) { it( 'password manager info link is in email template output for ' + type, function () { - var passwordManagerInfoUrl = mailer._generateLinks(config.get('smtp').passwordManagerInfoUrl, message.email, {}, type).passwordManagerInfoUrl + var passwordManagerInfoUrl = mailer._generateLinks(config.get('smtp').passwordManagerInfoUrl, message.email, {}, type).passwordManagerInfoUrl; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, passwordManagerInfoUrl)) - assert.ok(includes(emailConfig.text, passwordManagerInfoUrl)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, passwordManagerInfoUrl)); + assert.ok(includes(emailConfig.text, passwordManagerInfoUrl)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } if (includes(typesContainManageSettingsLinks, type)) { it('account settings info link is in email template output for ' + type, () => { - const accountSettingsUrl = mailer._generateSettingLinks(message, type).link + const accountSettingsUrl = mailer._generateSettingLinks(message, type).link; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, accountSettingsUrl)) - assert.ok(includes(emailConfig.text, accountSettingsUrl)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) - }) + assert.ok(includes(emailConfig.html, accountSettingsUrl)); + assert.ok(includes(emailConfig.text, accountSettingsUrl)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); + }); } if (includes(typesContainRecoveryCodeLinks, type)) { it('recovery code settings info link is in email template output for ' + type, () => { - const url = mailer._generateLowRecoveryCodesLinks(message, type).link + const url = mailer._generateLowRecoveryCodesLinks(message, type).link; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, url)) - assert.ok(includes(emailConfig.text, url)) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) - }) + assert.ok(includes(emailConfig.html, url)); + assert.ok(includes(emailConfig.text, url)); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); + }); } if (includes(typesContainLocationData, type)) { @@ -565,155 +565,155 @@ describe( city: 'Mountain View', country: 'USA', stateCode: 'CA' - } + }; if (type === 'verifySecondaryEmail') { it( 'original user email data is in template for ' + type, function () { - var message = getLocationMessage(defaultLocation) - message.primaryEmail = 'user@email.com' + var message = getLocationMessage(defaultLocation); + message.primaryEmail = 'user@email.com'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, message.primaryEmail)) - assert.ok(includes(emailConfig.html, message.email)) - assert.ok(includes(emailConfig.text, message.primaryEmail)) - assert.ok(includes(emailConfig.text, message.email)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, message.primaryEmail)); + assert.ok(includes(emailConfig.html, message.email)); + assert.ok(includes(emailConfig.text, message.primaryEmail)); + assert.ok(includes(emailConfig.text, message.email)); + }); + return mailer[type](message); } - ) + ); } it( 'ip data is in template for ' + type, function () { - var message = getLocationMessage(defaultLocation) + var message = getLocationMessage(defaultLocation); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, message.ip)) + assert.ok(includes(emailConfig.html, message.ip)); - assert.ok(includes(emailConfig.text, message.ip)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.text, message.ip)); + }); + return mailer[type](message); } - ) + ); it( 'location is correct with city, country, stateCode for ' + type, function () { - var location = defaultLocation - var message = getLocationMessage(defaultLocation) + var location = defaultLocation; + var message = getLocationMessage(defaultLocation); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, location.city + ', ' + location.stateCode + ', ' + location.country)) - assert.ok(includes(emailConfig.text, location.city + ', ' + location.stateCode + ', ' + location.country)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, location.city + ', ' + location.stateCode + ', ' + location.country)); + assert.ok(includes(emailConfig.text, location.city + ', ' + location.stateCode + ', ' + location.country)); + }); + return mailer[type](message); } - ) + ); it( 'location is correct with city, country for ' + type, function () { - var location = Object.assign({}, defaultLocation) - delete location.stateCode - var message = getLocationMessage(location) + var location = Object.assign({}, defaultLocation); + delete location.stateCode; + var message = getLocationMessage(location); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, location.city + ', ' + location.country)) - assert.ok(includes(emailConfig.text, location.city + ', ' + location.country)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, location.city + ', ' + location.country)); + assert.ok(includes(emailConfig.text, location.city + ', ' + location.country)); + }); + return mailer[type](message); } - ) + ); it( 'location is correct with stateCode, country for ' + type, function () { - var location = Object.assign({}, defaultLocation) - delete location.city - var message = getLocationMessage(location) + var location = Object.assign({}, defaultLocation); + delete location.city; + var message = getLocationMessage(location); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, location.stateCode + ', ' + location.country)) - assert.ok(includes(emailConfig.text, location.stateCode + ', ' + location.country)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, location.stateCode + ', ' + location.country)); + assert.ok(includes(emailConfig.text, location.stateCode + ', ' + location.country)); + }); + return mailer[type](message); } - ) + ); it( 'location is correct with country for ' + type, function () { - var location = Object.assign({}, defaultLocation) - delete location.city - delete location.stateCode - var message = getLocationMessage(location) + var location = Object.assign({}, defaultLocation); + delete location.city; + delete location.stateCode; + var message = getLocationMessage(location); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, location.country)) - assert.ok(includes(emailConfig.text, location.country)) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, location.country)); + assert.ok(includes(emailConfig.text, location.country)); + }); + return mailer[type](message); } - ) + ); it( 'device name is correct for ' + type, function () { - var message = getLocationMessage(defaultLocation) - message.uaBrowser = 'Firefox' - message.uaOS = 'BeOS' - message.uaOSVersion = '1.0' + var message = getLocationMessage(defaultLocation); + message.uaBrowser = 'Firefox'; + message.uaOS = 'BeOS'; + message.uaOSVersion = '1.0'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, 'Firefox on BeOS 1.0')) - assert.ok(includes(emailConfig.text, 'Firefox on BeOS 1.0')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, 'Firefox on BeOS 1.0')); + assert.ok(includes(emailConfig.text, 'Firefox on BeOS 1.0')); + }); + return mailer[type](message); } - ) + ); it(`drops dodgy-looking uaBrowser property for ${type}`, () => { - const message = getLocationMessage(defaultLocation) - message.uaBrowser = 'Firefox' - message.uaOS = 'Android' + const message = getLocationMessage(defaultLocation); + message.uaBrowser = 'Firefox'; + message.uaOS = 'Android'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(! includes(emailConfig.html, 'Firefox on Android')) - assert.ok(includes(emailConfig.html, 'Android')) - assert.ok(! includes(emailConfig.text, 'Firefox on Android')) - assert.ok(includes(emailConfig.text, 'Android')) - }) - return mailer[type](message) - }) + assert.ok(! includes(emailConfig.html, 'Firefox on Android')); + assert.ok(includes(emailConfig.html, 'Android')); + assert.ok(! includes(emailConfig.text, 'Firefox on Android')); + assert.ok(includes(emailConfig.text, 'Android')); + }); + return mailer[type](message); + }); it(`drops dodgy-looking uaOS property for ${type}`, () => { - const message = getLocationMessage(defaultLocation) - message.uaBrowser = 'Firefox' - message.uaOS = 'http://example.com' + const message = getLocationMessage(defaultLocation); + message.uaBrowser = 'Firefox'; + message.uaOS = 'http://example.com'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(! includes(emailConfig.html, 'http://example.com')) - assert.ok(! includes(emailConfig.text, 'http://example.com')) - }) - return mailer[type](message) - }) + assert.ok(! includes(emailConfig.html, 'http://example.com')); + assert.ok(! includes(emailConfig.text, 'http://example.com')); + }); + return mailer[type](message); + }); it(`drops dodgy-looking uaOSVersion property for ${type}`, () => { - const message = getLocationMessage(defaultLocation) - message.uaBrowser = 'Firefox' - message.uaOS = 'Android' - message.uaOSVersion = 'dodgy-looking' + const message = getLocationMessage(defaultLocation); + message.uaBrowser = 'Firefox'; + message.uaOS = 'Android'; + message.uaOSVersion = 'dodgy-looking'; mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(! includes(emailConfig.html, 'dodgy-looking')) - assert.ok(! includes(emailConfig.text, 'dodgy-looking')) - }) - return mailer[type](message) - }) + assert.ok(! includes(emailConfig.html, 'dodgy-looking')); + assert.ok(! includes(emailConfig.text, 'dodgy-looking')); + }); + return mailer[type](message); + }); } if (type === 'verifyEmail') { @@ -721,81 +721,81 @@ describe( 'passes the OAuth relier name to the template', () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(oauthClientInfo.fetch.callCount, 1) - assert.equal(oauthClientInfo.fetch.args[0][0], 'foo') - assert.ok(includes(emailConfig.html, 'biz baz relier name')) - assert.ok(includes(emailConfig.text, 'biz baz relier name')) - }) - message.service = 'foo' - return mailer[type](message) + assert.equal(oauthClientInfo.fetch.callCount, 1); + assert.equal(oauthClientInfo.fetch.args[0][0], 'foo'); + assert.ok(includes(emailConfig.html, 'biz baz relier name')); + assert.ok(includes(emailConfig.text, 'biz baz relier name')); + }); + message.service = 'foo'; + return mailer[type](message); } - ) + ); it( 'works without a service', () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.isFalse(oauthClientInfo.fetch.called) - assert.ok(! includes(emailConfig.html, 'and continue to')) - assert.ok(! includes(emailConfig.text, 'and continue to')) - }) + assert.isFalse(oauthClientInfo.fetch.called); + assert.ok(! includes(emailConfig.html, 'and continue to')); + assert.ok(! includes(emailConfig.text, 'and continue to')); + }); delete message.service; - return mailer[type](message) + return mailer[type](message); } - ) + ); } else if (type === 'verifyLoginEmail') { it( 'test verify token email', function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - var verifyLoginUrl = config.get('smtp').verifyLoginUrl - assert.equal(emailConfig.subject, 'Confirm new sign-in to Firefox') - assert.ok(emailConfig.html.indexOf(verifyLoginUrl) > 0) - assert.ok(emailConfig.text.indexOf(verifyLoginUrl) > 0) - }) - return mailer[type](message) + var verifyLoginUrl = config.get('smtp').verifyLoginUrl; + assert.equal(emailConfig.subject, 'Confirm new sign-in to Firefox'); + assert.ok(emailConfig.html.indexOf(verifyLoginUrl) > 0); + assert.ok(emailConfig.text.indexOf(verifyLoginUrl) > 0); + }); + return mailer[type](message); } - ) + ); } else if (type === 'newDeviceLoginEmail') { it( 'test new device login email', function () { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(emailConfig.subject, 'New sign-in to Firefox') - }) - return mailer[type](message) + assert.equal(emailConfig.subject, 'New sign-in to Firefox'); + }); + return mailer[type](message); } - ) + ); } else if (type === 'postVerifyEmail') { it( 'test utm params for ' + type, function () { - var syncLink = mailer._generateUTMLink(config.get('smtp').syncUrl, {}, type, 'connect-device') - var androidLink = mailer._generateUTMLink(config.get('smtp').androidUrl, {}, type, 'connect-android') - var iosLink = mailer._generateUTMLink(config.get('smtp').iosUrl, {}, type, 'connect-ios') + var syncLink = mailer._generateUTMLink(config.get('smtp').syncUrl, {}, type, 'connect-device'); + var androidLink = mailer._generateUTMLink(config.get('smtp').androidUrl, {}, type, 'connect-android'); + var iosLink = mailer._generateUTMLink(config.get('smtp').iosUrl, {}, type, 'connect-ios'); mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.ok(includes(emailConfig.html, syncLink)) - assert.ok(includes(emailConfig.html, androidLink)) - assert.ok(includes(emailConfig.html, iosLink)) - assert.ok(includes(emailConfig.html, 'utm_source=email')) - }) - return mailer[type](message) + assert.ok(includes(emailConfig.html, syncLink)); + assert.ok(includes(emailConfig.html, androidLink)); + assert.ok(includes(emailConfig.html, iosLink)); + assert.ok(includes(emailConfig.html, 'utm_source=email')); + }); + return mailer[type](message); } - ) + ); } else if (type === 'verifyPrimaryEmail') { it('test verify token email', () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - const verifyPrimaryEmailUrl = config.get('smtp').verifyPrimaryEmailUrl - assert.ok(emailConfig.html.indexOf(verifyPrimaryEmailUrl) > 0) - assert.ok(emailConfig.text.indexOf(verifyPrimaryEmailUrl) > 0) - assert.ok(! includes(emailConfig.html, 'utm_source=email')) - assert.ok(! includes(emailConfig.text, 'utm_source=email')) - }) - return mailer[type](message) - }) + const verifyPrimaryEmailUrl = config.get('smtp').verifyPrimaryEmailUrl; + assert.ok(emailConfig.html.indexOf(verifyPrimaryEmailUrl) > 0); + assert.ok(emailConfig.text.indexOf(verifyPrimaryEmailUrl) > 0); + assert.ok(! includes(emailConfig.html, 'utm_source=email')); + assert.ok(! includes(emailConfig.text, 'utm_source=email')); + }); + return mailer[type](message); + }); } } - ) + ); it( 'test user-agent info rendering', @@ -805,72 +805,72 @@ describe( uaBrowserVersion: '32', uaOS: 'Windows', uaOSVersion: '8.1' - }), 'Firefox on Windows 8.1') + }), 'Firefox on Windows 8.1'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: 'Chrome', uaBrowserVersion: undefined, uaOS: 'Windows', uaOSVersion: '10', - }), 'Chrome on Windows 10') + }), 'Chrome on Windows 10'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: undefined, uaBrowserVersion: '12', uaOS: 'Windows', uaOSVersion: '10' - }), 'Windows 10') + }), 'Windows 10'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: 'MSIE', uaBrowserVersion: '6', uaOS: 'Linux', uaOSVersion: '9' - }), 'MSIE on Linux 9') + }), 'MSIE on Linux 9'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: 'MSIE', uaBrowserVersion: undefined, uaOS: 'Linux', uaOSVersion: undefined - }), 'MSIE on Linux') + }), 'MSIE on Linux'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: 'MSIE', uaBrowserVersion: '8', uaOS: undefined, uaOSVersion: '4' - }), 'MSIE') + }), 'MSIE'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: 'MSIE', uaBrowserVersion: undefined, uaOS: undefined, uaOSVersion: undefined - }), 'MSIE') + }), 'MSIE'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: undefined, uaBrowserVersion: undefined, uaOS: 'Windows', uaOSVersion: undefined - }), 'Windows') + }), 'Windows'); assert.equal(mailer._formatUserAgentInfo({ uaBrowser: undefined, uaBrowserVersion: undefined, uaOS: undefined, uaOSVersion: undefined - }), '') + }), ''); } - ) + ); describe('mock sendMail method:', () => { beforeEach(() => { sinon.stub(mailer.mailer, 'sendMail').callsFake(function (config, cb) { - cb(null, { resp: 'ok' }) - }) - }) + cb(null, { resp: 'ok' }); + }); + }); it('resolves sendMail status', () => { const message = { @@ -878,13 +878,13 @@ describe( subject: 'subject', template: 'verifyLoginEmail', uid: 'foo' - } + }; return mailer.send(message) .then(status => { - assert.deepEqual(status, [ { resp: 'ok' } ]) - }) - }) + assert.deepEqual(status, [ { resp: 'ok' } ]); + }); + }); it('logs emailEvent on send', () => { const message = { @@ -893,29 +893,29 @@ describe( subject: 'subject', template: 'verifyLoginEmail', uid: 'foo' - } + }; return mailer.send(message) .then(() => { - assert.equal(mockLog.info.callCount, 3) - const emailEventLog = mockLog.info.getCalls()[2] - assert.equal(emailEventLog.args[0], 'emailEvent') - assert.equal(emailEventLog.args[1].domain, 'other') - assert.equal(emailEventLog.args[1].flow_id, 'wibble') - assert.equal(emailEventLog.args[1].template, 'verifyLoginEmail') - assert.equal(emailEventLog.args[1].type, 'sent') - assert.equal(emailEventLog.args[1].locale, 'en') - const mailerSend1 = mockLog.info.getCalls()[1] - assert.equal(mailerSend1.args[0], 'mailer.send.1') - assert.equal(mailerSend1.args[1].to, message.email) - }) - }) - }) + assert.equal(mockLog.info.callCount, 3); + const emailEventLog = mockLog.info.getCalls()[2]; + assert.equal(emailEventLog.args[0], 'emailEvent'); + assert.equal(emailEventLog.args[1].domain, 'other'); + assert.equal(emailEventLog.args[1].flow_id, 'wibble'); + assert.equal(emailEventLog.args[1].template, 'verifyLoginEmail'); + assert.equal(emailEventLog.args[1].type, 'sent'); + assert.equal(emailEventLog.args[1].locale, 'en'); + const mailerSend1 = mockLog.info.getCalls()[1]; + assert.equal(mailerSend1.args[0], 'mailer.send.1'); + assert.equal(mailerSend1.args[1].to, message.email); + }); + }); + }); describe('mock failing sendMail method:', () => { beforeEach(() => { - sinon.stub(mailer.mailer, 'sendMail').callsFake((config, cb) => cb(new Error('Fail'))) - }) + sinon.stub(mailer.mailer, 'sendMail').callsFake((config, cb) => cb(new Error('Fail'))); + }); it('rejects sendMail status', () => { const message = { @@ -923,19 +923,19 @@ describe( subject: 'subject', template: 'verifyLoginEmail', uid: 'foo' - } + }; return mailer.send(message) .then(assert.notOk, err => { - assert.equal(err.message, 'Fail') - }) - }) - }) + assert.equal(err.message, 'Fail'); + }); + }); + }); describe('delete template versions', () => { beforeEach(() => { - Object.keys(TEMPLATE_VERSIONS).forEach(key => TEMPLATE_VERSIONS[key] = undefined) - }) + Object.keys(TEMPLATE_VERSIONS).forEach(key => TEMPLATE_VERSIONS[key] = undefined); + }); messageTypes.forEach(type => { const message = { @@ -949,16 +949,16 @@ describe( type: 'secondary', flowId: 'flowId', flowBeginTime: Date.now() - } + }; it(`uses default template version for ${type}`, () => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(emailConfig.headers['X-Template-Version'], 1, 'template version defaults to 1') - }) - return mailer[type](message) - }) - }) - }) + assert.equal(emailConfig.headers['X-Template-Version'], 1, 'template version defaults to 1'); + }); + return mailer[type](message); + }); + }); + }); describe( 'sends request to the right mailer', @@ -968,17 +968,17 @@ describe( mailer.mailer, 'sendMail').callsFake( function (config, cb) { - cb(null, { resp: 'whatevs' }) + cb(null, { resp: 'whatevs' }); } - ) + ); sinon.stub( mailer.emailService, 'sendMail').callsFake( function (config, cb) { - cb(null, { resp: 'whatevs' }) + cb(null, { resp: 'whatevs' }); } - ) - }) + ); + }); it( 'sends request to fxa-email-service when the email pattern is right', @@ -988,40 +988,40 @@ describe( subject: 'subject', template: 'verifyLoginEmail', uid: 'foo' - } - mailer.sesConfigurationSet = 'wibble' + }; + mailer.sesConfigurationSet = 'wibble'; return mailer.send(message) .then( response => { - assert(mailer.emailService.sendMail.calledOnce) - assert(! mailer.mailer.sendMail.called) + assert(mailer.emailService.sendMail.calledOnce); + assert(! mailer.mailer.sendMail.called); - const args = mailer.emailService.sendMail.args[0] + const args = mailer.emailService.sendMail.args[0]; - assert.equal(args.length, 2) - assert.equal(args[0].to, 'emailservice.foo@restmail.net') - assert.equal(args[0].subject, 'subject') - assert.equal(args[0].provider, 'ses') + assert.equal(args.length, 2); + assert.equal(args[0].to, 'emailservice.foo@restmail.net'); + assert.equal(args[0].subject, 'subject'); + assert.equal(args[0].provider, 'ses'); - const headers = args[0].headers + const headers = args[0].headers; - assert.equal(headers['X-Template-Name'], 'verifyLoginEmail') - assert.equal(headers['X-Email-Service'], 'fxa-email-service') - assert.equal(headers['X-Email-Sender'], 'ses') - assert.equal(headers['X-Uid'], 'foo') + assert.equal(headers['X-Template-Name'], 'verifyLoginEmail'); + assert.equal(headers['X-Email-Service'], 'fxa-email-service'); + assert.equal(headers['X-Email-Sender'], 'ses'); + assert.equal(headers['X-Uid'], 'foo'); - const expectedSesMessageTags = sesMessageTagsHeaderValue(message.template, 'fxa-email-service') - assert.equal(headers['X-SES-MESSAGE-TAGS'], expectedSesMessageTags) - assert.equal(headers['X-SES-CONFIGURATION-SET'], 'wibble') + const expectedSesMessageTags = sesMessageTagsHeaderValue(message.template, 'fxa-email-service'); + assert.equal(headers['X-SES-MESSAGE-TAGS'], expectedSesMessageTags); + assert.equal(headers['X-SES-CONFIGURATION-SET'], 'wibble'); - assert.equal(typeof args[1], 'function') + assert.equal(typeof args[1], 'function'); - assert.equal(redis.get.callCount, 1) + assert.equal(redis.get.callCount, 1); } - ) + ); } - ) + ); it( 'doesn\'t send request to fxa-email-service when the email pattern is not right', @@ -1031,34 +1031,34 @@ describe( subject: 'subject', template: 'verifyLoginEmail', uid: 'foo' - } + }; return mailer.send(message) .then( response => { - assert(! mailer.emailService.sendMail.called) - assert( mailer.mailer.sendMail.calledOnce) - const args = mailer.mailer.sendMail.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'foo@restmail.net') - assert.equal(args[0].subject, 'subject') - assert.equal(args[0].headers['X-Template-Name'], 'verifyLoginEmail') - assert.equal(args[0].headers['X-Uid'], 'foo') - assert.equal(args[0].provider, undefined) - assert.equal(typeof mailer.mailer.sendMail.args[0][1], 'function') + assert(! mailer.emailService.sendMail.called); + assert( mailer.mailer.sendMail.calledOnce); + const args = mailer.mailer.sendMail.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'foo@restmail.net'); + assert.equal(args[0].subject, 'subject'); + assert.equal(args[0].headers['X-Template-Name'], 'verifyLoginEmail'); + assert.equal(args[0].headers['X-Uid'], 'foo'); + assert.equal(args[0].provider, undefined); + assert.equal(typeof mailer.mailer.sendMail.args[0][1], 'function'); - assert.equal(redis.get.callCount, 1) + assert.equal(redis.get.callCount, 1); } - ) + ); } - ) + ); it('sends request to fxa-email-service when selectEmailServices tells it to', () => { const message = { email: 'foo@example.com', subject: 'subject', template: 'verifyLoginEmail' - } + }; mailer.selectEmailServices = sinon.spy(() => P.resolve([ { emailAddresses: [ message.email ], @@ -1066,29 +1066,29 @@ describe( emailSender: 'sendgrid', mailer: mailer.emailService } - ])) + ])); return mailer.send(message) .then(() => { - assert.equal(mailer.selectEmailServices.callCount, 1) + assert.equal(mailer.selectEmailServices.callCount, 1); - let args = mailer.selectEmailServices.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], message) + let args = mailer.selectEmailServices.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], message); - assert.equal(mailer.emailService.sendMail.callCount, 1) - assert.equal(mailer.mailer.sendMail.callCount, 0) + assert.equal(mailer.emailService.sendMail.callCount, 1); + assert.equal(mailer.mailer.sendMail.callCount, 0); - args = mailer.emailService.sendMail.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'foo@example.com') - assert.equal(args[0].provider, 'sendgrid') + args = mailer.emailService.sendMail.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'foo@example.com'); + assert.equal(args[0].provider, 'sendgrid'); - const headers = args[0].headers - assert.equal(headers['X-Email-Service'], 'fxa-email-service') - assert.equal(headers['X-Email-Sender'], 'sendgrid') - }) - }) + const headers = args[0].headers; + assert.equal(headers['X-Email-Service'], 'fxa-email-service'); + assert.equal(headers['X-Email-Sender'], 'sendgrid'); + }); + }); it('correctly handles multiple email addresses from selectEmailServices', () => { const message = { @@ -1096,7 +1096,7 @@ describe( ccEmails: [ 'bar@example.com', 'baz@example.com' ], subject: 'subject', template: 'verifyLoginEmail' - } + }; mailer.selectEmailServices = sinon.spy(() => P.resolve([ { emailAddresses: [ message.email, ...message.ccEmails ], @@ -1104,24 +1104,24 @@ describe( emailSender: 'ses', mailer: mailer.mailer } - ])) + ])); return mailer.send(message) .then(() => { - assert.equal(mailer.selectEmailServices.callCount, 1) - assert.equal(mailer.mailer.sendMail.callCount, 1) - assert.equal(mailer.emailService.sendMail.callCount, 0) + assert.equal(mailer.selectEmailServices.callCount, 1); + assert.equal(mailer.mailer.sendMail.callCount, 1); + assert.equal(mailer.emailService.sendMail.callCount, 0); - const args = mailer.mailer.sendMail.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'foo@example.com') - assert.deepEqual(args[0].cc, [ 'bar@example.com', 'baz@example.com' ]) + const args = mailer.mailer.sendMail.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'foo@example.com'); + assert.deepEqual(args[0].cc, [ 'bar@example.com', 'baz@example.com' ]); - const headers = args[0].headers - assert.equal(headers['X-Email-Service'], 'fxa-auth-server') - assert.equal(headers['X-Email-Sender'], 'ses') - }) - }) + const headers = args[0].headers; + assert.equal(headers['X-Email-Service'], 'fxa-auth-server'); + assert.equal(headers['X-Email-Sender'], 'ses'); + }); + }); it('correctly handles multiple services from selectEmailServices', () => { const message = { @@ -1129,7 +1129,7 @@ describe( ccEmails: [ 'bar@example.com', 'baz@example.com' ], subject: 'subject', template: 'verifyLoginEmail' - } + }; mailer.selectEmailServices = sinon.spy(() => P.resolve([ { emailAddresses: [ message.email ], @@ -1149,58 +1149,58 @@ describe( emailSender: 'ses', mailer: mailer.mailer } - ])) + ])); return mailer.send(message) .then(() => { - assert.equal(mailer.selectEmailServices.callCount, 1) - assert.equal(mailer.emailService.sendMail.callCount, 2) - assert.equal(mailer.mailer.sendMail.callCount, 1) + assert.equal(mailer.selectEmailServices.callCount, 1); + assert.equal(mailer.emailService.sendMail.callCount, 2); + assert.equal(mailer.mailer.sendMail.callCount, 1); - let args = mailer.emailService.sendMail.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'foo@example.com') - assert.equal(args[0].cc, undefined) - assert.equal(args[0].provider, 'sendgrid') + let args = mailer.emailService.sendMail.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'foo@example.com'); + assert.equal(args[0].cc, undefined); + assert.equal(args[0].provider, 'sendgrid'); - let headers = args[0].headers - assert.equal(headers['X-Email-Service'], 'fxa-email-service') - assert.equal(headers['X-Email-Sender'], 'sendgrid') + let headers = args[0].headers; + assert.equal(headers['X-Email-Service'], 'fxa-email-service'); + assert.equal(headers['X-Email-Sender'], 'sendgrid'); - args = mailer.emailService.sendMail.args[1] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'bar@example.com') - assert.equal(args[0].cc, undefined) - assert.equal(args[0].provider, 'ses') + args = mailer.emailService.sendMail.args[1]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'bar@example.com'); + assert.equal(args[0].cc, undefined); + assert.equal(args[0].provider, 'ses'); - headers = args[0].headers - assert.equal(headers['X-Email-Service'], 'fxa-email-service') - assert.equal(headers['X-Email-Sender'], 'ses') + headers = args[0].headers; + assert.equal(headers['X-Email-Service'], 'fxa-email-service'); + assert.equal(headers['X-Email-Sender'], 'ses'); - args = mailer.mailer.sendMail.args[0] - assert.equal(args.length, 2) - assert.equal(args[0].to, 'baz@example.com') - assert.equal(args[0].cc, undefined) - assert.equal(args[0].provider, undefined) + args = mailer.mailer.sendMail.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0].to, 'baz@example.com'); + assert.equal(args[0].cc, undefined); + assert.equal(args[0].provider, undefined); - headers = args[0].headers - assert.equal(headers['X-Email-Service'], 'fxa-auth-server') - assert.equal(headers['X-Email-Sender'], 'ses') - }) - }) + headers = args[0].headers; + assert.equal(headers['X-Email-Service'], 'fxa-auth-server'); + assert.equal(headers['X-Email-Sender'], 'ses'); + }); + }); } - ) + ); describe('single email address:', () => { - const emailAddress = 'foo@example.com' + const emailAddress = 'foo@example.com'; describe('redis.get returns sendgrid percentage-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 11 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.109) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 11 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.109); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1211,17 +1211,17 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid percentage-only mismatch:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 11 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.11) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 11 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.11); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1232,14 +1232,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid regex-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^foo@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^foo@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1250,14 +1250,14 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid regex-only mismatch:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^fo@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^fo@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1268,9 +1268,9 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid combined match:', () => { beforeEach(() => { @@ -1279,11 +1279,11 @@ describe( percentage: 1, regex: '^foo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.009) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.009); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1294,9 +1294,9 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid combined mismatch (percentage):', () => { beforeEach(() => { @@ -1305,11 +1305,11 @@ describe( percentage: 1, regex: '^foo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.01) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.01); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1320,9 +1320,9 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid combined mismatch (regex):', () => { beforeEach(() => { @@ -1331,11 +1331,11 @@ describe( percentage: 1, regex: '^ffoo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1346,17 +1346,17 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns socketlabs percentage-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { percentage: 42 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.419) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { percentage: 42 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.419); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1367,17 +1367,17 @@ describe( emailService: 'fxa-email-service', emailSender: 'socketlabs' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns socketlabs percentage-only mismatch:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { percentage: 42 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.42) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { percentage: 42 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.42); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1388,14 +1388,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns socketlabs regex-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { regex: '^foo@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ socketlabs: { regex: '^foo@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1406,17 +1406,17 @@ describe( emailService: 'fxa-email-service', emailSender: 'socketlabs' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns ses percentage-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { percentage: 100 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.999) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { percentage: 100 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.999); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1427,17 +1427,17 @@ describe( emailService: 'fxa-email-service', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns ses percentage-only mismatch:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { percentage: 99 } }))) - sinon.stub(Math, 'random').callsFake(() => 0.999) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { percentage: 99 } }))); + sinon.stub(Math, 'random').callsFake(() => 0.999); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1448,14 +1448,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns ses regex-only match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { regex: '^foo@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ ses: { regex: '^foo@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1466,20 +1466,20 @@ describe( emailService: 'fxa-email-service', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid and ses matches:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 10 }, ses: { regex: '^foo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.09) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.09); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1490,20 +1490,20 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid match and ses mismatch:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 10 }, ses: { regex: '^ffoo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.09) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.09); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1514,20 +1514,20 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid mismatch and ses match:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 10 }, ses: { regex: '^foo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.1) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.1); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1538,20 +1538,20 @@ describe( emailService: 'fxa-email-service', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid and ses mismatches:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { percentage: 10 }, ses: { regex: '^ffoo@example\.com$' } - }))) - sinon.stub(Math, 'random').callsFake(() => 0.1) - }) + }))); + sinon.stub(Math, 'random').callsFake(() => 0.1); + }); - afterEach(() => Math.random.restore()) + afterEach(() => Math.random.restore()); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1562,14 +1562,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns undefined:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve()) - }) + redis.get = sinon.spy(() => P.resolve()); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1580,14 +1580,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns unsafe regex:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^(.+)+@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^(.+)+@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1598,14 +1598,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns quote-terminating regex:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '"@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '"@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1616,14 +1616,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('email address contains quote-terminator:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '@example\.com$' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '@example\.com$' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: '"@example.com' }) @@ -1634,14 +1634,14 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get fails:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.reject({ message: 'wibble' })) - }) + redis.get = sinon.spy(() => P.reject({ message: 'wibble' })); + }); it('selectEmailServices returns fallback data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1651,22 +1651,22 @@ describe( emailAddresses: [ emailAddress ], emailService: 'fxa-auth-server', emailSender: 'ses' - }]) - assert.equal(mockLog.error.callCount, 1) - const args = mockLog.error.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'emailConfig.read.error') + }]); + assert.equal(mockLog.error.callCount, 1); + const args = mockLog.error.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'emailConfig.read.error'); assert.deepEqual(args[1], { err: 'wibble' - }) - }) - }) - }) + }); + }); + }); + }); describe('redis.get returns invalid JSON:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve('wibble')) - }) + redis.get = sinon.spy(() => P.resolve('wibble')); + }); it('selectEmailServices returns fallback data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1676,21 +1676,21 @@ describe( emailAddresses: [ emailAddress ], emailService: 'fxa-auth-server', emailSender: 'ses' - }]) - assert.equal(mockLog.error.callCount, 1) - assert.equal(mockLog.error.args[0][0], 'emailConfig.parse.error') - }) - }) - }) - }) + }]); + assert.equal(mockLog.error.callCount, 1); + assert.equal(mockLog.error.args[0][0], 'emailConfig.parse.error'); + }); + }); + }); + }); describe('single email address matching local static email service config:', () => { - const emailAddress = 'emailservice.1@restmail.net' + const emailAddress = 'emailservice.1@restmail.net'; describe('redis.get returns sendgrid match:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: 'restmail' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: 'restmail' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1701,14 +1701,14 @@ describe( emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns sendgrid mismatch:', () => { beforeEach(() => { - redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: 'rustmail' } }))) - }) + redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: 'rustmail' } }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -1719,21 +1719,21 @@ describe( emailService: 'fxa-email-service', emailSender: 'ses' } - ])) - }) - }) - }) + ])); + }); + }); + }); describe('multiple email addresses:', () => { - const emailAddresses = [ 'a@example.com', 'b@example.com', 'c@example.com' ] + const emailAddresses = [ 'a@example.com', 'b@example.com', 'c@example.com' ]; describe('redis.get returns sendgrid and ses matches and a mismatch:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^a' }, ses: { regex: '^b' } - }))) - }) + }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ @@ -1759,17 +1759,17 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns a sendgrid match and two ses matches:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^a' }, ses: { regex: '^b|c' } - }))) - }) + }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ @@ -1789,17 +1789,17 @@ describe( emailService: 'fxa-email-service', emailSender: 'ses' } - ])) - }) - }) + ])); + }); + }); describe('redis.get returns three mismatches:', () => { beforeEach(() => { redis.get = sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: 'wibble' }, ses: { regex: 'blee' } - }))) - }) + }))); + }); it('selectEmailServices returns the correct data', () => { return mailer.selectEmailServices({ @@ -1813,15 +1813,15 @@ describe( emailService: 'fxa-auth-server', emailSender: 'ses' } - ])) - }) - }) - }) + ])); + }); + }); + }); } -) +); describe('mailer constructor:', () => { - let mailerConfig, mockLog, mailer + let mailerConfig, mockLog, mailer; beforeEach(() => { mailerConfig = [ @@ -1845,87 +1845,87 @@ describe('mailer constructor:', () => { 'verifySecondaryEmailUrl', 'verifyPrimaryEmailUrl' ].reduce((target, key) => { - target[key] = `mock ${key}` - return target - }, {}) - mockLog = mocks.mockLog() + target[key] = `mock ${key}`; + return target; + }, {}); + mockLog = mocks.mockLog(); return P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - const Mailer = require(`${ROOT_DIR}/lib/senders/email`)(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, mailerConfig, 'wibble') - }) - }) + const Mailer = require(`${ROOT_DIR}/lib/senders/email`)(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, mailerConfig, 'wibble'); + }); + }); it('mailer and emailService are both mocked', () => { - assert.equal(mailer.mailer, 'wibble') - assert.equal(mailer.emailService, 'wibble') - }) + assert.equal(mailer.mailer, 'wibble'); + assert.equal(mailer.emailService, 'wibble'); + }); it('set properties on self from config correctly', () => { Object.entries(mailerConfig).forEach(([key, expected]) => { - assert.equal(mailer[key], expected, `${key} was correct`) - }) - }) -}) + assert.equal(mailer[key], expected, `${key} was correct`); + }); + }); +}); describe('call selectEmailServices with mocked sandbox:', () => { - const emailAddress = 'foo@example.com' - let mockLog, redis, Sandbox, sandbox, mailer, promise, result, failed + const emailAddress = 'foo@example.com'; + let mockLog, redis, Sandbox, sandbox, mailer, promise, result, failed; beforeEach(done => { P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - mockLog = mocks.mockLog() + mockLog = mocks.mockLog(); redis = { get: sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^foo@example\.com$' } }))) - } - Sandbox = sinon.spy(function () { return sandbox }) + }; + Sandbox = sinon.spy(function () { return sandbox; }); sandbox = { run: sinon.spy() - } + }; const Mailer = proxyquire(`${ROOT_DIR}/lib/senders/email`, { '../redis': () => redis, 'sandbox': Sandbox - })(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, config.get('smtp')) + })(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, config.get('smtp')); promise = mailer.selectEmailServices({ email: emailAddress }) .then(r => result = r) - .catch(() => failed = true) - setImmediate(done) - }) - }) + .catch(() => failed = true); + setImmediate(done); + }); + }); - afterEach(() => mailer.stop()) + afterEach(() => mailer.stop()); it('called the sandbox correctly', () => { - assert.equal(Sandbox.callCount, 1) + assert.equal(Sandbox.callCount, 1); - let args = Sandbox.args[0] - assert.equal(args.length, 1) + let args = Sandbox.args[0]; + assert.equal(args.length, 1); assert.deepEqual(args[0], { timeout: 100 - }) + }); - assert.equal(sandbox.run.callCount, 1) + assert.equal(sandbox.run.callCount, 1); - args = sandbox.run.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'new RegExp("^foo@example\.com$").test("foo@example.com")') - assert.equal(typeof args[1], 'function') - }) + args = sandbox.run.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'new RegExp("^foo@example\.com$").test("foo@example.com")'); + assert.equal(typeof args[1], 'function'); + }); describe('call sandbox result handler with match:', () => { beforeEach(() => { - sandbox.run.args[0][1]({ result: 'true' }) - return promise - }) + sandbox.run.args[0][1]({ result: 'true' }); + return promise; + }); it('resolved', () => { assert.deepEqual(result, [ @@ -1935,19 +1935,19 @@ describe('call selectEmailServices with mocked sandbox:', () => { emailService: 'fxa-email-service', emailSender: 'sendgrid' } - ]) - }) + ]); + }); it('did not fail', () => { - assert.equal(failed, undefined) - }) - }) + assert.equal(failed, undefined); + }); + }); describe('call sandbox result handler with timeout:', () => { beforeEach(() => { - sandbox.run.args[0][1]({ result: 'TimeoutError' }) - return promise - }) + sandbox.run.args[0][1]({ result: 'TimeoutError' }); + return promise; + }); it('resolved', () => { assert.deepEqual(result, [ @@ -1957,38 +1957,38 @@ describe('call selectEmailServices with mocked sandbox:', () => { emailService: 'fxa-auth-server', emailSender: 'ses' } - ]) - }) + ]); + }); it('did not fail', () => { - assert.equal(failed, undefined) - }) - }) -}) + assert.equal(failed, undefined); + }); + }); +}); describe('call selectEmailServices with mocked safe-regex, regex-only match and redos regex:', () => { - const emailAddress = 'foo@example.com' - let mockLog, redis, safeRegex, mailer + const emailAddress = 'foo@example.com'; + let mockLog, redis, safeRegex, mailer; beforeEach(() => { return P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - mockLog = mocks.mockLog() + mockLog = mocks.mockLog(); redis = { get: sinon.spy(() => P.resolve(JSON.stringify({ sendgrid: { regex: '^((((.*)*)*)*)*@example\.com$' } }))) - } - safeRegex = sinon.spy(function () { return true }) + }; + safeRegex = sinon.spy(function () { return true; }); const Mailer = proxyquire(`${ROOT_DIR}/lib/senders/email`, { '../redis': () => redis, 'safe-regex': safeRegex - })(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, config.get('smtp')) - }) - }) + })(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, config.get('smtp')); + }); + }); - afterEach(() => mailer.stop()) + afterEach(() => mailer.stop()); it('email address was treated as mismatch', () => { return mailer.selectEmailServices({ email: emailAddress }) @@ -2000,19 +2000,19 @@ describe('call selectEmailServices with mocked safe-regex, regex-only match and emailService: 'fxa-auth-server', emailSender: 'ses' } - ]) + ]); - assert.equal(safeRegex.callCount, 1) - const args = safeRegex.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], '^((((.*)*)*)*)*@example\.com$') - }) - }) -}) + assert.equal(safeRegex.callCount, 1); + const args = safeRegex.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], '^((((.*)*)*)*)*@example\.com$'); + }); + }); +}); describe('email translations', () => { - let mockLog, redis, mailer + let mockLog, redis, mailer; const message = { email: 'a@b.com' }; @@ -2022,43 +2022,43 @@ describe('email translations', () => { require(`${ROOT_DIR}/lib/senders/translator`)([locale], locale), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - mockLog = mocks.mockLog() + mockLog = mocks.mockLog(); redis = { get: sinon.spy(() => P.resolve()) - } + }; const Mailer = proxyquire(`${ROOT_DIR}/lib/senders/email`, { '../redis': () => redis - })(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, config.get('smtp')) - }) + })(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, config.get('smtp')); + }); } - afterEach(() => mailer.stop()) + afterEach(() => mailer.stop()); it('arabic emails are translated', () => { return setupMailerWithTranslations('ar').then(() => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(emailConfig.headers['Content-Language'], 'ar', 'language header is correct') + assert.equal(emailConfig.headers['Content-Language'], 'ar', 'language header is correct'); // NOTE: translation might change, but we use the subject, we don't change that often. - assert.equal(emailConfig.subject, 'أكّد حساب فَيَرفُكس الخاص بك', 'translation is correct') - }) + assert.equal(emailConfig.subject, 'أكّد حساب فَيَرفُكس الخاص بك', 'translation is correct'); + }); - return mailer['verifyEmail'](message) - }) - }) + return mailer['verifyEmail'](message); + }); + }); it('russian emails are translated', () => { return setupMailerWithTranslations('ru').then(() => { mailer.mailer.sendMail = stubSendMail(emailConfig => { - assert.equal(emailConfig.headers['Content-Language'], 'ru', 'language header is correct') + assert.equal(emailConfig.headers['Content-Language'], 'ru', 'language header is correct'); // NOTE: translation might change, but we use the subject, we don't change that often. - assert.equal(emailConfig.subject, 'Подтвердите ваш Аккаунт Firefox', 'translation is correct') - }) + assert.equal(emailConfig.subject, 'Подтвердите ваш Аккаунт Firefox', 'translation is correct'); + }); - return mailer['verifyEmail'](message) - }) - }) -}) + return mailer['verifyEmail'](message); + }); + }); +}); if (config.get('redis.email.enabled')) { const emailAddress = 'foo@example.com'; @@ -2067,34 +2067,34 @@ if (config.get('redis.email.enabled')) { return promise.then(() => { return new P((resolve, reject) => { describe(`call selectEmailServices with real redis containing ${service} config:`, function () { - this.timeout(10000) - let mailer, result + this.timeout(10000); + let mailer, result; before(() => { return P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).spread((translator, templates) => { - const mockLog = mocks.mockLog() - const Mailer = require(`${ROOT_DIR}/lib/senders/email`)(mockLog, config.getProperties()) - mailer = new Mailer(translator, templates, config.get('smtp')) + const mockLog = mocks.mockLog(); + const Mailer = require(`${ROOT_DIR}/lib/senders/email`)(mockLog, config.getProperties()); + mailer = new Mailer(translator, templates, config.get('smtp')); return redisWrite({ [service]: { regex: '^foo@example\.com$', percentage: 100 } - }) + }); }) .then(() => mailer.selectEmailServices({ email: emailAddress })) - .then(r => result = r) - }) + .then(r => result = r); + }); after(() => { return redisRevert() .then(() => mailer.stop()) .then(resolve) - .catch(reject) - }) + .catch(reject); + }); it('returned the correct result', () => { assert.deepEqual(result, [ @@ -2104,22 +2104,22 @@ if (config.get('redis.email.enabled')) { emailSender: service, mailer: mailer.emailService } - ]) - }) - }) - }) - }) - }, P.resolve()) + ]); + }); + }); + }); + }); + }, P.resolve()); } function redisWrite (config) { return cp.execAsync(`echo '${JSON.stringify(config)}' | node scripts/email-config write`, { cwd: path.resolve(__dirname, '../../..') - }) + }); } function redisRevert () { return cp.execAsync('node scripts/email-config revert', { cwd: path.resolve(__dirname, '../../..') - }) + }); } diff --git a/test/local/senders/email_service.js b/test/local/senders/email_service.js index af07f472..19f6aed7 100644 --- a/test/local/senders/email_service.js +++ b/test/local/senders/email_service.js @@ -2,14 +2,14 @@ * 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/. */ - 'use strict' + 'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const proxyquire = require('proxyquire') +const { assert } = require('chai'); +const proxyquire = require('proxyquire'); -const config = require(`${ROOT_DIR}/config`).getProperties() +const config = require(`${ROOT_DIR}/config`).getProperties(); const emailConfig = { cc: ['bar@test.com', 'baz@test.com'], to: 'foo@test.com', @@ -21,7 +21,7 @@ const emailConfig = { 'x-numbers': 9999, 'x-null-header': null } -} +}; describe( 'lib/senders/email_service', @@ -29,25 +29,25 @@ describe( it('emailService serializes options correctly', (done) => { const mock = { 'request': function (config, cb) { - cb(config) + cb(config); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (serialized) => { - assert.equal(serialized.url, `http://${config.emailService.host}:${config.emailService.port}/send`) - assert.equal(serialized.method, 'POST') - assert.equal(serialized.json, true) - assert.deepEqual(serialized.body.cc, ['bar@test.com', 'baz@test.com']) - assert.equal(serialized.body.to, 'foo@test.com') - assert.equal(serialized.body.subject, 'subject') - assert.equal(serialized.body.body.text, 'text') - assert.equal(serialized.body.body.html, '

html

') - assert.equal(serialized.body.headers['x-header'], 'yeah') - assert.equal(serialized.body.headers['x-numbers'], '9999') - done() - }) - }) + assert.equal(serialized.url, `http://${config.emailService.host}:${config.emailService.port}/send`); + assert.equal(serialized.method, 'POST'); + assert.equal(serialized.json, true); + assert.deepEqual(serialized.body.cc, ['bar@test.com', 'baz@test.com']); + assert.equal(serialized.body.to, 'foo@test.com'); + assert.equal(serialized.body.subject, 'subject'); + assert.equal(serialized.body.body.text, 'text'); + assert.equal(serialized.body.body.html, '

html

'); + assert.equal(serialized.body.headers['x-header'], 'yeah'); + assert.equal(serialized.body.headers['x-numbers'], '9999'); + done(); + }); + }); it('emailService handles successful request and response', (done) => { const mock = { @@ -56,18 +56,18 @@ describe( statusCode: 200 }, { messageId: 'woopwoop' - }) + }); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (err, body) => { - assert.equal(err, null) - assert.equal(body.messageId, 'woopwoop') - assert.equal(body.message, undefined) - done() - }) - }) + assert.equal(err, null); + assert.equal(body.messageId, 'woopwoop'); + assert.equal(body.message, undefined); + done(); + }); + }); it('emailService handles 500 response', (done) => { const mock = { @@ -80,19 +80,19 @@ describe( errno: 104, message: 'FREAKOUT', name: 'SES' - }) + }); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (err, body) => { - assert.equal(err.errno, 999) - assert.equal(err.output.statusCode, 500) - assert.equal(body.messageId, undefined) - assert.equal(body.message, 'FREAKOUT') - done() - }) - }) + assert.equal(err.errno, 999); + assert.equal(err.output.statusCode, 500); + assert.equal(body.messageId, undefined); + assert.equal(body.message, 'FREAKOUT'); + done(); + }); + }); it('emailService handles old 429 response', (done) => { const mock = { @@ -105,21 +105,21 @@ describe( error: 'BounceComplaintError', message: 'FREAKOUT', bouncedAt: 1533641031755 - }) + }); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (err, body) => { - assert.equal(err.errno, 133) - assert.equal(err.output.statusCode, 400) - assert.equal(err.output.payload.bouncedAt, 1533641031755) - assert.equal(err.message, 'Email account sent complaint') - assert.equal(body.messageId, undefined) - assert.equal(body.message, 'FREAKOUT') - done() - }) - }) + assert.equal(err.errno, 133); + assert.equal(err.output.statusCode, 400); + assert.equal(err.output.payload.bouncedAt, 1533641031755); + assert.equal(err.message, 'Email account sent complaint'); + assert.equal(body.messageId, undefined); + assert.equal(body.message, 'FREAKOUT'); + done(); + }); + }); it('emailService handles new 429 response', (done) => { const mock = { @@ -132,37 +132,37 @@ describe( error: 'BounceComplaintError', message: 'FREAKOUT', time: 1533641031755 - }) + }); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (err, body) => { - assert.equal(err.errno, 133) - assert.equal(err.output.statusCode, 400) - assert.equal(err.output.payload.bouncedAt, 1533641031755) - assert.equal(err.message, 'Email account sent complaint') - assert.equal(body.messageId, undefined) - assert.equal(body.message, 'FREAKOUT') - done() - }) - }) + assert.equal(err.errno, 133); + assert.equal(err.output.statusCode, 400); + assert.equal(err.output.payload.bouncedAt, 1533641031755); + assert.equal(err.message, 'Email account sent complaint'); + assert.equal(body.messageId, undefined); + assert.equal(body.message, 'FREAKOUT'); + done(); + }); + }); it('emailService handles unsuccessful request', (done) => { const mock = { 'request': function (config, cb) { - cb(Error('FREAKOUT'), undefined, undefined) + cb(Error('FREAKOUT'), undefined, undefined); } - } + }; - const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config) + const emailService = proxyquire(`${ROOT_DIR}/lib/senders/email_service`, mock)(config); emailService.sendMail(emailConfig, (err, body) => { - assert(err instanceof Error) - assert.equal(err.message, 'FREAKOUT') - assert.equal(body.messageId, undefined) - assert.equal(body.message, undefined) - done() - }) - }) + assert(err instanceof Error); + assert.equal(err.message, 'FREAKOUT'); + assert.equal(body.messageId, undefined); + assert.equal(body.message, undefined); + done(); + }); + }); } -) +); diff --git a/test/local/senders/index.js b/test/local/senders/index.js index a1388f9b..87be8ce8 100644 --- a/test/local/senders/index.js +++ b/test/local/senders/index.js @@ -2,27 +2,27 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const config = require(`${ROOT_DIR}/config`).getProperties() -const crypto = require('crypto') -const error = require(`${ROOT_DIR}/lib/error`) -const mocks = require(`${ROOT_DIR}/test/mocks`) -const senders = require(`${ROOT_DIR}/lib/senders`) -const sinon = require('sinon') -const P = require('bluebird') +const { assert } = require('chai'); +const config = require(`${ROOT_DIR}/config`).getProperties(); +const crypto = require('crypto'); +const error = require(`${ROOT_DIR}/lib/error`); +const mocks = require(`${ROOT_DIR}/test/mocks`); +const senders = require(`${ROOT_DIR}/lib/senders`); +const sinon = require('sinon'); +const P = require('bluebird'); -const nullLog = mocks.mockLog() +const nullLog = mocks.mockLog(); describe('lib/senders/index', () => { describe('email', () => { - const UID = crypto.randomBytes(16) - const EMAIL = crypto.randomBytes(16).toString('hex') + '@example.test' + const UID = crypto.randomBytes(16); + const EMAIL = crypto.randomBytes(16).toString('hex') + '@example.test'; const EMAILS = [{ email: EMAIL, isPrimary: true, @@ -35,14 +35,14 @@ describe('lib/senders/index', () => { email: crypto.randomBytes(16).toString('hex') + '@example.test', isPrimary: false, isVerified: false - }] + }]; const bounces = { check: sinon.spy(() => P.resolve([])) - } + }; const acct = { email: EMAIL, uid: UID - } + }; function createSender(config, bounces, log) { return senders(log || nullLog, Object.assign({}, config, { @@ -51,329 +51,329 @@ describe('lib/senders/index', () => { } }), error, bounces, {}) .then(sndrs => { - const email = sndrs.email + const email = sndrs.email; email._ungatedMailer.mailer.sendMail = sinon.spy((opts, cb) => { - cb(null, {}) - }) - return email - }) + cb(null, {}); + }); + return email; + }); } beforeEach(() => { - bounces.check.resetHistory() - }) + bounces.check.resetHistory(); + }); describe('.sendVerifyCode()', () => { - const code = crypto.randomBytes(32) + const code = crypto.randomBytes(32); it('should call mailer.verifyEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.verifyEmail = sinon.spy(() => P.resolve({})) - return email.sendVerifyCode(EMAILS, acct, {code: code}) + email = e; + email._ungatedMailer.verifyEmail = sinon.spy(() => P.resolve({})); + return email.sendVerifyCode(EMAILS, acct, {code: code}); }) .then(() => { - assert.equal(bounces.check.callCount, 1) - assert.equal(email._ungatedMailer.verifyEmail.callCount, 1) + assert.equal(bounces.check.callCount, 1); + assert.equal(email._ungatedMailer.verifyEmail.callCount, 1); - const args = email._ungatedMailer.verifyEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - }) - }) - }) + const args = email._ungatedMailer.verifyEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + }); + }); + }); describe('.sendVerifyLoginEmail()', () => { - const code = crypto.randomBytes(32) + const code = crypto.randomBytes(32); it('should call mailer.verifyLoginEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.verifyLoginEmail = sinon.spy(() => P.resolve({})) - return email.sendVerifyLoginEmail(EMAILS, acct, {code: code}) + email = e; + email._ungatedMailer.verifyLoginEmail = sinon.spy(() => P.resolve({})); + return email.sendVerifyLoginEmail(EMAILS, acct, {code: code}); }) .then(() => { - assert.equal(bounces.check.callCount, 2) - assert.equal(email._ungatedMailer.verifyLoginEmail.callCount, 1) + assert.equal(bounces.check.callCount, 2); + assert.equal(email._ungatedMailer.verifyLoginEmail.callCount, 1); - const args = email._ungatedMailer.verifyLoginEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') - }) - }) - }) + const args = email._ungatedMailer.verifyLoginEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); + }); + }); + }); describe('.sendRecoveryCode()', () => { const token = { email: EMAIL, data: crypto.randomBytes(32) - } - const code = crypto.randomBytes(32) + }; + const code = crypto.randomBytes(32); it('should call mailer.recoveryEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.recoveryEmail = sinon.spy(() => P.resolve({})) - return email.sendRecoveryCode(EMAILS, acct, {code: code, token: token}) + email = e; + email._ungatedMailer.recoveryEmail = sinon.spy(() => P.resolve({})); + return email.sendRecoveryCode(EMAILS, acct, {code: code, token: token}); }) .then(() => { - assert.equal(bounces.check.callCount, 2) - assert.equal(email._ungatedMailer.recoveryEmail.callCount, 1) + assert.equal(bounces.check.callCount, 2); + assert.equal(email._ungatedMailer.recoveryEmail.callCount, 1); - const args = email._ungatedMailer.recoveryEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') - }) - }) - }) + const args = email._ungatedMailer.recoveryEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); + }); + }); + }); describe('.sendPasswordChangedNotification()', () => { it('should call mailer.passwordChangedEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.passwordChangedEmail = sinon.spy(() => P.resolve({})) - return email.sendPasswordChangedNotification(EMAILS, acct, {}) + email = e; + email._ungatedMailer.passwordChangedEmail = sinon.spy(() => P.resolve({})); + return email.sendPasswordChangedNotification(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.passwordChangedEmail.callCount, 1) + assert.equal(email._ungatedMailer.passwordChangedEmail.callCount, 1); - const args = email._ungatedMailer.passwordChangedEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + const args = email._ungatedMailer.passwordChangedEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('.sendPasswordResetNotification()', () => { it('should call mailer.passwordResetEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.passwordResetEmail = sinon.spy(() => P.resolve({})) - return email.sendPasswordResetNotification(EMAILS, acct, {}) + email = e; + email._ungatedMailer.passwordResetEmail = sinon.spy(() => P.resolve({})); + return email.sendPasswordResetNotification(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.passwordResetEmail.callCount, 1) + assert.equal(email._ungatedMailer.passwordResetEmail.callCount, 1); - const args = email._ungatedMailer.passwordResetEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + const args = email._ungatedMailer.passwordResetEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('.sendNewDeviceLoginNotification()', () => { it('should call mailer.newDeviceLoginEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.newDeviceLoginEmail = sinon.spy(() => P.resolve({})) - return email.sendNewDeviceLoginNotification(EMAILS, acct, {}) + email = e; + email._ungatedMailer.newDeviceLoginEmail = sinon.spy(() => P.resolve({})); + return email.sendNewDeviceLoginNotification(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.newDeviceLoginEmail.callCount, 1) + assert.equal(email._ungatedMailer.newDeviceLoginEmail.callCount, 1); - const args = email._ungatedMailer.newDeviceLoginEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + const args = email._ungatedMailer.newDeviceLoginEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('.sendPostVerifyEmail()', () => { it('should call mailer.postVerifyEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.postVerifyEmail = sinon.spy(() => P.resolve({})) - return email.sendPostVerifyEmail(EMAILS, acct, {}) + email = e; + email._ungatedMailer.postVerifyEmail = sinon.spy(() => P.resolve({})); + return email.sendPostVerifyEmail(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.postVerifyEmail.callCount, 1) + assert.equal(email._ungatedMailer.postVerifyEmail.callCount, 1); - const args = email._ungatedMailer.postVerifyEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails, undefined, 'no cc emails set') - assert.equal(bounces.check.callCount, 1) - }) - }) - }) + const args = email._ungatedMailer.postVerifyEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails, undefined, 'no cc emails set'); + assert.equal(bounces.check.callCount, 1); + }); + }); + }); describe('.sendUnblockCode()', () => { - const code = crypto.randomBytes(8).toString('hex') + const code = crypto.randomBytes(8).toString('hex'); it('should call mailer.unblockCodeEmail()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})) - return email.sendUnblockCode(EMAILS, acct, {code: code}) + email = e; + email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})); + return email.sendUnblockCode(EMAILS, acct, {code: code}); }) .then(() => { - assert.equal(email._ungatedMailer.unblockCodeEmail.callCount, 1) + assert.equal(email._ungatedMailer.unblockCodeEmail.callCount, 1); - const args = email._ungatedMailer.unblockCodeEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') + const args = email._ungatedMailer.unblockCodeEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('.sendPostAddTwoStepAuthNotification()', () => { it('should call mailer.sendPostAddTwoStepAuthNotification()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.postAddTwoStepAuthenticationEmail = sinon.spy(() => P.resolve({})) - return email.sendPostAddTwoStepAuthNotification(EMAILS, acct, {}) + email = e; + email._ungatedMailer.postAddTwoStepAuthenticationEmail = sinon.spy(() => P.resolve({})); + return email.sendPostAddTwoStepAuthNotification(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.postAddTwoStepAuthenticationEmail.callCount, 1) + assert.equal(email._ungatedMailer.postAddTwoStepAuthenticationEmail.callCount, 1); - const args = email._ungatedMailer.postAddTwoStepAuthenticationEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') + const args = email._ungatedMailer.postAddTwoStepAuthenticationEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('.sendPostRemoveTwoStepAuthNotification()', () => { it('should call mailer.sendPostRemoveTwoStepAuthNotification()', () => { - let email + let email; return createSender(config, bounces) .then(e => { - email = e - email._ungatedMailer.postRemoveTwoStepAuthenticationEmail = sinon.spy(() => P.resolve({})) - return email.sendPostRemoveTwoStepAuthNotification(EMAILS, acct, {}) + email = e; + email._ungatedMailer.postRemoveTwoStepAuthenticationEmail = sinon.spy(() => P.resolve({})); + return email.sendPostRemoveTwoStepAuthNotification(EMAILS, acct, {}); }) .then(() => { - assert.equal(email._ungatedMailer.postRemoveTwoStepAuthenticationEmail.callCount, 1) + assert.equal(email._ungatedMailer.postRemoveTwoStepAuthenticationEmail.callCount, 1); - const args = email._ungatedMailer.postRemoveTwoStepAuthenticationEmail.getCall(0).args - assert.equal(args[0].email, EMAIL, 'email correctly set') - assert.equal(args[0].ccEmails.length, 1, 'email correctly set') - assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set') + const args = email._ungatedMailer.postRemoveTwoStepAuthenticationEmail.getCall(0).args; + assert.equal(args[0].email, EMAIL, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 1, 'email correctly set'); + assert.equal(args[0].ccEmails[0], EMAILS[1].email, 'cc email correctly set'); - assert.equal(bounces.check.callCount, 2) - }) - }) - }) + assert.equal(bounces.check.callCount, 2); + }); + }); + }); describe('gated on bounces', () => { - const code = crypto.randomBytes(8).toString('hex') + const code = crypto.randomBytes(8).toString('hex'); it('errors if bounce check fails', () => { - const log = mocks.mockLog() - const DATE = Date.now() - 10000 + const log = mocks.mockLog(); + const DATE = Date.now() - 10000; const errorBounces = { check: sinon.spy(() => P.reject(error.emailComplaint(DATE))) - } + }; return createSender(config, errorBounces, log) .then(email => { - email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})) - return email.sendUnblockCode(EMAILS, acct, {code: code}) + email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})); + return email.sendUnblockCode(EMAILS, acct, {code: code}); }) .then(() => { - assert.fail('should have blocked the send') + assert.fail('should have blocked the send'); }, (e) => { - assert.equal(errorBounces.check.callCount, 2) - assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT) + assert.equal(errorBounces.check.callCount, 2); + assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT); - assert.ok(log.info.callCount >= 2) - const msg = log.info.args[1] - assert.equal(msg[0], 'mailer.blocked') - assert.equal(msg[1].errno, e.errno) - assert.equal(msg[1].bouncedAt, DATE) - }) - }) + assert.ok(log.info.callCount >= 2); + const msg = log.info.args[1]; + assert.equal(msg[0], 'mailer.blocked'); + assert.equal(msg[1].errno, e.errno); + assert.equal(msg[1].bouncedAt, DATE); + }); + }); it('on gated primary email + verified secondary, sends to secondary', () => { - const log = mocks.mockLog() - const DATE = Date.now() - 10000 - let email + const log = mocks.mockLog(); + const DATE = Date.now() - 10000; + let email; const errorBounces = { check: sinon.spy((email) => { if (email === EMAIL) { - return P.reject(error.emailComplaint(DATE)) + return P.reject(error.emailComplaint(DATE)); } - return P.resolve({}) + return P.resolve({}); }) - } + }; return createSender(config, errorBounces, log) .then(e => { - email = e - email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})) - return email.sendUnblockCode(EMAILS, acct, {code: code}) + email = e; + email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})); + return email.sendUnblockCode(EMAILS, acct, {code: code}); }) .then(() => { - const args = email._ungatedMailer.unblockCodeEmail.getCall(0).args - assert.equal(args[0].email, EMAILS[1].email, 'email correctly set') - assert.equal(args[0].ccEmails.length, 0, 'email does not appear twice') - assert.equal(errorBounces.check.callCount, 2) - }) - }) + const args = email._ungatedMailer.unblockCodeEmail.getCall(0).args; + assert.equal(args[0].email, EMAILS[1].email, 'email correctly set'); + assert.equal(args[0].ccEmails.length, 0, 'email does not appear twice'); + assert.equal(errorBounces.check.callCount, 2); + }); + }); it('on gated primary email + unverified secondary, blocks the send', () => { - const log = mocks.mockLog() - const DATE = Date.now() - 10000 - let email + const log = mocks.mockLog(); + const DATE = Date.now() - 10000; + let email; const errorBounces = { check: sinon.spy((email) => { if (email === EMAIL) { - return P.reject(error.emailComplaint(DATE)) + return P.reject(error.emailComplaint(DATE)); } - return P.resolve({}) + return P.resolve({}); }) - } + }; return createSender(config, errorBounces, log) .then(e => { - email = e - email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})) - EMAILS[1].isVerified = false - return email.sendUnblockCode(EMAILS, acct, {code: code}) + email = e; + email._ungatedMailer.unblockCodeEmail = sinon.spy(() => P.resolve({})); + EMAILS[1].isVerified = false; + return email.sendUnblockCode(EMAILS, acct, {code: code}); }) .then(() => { - assert.fail('should have blocked the send') + assert.fail('should have blocked the send'); }, (e) => { - assert.equal(errorBounces.check.callCount, 1) - assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT) + assert.equal(errorBounces.check.callCount, 1); + assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT); - assert.ok(log.info.callCount >= 2) - const msg = log.info.args[1] - assert.equal(msg[0], 'mailer.blocked') - assert.equal(msg[1].errno, e.errno) - assert.equal(msg[1].bouncedAt, DATE) + assert.ok(log.info.callCount >= 2); + const msg = log.info.args[1]; + assert.equal(msg[0], 'mailer.blocked'); + assert.equal(msg[1].errno, e.errno); + assert.equal(msg[1].bouncedAt, DATE); }) .finally(() => { - EMAILS[1].isVerified = true - }) - }) - }) - }) -}) + EMAILS[1].isVerified = true; + }); + }); + }); + }); +}); diff --git a/test/local/senders/legacy_log.js b/test/local/senders/legacy_log.js index 02fe853a..c82d7f4c 100644 --- a/test/local/senders/legacy_log.js +++ b/test/local/senders/legacy_log.js @@ -2,11 +2,11 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var sinon = require('sinon') -const legacyLog = require('../../../lib/senders/legacy_log') +const { assert } = require('chai'); +var sinon = require('sinon'); +const legacyLog = require('../../../lib/senders/legacy_log'); var spyLog = { critical: sinon.spy(), @@ -14,27 +14,27 @@ var spyLog = { error: sinon.spy(), info: sinon.spy(), warn: sinon.spy() -} +}; it('legacy_log unit tests', function () { var data = { op: 'testOp', err: 'Nooo!' - } - var log = legacyLog(spyLog) - log.trace(data) - assert.equal(spyLog.debug.args[0][0], data.op) - assert.equal(spyLog.debug.args[0][1], data) - log.error(data) - assert.equal(spyLog.error.args[0][0], data.op) - assert.equal(spyLog.error.args[0][1], data) - log.fatal(data) - assert.equal(spyLog.critical.args[0][0], data.op) - assert.equal(spyLog.critical.args[0][1], data) - log.warn(data) - assert.equal(spyLog.warn.args[0][0], data.op) - assert.equal(spyLog.warn.args[0][1], data) - log.info(data) - assert.equal(spyLog.info.args[0][0], data.op) - assert.equal(spyLog.info.args[0][1], data) -}) + }; + var log = legacyLog(spyLog); + log.trace(data); + assert.equal(spyLog.debug.args[0][0], data.op); + assert.equal(spyLog.debug.args[0][1], data); + log.error(data); + assert.equal(spyLog.error.args[0][0], data.op); + assert.equal(spyLog.error.args[0][1], data); + log.fatal(data); + assert.equal(spyLog.critical.args[0][0], data.op); + assert.equal(spyLog.critical.args[0][1], data); + log.warn(data); + assert.equal(spyLog.warn.args[0][0], data.op); + assert.equal(spyLog.warn.args[0][1], data); + log.info(data); + assert.equal(spyLog.info.args[0][0], data.op); + assert.equal(spyLog.info.args[0][1], data); +}); diff --git a/test/local/senders/oauth_client_info.js b/test/local/senders/oauth_client_info.js index d757c4e0..13693921 100644 --- a/test/local/senders/oauth_client_info.js +++ b/test/local/senders/oauth_client_info.js @@ -2,71 +2,71 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') -const sinon = require('sinon') -const P = require('../../../lib/promise') +const { assert } = require('chai'); +const sinon = require('sinon'); +const P = require('../../../lib/promise'); const FIREFOX_CLIENT = { name: 'Firefox' -} +}; const OAUTH_CLIENT = { name: 'FxA OAuth Console' -} +}; describe('lib/senders/oauth_client_info:', () => { describe('fetch:', () => { - let clientInfo - let fetch - let mockLog - let mockOAuthDB + let clientInfo; + let fetch; + let mockLog; + let mockOAuthDB; const mockConfig = { oauth: { url: 'http://localhost:9010', clientInfoCacheTTL: 5 } - } + }; beforeEach(() => { mockLog = { fatal: sinon.spy(), trace: sinon.spy(), warn: sinon.spy() - } - mockOAuthDB = {} - clientInfo = require(`${ROOT_DIR}/lib/senders/oauth_client_info`)(mockLog, mockConfig, mockOAuthDB) - fetch = clientInfo.fetch - }) + }; + mockOAuthDB = {}; + clientInfo = require(`${ROOT_DIR}/lib/senders/oauth_client_info`)(mockLog, mockConfig, mockOAuthDB); + fetch = clientInfo.fetch; + }); afterEach(() => { - return clientInfo.__clientCache.clear() - }) + return clientInfo.__clientCache.clear(); + }); it('returns Firefox if no client id', () => { return fetch().then((res) => { - assert.deepEqual(res, FIREFOX_CLIENT) - }) - }) + assert.deepEqual(res, FIREFOX_CLIENT); + }); + }); it('returns Firefox if service=sync', () => { return fetch('sync').then((res) => { - assert.deepEqual(res, FIREFOX_CLIENT) - }) - }) + assert.deepEqual(res, FIREFOX_CLIENT); + }); + }); it('falls back to Firefox if error', () => { mockOAuthDB.getClientInfo = sinon.spy(async () => { - throw new Error('Request failed') - }) + throw new Error('Request failed'); + }); return fetch('24bdbfa45cd300c5').then((res) => { - assert.deepEqual(res, FIREFOX_CLIENT) - assert.ok(mockLog.fatal.calledOnce, 'called fatal log') - }) - }) + assert.deepEqual(res, FIREFOX_CLIENT); + assert.ok(mockLog.fatal.calledOnce, 'called fatal log'); + }); + }); it('falls back to Firefox if non-200 response', () => { mockOAuthDB.getClientInfo = sinon.spy(async () => { @@ -74,55 +74,55 @@ describe('lib/senders/oauth_client_info:', () => { statusCode: 400, code: 400, errno: 109 - }) - }) + }); + }); return fetch('f00bdbfa45cd300c5').then((res) => { - assert.deepEqual(res, FIREFOX_CLIENT) - assert.ok(mockLog.warn.calledOnce, 'called warn log') - }) - }) + assert.deepEqual(res, FIREFOX_CLIENT); + assert.ok(mockLog.warn.calledOnce, 'called warn log'); + }); + }); it('fetches and memory caches client information', () => { mockOAuthDB.getClientInfo = sinon.spy(async (clientId) => { - assert.equal(clientId, '24bdbfa45cd300c5') - return OAUTH_CLIENT - }) + assert.equal(clientId, '24bdbfa45cd300c5'); + return OAUTH_CLIENT; + }); return fetch('24bdbfa45cd300c5').then((res) => { - assert.deepEqual(res, OAUTH_CLIENT) - assert.equal(mockLog.trace.getCall(0).args[0], 'fetch.start') - assert.equal(mockLog.trace.getCall(1).args[0], 'fetch.usedServer') - assert.equal(mockLog.trace.getCall(2), null) - assert.ok(mockOAuthDB.getClientInfo.calledOnce) + assert.deepEqual(res, OAUTH_CLIENT); + assert.equal(mockLog.trace.getCall(0).args[0], 'fetch.start'); + assert.equal(mockLog.trace.getCall(1).args[0], 'fetch.usedServer'); + assert.equal(mockLog.trace.getCall(2), null); + assert.ok(mockOAuthDB.getClientInfo.calledOnce); // second call is cached - return fetch('24bdbfa45cd300c5') + return fetch('24bdbfa45cd300c5'); }).then((res) => { - assert.equal(mockLog.trace.getCall(2).args[0], 'fetch.start') - assert.equal(mockLog.trace.getCall(3).args[0], 'fetch.usedCache') - assert.ok(mockOAuthDB.getClientInfo.calledOnce) - assert.deepEqual(res, OAUTH_CLIENT) - }) + assert.equal(mockLog.trace.getCall(2).args[0], 'fetch.start'); + assert.equal(mockLog.trace.getCall(3).args[0], 'fetch.usedCache'); + assert.ok(mockOAuthDB.getClientInfo.calledOnce); + assert.deepEqual(res, OAUTH_CLIENT); + }); - }) + }); it('memory cache expires', () => { mockOAuthDB.getClientInfo = sinon.spy(async (clientId) => { - return OAUTH_CLIENT - }) + return OAUTH_CLIENT; + }); return P.delay(15, fetch('24bdbfa45cd300c5')).then((res) => { - assert.deepEqual(res, OAUTH_CLIENT) - assert.equal(mockLog.trace.getCall(1).args[0], 'fetch.usedServer') - assert.ok(mockOAuthDB.getClientInfo.calledOnce) + assert.deepEqual(res, OAUTH_CLIENT); + assert.equal(mockLog.trace.getCall(1).args[0], 'fetch.usedServer'); + assert.ok(mockOAuthDB.getClientInfo.calledOnce); // second call uses server, cache expired - return fetch('24bdbfa45cd300c5') + return fetch('24bdbfa45cd300c5'); }).then((res) => { - assert.equal(mockLog.trace.getCall(3).args[0], 'fetch.usedServer') - assert.ok(mockOAuthDB.getClientInfo.calledTwice) - assert.deepEqual(res, OAUTH_CLIENT) - }) - }) - }) -}) + assert.equal(mockLog.trace.getCall(3).args[0], 'fetch.usedServer'); + assert.ok(mockOAuthDB.getClientInfo.calledTwice); + assert.deepEqual(res, OAUTH_CLIENT); + }); + }); + }); +}); diff --git a/test/local/senders/sms.js b/test/local/senders/sms.js index 49c5e1ef..61904591 100644 --- a/test/local/senders/sms.js +++ b/test/local/senders/sms.js @@ -2,19 +2,19 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const mocks = require('../../mocks') -const P = require('bluebird') -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const { assert } = require('chai'); +const mocks = require('../../mocks'); +const P = require('bluebird'); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); -const ROOT_DIR = '../../..' -const ISO_8601_FORMAT = /^20[1-9][0-9]-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:00Z$/ +const ROOT_DIR = '../../..'; +const ISO_8601_FORMAT = /^20[1-9][0-9]-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:00Z$/; describe('lib/senders/sms:', () => { - let config, log, results, cloudwatch, sns, mockSns, smsModule, translator, templates + let config, log, results, cloudwatch, sns, mockSns, smsModule, translator, templates; beforeEach(() => { config = { @@ -27,18 +27,18 @@ describe('lib/senders/sms:', () => { minimumCreditThresholdUSD: 2, useMock: false } - } - log = mocks.mockLog() + }; + log = mocks.mockLog(); results = { getMetricStatistics: { Datapoints: [ { Maximum: 0 } ] }, getSMSAttributes: { attributes: { MonthlySpendLimit: config.sms.minimumCreditThresholdUSD } }, publish: P.resolve({ MessageId: 'foo' }) - } + }; cloudwatch = { getMetricStatistics: sinon.spy(() => ({ promise: () => P.resolve(results.getMetricStatistics) })) - } + }; sns = { getSMSAttributes: sinon.spy(() => ({ promise: () => P.resolve(results.getSMSAttributes) @@ -46,7 +46,7 @@ describe('lib/senders/sms:', () => { publish: sinon.spy(() => ({ promise: () => results.publish })) - } + }; mockSns = { getSMSAttributes: sinon.spy(() => ({ promise: () => P.resolve(results.getSMSAttributes) @@ -54,178 +54,178 @@ describe('lib/senders/sms:', () => { publish: sinon.spy(() => ({ promise: () => results.publish })) - } + }; smsModule = proxyquire(`${ROOT_DIR}/lib/senders/sms`, { - 'aws-sdk/clients/cloudwatch': function () { return cloudwatch }, - 'aws-sdk/clients/sns': function () { return sns }, - '../../test/mock-sns': function () { return mockSns } - }) + 'aws-sdk/clients/cloudwatch': function () { return cloudwatch; }, + 'aws-sdk/clients/sns': function () { return sns; }, + '../../test/mock-sns': function () { return mockSns; } + }); return P.all([ require(`${ROOT_DIR}/lib/senders/translator`)(['en'], 'en'), require(`${ROOT_DIR}/lib/senders/templates`).init() ]).then(results => { - translator = results[0] - templates = results[1] - }) - }) + translator = results[0]; + templates = results[1]; + }); + }); describe('initialise:', () => { - let sms + let sms; beforeEach(() => { - sms = smsModule(log, translator, templates, config) - }) + sms = smsModule(log, translator, templates, config); + }); it('returned the expected interface', () => { - assert.equal(typeof sms.isBudgetOk, 'function') - assert.equal(sms.isBudgetOk.length, 0) + assert.equal(typeof sms.isBudgetOk, 'function'); + assert.equal(sms.isBudgetOk.length, 0); - assert.equal(typeof sms.send, 'function') - assert.equal(sms.send.length, 4) - }) + assert.equal(typeof sms.send, 'function'); + assert.equal(sms.send.length, 4); + }); it('did not call the AWS SDK', () => { - assert.equal(sns.getSMSAttributes.callCount, 0) - assert.equal(sns.publish.callCount, 0) - assert.equal(cloudwatch.getMetricStatistics.callCount, 0) - }) + assert.equal(sns.getSMSAttributes.callCount, 0); + assert.equal(sns.publish.callCount, 0); + assert.equal(cloudwatch.getMetricStatistics.callCount, 0); + }); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); describe('wait a tick:', () => { - beforeEach(done => setImmediate(done)) + beforeEach(done => setImmediate(done)); it('called sns.getSMSAttributes correctly', () => { - assert.equal(sns.getSMSAttributes.callCount, 1) - const args = sns.getSMSAttributes.args[0] - assert.equal(args.length, 1) - assert.deepEqual(args[0], { attributes: [ 'MonthlySpendLimit' ] }) - }) + assert.equal(sns.getSMSAttributes.callCount, 1); + const args = sns.getSMSAttributes.args[0]; + assert.equal(args.length, 1); + assert.deepEqual(args[0], { attributes: [ 'MonthlySpendLimit' ] }); + }); it('called cloudwatch.getMetricStatistics correctly', () => { - const PERIOD_IN_MINUTES = 5 // matches setting in ../../../lib/senders/sms.js - assert.equal(cloudwatch.getMetricStatistics.callCount, 1) - const args = cloudwatch.getMetricStatistics.args[0] - assert.equal(args.length, 1) - assert.equal(args[0].Namespace, 'AWS/SNS') - assert.equal(args[0].MetricName, 'SMSMonthToDateSpentUSD') - assert(ISO_8601_FORMAT.test(args[0].StartTime)) - assert(ISO_8601_FORMAT.test(args[0].EndTime)) - assert(new Date(args[0].StartTime).getTime() === new Date(args[0].EndTime).getTime() - PERIOD_IN_MINUTES * 60000) - assert(new Date(args[0].EndTime).getTime() > Date.now() - PERIOD_IN_MINUTES * 60000) - assert.equal(args[0].Period, PERIOD_IN_MINUTES * 60) - assert.deepEqual(args[0].Statistics, [ 'Maximum' ]) - }) + const PERIOD_IN_MINUTES = 5; // matches setting in ../../../lib/senders/sms.js + assert.equal(cloudwatch.getMetricStatistics.callCount, 1); + const args = cloudwatch.getMetricStatistics.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0].Namespace, 'AWS/SNS'); + assert.equal(args[0].MetricName, 'SMSMonthToDateSpentUSD'); + assert(ISO_8601_FORMAT.test(args[0].StartTime)); + assert(ISO_8601_FORMAT.test(args[0].EndTime)); + assert(new Date(args[0].StartTime).getTime() === new Date(args[0].EndTime).getTime() - PERIOD_IN_MINUTES * 60000); + assert(new Date(args[0].EndTime).getTime() > Date.now() - PERIOD_IN_MINUTES * 60000); + assert.equal(args[0].Period, PERIOD_IN_MINUTES * 60); + assert.deepEqual(args[0].Statistics, [ 'Maximum' ]); + }); it('called log.info correctly', () => { - assert.equal(log.info.callCount, 1) - const args = log.info.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.budget.ok') + assert.equal(log.info.callCount, 1); + const args = log.info.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.budget.ok'); assert.deepEqual(args[1], { isBudgetOk: true, current: 0, limit: config.sms.minimumCreditThresholdUSD, threshold: config.sms.minimumCreditThresholdUSD - }) - }) + }); + }); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); it('did not call sns.publish', () => { - assert.equal(sns.publish.callCount, 0) - }) + assert.equal(sns.publish.callCount, 0); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('spend > threshold:', () => { beforeEach(() => { - results.getMetricStatistics.Datapoints[0].Maximum = 1 - }) + results.getMetricStatistics.Datapoints[0].Maximum = 1; + }); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); describe('wait a tick:', () => { - beforeEach(done => setImmediate(done)) + beforeEach(done => setImmediate(done)); it('isBudgetOk returns false', () => { - assert.strictEqual(sms.isBudgetOk(), false) - }) + assert.strictEqual(sms.isBudgetOk(), false); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); + }); describe('invalid data:', () => { beforeEach(() => { - results.getMetricStatistics.Datapoints[0].Maximum = 'wibble' - }) + results.getMetricStatistics.Datapoints[0].Maximum = 'wibble'; + }); describe('wait a tick:', () => { - beforeEach(done => setImmediate(done)) + beforeEach(done => setImmediate(done)); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.budget.error') + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.budget.error'); assert.deepEqual(args[1], { err: 'Invalid getMetricStatistics result "wibble"', result: undefined - }) - }) - }) - }) + }); + }); + }); + }); describe('unexpected response:', () => { beforeEach(() => { - results.getMetricStatistics.Datapoints = [] - }) + results.getMetricStatistics.Datapoints = []; + }); describe('wait a tick:', () => { - beforeEach(done => setImmediate(done)) + beforeEach(done => setImmediate(done)); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.budget.error') + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.budget.error'); assert.deepEqual(args[1], { err: 'Cannot read property \'Maximum\' of undefined', result: JSON.stringify(results.getMetricStatistics) - }) - }) - }) - }) + }); + }); + }); + }); describe('send a valid sms without a signinCode:', () => { beforeEach(() => { - return sms.send('+442078553000', 'installFirefox', 'en') - }) + return sms.send('+442078553000', 'installFirefox', 'en'); + }); it('called sns.publish correctly', () => { - assert.equal(sns.publish.callCount, 1) - const args = sns.publish.args[0] - assert.equal(args.length, 1) + assert.equal(sns.publish.callCount, 1); + const args = sns.publish.args[0]; + assert.equal(args.length, 1); assert.deepEqual(args[0], { Message: 'Thanks for choosing Firefox! You can install Firefox for mobile here: https://baz/qux', MessageAttributes: { @@ -243,144 +243,144 @@ describe('lib/senders/sms:', () => { } }, PhoneNumber: '+442078553000' - }) - }) + }); + }); it('called log.trace correctly', () => { - assert.equal(log.trace.callCount, 1) - const args = log.trace.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.send') + assert.equal(log.trace.callCount, 1); + const args = log.trace.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.send'); assert.deepEqual(args[1], { templateName: 'installFirefox', acceptLanguage: 'en' - }) - }) + }); + }); it('called log.info correctly', () => { - assert.equal(log.info.callCount, 2) - const args = log.info.args[1] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.send.success') + assert.equal(log.info.callCount, 2); + const args = log.info.args[1]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.send.success'); assert.deepEqual(args[1], { templateName: 'installFirefox', acceptLanguage: 'en', messageId: 'foo' - }) - }) + }); + }); it('did not call mockSns.publish', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('send a valid sms with a signinCode:', () => { beforeEach(() => { - return sms.send('+442078553000', 'installFirefox', 'en', Buffer.from('++//ff0=', 'base64')) - }) + return sms.send('+442078553000', 'installFirefox', 'en', Buffer.from('++//ff0=', 'base64')); + }); it('called sns.publish correctly', () => { - assert.equal(sns.publish.callCount, 1) - assert.equal(sns.publish.args[0][0].Message, 'Thanks for choosing Firefox! You can install Firefox for mobile here: https://wibble/--__ff0') - }) + assert.equal(sns.publish.callCount, 1); + assert.equal(sns.publish.args[0][0].Message, 'Thanks for choosing Firefox! You can install Firefox for mobile here: https://wibble/--__ff0'); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('attempt to send an sms with an invalid template name:', () => { - let error + let error; beforeEach(() => { return sms.send('+442078553000', 'wibble', 'en', Buffer.from('++//ff0=', 'base64')) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('failed correctly', () => { - assert.equal(error.errno, 131) - assert.equal(error.message, 'Invalid message id') - }) + assert.equal(error.errno, 131); + assert.equal(error.message, 'Invalid message id'); + }); it('called log.error correctly', () => { - assert.equal(log.error.callCount, 1) - const args = log.error.args[0] - assert.lengthOf(args, 2) - assert.equal(args[0], 'sms.getMessage.error') + assert.equal(log.error.callCount, 1); + const args = log.error.args[0]; + assert.lengthOf(args, 2); + assert.equal(args[0], 'sms.getMessage.error'); assert.deepEqual(args[1], { templateName: 'wibble' - }) - }) + }); + }); it('did not call sns.publish', () => { - assert.equal(sns.publish.callCount, 0) - }) - }) + assert.equal(sns.publish.callCount, 0); + }); + }); describe('attempt to send an sms that is rejected by the network provider:', () => { - let error + let error; beforeEach(() => { results.publish = P.reject({ statusCode: 400, code: 42, message: 'this is an error' - }) + }); return sms.send('+442078553000', 'installFirefox', 'en', Buffer.from('++//ff0=', 'base64')) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('failed correctly', () => { - assert.equal(error.errno, 132) - assert.equal(error.message, 'Message rejected') - assert.equal(error.output.payload.reason, 'this is an error') - assert.equal(error.output.payload.reasonCode, 42) - }) - }) - }) + assert.equal(error.errno, 132); + assert.equal(error.message, 'Message rejected'); + assert.equal(error.output.payload.reason, 'this is an error'); + assert.equal(error.output.payload.reasonCode, 42); + }); + }); + }); describe('initialise, useMock=true:', () => { - let sms + let sms; beforeEach(() => { - config.sms.useMock = true - sms = smsModule(log, translator, templates, config) - }) + config.sms.useMock = true; + sms = smsModule(log, translator, templates, config); + }); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); describe('wait a tick:', () => { - beforeEach(done => setImmediate(done)) + beforeEach(done => setImmediate(done)); it('isBudgetOk returns true', () => { - assert.strictEqual(sms.isBudgetOk(), true) - }) + assert.strictEqual(sms.isBudgetOk(), true); + }); it('did not call the AWS SDK', () => { - assert.equal(sns.getSMSAttributes.callCount, 0) - assert.equal(cloudwatch.getMetricStatistics.callCount, 0) - }) + assert.equal(sns.getSMSAttributes.callCount, 0); + assert.equal(cloudwatch.getMetricStatistics.callCount, 0); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('send an sms:', () => { beforeEach(() => { - return sms.send('+442078553000', 'installFirefox', 'en') - }) + return sms.send('+442078553000', 'installFirefox', 'en'); + }); it('called mockSns.publish correctly', () => { - assert.equal(mockSns.publish.callCount, 1) - const args = mockSns.publish.args[0] - assert.equal(args.length, 1) + assert.equal(mockSns.publish.callCount, 1); + const args = mockSns.publish.args[0]; + assert.equal(args.length, 1); assert.deepEqual(args[0], { Message: 'Thanks for choosing Firefox! You can install Firefox for mobile here: https://baz/qux', MessageAttributes: { @@ -398,16 +398,16 @@ describe('lib/senders/sms:', () => { } }, PhoneNumber: '+442078553000' - }) - }) + }); + }); it('did not call sns.publish', () => { - assert.equal(sns.publish.callCount, 0) - }) + assert.equal(sns.publish.callCount, 0); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) - }) -}) + assert.equal(log.error.callCount, 0); + }); + }); + }); +}); diff --git a/test/local/senders/templates.js b/test/local/senders/templates.js index 776dd80d..12682feb 100644 --- a/test/local/senders/templates.js +++ b/test/local/senders/templates.js @@ -2,51 +2,51 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../../..' +const ROOT_DIR = '../../..'; -const { assert } = require('chai') +const { assert } = require('chai'); describe('lib/senders/templates/index:', () => { - let templates + let templates; before(() => { - templates = require(`${ROOT_DIR}/lib/senders/templates`) - }) + templates = require(`${ROOT_DIR}/lib/senders/templates`); + }); it('interface is correct', () => { - assert.equal(typeof templates, 'object') - assert.equal(Object.keys(templates).length, 2) - assert.equal(typeof templates.generateTemplateName, 'function') - assert.equal(templates.generateTemplateName.length, 1) - assert.equal(typeof templates.init, 'function') - assert.equal(templates.init.length, 0) - }) + assert.equal(typeof templates, 'object'); + assert.equal(Object.keys(templates).length, 2); + assert.equal(typeof templates.generateTemplateName, 'function'); + assert.equal(templates.generateTemplateName.length, 1); + assert.equal(typeof templates.init, 'function'); + assert.equal(templates.init.length, 0); + }); it('templates.generateTemplateName converts from snake case to camel case', () => assert.equal(templates.generateTemplateName('this_is-a_Test'), 'thisIs-aTestEmail') - ) + ); it('templates.generateTemplateName does not alter SMS template names', () => assert.equal(templates.generateTemplateName('sms.another_test'), 'sms.another_test') - ) + ); describe('templates.init:', () => { - let result + let result; - before(() => templates.init().then(r => result = r)) + before(() => templates.init().then(r => result = r)); it('result is correct', () => { - assert.equal(typeof result, 'object') - const keys = Object.keys(result) - assert.equal(keys.length, 25) + assert.equal(typeof result, 'object'); + const keys = Object.keys(result); + assert.equal(keys.length, 25); keys.forEach(key => { - const fn = result[key] - assert.equal(typeof fn, 'function') - assert.equal(fn.length, 1) - }) - }) - }) -}) + const fn = result[key]; + assert.equal(typeof fn, 'function'); + assert.equal(fn.length, 1); + }); + }); + }); +}); diff --git a/test/local/senders/translator.js b/test/local/senders/translator.js index c4ad6c51..304df35d 100644 --- a/test/local/senders/translator.js +++ b/test/local/senders/translator.js @@ -2,37 +2,37 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); require('../../../lib/senders/translator')(['en', 'pt_br', 'DE', 'ES_AR', 'ES_cl'], 'en') .then(translator => { it('returns the correct interface', () => { - assert.equal(typeof translator, 'object') - assert.equal(Object.keys(translator).length, 2) - assert.equal(typeof translator.getTranslator, 'function') - assert.equal(typeof translator.getLocale, 'function') - }) + assert.equal(typeof translator, 'object'); + assert.equal(Object.keys(translator).length, 2); + assert.equal(typeof translator.getTranslator, 'function'); + assert.equal(typeof translator.getLocale, 'function'); + }); it('getTranslator works with upper and lowercase languages', () => { - let x = translator.getTranslator('PT-br,DE') - assert.equal(x.language, 'pt-BR') - x = translator.getTranslator('bu-ll,es-ar') - assert.equal(x.language, 'es-AR') - x = translator.getTranslator('es-CL') - assert.equal(x.language, 'es-CL') - x = translator.getTranslator('en-US') - assert.equal(x.language, 'en') - x = translator.getTranslator('db-LB') // a locale that does not exist - assert.equal(x.language, 'en') - }) + let x = translator.getTranslator('PT-br,DE'); + assert.equal(x.language, 'pt-BR'); + x = translator.getTranslator('bu-ll,es-ar'); + assert.equal(x.language, 'es-AR'); + x = translator.getTranslator('es-CL'); + assert.equal(x.language, 'es-CL'); + x = translator.getTranslator('en-US'); + assert.equal(x.language, 'en'); + x = translator.getTranslator('db-LB'); // a locale that does not exist + assert.equal(x.language, 'en'); + }); it('getLocale works with upper and lowercase languages', () => { - assert.equal(translator.getLocale('PT-br,DE'), 'pt-BR') - assert.equal(translator.getLocale('bu-ll,es-ar'), 'es-AR') - assert.equal(translator.getLocale('es-CL'), 'es-CL') - assert.equal(translator.getLocale('en-US'), 'en') - assert.equal(translator.getLocale('db-LB'), 'en') - }) -}) + assert.equal(translator.getLocale('PT-br,DE'), 'pt-BR'); + assert.equal(translator.getLocale('bu-ll,es-ar'), 'es-AR'); + assert.equal(translator.getLocale('es-CL'), 'es-CL'); + assert.equal(translator.getLocale('en-US'), 'en'); + assert.equal(translator.getLocale('db-LB'), 'en'); + }); +}); diff --git a/test/local/sentry.js b/test/local/sentry.js index 64babeea..bfc14308 100644 --- a/test/local/sentry.js +++ b/test/local/sentry.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const Hapi = require('hapi') +const Hapi = require('hapi'); -const config = require('../../config').getProperties() -const _configureSentry = require('../../lib/server')._configureSentry -const server = new Hapi.Server({}) +const config = require('../../config').getProperties(); +const _configureSentry = require('../../lib/server')._configureSentry; +const server = new Hapi.Server({}); describe('Sentry', () => { - let sentryDsn + let sentryDsn; beforeEach(() => { - sentryDsn = config.sentryDsn - }) + sentryDsn = config.sentryDsn; + }); afterEach(() => { - config.sentryDsn = sentryDsn - }) + config.sentryDsn = sentryDsn; + }); it('can be set up when sentry is enabled', () => { - config.sentryDsn = 'https://deadbeef:deadbeef@127.0.0.1/123' - assert.doesNotThrow(() => _configureSentry(server, config)) - }) + config.sentryDsn = 'https://deadbeef:deadbeef@127.0.0.1/123'; + assert.doesNotThrow(() => _configureSentry(server, config)); + }); it('can be set up when sentry is not enabled', () => { - assert.doesNotThrow(() => _configureSentry(server, config)) - }) -}) + assert.doesNotThrow(() => _configureSentry(server, config)); + }); +}); diff --git a/test/local/server.js b/test/local/server.js index 0c8211cd..a2be0a53 100644 --- a/test/local/server.js +++ b/test/local/server.js @@ -2,113 +2,113 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const EndpointError = require('poolee/lib/error')(require('util').inherits) -const error = require(`${ROOT_DIR}/lib/error`) -const hawk = require('hawk') -const knownIpLocation = require('../known-ip-location') -const mocks = require('../mocks') -const server = require(`${ROOT_DIR}/lib/server`) -const sinon = require('sinon') +const { assert } = require('chai'); +const EndpointError = require('poolee/lib/error')(require('util').inherits); +const error = require(`${ROOT_DIR}/lib/error`); +const hawk = require('hawk'); +const knownIpLocation = require('../known-ip-location'); +const mocks = require('../mocks'); +const server = require(`${ROOT_DIR}/lib/server`); +const sinon = require('sinon'); describe('lib/server', () => { describe('trimLocale', () => { it('trims given locale', () => { - assert.equal(server._trimLocale(' fr-CH, fr;q=0.9 '), 'fr-CH, fr;q=0.9') - }) - }) + assert.equal(server._trimLocale(' fr-CH, fr;q=0.9 '), 'fr-CH, fr;q=0.9'); + }); + }); describe('logEndpointErrors', () => { - const msg = 'Timeout' - const reason = 'Socket fail' + const msg = 'Timeout'; + const reason = 'Socket fail'; const response = { __proto__: { name: 'EndpointError' }, message: msg, reason: reason - } + }; it('logs an endpoint error', (done) => { const mockLog = { error: (op, err) => { - assert.equal(op, 'server.EndpointError') - assert.equal(err.message, msg) - assert.equal(err.reason, reason) - done() + assert.equal(op, 'server.EndpointError'); + assert.equal(err.message, msg); + assert.equal(err.reason, reason); + done(); } - } - assert.equal(server._logEndpointErrors(response, mockLog)) - }) + }; + assert.equal(server._logEndpointErrors(response, mockLog)); + }); it('logs an endpoint error with a method', (done) => { response.attempt = { method: 'PUT' - } + }; const mockLog = { error: (op, err) => { - assert.equal(op, 'server.EndpointError') - assert.equal(err.message, msg) - assert.equal(err.reason, reason) - assert.equal(err.method, 'PUT') - done() + assert.equal(op, 'server.EndpointError'); + assert.equal(err.message, msg); + assert.equal(err.reason, reason); + assert.equal(err.method, 'PUT'); + done(); } - } - assert.equal(server._logEndpointErrors(response, mockLog)) - }) - }) + }; + assert.equal(server._logEndpointErrors(response, mockLog)); + }); + }); describe('set up mocks:', () => { - let config, log, locale, routes, Token, oauthdb, translator, response + let config, log, locale, routes, Token, oauthdb, translator, response; beforeEach(() => { - config = getConfig() - locale = 'en' - log = mocks.mockLog() - routes = getRoutes() - Token = require(`${ROOT_DIR}/lib/tokens`)(log, config) - oauthdb = {} + config = getConfig(); + locale = 'en'; + log = mocks.mockLog(); + routes = getRoutes(); + Token = require(`${ROOT_DIR}/lib/tokens`)(log, config); + oauthdb = {}; translator = { getTranslator: sinon.spy(() => ({ en: { format: () => {}, language: 'en' } })), getLocale: sinon.spy(() => locale) - } - }) + }; + }); describe('create:', () => { - let db, instance + let db, instance; beforeEach(() => { db = mocks.mockDB({ devices: [ { id: 'fake device id' } ] - }) + }); return server.create(log, error, config, routes, db, oauthdb, translator, Token).then((s) => { - instance = s - }) - }) + instance = s; + }); + }); describe('server.start:', () => { - beforeEach(() => instance.start()) - afterEach(() => instance.stop()) + beforeEach(() => instance.start()); + afterEach(() => instance.stop()); it('did not call log.begin', () => { - assert.equal(log.begin.callCount, 0) - }) + assert.equal(log.begin.callCount, 0); + }); it('did not call log.summary', () => { - assert.equal(log.summary.callCount, 0) - }) + assert.equal(log.summary.callCount, 0); + }); describe('successful request, authenticated, acceptable locale, signinCodes feature enabled:', () => { - let request + let request; beforeEach(() => { - response = 'ok' + response = 'ok'; return instance.inject({ credentials: { uid: 'fake uid' @@ -124,101 +124,101 @@ describe('lib/server', () => { features: [ 'signinCodes' ] }, remoteAddress: knownIpLocation.ip - }).then(response => request = response.request) - }) + }).then(response => request = response.request); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'server.onRequest') - assert.ok(args[1]) - assert.equal(args[1].path, '/account/create') - assert.equal(args[1].app.locale, 'en') - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'server.onRequest'); + assert.ok(args[1]); + assert.equal(args[1].path, '/account/create'); + assert.equal(args[1].app.locale, 'en'); + }); it('called log.summary correctly', () => { - assert.equal(log.summary.callCount, 1) - const args = log.summary.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], log.begin.args[0][1]) - assert.ok(args[1]) - assert.equal(args[1].isBoom, undefined) - assert.equal(args[1].errno, undefined) - assert.equal(args[1].statusCode, 200) - assert.equal(args[1].source, 'ok') - }) + assert.equal(log.summary.callCount, 1); + const args = log.summary.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], log.begin.args[0][1]); + assert.ok(args[1]); + assert.equal(args[1].isBoom, undefined); + assert.equal(args[1].errno, undefined); + assert.equal(args[1].statusCode, 200); + assert.equal(args[1].source, 'ok'); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('parsed features correctly', () => { - assert.ok(request.app.features) - assert.equal(typeof request.app.features.has, 'function') - assert.equal(request.app.features.has('signinCodes'), true) - }) + assert.ok(request.app.features); + assert.equal(typeof request.app.features.has, 'function'); + assert.equal(request.app.features.has('signinCodes'), true); + }); it('parsed remote address chain correctly', () => { - assert.ok(Array.isArray(request.app.remoteAddressChain)) - assert.equal(request.app.remoteAddressChain.length, 3) - assert.equal(request.app.remoteAddressChain[0], knownIpLocation.ip) - assert.equal(request.app.remoteAddressChain[1], '1.2.3.4') - assert.equal(request.app.remoteAddressChain[2], request.app.remoteAddressChain[0]) - }) + assert.ok(Array.isArray(request.app.remoteAddressChain)); + assert.equal(request.app.remoteAddressChain.length, 3); + assert.equal(request.app.remoteAddressChain[0], knownIpLocation.ip); + assert.equal(request.app.remoteAddressChain[1], '1.2.3.4'); + assert.equal(request.app.remoteAddressChain[2], request.app.remoteAddressChain[0]); + }); it('parsed client address correctly', () => { - assert.equal(request.app.clientAddress, knownIpLocation.ip) - }) + assert.equal(request.app.clientAddress, knownIpLocation.ip); + }); it('parsed accept-language correctly', () => { - assert.equal(request.app.acceptLanguage, 'fr-CH, fr;q=0.9, en-GB, en;q=0.5') - }) + assert.equal(request.app.acceptLanguage, 'fr-CH, fr;q=0.9, en-GB, en;q=0.5'); + }); it('parsed locale correctly', () => { - assert.equal(translator.getLocale.callCount, 0) - assert.equal(request.app.locale, 'en') - assert.equal(translator.getLocale.callCount, 1) - }) + assert.equal(translator.getLocale.callCount, 0); + assert.equal(request.app.locale, 'en'); + assert.equal(translator.getLocale.callCount, 1); + }); it('parsed user agent correctly', () => { - assert.ok(request.app.ua) - assert.equal(request.app.ua.browser, 'Firefox') - assert.equal(request.app.ua.browserVersion, '57.0') - assert.equal(request.app.ua.os, 'Mac OS X') - assert.equal(request.app.ua.osVersion, '10.11') - assert.equal(request.app.ua.deviceType, null) - assert.equal(request.app.ua.formFactor, null) - }) + assert.ok(request.app.ua); + assert.equal(request.app.ua.browser, 'Firefox'); + assert.equal(request.app.ua.browserVersion, '57.0'); + assert.equal(request.app.ua.os, 'Mac OS X'); + assert.equal(request.app.ua.osVersion, '10.11'); + assert.equal(request.app.ua.deviceType, null); + assert.equal(request.app.ua.formFactor, null); + }); it('parsed location correctly', () => { - const geo = request.app.geo - assert.ok(geo) - assert.ok(knownIpLocation.location.city.has(geo.location.city)) - assert.equal(geo.location.country, knownIpLocation.location.country) - assert.equal(geo.location.countryCode, knownIpLocation.location.countryCode) - assert.equal(geo.location.state, knownIpLocation.location.state) - assert.equal(geo.location.stateCode, knownIpLocation.location.stateCode) - assert.equal(geo.timeZone, knownIpLocation.location.tz) - }) + const geo = request.app.geo; + assert.ok(geo); + assert.ok(knownIpLocation.location.city.has(geo.location.city)); + assert.equal(geo.location.country, knownIpLocation.location.country); + assert.equal(geo.location.countryCode, knownIpLocation.location.countryCode); + assert.equal(geo.location.state, knownIpLocation.location.state); + assert.equal(geo.location.stateCode, knownIpLocation.location.stateCode); + assert.equal(geo.timeZone, knownIpLocation.location.tz); + }); it('fetched devices correctly', () => { - assert.ok(request.app.devices) - assert.equal(typeof request.app.devices.then, 'function') - assert.equal(db.devices.callCount, 1) - assert.equal(db.devices.args[0].length, 1) - assert.equal(db.devices.args[0][0], 'fake uid') + assert.ok(request.app.devices); + assert.equal(typeof request.app.devices.then, 'function'); + assert.equal(db.devices.callCount, 1); + assert.equal(db.devices.args[0].length, 1); + assert.equal(db.devices.args[0][0], 'fake uid'); return request.app.devices.then(devices => { - assert.deepEqual(devices, [ { id: 'fake device id' } ]) - }) - }) + assert.deepEqual(devices, [ { id: 'fake device id' } ]); + }); + }); describe('successful request, unauthenticated, uid in payload:', () => { - let secondRequest + let secondRequest; beforeEach(() => { - response = 'ok' - locale = 'fr' + response = 'ok'; + locale = 'fr'; return instance.inject({ headers: { 'accept-language': 'fr-CH, fr;q=0.9, en-GB, en;q=0.5', @@ -232,70 +232,70 @@ describe('lib/server', () => { uid: 'another fake uid' }, remoteAddress: knownIpLocation.ip - }).then(response => secondRequest = response.request) - }) + }).then(response => secondRequest = response.request); + }); it('second request has its own remote address chain', () => { - assert.notEqual(request, secondRequest) - assert.notEqual(request.app.remoteAddressChain, secondRequest.app.remoteAddressChain) - assert.equal(secondRequest.app.remoteAddressChain.length, 3) - assert.equal(secondRequest.app.remoteAddressChain[0], '194.12.187.0') - assert.equal(secondRequest.app.remoteAddressChain[1], '194.12.187.1') - assert.equal(secondRequest.app.remoteAddressChain[2], knownIpLocation.ip) - }) + assert.notEqual(request, secondRequest); + assert.notEqual(request.app.remoteAddressChain, secondRequest.app.remoteAddressChain); + assert.equal(secondRequest.app.remoteAddressChain.length, 3); + assert.equal(secondRequest.app.remoteAddressChain[0], '194.12.187.0'); + assert.equal(secondRequest.app.remoteAddressChain[1], '194.12.187.1'); + assert.equal(secondRequest.app.remoteAddressChain[2], knownIpLocation.ip); + }); it('second request has correct client address', () => { - assert.equal(secondRequest.app.clientAddress, knownIpLocation.ip) - }) + assert.equal(secondRequest.app.clientAddress, knownIpLocation.ip); + }); it('second request has its own accept-language', () => { - assert.equal(secondRequest.app.acceptLanguage, 'fr-CH, fr;q=0.9, en-GB, en;q=0.5') - }) + assert.equal(secondRequest.app.acceptLanguage, 'fr-CH, fr;q=0.9, en-GB, en;q=0.5'); + }); it('second request has its own locale', () => { - assert.equal(translator.getLocale.callCount, 0) - assert.equal(secondRequest.app.locale, 'fr') - assert.equal(translator.getLocale.callCount, 1) - }) + assert.equal(translator.getLocale.callCount, 0); + assert.equal(secondRequest.app.locale, 'fr'); + assert.equal(translator.getLocale.callCount, 1); + }); it('second request has its own user agent info', () => { - assert.notEqual(request.app.ua, secondRequest.app.ua) - assert.equal(secondRequest.app.ua.browser, 'Nightly') - assert.equal(secondRequest.app.ua.browserVersion, '34.0a1') - assert.equal(secondRequest.app.ua.os, 'Android') - assert.equal(secondRequest.app.ua.osVersion, null) - assert.equal(secondRequest.app.ua.deviceType, 'mobile') - assert.equal(secondRequest.app.ua.formFactor, null) - }) + assert.notEqual(request.app.ua, secondRequest.app.ua); + assert.equal(secondRequest.app.ua.browser, 'Nightly'); + assert.equal(secondRequest.app.ua.browserVersion, '34.0a1'); + assert.equal(secondRequest.app.ua.os, 'Android'); + assert.equal(secondRequest.app.ua.osVersion, null); + assert.equal(secondRequest.app.ua.deviceType, 'mobile'); + assert.equal(secondRequest.app.ua.formFactor, null); + }); it('second request has its own location info', () => { - const geo = secondRequest.app.geo - assert.notEqual(request.app.geo, secondRequest.app.geo) - assert.ok(knownIpLocation.location.city.has(geo.location.city)) - assert.equal(geo.location.country, knownIpLocation.location.country) - assert.equal(geo.location.countryCode, knownIpLocation.location.countryCode) - assert.equal(geo.location.state, knownIpLocation.location.state) - assert.equal(geo.location.stateCode, knownIpLocation.location.stateCode) - assert.equal(geo.timeZone, knownIpLocation.location.tz) - }) + const geo = secondRequest.app.geo; + assert.notEqual(request.app.geo, secondRequest.app.geo); + assert.ok(knownIpLocation.location.city.has(geo.location.city)); + assert.equal(geo.location.country, knownIpLocation.location.country); + assert.equal(geo.location.countryCode, knownIpLocation.location.countryCode); + assert.equal(geo.location.state, knownIpLocation.location.state); + assert.equal(geo.location.stateCode, knownIpLocation.location.stateCode); + assert.equal(geo.timeZone, knownIpLocation.location.tz); + }); it('second request fetched devices correctly', () => { - assert.notEqual(request.app.devices, secondRequest.app.devices) - assert.equal(db.devices.callCount, 2) - assert.equal(db.devices.args[1].length, 1) - assert.equal(db.devices.args[1][0], 'another fake uid') + assert.notEqual(request.app.devices, secondRequest.app.devices); + assert.equal(db.devices.callCount, 2); + assert.equal(db.devices.args[1].length, 1); + assert.equal(db.devices.args[1][0], 'another fake uid'); return request.app.devices.then(devices => { - assert.deepEqual(devices, [ { id: 'fake device id' } ]) - }) - }) - }) - }) + assert.deepEqual(devices, [ { id: 'fake device id' } ]); + }); + }); + }); + }); describe('successful request, unacceptable locale, no features enabled:', () => { - let request + let request; beforeEach(() => { - response = 'ok' + response = 'ok'; return instance.inject({ headers: { 'accept-language': 'fr-CH, fr;q=0.9', @@ -305,147 +305,147 @@ describe('lib/server', () => { url: '/account/create', payload: {}, remoteAddress: 'this is not an ip address' - }).then(response => request = response.request) - }) + }).then(response => request = response.request); + }); it('called log.begin correctly', () => { - assert.equal(log.begin.callCount, 1) - const args = log.begin.args[0] - assert.equal(args[1].app.locale, 'en') - assert.equal(args[1].app.ua.browser, 'Chrome Mobile iOS') - assert.equal(args[1].app.ua.browserVersion, '56.0.2924') - assert.equal(args[1].app.ua.os, 'iOS') - assert.equal(args[1].app.ua.osVersion, '10.3') - assert.equal(args[1].app.ua.deviceType, 'mobile') - assert.equal(args[1].app.ua.formFactor, 'iPhone') - }) + assert.equal(log.begin.callCount, 1); + const args = log.begin.args[0]; + assert.equal(args[1].app.locale, 'en'); + assert.equal(args[1].app.ua.browser, 'Chrome Mobile iOS'); + assert.equal(args[1].app.ua.browserVersion, '56.0.2924'); + assert.equal(args[1].app.ua.os, 'iOS'); + assert.equal(args[1].app.ua.osVersion, '10.3'); + assert.equal(args[1].app.ua.deviceType, 'mobile'); + assert.equal(args[1].app.ua.formFactor, 'iPhone'); + }); it('called log.summary once', () => { - assert.equal(log.summary.callCount, 1) - }) + assert.equal(log.summary.callCount, 1); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) + assert.equal(log.error.callCount, 0); + }); it('parsed features correctly', () => { - assert.ok(request.app.features) - assert.equal(request.app.features.has('signinCodes'), false) - }) + assert.ok(request.app.features); + assert.equal(request.app.features.has('signinCodes'), false); + }); it('ignored invalid remoteAddress', () => { - assert.equal(request.app.clientAddress, undefined) - }) - }) + assert.equal(request.app.clientAddress, undefined); + }); + }); describe('unsuccessful request:', () => { beforeEach(() => { - response = error.requestBlocked() + response = error.requestBlocked(); return instance.inject({ method: 'POST', url: '/account/create', payload: {} - }).catch(() => {}) - }) + }).catch(() => {}); + }); it('called log.begin', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called log.summary correctly', () => { - assert.equal(log.summary.callCount, 1) - const args = log.summary.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], log.begin.args[0][1]) - assert.ok(args[1]) - assert.equal(args[1].statusCode, undefined) - assert.equal(args[1].source, undefined) - assert.equal(args[1].isBoom, true) - assert.equal(args[1].message, 'The request was blocked for security reasons') - assert.equal(args[1].errno, 125) - }) + assert.equal(log.summary.callCount, 1); + const args = log.summary.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], log.begin.args[0][1]); + assert.ok(args[1]); + assert.equal(args[1].statusCode, undefined); + assert.equal(args[1].source, undefined); + assert.equal(args[1].isBoom, true); + assert.equal(args[1].message, 'The request was blocked for security reasons'); + assert.equal(args[1].errno, 125); + }); it('did not call log.error', () => { - assert.equal(log.error.callCount, 0) - }) - }) + assert.equal(log.error.callCount, 0); + }); + }); describe('unsuccessful request, db error:', () => { beforeEach(() => { - response = new EndpointError('request failed', { reason: 'because i said so' }) + response = new EndpointError('request failed', { reason: 'because i said so' }); return instance.inject({ method: 'POST', url: '/account/create', payload: {} - }).catch(() => {}) - }) + }).catch(() => {}); + }); it('called log.begin', () => { - assert.equal(log.begin.callCount, 1) - }) + assert.equal(log.begin.callCount, 1); + }); it('called log.summary', () => { - assert.equal(log.summary.callCount, 1) - }) + assert.equal(log.summary.callCount, 1); + }); it('called log.error correctly', () => { - assert.isAtLeast(log.error.callCount, 1) - const args = log.error.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'server.EndpointError') + assert.isAtLeast(log.error.callCount, 1); + const args = log.error.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'server.EndpointError'); assert.deepEqual(args[1], { message: 'request failed', reason: 'because i said so' - }) - }) - }) + }); + }); + }); describe('authenticated request, session token not expired:', () => { beforeEach(() => { - response = 'ok' + response = 'ok'; const auth = hawk.client.header(`${config.publicUrl}account/status`, 'GET', { credentials: { id: 'deadbeef', key: 'baadf00d', algorithm: 'sha256' } - }) + }); return instance.inject({ headers: { authorization: auth.header }, method: 'GET', url: '/account/status' - }) - }) + }); + }); it('called db.sessionToken correctly', () => { - assert.equal(db.sessionToken.callCount, 1) - const args = db.sessionToken.args[0] - assert.equal(args.length, 1) - assert.equal(args[0], 'deadbeef') - }) + assert.equal(db.sessionToken.callCount, 1); + const args = db.sessionToken.args[0]; + assert.equal(args.length, 1); + assert.equal(args[0], 'deadbeef'); + }); it('did not call db.pruneSessionTokens', () => { - assert.equal(db.pruneSessionTokens.callCount, 0) - }) - }) - }) - }) + assert.equal(db.pruneSessionTokens.callCount, 0); + }); + }); + }); + }); describe('authenticated request, session token expired:', () => { - let db, instance + let db, instance; beforeEach(() => { - response = 'ok' + response = 'ok'; db = mocks.mockDB({ sessionTokenId: 'wibble', uid: 'blee', expired: true - }) + }); return server.create(log, error, config, routes, db, oauthdb, translator, Token).then((s) => { - instance = s + instance = s; return instance.start() .then(() => { const auth = hawk.client.header(`${config.publicUrl}account/status`, 'GET', { @@ -454,34 +454,34 @@ describe('lib/server', () => { key: 'baadf00d', algorithm: 'sha256' } - }) + }); return instance.inject({ headers: { authorization: auth.header }, method: 'GET', url: '/account/status' - }) - }) - }) - }) + }); + }); + }); + }); - afterEach(() => instance.stop()) + afterEach(() => instance.stop()); it('called db.sessionToken', () => { - assert.equal(db.sessionToken.callCount, 1) - }) + assert.equal(db.sessionToken.callCount, 1); + }); it('called db.pruneSessionTokens correctly', () => { - assert.equal(db.pruneSessionTokens.callCount, 1) - const args = db.pruneSessionTokens.args[0] - assert.equal(args.length, 2) - assert.equal(args[0], 'blee') - assert.ok(Array.isArray(args[1])) - assert.equal(args[1].length, 1) - assert.equal(args[1][0].id, 'wibble') - }) - }) + assert.equal(db.pruneSessionTokens.callCount, 1); + const args = db.pruneSessionTokens.args[0]; + assert.equal(args.length, 2); + assert.equal(args[0], 'blee'); + assert.ok(Array.isArray(args[1])); + assert.equal(args[1].length, 1); + assert.equal(args[1][0].id, 'wibble'); + }); + }); function getRoutes () { return [ @@ -489,7 +489,7 @@ describe('lib/server', () => { path: '/account/create', method: 'POST', handler (request) { - return response + return response; } }, { @@ -502,13 +502,13 @@ describe('lib/server', () => { } }, handler (request) { - return response + return response; } } - ] + ]; } - }) -}) + }); +}); function getConfig () { return { @@ -534,5 +534,5 @@ function getConfig () { flow_id_expiry: 7200000, flow_id_key: 'wibble' } - } + }; } diff --git a/test/local/time.js b/test/local/time.js index 24d215f8..241d524a 100644 --- a/test/local/time.js +++ b/test/local/time.js @@ -2,67 +2,67 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); describe('require:', () => { - let time + let time; beforeEach(() => { - time = require('../../lib/time') - }) + time = require('../../lib/time'); + }); it('returned the expected interface', () => { - assert.equal(typeof time.startOfMinute, 'function') - assert.equal(time.startOfMinute.length, 1) - }) + assert.equal(typeof time.startOfMinute, 'function'); + assert.equal(time.startOfMinute.length, 1); + }); describe('start of minute:', () => { - let date + let date; beforeEach(() => { - date = new Date('2018-10-10T10:10Z') - }) + date = new Date('2018-10-10T10:10Z'); + }); it('returns the correct time', () => { - assert.equal(time.startOfMinute(date), '2018-10-10T10:10:00Z') - }) - }) + assert.equal(time.startOfMinute(date), '2018-10-10T10:10:00Z'); + }); + }); describe('end of minute:', () => { - let date + let date; beforeEach(() => { - date = new Date('2018-10-10T10:10:59.999Z') - }) + date = new Date('2018-10-10T10:10:59.999Z'); + }); it('returns the correct time', () => { - assert.equal(time.startOfMinute(date), '2018-10-10T10:10:00Z') - }) - }) + assert.equal(time.startOfMinute(date), '2018-10-10T10:10:00Z'); + }); + }); describe('with padding:', () => { - let date + let date; beforeEach(() => { - date = new Date('2018-09-09T09:09Z') - }) + date = new Date('2018-09-09T09:09Z'); + }); it('returns the correct time', () => { - assert.equal(time.startOfMinute(date), '2018-09-09T09:09:00Z') - }) - }) + assert.equal(time.startOfMinute(date), '2018-09-09T09:09:00Z'); + }); + }); describe('non-UTC timezone:', () => { - let date + let date; beforeEach(() => { - date = new Date('2018-01-01T00:00+01:00') - }) + date = new Date('2018-01-01T00:00+01:00'); + }); it('returns the correct time', () => { - assert.equal(time.startOfMinute(date), '2017-12-31T23:00:00Z') - }) - }) -}) + assert.equal(time.startOfMinute(date), '2017-12-31T23:00:00Z'); + }); + }); +}); diff --git a/test/local/tokens/account_reset_token.js b/test/local/tokens/account_reset_token.js index b25f7d45..7e2d602d 100644 --- a/test/local/tokens/account_reset_token.js +++ b/test/local/tokens/account_reset_token.js @@ -2,28 +2,28 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') +const { assert } = require('chai'); -const log = { trace() {} } -const tokens = require('../../../lib/tokens/index')(log) -const AccountResetToken = tokens.AccountResetToken +const log = { trace() {} }; +const tokens = require('../../../lib/tokens/index')(log); +const AccountResetToken = tokens.AccountResetToken; const ACCOUNT = { uid: 'xxx' -} +}; describe('account reset tokens', () => { it( 'should re-create from tokenData', () => { - let token = null + let token = null; return AccountResetToken.create(ACCOUNT) .then( (x) => { - token = x + token = x; } ) .then( @@ -31,31 +31,31 @@ describe('account reset tokens', () => { ) .then( (token2) => { - assert.deepEqual(token.data, token2.data) - assert.deepEqual(token.id, token2.id) - assert.deepEqual(token.authKey, token2.authKey) - assert.deepEqual(token.bundleKey, token2.bundleKey) - assert.deepEqual(token.uid, token2.uid) + assert.deepEqual(token.data, token2.data); + assert.deepEqual(token.id, token2.id); + assert.deepEqual(token.authKey, token2.authKey); + assert.deepEqual(token.bundleKey, token2.bundleKey); + assert.deepEqual(token.uid, token2.uid); } - ) + ); } - ) + ); it( 'should have test-vector compliant key derivations', () => { - let token = null - const tokenData = 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + let token = null; + const tokenData = 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'; return AccountResetToken.fromHex(tokenData, ACCOUNT) .then( (x) => { - token = x - assert.equal(token.data.toString('hex'), tokenData) - assert.equal(token.id, '46ec557e56e531a058620e9344ca9c75afac0d0bcbdd6f8c3c2f36055d9540cf') - assert.equal(token.authKey.toString('hex'), '716ebc28f5122ef48670a48209190a1605263c3188dfe45256265929d1c45e48') - assert.equal(token.bundleKey.toString('hex'), 'aa5906d2318c6e54ecebfa52f10df4c036165c230cc78ee859f546c66ea3c126') + token = x; + assert.equal(token.data.toString('hex'), tokenData); + assert.equal(token.id, '46ec557e56e531a058620e9344ca9c75afac0d0bcbdd6f8c3c2f36055d9540cf'); + assert.equal(token.authKey.toString('hex'), '716ebc28f5122ef48670a48209190a1605263c3188dfe45256265929d1c45e48'); + assert.equal(token.bundleKey.toString('hex'), 'aa5906d2318c6e54ecebfa52f10df4c036165c230cc78ee859f546c66ea3c126'); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/tokens/forgot_password_token.js b/test/local/tokens/forgot_password_token.js index bb1c5ac3..84ada601 100644 --- a/test/local/tokens/forgot_password_token.js +++ b/test/local/tokens/forgot_password_token.js @@ -2,49 +2,49 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const log = { trace() {} } +const { assert } = require('chai'); +const log = { trace() {} }; -const timestamp = Date.now() +const timestamp = Date.now(); -const PasswordForgotToken = require('../../../lib/tokens')(log).PasswordForgotToken +const PasswordForgotToken = require('../../../lib/tokens')(log).PasswordForgotToken; const ACCOUNT = { uid: 'xxx', email: Buffer.from('test@example.com').toString('hex') -} +}; describe('PasswordForgotToken', () => { it( 'can re-create from tokenData', () => { - var token = null + var token = null; return PasswordForgotToken.create(ACCOUNT) .then( function (x) { - token = x + token = x; } ) .then( function () { - return PasswordForgotToken.fromHex(token.data, ACCOUNT) + return PasswordForgotToken.fromHex(token.data, ACCOUNT); } ) .then( function (token2) { - assert.deepEqual(token.data, token2.data) - assert.deepEqual(token.id, token2.id) - assert.deepEqual(token.authKey, token2.authKey) - assert.deepEqual(token.bundleKey, token2.bundleKey) - assert.deepEqual(token.uid, token2.uid) - assert.deepEqual(token.email, token2.email) + assert.deepEqual(token.data, token2.data); + assert.deepEqual(token.id, token2.id); + assert.deepEqual(token.authKey, token2.authKey); + assert.deepEqual(token.bundleKey, token2.bundleKey); + assert.deepEqual(token.uid, token2.uid); + assert.deepEqual(token.email, token2.email); } - ) + ); } - ) + ); it( @@ -53,14 +53,14 @@ describe('PasswordForgotToken', () => { return PasswordForgotToken.create(ACCOUNT) .then( function (token) { - token.createdAt = timestamp - assert.equal(token.ttl(timestamp), 900) - assert.equal(token.ttl(timestamp + 1000), 899) - assert.equal(token.ttl(timestamp + 2000), 898) + token.createdAt = timestamp; + assert.equal(token.ttl(timestamp), 900); + assert.equal(token.ttl(timestamp + 1000), 899); + assert.equal(token.ttl(timestamp + 2000), 898); } - ) + ); } - ) + ); it( @@ -69,14 +69,14 @@ describe('PasswordForgotToken', () => { return PasswordForgotToken.create(ACCOUNT) .then( function (x) { - assert.equal(x.tries, 3) - assert.equal(x.failAttempt(), false) - assert.equal(x.tries, 2) - assert.equal(x.failAttempt(), false) - assert.equal(x.tries, 1) - assert.equal(x.failAttempt(), true) + assert.equal(x.tries, 3); + assert.equal(x.failAttempt(), false); + assert.equal(x.tries, 2); + assert.equal(x.failAttempt(), false); + assert.equal(x.tries, 1); + assert.equal(x.failAttempt(), true); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/tokens/key_fetch_token.js b/test/local/tokens/key_fetch_token.js index fe184ff6..80360a63 100644 --- a/test/local/tokens/key_fetch_token.js +++ b/test/local/tokens/key_fetch_token.js @@ -2,161 +2,161 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const log = { trace () {}, error () {} } +const { assert } = require('chai'); +const crypto = require('crypto'); +const log = { trace () {}, error () {} }; -const tokens = require('../../../lib/tokens/index')(log) -const KeyFetchToken = tokens.KeyFetchToken +const tokens = require('../../../lib/tokens/index')(log); +const KeyFetchToken = tokens.KeyFetchToken; const ACCOUNT = { uid: 'xxx', kA: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'), wrapKb: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'), emailVerified: true -} +}; describe('KeyFetchToken', () => { it( 'should re-create from tokenData', () => { - var token = null + var token = null; return KeyFetchToken.create(ACCOUNT) .then( function (x) { - token = x + token = x; } ) .then( function () { - return KeyFetchToken.fromHex(token.data, ACCOUNT) + return KeyFetchToken.fromHex(token.data, ACCOUNT); } ) .then( function (token2) { - assert.deepEqual(token.data, token2.data) - assert.deepEqual(token.id, token2.id) - assert.deepEqual(token.authKey, token2.authKey) - assert.deepEqual(token.bundleKey, token2.bundleKey) - assert.deepEqual(token.uid, token2.uid) - assert.deepEqual(token.kA, token2.kA) - assert.deepEqual(token.wrapKb, token2.wrapKb) - assert.equal(token.emailVerified, token2.emailVerified) + assert.deepEqual(token.data, token2.data); + assert.deepEqual(token.id, token2.id); + assert.deepEqual(token.authKey, token2.authKey); + assert.deepEqual(token.bundleKey, token2.bundleKey); + assert.deepEqual(token.uid, token2.uid); + assert.deepEqual(token.kA, token2.kA); + assert.deepEqual(token.wrapKb, token2.wrapKb); + assert.equal(token.emailVerified, token2.emailVerified); } - ) + ); } - ) + ); it( 'should re-create from id', () => { - let token = null + let token = null; return KeyFetchToken.create(ACCOUNT) .then( function (x) { - token = x - return KeyFetchToken.fromId(token.id, token) + token = x; + return KeyFetchToken.fromId(token.id, token); } ) .then( function (x) { - assert.equal(x.id, token.id, 'should have same id') - assert.equal(x.authKey, token.authKey, 'should have same authKey') + assert.equal(x.id, token.id, 'should have same id'); + assert.equal(x.authKey, token.authKey, 'should have same authKey'); } - ) + ); } - ) + ); it( 'should bundle / unbundle of keys', () => { - let token = null - const kA = crypto.randomBytes(32).toString('hex') - const wrapKb = crypto.randomBytes(32).toString('hex') + let token = null; + const kA = crypto.randomBytes(32).toString('hex'); + const wrapKb = crypto.randomBytes(32).toString('hex'); return KeyFetchToken.create(ACCOUNT) .then( function (x) { - token = x - return x.bundleKeys(kA, wrapKb) + token = x; + return x.bundleKeys(kA, wrapKb); } ) .then( function (b) { - return token.unbundleKeys(b) + return token.unbundleKeys(b); } ) .then( function (ub) { - assert.deepEqual(ub.kA, kA) - assert.deepEqual(ub.wrapKb, wrapKb) + assert.deepEqual(ub.kA, kA); + assert.deepEqual(ub.wrapKb, wrapKb); } - ) + ); } - ) + ); it( 'should only bundle / unbundle of keys with correct token', () => { - let token1 = null - let token2 = null - const kA = crypto.randomBytes(32).toString('hex') - const wrapKb = crypto.randomBytes(32).toString('hex') + let token1 = null; + let token2 = null; + const kA = crypto.randomBytes(32).toString('hex'); + const wrapKb = crypto.randomBytes(32).toString('hex'); return KeyFetchToken.create(ACCOUNT) .then( function (x) { - token1 = x - return KeyFetchToken.create(ACCOUNT) + token1 = x; + return KeyFetchToken.create(ACCOUNT); } ) .then( function (x) { - token2 = x - return token1.bundleKeys(kA, wrapKb) + token2 = x; + return token1.bundleKeys(kA, wrapKb); } ) .then( function (b) { - return token2.unbundleKeys(b) + return token2.unbundleKeys(b); } ) .then( function (ub) { - assert(false, 'was able to unbundle using wrong token') + assert(false, 'was able to unbundle using wrong token'); }, function (err) { - assert.equal(err.errno, 109, 'expected an invalidSignature error') + assert.equal(err.errno, 109, 'expected an invalidSignature error'); } - ) + ); } - ) + ); it( 'should have key derivations that are test-vector compliant', () => { - let token = null - const tokenData = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f' + let token = null; + const tokenData = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'; return KeyFetchToken.fromHex(tokenData, ACCOUNT) .then( function (x) { - token = x - assert.equal(token.data, tokenData) - assert.equal(token.id, '3d0a7c02a15a62a2882f76e39b6494b500c022a8816e048625a495718998ba60') - assert.equal(token.authKey, '87b8937f61d38d0e29cd2d5600b3f4da0aa48ac41de36a0efe84bb4a9872ceb7') - assert.equal(token.bundleKey, '14f338a9e8c6324d9e102d4e6ee83b209796d5c74bb734a410e729e014a4a546') + token = x; + assert.equal(token.data, tokenData); + assert.equal(token.id, '3d0a7c02a15a62a2882f76e39b6494b500c022a8816e048625a495718998ba60'); + assert.equal(token.authKey, '87b8937f61d38d0e29cd2d5600b3f4da0aa48ac41de36a0efe84bb4a9872ceb7'); + assert.equal(token.bundleKey, '14f338a9e8c6324d9e102d4e6ee83b209796d5c74bb734a410e729e014a4a546'); } ) .then( function () { - const kA = Buffer.from('202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f', 'hex') - const wrapKb = Buffer.from('404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f', 'hex') - return token.bundleKeys(kA, wrapKb) + const kA = Buffer.from('202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f', 'hex'); + const wrapKb = Buffer.from('404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f', 'hex'); + return token.bundleKeys(kA, wrapKb); } ) .then( @@ -165,9 +165,9 @@ describe('KeyFetchToken', () => { 'ee5c58845c7c9412b11bbd20920c2fddd83c33c9cd2c2de2' + 'd66b222613364636c2c0f8cfbb7c630472c0bd88451342c6' + 'c05b14ce342c5ad46ad89e84464c993c3927d30230157d08' + - '17a077eef4b20d976f7a97363faf3f064c003ada7d01aa70') + '17a077eef4b20d976f7a97363faf3f064c003ada7d01aa70'); } - ) + ); } - ) -}) + ); +}); diff --git a/test/local/tokens/session_token.js b/test/local/tokens/session_token.js index 89eb04d9..f48dd5ff 100644 --- a/test/local/tokens/session_token.js +++ b/test/local/tokens/session_token.js @@ -2,16 +2,16 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') +const { assert } = require('chai'); +const sinon = require('sinon'); const log = { trace () {}, info () {}, error: sinon.spy() -} -const crypto = require('crypto') +}; +const crypto = require('crypto'); const TOKEN = { createdAt: Date.now(), @@ -22,78 +22,78 @@ const TOKEN = { tokenVerificationId: crypto.randomBytes(16), verificationMethod: 2, // Totp verification method verifiedAt: Date.now() -} +}; describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { - const MAX_AGE_WITHOUT_DEVICE = 1000 * 60 * 60 * 24 * 7 * 4 + const MAX_AGE_WITHOUT_DEVICE = 1000 * 60 * 60 * 24 * 7 * 4; const config = { lastAccessTimeUpdates: {}, tokenLifetimes: { sessionTokenWithoutDevice: MAX_AGE_WITHOUT_DEVICE } - } - const tokens = require('../../../lib/tokens/index')(log, config) - const SessionToken = tokens.SessionToken + }; + const tokens = require('../../../lib/tokens/index')(log, config); + const SessionToken = tokens.SessionToken; it( 'interface is correct', () => { return SessionToken.create(TOKEN) .then(token => { - assert.equal(typeof token.lastAuthAt, 'function', 'lastAuthAt method is defined') - assert.equal(typeof token.setUserAgentInfo, 'function', 'setUserAgentInfo method is defined') - assert.equal(Object.getOwnPropertyDescriptor(token, 'state'), undefined, 'state property is undefined') + assert.equal(typeof token.lastAuthAt, 'function', 'lastAuthAt method is defined'); + assert.equal(typeof token.setUserAgentInfo, 'function', 'setUserAgentInfo method is defined'); + assert.equal(Object.getOwnPropertyDescriptor(token, 'state'), undefined, 'state property is undefined'); assert.equal( typeof Object.getOwnPropertyDescriptor(Object.getPrototypeOf(token), 'state').get, 'function', 'state is a getter' - ) - assert.notEqual(token.createdAt, TOKEN.createdAt, 'createdAt values are completely ignored') - }) + ); + assert.notEqual(token.createdAt, TOKEN.createdAt, 'createdAt values are completely ignored'); + }); } - ) + ); it( 're-creation from tokenData works', () => { - var token = null + var token = null; return SessionToken.create(TOKEN) .then( function (x) { - token = x + token = x; } ) .then( function () { - return SessionToken.fromHex(token.data, token) + return SessionToken.fromHex(token.data, token); } ) .then( function (token2) { - assert.deepEqual(token.data, token2.data) - assert.deepEqual(token.id, token2.id) - assert.deepEqual(token.authKey, token2.authKey) - assert.deepEqual(token.bundleKey, token2.bundleKey) - assert.equal(typeof token.authKey, 'string') - assert(Buffer.isBuffer(token.key)) - assert.equal(token.key.toString('hex'), token.authKey) - assert.deepEqual(token.uid, token2.uid) - assert.equal(token.email, token2.email) - assert.equal(token.emailCode, token2.emailCode) - assert.equal(token.emailVerified, token2.emailVerified) - assert.equal(token.createdAt, token2.createdAt) - assert.equal(token.tokenVerified, token2.tokenVerified) - assert.equal(token.tokenVerificationId, token2.tokenVerificationId) - assert.equal(token.state, token2.state) - assert.equal(token.verificationMethod, token2.verificationMethod) - assert.equal(token.verificationMethodValue, 'totp-2fa') - assert.equal(token.verifiedAt, token2.verifiedAt) - assert.deepEqual(token.authenticationMethods, token2.authenticationMethods) - assert.deepEqual(token.authenticatorAssuranceLevel, token2.authenticatorAssuranceLevel) + assert.deepEqual(token.data, token2.data); + assert.deepEqual(token.id, token2.id); + assert.deepEqual(token.authKey, token2.authKey); + assert.deepEqual(token.bundleKey, token2.bundleKey); + assert.equal(typeof token.authKey, 'string'); + assert(Buffer.isBuffer(token.key)); + assert.equal(token.key.toString('hex'), token.authKey); + assert.deepEqual(token.uid, token2.uid); + assert.equal(token.email, token2.email); + assert.equal(token.emailCode, token2.emailCode); + assert.equal(token.emailVerified, token2.emailVerified); + assert.equal(token.createdAt, token2.createdAt); + assert.equal(token.tokenVerified, token2.tokenVerified); + assert.equal(token.tokenVerificationId, token2.tokenVerificationId); + assert.equal(token.state, token2.state); + assert.equal(token.verificationMethod, token2.verificationMethod); + assert.equal(token.verificationMethodValue, 'totp-2fa'); + assert.equal(token.verifiedAt, token2.verifiedAt); + assert.deepEqual(token.authenticationMethods, token2.authenticationMethods); + assert.deepEqual(token.authenticatorAssuranceLevel, token2.authenticatorAssuranceLevel); } - ) + ); } - ) + ); it('SessionToken.fromHex creates expired token if deviceId is null and createdAt is too old', () => { return SessionToken.create(TOKEN) @@ -102,10 +102,10 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { deviceId: null })) .then(token => { - assert.equal(token.ttl(), 0) - assert.equal(token.expired(), true) - }) - }) + assert.equal(token.ttl(), 0); + assert.equal(token.expired(), true); + }); + }); it('SessionToken.fromHex creates non-expired token if deviceId is null and createdAt is recent enough', () => { return SessionToken.create(TOKEN) @@ -114,10 +114,10 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { deviceId: null })) .then(token => { - assert.equal(token.ttl() > 0, true) - assert.equal(token.expired(), false) - }) - }) + assert.equal(token.ttl() > 0, true); + assert.equal(token.expired(), false); + }); + }); it('SessionToken.fromHex creates non-expired token if deviceId is set and createdAt is too old', () => { return SessionToken.create(TOKEN) @@ -126,10 +126,10 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { deviceId: crypto.randomBytes(16) })) .then(token => { - assert.equal(token.ttl() > 0, true) - assert.equal(token.expired(), false) - }) - }) + assert.equal(token.ttl() > 0, true); + assert.equal(token.expired(), false); + }); + }); it( 'create with NaN createdAt', @@ -140,29 +140,29 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { uid: 'bar' }).then( function (token) { - var now = Date.now() - assert.ok(token.createdAt > now - 1000 && token.createdAt <= now) + var now = Date.now(); + assert.ok(token.createdAt > now - 1000 && token.createdAt <= now); } - ) + ); } - ) + ); it( 'sessionToken key derivations are test-vector compliant', () => { - var token = null - var tokenData = 'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + var token = null; + var tokenData = 'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf'; return SessionToken.fromHex(tokenData, TOKEN) .then( function (x) { - token = x - assert.equal(token.data.toString('hex'), tokenData) - assert.equal(token.id, 'c0a29dcf46174973da1378696e4c82ae10f723cf4f4d9f75e39f4ae3851595ab') - assert.equal(token.authKey.toString('hex'), '9d8f22998ee7f5798b887042466b72d53e56ab0c094388bf65831f702d2febc0') + token = x; + assert.equal(token.data.toString('hex'), tokenData); + assert.equal(token.id, 'c0a29dcf46174973da1378696e4c82ae10f723cf4f4d9f75e39f4ae3851595ab'); + assert.equal(token.authKey.toString('hex'), '9d8f22998ee7f5798b887042466b72d53e56ab0c094388bf65831f702d2febc0'); } - ) + ); } - ) + ); it( 'SessionToken.setUserAgentInfo', @@ -190,36 +190,36 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { uaDeviceType: 'wibble', uaFormFactor: 'blee', lastAccessTime: 'mnngh' - }) - assert.notEqual(token.data, 'foo', 'data was not updated') - assert.notEqual(token.id, 'foo', 'id was not updated') - assert.notEqual(token.authKey, 'foo', 'authKey was not updated') - assert.notEqual(token.bundleKey, 'foo', 'bundleKey was not updated') - assert.notEqual(token.algorithm, 'foo', 'algorithm was not updated') - assert.notEqual(token.uid, 'foo', 'uid was not updated') - assert.notEqual(token.lifetime, 'foo', 'lifetime was not updated') - assert.notEqual(token.createdAt, 'foo', 'createdAt was not updated') - assert.notEqual(token.email, 'foo', 'email was not updated') - assert.notEqual(token.emailVerified, 'foo', 'emailVerified was not updated') - assert.notEqual(token.verifierSetAt, 'foo', 'verifierSetAt was not updated') - assert.notEqual(token.locale, 'foo', 'locale was not updated') - assert.equal(token.uaBrowser, 'foo', 'uaBrowser was updated') - assert.equal(token.uaBrowserVersion, 'bar', 'uaBrowserVersion was updated') - assert.equal(token.uaOS, 'baz', 'uaOS was updated') - assert.equal(token.uaOSVersion, 'qux', 'uaOSVersion was updated') - assert.equal(token.uaDeviceType, 'wibble', 'uaDeviceType was updated') - assert.equal(token.uaFormFactor, 'blee', 'uaFormFactor was updated') - assert.equal(token.lastAccessTime, 'mnngh', 'lastAccessTime was updated') - }) + }); + assert.notEqual(token.data, 'foo', 'data was not updated'); + assert.notEqual(token.id, 'foo', 'id was not updated'); + assert.notEqual(token.authKey, 'foo', 'authKey was not updated'); + assert.notEqual(token.bundleKey, 'foo', 'bundleKey was not updated'); + assert.notEqual(token.algorithm, 'foo', 'algorithm was not updated'); + assert.notEqual(token.uid, 'foo', 'uid was not updated'); + assert.notEqual(token.lifetime, 'foo', 'lifetime was not updated'); + assert.notEqual(token.createdAt, 'foo', 'createdAt was not updated'); + assert.notEqual(token.email, 'foo', 'email was not updated'); + assert.notEqual(token.emailVerified, 'foo', 'emailVerified was not updated'); + assert.notEqual(token.verifierSetAt, 'foo', 'verifierSetAt was not updated'); + assert.notEqual(token.locale, 'foo', 'locale was not updated'); + assert.equal(token.uaBrowser, 'foo', 'uaBrowser was updated'); + assert.equal(token.uaBrowserVersion, 'bar', 'uaBrowserVersion was updated'); + assert.equal(token.uaOS, 'baz', 'uaOS was updated'); + assert.equal(token.uaOSVersion, 'qux', 'uaOSVersion was updated'); + assert.equal(token.uaDeviceType, 'wibble', 'uaDeviceType was updated'); + assert.equal(token.uaFormFactor, 'blee', 'uaFormFactor was updated'); + assert.equal(token.lastAccessTime, 'mnngh', 'lastAccessTime was updated'); + }); } - ) + ); it( 'SessionToken.setUserAgentInfo without lastAccessTime', () => { return SessionToken.create(TOKEN) .then(function (token) { - token.lastAccessTime = 'foo' + token.lastAccessTime = 'foo'; token.setUserAgentInfo({ uaBrowser: 'foo', uaBrowserVersion: 'bar', @@ -227,26 +227,26 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { uaOSVersion: 'qux', uaDeviceType: 'wibble', uaFormFactor: 'blee' - }) - assert.notEqual(token.lastAccessTime, undefined, 'lastAccessTime was not clobbered') - }) + }); + assert.notEqual(token.lastAccessTime, undefined, 'lastAccessTime was not clobbered'); + }); } - ) + ); describe('state', () => { it('should be unverified if token is not verified', () => { - const token = new SessionToken({}, {}) - token.tokenVerified = false - assert.equal(token.state, 'unverified') - }) + const token = new SessionToken({}, {}); + token.tokenVerified = false; + assert.equal(token.state, 'unverified'); + }); it('should be verified if token is verified', () => { - const token = new SessionToken({}, {}) - token.tokenVerified = true - assert.equal(token.state, 'verified') - }) - }) + const token = new SessionToken({}, {}); + token.tokenVerified = true; + assert.equal(token.state, 'verified'); + }); + }); describe('authenticationMethods', () => { @@ -256,9 +256,9 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { verifiedAt: null })) .then(token => { - assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['pwd']) - }) - }) + assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['pwd']); + }); + }); it('should be [`pwd`, `email`] for verified tokens', () => { return SessionToken.create(Object.assign({}, TOKEN, { @@ -267,9 +267,9 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { verifiedAt: null })) .then(token => { - assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['email', 'pwd']) - }) - }) + assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['email', 'pwd']); + }); + }); it('should be [`pwd`, `email`] for tokens verified via email-2fa', () => { return SessionToken.create(Object.assign({}, TOKEN, { @@ -277,20 +277,20 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice > 0', () => { verificationMethod: 1 })) .then(token => { - assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['email', 'pwd']) - }) - }) + assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['email', 'pwd']); + }); + }); it('should be [`pwd`, `otp`] for tokens verified via totp-2fa', () => { return SessionToken.create(Object.assign({}, TOKEN, { verificationMethod: 2 })) .then(token => { - assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['otp', 'pwd']) - }) - }) - }) -}) + assert.deepEqual(Array.from(token.authenticationMethods).sort(), ['otp', 'pwd']); + }); + }); + }); +}); describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice === 0', () => { const config = { @@ -298,9 +298,9 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice === 0', () => { tokenLifetimes: { sessionTokenWithoutDevice: 0 } - } - const tokens = require('../../../lib/tokens/index')(log, config) - const SessionToken = tokens.SessionToken + }; + const tokens = require('../../../lib/tokens/index')(log, config); + const SessionToken = tokens.SessionToken; it('SessionToken.fromHex creates non-expired token if deviceId is null and createdAt is too old', () => { return SessionToken.create(TOKEN) @@ -309,8 +309,8 @@ describe('SessionToken, tokenLifetimes.sessionTokenWithoutDevice === 0', () => { deviceId: null })) .then(token => { - assert.equal(token.ttl() > 0, true) - assert.equal(token.expired(), false) - }) - }) -}) + assert.equal(token.ttl() > 0, true); + assert.equal(token.expired(), false); + }); + }); +}); diff --git a/test/local/tokens/token.js b/test/local/tokens/token.js index 48d6e692..fc78195a 100644 --- a/test/local/tokens/token.js +++ b/test/local/tokens/token.js @@ -2,134 +2,134 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const config = require('../../../config').getProperties() -var random = require('../../../lib/crypto/random') -var hkdf = require('../../../lib/crypto/hkdf') -var mocks = require('../../mocks') -var P = require('../../../lib/promise') -var sinon = require('sinon') +const { assert } = require('chai'); +const config = require('../../../config').getProperties(); +var random = require('../../../lib/crypto/random'); +var hkdf = require('../../../lib/crypto/hkdf'); +var mocks = require('../../mocks'); +var P = require('../../../lib/promise'); +var sinon = require('sinon'); var Bundle = { bundle: sinon.spy(), unbundle: sinon.spy() -} -var log = mocks.mockLog() -var modulePath = '../../../lib/tokens/token' +}; +var log = mocks.mockLog(); +var modulePath = '../../../lib/tokens/token'; describe('Token', () => { describe('NODE_ENV=dev', () => { - let Token + let Token; before(() => { - config.isProduction = false - Token = require(modulePath)(log, config, random, P, hkdf, Bundle, null) - }) + config.isProduction = false; + Token = require(modulePath)(log, config, random, P, hkdf, Bundle, null); + }); it('Token constructor was exported', () => { - assert.equal(typeof Token, 'function', 'Token is function') - assert.equal(Token.name, 'Token', 'function is called Token') - assert.equal(Token.length, 2, 'function expects two arguments') - }) + assert.equal(typeof Token, 'function', 'Token is function'); + assert.equal(Token.name, 'Token', 'function is called Token'); + assert.equal(Token.length, 2, 'function expects two arguments'); + }); it('Token constructor has expected factory methods', () => { - assert.equal(typeof Token.createNewToken, 'function', 'Token.createNewToken is function') - assert.equal(Token.createNewToken.length, 2, 'function expects two arguments') - assert.equal(typeof Token.createTokenFromHexData, 'function', 'Token.createTokenFromHexData is function') - assert.equal(Token.createTokenFromHexData.length, 3, 'function expects three arguments') - }) + assert.equal(typeof Token.createNewToken, 'function', 'Token.createNewToken is function'); + assert.equal(Token.createNewToken.length, 2, 'function expects two arguments'); + assert.equal(typeof Token.createTokenFromHexData, 'function', 'Token.createTokenFromHexData is function'); + assert.equal(Token.createTokenFromHexData.length, 3, 'function expects three arguments'); + }); it('Token constructor sets createdAt', () => { - var now = Date.now() - 1 - var token = new Token({}, { createdAt: now }) + var now = Date.now() - 1; + var token = new Token({}, { createdAt: now }); - assert.equal(token.createdAt, now, 'token.createdAt is correct') - }) + assert.equal(token.createdAt, now, 'token.createdAt is correct'); + }); it('Token constructor defaults createdAt to zero if not given a value', () => { - var token = new Token({}, {}) - assert.equal(token.createdAt, 0, 'token.createdAt is correct') - }) + var token = new Token({}, {}); + assert.equal(token.createdAt, 0, 'token.createdAt is correct'); + }); it('Token.createNewToken defaults createdAt to the current time', () => { - var now = Date.now() + var now = Date.now(); return Token.createNewToken(Token, {}).then(token => { - assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct') - }) - }) + assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct'); + }); + }); it('Token.createNewToken ignores an override for createdAt', () => { - var now = Date.now() - 1 + var now = Date.now() - 1; return Token.createNewToken(Token, { createdAt: now }).then(token => { - assert.notEqual(token.createdAt, now, 'token.createdAt is new') - }) - }) + assert.notEqual(token.createdAt, now, 'token.createdAt is new'); + }); + }); it('Token.createNewToken ignores a negative value for createdAt', () => { - var now = Date.now() - var notNow = -now + var now = Date.now(); + var notNow = -now; return Token.createNewToken(Token, { createdAt: notNow }).then(token => { - assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct') - }) - }) + assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct'); + }); + }); it('Token.createNewToken ignores a createdAt timestamp in the future', () => { - var now = Date.now() - var notNow = Date.now() + 1000 + var now = Date.now(); + var notNow = Date.now() + 1000; return Token.createNewToken(Token, { createdAt: notNow }).then(token => { - assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct') - }) - }) + assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct'); + }); + }); it('Token.createTokenFromHexData accepts a value for createdAt', () => { - var now = Date.now() - 20 + var now = Date.now() - 20; return Token.createTokenFromHexData(Token, 'ABCD', { createdAt: now }).then(token => { - assert.equal(token.createdAt, now, 'token.createdAt is correct') - }) - }) + assert.equal(token.createdAt, now, 'token.createdAt is correct'); + }); + }); it('Token.createTokenFromHexData defaults to zero if not given a value for createdAt', () => { return Token.createTokenFromHexData(Token, 'ABCD', { other: 'data' }).then(token => { - assert.equal(token.createdAt, 0, 'token.createdAt is correct') - }) - }) - }) + assert.equal(token.createdAt, 0, 'token.createdAt is correct'); + }); + }); + }); describe('NODE_ENV=prod', () => { - let Token + let Token; before(() => { - config.isProduction = true - Token = require(modulePath)(log, config, random, P, hkdf, Bundle, null) - }) + config.isProduction = true; + Token = require(modulePath)(log, config, random, P, hkdf, Bundle, null); + }); it('Token.createNewToken defaults createdAt to the current time', () => { - var now = Date.now() + var now = Date.now(); return Token.createNewToken(Token, {}).then(token => { - assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct') - }) - }) + assert.ok(token.createdAt >= now && token.createdAt <= Date.now(), 'token.createdAt seems correct'); + }); + }); it('Token.createNewToken does not accept an override for createdAt', () => { - var now = Date.now() - 1 + var now = Date.now() - 1; return Token.createNewToken(Token, { createdAt: now }).then(token => { - assert.ok(token.createdAt > now && token.createdAt <= Date.now(), 'token.createdAt seems correct') - }) - }) + assert.ok(token.createdAt > now && token.createdAt <= Date.now(), 'token.createdAt seems correct'); + }); + }); it('Token.createTokenFromHexData accepts a value for createdAt', () => { - var now = Date.now() - 20 + var now = Date.now() - 20; return Token.createTokenFromHexData(Token, 'ABCD', { createdAt: now }).then(token => { - assert.equal(token.createdAt, now, 'token.createdAt is correct') - }) - }) + assert.equal(token.createdAt, now, 'token.createdAt is correct'); + }); + }); it('Token.createTokenFromHexData defaults to zero if not given a value for createdAt', () => { return Token.createTokenFromHexData(Token, 'ABCD', { other: 'data' }).then(token => { - assert.equal(token.createdAt, 0, 'token.createdAt is correct') - }) - }) - }) + assert.equal(token.createdAt, 0, 'token.createdAt is correct'); + }); + }); + }); -}) +}); diff --git a/test/local/user_agent.js b/test/local/user_agent.js index 03c20d4b..cefac30f 100644 --- a/test/local/user_agent.js +++ b/test/local/user_agent.js @@ -2,32 +2,32 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const proxyquire = require('proxyquire').noPreserveCache() -const sinon = require('sinon') +const { assert } = require('chai'); +const proxyquire = require('proxyquire').noPreserveCache(); +const sinon = require('sinon'); describe('userAgent, mocked dependency', () => { - let uaParser, userAgent, parserResult + let uaParser, userAgent, parserResult; beforeEach(() => { uaParser = { parse: sinon.spy(() => parserResult) - } + }; userAgent = proxyquire('../../lib/userAgent', { 'node-uap': uaParser - }) - }) + }); + }); it( 'exports function', () => { - assert.equal(typeof userAgent, 'function') - assert.equal(userAgent.length, 1) + assert.equal(typeof userAgent, 'function'); + assert.equal(userAgent.length, 1); } - ) + ); it( 'sets data correctly', @@ -44,21 +44,21 @@ describe('userAgent, mocked dependency', () => { device: { family: 'baz' } - } - const result = userAgent('qux') + }; + const result = userAgent('qux'); - assert.equal(uaParser.parse.callCount, 1) - assert.ok(uaParser.parse.calledWithExactly('qux')) + assert.equal(uaParser.parse.callCount, 1); + assert.ok(uaParser.parse.calledWithExactly('qux')); - assert.equal(Object.keys(result).length, 6) - assert.equal(result.browser, 'foo') - assert.equal(result.browserVersion, '1') - assert.equal(result.os, 'bar') - assert.equal(result.osVersion, '2') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, 'baz') + assert.equal(Object.keys(result).length, 6); + assert.equal(result.browser, 'foo'); + assert.equal(result.browserVersion, '1'); + assert.equal(result.os, 'bar'); + assert.equal(result.osVersion, '2'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, 'baz'); } - ) + ); it( 'ignores family:Other', @@ -75,21 +75,21 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent('wibble') + }; + const result = userAgent('wibble'); - assert.equal(uaParser.parse.callCount, 1) - assert.ok(uaParser.parse.calledWithExactly('wibble')) + assert.equal(uaParser.parse.callCount, 1); + assert.ok(uaParser.parse.calledWithExactly('wibble')); - assert.equal(Object.keys(result).length, 6) - assert.equal(result.browser, null) - assert.equal(result.browserVersion, '1.0') - assert.equal(result.os, null) - assert.equal(result.osVersion, '2.0') - assert.equal(result.deviceType, null) - assert.equal(result.formFactor, null) + assert.equal(Object.keys(result).length, 6); + assert.equal(result.browser, null); + assert.equal(result.browserVersion, '1.0'); + assert.equal(result.os, null); + assert.equal(result.osVersion, '2.0'); + assert.equal(result.deviceType, null); + assert.equal(result.formFactor, null); } - ) + ); it( 'recognises Android phones as a mobile OS', @@ -107,13 +107,13 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, null) + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, null); } - ) + ); it( 'recognises iOS as a mobile OS', @@ -130,13 +130,13 @@ describe('userAgent, mocked dependency', () => { device: { family: 'iPhone 7' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, 'iPhone 7') + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, 'iPhone 7'); } - ) + ); it( 'recognises Firefox OS as a mobile OS', @@ -153,12 +153,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') + assert.equal(result.deviceType, 'mobile'); } - ) + ); it( 'recognises Windows Phone as a mobile OS', @@ -175,12 +175,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') + assert.equal(result.deviceType, 'mobile'); } - ) + ); it( 'recognises BlackBerry OS as a mobile OS', @@ -197,12 +197,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') + assert.equal(result.deviceType, 'mobile'); } - ) + ); it( 'does not recognise Mac OS X as a mobile OS', @@ -219,12 +219,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, null) + assert.equal(result.deviceType, null); } - ) + ); it( 'does not recognise Linux as a mobile OS', @@ -241,12 +241,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, null) + assert.equal(result.deviceType, null); } - ) + ); it( 'does not recognise Windows as a mobile OS', @@ -263,12 +263,12 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Other' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, null) + assert.equal(result.deviceType, null); } - ) + ); it( 'recognises iPads as tablets', @@ -286,13 +286,13 @@ describe('userAgent, mocked dependency', () => { device: { family: 'iPad Pro' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'iPad Pro') + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'iPad Pro'); } - ) + ); it( 'recognises Android tablets as tablets', @@ -310,13 +310,13 @@ describe('userAgent, mocked dependency', () => { device: { family: 'Nexus 7' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'Nexus 7') + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'Nexus 7'); } - ) + ); it('recognises FirefoxOS mobiles as mobiles', () => { parserResult = { @@ -334,11 +334,11 @@ describe('userAgent, mocked dependency', () => { brand: 'Generic', model: 'Smartphone' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') - }) + assert.equal(result.deviceType, 'mobile'); + }); it('recognises FirefoxOS tablets as tablets', () => { parserResult = { @@ -356,11 +356,11 @@ describe('userAgent, mocked dependency', () => { brand: 'Generic', model: 'Tablet' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'tablet') - }) + assert.equal(result.deviceType, 'tablet'); + }); it('ignores form factor for generic devices', () => { parserResult = { @@ -377,70 +377,70 @@ describe('userAgent, mocked dependency', () => { family: 'Generic Smartphone', brand: 'Generic' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, null) - }) + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, null); + }); it( 'recognises old Firefox-iOS user agents', () => { - parserResult = null - const result = userAgent('Firefox-iOS-FxA/5.3 (Firefox)') + parserResult = null; + const result = userAgent('Firefox-iOS-FxA/5.3 (Firefox)'); - assert.equal(result.browser, 'Firefox') - assert.equal(result.browserVersion, '5.3') - assert.equal(result.os, 'iOS') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, null) + assert.equal(result.browser, 'Firefox'); + assert.equal(result.browserVersion, '5.3'); + assert.equal(result.os, 'iOS'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, null); } - ) + ); it( 'recognises new Firefox-iOS user agents', () => { - parserResult = null - const result = userAgent('Firefox-iOS-FxA/6.0b42 (iPhone 6S; iPhone OS 10.3) (Nightly)') + parserResult = null; + const result = userAgent('Firefox-iOS-FxA/6.0b42 (iPhone 6S; iPhone OS 10.3) (Nightly)'); - assert.equal(result.browser, 'Nightly') - assert.equal(result.browserVersion, '6.0') - assert.equal(result.os, 'iOS') - assert.equal(result.osVersion, '10.3') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, 'iPhone 6S') + assert.equal(result.browser, 'Nightly'); + assert.equal(result.browserVersion, '6.0'); + assert.equal(result.os, 'iOS'); + assert.equal(result.osVersion, '10.3'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, 'iPhone 6S'); } - ) + ); it( 'recognises new Firefox-iOS user agent on iPads', () => { - parserResult = null - const result = userAgent('Firefox-iOS-FxA/6.0b42 (iPad Mini; iPhone OS 10.3) (Nightly)') + parserResult = null; + const result = userAgent('Firefox-iOS-FxA/6.0b42 (iPad Mini; iPhone OS 10.3) (Nightly)'); - assert.equal(result.browser, 'Nightly') - assert.equal(result.browserVersion, '6.0') - assert.equal(result.os, 'iOS') - assert.equal(result.osVersion, '10.3') - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'iPad Mini') + assert.equal(result.browser, 'Nightly'); + assert.equal(result.browserVersion, '6.0'); + assert.equal(result.os, 'iOS'); + assert.equal(result.osVersion, '10.3'); + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'iPad Mini'); } - ) + ); it( 'recognises Firefox-Android user agents', () => { - parserResult = null - const result = userAgent('Firefox-Android-FxAccounts/49.0.2 (Firefox)') + parserResult = null; + const result = userAgent('Firefox-Android-FxAccounts/49.0.2 (Firefox)'); - assert.equal(result.browser, 'Firefox') - assert.equal(result.browserVersion, '49.0.2') - assert.equal(result.os, 'Android') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, null) + assert.equal(result.browser, 'Firefox'); + assert.equal(result.browserVersion, '49.0.2'); + assert.equal(result.os, 'Android'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, null); } - ) + ); it('recognises old Android Sync user agents', () => { parserResult = { @@ -458,39 +458,39 @@ describe('userAgent, mocked dependency', () => { brand: 'Generic', model: 'Smartphone' } - } - const result = userAgent('Firefox AndroidSync 1.51.0.0 (Firefox)') + }; + const result = userAgent('Firefox AndroidSync 1.51.0.0 (Firefox)'); - assert.equal(result.browser, null) - assert.equal(result.browserVersion, null) - assert.equal(result.os, 'Android') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, null) - }) + assert.equal(result.browser, null); + assert.equal(result.browserVersion, null); + assert.equal(result.os, 'Android'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, null); + }); it('recognises new mobile Sync library user agents on Android', () => { - parserResult = null - const result = userAgent('Mobile-Android-Sync/(Mobile; Android 6.0) (foo bar)') + parserResult = null; + const result = userAgent('Mobile-Android-Sync/(Mobile; Android 6.0) (foo bar)'); - assert.equal(result.browser, 'foo bar') - assert.equal(result.browserVersion, null) - assert.equal(result.os, 'Android') - assert.equal(result.osVersion, '6.0') - assert.equal(result.deviceType, 'mobile') - assert.equal(result.formFactor, 'Mobile') - }) + assert.equal(result.browser, 'foo bar'); + assert.equal(result.browserVersion, null); + assert.equal(result.os, 'Android'); + assert.equal(result.osVersion, '6.0'); + assert.equal(result.deviceType, 'mobile'); + assert.equal(result.formFactor, 'Mobile'); + }); it('recognises new mobile Sync library user agents on iOS', () => { - parserResult = null - const result = userAgent('Mobile-iOS-Sync/(iPad Mini; iOS 10.3) (wibble)') + parserResult = null; + const result = userAgent('Mobile-iOS-Sync/(iPad Mini; iOS 10.3) (wibble)'); - assert.equal(result.browser, 'wibble') - assert.equal(result.browserVersion, null) - assert.equal(result.os, 'iOS') - assert.equal(result.osVersion, '10.3') - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'iPad Mini') - }) + assert.equal(result.browser, 'wibble'); + assert.equal(result.browserVersion, null); + assert.equal(result.os, 'iOS'); + assert.equal(result.osVersion, '10.3'); + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'iPad Mini'); + }); it('recognises old Kindle user agents', () => { parserResult = { @@ -508,15 +508,15 @@ describe('userAgent, mocked dependency', () => { brand: 'Amazon', model: 'Kindle 3.0' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.browser, 'Kindle') - assert.equal(result.browserVersion, null) - assert.equal(result.os, 'Kindle') - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'Kindle') - }) + assert.equal(result.browser, 'Kindle'); + assert.equal(result.browserVersion, null); + assert.equal(result.os, 'Kindle'); + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'Kindle'); + }); it('recognises Kindle Fire user agents', () => { parserResult = { @@ -534,23 +534,23 @@ describe('userAgent, mocked dependency', () => { brand: 'Amazon', model: 'Kindle Fire' } - } - const result = userAgent() + }; + const result = userAgent(); - assert.equal(result.browser, 'Android') - assert.equal(result.browserVersion, '2.3.4') - assert.equal(result.os, 'Android') - assert.equal(result.deviceType, 'tablet') - assert.equal(result.formFactor, 'Kindle') - }) -}) + assert.equal(result.browser, 'Android'); + assert.equal(result.browserVersion, '2.3.4'); + assert.equal(result.os, 'Android'); + assert.equal(result.deviceType, 'tablet'); + assert.equal(result.formFactor, 'Kindle'); + }); +}); describe('userAgent, real dependency', () => { - let userAgent + let userAgent; beforeEach(() => { - userAgent = require('../../lib/userAgent') - }) + userAgent = require('../../lib/userAgent'); + }); it('drops dodgy-looking fields from vulnerable node-uap regexes', () => { assert.deepEqual(userAgent('http://example.com-iPad/1.0 CFNetwork'), { @@ -560,7 +560,7 @@ describe('userAgent, real dependency', () => { osVersion: '1.0', deviceType: 'mobile', formFactor: undefined - }) + }); assert.deepEqual(userAgent('foo-iPhone/2 CFNetwork'), { browser: null, browserVersion: '2', @@ -568,7 +568,7 @@ describe('userAgent, real dependency', () => { osVersion: null, deviceType: 'mobile', formFactor: 'iPhone' - }) + }); assert.deepEqual(userAgent('wibble\t/7 CFNetwork'), { browser: null, browserVersion: '7', @@ -576,6 +576,6 @@ describe('userAgent, real dependency', () => { osVersion: null, deviceType: null, formFactor: undefined - }) - }) -}) + }); + }); +}); diff --git a/test/mail_helper.js b/test/mail_helper.js index 275a1517..67feaf0c 100644 --- a/test/mail_helper.js +++ b/test/mail_helper.js @@ -4,128 +4,128 @@ /* eslint-disable no-console */ -'use strict' -const MailParser = require('mailparser').MailParser -const simplesmtp = require('simplesmtp') -const P = require('../lib/promise') +'use strict'; +const MailParser = require('mailparser').MailParser; +const simplesmtp = require('simplesmtp'); +const P = require('../lib/promise'); -const config = require('../config').getProperties() +const config = require('../config').getProperties(); const TEMPLATES_WITH_NO_CODE = new Set([ 'passwordResetEmail' -]) +]); // SMTP half -var users = {} +var users = {}; function emailName(emailAddress) { - var utf8Address = Buffer.from(emailAddress, 'binary').toString('utf-8') - return utf8Address.split('@')[0] + var utf8Address = Buffer.from(emailAddress, 'binary').toString('utf-8'); + return utf8Address.split('@')[0]; } module.exports = (printLogs) => { - printLogs = printLogs || process.env.MAIL_HELPER_LOGS + printLogs = printLogs || process.env.MAIL_HELPER_LOGS; const console = printLogs ? global.console : { log() {}, error() {} - } + }; return new P((resolve, reject) => { const smtp = simplesmtp.createSimpleServer( { SMTPBanner: 'FXATEST' }, function (req) { - var mp = new MailParser({ defaultCharset: 'utf-8' }) + var mp = new MailParser({ defaultCharset: 'utf-8' }); mp.on('end', function (mail) { - var link = mail.headers['x-link'] - var rc = mail.headers['x-recovery-code'] - var rul = mail.headers['x-report-signin-link'] - var uc = mail.headers['x-unblock-code'] - var vc = mail.headers['x-verify-code'] - var sc = mail.headers['x-signin-verify-code'] - var template = mail.headers['x-template-name'] + var link = mail.headers['x-link']; + var rc = mail.headers['x-recovery-code']; + var rul = mail.headers['x-report-signin-link']; + var uc = mail.headers['x-unblock-code']; + var vc = mail.headers['x-verify-code']; + var sc = mail.headers['x-signin-verify-code']; + var template = mail.headers['x-template-name']; - var smsLink + var smsLink; if (/MockNexmo\.message\.sendSms/.test(mail.subject)) { - const smsUrlMatch = /(https?:\/\/.*$)/.exec(mail.text) - smsLink = smsUrlMatch && smsUrlMatch[1] + const smsUrlMatch = /(https?:\/\/.*$)/.exec(mail.text); + smsLink = smsUrlMatch && smsUrlMatch[1]; } // Workaround because the email service wraps this header in `< >`. // See: https://github.com/mozilla/fxa-content-server/pull/6470#issuecomment-415224438 - var name = emailName(mail.headers.to.replace(/\<(.*?)\>/g, '$1')) + var name = emailName(mail.headers.to.replace(/\<(.*?)\>/g, '$1')); if (vc) { - console.log('\x1B[32m', link, '\x1B[39m') + console.log('\x1B[32m', link, '\x1B[39m'); } else if (sc) { - console.log('\x1B[32mToken code: ', sc, '\x1B[39m') + console.log('\x1B[32mToken code: ', sc, '\x1B[39m'); } else if (rc) { - console.log('\x1B[34m', link, '\x1B[39m') + console.log('\x1B[34m', link, '\x1B[39m'); } else if (uc) { - console.log('\x1B[36mUnblock code:', uc, '\x1B[39m') - console.log('\x1B[36mReport link:', rul, '\x1B[39m') + console.log('\x1B[36mUnblock code:', uc, '\x1B[39m'); + console.log('\x1B[36mReport link:', rul, '\x1B[39m'); } else if (smsLink) { - console.log('\x1B[36mSMS link:', smsLink, '\x1B[39m') + console.log('\x1B[36mSMS link:', smsLink, '\x1B[39m'); } else if (TEMPLATES_WITH_NO_CODE.has(template)) { - console.log(`Notification email: ${template}`) + console.log(`Notification email: ${template}`); } else { - console.error('\x1B[31mNo verify code match\x1B[39m') - console.error(mail) + console.error('\x1B[31mNo verify code match\x1B[39m'); + console.error(mail); } if (users[name]) { - users[name].push(mail) + users[name].push(mail); } else { - users[name] = [mail] + users[name] = [mail]; } if (mail.headers.cc) { // Support for CC headers - var ccName = emailName(mail.headers.cc) + var ccName = emailName(mail.headers.cc); if (users[ccName]) { - users[ccName].push(mail) + users[ccName].push(mail); } else { - users[ccName] = [mail] + users[ccName] = [mail]; } } } - ) - req.pipe(mp) - req.accept() + ); + req.pipe(mp); + req.accept(); } - ) + ); smtp.listen(config.smtp.port, function(err) { if (! err) { - console.log(`Local SMTP server listening on port ${config.smtp.port}`) + console.log(`Local SMTP server listening on port ${config.smtp.port}`); } else { - console.log('Error starting SMTP server...') - console.log(err.message) + console.log('Error starting SMTP server...'); + console.log(err.message); } - }) + }); // HTTP half - var hapi = require('hapi') + var hapi = require('hapi'); var api = new hapi.Server({ host: config.smtp.api.host, port: config.smtp.api.port - }) + }); function loop(email, cb) { - var mail = users[email] + var mail = users[email]; if (! mail) { - return setTimeout(loop.bind(null, email, cb), 50) + return setTimeout(loop.bind(null, email, cb), 50); } - cb(mail) + cb(mail); } api.route( @@ -139,55 +139,55 @@ module.exports = (printLogs) => { loop( decodeURIComponent(request.params.email), function (emailData) { - resolve(emailData) + resolve(emailData); } - ) - }) - } + ); + }); + }; return emailLoop().then((emailData) => { return emailData; - }) + }); } }, { method: 'DELETE', path: '/mail/{email}', handler: async function (request) { - delete users[decodeURIComponent(request.params.email)] - return {} + delete users[decodeURIComponent(request.params.email)]; + return {}; } } ] - ) + ); api.start().then(() => { - console.log('mail_helper started...') + console.log('mail_helper started...'); return resolve({ close() { return new P((resolve, reject) => { - let smtpClosed = false - let apiClosed = false + let smtpClosed = false; + let apiClosed = false; smtp.server.end(() => { - smtpClosed = true + smtpClosed = true; if (apiClosed) { - resolve() + resolve(); } - }) + }); api.stop().then(() => { - apiClosed = true + apiClosed = true; if (smtpClosed) { - resolve() + resolve(); } - }) - }) + }); + }); } - }) - }) - }) -} + }); + }); + }); +}; if (require.main === module) { - module.exports(true) + module.exports(true); } diff --git a/test/mailbox.js b/test/mailbox.js index 9b24feb6..666f9e2b 100644 --- a/test/mailbox.js +++ b/test/mailbox.js @@ -2,23 +2,23 @@ * 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/. */ -'use strict' +'use strict'; -var P = require('../lib/promise') -var request = require('request') -const EventEmitter = require('events').EventEmitter +var P = require('../lib/promise'); +var request = require('request'); +const EventEmitter = require('events').EventEmitter; /* eslint-disable no-console */ module.exports = function (host, port, printLogs) { - host = host || '127.0.0.1' - port = port || 9001 + host = host || '127.0.0.1'; + port = port || 9001; - const eventEmitter = new EventEmitter() + const eventEmitter = new EventEmitter(); function log() { if (printLogs) { - console.log.apply(console, arguments) + console.log.apply(console, arguments); } } @@ -27,65 +27,65 @@ module.exports = function (host, port, printLogs) { .then( function (emailData) { var code = emailData.headers['x-verify-code'] || - emailData.headers['x-recovery-code'] + emailData.headers['x-recovery-code']; if (! code) { - throw new Error('email did not contain a verification code') + throw new Error('email did not contain a verification code'); } - return code + return code; } - ) + ); } function loop(name, tries, cb) { - var url = 'http://' + host + ':' + port + '/mail/' + encodeURIComponent(name) - log('checking mail', url) + var url = 'http://' + host + ':' + port + '/mail/' + encodeURIComponent(name); + log('checking mail', url); request({ url: url, method: 'GET' }, function (err, res, body) { - log('mail status', res && res.statusCode, 'tries', tries) - log('mail body', body) - var json = null + log('mail status', res && res.statusCode, 'tries', tries); + log('mail body', body); + var json = null; try { - json = JSON.parse(body) + json = JSON.parse(body); if (json.length === 1) { - json = json[0] + json = json[0]; } } catch (e) { - return cb(e) + return cb(e); } if (! json) { if (tries === 0) { - return cb(new Error('could not get mail for ' + url)) + return cb(new Error('could not get mail for ' + url)); } - return setTimeout(loop.bind(null, name, --tries, cb), 1000) + return setTimeout(loop.bind(null, name, --tries, cb), 1000); } - log('deleting mail', url) + log('deleting mail', url); request({ url: url, method: 'DELETE' }, function (err, res, body) { - cb(err, json) + cb(err, json); } - ) + ); } - ) + ); } function waitForEmail(email) { - var d = P.defer() + var d = P.defer(); loop(email.split('@')[0], 20, function (err, json) { if (err) { - eventEmitter.emit('email:error', email, err) - return d.reject(err) + eventEmitter.emit('email:error', email, err); + return d.reject(err); } - eventEmitter.emit('email:message', email, json) - return d.resolve(json) - }) - return d.promise + eventEmitter.emit('email:message', email, json); + return d.resolve(json); + }); + return d.promise; } function waitForSms (phoneNumber) { - return waitForEmail(`sms.${phoneNumber}@restmail.net`) + return waitForEmail(`sms.${phoneNumber}@restmail.net`); } return { @@ -93,5 +93,5 @@ module.exports = function (host, port, printLogs) { waitForCode: waitForCode, waitForSms: waitForSms, eventEmitter: eventEmitter - } -} + }; +}; diff --git a/test/mailer_helper.js b/test/mailer_helper.js index c894df69..1375decc 100644 --- a/test/mailer_helper.js +++ b/test/mailer_helper.js @@ -4,12 +4,12 @@ /*eslint no-console: 0*/ -'use strict' +'use strict'; -var uuid = require('uuid') +var uuid = require('uuid'); -var zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex') -var zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex') +var zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex'); +var zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'); function createTestAccount() { var account = { @@ -25,13 +25,13 @@ function createTestAccount() { createdAt: Date.now(), verifierSetAt: Date.now(), locale: 'da, en-gb;q=0.8, en;q=0.7' - } + }; - account.normalizedEmail = account.email.toLowerCase() + account.normalizedEmail = account.email.toLowerCase(); - return account + return account; } module.exports = { createTestAccount: createTestAccount -} +}; diff --git a/test/mock-sns.js b/test/mock-sns.js index b826b56e..40e193be 100644 --- a/test/mock-sns.js +++ b/test/mock-sns.js @@ -2,11 +2,11 @@ * 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/. */ -'use strict' +'use strict'; -const P = require('../lib/promise') +const P = require('../lib/promise'); -module.exports = MockSNS +module.exports = MockSNS; function MockSNS (options, config) { const mailerOptions = { @@ -14,20 +14,20 @@ function MockSNS (options, config) { secure: config.smtp.secure, ignoreTLS: ! config.smtp.secure, port: config.smtp.port - } + }; if (config.smtp.user && config.smtp.password) { mailerOptions.auth = { user: config.smtp.user, password: config.smtp.password - } + }; } - const mailer = require('nodemailer').createTransport(mailerOptions) + const mailer = require('nodemailer').createTransport(mailerOptions); return { getSMSAttributes () { return { promise: () => P.resolve({ attributes: { MonthlySpendLimit: config.sms.minimumCreditThresholdUSD } }) - } + }; }, publish (params) { @@ -41,13 +41,13 @@ function MockSNS (options, config) { }, () => { resolve({ MessageId: 'fake message id' - }) - }) - }) + }); + }); + }); return { promise: () => promise - } + }; } - } + }; } diff --git a/test/mocks.js b/test/mocks.js index f7d3b50a..0c2a6f6c 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -6,15 +6,15 @@ * Shared helpers for mocking things out in the tests. */ -'use strict' +'use strict'; -const assert = require('assert') -const config = require('../config').getProperties() -const crypto = require('crypto') -const error = require('../lib/error') -const knownIpLocation = require('./known-ip-location') -const P = require('../lib/promise') -const sinon = require('sinon') +const assert = require('assert'); +const config = require('../config').getProperties(); +const crypto = require('crypto'); +const error = require('../lib/error'); +const knownIpLocation = require('./known-ip-location'); +const P = require('../lib/promise'); +const sinon = require('sinon'); const CUSTOMS_METHOD_NAMES = [ 'check', @@ -22,7 +22,7 @@ const CUSTOMS_METHOD_NAMES = [ 'checkIpOnly', 'flag', 'reset' -] +]; const DB_METHOD_NAMES = [ 'account', @@ -84,14 +84,14 @@ const DB_METHOD_NAMES = [ 'verifyTokens', 'verifyTokenCode', 'verifyTokensWithMethod' -] +]; const OAUTHDB_METHOD_NAMES = [ 'checkRefreshToken', 'revokeRefreshTokenById', 'getClientInfo', 'getScopedKeyData', -] +]; const LOG_METHOD_NAMES = [ 'activityEvent', @@ -104,7 +104,7 @@ const LOG_METHOD_NAMES = [ 'warn', 'summary', 'trace' -] +]; const MAILER_METHOD_NAMES = [ 'sendNewDeviceLoginNotification', @@ -126,7 +126,7 @@ const MAILER_METHOD_NAMES = [ 'sendPostAddAccountRecoveryNotification', 'sendPostRemoveAccountRecoveryNotification', 'sendPasswordResetAccountRecoveryNotification' -] +]; const METRICS_CONTEXT_METHOD_NAMES = [ 'clear', @@ -135,7 +135,7 @@ const METRICS_CONTEXT_METHOD_NAMES = [ 'setFlowCompleteSignal', 'stash', 'validate' -] +]; const PUSH_METHOD_NAMES = [ 'notifyDeviceConnected', @@ -147,12 +147,12 @@ const PUSH_METHOD_NAMES = [ 'notifyCommandReceived', 'notifyProfileUpdated', 'sendPush' -] +]; const PUSHBOX_METHOD_NAMES = [ 'retrieve', 'store' -] +]; module.exports = { MOCK_PUSH_KEY: 'BDLugiRzQCANNj5KI1fAqui8ELrE7qboxzfa5K_R0wnUoJ89xY1D_SOXI_QJKNmellykaW_7U2BZ7hnrPW3A3LM', @@ -168,33 +168,33 @@ module.exports = { mockPush, mockPushbox, mockRequest -} +}; function mockCustoms (errors) { - errors = errors || {} + errors = errors || {}; return mockObject(CUSTOMS_METHOD_NAMES)({ checkAuthenticated: optionallyThrow(errors, 'checkAuthenticated'), checkIpOnly: optionallyThrow(errors, 'checkIpOnly') - }) + }); } function optionallyThrow (errors, methodName) { return sinon.spy(() => { if (errors[methodName]) { - return P.reject(errors[methodName]) + return P.reject(errors[methodName]); } - return P.resolve() - }) + return P.resolve(); + }); } function mockDB (data, errors) { - data = data || {} - errors = errors || {} + data = data || {}; + errors = errors || {}; return mockObject(DB_METHOD_NAMES)({ account: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') + assert.ok(typeof uid === 'string'); return P.resolve({ email: data.email, emailCode: data.emailCode, @@ -204,10 +204,10 @@ function mockDB (data, errors) { uid: data.uid, verifierSetAt: Date.now(), wrapWrapKb: data.wrapWrapKb - }) + }); }), accountEmails: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') + assert.ok(typeof uid === 'string'); return P.resolve([ { email: data.email || 'primary@email.com', @@ -223,11 +223,11 @@ function mockDB (data, errors) { isVerified: data.secondEmailisVerified || false, isPrimary: false } - ]) + ]); }), accountRecord: sinon.spy(() => { if (errors.emailRecord) { - return P.reject(errors.emailRecord) + return P.reject(errors.emailRecord); } return P.resolve({ authSalt: crypto.randomBytes(32), @@ -239,20 +239,20 @@ function mockDB (data, errors) { emails: [{normalizedEmail: data.email.toLowerCase(), email: data.email, isVerified: data.emailVerified, isPrimary: true}], kA: crypto.randomBytes(32), lastAuthAt: () => { - return Date.now() + return Date.now(); }, uid: data.uid, wrapWrapKb: crypto.randomBytes(32) - }) + }); }), consumeSigninCode: sinon.spy(() => { if (errors.consumeSigninCode) { - return P.reject(errors.consumeSigninCode) + return P.reject(errors.consumeSigninCode); } return P.resolve({ email: data.email, flowId: data.flowId - }) + }); }), createAccount: sinon.spy(() => { return P.resolve({ @@ -262,24 +262,24 @@ function mockDB (data, errors) { emailVerified: data.emailVerified, locale: data.locale, wrapWrapKb: data.wrapWrapKb - }) + }); }), createDevice: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') + assert.ok(typeof uid === 'string'); return P.resolve(Object.keys(data.device).reduce((result, key) => { - result[key] = data.device[key] - return result + result[key] = data.device[key]; + return result; }, { id: data.deviceId, createdAt: data.deviceCreatedAt - })) + })); }), createKeyFetchToken: sinon.spy(() => { return P.resolve({ data: crypto.randomBytes(32).toString('hex'), id: data.keyFetchTokenId, uid: data.uid - }) + }); }), createPasswordForgotToken: sinon.spy(() => { return P.resolve({ @@ -288,9 +288,9 @@ function mockDB (data, errors) { id: data.passwordForgotTokenId, uid: data.uid, ttl: function () { - return data.passwordForgotTokenTTL || 100 + return data.passwordForgotTokenTTL || 100; } - }) + }); }), createSessionToken: sinon.spy((opts) => { return P.resolve({ @@ -299,7 +299,7 @@ function mockDB (data, errors) { email: opts.email || data.email, emailVerified: typeof opts.emailVerified !== 'undefined' ? opts.emailVerified : data.emailVerified, lastAuthAt: () => { - return opts.createdAt || Date.now() + return opts.createdAt || Date.now(); }, id: data.sessionTokenId, tokenVerificationId: opts.tokenVerificationId || data.tokenVerificationId, @@ -312,30 +312,30 @@ function mockDB (data, errors) { uaDeviceType: opts.uaDeviceType || data.uaDeviceType, uaFormFactor: opts.uaFormFactor || data.uaFormFactor, uid: opts.uid || data.uid - }) + }); }), createSigninCode: sinon.spy((uid, flowId) => { - assert.ok(typeof uid === 'string') - assert.ok(typeof flowId === 'string') - return P.resolve(data.signinCode || []) + assert.ok(typeof uid === 'string'); + assert.ok(typeof flowId === 'string'); + return P.resolve(data.signinCode || []); }), devices: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') - return P.resolve(data.devices || []) + assert.ok(typeof uid === 'string'); + return P.resolve(data.devices || []); }), device: sinon.spy((uid, deviceId) => { - assert.ok(typeof uid === 'string') - assert.ok(typeof deviceId === 'string') - const device = data.devices.find(d => d.id === deviceId) - assert.ok(device) - return P.resolve(device) + assert.ok(typeof uid === 'string'); + assert.ok(typeof deviceId === 'string'); + const device = data.devices.find(d => d.id === deviceId); + assert.ok(device); + return P.resolve(device); }), deleteSessionToken: sinon.spy(() => { - return P.resolve() + return P.resolve(); }), emailRecord: sinon.spy(() => { if (errors.emailRecord) { - return P.reject(errors.emailRecord) + return P.reject(errors.emailRecord); } return P.resolve({ authSalt: crypto.randomBytes(32).toString('hex'), @@ -347,42 +347,42 @@ function mockDB (data, errors) { emails: [{normalizedEmail: data.email.toLowerCase(), email: data.email, isVerified: data.emailVerified, isPrimary: true}], kA: crypto.randomBytes(32).toString('hex'), lastAuthAt: () => { - return Date.now() + return Date.now(); }, uid: data.uid, wrapWrapKb: crypto.randomBytes(32).toString('hex') - }) + }); }), forgotPasswordVerified: sinon.spy(() => { - return P.resolve(data.accountResetToken) + return P.resolve(data.accountResetToken); }), getSecondaryEmail: sinon.spy(() => { - return P.reject(error.unknownSecondaryEmail()) + return P.reject(error.unknownSecondaryEmail()); }), getRecoveryKey: sinon.spy(() => { if (data.recoveryKeyIdInvalid) { - return P.reject(error.recoveryKeyInvalid()) + return P.reject(error.recoveryKeyInvalid()); } return P.resolve({ recoveryData: data.recoveryData - }) + }); }), recoveryKeyExists: sinon.spy(() => { return P.resolve({ exists: !! data.recoveryData - }) + }); }), securityEvents: sinon.spy(() => { - return P.resolve([]) + return P.resolve([]); }), sessions: sinon.spy((uid) => { - assert.ok(typeof uid === 'string') - return P.resolve(data.sessions || []) + assert.ok(typeof uid === 'string'); + return P.resolve(data.sessions || []); }), updateDevice: sinon.spy((uid, device) => { - assert.ok(typeof uid === 'string') - return P.resolve(device) + assert.ok(typeof uid === 'string'); + return P.resolve(device); }), sessionToken: sinon.spy(() => { const res = { @@ -395,95 +395,95 @@ function mockDB (data, errors) { uaOSVersion: data.uaOSVersion, uaDeviceType: data.uaDeviceType, expired: () => data.expired || false - } + }; // SessionToken is a class, and tokenTypeID is a class attribute. Fake that. - res.constructor.tokenTypeID = 'sessionToken' + res.constructor.tokenTypeID = 'sessionToken'; if (data.devices && data.devices.length > 0) { Object.keys(data.devices[0]).forEach(key => { - var keyOnSession = 'device' + key.charAt(0).toUpperCase() + key.substr(1) - res[keyOnSession] = data.devices[0][key] - }) + var keyOnSession = 'device' + key.charAt(0).toUpperCase() + key.substr(1); + res[keyOnSession] = data.devices[0][key]; + }); } - return P.resolve(res) + return P.resolve(res); }), verifyTokens: optionallyThrow(errors, 'verifyTokens'), verifyTokenCode: sinon.spy(() => { if (errors.verifyTokenCode) { - return P.reject(errors.verifyTokenCode) + return P.reject(errors.verifyTokenCode); } - return P.resolve({}) + return P.resolve({}); }), replaceRecoveryCodes: sinon.spy(() => { - return P.resolve(['12312312', '12312312']) + return P.resolve(['12312312', '12312312']); }) - }) + }); } function mockOAuthDB(methods) { // For OAuthDB, the mock object needs to expose a `.api` property // with route validation info, so we load the module directly. - const log = methods.log || module.exports.mockLog() - const config = methods.config || { oauth: { url: 'http://mocked-oauth-url.net' } } - return mockObject(OAUTHDB_METHOD_NAMES, require('../lib/oauthdb')(log, config))(methods) + const log = methods.log || module.exports.mockLog(); + const config = methods.config || { oauth: { url: 'http://mocked-oauth-url.net' } }; + return mockObject(OAUTHDB_METHOD_NAMES, require('../lib/oauthdb')(log, config))(methods); } function mockObject (methodNames, baseObj) { return methods => { - methods = methods || {} + methods = methods || {}; return methodNames.reduce((object, name) => { - object[name] = methods[name] || sinon.spy(() => P.resolve()) - return object - }, baseObj || {}) - } + object[name] = methods[name] || sinon.spy(() => P.resolve()); + return object; + }, baseObj || {}); + }; } function mockPush (methods) { - const push = Object.assign({}, methods) + const push = Object.assign({}, methods); PUSH_METHOD_NAMES.forEach((name) => { if (! push[name]) { - push[name] = sinon.spy(() => P.resolve()) + push[name] = sinon.spy(() => P.resolve()); } - }) - return push + }); + return push; } function mockPushbox (methods) { - const pushbox = Object.assign({}, methods) + const pushbox = Object.assign({}, methods); PUSHBOX_METHOD_NAMES.forEach((name) => { if (! pushbox[name]) { - pushbox[name] = sinon.spy(() => P.resolve()) + pushbox[name] = sinon.spy(() => P.resolve()); } - }) - return pushbox + }); + return pushbox; } function mockDevices (data, errors) { - data = data || {} - errors = errors || {} + data = data || {}; + errors = errors || {}; return { isSpuriousUpdate: sinon.spy(() => data.spurious || false), upsert: sinon.spy(() => { if (errors.upsert) { - return P.reject(errors.upsert) + return P.reject(errors.upsert); } return P.resolve({ id: data.deviceId || crypto.randomBytes(16).toString('hex'), name: data.deviceName || 'mock device name', type: data.deviceType || 'desktop' - }) + }); }), synthesizeName: sinon.spy(() => { - return data.deviceName || null + return data.deviceName || null; }) - } + }; } function mockMetricsContext (methods) { - methods = methods || {} + methods = methods || {}; return mockObject(METRICS_CONTEXT_METHOD_NAMES)({ gather: methods.gather || sinon.spy(function (data) { - const time = Date.now() + const time = Date.now(); return P.resolve() .then(() => { if (this.payload && this.payload.metricsContext) { @@ -501,38 +501,38 @@ function mockMetricsContext (methods) { utm_medium: this.payload.metricsContext.utmMedium, utm_source: this.payload.metricsContext.utmSource, utm_term: this.payload.metricsContext.utmTerm - }) + }); } - return data - }) + return data; + }); }), setFlowCompleteSignal: sinon.spy(function (flowCompleteSignal) { if (this.payload && this.payload.metricsContext) { - this.payload.metricsContext.flowCompleteSignal = flowCompleteSignal + this.payload.metricsContext.flowCompleteSignal = flowCompleteSignal; } }), validate: methods.validate || sinon.spy(() => true) - }) + }); } function generateMetricsContext(){ - const randomBytes = crypto.randomBytes(16).toString('hex') - const flowBeginTime = Date.now() + const randomBytes = crypto.randomBytes(16).toString('hex'); + const flowBeginTime = Date.now(); const flowSignature = crypto.createHmac('sha256', config.metrics.flow_id_key) .update([ randomBytes, flowBeginTime.toString(16) ].join('\n')) .digest('hex') - .substr(0, 32) + .substr(0, 32); return { flowBeginTime: flowBeginTime, flowId: randomBytes + flowSignature - } + }; } function mockRequest (data, errors) { @@ -540,8 +540,8 @@ function mockRequest (data, errors) { oauth: { clientIds: data.clientIds || {} } - }) - const metricsContext = data.metricsContext || module.exports.mockMetricsContext() + }); + const metricsContext = data.metricsContext || module.exports.mockMetricsContext(); const geo = data.geo || { timeZone: knownIpLocation.location.tz, @@ -552,18 +552,18 @@ function mockRequest (data, errors) { state: knownIpLocation.location.state, stateCode: knownIpLocation.location.stateCode } - } + }; - let devices + let devices; if (errors && errors.devices) { - devices = P.reject(errors.devices) + devices = P.reject(errors.devices); } else { - devices = P.resolve(data.devices || []) + devices = P.resolve(data.devices || []); } - let metricsContextData = data.payload && data.payload.metricsContext + let metricsContextData = data.payload && data.payload.metricsContext; if (! metricsContextData) { - metricsContextData = {} + metricsContextData = {}; } return { @@ -606,5 +606,5 @@ function mockRequest (data, errors) { setMetricsFlowCompleteSignal: metricsContext.setFlowCompleteSignal, stashMetricsContext: metricsContext.stash, validateMetricsContext: metricsContext.validate - } + }; } diff --git a/test/oauth_helper.js b/test/oauth_helper.js index 23fac265..eec8d8ca 100644 --- a/test/oauth_helper.js +++ b/test/oauth_helper.js @@ -2,19 +2,19 @@ * 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/. */ -'use strict' +'use strict'; -const config = require('../config').getProperties() -const hapi = require('hapi') -const url = require('url') -const P = require('../lib/promise') +const config = require('../config').getProperties(); +const hapi = require('hapi'); +const url = require('url'); +const P = require('../lib/promise'); module.exports = () => { return new P((resolve, reject) => { const api = new hapi.Server({ host: url.parse(config.oauth.url).hostname, port: parseInt(url.parse(config.oauth.url).port) - }) + }); api.route( [ @@ -22,31 +22,31 @@ module.exports = () => { method: 'POST', path: '/v1/verify', handler: async function (request, h) { - const data = JSON.parse(Buffer.from(request.payload.token, 'hex')) - return h.response(data).code(data.code || 200) + const data = JSON.parse(Buffer.from(request.payload.token, 'hex')); + return h.response(data).code(data.code || 200); } } ] - ) + ); api.start().then((err) => { if (err) { - console.log(err) // eslint-disable-line no-console - return reject(err) + console.log(err); // eslint-disable-line no-console + return reject(err); } resolve({ close() { return new P((resolve, reject) => { api.stop().then((err) => { if (err) { - reject(err) + reject(err); } else { - resolve() + resolve(); } - }) - }) + }); + }); } - }) - }) - }) -} + }); + }); + }); +}; diff --git a/test/push_helper.js b/test/push_helper.js index 0a5b60c9..d8d279e4 100644 --- a/test/push_helper.js +++ b/test/push_helper.js @@ -2,10 +2,10 @@ * 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/. */ -'use strict' +'use strict'; -var WebSocket = require('ws') -var P = require('../lib/promise') +var WebSocket = require('ws'); +var P = require('../lib/promise'); /** * PushManager, helps create subscriptions against a push server @@ -23,11 +23,11 @@ var P = require('../lib/promise') */ function PushManager(options) { if (! options || ! options.server) { - throw new Error('Server is required') + throw new Error('Server is required'); } - this.server = options.server - this.channelId = options.channelId + this.server = options.server; + this.channelId = options.channelId; } /** @@ -38,9 +38,9 @@ function PushManager(options) { * @returns {Promise} */ PushManager.prototype.getSubscription = function getSubscription() { - var self = this - var d = P.defer() - var ws = new WebSocket(this.server) + var self = this; + var d = P.defer(); + var ws = new WebSocket(this.server); // Registration is a two-step handshake. // We send and receive a "hello" message, then send and receive a "register" message. @@ -48,13 +48,13 @@ PushManager.prototype.getSubscription = function getSubscription() { function send(msg) { ws.send(JSON.stringify(msg), { mask: true }, function(err) { - if (err) onError(err) - }) + if (err) onError(err); + }); } function onError(err) { - d.reject(err) - ws.close() + d.reject(err); + ws.close(); } var handlers = { @@ -62,35 +62,35 @@ PushManager.prototype.getSubscription = function getSubscription() { send({ messageType: 'register', channelID: self.channelId - }) + }); }, 'register': function(data) { d.resolve({ endpoint: data.pushEndpoint - }) - ws.close() + }); + ws.close(); }, '': function(data) { - onError(new Error('Unexpected ws message: ' + JSON.stringify(data))) + onError(new Error('Unexpected ws message: ' + JSON.stringify(data))); } - } + }; ws.on('open', function open() { send({ messageType: 'hello', use_webpush: true - }) + }); }).on('error', function error(code, description) { - onError(new Error(code + description)) + onError(new Error(code + description)); }).on('message', function message(data, flags) { - data = JSON.parse(data) + data = JSON.parse(data); if (data && data.messageType) { - var handler = handlers[data.messageType] || handlers[''] - handler(data) + var handler = handlers[data.messageType] || handlers['']; + handler(data); } - }) + }); - return d.promise -} + return d.promise; +}; -module.exports.PushManager = PushManager +module.exports.PushManager = PushManager; diff --git a/test/remote/account_create_tests.js b/test/remote/account_create_tests.js index 783db0e4..53f5ecf4 100644 --- a/test/remote/account_create_tests.js +++ b/test/remote/account_create_tests.js @@ -2,93 +2,93 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -var crypto = require('crypto') -const Client = require('../client')() -var config = require('../../config').getProperties() -const mocks = require('../mocks') +const { assert } = require('chai'); +var TestServer = require('../test_server'); +var crypto = require('crypto'); +const Client = require('../client')(); +var config = require('../../config').getProperties(); +const mocks = require('../mocks'); describe('remote account create', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(async () => { - server = await TestServer.start(config) - return server - }) + server = await TestServer.start(config); + return server; + }); it( 'unverified account fail when getting keys', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password) .then( x => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); } ) .then(() => { - return client.keys() + return client.keys(); }) .then((keys) => { - assert(false, 'got keys before verifying email') + assert(false, 'got keys before verifying email'); }, (err) => { - assert.equal(err.errno, 104, 'Unverified account error code') - assert.equal(err.message, 'Unverified account', 'Unverified account error message') + assert.equal(err.errno, 104, 'Unverified account error code'); + assert.equal(err.message, 'Unverified account', 'Unverified account error message'); } - ) + ); } - ) + ); it( 'create and verify sync account', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password, {service: 'sync'}) .then( function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false) + assert.equal(status.verified, false); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-mailer'], undefined) - assert.equal(emailData.headers['x-template-name'], 'verifySyncEmail') - assert.equal(emailData.html.indexOf('IP address') > -1, true) // Ensure some location data is present - return emailData.headers['x-verify-code'] + assert.equal(emailData.headers['x-mailer'], undefined); + assert.equal(emailData.headers['x-template-name'], 'verifySyncEmail'); + assert.equal(emailData.html.indexOf('IP address') > -1, true); // Ensure some location data is present + return emailData.headers['x-verify-code']; } ) .then( function (verifyCode) { - return client.verifyEmail(verifyCode, { service: 'sync' }) + return client.verifyEmail(verifyCode, { service: 'sync' }); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( @@ -96,288 +96,288 @@ describe('remote account create', function() { assert.equal( emailData.headers['x-link'].indexOf(config.smtp.syncUrl), 0, - 'sync url present') + 'sync url present'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true) + assert.equal(status.verified, true); } - ) + ); } - ) + ); it( 'create account with service identifier and resume', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null // eslint-disable-line no-unused-vars - var options = { service: 'abcdef', resume: 'foo' } + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; // eslint-disable-line no-unused-vars + var options = { service: 'abcdef', resume: 'foo' }; return Client.create(config.publicUrl, email, password, options) .then( function (x) { - client = x + client = x; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-service-id'], 'abcdef') - assert.ok(emailData.headers['x-link'].indexOf('resume=foo') > -1) + assert.equal(emailData.headers['x-service-id'], 'abcdef'); + assert.ok(emailData.headers['x-link'].indexOf('resume=foo') > -1); } - ) + ); } - ) + ); it( 'create account allows localization of emails', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password) .then( function (x) { - client = x + client = x; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert(emailData.text.indexOf('Activate now') !== -1, 'not en-US') - assert(emailData.text.indexOf('Ativar agora') === -1, 'not pt-BR') - return client.destroyAccount() + assert(emailData.text.indexOf('Activate now') !== -1, 'not en-US'); + assert(emailData.text.indexOf('Ativar agora') === -1, 'not pt-BR'); + return client.destroyAccount(); } ) .then( function () { - return Client.create(config.publicUrl, email, password, { lang: 'pt-br' }) + return Client.create(config.publicUrl, email, password, { lang: 'pt-br' }); } ) .then( function (x) { - client = x + client = x; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert(emailData.text.indexOf('Activate now') === -1, 'not en-US') - assert(emailData.text.indexOf('Ativar agora') !== -1, 'is pt-BR') - return client.destroyAccount() + assert(emailData.text.indexOf('Activate now') === -1, 'not en-US'); + assert(emailData.text.indexOf('Ativar agora') !== -1, 'is pt-BR'); + return client.destroyAccount(); } - ) + ); } - ) + ); it( 'Unknown account should not exist', () => { - var client = new Client(config.publicUrl) - client.email = server.uniqueEmail() - client.authPW = crypto.randomBytes(32) + var client = new Client(config.publicUrl); + client.email = server.uniqueEmail(); + client.authPW = crypto.randomBytes(32); return client.auth() .then( function () { - assert(false, 'account should not exist') + assert(false, 'account should not exist'); }, function (err) { - assert.equal(err.errno, 102, 'account does not exist') + assert.equal(err.errno, 102, 'account does not exist'); } - ) + ); } - ) + ); it( '/account/create works with proper data', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' - var client + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; + var client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - assert.ok(client.uid, 'account created') + client = x; + assert.ok(client.uid, 'account created'); } ).then( function () { - return client.login() + return client.login(); } ).then( function () { - assert.ok(client.sessionToken, 'client can login') + assert.ok(client.sessionToken, 'client can login'); } - ) + ); } - ) + ); it( '/account/create returns a sessionToken', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' - var client = new Client(config.publicUrl) + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; + var client = new Client(config.publicUrl); return client.setupCredentials(email, password) .then( function (c) { return c.api.accountCreate(c.email, c.authPW) .then( function (response) { - assert.ok(response.sessionToken, 'has a sessionToken') - assert.equal(response.keyFetchToken, undefined, 'no keyFetchToken without keys=true') + assert.ok(response.sessionToken, 'has a sessionToken'); + assert.equal(response.keyFetchToken, undefined, 'no keyFetchToken without keys=true'); } - ) + ); } - ) + ); } - ) + ); it( '/account/create returns a keyFetchToken when keys=true', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' - var client = new Client(config.publicUrl) + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; + var client = new Client(config.publicUrl); return client.setupCredentials(email, password) .then( function (c) { return c.api.accountCreate(c.email, c.authPW, { keys: true }) .then( function (response) { - assert.ok(response.sessionToken, 'has a sessionToken') - assert.ok(response.keyFetchToken, 'keyFetchToken with keys=true') + assert.ok(response.sessionToken, 'has a sessionToken'); + assert.ok(response.keyFetchToken, 'keyFetchToken with keys=true'); } - ) + ); } - ) + ); } - ) + ); it( 'signup with same email, different case', () => { - var email = server.uniqueEmail() - var email2 = email.toUpperCase() - var password = 'abcdef' + var email = server.uniqueEmail(); + var email2 = email.toUpperCase(); + var password = 'abcdef'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - return Client.create(config.publicUrl, email2, password) + return Client.create(config.publicUrl, email2, password); } ) .then( assert.fail, function (err) { - assert.equal(err.code, 400) - assert.equal(err.errno, 101, 'Account already exists') - assert.equal(err.email, email, 'The existing email address is returned') + assert.equal(err.code, 400); + assert.equal(err.errno, 101, 'Account already exists'); + assert.equal(err.email, email, 'The existing email address is returned'); } - ) + ); } - ) + ); it( 're-signup against an unverified email', () => { - var email = server.uniqueEmail() - var password = 'abcdef' + var email = server.uniqueEmail(); + var password = 'abcdef'; return Client.create(config.publicUrl, email, password) .then( function () { // delete the first verification email - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function () { - return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) + return Client.createAndVerify(config.publicUrl, email, password, server.mailbox); } ) .then( function (client) { - assert.ok(client.uid, 'account created') + assert.ok(client.uid, 'account created'); } - ) + ); } - ) + ); it( 'invalid redirectTo', () => { - var api = new Client.Api(config.publicUrl) - var email = server.uniqueEmail() - var authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + var api = new Client.Api(config.publicUrl); + var email = server.uniqueEmail(); + var authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; var options = { redirectTo: 'http://accounts.firefox.com.evil.us' - } + }; return api.accountCreate(email, authPW, options) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'bad redirectTo rejected') + assert.equal(err.errno, 107, 'bad redirectTo rejected'); } ) .then(() => { - return api.passwordForgotSendCode(email, options) + return api.passwordForgotSendCode(email, options); } ) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'bad redirectTo rejected') + assert.equal(err.errno, 107, 'bad redirectTo rejected'); } - ) + ); } - ) + ); it( 'another invalid redirectTo', () => { - var api = new Client.Api(config.publicUrl) - var email = server.uniqueEmail() - var authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + var api = new Client.Api(config.publicUrl); + var email = server.uniqueEmail(); + var authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; var options = { redirectTo: 'https://www.fake.com/.firefox.com' - } + }; return api.accountCreate(email, authPW, options) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'bad redirectTo rejected') + assert.equal(err.errno, 107, 'bad redirectTo rejected'); } ) .then(() => { return api.passwordForgotSendCode(email, { redirectTo: 'https://fakefirefox.com' - }) + }); } ) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'bad redirectTo rejected') + assert.equal(err.errno, 107, 'bad redirectTo rejected'); } - ) + ); } - ) + ); it('valid metricsContext', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -387,14 +387,14 @@ describe('remote account create', function() { utmSource: 'wibble', utmTerm: 'blee', } - } - return api.accountCreate(email, authPW, options) - }) + }; + return api.accountCreate(email, authPW, options); + }); it('invalid entrypoint', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: ';', @@ -404,15 +404,15 @@ describe('remote account create', function() { utmSource: 'qux', utmTerm: 'wibble', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it('invalid utmCampaign', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -422,15 +422,15 @@ describe('remote account create', function() { utmSource: 'qux', utmTerm: 'wibble', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it('invalid utmContent', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -440,15 +440,15 @@ describe('remote account create', function() { utmSource: 'qux', utmTerm: 'wibble', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it('invalid utmMedium', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -458,15 +458,15 @@ describe('remote account create', function() { utmSource: 'qux', utmTerm: 'wibble', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it('invalid utmSource', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -476,15 +476,15 @@ describe('remote account create', function() { utmSource: ';', utmTerm: 'wibble', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it('invalid utmTerm', () => { - const api = new Client.Api(config.publicUrl) - const email = server.uniqueEmail() - const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + const api = new Client.Api(config.publicUrl); + const email = server.uniqueEmail(); + const authPW = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; const options = { metricsContext: { entrypoint: 'foo', @@ -494,173 +494,173 @@ describe('remote account create', function() { utmSource: 'wibble', utmTerm: ';', } - } + }; return api.accountCreate(email, authPW, options) - .then(assert.fail, err => assert.equal(err.errno, 107)) - }) + .then(assert.fail, err => assert.equal(err.errno, 107)); + }); it( 'create account with service query parameter', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { serviceQuery: 'bar' }) .then(function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then(function (emailData) { - assert.equal(emailData.headers['x-service-id'], 'bar', 'service query parameter was propagated') - }) + assert.equal(emailData.headers['x-service-id'], 'bar', 'service query parameter was propagated'); + }); } - ) + ); it( 'account creation works with unicode email address', () => { - var email = server.uniqueUnicodeEmail() + var email = server.uniqueUnicodeEmail(); return Client.create(config.publicUrl, email, 'foo') .then(function (client) { - assert.ok(client, 'created account') - return server.mailbox.waitForEmail(email) + assert.ok(client, 'created account'); + return server.mailbox.waitForEmail(email); }) .then(function (emailData) { - assert.ok(emailData, 'received email') - }) + assert.ok(emailData, 'received email'); + }); } - ) + ); it( 'account creation fails with invalid metricsContext flowId', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { metricsContext: { flowId: 'deadbeefbaadf00ddeadbeefbaadf00d', flowBeginTime: 1 } }).then(function () { - assert(false, 'account creation should have failed') + assert(false, 'account creation should have failed'); }, function (err) { - assert.ok(err, 'account creation failed') - }) + assert.ok(err, 'account creation failed'); + }); } - ) + ); it( 'account creation fails with invalid metricsContext flowBeginTime', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { metricsContext: { flowId: 'deadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00d', flowBeginTime: 0 } }).then(function () { - assert(false, 'account creation should have failed') + assert(false, 'account creation should have failed'); }, function (err) { - assert.ok(err, 'account creation failed') - }) + assert.ok(err, 'account creation failed'); + }); } - ) + ); it( 'account creation works with maximal metricsContext metadata', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); var opts = { metricsContext: mocks.generateMetricsContext() - } + }; return Client.create(config.publicUrl, email, 'foo', opts).then(function (client) { - assert.ok(client, 'created account') - return server.mailbox.waitForEmail(email) + assert.ok(client, 'created account'); + return server.mailbox.waitForEmail(email); }) .then(function (emailData) { - assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') - assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') - }) + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set'); + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set'); + }); } - ) + ); it( 'account creation works with empty metricsContext metadata', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { metricsContext: {} }).then(function (client) { - assert.ok(client, 'created account') - }) + assert.ok(client, 'created account'); + }); } - ) + ); it( 'account creation fails with missing flowBeginTime', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { metricsContext: { flowId: 'deadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00d' } }).then(function () { - assert(false, 'account creation should have failed') + assert(false, 'account creation should have failed'); }, function (err) { - assert.ok(err, 'account creation failed') - }) + assert.ok(err, 'account creation failed'); + }); } - ) + ); it( 'account creation fails with missing flowId', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'foo', { metricsContext: { flowBeginTime: Date.now() } }).then(function () { - assert(false, 'account creation should have failed') + assert(false, 'account creation should have failed'); }, function (err) { - assert.ok(err, 'account creation failed') - }) + assert.ok(err, 'account creation failed'); + }); } - ) + ); it( 'create account for non-sync service, gets generic sign-up email and does not get post-verify email', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password) .then( function (x) { - client = x - assert.ok('account was created') + client = x; + assert.ok('account was created'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-template-name'], 'verifyEmail') - return emailData.headers['x-verify-code'] + assert.equal(emailData.headers['x-template-name'], 'verifyEmail'); + return emailData.headers['x-verify-code']; } ) .then( function (verifyCode) { - return client.verifyEmail(verifyCode, { service: 'testpilot' }) + return client.verifyEmail(verifyCode, { service: 'testpilot' }); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true) + assert.equal(status.verified, true); } ) .then( @@ -668,60 +668,60 @@ describe('remote account create', function() { // It's hard to test for "an email didn't arrive. // Instead trigger sending of another email and test // that there wasn't anything in the queue before it. - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - assert.ok(code, 'the next email was reset-password, not post-verify') + assert.ok(code, 'the next email was reset-password, not post-verify'); } - ) + ); } - ) + ); it( 'create account for unspecified service does not get create sync email and no post-verify email', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password) .then( function (x) { - client = x - assert.ok('account was created') + client = x; + assert.ok('account was created'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-template-name'], 'verifyEmail') - assert.equal(emailData.html.indexOf('IP address') === -1, true) // Does not contain location data - return emailData.headers['x-verify-code'] + assert.equal(emailData.headers['x-template-name'], 'verifyEmail'); + assert.equal(emailData.html.indexOf('IP address') === -1, true); // Does not contain location data + return emailData.headers['x-verify-code']; } ) .then( function (verifyCode) { - return client.verifyEmail(verifyCode, { }) // no 'service' param + return client.verifyEmail(verifyCode, { }); // no 'service' param } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true) + assert.equal(status.verified, true); } ) .then( @@ -729,23 +729,23 @@ describe('remote account create', function() { // It's hard to test for "an email didn't arrive. // Instead trigger sending of another email and test // that there wasn't anything in the queue before it. - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - assert.ok(code, 'the next email was reset-password, not post-verify') + assert.ok(code, 'the next email was reset-password, not post-verify'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_destroy_tests.js b/test/remote/account_destroy_tests.js index ae8f3a66..9d164c46 100644 --- a/test/remote/account_destroy_tests.js +++ b/test/remote/account_destroy_tests.js @@ -2,120 +2,120 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); -const config = require('../../config').getProperties() +const config = require('../../config').getProperties(); describe('remote account destroy', function () { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'account destroy', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - return client.sessionStatus() + client = x; + return client.sessionStatus(); } ) .then( function (status) { - return client.destroyAccount() + return client.destroyAccount(); } ) .then( function () { - return client.keys() + return client.keys(); } ) .then( function (keys) { - assert(false, 'account not destroyed') + assert(false, 'account not destroyed'); }, function (err) { - assert.equal(err.message, 'Unknown account', 'account destroyed') + assert.equal(err.message, 'Unknown account', 'account destroyed'); } - ) + ); } - ) + ); it( 'invalid authPW on account destroy', () => { - var email = server.uniqueEmail() - var password = 'ok' + var email = server.uniqueEmail(); + var password = 'ok'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - c.authPW = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') - return c.destroyAccount() + c.authPW = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); + return c.destroyAccount(); } ) .then( () => { - assert(false) + assert(false); }, function (err) { - assert.equal(err.errno, 103) + assert.equal(err.errno, 103); } - ) + ); } - ) + ); it('should fail to delete account with TOTP without passing sessionToken', () => { - const email = server.uniqueEmail() - const password = 'ok' - let client + const email = server.uniqueEmail(); + const password = 'ok'; + let client; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((res) => { - client = res + client = res; // Doesn't specify a sessionToken to use - delete client.sessionToken - return client.destroyAccount() + delete client.sessionToken; + return client.destroyAccount(); }) .then(assert.fail, (err) => { - assert.equal(err.errno, 138, 'unverified session') - }) - }) + assert.equal(err.errno, 138, 'unverified session'); + }); + }); it('should fail to delete account with TOTP with unverified session', () => { - const email = server.uniqueEmail() - const password = 'ok' - let client + const email = server.uniqueEmail(); + const password = 'ok'; + let client; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) .then(() => { // Create a new unverified session - return Client.login(config.publicUrl, email, password) + return Client.login(config.publicUrl, email, password); }) .then((response) => { - client = response - return client.emailStatus() + client = response; + return client.emailStatus(); }) .then((res) => assert.equal(res.sessionVerified, false, 'session not verified')) .then(() => client.destroyAccount()) .then(assert.fail, (err) => { - assert.equal(err.errno, 138, 'unverified session') - }) - }) + assert.equal(err.errno, 138, 'unverified session'); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_locale_tests.js b/test/remote/account_locale_tests.js index f9874fe8..79e4e3e7 100644 --- a/test/remote/account_locale_tests.js +++ b/test/remote/account_locale_tests.js @@ -2,48 +2,48 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); -var config = require('../../config').getProperties() -config.redis.sessionTokens.enabled = false +var config = require('../../config').getProperties(); +config.redis.sessionTokens.enabled = false; var key = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' -} +}; describe('remote account locale', function() { - this.timeout(15000) + this.timeout(15000); - let server + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'signing a cert against an account with no locale will save the locale', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' - var client + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; + var client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - client = c - return c.api.accountStatus(c.uid, c.sessionToken) + client = c; + return c.api.accountStatus(c.uid, c.sessionToken); } ) .then( function (response) { - assert.ok(! response.locale, 'account has no locale') - return client.login() + assert.ok(! response.locale, 'account has no locale'); + return client.login(); } ) .then( @@ -53,27 +53,27 @@ describe('remote account locale', function() { key, 1000, 'en-US' - ) + ); } ) .then( function () { - return client.api.accountStatus(client.uid, client.sessionToken) + return client.api.accountStatus(client.uid, client.sessionToken); } ) .then( function (response) { - assert.equal(response.locale, 'en-US', 'account has a locale') + assert.equal(response.locale, 'en-US', 'account has a locale'); } - ) + ); } - ) + ); it( 'a really long (invalid) locale', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; return Client.create( config.publicUrl, email, @@ -82,22 +82,22 @@ describe('remote account locale', function() { ) .then( function (c) { - return c.api.accountStatus(c.uid, c.sessionToken) + return c.api.accountStatus(c.uid, c.sessionToken); } ) .then( function (response) { - assert.ok(! response.locale, 'account has no locale') + assert.ok(! response.locale, 'account has no locale'); } - ) + ); } - ) + ); it( 'a really long (valid) locale', () => { - var email = server.uniqueEmail() - var password = 'ilikepancakes' + var email = server.uniqueEmail(); + var password = 'ilikepancakes'; return Client.create( config.publicUrl, email, @@ -106,19 +106,19 @@ describe('remote account locale', function() { ) .then( function (c) { - return c.api.accountStatus(c.uid, c.sessionToken) + return c.api.accountStatus(c.uid, c.sessionToken); } ) .then( function (response) { - assert.equal(response.locale, 'en-US,en;q=0.8', 'account has no locale') + assert.equal(response.locale, 'en-US,en;q=0.8', 'account has no locale'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) + return TestServer.stop(server); + }); -}) +}); diff --git a/test/remote/account_login_tests.js b/test/remote/account_login_tests.js index 361397a7..e86eb893 100644 --- a/test/remote/account_login_tests.js +++ b/test/remote/account_login_tests.js @@ -2,133 +2,133 @@ * 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/. */ -'use strict' +'use strict'; -const assert = require('assert') -const Client = require('../client')() -var crypto = require('crypto') -var TestServer = require('../test_server') +const assert = require('assert'); +const Client = require('../client')(); +var crypto = require('crypto'); +var TestServer = require('../test_server'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote account login', () => { - let server + let server; before(function() { - this.timeout(15000) - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts.enabled = false + this.timeout(15000); + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'the email is returned in the error on Incorrect password errors', () => { - var email = server.uniqueEmail() - var password = 'abcdef' + var email = server.uniqueEmail(); + var password = 'abcdef'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function () { - return Client.login(config.publicUrl, email, password + 'x') + return Client.login(config.publicUrl, email, password + 'x'); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400) - assert.equal(err.errno, 103) - assert.equal(err.email, email) + assert.equal(err.code, 400); + assert.equal(err.errno, 103); + assert.equal(err.email, email); } - ) + ); } - ) + ); it( 'the email is returned in the error on Incorrect email case errors with correct password', () => { - var signupEmail = server.uniqueEmail() - var loginEmail = signupEmail.toUpperCase() - var password = 'abcdef' + var signupEmail = server.uniqueEmail(); + var loginEmail = signupEmail.toUpperCase(); + var password = 'abcdef'; return Client.createAndVerify(config.publicUrl, signupEmail, password, server.mailbox) .then( function () { - return Client.login(config.publicUrl, loginEmail, password) + return Client.login(config.publicUrl, loginEmail, password); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400) - assert.equal(err.errno, 120) - assert.equal(err.email, signupEmail) + assert.equal(err.code, 400); + assert.equal(err.errno, 120); + assert.equal(err.email, signupEmail); } - ) + ); } - ) + ); it( 'Unknown account should not exist', () => { - var client = new Client(config.publicUrl) - client.email = server.uniqueEmail() - client.authPW = crypto.randomBytes(32) + var client = new Client(config.publicUrl); + client.email = server.uniqueEmail(); + client.authPW = crypto.randomBytes(32); return client.login() .then( function () { - assert(false, 'account should not exist') + assert(false, 'account should not exist'); }, function (err) { - assert.equal(err.errno, 102, 'account does not exist') + assert.equal(err.errno, 102, 'account does not exist'); } - ) + ); } - ) + ); it( 'No keyFetchToken without keys=true', () => { - var email = server.uniqueEmail() - var password = 'abcdef' + var email = server.uniqueEmail(); + var password = 'abcdef'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - return Client.login(config.publicUrl, email, password, { keys: false }) + return Client.login(config.publicUrl, email, password, { keys: false }); } ) .then( function (c) { - assert.equal(c.keyFetchToken, null, 'should not have keyFetchToken') + assert.equal(c.keyFetchToken, null, 'should not have keyFetchToken'); } - ) + ); } - ) + ); it( 'login works with unicode email address', () => { - var email = server.uniqueUnicodeEmail() - var password = 'wibble' + var email = server.uniqueUnicodeEmail(); + var password = 'wibble'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function () { - return Client.login(config.publicUrl, email, password) + return Client.login(config.publicUrl, email, password); } ) .then( function (client) { - assert.ok(client, 'logged in to account') + assert.ok(client, 'logged in to account'); } - ) + ); } - ) + ); it( 'account login works with minimal metricsContext metadata', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, 'foo', server.mailbox) .then(function () { return Client.login(config.publicUrl, email, 'foo', { @@ -136,18 +136,18 @@ describe('remote account login', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', flowBeginTime: Date.now() } - }) + }); }) .then(function (client) { - assert.ok(client, 'logged in to account') - }) + assert.ok(client, 'logged in to account'); + }); } - ) + ); it( 'account login fails with invalid metricsContext flowId', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, 'foo', server.mailbox) .then(function () { return Client.login(config.publicUrl, email, 'foo', { @@ -155,20 +155,20 @@ describe('remote account login', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0', flowBeginTime: Date.now() } - }) + }); }) .then(function () { - assert(false, 'account login should have failed') + assert(false, 'account login should have failed'); }, function (err) { - assert.ok(err, 'account login failed') - }) + assert.ok(err, 'account login failed'); + }); } - ) + ); it( 'account login fails with invalid metricsContext flowBeginTime', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, 'foo', server.mailbox) .then(function () { return Client.login(config.publicUrl, email, 'foo', { @@ -176,23 +176,23 @@ describe('remote account login', () => { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', flowBeginTime: 'wibble' } - }) + }); }) .then(function () { - assert(false, 'account login should have failed') + assert(false, 'account login should have failed'); }, function (err) { - assert.ok(err, 'account login failed') - }) + assert.ok(err, 'account login failed'); + }); } - ) + ); describe('can use verificationMethod', () => { - let client, email - const password = 'foo' + let client, email; + const password = 'foo'; beforeEach(() => { - email = server.uniqueEmail('@mozilla.com') - return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) - }) + email = server.uniqueEmail('@mozilla.com'); + return Client.createAndVerify(config.publicUrl, email, password, server.mailbox); + }); it('fails with invalid verification method', () => { return Client.login(config.publicUrl, email, password, { @@ -200,11 +200,11 @@ describe('remote account login', () => { keys: true }) .then(() => { - assert.fail('should not have succeed') + assert.fail('should not have succeed'); }, (err) => { - assert.equal(err.errno, 107, 'invalid parameter') - }) - }) + assert.equal(err.errno, 107, 'invalid parameter'); + }); + }); it('can use `email` verification', () => { return Client.login(config.publicUrl, email, password, { @@ -212,32 +212,32 @@ describe('remote account login', () => { keys: true }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'email', 'sets correct verification method') - return client.emailStatus() + client = res; + assert.equal(res.verificationMethod, 'email', 'sets correct verification method'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - return server.mailbox.waitForEmail(email) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginEmail', 'confirm sign-in link sent') - const code = emailData.headers['x-verify-code'] - assert.ok(code, 'code is sent') - return client.verifyEmail(code) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginEmail', 'confirm sign-in link sent'); + const code = emailData.headers['x-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyEmail(code); }) .then((res) => { - assert.ok(res, 'verified successful response') - return client.emailStatus() + assert.ok(res, 'verified successful response'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, true, 'session is verified') - }) - }) + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, true, 'session is verified'); + }); + }); it('can use `email-2fa` verification', () => { return Client.login(config.publicUrl, email, password, { @@ -245,40 +245,40 @@ describe('remote account login', () => { keys: true }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method') - return client.emailStatus() + client = res; + assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - return server.mailbox.waitForEmail(email) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent') - const code = emailData.headers['x-signin-verify-code'] - assert.ok(code, 'code is sent') - }) - }) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent'); + const code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + }); + }); it('can use `totp-2fa` verification', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) .then(() => { - return Client.login(config.publicUrl, email, password, {verificationMethod: 'totp-2fa', keys: true}) + return Client.login(config.publicUrl, email, password, {verificationMethod: 'totp-2fa', keys: true}); }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'totp-2fa', 'sets correct verification method') - return client.emailStatus() + client = res; + assert.equal(res.verificationMethod, 'totp-2fa', 'sets correct verification method'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - }) - }) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + }); + }); it('should ignore verificationMethod if not requesting keys', () => { return Client.login(config.publicUrl, email, password, { @@ -286,19 +286,19 @@ describe('remote account login', () => { keys: false }) .then((res) => { - client = res - assert.equal(res.verificationMethod, undefined, 'sets correct verification method') - return client.emailStatus() + client = res; + assert.equal(res.verificationMethod, undefined, 'sets correct verification method'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - }) - }) - }) + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + }); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_profile_tests.js b/test/remote/account_profile_tests.js index 058b0a62..595b1bf9 100644 --- a/test/remote/account_profile_tests.js +++ b/test/remote/account_profile_tests.js @@ -2,47 +2,47 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); function makeMockOAuthHeader(opts) { - var token = Buffer.from(JSON.stringify(opts)).toString('hex') - return 'Bearer ' + token + var token = Buffer.from(JSON.stringify(opts)).toString('hex'); + return 'Bearer ' + token; } describe('remote account profile', function() { - this.timeout(15000) + this.timeout(15000); - let server + let server; before(() => { - config.oauth.url = 'http://localhost:9010' + config.oauth.url = 'http://localhost:9010'; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'account profile authenticated with session returns profile data', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then(c => { - return c.api.accountProfile(c.sessionToken) + return c.api.accountProfile(c.sessionToken); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.equal(response.locale, 'en-US', 'locale is returned') - assert.deepEqual(response.authenticationMethods, ['pwd', 'email'], 'authentication methods are returned') - assert.equal(response.authenticatorAssuranceLevel, 1, 'assurance level is returned') - assert.ok(response.profileChangedAt, 'profileChangedAt is returned') - }) + assert.ok(response.email, 'email address is returned'); + assert.equal(response.locale, 'en-US', 'locale is returned'); + assert.deepEqual(response.authenticationMethods, ['pwd', 'email'], 'authentication methods are returned'); + assert.equal(response.authenticatorAssuranceLevel, 1, 'assurance level is returned'); + assert.ok(response.profileChangedAt, 'profileChangedAt is returned'); + }); } - ) + ); it( 'account profile authenticated with oauth returns profile data', @@ -54,33 +54,33 @@ describe('remote account profile', function() { user: c.uid, scope: ['profile'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.equal(response.locale, 'en-US', 'locale is returned') - assert.deepEqual(response.authenticationMethods, ['pwd', 'email'], 'authentication methods are returned') - assert.equal(response.authenticatorAssuranceLevel, 1, 'assurance level is returned') - assert.ok(response.profileChangedAt, 'profileChangedAt is returned') - }) + assert.ok(response.email, 'email address is returned'); + assert.equal(response.locale, 'en-US', 'locale is returned'); + assert.deepEqual(response.authenticationMethods, ['pwd', 'email'], 'authentication methods are returned'); + assert.equal(response.authenticatorAssuranceLevel, 1, 'assurance level is returned'); + assert.ok(response.profileChangedAt, 'profileChangedAt is returned'); + }); } - ) + ); it( 'account profile with no authentication returns a 401', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then(c => { - return c.api.accountProfile(null, {}) + return c.api.accountProfile(null, {}); }) .then( - (response) => { assert.fail('request should have failed') }, + (response) => { assert.fail('request should have failed'); }, (err) => { - assert.equal(err.code, 401, 'request failed with a 401') + assert.equal(err.code, 401, 'request failed with a 401'); } - ) + ); } - ) + ); it( 'account profile authenticated with invalid oauth token returns an error', @@ -92,43 +92,43 @@ describe('remote account profile', function() { code: 401, errno: 108 }) - }) + }); }) .then( - () => { assert(false, 'should get an error') }, + () => { assert(false, 'should get an error'); }, (e) => { - assert.equal(e.code, 401, 'correct error status code') - assert.equal(e.errno, 110, 'correct errno') + assert.equal(e.code, 401, 'correct error status code'); + assert.equal(e.errno, 110, 'correct errno'); } - ) + ); } - ) + ); it( 'account status authenticated with oauth for unknown uid returns an error', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then(c => { - var UNKNOWN_UID = 'abcdef123456' - assert.notEqual(c.uid, UNKNOWN_UID) + var UNKNOWN_UID = 'abcdef123456'; + assert.notEqual(c.uid, UNKNOWN_UID); return c.api.accountProfile(null, { Authorization: makeMockOAuthHeader({ user: UNKNOWN_UID, scope: ['profile'] }) - }) + }); }) .then( () => { - assert(false, 'should get an error') + assert(false, 'should get an error'); }, (e) => { - assert.equal(e.code, 400, 'correct error status code') - assert.equal(e.errno, 102, 'correct errno') + assert.equal(e.code, 400, 'correct error status code'); + assert.equal(e.errno, 102, 'correct errno'); } - ) + ); } - ) + ); it( 'account status authenticated with oauth for wrong scope returns no info', @@ -140,32 +140,32 @@ describe('remote account profile', function() { user: c.uid, scope: ['readinglist', 'payments'] }) - }) + }); }) .then(response => { - assert.deepEqual(response, {}, 'no info should be returned') - }) + assert.deepEqual(response, {}, 'no info should be returned'); + }); } - ) + ); it( 'account profile authenticated with limited oauth scopes returns limited profile data', () => { - var client + var client; return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then(c => { - client = c + client = c; return client.api.accountProfile(null, { Authorization: makeMockOAuthHeader({ user: client.uid, scope: ['profile:email'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.ok(! response.locale, 'locale should not be returned') - assert.ok(response.profileChangedAt, 'profileChangedAt is returned') + assert.ok(response.email, 'email address is returned'); + assert.ok(! response.locale, 'locale should not be returned'); + assert.ok(response.profileChangedAt, 'profileChangedAt is returned'); }) .then(() => { return client.api.accountProfile(null, { @@ -173,36 +173,36 @@ describe('remote account profile', function() { user: client.uid, scope: ['profile:locale'] }) - }) + }); }) .then(response => { - assert.ok(! response.email, 'email address should not be returned') - assert.equal(response.locale, 'en-US', 'locale is returned') - assert.ok(response.profileChangedAt, 'profileChangedAt is returned') - }) + assert.ok(! response.email, 'email address should not be returned'); + assert.equal(response.locale, 'en-US', 'locale is returned'); + assert.ok(response.profileChangedAt, 'profileChangedAt is returned'); + }); } - ) + ); it( 'account profile authenticated with oauth :write scopes returns profile data', () => { - var client + var client; return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then(c => { - client = c + client = c; return client.api.accountProfile(null, { Authorization: makeMockOAuthHeader({ user: client.uid, scope: ['profile:write'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.ok(response.locale, 'locale is returned') - assert.ok(response.authenticationMethods, 'authenticationMethods is returned') - assert.ok(response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel is returned') - assert.ok(response.profileChangedAt, 'profileChangedAt is returned') + assert.ok(response.email, 'email address is returned'); + assert.ok(response.locale, 'locale is returned'); + assert.ok(response.authenticationMethods, 'authenticationMethods is returned'); + assert.ok(response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel is returned'); + assert.ok(response.profileChangedAt, 'profileChangedAt is returned'); }) .then(() => { return client.api.accountProfile(null, { @@ -210,13 +210,13 @@ describe('remote account profile', function() { user: client.uid, scope: ['profile:locale:write', 'readinglist'] }) - }) + }); }) .then(response => { - assert.ok(! response.email, 'email address should not be returned') - assert.ok(response.locale, 'locale is returned') - assert.ok(! response.authenticationMethods, 'authenticationMethods should not be returned') - assert.ok(! response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel should not be returned') + assert.ok(! response.email, 'email address should not be returned'); + assert.ok(response.locale, 'locale is returned'); + assert.ok(! response.authenticationMethods, 'authenticationMethods should not be returned'); + assert.ok(! response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel should not be returned'); }) .then(() => { return client.api.accountProfile(null, { @@ -224,13 +224,13 @@ describe('remote account profile', function() { user: client.uid, scope: ['storage', 'profile:email:write'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.ok(! response.locale, 'locale should not be returned') - assert.ok(! response.authenticationMethods, 'authenticationMethods should not be returned') - assert.ok(! response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel should not be returned') + assert.ok(response.email, 'email address is returned'); + assert.ok(! response.locale, 'locale should not be returned'); + assert.ok(! response.authenticationMethods, 'authenticationMethods should not be returned'); + assert.ok(! response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel should not be returned'); }) .then(() => { return client.api.accountProfile(null, { @@ -238,30 +238,30 @@ describe('remote account profile', function() { user: client.uid, scope: ['profile:amr', 'profile:email:write'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.ok(! response.locale, 'locale should not be returned') - assert.ok(response.authenticationMethods, 'authenticationMethods is returned') - assert.ok(response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel is returned') - }) + assert.ok(response.email, 'email address is returned'); + assert.ok(! response.locale, 'locale should not be returned'); + assert.ok(response.authenticationMethods, 'authenticationMethods is returned'); + assert.ok(response.authenticatorAssuranceLevel, 'authenticatorAssuranceLevel is returned'); + }); } - ) + ); it( 'account profile works with unicode email address', () => { - var email = server.uniqueUnicodeEmail() + var email = server.uniqueUnicodeEmail(); return Client.create(config.publicUrl, email, 'password') .then(c => { - return c.api.accountProfile(c.sessionToken) + return c.api.accountProfile(c.sessionToken); }) .then(response => { - assert.equal(response.email, email, 'email address is returned') - }) + assert.equal(response.email, email, 'email address is returned'); + }); } - ) + ); it( 'account profile reflects TOTP status', @@ -273,18 +273,18 @@ describe('remote account profile', function() { user: c.uid, scope: ['profile'] }) - }) + }); }) .then(response => { - assert.ok(response.email, 'email address is returned') - assert.equal(response.locale, 'en-US', 'locale is returned') - assert.deepEqual(response.authenticationMethods, ['pwd', 'email', 'otp'], 'correct authentication methods are returned') - assert.equal(response.authenticatorAssuranceLevel, 2, 'correct assurance level is returned') - }) + assert.ok(response.email, 'email address is returned'); + assert.equal(response.locale, 'en-US', 'locale is returned'); + assert.deepEqual(response.authenticationMethods, ['pwd', 'email', 'otp'], 'correct authentication methods are returned'); + assert.equal(response.authenticatorAssuranceLevel, 2, 'correct assurance level is returned'); + }); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_reset_tests.js b/test/remote/account_reset_tests.js index 21fc2e50..ac55b8b5 100644 --- a/test/remote/account_reset_tests.js +++ b/test/remote/account_reset_tests.js @@ -2,284 +2,284 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var url = require('url') -const Client = require('../client')() -var TestServer = require('../test_server') +const { assert } = require('chai'); +var url = require('url'); +const Client = require('../client')(); +var TestServer = require('../test_server'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote account reset', function() { - this.timeout(15000) - let server - config.signinConfirmation.skipForNewAccounts.enabled = true + this.timeout(15000); + let server; + config.signinConfirmation.skipForNewAccounts.enabled = true; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'account reset w/o sessionToken', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'ez' - var wrapKb, kA, client + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'ez'; + var wrapKb, kA, client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x + client = x; } ) .then( function () { - return client.keys() + return client.keys(); } ) .then( function (keys) { - wrapKb = keys.wrapKb - kA = keys.kA - return client.forgotPassword() + wrapKb = keys.wrapKb; + kA = keys.kA; + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { assert.throws(function() { - client.resetPassword(newPassword) - }) - return resetPassword(client, code, newPassword, {sessionToken: false}) + client.resetPassword(newPassword); + }); + return resetPassword(client, code, newPassword, {sessionToken: false}); } ) .then( function (response) { - assert(! response.sessionToken, 'session token is not in response') - assert(! response.keyFetchToken, 'keyFetchToken token is not in response') - assert(! response.verified, 'verified is not in response') + assert(! response.sessionToken, 'session token is not in response'); + assert(! response.keyFetchToken, 'keyFetchToken token is not in response'); + assert(! response.verified, 'verified is not in response'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); } ) .then( function () { // make sure we can still login after password reset - return Client.login(config.publicUrl, email, newPassword, {keys:true}) + return Client.login(config.publicUrl, email, newPassword, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset') - assert.equal(kA, keys.kA, 'kA was not reset') - assert.equal(typeof client.kB, 'string') - assert.equal(client.kB.length, 64, 'kB exists, has the right length') + assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset'); + assert.equal(kA, keys.kA, 'kA was not reset'); + assert.equal(typeof client.kB, 'string'); + assert.equal(client.kB.length, 64, 'kB exists, has the right length'); } - ) + ); } - ) + ); it( 'account reset with keys', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'ez' - var wrapKb, kA, client + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'ez'; + var wrapKb, kA, client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x + client = x; } ) .then( function () { - return client.keys() + return client.keys(); } ) .then( function (keys) { - wrapKb = keys.wrapKb - kA = keys.kA - return client.forgotPassword() + wrapKb = keys.wrapKb; + kA = keys.kA; + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { assert.throws(function() { - client.resetPassword(newPassword) - }) - return resetPassword(client, code, newPassword, {keys:true}) + client.resetPassword(newPassword); + }); + return resetPassword(client, code, newPassword, {keys:true}); } ) .then( function (response) { - assert.ok(response.sessionToken, 'session token is in response') - assert.ok(response.keyFetchToken, 'keyFetchToken token is in response') - assert.equal(response.verified, true, 'verified is true') + assert.ok(response.sessionToken, 'session token is in response'); + assert.ok(response.keyFetchToken, 'keyFetchToken token is in response'); + assert.equal(response.verified, true, 'verified is true'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); } ) .then( function () { // make sure we can still login after password reset - return Client.login(config.publicUrl, email, newPassword, {keys:true}) + return Client.login(config.publicUrl, email, newPassword, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset') - assert.equal(kA, keys.kA, 'kA was not reset') - assert.equal(typeof client.kB, 'string') - assert.equal(client.kB.length, 64, 'kB exists, has the right length') + assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset'); + assert.equal(kA, keys.kA, 'kA was not reset'); + assert.equal(typeof client.kB, 'string'); + assert.equal(client.kB.length, 64, 'kB exists, has the right length'); } - ) + ); } - ) + ); it( 'account reset w/o keys, with sessionToken', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'ez' - var client + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'ez'; + var client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x + client = x; } ) .then( function () { - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { assert.throws(function() { - client.resetPassword(newPassword) - }) - return resetPassword(client, code, newPassword) + client.resetPassword(newPassword); + }); + return resetPassword(client, code, newPassword); } ) .then( function (response) { - assert.ok(response.sessionToken, 'session token is in response') - assert(! response.keyFetchToken, 'keyFetchToken token is not in response') - assert.equal(response.verified, true, 'verified is true') + assert.ok(response.sessionToken, 'session token is in response'); + assert(! response.keyFetchToken, 'keyFetchToken token is not in response'); + assert.equal(response.verified, true, 'verified is true'); } - ) + ); } - ) + ); it( 'account reset deletes tokens', () => { - const email = server.uniqueEmail() - const password = 'allyourbasearebelongtous' - const newPassword = 'ez' - let client = null - let originalCode = null + const email = server.uniqueEmail(); + const password = 'allyourbasearebelongtous'; + const newPassword = 'ez'; + let client = null; + let originalCode = null; const opts = { keys: true - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, opts) .then((x) => { - client = x + client = x; - return client.forgotPassword() + return client.forgotPassword(); }) .then(() => server.mailbox.waitForCode(email)) .then((code) => { // Stash original reset code then attempt to use it after another reset - originalCode = code + originalCode = code; - return client.forgotPassword() + return client.forgotPassword(); }) .then(() => server.mailbox.waitForCode(email)) .then((code) => { - assert.throws(() => client.resetPassword(newPassword)) + assert.throws(() => client.resetPassword(newPassword)); - return resetPassword(client, code, newPassword, undefined, opts) + return resetPassword(client, code, newPassword, undefined, opts); }) .then(() => server.mailbox.waitForEmail(email)) .then((emailData) => { - const templateName = emailData.headers['x-template-name'] - assert.equal(templateName, 'passwordResetEmail') + const templateName = emailData.headers['x-template-name']; + assert.equal(templateName, 'passwordResetEmail'); return resetPassword(client, originalCode, newPassword, undefined, opts) .then(() => assert.fail('Should not have succeeded password reset'), (err) => { // Ensure that password reset fails with unknown token error codes - assert.equal(err.code, 401) - assert.equal(err.errno, 110) - }) - }) + assert.equal(err.code, 401); + assert.equal(err.errno, 110); + }); + }); } - ) + ); after(() => { - return TestServer.stop(server) - }) + return TestServer.stop(server); + }); function resetPassword(client, code, newPassword, options) { return client.verifyPasswordResetCode(code) .then(function() { - return client.resetPassword(newPassword, {}, options) - }) + return client.resetPassword(newPassword, {}, options); + }); } -}) +}); diff --git a/test/remote/account_signin_verification_tests.js b/test/remote/account_signin_verification_tests.js index bab4abab..76fb410a 100644 --- a/test/remote/account_signin_verification_tests.js +++ b/test/remote/account_signin_verification_tests.js @@ -2,805 +2,805 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() -var config = require('../../config').getProperties() -config.redis.sessionTokens.enabled = false -var url = require('url') -var jwtool = require('fxa-jwtool') -var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile) -var duration = 1000 * 60 * 60 * 24 // 24 hours +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); +var config = require('../../config').getProperties(); +config.redis.sessionTokens.enabled = false; +var url = require('url'); +var jwtool = require('fxa-jwtool'); +var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile); +var duration = 1000 * 60 * 60 * 24; // 24 hours var publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' -} +}; -const mocks = require('../mocks') +const mocks = require('../mocks'); describe('remote account signin verification', function() { - this.timeout(30000) - let server + this.timeout(30000); + let server; before(() => { - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts.enabled = false + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'account signin without keys does not set challenge', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { - return client.login({keys:false}) + return client.login({keys:false}); } ) .then( function (response) { - assert(! response.verificationMethod, 'no challenge method set') - assert(! response.verificationReason, 'no challenge reason set') - assert.equal(response.verified, true, 'verified set true') + assert(! response.verificationMethod, 'no challenge method set'); + assert(! response.verificationReason, 'no challenge reason set'); + assert.equal(response.verified, true, 'verified set true'); } - ) + ); } - ) + ); it( 'account signin with keys does set challenge', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { - return client.login({keys:true}) + return client.login({keys:true}); } ) .then( function (response) { - assert.equal(response.verificationMethod, 'email', 'challenge method set') - assert.equal(response.verificationReason, 'login', 'challenge reason set') - assert.equal(response.verified, false, 'verified set to false') + assert.equal(response.verificationMethod, 'email', 'challenge method set'); + assert.equal(response.verificationReason, 'login', 'challenge reason set'); + assert.equal(response.verified, false, 'verified set to false'); } - ) + ); } - ) + ); it( 'account can verify new sign-in from email', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var uid - var code + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var uid; + var code; var loginOpts = { keys: true, metricsContext: mocks.generateMetricsContext() - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'new account is verified') + assert.equal(status.verified, true, 'new account is verified'); } ) .then( function () { - return client.login(loginOpts) + return client.login(loginOpts); } ) .then( function (response) { - assert.equal(response.verificationMethod, 'email', 'challenge method set to email') - assert.equal(response.verificationReason, 'login', 'challenge reason set to signin') - assert.equal(response.verified, false, 'verified set to false') + assert.equal(response.verificationMethod, 'email', 'challenge method set to email'); + assert.equal(response.verificationReason, 'login', 'challenge reason set to signin'); + assert.equal(response.verified, false, 'verified set to false'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - uid = emailData.headers['x-uid'] - code = emailData.headers['x-verify-code'] - assert.equal(emailData.subject, 'Confirm new sign-in to Firefox') - assert.ok(uid, 'sent uid') - assert.ok(code, 'sent verify code') + uid = emailData.headers['x-uid']; + code = emailData.headers['x-verify-code']; + assert.equal(emailData.subject, 'Confirm new sign-in to Firefox'); + assert.ok(uid, 'sent uid'); + assert.ok(code, 'sent verify code'); - assert.equal(emailData.headers['x-flow-begin-time'], loginOpts.metricsContext.flowBeginTime, 'flow begin time set') - assert.equal(emailData.headers['x-flow-id'], loginOpts.metricsContext.flowId, 'flow id set') + assert.equal(emailData.headers['x-flow-begin-time'], loginOpts.metricsContext.flowBeginTime, 'flow begin time set'); + assert.equal(emailData.headers['x-flow-id'], loginOpts.metricsContext.flowId, 'flow id set'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'account is not verified, unverified sign-in') + assert.equal(status.verified, false, 'account is not verified, unverified sign-in'); } ) .then( function () { - return client.verifyEmail(code) + return client.verifyEmail(code); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified confirming email') + assert.equal(status.verified, true, 'account is verified confirming email'); } - ) + ); } - ) + ); it( 'Account verification links still work after session verification', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var emailCode, tokenCode, uid + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var emailCode, tokenCode, uid; // Create unverified account return Client.create(config.publicUrl, email, password) .then( function (x) { - client = x + client = x; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { // Ensure correct email sent - assert.equal(emailData.subject, 'Verify your Firefox Account') - emailCode = emailData.headers['x-verify-code'] - assert.ok(emailCode, 'sent verify code') - return client.verifyEmail(emailCode) + assert.equal(emailData.subject, 'Verify your Firefox Account'); + emailCode = emailData.headers['x-verify-code']; + assert.ok(emailCode, 'sent verify code'); + return client.verifyEmail(emailCode); } ) .then( function () { // Trigger sign-in confirm email - return client.login({keys:true}) + return client.login({keys:true}); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { // Verify sign-confirm email - uid = emailData.headers['x-uid'] - tokenCode = emailData.headers['x-verify-code'] - assert.equal(emailData.subject, 'Confirm new sign-in to Firefox') - assert.ok(uid, 'sent uid') - assert.ok(tokenCode, 'sent verify code') - assert.notEqual(tokenCode, emailCode, 'email and token codes are different') + uid = emailData.headers['x-uid']; + tokenCode = emailData.headers['x-verify-code']; + assert.equal(emailData.subject, 'Confirm new sign-in to Firefox'); + assert.ok(uid, 'sent uid'); + assert.ok(tokenCode, 'sent verify code'); + assert.notEqual(tokenCode, emailCode, 'email and token codes are different'); - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { // Verify account is unverified because of sign-in attempt - assert.equal(status.verified, false, 'account is not verified,') - assert.equal(status.sessionVerified, false, 'account is not verified, unverified sign-in session') + assert.equal(status.verified, false, 'account is not verified,'); + assert.equal(status.sessionVerified, false, 'account is not verified, unverified sign-in session'); // Attempt to verify account reusing original email code - return client.verifyEmail(emailCode) + return client.verifyEmail(emailCode); } - ) + ); } - ) + ); it( 'sign-in verification email link', () => { - var email = server.uniqueEmail() - var password = 'something' - var client = null + var email = server.uniqueEmail(); + var password = 'something'; + var client = null; var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain, service: 'sync', resume: 'resumeToken', keys: true - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, options) .then( function (c) { - client = c + client = c; } ) .then( function () { - return client.login(options) + return client.login(options); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.uid, 'uid is in link') - assert.ok(query.code, 'code is in link') - assert.equal(query.service, options.service, 'service is in link') - assert.equal(query.resume, options.resume, 'resume is in link') - assert.equal(emailData.subject, 'Confirm new sign-in to Firefox') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.uid, 'uid is in link'); + assert.ok(query.code, 'code is in link'); + assert.equal(query.service, options.service, 'service is in link'); + assert.equal(query.resume, options.resume, 'resume is in link'); + assert.equal(emailData.subject, 'Confirm new sign-in to Firefox'); } - ) + ); } - ) + ); it( 'sign-in verification from different client', () => { - var email = server.uniqueEmail() - var password = 'something' - var client = null - var client2 = null + var email = server.uniqueEmail(); + var password = 'something'; + var client = null; + var client2 = null; var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain, service: 'sync', resume: 'resumeToken', keys: true - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, options) .then( function (c) { - client = c + client = c; } ) .then( function () { - return client.login(options) + return client.login(options); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.uid, 'uid is in link') - assert.ok(query.code, 'code is in link') - assert.equal(query.service, options.service, 'service is in link') - assert.equal(query.resume, options.resume, 'resume is in link') - assert.equal(emailData.subject, 'Confirm new sign-in to Firefox', 'email subject is correct') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.uid, 'uid is in link'); + assert.ok(query.code, 'code is in link'); + assert.equal(query.service, options.service, 'service is in link'); + assert.equal(query.resume, options.resume, 'resume is in link'); + assert.equal(emailData.subject, 'Confirm new sign-in to Firefox', 'email subject is correct'); } ) .then( function () { // Attempt to login from new location - return Client.login(config.publicUrl, email, password, server.mailbox, options) + return Client.login(config.publicUrl, email, password, server.mailbox, options); } ) .then( function (c) { - client2 = c + client2 = c; } ) .then( function () { - return client2.login(options) + return client2.login(options); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { // Verify account from client2 - return client2.verifyEmail(code, options) + return client2.verifyEmail(code, options); } ) .then( function () { - return client2.emailStatus() + return client2.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is not verified') + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is not verified'); } - ) + ); } - ) + ); it( 'certificate sign with unverified session, keys=true', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Attempt to login from new location - return client.login({keys:true}) + return client.login({keys:true}); } ) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { // Ensure unverified session - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is not verified') + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is not verified'); } ) .then( function () { // Attempt to get signed cert - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); } ) .then( function () { - assert.fail('should not have succeeded') + assert.fail('should not have succeeded'); }, function (err){ - assert.equal(err.errno, 138, 'Correct error number') - assert.equal(err.code, 400, 'Correct error code') - assert.equal(err.message, 'Unverified session', 'Correct error message') + assert.equal(err.errno, 138, 'Correct error number'); + assert.equal(err.code, 400, 'Correct error code'); + assert.equal(err.message, 'Unverified session', 'Correct error message'); } - ) + ); } - ) + ); it( 'certificate sign with unverified session, keys=false', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; // Initial account creation uses keys=true return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Attempt a second login, but don't request keys - return client.login({keys:false}) + return client.login({keys:false}); } ) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { // Ensure unverified session - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is not verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is not verified'); } ) .then( function () { // Attempt to get signed cert - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); } ) .then( function (cert) { - assert.equal(typeof(cert), 'string', 'cert exists') - var payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.iss, config.domain, 'issuer is correct') - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number') - assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible') - assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct') - assert.equal(payload['fxa-tokenVerified'], false, 'tokenVerified is not verified') + assert.equal(typeof(cert), 'string', 'cert exists'); + var payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.iss, config.domain, 'issuer is correct'); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number'); + assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible'); + assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct'); + assert.equal(payload['fxa-tokenVerified'], false, 'tokenVerified is not verified'); } - ) + ); } - ) + ); it( 'certificate sign with verified session', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Attempt to get signed cert - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); } ) .then( function (cert) { - assert.equal(typeof(cert), 'string', 'cert exists') - var payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.iss, config.domain, 'issuer is correct') - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number') - assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible') - assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct') - assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is verified') + assert.equal(typeof(cert), 'string', 'cert exists'); + var payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.iss, config.domain, 'issuer is correct'); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number'); + assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible'); + assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct'); + assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is verified'); } - ) + ); } - ) + ); it( 'account keys, return keys on verified account', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var tokenCode + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var tokenCode; return Client.create(config.publicUrl, email, password, {keys:true}) .then( function (c) { - client = c - return client.emailStatus() + client = c; + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, false, 'account email is not verified') - assert.equal(status.sessionVerified, false, 'account session is not verified') + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, false, 'account email is not verified'); + assert.equal(status.sessionVerified, false, 'account session is not verified'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.subject, 'Verify your Firefox Account') - tokenCode = emailData.headers['x-verify-code'] - assert.ok(tokenCode, 'sent verify code') + assert.equal(emailData.subject, 'Verify your Firefox Account'); + tokenCode = emailData.headers['x-verify-code']; + assert.ok(tokenCode, 'sent verify code'); } ) .then( function () { // Unverified accounts can not retrieve keys - return client.keys() + return client.keys(); } ) .catch(function(err){ - assert.equal(err.errno, 104, 'Correct error number') - assert.equal(err.code, 400, 'Correct error code') - assert.equal(err.message, 'Unverified account', 'Correct error message') + assert.equal(err.errno, 104, 'Correct error number'); + assert.equal(err.code, 400, 'Correct error code'); + assert.equal(err.message, 'Unverified account', 'Correct error message'); }) .then( function () { // Verify the account will set emails and tokens verified, which // will user to retrieve keys. - return client.verifyEmail(tokenCode) + return client.verifyEmail(tokenCode); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Once verified, keys can be returned - return client.keys() + return client.keys(); } ) .then( function (keys) { - assert.ok(keys.kA, 'has kA keys') - assert.ok(keys.kB, 'has kB keys') - assert.ok(keys.wrapKb, 'has wrapKb keys') + assert.ok(keys.kA, 'has kA keys'); + assert.ok(keys.kB, 'has kB keys'); + assert.ok(keys.wrapKb, 'has wrapKb keys'); } - ) + ); } - ) + ); it( 'account keys, return keys on verified login', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var tokenCode + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var tokenCode; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (c) { // Trigger confirm sign-in - client = c - return client.login({keys: true}) + client = c; + return client.login({keys: true}); } ) .then( function (c) { - client = c - return server.mailbox.waitForEmail(email) + client = c; + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.subject, 'Confirm new sign-in to Firefox') - tokenCode = emailData.headers['x-verify-code'] - assert.ok(tokenCode, 'sent verify code') + assert.equal(emailData.subject, 'Confirm new sign-in to Firefox'); + tokenCode = emailData.headers['x-verify-code']; + assert.ok(tokenCode, 'sent verify code'); } ) .then( function () { - return client.keys() + return client.keys(); } ) .then(() => assert(false), function(err){ // Because of unverified sign-in, requests for keys will fail - assert.equal(err.errno, 104, 'Correct error number') - assert.equal(err.code, 400, 'Correct error code') - assert.equal(err.message, 'Unverified account', 'Correct error message') + assert.equal(err.errno, 104, 'Correct error number'); + assert.equal(err.code, 400, 'Correct error code'); + assert.equal(err.message, 'Unverified account', 'Correct error message'); }) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { // Verify status of account, only email should be verified - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is not verified') + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is not verified'); } ) .then( function () { // Verify the account will set tokens verified. - return client.verifyEmail(tokenCode) + return client.verifyEmail(tokenCode); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Can retrieve keys now that account tokens verified - return client.keys() + return client.keys(); } ) .then( function (keys) { - assert.ok(keys.kA, 'has kA keys') - assert.ok(keys.kB, 'has kB keys') - assert.ok(keys.wrapKb, 'has wrapKb keys') + assert.ok(keys.kA, 'has kA keys'); + assert.ok(keys.kB, 'has kB keys'); + assert.ok(keys.wrapKb, 'has wrapKb keys'); } - ) + ); } - ) + ); it( 'unverified account is verified on sign-in confirmation', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var tokenCode + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var tokenCode; return Client.create(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (c) { - client = c - return server.mailbox.waitForEmail(email) + client = c; + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-template-name'], 'verifyEmail') - tokenCode = emailData.headers['x-verify-code'] - assert.ok(tokenCode, 'sent verify code') + assert.equal(emailData.headers['x-template-name'], 'verifyEmail'); + tokenCode = emailData.headers['x-verify-code']; + assert.ok(tokenCode, 'sent verify code'); } ) .then( function () { - return client.login({keys:true}) + return client.login({keys:true}); } ) .then( function (c) { - client = c - return server.mailbox.waitForEmail(email) + client = c; + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.headers['x-template-name'], 'verifyEmail') - const siginToken = emailData.headers['x-verify-code'] - assert.notEqual(tokenCode, siginToken, 'login codes should not match') + assert.equal(emailData.headers['x-template-name'], 'verifyEmail'); + const siginToken = emailData.headers['x-verify-code']; + assert.notEqual(tokenCode, siginToken, 'login codes should not match'); - return client.verifyEmail(siginToken) + return client.verifyEmail(siginToken); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } ) .then( function () { // Can retrieve keys now that account tokens verified - return client.keys() + return client.keys(); } ) .then( function (keys) { - assert.ok(keys.kA, 'has kA keys') - assert.ok(keys.kB, 'has kB keys') - assert.ok(keys.wrapKb, 'has wrapKb keys') + assert.ok(keys.kA, 'has kA keys'); + assert.ok(keys.kB, 'has kB keys'); + assert.ok(keys.wrapKb, 'has wrapKb keys'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_status_tests.js b/test/remote/account_status_tests.js index 343aa707..d70f0d75 100644 --- a/test/remote/account_status_tests.js +++ b/test/remote/account_status_tests.js @@ -2,23 +2,23 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote account status', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'account status with existing account', @@ -26,16 +26,16 @@ describe('remote account status', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password') .then( function (c) { - return c.api.accountStatus(c.uid) + return c.api.accountStatus(c.uid); } ) .then( function (response) { - assert.ok(response.exists, 'account exists') + assert.ok(response.exists, 'account exists'); } - ) + ); } - ) + ); it( 'account status includes locale when authenticated', @@ -43,16 +43,16 @@ describe('remote account status', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then( function (c) { - return c.api.accountStatus(c.uid, c.sessionToken) + return c.api.accountStatus(c.uid, c.sessionToken); } ) .then( function (response) { - assert.equal(response.locale, 'en-US', 'locale is stored') + assert.equal(response.locale, 'en-US', 'locale is stored'); } - ) + ); } - ) + ); it( 'account status does not include locale when unauthenticated', @@ -60,16 +60,16 @@ describe('remote account status', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then( function (c) { - return c.api.accountStatus(c.uid) + return c.api.accountStatus(c.uid); } ) .then( function (response) { - assert.ok(! response.locale, 'locale is not present') + assert.ok(! response.locale, 'locale is not present'); } - ) + ); } - ) + ); it( 'account status unauthenticated with no uid returns an error', @@ -77,114 +77,114 @@ describe('remote account status', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password', { lang: 'en-US' }) .then( function (c) { - return c.api.accountStatus() + return c.api.accountStatus(); } ) .then( function () { - assert(false, 'should get an error') + assert(false, 'should get an error'); }, function (e) { - assert.equal(e.code, 400, 'correct error status code') - assert.equal(e.errno, 108, 'correct errno') + assert.equal(e.code, 400, 'correct error status code'); + assert.equal(e.errno, 108, 'correct errno'); } - ) + ); } - ) + ); it( 'account status with non-existing account', () => { - var api = new Client.Api(config.publicUrl) + var api = new Client.Api(config.publicUrl); return api.accountStatus('0123456789ABCDEF0123456789ABCDEF') .then( function (response) { - assert.ok(! response.exists, 'account does not exist') + assert.ok(! response.exists, 'account does not exist'); } - ) + ); } - ) + ); it( 'account status by email with existing account', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'password') .then( function (c) { - return c.api.accountStatusByEmail(email) + return c.api.accountStatusByEmail(email); } ) .then( function (response) { - assert.ok(response.exists, 'account exists') + assert.ok(response.exists, 'account exists'); } - ) + ); } - ) + ); it( 'account status by email with non-existing account', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'password') .then( function (c) { - var nonExistEmail = server.uniqueEmail() - return c.api.accountStatusByEmail(nonExistEmail) + var nonExistEmail = server.uniqueEmail(); + return c.api.accountStatusByEmail(nonExistEmail); } ) .then( function (response) { - assert.ok(! response.exists, 'account does not exist') + assert.ok(! response.exists, 'account does not exist'); } - ) + ); } - ) + ); it( 'account status by email with an invalid email', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'password') .then( function (c) { - var invalidEmail = 'notAnEmail' - return c.api.accountStatusByEmail(invalidEmail) + var invalidEmail = 'notAnEmail'; + return c.api.accountStatusByEmail(invalidEmail); } ) .then( function () { - assert(false, 'should not have successful request') + assert(false, 'should not have successful request'); }, function (err) { - assert.equal(err.code, 400) - assert.equal(err.errno, 107) - assert.equal(err.message, 'Invalid parameter in request body') + assert.equal(err.code, 400); + assert.equal(err.errno, 107); + assert.equal(err.message, 'Invalid parameter in request body'); } - ) + ); } - ) + ); it( 'account status by email works with unicode email address', () => { - var email = server.uniqueUnicodeEmail() + var email = server.uniqueUnicodeEmail(); return Client.create(config.publicUrl, email, 'password') .then( function (c) { - return c.api.accountStatusByEmail(email) + return c.api.accountStatusByEmail(email); } ) .then( function (response) { - assert.ok(response.exists, 'account exists') + assert.ok(response.exists, 'account exists'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/account_unlock_tests.js b/test/remote/account_unlock_tests.js index 7865d4bf..d50fec13 100644 --- a/test/remote/account_unlock_tests.js +++ b/test/remote/account_unlock_tests.js @@ -2,23 +2,23 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote account unlock', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( '/account/lock is no longer supported', @@ -26,19 +26,19 @@ describe('remote account unlock', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password') .then( function (c) { - return c.lockAccount() + return c.lockAccount(); } ) .then( function () { - assert(false, 'should get an error') + assert(false, 'should get an error'); }, function (e) { - assert.equal(e.code, 410, 'correct error status code') + assert.equal(e.code, 410, 'correct error status code'); } - ) + ); } - ) + ); it( '/account/unlock/resend_code is no longer supported', @@ -46,19 +46,19 @@ describe('remote account unlock', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password') .then( function (c) { - return c.resendAccountUnlockCode('en') + return c.resendAccountUnlockCode('en'); } ) .then( function () { - assert(false, 'should get an error') + assert(false, 'should get an error'); }, function (e) { - assert.equal(e.code, 410, 'correct error status code') + assert.equal(e.code, 410, 'correct error status code'); } - ) + ); } - ) + ); it( '/account/unlock/verify_code is no longer supported', @@ -66,21 +66,21 @@ describe('remote account unlock', function() { return Client.create(config.publicUrl, server.uniqueEmail(), 'password') .then( function (c) { - return c.verifyAccountUnlockCode('bigscaryuid', 'bigscarycode') + return c.verifyAccountUnlockCode('bigscaryuid', 'bigscarycode'); } ) .then( function () { - assert(false, 'should get an error') + assert(false, 'should get an error'); }, function (e) { - assert.equal(e.code, 410, 'correct error status code') + assert.equal(e.code, 410, 'correct error status code'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/base_path_tests.js b/test/remote/base_path_tests.js index 44ebe084..4cfe9f10 100644 --- a/test/remote/base_path_tests.js +++ b/test/remote/base_path_tests.js @@ -2,76 +2,76 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const P = require('../../lib/promise') -const request = P.promisify(require('request'), { multiArgs: true }) +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const P = require('../../lib/promise'); +const request = P.promisify(require('request'), { multiArgs: true }); describe('remote base path', function() { - this.timeout(15000) - let server, config + this.timeout(15000); + let server, config; before(() => { - config = require('../../config').getProperties() - config.publicUrl = 'http://127.0.0.1:9000/auth' + config = require('../../config').getProperties(); + config.publicUrl = 'http://127.0.0.1:9000/auth'; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); function testVersionRoute(path) { return () => { return request(config.publicUrl + path) .spread((res, body) => { - assert.equal(res.statusCode, 200) - var json = JSON.parse(body) - assert.deepEqual(Object.keys(json), ['version', 'commit', 'source']) - assert.equal(json.version, require('../../package.json').version, 'package version') - assert.ok(json.source && json.source !== 'unknown', 'source repository') + assert.equal(res.statusCode, 200); + var json = JSON.parse(body); + assert.deepEqual(Object.keys(json), ['version', 'commit', 'source']); + assert.equal(json.version, require('../../package.json').version, 'package version'); + assert.ok(json.source && json.source !== 'unknown', 'source repository'); // check that the git hash just looks like a hash - assert.ok(json.commit.match(/^[0-9a-f]{40}$/), 'The git hash actually looks like one') - }) - } + assert.ok(json.commit.match(/^[0-9a-f]{40}$/), 'The git hash actually looks like one'); + }); + }; } it( 'alternate base path', () => { - var email = Math.random() + '@example.com' - var password = 'ok' + var email = Math.random() + '@example.com'; + var password = 'ok'; // if this doesn't crash, we're all good - return Client.create(config.publicUrl, email, password, server.mailbox) + return Client.create(config.publicUrl, email, password, server.mailbox); } - ) + ); it( '.well-known did not move', () => { return request('http://127.0.0.1:9000/.well-known/browserid') .spread((res, body) => { - assert.equal(res.statusCode, 200) - var json = JSON.parse(body) - assert.equal(json.authentication, '/.well-known/browserid/nonexistent.html') - }) + assert.equal(res.statusCode, 200); + var json = JSON.parse(body); + assert.equal(json.authentication, '/.well-known/browserid/nonexistent.html'); + }); } - ) + ); it( '"/" returns valid version information', testVersionRoute('/') - ) + ); it( '"/__version__" returns valid version information', testVersionRoute('/__version__') - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/certificate_sign_tests.js b/test/remote/certificate_sign_tests.js index 370c6c70..4f069564 100644 --- a/test/remote/certificate_sign_tests.js +++ b/test/remote/certificate_sign_tests.js @@ -2,215 +2,215 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() -var jwtool = require('fxa-jwtool') +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); +var jwtool = require('fxa-jwtool'); -var config = require('../../config').getProperties() -config.redis.sessionTokens.enabled = false -var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile) +var config = require('../../config').getProperties(); +config.redis.sessionTokens.enabled = false; +var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile); var publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' -} +}; describe('remote certificate sign', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'certificate sign', () => { - const email = server.uniqueEmail() - const password = 'allyourbasearebelongtous' - const duration = 1000 * 60 * 60 * 24 // 24 hours - let client = null + const email = server.uniqueEmail(); + const password = 'allyourbasearebelongtous'; + const duration = 1000 * 60 * 60 * 24; // 24 hours + let client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then(c => { - client = c - return client.sign(publicKey, duration) + client = c; + return client.sign(publicKey, duration); }) .then(cert => { - assert.equal(typeof(cert), 'string', 'cert exists') - const payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.iss, config.domain, 'issuer is correct') - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number') - assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible') - assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct') - assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is correct') - assert.deepEqual(payload['fxa-amr'].sort(), ['email', 'pwd'], 'amr values are correct') - assert.equal(payload['fxa-aal'], 1, 'aal value is correct') - assert.ok(new Date() - new Date(payload['fxa-profileChangedAt'] * 1000) < 1000 * 60 * 60, 'profileChangedAt is plausible') - }) + assert.equal(typeof(cert), 'string', 'cert exists'); + const payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.iss, config.domain, 'issuer is correct'); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number'); + assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible'); + assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct'); + assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is correct'); + assert.deepEqual(payload['fxa-amr'].sort(), ['email', 'pwd'], 'amr values are correct'); + assert.equal(payload['fxa-aal'], 1, 'aal value is correct'); + assert.ok(new Date() - new Date(payload['fxa-profileChangedAt'] * 1000) < 1000 * 60 * 60, 'profileChangedAt is plausible'); + }); } - ) + ); it( 'certificate sign with TOTP', () => { - const email = server.uniqueEmail() - const password = 'allyourbasearebelongtous' - const duration = 1000 * 60 * 60 * 24 // 24 hours - let client = null + const email = server.uniqueEmail(); + const password = 'allyourbasearebelongtous'; + const duration = 1000 * 60 * 60 * 24; // 24 hours + let client = null; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys:true}) .then(c => { - client = c - return client.sign(publicKey, duration) + client = c; + return client.sign(publicKey, duration); }) .then(cert => { - assert.equal(typeof(cert), 'string', 'cert exists') - const payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.iss, config.domain, 'issuer is correct') - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number') - assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible') - assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct') - assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is correct') - assert.deepEqual(payload['fxa-amr'].sort(), ['otp', 'pwd'], 'amr values are correct') - assert.equal(payload['fxa-aal'], 2, 'aal value is correct') - assert.ok(new Date() - new Date(payload['fxa-profileChangedAt'] * 1000) < 1000 * 60 * 60, 'profileChangedAt is plausible') - }) + assert.equal(typeof(cert), 'string', 'cert exists'); + const payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.iss, config.domain, 'issuer is correct'); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + assert.ok(payload['fxa-generation'] > 0, 'cert has non-zero generation number'); + assert.ok(new Date() - new Date(payload['fxa-lastAuthAt'] * 1000) < 1000 * 60 * 60, 'lastAuthAt is plausible'); + assert.equal(payload['fxa-verifiedEmail'], email, 'verifiedEmail is correct'); + assert.equal(payload['fxa-tokenVerified'], true, 'tokenVerified is correct'); + assert.deepEqual(payload['fxa-amr'].sort(), ['otp', 'pwd'], 'amr values are correct'); + assert.equal(payload['fxa-aal'], 2, 'aal value is correct'); + assert.ok(new Date() - new Date(payload['fxa-profileChangedAt'] * 1000) < 1000 * 60 * 60, 'profileChangedAt is plausible'); + }); } - ) + ); it( 'certificate sign requires a verified account', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null - var duration = 1000 * 60 * 60 * 24 // 24 hours + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; + var duration = 1000 * 60 * 60 * 24; // 24 hours return Client.create(config.publicUrl, email, password) .then( function (c) { - client = c - return client.sign(publicKey, duration) + client = c; + return client.sign(publicKey, duration); } ) .then( function (cert) { - assert(false, 'should not be able to sign with unverified account') + assert(false, 'should not be able to sign with unverified account'); }, function (err) { - assert.equal(err.errno, 104, 'should get an unverifiedAccount error') + assert.equal(err.errno, 104, 'should get an unverifiedAccount error'); } - ) + ); } - ) + ); it( '/certificate/sign inputs', () => { - var email = server.uniqueEmail() - var password = '123456' - var client = null + var email = server.uniqueEmail(); + var password = '123456'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - client = c + client = c; // string as publicKey - return client.sign('tada', 1000) + return client.sign('tada', 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'string as publicKey') + assert.equal(err.code, 400, 'string as publicKey'); // empty object as publicKey - return client.sign({}, 1000) + return client.sign({}, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'empty object as publicKey') + assert.equal(err.code, 400, 'empty object as publicKey'); // undefined duration - return client.sign({ algorithm: 'RS', n: 'x', e: 'y' }, undefined) + return client.sign({ algorithm: 'RS', n: 'x', e: 'y' }, undefined); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'undefined duration') + assert.equal(err.code, 400, 'undefined duration'); // missing publicKey arguments (e) - return client.sign({ algorithm: 'RS', n: 'x' }, 1000) + return client.sign({ algorithm: 'RS', n: 'x' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (e)') + assert.equal(err.code, 400, 'missing publicKey arguments (e)'); // missing publicKey arguments (n) - return client.sign({ algorithm: 'RS', e: 'x' }, 1000) + return client.sign({ algorithm: 'RS', e: 'x' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (n)') + assert.equal(err.code, 400, 'missing publicKey arguments (n)'); // missing publicKey arguments (y) - return client.sign({ algorithm: 'DS', p: 'p', q: 'q', g: 'g' }, 1000) + return client.sign({ algorithm: 'DS', p: 'p', q: 'q', g: 'g' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (y)') + assert.equal(err.code, 400, 'missing publicKey arguments (y)'); // missing publicKey arguments (p) - return client.sign({ algorithm: 'DS', y: 'y', q: 'q', g: 'g' }, 1000) + return client.sign({ algorithm: 'DS', y: 'y', q: 'q', g: 'g' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (p)') + assert.equal(err.code, 400, 'missing publicKey arguments (p)'); // missing publicKey arguments (q) - return client.sign({ algorithm: 'DS', y: 'y', p: 'p', g: 'g' }, 1000) + return client.sign({ algorithm: 'DS', y: 'y', p: 'p', g: 'g' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (q)') + assert.equal(err.code, 400, 'missing publicKey arguments (q)'); // missing publicKey arguments (g) - return client.sign({ algorithm: 'DS', y: 'y', p: 'p', q: 'q' }, 1000) + return client.sign({ algorithm: 'DS', y: 'y', p: 'p', q: 'q' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'missing publicKey arguments (g)') + assert.equal(err.code, 400, 'missing publicKey arguments (g)'); // invalid algorithm - return client.sign({ algorithm: 'NSA' }, 1000) + return client.sign({ algorithm: 'NSA' }, 1000); } ) .then( () => assert(false), function (err) { - assert.equal(err.code, 400, 'invalid algorithm') + assert.equal(err.code, 400, 'invalid algorithm'); } - ) + ); } - ) + ); it( 'no payload', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var duration = 1000 * 60 * 60 * 24 // 24 hours + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var duration = 1000 * 60 * 60 * 24; // 24 hours return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (client) { @@ -219,9 +219,9 @@ describe('remote certificate sign', function() { function hijackPayload(options) { // we want the payload hash in the auth header // but no payload in the request body - options.json = true + options.json = true; } - ) + ); return client.api.Token.SessionToken.fromHex(client.sessionToken) .then( function (token) { @@ -233,21 +233,21 @@ describe('remote certificate sign', function() { publicKey: publicKey, duration: duration } - ) + ); } - ) + ); } ) .then( () => assert(false), function (err) { - assert.equal(err.errno, 109, 'Missing payload authentication') + assert.equal(err.errno, 109, 'Missing payload authentication'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/concurrent_tests.js b/test/remote/concurrent_tests.js index f1f488b9..8046473a 100644 --- a/test/remote/concurrent_tests.js +++ b/test/remote/concurrent_tests.js @@ -2,60 +2,60 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() -var P = require('../../lib/promise') +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); +var P = require('../../lib/promise'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote concurrect', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { - config.verifierVersion = 1 + config.verifierVersion = 1; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'concurrent create requests', () => { - var email = server.uniqueEmail() - var password = 'abcdef' + var email = server.uniqueEmail(); + var password = 'abcdef'; // Two shall enter, only one shall survive! - var r1 = Client.create(config.publicUrl, email, password, server.mailbox) - var r2 = Client.create(config.publicUrl, email, password, server.mailbox) + var r1 = Client.create(config.publicUrl, email, password, server.mailbox); + var r2 = Client.create(config.publicUrl, email, password, server.mailbox); return P.all( [r1, r2] ) .then( () => assert(false, 'created both accounts'), function (err) { - assert.equal(err.errno, 101, 'account exists') + assert.equal(err.errno, 101, 'account exists'); // Note that P.all fails fast when one of the requests fails, // but we have to wait for *both* to complete before tearing // down the test infrastructure. Bleh. if (! r1.isRejected()) { - return r1 + return r1; } else { - return r2 + return r2; } } ).then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/db_tests.js b/test/remote/db_tests.js index 88ba5e7d..ac83ca5d 100644 --- a/test/remote/db_tests.js +++ b/test/remote/db_tests.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const base64url = require('base64url') -const config = require('../../config').getProperties() -const crypto = require('crypto') -const P = require('../../lib/promise') -const sinon = require('sinon') -const TestServer = require('../test_server') -const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength) -const uuid = require('uuid') +const { assert } = require('chai'); +const base64url = require('base64url'); +const config = require('../../config').getProperties(); +const crypto = require('crypto'); +const P = require('../../lib/promise'); +const sinon = require('sinon'); +const TestServer = require('../test_server'); +const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength); +const uuid = require('uuid'); -const log = { trace () {}, info () {}, error () {} } +const log = { trace () {}, info () {}, error () {} }; const lastAccessTimeUpdates = { enabled: true, sampleRate: 1 -} +}; const Token = require('../../lib/tokens')(log, { lastAccessTimeUpdates: lastAccessTimeUpdates, tokenLifetimes: { sessionTokenWithoutDevice: 2419200000 } -}) +}); const tokenPruning = { enabled: true, maxAge: 1000 * 60 * 60 -} +}; const DB = require('../../lib/db')({ lastAccessTimeUpdates, signinCodeSize: config.signinCodeSize, @@ -41,34 +41,34 @@ const DB = require('../../lib/db')({ }, tokenLifetimes: {}, tokenPruning -}, log, Token, UnblockCode) +}, log, Token, UnblockCode); const redis = require('redis').createClient({ host: config.redis.host, port: config.redis.port, prefix: config.redis.sessionTokens.prefix, enable_offline_queue: false -}) -P.promisifyAll(redis) +}); +P.promisifyAll(redis); -const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex') -const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex') +const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex'); +const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'); -let account, secondEmail +let account, secondEmail; describe('remote db', function() { - this.timeout(20000) - let dbServer, db + this.timeout(20000); + let dbServer, db; before(() => { return TestServer.start(config) .then(s => { - dbServer = s - return DB.connect(config[config.db.backend]) + dbServer = s; + return DB.connect(config[config.db.backend]); }) .then(x => { - db = x - }) - }) + db = x; + }); + }); beforeEach(() => { account = { @@ -82,13 +82,13 @@ describe('remote db', function() { kA: zeroBuffer32, wrapWrapKb: zeroBuffer32, tokenVerificationId: zeroBuffer16 - } + }; return db.createAccount(account) .then((account) => { - assert.deepEqual(account.uid, account.uid, 'account.uid is the same as the input account.uid') + assert.deepEqual(account.uid, account.uid, 'account.uid is the same as the input account.uid'); - secondEmail = dbServer.uniqueEmail() + secondEmail = dbServer.uniqueEmail(); const emailData = { email: secondEmail, emailCode: crypto.randomBytes(16).toString('hex'), @@ -96,137 +96,137 @@ describe('remote db', function() { isVerified: true, isPrimary: false, uid: account.uid - } - return db.createEmail(account.uid, emailData) + }; + return db.createEmail(account.uid, emailData); }) // Ensure redis is empty for the uid - .then(() => redis.delAsync(account.uid)) - }) + .then(() => redis.delAsync(account.uid)); + }); it( 'ping', () => { - return db.ping() + return db.ping(); } - ) + ); it( 'account creation', () => { return db.accountExists(account.email) .then(function(exists) { - assert.ok(exists, 'account exists for this email address') + assert.ok(exists, 'account exists for this email address'); }) .then(function() { - return db.account(account.uid) + return db.account(account.uid); }) .then(function(account) { - assert.deepEqual(account.uid, account.uid, 'uid') - assert.equal(account.email, account.email, 'email') - assert.deepEqual(account.emailCode, account.emailCode, 'emailCode') - assert.equal(account.emailVerified, account.emailVerified, 'emailVerified') - assert.deepEqual(account.kA, account.kA, 'kA') - assert.deepEqual(account.wrapWrapKb, account.wrapWrapKb, 'wrapWrapKb') - assert(! account.verifyHash, 'verifyHash') - assert.deepEqual(account.authSalt, account.authSalt, 'authSalt') - assert.equal(account.verifierVersion, account.verifierVersion, 'verifierVersion') - assert.ok(account.createdAt, 'createdAt') - }) + assert.deepEqual(account.uid, account.uid, 'uid'); + assert.equal(account.email, account.email, 'email'); + assert.deepEqual(account.emailCode, account.emailCode, 'emailCode'); + assert.equal(account.emailVerified, account.emailVerified, 'emailVerified'); + assert.deepEqual(account.kA, account.kA, 'kA'); + assert.deepEqual(account.wrapWrapKb, account.wrapWrapKb, 'wrapWrapKb'); + assert(! account.verifyHash, 'verifyHash'); + assert.deepEqual(account.authSalt, account.authSalt, 'authSalt'); + assert.equal(account.verifierVersion, account.verifierVersion, 'verifierVersion'); + assert.ok(account.createdAt, 'createdAt'); + }); } - ) + ); it( 'session token handling', () => { - let tokenId + let tokenId; // Fetch all sessions for the account return db.sessions(account.uid) .then(sessions => { - assert.ok(Array.isArray(sessions), 'sessions is array') - assert.equal(sessions.length, 0, 'sessions is empty') + assert.ok(Array.isArray(sessions), 'sessions is array'); + assert.equal(sessions.length, 0, 'sessions is empty'); // Fetch the email record - return db.emailRecord(account.email) + return db.emailRecord(account.email); }) .then(emailRecord => { - emailRecord.createdAt = Date.now() - 1000 - emailRecord.tokenVerificationId = account.tokenVerificationId - emailRecord.uaBrowser = 'Firefox' - emailRecord.uaBrowserVersion = '41' - emailRecord.uaOS = 'Mac OS X' - emailRecord.uaOSVersion = '10.10' - emailRecord.uaDeviceType = emailRecord.uaFormFactor = null + emailRecord.createdAt = Date.now() - 1000; + emailRecord.tokenVerificationId = account.tokenVerificationId; + emailRecord.uaBrowser = 'Firefox'; + emailRecord.uaBrowserVersion = '41'; + emailRecord.uaOS = 'Mac OS X'; + emailRecord.uaOSVersion = '10.10'; + emailRecord.uaDeviceType = emailRecord.uaFormFactor = null; // Create a session token - return db.createSessionToken(emailRecord) + return db.createSessionToken(emailRecord); }) .then(sessionToken => { - assert.deepEqual(sessionToken.uid, account.uid) - tokenId = sessionToken.id + assert.deepEqual(sessionToken.uid, account.uid); + tokenId = sessionToken.id; // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions contains one item') - assert.equal(Object.keys(sessions[0]).length, 20, 'session has correct number of properties') - assert.equal(typeof sessions[0].id, 'string', 'id property is not a buffer') - assert.equal(sessions[0].uid, account.uid, 'uid property is correct') - assert.ok(sessions[0].createdAt >= account.createdAt, 'createdAt property seems correct') - assert.equal(sessions[0].uaBrowser, 'Firefox', 'uaBrowser property is correct') - assert.equal(sessions[0].uaBrowserVersion, '41', 'uaBrowserVersion property is correct') - assert.equal(sessions[0].uaOS, 'Mac OS X', 'uaOS property is correct') - assert.equal(sessions[0].uaOSVersion, '10.10', 'uaOSVersion property is correct') - assert.equal(sessions[0].uaDeviceType, null, 'uaDeviceType property is correct') - assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct') - assert.equal(sessions[0].lastAccessTime, sessions[0].createdAt, 'lastAccessTime property is correct') - assert.equal(sessions[0].authAt, sessions[0].createdAt, 'authAt property is correct') - assert.equal(sessions[0].location, undefined, 'location property is correct') - assert.deepEqual(sessions[0].deviceId, null, 'deviceId property is correct') - assert.deepEqual(sessions[0].deviceAvailableCommands, null, 'deviceAvailableCommands property is correct') + assert.equal(sessions.length, 1, 'sessions contains one item'); + assert.equal(Object.keys(sessions[0]).length, 20, 'session has correct number of properties'); + assert.equal(typeof sessions[0].id, 'string', 'id property is not a buffer'); + assert.equal(sessions[0].uid, account.uid, 'uid property is correct'); + assert.ok(sessions[0].createdAt >= account.createdAt, 'createdAt property seems correct'); + assert.equal(sessions[0].uaBrowser, 'Firefox', 'uaBrowser property is correct'); + assert.equal(sessions[0].uaBrowserVersion, '41', 'uaBrowserVersion property is correct'); + assert.equal(sessions[0].uaOS, 'Mac OS X', 'uaOS property is correct'); + assert.equal(sessions[0].uaOSVersion, '10.10', 'uaOSVersion property is correct'); + assert.equal(sessions[0].uaDeviceType, null, 'uaDeviceType property is correct'); + assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct'); + assert.equal(sessions[0].lastAccessTime, sessions[0].createdAt, 'lastAccessTime property is correct'); + assert.equal(sessions[0].authAt, sessions[0].createdAt, 'authAt property is correct'); + assert.equal(sessions[0].location, undefined, 'location property is correct'); + assert.deepEqual(sessions[0].deviceId, null, 'deviceId property is correct'); + assert.deepEqual(sessions[0].deviceAvailableCommands, null, 'deviceAvailableCommands property is correct'); // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { - assert.equal(sessionToken.id, tokenId, 'token id matches') - assert.equal(sessionToken.uaBrowser, 'Firefox') - assert.equal(sessionToken.uaBrowserVersion, '41') - assert.equal(sessionToken.uaOS, 'Mac OS X') - assert.equal(sessionToken.uaOSVersion, '10.10') - assert.equal(sessionToken.uaDeviceType, null) - assert.equal(sessionToken.lastAccessTime, sessionToken.createdAt) - assert.equal(sessionToken.uid, account.uid) - assert.equal(sessionToken.email, account.email) - assert.equal(sessionToken.emailCode, account.emailCode) - assert.equal(sessionToken.emailVerified, account.emailVerified) - assert.equal(sessionToken.lifetime < Infinity, true) + assert.equal(sessionToken.id, tokenId, 'token id matches'); + assert.equal(sessionToken.uaBrowser, 'Firefox'); + assert.equal(sessionToken.uaBrowserVersion, '41'); + assert.equal(sessionToken.uaOS, 'Mac OS X'); + assert.equal(sessionToken.uaOSVersion, '10.10'); + assert.equal(sessionToken.uaDeviceType, null); + assert.equal(sessionToken.lastAccessTime, sessionToken.createdAt); + assert.equal(sessionToken.uid, account.uid); + assert.equal(sessionToken.email, account.email); + assert.equal(sessionToken.emailCode, account.emailCode); + assert.equal(sessionToken.emailVerified, account.emailVerified); + assert.equal(sessionToken.lifetime < Infinity, true); // Disable session token updates - lastAccessTimeUpdates.enabled = false + lastAccessTimeUpdates.enabled = false; // Attempt to update the session token - return db.touchSessionToken(sessionToken, {}) + return db.touchSessionToken(sessionToken, {}); }) .then(result => { - assert.equal(result, undefined) + assert.equal(result, undefined); // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions contains one item') - assert.equal(Object.keys(sessions[0]).length, 20, 'session has correct number of properties') - assert.equal(sessions[0].uid, account.uid, 'uid property is correct') - assert.equal(sessions[0].lastAccessTime, undefined, 'lastAccessTime not reported if disabled') - assert.equal(sessions[0].location, undefined, 'location property is correct') + assert.equal(sessions.length, 1, 'sessions contains one item'); + assert.equal(Object.keys(sessions[0]).length, 20, 'session has correct number of properties'); + assert.equal(sessions[0].uid, account.uid, 'uid property is correct'); + assert.equal(sessions[0].lastAccessTime, undefined, 'lastAccessTime not reported if disabled'); + assert.equal(sessions[0].location, undefined, 'location property is correct'); // Re-enable session token updates - lastAccessTimeUpdates.enabled = true + lastAccessTimeUpdates.enabled = true; // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { // Update the session token @@ -241,25 +241,25 @@ describe('remote db', function() { stateCode: 'EN' }, timeZone: 'Europe/London' - }) + }); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions contains one item') - assert.equal(sessions[0].uid, account.uid, 'uid property is correct') - assert.ok(sessions[0].lastAccessTime > sessions[0].createdAt, 'lastAccessTime is correct') - assert.equal(sessions[0].location.city, 'Bournemouth', 'city is correct') - assert.equal(sessions[0].location.country, 'United Kingdom', 'country is correct') - assert.equal(sessions[0].location.countryCode, 'GB', 'countryCode is correct') - assert.equal(sessions[0].location.state, 'England', 'state is correct') - assert.equal(sessions[0].location.stateCode, 'EN', 'stateCode is correct') - assert.equal(sessions[0].location.timeZone, undefined, 'timeZone is not set') + assert.equal(sessions.length, 1, 'sessions contains one item'); + assert.equal(sessions[0].uid, account.uid, 'uid property is correct'); + assert.ok(sessions[0].lastAccessTime > sessions[0].createdAt, 'lastAccessTime is correct'); + assert.equal(sessions[0].location.city, 'Bournemouth', 'city is correct'); + assert.equal(sessions[0].location.country, 'United Kingdom', 'country is correct'); + assert.equal(sessions[0].location.countryCode, 'GB', 'countryCode is correct'); + assert.equal(sessions[0].location.state, 'England', 'state is correct'); + assert.equal(sessions[0].location.stateCode, 'EN', 'stateCode is correct'); + assert.equal(sessions[0].location.timeZone, undefined, 'timeZone is not set'); // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { // Update the session token @@ -270,134 +270,134 @@ describe('remote db', function() { uaOSVersion: '4.4', uaDeviceType: 'mobile', uaFormFactor: null - }), {}) + }), {}); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions still contains one item') - assert.equal(sessions[0].uaBrowser, 'Firefox Mobile', 'uaBrowser property is correct') - assert.equal(sessions[0].uaBrowserVersion, '42', 'uaBrowserVersion property is correct') - assert.equal(sessions[0].uaOS, 'Android', 'uaOS property is correct') - assert.equal(sessions[0].uaOSVersion, '4.4', 'uaOSVersion property is correct') - assert.equal(sessions[0].uaDeviceType, 'mobile', 'uaDeviceType property is correct') - assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct') - assert.equal(sessions[0].location, null, 'location property is correct') + assert.equal(sessions.length, 1, 'sessions still contains one item'); + assert.equal(sessions[0].uaBrowser, 'Firefox Mobile', 'uaBrowser property is correct'); + assert.equal(sessions[0].uaBrowserVersion, '42', 'uaBrowserVersion property is correct'); + assert.equal(sessions[0].uaOS, 'Android', 'uaOS property is correct'); + assert.equal(sessions[0].uaOSVersion, '4.4', 'uaOSVersion property is correct'); + assert.equal(sessions[0].uaDeviceType, 'mobile', 'uaDeviceType property is correct'); + assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct'); + assert.equal(sessions[0].location, null, 'location property is correct'); }) .then(() => { // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { // this returns previously stored data since sessionToken doesnt read from cache - assert.equal(sessionToken.uaBrowser, 'Firefox') - assert.equal(sessionToken.uaBrowserVersion, '41') - assert.equal(sessionToken.uaOS, 'Mac OS X') - assert.equal(sessionToken.uaOSVersion, '10.10') - assert.equal(sessionToken.lastAccessTime, sessionToken.createdAt) + assert.equal(sessionToken.uaBrowser, 'Firefox'); + assert.equal(sessionToken.uaBrowserVersion, '41'); + assert.equal(sessionToken.uaOS, 'Mac OS X'); + assert.equal(sessionToken.uaOSVersion, '10.10'); + assert.equal(sessionToken.lastAccessTime, sessionToken.createdAt); // Attempt to prune a session token that is younger than maxAge - sessionToken.createdAt = Date.now() - tokenPruning.maxAge + 10000 - return db.pruneSessionTokens(account.uid, [ sessionToken ]) + sessionToken.createdAt = Date.now() - tokenPruning.maxAge + 10000; + return db.pruneSessionTokens(account.uid, [ sessionToken ]); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions still contains one item') - assert.equal(sessions[0].uaBrowser, 'Firefox Mobile', 'uaBrowser property is correct') - assert.equal(sessions[0].uaBrowserVersion, '42', 'uaBrowserVersion property is correct') - assert.equal(sessions[0].uaOS, 'Android', 'uaOS property is correct') - assert.equal(sessions[0].uaOSVersion, '4.4', 'uaOSVersion property is correct') - assert.equal(sessions[0].uaDeviceType, 'mobile', 'uaDeviceType property is correct') - assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct') + assert.equal(sessions.length, 1, 'sessions still contains one item'); + assert.equal(sessions[0].uaBrowser, 'Firefox Mobile', 'uaBrowser property is correct'); + assert.equal(sessions[0].uaBrowserVersion, '42', 'uaBrowserVersion property is correct'); + assert.equal(sessions[0].uaOS, 'Android', 'uaOS property is correct'); + assert.equal(sessions[0].uaOSVersion, '4.4', 'uaOSVersion property is correct'); + assert.equal(sessions[0].uaDeviceType, 'mobile', 'uaDeviceType property is correct'); + assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is correct'); // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { // Prune a session token that is older than maxAge - sessionToken.createdAt = Date.now() - tokenPruning.maxAge - 1 - return db.pruneSessionTokens(account.uid, [ sessionToken ]) + sessionToken.createdAt = Date.now() - tokenPruning.maxAge - 1; + return db.pruneSessionTokens(account.uid, [ sessionToken ]); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 1, 'sessions still contains one item') - assert.equal(sessions[0].uaBrowser, 'Firefox', 'uaBrowser property is the original value') - assert.equal(sessions[0].uaBrowserVersion, '41', 'uaBrowserVersion property is the original value') - assert.equal(sessions[0].uaOS, 'Mac OS X', 'uaOS property is the original value') - assert.equal(sessions[0].uaOSVersion, '10.10', 'uaOSVersion property is the original value') - assert.equal(sessions[0].uaDeviceType, null, 'uaDeviceType property is the original value') - assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is the original value') + assert.equal(sessions.length, 1, 'sessions still contains one item'); + assert.equal(sessions[0].uaBrowser, 'Firefox', 'uaBrowser property is the original value'); + assert.equal(sessions[0].uaBrowserVersion, '41', 'uaBrowserVersion property is the original value'); + assert.equal(sessions[0].uaOS, 'Mac OS X', 'uaOS property is the original value'); + assert.equal(sessions[0].uaOSVersion, '10.10', 'uaOSVersion property is the original value'); + assert.equal(sessions[0].uaDeviceType, null, 'uaDeviceType property is the original value'); + assert.equal(sessions[0].uaFormFactor, null, 'uaFormFactor property is the original value'); // Fetch the session token - return db.sessionToken(tokenId) + return db.sessionToken(tokenId); }) .then(sessionToken => { // Delete the session token - return db.deleteSessionToken(sessionToken) + return db.deleteSessionToken(sessionToken); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { - assert.equal(sessions.length, 0, 'sessions is empty') + assert.equal(sessions.length, 0, 'sessions is empty'); // Attempt to delete the deleted session token return db.sessionToken(tokenId) .then(sessionToken => { - assert(false, 'db.sessionToken should have failed') + assert(false, 'db.sessionToken should have failed'); }, err => { - assert.equal(err.errno, 110, 'sessionToken() fails with the correct error code') - var msg = 'Error: The authentication token could not be found' - assert.equal(msg, '' + err, 'sessionToken() fails with the correct message') - }) + assert.equal(err.errno, 110, 'sessionToken() fails with the correct error code'); + var msg = 'Error: The authentication token could not be found'; + assert.equal(msg, '' + err, 'sessionToken() fails with the correct message'); + }); }) .then(() => { // Fetch the email record again - return db.emailRecord(account.email) + return db.emailRecord(account.email); }) .then(emailRecord => { - emailRecord.createdAt = Date.now() - 1000 - emailRecord.tokenVerificationId = account.tokenVerificationId - emailRecord.uaBrowser = 'Firefox' - emailRecord.uaBrowserVersion = '41' - emailRecord.uaOS = 'Mac OS X' - emailRecord.uaOSVersion = '10.10' - emailRecord.uaDeviceType = emailRecord.uaFormFactor = null + emailRecord.createdAt = Date.now() - 1000; + emailRecord.tokenVerificationId = account.tokenVerificationId; + emailRecord.uaBrowser = 'Firefox'; + emailRecord.uaBrowserVersion = '41'; + emailRecord.uaOS = 'Mac OS X'; + emailRecord.uaOSVersion = '10.10'; + emailRecord.uaDeviceType = emailRecord.uaFormFactor = null; // Create a session token with the same data as the deleted token - return db.createSessionToken(emailRecord) + return db.createSessionToken(emailRecord); }) .then(() => { // Fetch all sessions for the account - return db.sessions(account.uid) + return db.sessions(account.uid); }) .then(sessions => { // Make sure that the data got deleted from redis too - assert.equal(sessions.length, 1, 'sessions contains one item') - assert.equal(sessions[0].lastAccessTime, sessions[0].createdAt, 'lastAccessTime property is correct') - assert.equal(sessions[0].location, undefined, 'location property is correct') + assert.equal(sessions.length, 1, 'sessions contains one item'); + assert.equal(sessions[0].lastAccessTime, sessions[0].createdAt, 'lastAccessTime property is correct'); + assert.equal(sessions[0].location, undefined, 'location property is correct'); // Delete the session token again - return db.deleteSessionToken(sessions[0]) + return db.deleteSessionToken(sessions[0]); }) .then(() => redis.getAsync(account.uid)) - .then(result => assert.equal(result, null, 'redis was cleared')) + .then(result => assert.equal(result, null, 'redis was cleared')); } - ) + ); it( 'device registration', () => { - let sessionToken, anotherSessionToken + let sessionToken, anotherSessionToken; const deviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: '', @@ -406,126 +406,126 @@ describe('remote db', function() { pushCallback: 'https://foo/bar', pushPublicKey: base64url(Buffer.concat([Buffer.from('\x04'), crypto.randomBytes(64)])), pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; const conflictingDeviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'wibble' - } + }; return db.emailRecord(account.email) .then((emailRecord) => { - emailRecord.tokenVerificationId = account.tokenVerificationId - emailRecord.uaBrowser = 'Firefox Mobile' - emailRecord.uaBrowserVersion = '41' - emailRecord.uaOS = 'Android' - emailRecord.uaOSVersion = '4.4' - emailRecord.uaDeviceType = 'mobile' - emailRecord.uaFormFactor = null + emailRecord.tokenVerificationId = account.tokenVerificationId; + emailRecord.uaBrowser = 'Firefox Mobile'; + emailRecord.uaBrowserVersion = '41'; + emailRecord.uaOS = 'Android'; + emailRecord.uaOSVersion = '4.4'; + emailRecord.uaDeviceType = 'mobile'; + emailRecord.uaFormFactor = null; // Create a session token - return db.createSessionToken(emailRecord) + return db.createSessionToken(emailRecord); }) .then((result) => { - sessionToken = result - deviceInfo.sessionTokenId = sessionToken.id + sessionToken = result; + deviceInfo.sessionTokenId = sessionToken.id; // Attempt to update a non-existent device return db.updateDevice(account.uid, deviceInfo) .then(() => { - assert(false, 'updating a non-existent device should have failed') + assert(false, 'updating a non-existent device should have failed'); }, (err) => { - assert.equal(err.errno, 123, 'err.errno === 123') - }) + assert.equal(err.errno, 123, 'err.errno === 123'); + }); }) .then(() => { // Attempt to delete a non-existent device return db.deleteDevice(account.uid, deviceInfo.id) .then(function () { - assert(false, 'deleting a non-existent device should have failed') + assert(false, 'deleting a non-existent device should have failed'); }, function (err) { - assert.equal(err.errno, 123, 'err.errno === 123') - }) + assert.equal(err.errno, 123, 'err.errno === 123'); + }); }) .then(() => { // Fetch all of the devices for the account return db.devices(account.uid) .catch(function () { - assert(false, 'getting devices should not have failed') - }) + assert(false, 'getting devices should not have failed'); + }); }) .then((devices) => { - assert.ok(Array.isArray(devices), 'devices is array') - assert.equal(devices.length, 0, 'devices array is empty') + assert.ok(Array.isArray(devices), 'devices is array'); + assert.equal(devices.length, 0, 'devices array is empty'); // Create a device return db.createDevice(account.uid, deviceInfo) .catch((err) => { - assert(false, 'adding a new device should not have failed') - }) + assert(false, 'adding a new device should not have failed'); + }); }) .then((device) => { - assert.ok(device.id, 'device.id is set') - assert.ok(device.createdAt > 0, 'device.createdAt is set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct') + assert.ok(device.id, 'device.id is set'); + assert.ok(device.createdAt > 0, 'device.createdAt is set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct'); // Fetch the session token - return db.sessionToken(sessionToken.id) + return db.sessionToken(sessionToken.id); }) .then(sessionToken => { - assert.equal(sessionToken.lifetime, Infinity) - conflictingDeviceInfo.sessionTokenId = sessionToken.id + assert.equal(sessionToken.lifetime, Infinity); + conflictingDeviceInfo.sessionTokenId = sessionToken.id; // Attempt to create a device with a duplicate session token return db.createDevice(account.uid, conflictingDeviceInfo) .then(() => { - assert(false, 'adding a device with a duplicate session token should have failed') + assert(false, 'adding a device with a duplicate session token should have failed'); }, (err) => { - assert.equal(err.errno, 124, 'err.errno') - assert.equal(err.output.payload.deviceId, deviceInfo.id) - }) + assert.equal(err.errno, 124, 'err.errno'); + assert.equal(err.output.payload.deviceId, deviceInfo.id); + }); }) .then(() => { // Fetch all of the devices for the account - return db.devices(account.uid) + return db.devices(account.uid); }) .then((devices) => { - assert.equal(devices.length, 1, 'devices array contains one item') - return devices[0] + assert.equal(devices.length, 1, 'devices array contains one item'); + return devices[0]; }) .then((device) => { - assert.ok(device.id, 'device.id is set') - assert.ok(device.lastAccessTime > 0, 'device.lastAccessTime is set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct') - assert.equal(device.uaBrowser, 'Firefox Mobile', 'device.uaBrowser is correct') - assert.equal(device.uaBrowserVersion, '41', 'device.uaBrowserVersion is correct') - assert.equal(device.uaOS, 'Android', 'device.uaOS is correct') - assert.equal(device.uaOSVersion, '4.4', 'device.uaOSVersion is correct') - assert.equal(device.uaDeviceType, 'mobile', 'device.uaDeviceType is correct') - assert.equal(device.uaFormFactor, null, 'device.uaFormFactor is correct') - assert.equal(device.location, undefined, 'device.location was not set') - deviceInfo.id = device.id - deviceInfo.name = 'wibble' - deviceInfo.type = 'desktop' - deviceInfo.availableCommands = {} - deviceInfo.pushCallback = '' - deviceInfo.pushPublicKey = '' - deviceInfo.pushAuthKey = '' - deviceInfo.sessionTokenId = sessionToken.id - sessionToken.lastAccessTime = 42 - sessionToken.uaBrowser = 'Firefox' - sessionToken.uaBrowserVersion = '44' - sessionToken.uaOS = 'Mac OS X' - sessionToken.uaOSVersion = '10.10' - sessionToken.uaDeviceType = sessionToken.uaFormFactor = null + assert.ok(device.id, 'device.id is set'); + assert.ok(device.lastAccessTime > 0, 'device.lastAccessTime is set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct'); + assert.equal(device.uaBrowser, 'Firefox Mobile', 'device.uaBrowser is correct'); + assert.equal(device.uaBrowserVersion, '41', 'device.uaBrowserVersion is correct'); + assert.equal(device.uaOS, 'Android', 'device.uaOS is correct'); + assert.equal(device.uaOSVersion, '4.4', 'device.uaOSVersion is correct'); + assert.equal(device.uaDeviceType, 'mobile', 'device.uaDeviceType is correct'); + assert.equal(device.uaFormFactor, null, 'device.uaFormFactor is correct'); + assert.equal(device.location, undefined, 'device.location was not set'); + deviceInfo.id = device.id; + deviceInfo.name = 'wibble'; + deviceInfo.type = 'desktop'; + deviceInfo.availableCommands = {}; + deviceInfo.pushCallback = ''; + deviceInfo.pushPublicKey = ''; + deviceInfo.pushAuthKey = ''; + deviceInfo.sessionTokenId = sessionToken.id; + sessionToken.lastAccessTime = 42; + sessionToken.uaBrowser = 'Firefox'; + sessionToken.uaBrowserVersion = '44'; + sessionToken.uaOS = 'Mac OS X'; + sessionToken.uaOSVersion = '10.10'; + sessionToken.uaDeviceType = sessionToken.uaFormFactor = null; // Update the device and the session token return P.all([ db.updateDevice(account.uid, deviceInfo), @@ -539,308 +539,308 @@ describe('remote db', function() { }, timeZone: 'America/Los_Angeles' }) - ]) + ]); }) .then(results => { // Create another session token - return db.createSessionToken(sessionToken) + return db.createSessionToken(sessionToken); }) .then(result => { - anotherSessionToken = result - conflictingDeviceInfo.sessionTokenId = anotherSessionToken.id + anotherSessionToken = result; + conflictingDeviceInfo.sessionTokenId = anotherSessionToken.id; // Create another device - return db.createDevice(account.uid, conflictingDeviceInfo) + return db.createDevice(account.uid, conflictingDeviceInfo); }) .then(() => { // Attempt to update a device with a duplicate session token - deviceInfo.sessionTokenId = anotherSessionToken.id + deviceInfo.sessionTokenId = anotherSessionToken.id; return db.updateDevice(account.uid, deviceInfo) .then(() => { - assert(false, 'updating a device with a duplicate session token should have failed') + assert(false, 'updating a device with a duplicate session token should have failed'); }, (err) => { - assert.equal(err.errno, 124, 'err.errno') - assert.equal(err.output.payload.deviceId, conflictingDeviceInfo.id) - }) + assert.equal(err.errno, 124, 'err.errno'); + assert.equal(err.output.payload.deviceId, conflictingDeviceInfo.id); + }); }) .then(() => { // Fetch all of the devices for the account - return db.devices(account.uid) + return db.devices(account.uid); }) .then((devices) => { - assert.equal(devices.length, 2, 'devices array contains two items') + assert.equal(devices.length, 2, 'devices array contains two items'); if (devices[0].id === deviceInfo.id) { - return devices[0] + return devices[0]; } - return devices[1] + return devices[1]; }) .then((device) => { // Fetch a single device return db.device(account.uid, device.id).then(result => { - assert.deepEqual(device, result) - return device - }) + assert.deepEqual(device, result); + return device; + }); }) .then((device) => { - assert.equal(device.lastAccessTime, 42, 'device.lastAccessTime is correct') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') - assert.equal(device.pushPublicKey, '', 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, '', 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct') - assert.equal(device.uaBrowser, 'Firefox', 'device.uaBrowser is correct') - assert.equal(device.uaBrowserVersion, '44', 'device.uaBrowserVersion is correct') - assert.equal(device.uaOS, 'Mac OS X', 'device.uaOS is correct') - assert.equal(device.uaOSVersion, '10.10', 'device.uaOSVersion is correct') - assert.equal(device.uaDeviceType, null, 'device.uaDeviceType is correct') - assert.equal(device.uaFormFactor, null, 'device.uaFormFactor is correct') - assert.equal(device.location.city, 'Mountain View', 'device.location.city is correct') - assert.equal(device.location.country, 'United States', 'device.location.country is correct') - assert.equal(device.location.countryCode, 'US', 'device.location.countryCode is correct') - assert.equal(device.location.state, 'California', 'device.location.state is correct') - assert.equal(device.location.stateCode, 'CA', 'device.location.stateCode is correct') + assert.equal(device.lastAccessTime, 42, 'device.lastAccessTime is correct'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); + assert.equal(device.pushPublicKey, '', 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, '', 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct'); + assert.equal(device.uaBrowser, 'Firefox', 'device.uaBrowser is correct'); + assert.equal(device.uaBrowserVersion, '44', 'device.uaBrowserVersion is correct'); + assert.equal(device.uaOS, 'Mac OS X', 'device.uaOS is correct'); + assert.equal(device.uaOSVersion, '10.10', 'device.uaOSVersion is correct'); + assert.equal(device.uaDeviceType, null, 'device.uaDeviceType is correct'); + assert.equal(device.uaFormFactor, null, 'device.uaFormFactor is correct'); + assert.equal(device.location.city, 'Mountain View', 'device.location.city is correct'); + assert.equal(device.location.country, 'United States', 'device.location.country is correct'); + assert.equal(device.location.countryCode, 'US', 'device.location.countryCode is correct'); + assert.equal(device.location.state, 'California', 'device.location.state is correct'); + assert.equal(device.location.stateCode, 'CA', 'device.location.stateCode is correct'); // Disable session token updates - lastAccessTimeUpdates.enabled = false - return db.devices(account.uid) + lastAccessTimeUpdates.enabled = false; + return db.devices(account.uid); }) .then((devices) => { - assert.equal(devices.length, 2, 'devices array contains two items') - assert.equal(devices[0].lastAccessTime, undefined, 'lastAccessTime is not set when feature is disabled') - assert.equal(devices[1].lastAccessTime, undefined, 'lastAccessTime is not set when feature is disabled') + assert.equal(devices.length, 2, 'devices array contains two items'); + assert.equal(devices[0].lastAccessTime, undefined, 'lastAccessTime is not set when feature is disabled'); + assert.equal(devices[1].lastAccessTime, undefined, 'lastAccessTime is not set when feature is disabled'); // Re-enable session token updates - lastAccessTimeUpdates.enabled = true + lastAccessTimeUpdates.enabled = true; // Delete the devices - return db.deleteDevice(account.uid, deviceInfo.id) + return db.deleteDevice(account.uid, deviceInfo.id); }) // Deleting these serially ensures there's no Redis WATCH conflict for account.uid .then(() => db.deleteDevice(account.uid, conflictingDeviceInfo.id)) // Deleting the devices should also have cleared the data from Redis .then(() => redis.getAsync(account.uid)) .then(result => { - assert.equal(result, null, 'redis was cleared') + assert.equal(result, null, 'redis was cleared'); }) .then(function () { // Fetch all of the devices for the account - return db.devices(account.uid) + return db.devices(account.uid); }) .then(function (devices) { - assert.equal(devices.length, 0, 'devices array is empty') + assert.equal(devices.length, 0, 'devices array is empty'); // Delete the account - return db.deleteAccount(account) - }) + return db.deleteAccount(account); + }); } - ) + ); it( 'keyfetch token handling', () => { - var tokenId + var tokenId; return db.emailRecord(account.email) .then(function(emailRecord) { return db.createKeyFetchToken({ uid: emailRecord.uid, kA: emailRecord.kA, wrapKb: account.wrapWrapKb - }) + }); }) .then(function(keyFetchToken) { - assert.deepEqual(keyFetchToken.uid, account.uid) - tokenId = keyFetchToken.id + assert.deepEqual(keyFetchToken.uid, account.uid); + tokenId = keyFetchToken.id; }) .then(function() { - return db.keyFetchToken(tokenId) + return db.keyFetchToken(tokenId); }) .then(function(keyFetchToken) { - assert.deepEqual(keyFetchToken.id, tokenId, 'token id matches') - assert.deepEqual(keyFetchToken.uid, account.uid) - assert.equal(keyFetchToken.emailVerified, account.emailVerified) - return keyFetchToken + assert.deepEqual(keyFetchToken.id, tokenId, 'token id matches'); + assert.deepEqual(keyFetchToken.uid, account.uid); + assert.equal(keyFetchToken.emailVerified, account.emailVerified); + return keyFetchToken; }) .then(function(keyFetchToken) { - return db.deleteKeyFetchToken(keyFetchToken) + return db.deleteKeyFetchToken(keyFetchToken); }) .then(function() { - return db.keyFetchToken(tokenId) + return db.keyFetchToken(tokenId); }) .then(function(keyFetchToken) { - assert(false, 'The above keyFetchToken() call should fail, since the keyFetchToken has been deleted') + assert(false, 'The above keyFetchToken() call should fail, since the keyFetchToken has been deleted'); }, function(err) { - assert.equal(err.errno, 110, 'keyFetchToken() fails with the correct error code') - var msg = 'Error: The authentication token could not be found' - assert.equal(msg, '' + err, 'keyFetchToken() fails with the correct message') - }) + assert.equal(err.errno, 110, 'keyFetchToken() fails with the correct error code'); + var msg = 'Error: The authentication token could not be found'; + assert.equal(msg, '' + err, 'keyFetchToken() fails with the correct message'); + }); } - ) + ); it( 'reset token handling', () => { - var tokenId + var tokenId; return db.emailRecord(account.email) .then(function(emailRecord) { - return db.createPasswordForgotToken(emailRecord) + return db.createPasswordForgotToken(emailRecord); }) .then(function(passwordForgotToken) { return db.forgotPasswordVerified(passwordForgotToken) .then(accountResetToken => { - assert.ok(accountResetToken.createdAt >= passwordForgotToken.createdAt, 'account reset token should be equal or newer than password forgot token') - return accountResetToken - }) + assert.ok(accountResetToken.createdAt >= passwordForgotToken.createdAt, 'account reset token should be equal or newer than password forgot token'); + return accountResetToken; + }); }) .then(function(accountResetToken) { - assert.deepEqual(accountResetToken.uid, account.uid, 'account reset token uid should be the same as the account.uid') - tokenId = accountResetToken.id - return db.accountResetToken(tokenId) + assert.deepEqual(accountResetToken.uid, account.uid, 'account reset token uid should be the same as the account.uid'); + tokenId = accountResetToken.id; + return db.accountResetToken(tokenId); }) .then(function(accountResetToken) { - assert.deepEqual(accountResetToken.id, tokenId, 'token id matches') - assert.deepEqual(accountResetToken.uid, account.uid, 'account reset token uid should still be the same as the account.uid') - return accountResetToken + assert.deepEqual(accountResetToken.id, tokenId, 'token id matches'); + assert.deepEqual(accountResetToken.uid, account.uid, 'account reset token uid should still be the same as the account.uid'); + return accountResetToken; }) .then(function(accountResetToken) { - return db.deleteAccountResetToken(accountResetToken) + return db.deleteAccountResetToken(accountResetToken); }) .then(function () { return db.accountResetToken(tokenId) .then(assert.fail, function (err) { - assert.equal(err.errno, 110, 'accountResetToken() fails with the correct error code') - var msg = 'Error: The authentication token could not be found' - assert.equal(msg, '' + err, 'accountResetToken() fails with the correct message') - }) - }) + assert.equal(err.errno, 110, 'accountResetToken() fails with the correct error code'); + var msg = 'Error: The authentication token could not be found'; + assert.equal(msg, '' + err, 'accountResetToken() fails with the correct message'); + }); + }); } - ) + ); it( 'forgotpwd token handling', () => { - var token1 - var token1tries = 0 + var token1; + var token1tries = 0; return db.emailRecord(account.email) .then(function(emailRecord) { - return db.createPasswordForgotToken(emailRecord) + return db.createPasswordForgotToken(emailRecord); }) .then(function(passwordForgotToken) { - assert.deepEqual(passwordForgotToken.uid, account.uid, 'passwordForgotToken uid same as account.uid') - token1 = passwordForgotToken - token1tries = token1.tries + assert.deepEqual(passwordForgotToken.uid, account.uid, 'passwordForgotToken uid same as account.uid'); + token1 = passwordForgotToken; + token1tries = token1.tries; }) .then(function() { - return db.passwordForgotToken(token1.id) + return db.passwordForgotToken(token1.id); }) .then(function(passwordForgotToken) { - assert.deepEqual(passwordForgotToken.id, token1.id, 'token id matches') - assert.deepEqual(passwordForgotToken.uid, token1.uid, 'tokens are identical') - return passwordForgotToken + assert.deepEqual(passwordForgotToken.id, token1.id, 'token id matches'); + assert.deepEqual(passwordForgotToken.uid, token1.uid, 'tokens are identical'); + return passwordForgotToken; }) .then(function(passwordForgotToken) { - passwordForgotToken.tries -= 1 - return db.updatePasswordForgotToken(passwordForgotToken) + passwordForgotToken.tries -= 1; + return db.updatePasswordForgotToken(passwordForgotToken); }) .then(function() { - return db.passwordForgotToken(token1.id) + return db.passwordForgotToken(token1.id); }) .then(function(passwordForgotToken) { - assert.deepEqual(passwordForgotToken.id, token1.id, 'token id matches again') - assert.equal(passwordForgotToken.tries, token1tries - 1, '') - return passwordForgotToken + assert.deepEqual(passwordForgotToken.id, token1.id, 'token id matches again'); + assert.equal(passwordForgotToken.tries, token1tries - 1, ''); + return passwordForgotToken; }) .then(function(passwordForgotToken) { - return db.deletePasswordForgotToken(passwordForgotToken) + return db.deletePasswordForgotToken(passwordForgotToken); }) .then(function() { - return db.passwordForgotToken(token1.id) + return db.passwordForgotToken(token1.id); }) .then(function(passwordForgotToken) { - assert(false, 'The above passwordForgotToken() call should fail, since the passwordForgotToken has been deleted') + assert(false, 'The above passwordForgotToken() call should fail, since the passwordForgotToken has been deleted'); }, function(err) { - assert.equal(err.errno, 110, 'passwordForgotToken() fails with the correct error code') - var msg = 'Error: The authentication token could not be found' - assert.equal(msg, '' + err, 'passwordForgotToken() fails with the correct message') - }) + assert.equal(err.errno, 110, 'passwordForgotToken() fails with the correct error code'); + var msg = 'Error: The authentication token could not be found'; + assert.equal(msg, '' + err, 'passwordForgotToken() fails with the correct message'); + }); } - ) + ); it( 'email verification', () => { return db.emailRecord(account.email) .then(function(emailRecord) { - return db.verifyEmail(emailRecord, emailRecord.emailCode) + return db.verifyEmail(emailRecord, emailRecord.emailCode); }) .then(function() { - return db.account(account.uid) + return db.account(account.uid); }) .then(function(account) { - assert.ok(account.emailVerified, 'account should now be emailVerified') - }) + assert.ok(account.emailVerified, 'account should now be emailVerified'); + }); } - ) + ); it( 'db.forgotPasswordVerified', () => { - var token1 + var token1; return db.emailRecord(account.email) .then(function(emailRecord) { - return db.createPasswordForgotToken(emailRecord) + return db.createPasswordForgotToken(emailRecord); }) .then(function(passwordForgotToken) { - return db.forgotPasswordVerified(passwordForgotToken) + return db.forgotPasswordVerified(passwordForgotToken); }) .then(function(accountResetToken) { - assert.deepEqual(accountResetToken.uid, account.uid, 'uid is the same as account.uid') - token1 = accountResetToken + assert.deepEqual(accountResetToken.uid, account.uid, 'uid is the same as account.uid'); + token1 = accountResetToken; }) .then(function() { - return db.accountResetToken(token1.id) + return db.accountResetToken(token1.id); }) .then(function(accountResetToken) { - assert.deepEqual(accountResetToken.uid, account.uid) - return db.deleteAccountResetToken(token1) - }) + assert.deepEqual(accountResetToken.uid, account.uid); + return db.deleteAccountResetToken(token1); + }); } - ) + ); it( 'db.resetAccount', () => { return db.emailRecord(account.email) .then(function(emailRecord) { - emailRecord.tokenVerificationId = account.tokenVerificationId - emailRecord.uaBrowser = 'Firefox' - emailRecord.uaBrowserVersion = '41' - emailRecord.uaOS = 'Mac OS X' - emailRecord.uaOSVersion = '10.10' - emailRecord.uaDeviceType = emailRecord.uaFormFactor = null - return db.createSessionToken(emailRecord) + emailRecord.tokenVerificationId = account.tokenVerificationId; + emailRecord.uaBrowser = 'Firefox'; + emailRecord.uaBrowserVersion = '41'; + emailRecord.uaOS = 'Mac OS X'; + emailRecord.uaOSVersion = '10.10'; + emailRecord.uaDeviceType = emailRecord.uaFormFactor = null; + return db.createSessionToken(emailRecord); }) .then(function(sessionToken) { - return db.forgotPasswordVerified(sessionToken) + return db.forgotPasswordVerified(sessionToken); }) .then(function(accountResetToken) { - return db.resetAccount(accountResetToken, account) + return db.resetAccount(accountResetToken, account); }) .then(() => { - return redis.getAsync(account.uid) + return redis.getAsync(account.uid); }) .then(result => { - assert.equal(result, null, 'redis was cleared') + assert.equal(result, null, 'redis was cleared'); // account should STILL exist for this email address - return db.accountExists(account.email) + return db.accountExists(account.email); }) .then(function(exists) { - assert.equal(exists, true, 'account should still exist') - }) + assert.equal(exists, true, 'account should still exist'); + }); } - ) + ); it( 'db.securityEvent', @@ -851,21 +851,21 @@ describe('remote db', function() { uid: account.uid }) .then(function(resp) { - assert.equal(typeof resp, 'object') - assert.equal(Object.keys(resp).length, 0) + assert.equal(typeof resp, 'object'); + assert.equal(Object.keys(resp).length, 0); return db.securityEvent({ ipAddr: '127.0.0.1', name: 'account.login', uid: account.uid - }) + }); }) .then(function(resp) { - assert.equal(typeof resp, 'object') - assert.equal(Object.keys(resp).length, 0) - }) + assert.equal(typeof resp, 'object'); + assert.equal(Object.keys(resp).length, 0); + }); } - ) + ); it( 'db.securityEvents', @@ -879,188 +879,188 @@ describe('remote db', function() { return db.securityEvents({ ipAddr: '127.0.0.1', uid: account.uid - }) + }); }) .then(function (events) { - assert.equal(events.length, 1) - }) + assert.equal(events.length, 1); + }); } - ) + ); it( 'unblock code', () => { - var unblockCode + var unblockCode; return db.createUnblockCode(account.uid) .then(function(_unblockCode) { - assert.ok(_unblockCode) - unblockCode = _unblockCode + assert.ok(_unblockCode); + unblockCode = _unblockCode; - return db.consumeUnblockCode(account.uid, 'NOTREAL') + return db.consumeUnblockCode(account.uid, 'NOTREAL'); }) .then( function () { - assert(false, 'consumeUnblockCode() with an invalid unblock code should not succeed') + assert(false, 'consumeUnblockCode() with an invalid unblock code should not succeed'); }, function (err) { - assert.equal(err.errno, 127, 'consumeUnblockCode() fails with the correct error code') - var msg = 'Error: Invalid unblock code' - assert.equal(msg, '' + err, 'consumeUnblockCode() fails with the correct message') + assert.equal(err.errno, 127, 'consumeUnblockCode() fails with the correct error code'); + var msg = 'Error: Invalid unblock code'; + assert.equal(msg, '' + err, 'consumeUnblockCode() fails with the correct message'); } ) .then( function() { - return db.consumeUnblockCode(account.uid, unblockCode) + return db.consumeUnblockCode(account.uid, unblockCode); } ) .then( function() { // re-use unblock code, no longer valid - return db.consumeUnblockCode(account.uid, unblockCode) + return db.consumeUnblockCode(account.uid, unblockCode); }, function (err) { - assert(false, 'consumeUnblockCode() with a valid unblock code should succeed') + assert(false, 'consumeUnblockCode() with a valid unblock code should succeed'); } ) .then( function () { - assert(false, 'consumeUnblockCode() with an invalid unblock code should not succeed') + assert(false, 'consumeUnblockCode() with an invalid unblock code should not succeed'); }, function (err) { - assert.equal(err.errno, 127, 'consumeUnblockCode() fails with the correct error code') - var msg = 'Error: Invalid unblock code' - assert.equal(msg, '' + err, 'consumeUnblockCode() fails with the correct message') + assert.equal(err.errno, 127, 'consumeUnblockCode() fails with the correct error code'); + var msg = 'Error: Invalid unblock code'; + assert.equal(msg, '' + err, 'consumeUnblockCode() fails with the correct message'); } - ) + ); } - ) + ); it('signinCodes', () => { - let previousCode - const flowId = crypto.randomBytes(32).toString('hex') + let previousCode; + const flowId = crypto.randomBytes(32).toString('hex'); // Create a signinCode without a flowId return db.createSigninCode(account.uid) .then(code => { - assert.equal(typeof code, 'string', 'db.createSigninCode should return a string') - assert.equal(Buffer.from(code, 'hex').length, config.signinCodeSize, 'db.createSigninCode should return the correct size code') + assert.equal(typeof code, 'string', 'db.createSigninCode should return a string'); + assert.equal(Buffer.from(code, 'hex').length, config.signinCodeSize, 'db.createSigninCode should return the correct size code'); - previousCode = code + previousCode = code; // Stub crypto.randomBytes to return a duplicate code sinon.stub(crypto, 'randomBytes').callsFake((size, callback) => { // Reinstate the real crypto.randomBytes after we've returned a duplicate - crypto.randomBytes.restore() + crypto.randomBytes.restore(); if (! callback) { - return previousCode + return previousCode; } - callback(null, previousCode) - }) + callback(null, previousCode); + }); // Create a signinCode with crypto.randomBytes rigged to return a duplicate, // and this time specifying a flowId - return db.createSigninCode(account.uid, flowId) + return db.createSigninCode(account.uid, flowId); }) .then(code => { - assert.equal(typeof code, 'string', 'db.createSigninCode should return a string') - assert.notEqual(code, previousCode, 'db.createSigninCode should not return a duplicate code') - assert.equal(Buffer.from(code, 'hex').length, config.signinCodeSize, 'db.createSigninCode should return the correct size code') + assert.equal(typeof code, 'string', 'db.createSigninCode should return a string'); + assert.notEqual(code, previousCode, 'db.createSigninCode should not return a duplicate code'); + assert.equal(Buffer.from(code, 'hex').length, config.signinCodeSize, 'db.createSigninCode should return the correct size code'); // Consume both signinCodes return P.all([ db.consumeSigninCode(previousCode), db.consumeSigninCode(code) - ]) + ]); }) .then(results => { - assert.equal(results[0].email, account.email, 'db.consumeSigninCode should return the email address') - assert.equal(results[1].email, account.email, 'db.consumeSigninCode should return the email address') + assert.equal(results[0].email, account.email, 'db.consumeSigninCode should return the email address'); + assert.equal(results[1].email, account.email, 'db.consumeSigninCode should return the email address'); if (results[1].flowId) { // This assertion is conditional so that tests pass regardless of db version - assert.equal(results[1].flowId, flowId, 'db.consumeSigninCode should return the flowId') + assert.equal(results[1].flowId, flowId, 'db.consumeSigninCode should return the flowId'); } // Attempt to consume a consumed signinCode return db.consumeSigninCode(previousCode) .then(() => assert.fail('db.consumeSigninCode should have failed')) .catch(err => { - assert.equal(err.errno, 146, 'db.consumeSigninCode should fail with errno 146') - assert.equal(err.message, 'Invalid signin code', 'db.consumeSigninCode should fail with message "Invalid signin code"') - assert.equal(err.output.statusCode, 400, 'db.consumeSigninCode should fail with status 400') - }) - }) - }) + assert.equal(err.errno, 146, 'db.consumeSigninCode should fail with errno 146'); + assert.equal(err.message, 'Invalid signin code', 'db.consumeSigninCode should fail with message "Invalid signin code"'); + assert.equal(err.output.statusCode, 400, 'db.consumeSigninCode should fail with status 400'); + }); + }); + }); it( 'account deletion', () => { return db.emailRecord(account.email) .then(function(emailRecord) { - assert.deepEqual(emailRecord.uid, account.uid, 'retrieving uid should be the same') - return db.deleteAccount(emailRecord) + assert.deepEqual(emailRecord.uid, account.uid, 'retrieving uid should be the same'); + return db.deleteAccount(emailRecord); }) .then(() => { - return redis.getAsync(account.uid) + return redis.getAsync(account.uid); }) .then(result => { - assert.equal(result, null, 'redis was cleared') + assert.equal(result, null, 'redis was cleared'); // account should no longer exist for this email address - return db.accountExists(account.email) + return db.accountExists(account.email); }) .then(function(exists) { - assert.equal(exists, false, 'account should no longer exist') - }) + assert.equal(exists, false, 'account should no longer exist'); + }); } - ) + ); describe('account record', () => { it('can retrieve account from account email', () => { return P.all([db.emailRecord(account.email), db.accountRecord(account.email)]) .spread(function (emailRecord, accountRecord) { - assert.equal(emailRecord.email, accountRecord.email, 'original account and email records should be equal') - assert.deepEqual(emailRecord.emails, accountRecord.emails, 'emails should be equal') - assert.deepEqual(emailRecord.primaryEmail, accountRecord.primaryEmail, 'primary emails should be equal') - }) - }) + assert.equal(emailRecord.email, accountRecord.email, 'original account and email records should be equal'); + assert.deepEqual(emailRecord.emails, accountRecord.emails, 'emails should be equal'); + assert.deepEqual(emailRecord.primaryEmail, accountRecord.primaryEmail, 'primary emails should be equal'); + }); + }); it('can retrieve account from secondary email', () => { return P.all([db.accountRecord(account.email), db.accountRecord(secondEmail)]) .spread(function (accountRecord, accountRecordFromSecondEmail) { - assert.equal(accountRecordFromSecondEmail.email, accountRecord.email, 'original account and email records should be equal') - assert.deepEqual(accountRecordFromSecondEmail.emails, accountRecord.emails, 'emails should be equal') - assert.deepEqual(accountRecordFromSecondEmail.primaryEmail, accountRecord.primaryEmail, 'primary emails should be equal') - }) - }) + assert.equal(accountRecordFromSecondEmail.email, accountRecord.email, 'original account and email records should be equal'); + assert.deepEqual(accountRecordFromSecondEmail.emails, accountRecord.emails, 'emails should be equal'); + assert.deepEqual(accountRecordFromSecondEmail.primaryEmail, accountRecord.primaryEmail, 'primary emails should be equal'); + }); + }); it('returns unknown account', () => { return db.accountRecord('idontexist@email.com') .then(function () { - assert.fail('should not have retrieved non-existent account') + assert.fail('should not have retrieved non-existent account'); }) .catch((err) => { - assert.equal(err.errno, 102, 'unknown account error code') - }) - }) - }) + assert.equal(err.errno, 102, 'unknown account error code'); + }); + }); + }); describe('set primary email', () => { it('can set primary email address', () => { return db.setPrimaryEmail(account.uid, secondEmail) .then(function (res) { - assert.ok(res, 'ok response') - return db.accountRecord(secondEmail) + assert.ok(res, 'ok response'); + return db.accountRecord(secondEmail); }) .then(function (account) { - assert.equal(account.primaryEmail.email, secondEmail, 'primary email set') - }) - }) - }) + assert.equal(account.primaryEmail.email, secondEmail, 'primary email set'); + }); + }); + }); after(() => { return TestServer.stop(dbServer) .then(() => { - return db && db.close() - }) - }) -}) + return db && db.close(); + }); + }); +}); diff --git a/test/remote/device_tests.js b/test/remote/device_tests.js index 3bae322d..cd5988ef 100644 --- a/test/remote/device_tests.js +++ b/test/remote/device_tests.js @@ -2,38 +2,38 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const config = require('../../config').getProperties() -const crypto = require('crypto') -const base64url = require('base64url') -const P = require('../../lib/promise') -const mocks = require('../mocks') +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const config = require('../../config').getProperties(); +const crypto = require('crypto'); +const base64url = require('base64url'); +const P = require('../../lib/promise'); +const mocks = require('../mocks'); describe('remote device', function () { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { config.lastAccessTimeUpdates = { enabled: true, sampleRate: 1, earliestSaneTimestamp: config.lastAccessTimeUpdates.earliestSaneTimestamp - } + }; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'device registration after account creation', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -44,108 +44,108 @@ describe('remote device', function () { pushCallback: '', pushPublicKey: '', pushAuthKey: '' - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct'); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name') - assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type') - assert.deepEqual(devices[0].availableCommands, deviceInfo.availableCommands, 'devices returned correct availableCommands') - assert.equal(devices[0].pushCallback, '', 'devices returned empty pushCallback') - assert.equal(devices[0].pushPublicKey, '', 'devices returned correct pushPublicKey') - assert.equal(devices[0].pushAuthKey, '', 'devices returned correct pushAuthKey') - assert.equal(devices[0].pushEndpointExpired, '', 'devices returned correct pushEndpointExpired') - return client.destroyDevice(devices[0].id) + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name'); + assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type'); + assert.deepEqual(devices[0].availableCommands, deviceInfo.availableCommands, 'devices returned correct availableCommands'); + assert.equal(devices[0].pushCallback, '', 'devices returned empty pushCallback'); + assert.equal(devices[0].pushPublicKey, '', 'devices returned correct pushPublicKey'); + assert.equal(devices[0].pushAuthKey, '', 'devices returned correct pushAuthKey'); + assert.equal(devices[0].pushEndpointExpired, '', 'devices returned correct pushEndpointExpired'); + return client.destroyDevice(devices[0].id); } - ) + ); } - ) + ); } - ) + ); it( 'device registration without optional parameters', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { var deviceInfo = { name: 'test device', type: 'mobile' - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.equal(device.pushCallback, undefined, 'device.pushCallback is undefined') - assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is undefined') - assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is undefined') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.equal(device.pushCallback, undefined, 'device.pushCallback is undefined'); + assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is undefined'); + assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is undefined'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false'); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name') - assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type') - assert.equal(devices[0].pushCallback, undefined, 'devices returned undefined pushCallback') - assert.equal(devices[0].pushPublicKey, undefined, 'devices returned undefined pushPublicKey') - assert.equal(devices[0].pushAuthKey, undefined, 'devices returned undefined pushAuthKey') - assert.equal(devices[0].pushEndpointExpired, false, 'devices returned false pushEndpointExpired') - return client.destroyDevice(devices[0].id) + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name'); + assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type'); + assert.equal(devices[0].pushCallback, undefined, 'devices returned undefined pushCallback'); + assert.equal(devices[0].pushPublicKey, undefined, 'devices returned undefined pushPublicKey'); + assert.equal(devices[0].pushAuthKey, undefined, 'devices returned undefined pushAuthKey'); + assert.equal(devices[0].pushEndpointExpired, false, 'devices returned false pushEndpointExpired'); + return client.destroyDevice(devices[0].id); } - ) + ); } - ) + ); } - ) + ); it( 'device registration with unicode characters in the name', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -153,81 +153,81 @@ describe('remote device', function () { // That's a beta, and a CJK character from https://bugzilla.mozilla.org/show_bug.cgi?id=1348298 name: 'Firefox \u5728 \u03b2 test', type: 'desktop', - } + }; return client.updateDevice(deviceInfo) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name') + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name'); } - ) + ); } - ) + ); } - ) + ); it( 'device registration without required name parameter', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice({ type: 'mobile' }) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, '', 'device.name is empty') - assert.equal(device.type, 'mobile', 'device.type is correct') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, '', 'device.name is empty'); + assert.equal(device.type, 'mobile', 'device.type is correct'); } - ) + ); } - ) + ); } - ) + ); it( 'device registration without required type parameter', () => { - var email = server.uniqueEmail() - var deviceName = 'test device' - var password = 'test password' + var email = server.uniqueEmail(); + var deviceName = 'test device'; + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice({ name: 'test device' }) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceName, 'device.name is correct') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceName, 'device.name is correct'); } - ) + ); } - ) + ); } - ) + ); it( 'update device fails with bad callbackUrl', () => { - var badPushCallback = 'https://updates.push.services.mozilla.com.different-push-server.technology' - var email = server.uniqueEmail() - var password = 'test password' + var badPushCallback = 'https://updates.push.services.mozilla.com.different-push-server.technology'; + var email = server.uniqueEmail(); + var password = 'test password'; var deviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'test device', @@ -236,33 +236,33 @@ describe('remote device', function () { pushCallback: badPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice(deviceInfo) .then( function (r) { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); } ) .catch( function (err) { - assert.equal(err.code, 400, 'err.code was 400') - assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter') - assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation') + assert.equal(err.code, 400, 'err.code was 400'); + assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter'); + assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation'); } - ) - }) + ); + }); } - ) + ); it( 'update device fails with non-normalized callbackUrl', () => { - var badPushCallback = 'https://updates.push.services.mozilla.com/invalid/\u010D/char' - var email = server.uniqueEmail() - var password = 'test password' + var badPushCallback = 'https://updates.push.services.mozilla.com/invalid/\u010D/char'; + var email = server.uniqueEmail(); + var password = 'test password'; var deviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'test device', @@ -271,33 +271,33 @@ describe('remote device', function () { pushCallback: badPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice(deviceInfo) .then( function (r) { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); } ) .catch( function (err) { - assert.equal(err.code, 400, 'err.code was 400') - assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter') - assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation') + assert.equal(err.code, 400, 'err.code was 400'); + assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter'); + assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation'); } - ) - }) + ); + }); } - ) + ); it( 'update device works with stage servers', () => { - var goodPushCallback = 'https://updates-autopush.stage.mozaws.net' - var email = server.uniqueEmail() - var password = 'test password' + var goodPushCallback = 'https://updates-autopush.stage.mozaws.net'; + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -308,35 +308,35 @@ describe('remote device', function () { pushCallback: goodPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') + assert.ok(device.id, 'device.id was set'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); } ) .catch( function (err) { - assert.fail(err, 'request should have worked') + assert.fail(err, 'request should have worked'); } - ) - }) + ); + }); } - ) + ); it( 'update device works with dev servers', () => { - var goodPushCallback = 'https://updates-autopush.dev.mozaws.net' - var email = server.uniqueEmail() - var password = 'test password' + var goodPushCallback = 'https://updates-autopush.dev.mozaws.net'; + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -346,36 +346,36 @@ describe('remote device', function () { pushCallback: goodPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') + assert.ok(device.id, 'device.id was set'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); } ) .catch( function (err) { - assert.fail(err, 'request should have worked') + assert.fail(err, 'request should have worked'); } - ) - }) + ); + }); } - ) + ); it( 'update device works with callback urls that :443 as a port', () => { - var goodPushCallback = 'https://updates.push.services.mozilla.com:443/wpush/v1/gAAAAABbkq0Eafe6IANS4OV3pmoQ5Z8AhqFSGKtozz5FIvu0CfrTGmcv07CYziPaysTv_9dgisB0yr3UjEIlGEyoprRFX1WU5VA4nG-9tofPdA3FYREPf6xh3JL1qBhTa9mEFS2dSn--' + var goodPushCallback = 'https://updates.push.services.mozilla.com:443/wpush/v1/gAAAAABbkq0Eafe6IANS4OV3pmoQ5Z8AhqFSGKtozz5FIvu0CfrTGmcv07CYziPaysTv_9dgisB0yr3UjEIlGEyoprRFX1WU5VA4nG-9tofPdA3FYREPf6xh3JL1qBhTa9mEFS2dSn--'; - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -385,35 +385,35 @@ describe('remote device', function () { pushCallback: goodPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') + assert.ok(device.id, 'device.id was set'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); } ) .catch( function (err) { - assert.fail(err, 'request should have worked') + assert.fail(err, 'request should have worked'); } - ) - }) + ); + }); } - ) + ); it( 'update device works with callback urls that :4430 as a port', () => { - var goodPushCallback = 'https://updates.push.services.mozilla.com:4430' - var email = server.uniqueEmail() - var password = 'test password' + var goodPushCallback = 'https://updates.push.services.mozilla.com:4430'; + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -423,35 +423,35 @@ describe('remote device', function () { pushCallback: goodPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') + assert.ok(device.id, 'device.id was set'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); } ) .catch( function (err) { - assert.fail(err, 'request should have worked') + assert.fail(err, 'request should have worked'); } - ) - }) + ); + }); } - ) + ); it( 'update device works with callback urls that a custom port', () => { - var goodPushCallback = 'https://updates.push.services.mozilla.com:10332' - var email = server.uniqueEmail() - var password = 'test password' + var goodPushCallback = 'https://updates.push.services.mozilla.com:10332'; + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -461,35 +461,35 @@ describe('remote device', function () { pushCallback: goodPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return client.devices() .then( function (devices) { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDevice(deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDevice(deviceInfo); } ) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') + assert.ok(device.id, 'device.id was set'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); } ) .catch( function (err) { - assert.fail(err, 'request should have worked') + assert.fail(err, 'request should have worked'); } - ) - }) + ); + }); } - ) + ); it( 'update device fails with bad dev callbackUrl', () => { - var badPushCallback = 'https://evil.mozaws.net' - var email = server.uniqueEmail() - var password = 'test password' + var badPushCallback = 'https://evil.mozaws.net'; + var email = server.uniqueEmail(); + var password = 'test password'; var deviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'test device', @@ -497,32 +497,32 @@ describe('remote device', function () { pushCallback: badPushCallback, pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice(deviceInfo) .then( function (r) { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); } ) .catch( function (err) { - assert.equal(err.code, 400, 'err.code was 400') - assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter') - assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation') + assert.equal(err.code, 400, 'err.code was 400'); + assert.equal(err.errno, 107, 'err.errno was 107, invalid parameter'); + assert.equal(err.validation.keys[0], 'pushCallback', 'bad pushCallback caught in validation'); } - ) - }) + ); + }); } - ) + ); it( 'device registration ignores deprecated "capabilities" field', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; return Client.create(config.publicUrl, email, password) .then( function (client) { @@ -530,26 +530,26 @@ describe('remote device', function () { name: 'a very capable device', type: 'desktop', capabilities: [], - } + }; return client.updateDevice(deviceInfo) .then( function (device) { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.ok(! device.capabilities, 'device.capabilities was ignored') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.ok(! device.capabilities, 'device.capabilities was ignored'); } - ) + ); } - ) + ); } - ) + ); it( 'device registration from a different session', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; var deviceInfo = [ { name: 'first device', @@ -559,133 +559,133 @@ describe('remote device', function () { name: 'second device', type: 'desktop' } - ] + ]; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (client) { return Client.login(config.publicUrl, email, password) .then( function (secondClient) { - return secondClient.updateDevice(deviceInfo[0]) + return secondClient.updateDevice(deviceInfo[0]); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].isCurrentDevice, false, 'devices returned false isCurrentDevice') - assert.equal(devices[0].name, deviceInfo[0].name, 'devices returned correct name') - assert.equal(devices[0].type, deviceInfo[0].type, 'devices returned correct type') - return client.updateDevice(deviceInfo[1]) + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].isCurrentDevice, false, 'devices returned false isCurrentDevice'); + assert.equal(devices[0].name, deviceInfo[0].name, 'devices returned correct name'); + assert.equal(devices[0].type, deviceInfo[0].type, 'devices returned correct type'); + return client.updateDevice(deviceInfo[1]); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 2, 'devices returned two items') + assert.equal(devices.length, 2, 'devices returned two items'); if (devices[0].name === deviceInfo[1].name) { // database results are unordered, swap them if necessary - var swap = {} + var swap = {}; Object.keys(devices[0]).forEach(function (key) { - swap[key] = devices[0][key] - devices[0][key] = devices[1][key] - devices[1][key] = swap[key] - }) + swap[key] = devices[0][key]; + devices[0][key] = devices[1][key]; + devices[1][key] = swap[key]; + }); } - assert.equal(devices[0].isCurrentDevice, false, 'devices returned false isCurrentDevice for first item') - assert.equal(devices[0].name, deviceInfo[0].name, 'devices returned correct name for first item') - assert.equal(devices[0].type, deviceInfo[0].type, 'devices returned correct type for first item') - assert.equal(devices[1].isCurrentDevice, true, 'devices returned true isCurrentDevice for second item') - assert.equal(devices[1].name, deviceInfo[1].name, 'devices returned correct name for second item') - assert.equal(devices[1].type, deviceInfo[1].type, 'devices returned correct type for second item') + assert.equal(devices[0].isCurrentDevice, false, 'devices returned false isCurrentDevice for first item'); + assert.equal(devices[0].name, deviceInfo[0].name, 'devices returned correct name for first item'); + assert.equal(devices[0].type, deviceInfo[0].type, 'devices returned correct type for first item'); + assert.equal(devices[1].isCurrentDevice, true, 'devices returned true isCurrentDevice for second item'); + assert.equal(devices[1].name, deviceInfo[1].name, 'devices returned correct name for second item'); + assert.equal(devices[1].type, deviceInfo[1].type, 'devices returned correct type for second item'); return P.all([ client.destroyDevice(devices[0].id), client.destroyDevice(devices[1].id) - ]) + ]); } - ) + ); } - ) + ); } - ) + ); it( 'ensures all device push fields appear together', () => { - var email = server.uniqueEmail() - var password = 'test password' + var email = server.uniqueEmail(); + var password = 'test password'; var deviceInfo = { name: 'test device', type: 'desktop', pushCallback: 'https://updates.push.services.mozilla.com/qux', pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return Client.create(config.publicUrl, email, password) .then( function (client) { return client.updateDevice(deviceInfo) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices[0].pushCallback, deviceInfo.pushCallback, 'devices returned correct pushCallback') - assert.equal(devices[0].pushPublicKey, deviceInfo.pushPublicKey, 'devices returned correct pushPublicKey') - assert.equal(devices[0].pushAuthKey, deviceInfo.pushAuthKey, 'devices returned correct pushAuthKey') - assert.equal(devices[0].pushEndpointExpired, false, 'devices returned correct pushEndpointExpired') + assert.equal(devices[0].pushCallback, deviceInfo.pushCallback, 'devices returned correct pushCallback'); + assert.equal(devices[0].pushPublicKey, deviceInfo.pushPublicKey, 'devices returned correct pushPublicKey'); + assert.equal(devices[0].pushAuthKey, deviceInfo.pushAuthKey, 'devices returned correct pushAuthKey'); + assert.equal(devices[0].pushEndpointExpired, false, 'devices returned correct pushEndpointExpired'); return client.updateDevice({ id: client.device.id, pushCallback: 'https://updates.push.services.mozilla.com/foo' - }) + }); } ) .then(assert.fail, function (err) { - assert.equal(err.errno, 107) - assert.equal(err.message, 'Invalid parameter in request body') + assert.equal(err.errno, 107); + assert.equal(err.message, 'Invalid parameter in request body'); } - ) + ); } - ) + ); } - ) + ); it( 'invalid public keys are cleanly rejected', () => { - var email = server.uniqueEmail() - var password = 'test password' - var invalidPublicKey = Buffer.alloc(65) - invalidPublicKey.fill('\0') + var email = server.uniqueEmail(); + var password = 'test password'; + var invalidPublicKey = Buffer.alloc(65); + invalidPublicKey.fill('\0'); var deviceInfo = { name: 'test device', type: 'desktop', pushCallback: 'https://updates.push.services.mozilla.com/qux', pushPublicKey: base64url(invalidPublicKey), pushAuthKey: base64url(crypto.randomBytes(16)) - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (client) { return client.updateDevice(deviceInfo) .then( function () { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (err) { - assert.equal(err.code, 400, 'err.code was 400') - assert.equal(err.errno, 107, 'err.errno was 107') + assert.equal(err.code, 400, 'err.code was 400'); + assert.equal(err.errno, 107, 'err.errno was 107'); } ) // A rather strange nodejs bug means that invalid push keys @@ -699,30 +699,30 @@ describe('remote device', function () { '7909539347682027142806529730913413168629935827890798' + '72007974809511698859885077002492642203267408776123', 'e': '65537' - } - return client.sign(publicKey, 1000 * 60 * 5) + }; + return client.sign(publicKey, 1000 * 60 * 5); } ) .then( function (cert) { - assert.equal(typeof(cert), 'string', 'cert was successfully signed') + assert.equal(typeof(cert), 'string', 'cert was successfully signed'); } - ) + ); } - ) + ); } - ) + ); it( 'device updates can correctly handle upgrades from placeholder record', () => { - const email = server.uniqueEmail() - const password = 'test password' + const email = server.uniqueEmail(); + const password = 'test password'; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((client) => { const deviceInfo = { name: 'test device' - } + }; // Sign a certificate to generate a placeholder device record. const publicKey = { 'algorithm': 'RS', @@ -730,30 +730,30 @@ describe('remote device', function () { '7909539347682027142806529730913413168629935827890798' + '72007974809511698859885077002492642203267408776123', 'e': '65537' - } + }; return client.sign(publicKey, 1000 * 60 * 5, undefined, { service: 'sync' }) .then(() => { - return client.devices() + return client.devices(); }) .then((devices) => { - assert.equal(devices.length, 1, 'devices returned 1 item') - assert.equal(devices[0].name, '', 'placeholder device record has an empty name') - assert.equal(devices[0].type, 'desktop', 'placeholder device record type defaults to desktop') + assert.equal(devices.length, 1, 'devices returned 1 item'); + assert.equal(devices[0].name, '', 'placeholder device record has an empty name'); + assert.equal(devices[0].type, 'desktop', 'placeholder device record type defaults to desktop'); // Now attempt to update the name on the placeholder record. - deviceInfo.id = devices[0].id - return client.updateDevice(deviceInfo) + deviceInfo.id = devices[0].id; + return client.updateDevice(deviceInfo); }) .then((device) => { - assert.equal(device.id, deviceInfo.id, 'device.id was set correctly') - assert.equal(device.name, deviceInfo.name, 'device name was updated correctly') - assert.equal(device.type, 'desktop', 'device type still defaults to desktop') - }) - }) + assert.equal(device.id, deviceInfo.id, 'device.id was set correctly'); + assert.equal(device.name, deviceInfo.name, 'device name was updated correctly'); + assert.equal(device.type, 'desktop', 'device type still defaults to desktop'); + }); + }); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/device_tests_refresh_tokens.js b/test/remote/device_tests_refresh_tokens.js index 4616ddda..259b6882 100644 --- a/test/remote/device_tests_refresh_tokens.js +++ b/test/remote/device_tests_refresh_tokens.js @@ -2,79 +2,79 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const TestServer = require('../test_server') -const Client = require('../client')() -const config = require('../../config').getProperties() -const buf = require('buf').hex -const testUtils = require('../lib/util') -const oauthServerModule = require('../../fxa-oauth-server/lib/server') +const { assert } = require('chai'); +const crypto = require('crypto'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const config = require('../../config').getProperties(); +const buf = require('buf').hex; +const testUtils = require('../lib/util'); +const oauthServerModule = require('../../fxa-oauth-server/lib/server'); const encrypt = require('../../fxa-oauth-server/lib/encrypt'); -const log = { trace () {}, info () {}, error () {} } +const log = { trace () {}, info () {}, error () {} }; const lastAccessTimeUpdates = { enabled: true, sampleRate: 1, earliestSaneTimestamp: config.lastAccessTimeUpdates.earliestSaneTimestamp, -} +}; const Token = require('../../lib/tokens')(log, { lastAccessTimeUpdates: lastAccessTimeUpdates, tokenLifetimes: { sessionTokenWithoutDevice: 2419200000 } -}) +}); -const PUBLIC_CLIENT_ID = '3c49430b43dfba77' -const NON_PUBLIC_CLIENT_ID = 'dcdb5ae7add825d2' -const OAUTH_CLIENT_NAME = 'Android Components Reference Browser' +const PUBLIC_CLIENT_ID = '3c49430b43dfba77'; +const NON_PUBLIC_CLIENT_ID = 'dcdb5ae7add825d2'; +const OAUTH_CLIENT_NAME = 'Android Components Reference Browser'; describe('remote device with refresh tokens', function () { - this.timeout(15000) - let client - let db - let email - let oauthServer - let oauthServerDb - let password - let refreshToken - let server + this.timeout(15000); + let client; + let db; + let email; + let oauthServer; + let oauthServerDb; + let password; + let refreshToken; + let server; before(() => { - config.lastAccessTimeUpdates = lastAccessTimeUpdates - const DB = require('../../lib/db')(config, log, Token) + config.lastAccessTimeUpdates = lastAccessTimeUpdates; + const DB = require('../../lib/db')(config, log, Token); - testUtils.disableLogs() + testUtils.disableLogs(); return oauthServerModule.create().then((s) => { - oauthServer = s - oauthServerDb = require('../../fxa-oauth-server/lib/db') + oauthServer = s; + oauthServerDb = require('../../fxa-oauth-server/lib/db'); - return oauthServer.start() + return oauthServer.start(); }).then(() => { return TestServer.start(config, false, {oauthServer}).then(s => { - server = s - return DB.connect(config[config.db.backend]) + server = s; + return DB.connect(config[config.db.backend]); }).then(x => { - db = x - }) - }) - }) + db = x; + }); + }); + }); after(async () => { - await TestServer.stop(server) - await oauthServer.stop() - testUtils.restoreStdoutWrite() - }) + await TestServer.stop(server); + await oauthServer.stop(); + testUtils.restoreStdoutWrite(); + }); beforeEach(() => { - email = server.uniqueEmail() - password = 'test password' + email = server.uniqueEmail(); + password = 'test password'; return Client.create(config.publicUrl, email, password).then((c) => { - client = c - }) - }) + client = c; + }); + }); it('device registration after account creation', () => { return oauthServerDb.generateRefreshToken({ @@ -83,7 +83,7 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') + refreshToken = refresh.token.toString('hex'); const deviceInfo = { name: 'test device 🍓🔥在𝌆', type: 'mobile', @@ -91,42 +91,42 @@ describe('remote device with refresh tokens', function () { pushCallback: '', pushPublicKey: '', pushAuthKey: '' - } + }; return client.devicesWithRefreshToken(refreshToken) .then((devices) => { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDeviceWithRefreshToken(refreshToken, deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDeviceWithRefreshToken(refreshToken, deviceInfo); }) .then((device) => { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct') - assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct') - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.deepEqual(device.availableCommands, deviceInfo.availableCommands, 'device.availableCommands is correct'); + assert.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct'); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is correct'); - return client.devicesWithRefreshToken(refreshToken) + return client.devicesWithRefreshToken(refreshToken); }) .then((devices) => { - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name') - assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type') - assert.deepEqual(devices[0].availableCommands, deviceInfo.availableCommands, 'devices returned correct availableCommands') - assert.equal(devices[0].pushCallback, deviceInfo.pushCallback, 'devices returned empty pushCallback') - assert.equal(devices[0].pushPublicKey, deviceInfo.pushPublicKey, 'devices returned correct pushPublicKey') - assert.equal(devices[0].pushAuthKey, deviceInfo.pushAuthKey, 'devices returned correct pushAuthKey') - assert.equal(devices[0].pushEndpointExpired, '', 'devices returned correct pushEndpointExpired') - return client.destroyDeviceWithRefreshToken(refreshToken, devices[0].id) - }) - }) - }) + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name'); + assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type'); + assert.deepEqual(devices[0].availableCommands, deviceInfo.availableCommands, 'devices returned correct availableCommands'); + assert.equal(devices[0].pushCallback, deviceInfo.pushCallback, 'devices returned empty pushCallback'); + assert.equal(devices[0].pushPublicKey, deviceInfo.pushPublicKey, 'devices returned correct pushPublicKey'); + assert.equal(devices[0].pushAuthKey, deviceInfo.pushAuthKey, 'devices returned correct pushAuthKey'); + assert.equal(devices[0].pushEndpointExpired, '', 'devices returned correct pushEndpointExpired'); + return client.destroyDeviceWithRefreshToken(refreshToken, devices[0].id); + }); + }); + }); it('device registration without optional parameters', () => { - let deviceId + let deviceId; return oauthServerDb.generateRefreshToken({ clientId: buf(PUBLIC_CLIENT_ID), @@ -134,57 +134,57 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') + refreshToken = refresh.token.toString('hex'); const deviceInfo = { name: 'test device', type: 'mobile' - } + }; return client.devicesWithRefreshToken(refreshToken) .then((devices) => { - assert.equal(devices.length, 0, 'devices returned no items') - return client.updateDeviceWithRefreshToken(refreshToken, deviceInfo) + assert.equal(devices.length, 0, 'devices returned no items'); + return client.updateDeviceWithRefreshToken(refreshToken, deviceInfo); }) .then((device) => { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, deviceInfo.name, 'device.name is correct') - assert.equal(device.type, deviceInfo.type, 'device.type is correct') - assert.equal(device.pushCallback, undefined, 'device.pushCallback is empty') - assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is empty') - assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is empty') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, deviceInfo.name, 'device.name is correct'); + assert.equal(device.type, deviceInfo.type, 'device.type is correct'); + assert.equal(device.pushCallback, undefined, 'device.pushCallback is empty'); + assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is empty'); + assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is empty'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false'); - return client.devicesWithRefreshToken(refreshToken) + return client.devicesWithRefreshToken(refreshToken); }) .then((devices) => { - deviceId = devices[0].id + deviceId = devices[0].id; - assert.equal(devices.length, 1, 'devices returned one item') - assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name') - assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type') - assert.equal(devices[0].pushCallback, undefined, 'pushCallback is empty') - assert.equal(devices[0].pushPublicKey, undefined, 'pushPublicKey is empty') - assert.equal(devices[0].pushAuthKey, undefined, 'pushAuthKey is empty') - assert.equal(devices[0].pushEndpointExpired, false, 'devices returned false pushEndpointExpired') + assert.equal(devices.length, 1, 'devices returned one item'); + assert.equal(devices[0].name, deviceInfo.name, 'devices returned correct name'); + assert.equal(devices[0].type, deviceInfo.type, 'devices returned correct type'); + assert.equal(devices[0].pushCallback, undefined, 'pushCallback is empty'); + assert.equal(devices[0].pushPublicKey, undefined, 'pushPublicKey is empty'); + assert.equal(devices[0].pushAuthKey, undefined, 'pushAuthKey is empty'); + assert.equal(devices[0].pushEndpointExpired, false, 'devices returned false pushEndpointExpired'); - return oauthServerDb.getRefreshToken(encrypt.hash((refreshToken))) + return oauthServerDb.getRefreshToken(encrypt.hash((refreshToken))); }).then((tokenObj) => { - assert.ok(tokenObj, 'refreshToken should exist') + assert.ok(tokenObj, 'refreshToken should exist'); - return client.destroyDeviceWithRefreshToken(refreshToken, deviceId) + return client.destroyDeviceWithRefreshToken(refreshToken, deviceId); }).then(() => { // deleting the device also deletes the associated refreshToken - return oauthServerDb.getRefreshToken(encrypt.hash((refreshToken))) + return oauthServerDb.getRefreshToken(encrypt.hash((refreshToken))); }).then((tokenObj) => { - assert.notOk(tokenObj, 'refreshToken should be gone') - }) - }) + assert.notOk(tokenObj, 'refreshToken should be gone'); + }); + }); - }) + }); it('device registration using oauth client name', () => { - let refreshToken2 + let refreshToken2; return oauthServerDb.generateRefreshToken({ clientId: buf(PUBLIC_CLIENT_ID), @@ -192,38 +192,38 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') + refreshToken = refresh.token.toString('hex'); return oauthServerDb.generateRefreshToken({ clientId: buf(PUBLIC_CLIENT_ID), userId: buf(client.uid), email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' - }) + }); }).then((refresh) => { - refreshToken2 = refresh.token.toString('hex') + refreshToken2 = refresh.token.toString('hex'); - return client.devicesWithRefreshToken(refreshToken) + return client.devicesWithRefreshToken(refreshToken); }).then((devices) => { - assert.equal(devices.length, 0) - return client.updateDeviceWithRefreshToken(refreshToken, {}) + assert.equal(devices.length, 0); + return client.updateDeviceWithRefreshToken(refreshToken, {}); }).then((device) => { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, OAUTH_CLIENT_NAME, 'device.name is correct') - assert.equal(device.type, 'desktop', 'device.type is correct') - assert.equal(device.pushCallback, undefined, 'device.pushCallback is empty') - assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is empty') - assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is empty') - assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false') + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, OAUTH_CLIENT_NAME, 'device.name is correct'); + assert.equal(device.type, 'desktop', 'device.type is correct'); + assert.equal(device.pushCallback, undefined, 'device.pushCallback is empty'); + assert.equal(device.pushPublicKey, undefined, 'device.pushPublicKey is empty'); + assert.equal(device.pushAuthKey, undefined, 'device.pushAuthKey is empty'); + assert.equal(device.pushEndpointExpired, false, 'device.pushEndpointExpired is false'); - return client.devicesWithRefreshToken(refreshToken2) + return client.devicesWithRefreshToken(refreshToken2); }).then((devices) => { - assert.equal(devices.length, 1) - assert.equal(devices[0].name, OAUTH_CLIENT_NAME, 'device.name is correct') - assert.equal(devices[0].type, 'desktop', 'device.type is correct') + assert.equal(devices.length, 1); + assert.equal(devices[0].name, OAUTH_CLIENT_NAME, 'device.name is correct'); + assert.equal(devices[0].type, 'desktop', 'device.type is correct'); }); - }) + }); it('device registration without required name parameter', () => { return oauthServerDb.generateRefreshToken({ @@ -232,15 +232,15 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') - return client.updateDeviceWithRefreshToken(refreshToken, { type: 'mobile' }) + refreshToken = refresh.token.toString('hex'); + return client.updateDeviceWithRefreshToken(refreshToken, { type: 'mobile' }); }).then((device) => { - assert.ok(device.id, 'device.id was set') - assert.ok(device.createdAt > 0, 'device.createdAt was set') - assert.equal(device.name, OAUTH_CLIENT_NAME, 'device.name is correct') - assert.equal(device.type, 'mobile', 'device.type is correct') - }) - }) + assert.ok(device.id, 'device.id was set'); + assert.ok(device.createdAt > 0, 'device.createdAt was set'); + assert.equal(device.name, OAUTH_CLIENT_NAME, 'device.name is correct'); + assert.equal(device.type, 'mobile', 'device.type is correct'); + }); + }); it('does not allow non-public clients', () => { return oauthServerDb.generateRefreshToken({ @@ -249,19 +249,19 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') - return client.updateDeviceWithRefreshToken(refreshToken, { type: 'mobile' }) + refreshToken = refresh.token.toString('hex'); + return client.updateDeviceWithRefreshToken(refreshToken, { type: 'mobile' }); }).then(() => assert.fail('must fail'), (err) => { - assert.equal(err.message, 'Not a public client') - assert.equal(err.errno, 166, 'Uses the auth-server errno') - }) - }) + assert.equal(err.message, 'Not a public client'); + assert.equal(err.errno, 166, 'Uses the auth-server errno'); + }); + }); it('throws conflicting device errors', () => { const conflictingDeviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'Device' - } + }; return oauthServerDb.generateRefreshToken({ clientId: buf(PUBLIC_CLIENT_ID), @@ -269,15 +269,15 @@ describe('remote device with refresh tokens', function () { email: client.email, scope: 'profile https://identity.mozilla.com/apps/oldsync' }).then((refresh) => { - refreshToken = refresh.token.toString('hex') - conflictingDeviceInfo.refreshTokenId = refreshToken + refreshToken = refresh.token.toString('hex'); + conflictingDeviceInfo.refreshTokenId = refreshToken; - return db.createDevice(client.uid, conflictingDeviceInfo) + return db.createDevice(client.uid, conflictingDeviceInfo); }).then(() => { - conflictingDeviceInfo.id = crypto.randomBytes(16).toString('hex') - return db.createDevice(client.uid, conflictingDeviceInfo) + conflictingDeviceInfo.id = crypto.randomBytes(16).toString('hex'); + return db.createDevice(client.uid, conflictingDeviceInfo); }).then(() => assert.fail('must fail'), (err) => { - assert.equal(err.message, 'Session already registered by another device') - }) - }) -}) + assert.equal(err.message, 'Session already registered by another device'); + }); + }); +}); diff --git a/test/remote/email_validity_tests.js b/test/remote/email_validity_tests.js index 787e6c71..2a8e986a 100644 --- a/test/remote/email_validity_tests.js +++ b/test/remote/email_validity_tests.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() -var P = require('../../lib/promise') +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); +var P = require('../../lib/promise'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote email validity', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( '/account/create with a variety of malformed email addresses', () => { - var pwd = '123456' + var pwd = '123456'; var emails = [ 'notAnEmailAddress', @@ -38,25 +38,25 @@ describe('remote email validity', function() { 'me@example-.com', 'me@example.-com', '\uD83D\uDCA9@unicodepooforyou.com' - ] + ]; emails.forEach(function(email, i) { emails[i] = Client.create(config.publicUrl, email, pwd) .then( assert.fail, function (err) { - assert.equal(err.code, 400, 'http 400 : malformed email is rejected') + assert.equal(err.code, 400, 'http 400 : malformed email is rejected'); } - ) - }) + ); + }); - return P.all(emails) + return P.all(emails); } - ) + ); it( '/account/create with a variety of unusual but valid email addresses', () => { - var pwd = '123456' + var pwd = '123456'; var emails = [ 'tim@tim-example.net', @@ -64,25 +64,25 @@ describe('remote email validity', function() { '#!?-@t-e-s-assert.c-o-m', String.fromCharCode(1234) + '@example.com', 'test@' + String.fromCharCode(5678) + '.com' - ] + ]; emails.forEach(function(email, i) { emails[i] = Client.create(config.publicUrl, email, pwd) .then( function(c) { - return c.destroyAccount() + return c.destroyAccount(); }, function (err) { - assert(false, 'Email address ' + email + " should have been allowed, but it wasn't") + assert(false, 'Email address ' + email + " should have been allowed, but it wasn't"); } - ) - }) + ); + }); - return P.all(emails) + return P.all(emails); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/flow_tests.js b/test/remote/flow_tests.js index 10d4e665..c3a31bd8 100644 --- a/test/remote/flow_tests.js +++ b/test/remote/flow_tests.js @@ -2,116 +2,116 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const Client = require('../client')() -var TestServer = require('../test_server') -var jwtool = require('fxa-jwtool') +const { assert } = require('chai'); +const Client = require('../client')(); +var TestServer = require('../test_server'); +var jwtool = require('fxa-jwtool'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); -var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile) +var pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile); describe('remote flow', function() { - this.timeout(15000) - let server - let email1 - config.signinConfirmation.skipForNewAccounts.enabled = true + this.timeout(15000); + let server; + let email1; + config.signinConfirmation.skipForNewAccounts.enabled = true; before(() => { return TestServer.start(config) .then(s => { - server = s - email1 = server.uniqueEmail() - }) - }) + server = s; + email1 = server.uniqueEmail(); + }); + }); it( 'Create account flow', () => { - var email = email1 - var password = 'allyourbasearebelongtous' - var client = null + var email = email1; + var password = 'allyourbasearebelongtous'; + var client = null; var publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' - } - var duration = 1000 * 60 * 60 * 24 // 24 hours + }; + var duration = 1000 * 60 * 60 * 24; // 24 hours return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.equal(typeof keys.kA, 'string', 'kA exists') - assert.equal(typeof keys.wrapKb, 'string', 'wrapKb exists') - assert.equal(typeof keys.kB, 'string', 'kB exists') - assert.equal(client.kB.length, 64, 'kB exists, has the right length') + assert.equal(typeof keys.kA, 'string', 'kA exists'); + assert.equal(typeof keys.wrapKb, 'string', 'wrapKb exists'); + assert.equal(typeof keys.kB, 'string', 'kB exists'); + assert.equal(client.kB.length, 64, 'kB exists, has the right length'); } ) .then( function () { - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); } ) .then( function (cert) { - assert.equal(typeof(cert), 'string', 'cert exists') - var payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') + assert.equal(typeof(cert), 'string', 'cert exists'); + var payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); } - ) + ); } - ) + ); it( 'Login flow', () => { - var email = email1 - var password = 'allyourbasearebelongtous' - var client = null + var email = email1; + var password = 'allyourbasearebelongtous'; + var client = null; var publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' - } - var duration = 1000 * 60 * 60 * 24 // 24 hours + }; + var duration = 1000 * 60 * 60 * 24; // 24 hours return Client.login(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') - assert.ok(client.uid, 'got a uid') - return client.keys() + client = x; + assert.ok(client.authAt, 'authAt was set'); + assert.ok(client.uid, 'got a uid'); + return client.keys(); } ) .then( function (keys) { - assert.equal(typeof keys.kA, 'string', 'kA exists') - assert.equal(typeof keys.wrapKb, 'string', 'wrapKb exists') - assert.equal(typeof keys.kB, 'string', 'kB exists') - assert.equal(client.kB.length, 64, 'kB exists, has the right length') + assert.equal(typeof keys.kA, 'string', 'kA exists'); + assert.equal(typeof keys.wrapKb, 'string', 'wrapKb exists'); + assert.equal(typeof keys.kB, 'string', 'kB exists'); + assert.equal(client.kB.length, 64, 'kB exists, has the right length'); } ) .then( function () { - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); } ) .then( function (cert) { - assert.equal(typeof(cert), 'string', 'cert exists') + assert.equal(typeof(cert), 'string', 'cert exists'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/misc_tests.js b/test/remote/misc_tests.js index 543c58ca..b4dbe3cd 100644 --- a/test/remote/misc_tests.js +++ b/test/remote/misc_tests.js @@ -2,174 +2,174 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const P = require('../../lib/promise') -const hawk = require('hawk') -const request = P.promisify(require('request'), { multiArgs: true }) +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const P = require('../../lib/promise'); +const hawk = require('hawk'); +const request = P.promisify(require('request'), { multiArgs: true }); -const config = require('../../config').getProperties() +const config = require('../../config').getProperties(); describe('remote misc', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); function testVersionRoute(route) { return () => { return request(config.publicUrl + route).spread((res, body) => { - var json = JSON.parse(body) - assert.deepEqual(Object.keys(json), ['version', 'commit', 'source']) - assert.equal(json.version, require('../../package.json').version, 'package version') - assert.ok(json.source && json.source !== 'unknown', 'source repository') + var json = JSON.parse(body); + assert.deepEqual(Object.keys(json), ['version', 'commit', 'source']); + assert.equal(json.version, require('../../package.json').version, 'package version'); + assert.ok(json.source && json.source !== 'unknown', 'source repository'); // check that the git hash just looks like a hash - assert.ok(json.commit.match(/^[0-9a-f]{40}$/), 'The git hash actually looks like one') - }) - } + assert.ok(json.commit.match(/^[0-9a-f]{40}$/), 'The git hash actually looks like one'); + }); + }; } function testCORSHeader(withAllowedOrigin) { - var randomAllowedOrigin = config.corsOrigin[Math.floor(Math.random() * config.corsOrigin.length)] - var expectedOrigin = withAllowedOrigin ? randomAllowedOrigin : undefined + var randomAllowedOrigin = config.corsOrigin[Math.floor(Math.random() * config.corsOrigin.length)]; + var expectedOrigin = withAllowedOrigin ? randomAllowedOrigin : undefined; return () => { var options = { url: config.publicUrl + '/' - } + }; if (withAllowedOrigin !== undefined) { options.headers = { 'Origin': (withAllowedOrigin ? randomAllowedOrigin : 'http://notallowed') - } + }; } return request(options).spread((res, body) => { - assert.equal(res.headers['access-control-allow-origin'], expectedOrigin, 'Access-Control-Allow-Origin header was set correctly') - }) - } + assert.equal(res.headers['access-control-allow-origin'], expectedOrigin, 'Access-Control-Allow-Origin header was set correctly'); + }); + }; } it( 'unsupported api version', () => { return request(config.publicUrl + '/v0/account/create').spread((res) => { - assert.equal(res.statusCode, 410, 'http gone') - }) + assert.equal(res.statusCode, 410, 'http gone'); + }); } - ) + ); it( '/__heartbeat__ returns a 200 OK', () => { return request(config.publicUrl + '/__heartbeat__').spread((res) => { - assert.equal(res.statusCode, 200, 'http ok') - }) + assert.equal(res.statusCode, 200, 'http ok'); + }); } - ) + ); it( '/__lbheartbeat__ returns a 200 OK', () => { return request(config.publicUrl + '/__lbheartbeat__').spread((res) => { - assert.equal(res.statusCode, 200, 'http ok') - }) + assert.equal(res.statusCode, 200, 'http ok'); + }); } - ) + ); it( '/ returns version, git hash and source repo', testVersionRoute('/') - ) + ); it( '/__version__ returns version, git hash and source repo', testVersionRoute('/__version__') - ) + ); it( 'returns no Access-Control-Allow-Origin with no Origin set', testCORSHeader(undefined) - ) + ); it( 'returns correct Access-Control-Allow-Origin with whitelisted Origin', testCORSHeader(true) - ) + ); it( 'returns no Access-Control-Allow-Origin with not whitelisted Origin', testCORSHeader(false) - ) + ); it( '/verify_email redirects', () => { - var path = '/v1/verify_email?code=0000&uid=0000' + var path = '/v1/verify_email?code=0000&uid=0000'; return request( { url: config.publicUrl + path, followRedirect: false }) .spread((res, body) => { - assert.equal(res.statusCode, 302, 'redirected') + assert.equal(res.statusCode, 302, 'redirected'); //assert.equal(res.headers.location, config.contentServer.url + path) - }) + }); } - ) + ); it( '/complete_reset_password redirects', () => { - var path = '/v1/complete_reset_password?code=0000&email=a@b.c&token=0000' + var path = '/v1/complete_reset_password?code=0000&email=a@b.c&token=0000'; return request( { url: config.publicUrl + path, followRedirect: false }) .spread((res, body) => { - assert.equal(res.statusCode, 302, 'redirected') + assert.equal(res.statusCode, 302, 'redirected'); //assert.equal(res.headers.location, config.contentServer.url + path) - }) + }); } - ) + ); it( 'timestamp header', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var url = null - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var url = null; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - client = c - return client.login() + client = c; + return client.login(); } ) .then( function () { - url = client.api.baseURL + '/account/keys' - return client.api.Token.KeyFetchToken.fromHex(client.keyFetchToken) + url = client.api.baseURL + '/account/keys'; + return client.api.Token.KeyFetchToken.fromHex(client.keyFetchToken); } ) .then( function (token) { - var method = 'GET' + var method = 'GET'; var verify = { credentials: token, timestamp: Math.floor(Date.now() / 1000) - } + }; var headers = { Authorization: hawk.client.header(url, method, verify).header - } + }; return request( { method: method, @@ -178,14 +178,14 @@ describe('remote misc', function() { json: true }) .spread((res, body) => { - var now = +new Date() / 1000 - assert.ok(res.headers.timestamp > now - 60, 'has timestamp header') - assert.ok(res.headers.timestamp < now + 60, 'has timestamp header') - }) + var now = +new Date() / 1000; + assert.ok(res.headers.timestamp > now - 60, 'has timestamp header'); + assert.ok(res.headers.timestamp < now + 60, 'has timestamp header'); + }); } - ) + ); } - ) + ); it( 'Strict-Transport-Security header', @@ -195,15 +195,15 @@ describe('remote misc', function() { url: config.publicUrl + '/' }) .spread((res, body) => { - assert.equal(res.headers['strict-transport-security'], 'max-age=15552000; includeSubDomains') - }) + assert.equal(res.headers['strict-transport-security'], 'max-age=15552000; includeSubDomains'); + }); } - ) + ); it( 'oversized payload', () => { - var client = new Client(config.publicUrl) + var client = new Client(config.publicUrl); return client.api.doRequest( 'POST', client.api.baseURL + '/get_random_bytes', @@ -213,84 +213,84 @@ describe('remote misc', function() { ) .then( function (body) { - assert(false, 'request should have failed') + assert(false, 'request should have failed'); }, function (err) { if (err.errno) { - assert.equal(err.errno, 113, 'payload too large') + assert.equal(err.errno, 113, 'payload too large'); } else { // nginx returns an html response - assert.ok(/413 Request Entity Too Large/.test(err), 'payload too large') + assert.ok(/413 Request Entity Too Large/.test(err), 'payload too large'); } } - ) + ); } - ) + ); it( 'random bytes', () => { - var client = new Client(config.publicUrl) + var client = new Client(config.publicUrl); return client.api.getRandomBytes() .then( function (x) { - assert.equal(x.data.length, 64) + assert.equal(x.data.length, 64); } - ) + ); } - ) + ); it( 'fetch /.well-known/browserid support document', () => { - var client = new Client(config.publicUrl) + var client = new Client(config.publicUrl); function fetch(url) { - return client.api.doRequest('GET', config.publicUrl + url) + return client.api.doRequest('GET', config.publicUrl + url); } return fetch('/.well-known/browserid') .then( function (doc) { - assert.ok(doc.hasOwnProperty('public-key'), 'doc has public key') - assert.ok(/^[0-9]+$/.test(doc['public-key'].n), 'n is base 10') - assert.ok(/^[0-9]+$/.test(doc['public-key'].e), 'e is base 10') - assert.ok(doc.hasOwnProperty('authentication'), 'doc has auth page') - assert.ok(doc.hasOwnProperty('provisioning'), 'doc has provisioning page') - assert.equal(doc.keys.length, 1) - return doc + assert.ok(doc.hasOwnProperty('public-key'), 'doc has public key'); + assert.ok(/^[0-9]+$/.test(doc['public-key'].n), 'n is base 10'); + assert.ok(/^[0-9]+$/.test(doc['public-key'].e), 'e is base 10'); + assert.ok(doc.hasOwnProperty('authentication'), 'doc has auth page'); + assert.ok(doc.hasOwnProperty('provisioning'), 'doc has provisioning page'); + assert.equal(doc.keys.length, 1); + return doc; } - ) + ); } - ) + ); it( 'fail on hawk payload mismatch', () => { - const email = server.uniqueEmail() - const password = 'allyourbasearebelongtous' - let url = null - let client = null + const email = server.uniqueEmail(); + const password = 'allyourbasearebelongtous'; + let url = null; + let client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((c) => { - client = c - return client.api.Token.SessionToken.fromHex(client.sessionToken) + client = c; + return client.api.Token.SessionToken.fromHex(client.sessionToken); }) .then((token) => { - url = client.api.baseURL + '/account/device' - const method = 'POST' + url = client.api.baseURL + '/account/device'; + const method = 'POST'; const payload = { name: 'my cool device', type: 'desktop' - } + }; const verify = { credentials: token, payload: JSON.stringify(payload), timestamp: Math.floor(Date.now() / 1000) - } + }; const headers = { Authorization: hawk.client.header(url, method, verify).header - } - payload.name = 'my stealthily-changed device name' + }; + payload.name = 'my stealthily-changed device name'; return request( { method: method, @@ -299,15 +299,15 @@ describe('remote misc', function() { body: JSON.stringify(payload) }) .spread((res) => { - const body = JSON.parse(res.body) - assert.equal(res.statusCode, 401, 'the request was rejected') - assert.equal(body.errno, 109, 'the errno indicates an invalid signature') - }) - }) + const body = JSON.parse(res.body); + assert.equal(res.statusCode, 401, 'the request was rejected'); + assert.equal(body.errno, 109, 'the errno indicates an invalid signature'); + }); + }); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/password_change_tests.js b/test/remote/password_change_tests.js index a7de5e53..3ada7a57 100644 --- a/test/remote/password_change_tests.js +++ b/test/remote/password_change_tests.js @@ -2,454 +2,454 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const Client = require('../client')() -var config = require('../../config').getProperties() -var TestServer = require('../test_server') -var url = require('url') +const { assert } = require('chai'); +const Client = require('../client')(); +var config = require('../../config').getProperties(); +var TestServer = require('../test_server'); +var url = require('url'); -var tokens = require('../../lib/tokens')({ trace: function() {}}) +var tokens = require('../../lib/tokens')({ trace: function() {}}); function getSessionTokenId(sessionTokenHex) { return tokens.SessionToken.fromHex(sessionTokenHex) .then( function (token) { - return token.id + return token.id; } - ) + ); } describe('remote password change', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts.enabled = false + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'password change, with unverified session', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'foobar' - var kB, kA, client, firstAuthPW, originalSessionToken + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'foobar'; + var kB, kA, client, firstAuthPW, originalSessionToken; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - originalSessionToken = client.sessionToken - firstAuthPW = x.authPW.toString('hex') - return client.keys() + client = x; + originalSessionToken = client.sessionToken; + firstAuthPW = x.authPW.toString('hex'); + return client.keys(); } ) .then( function (keys) { - kB = keys.kB - kA = keys.kA + kB = keys.kB; + kA = keys.kA; } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { // Login from different location to created unverified session - return Client.login(config.publicUrl, email, password, {keys:true}) + return Client.login(config.publicUrl, email, password, {keys:true}); } ) .then( function (c) { - client = c + client = c; } ) .then( function () { // Ignore confirm login email - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { // Verify correct status - assert.equal(status.verified, false, 'account is unverified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is unverified') + assert.equal(status.verified, false, 'account is unverified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is unverified'); } ) .then( function () { - return getSessionTokenId(client.sessionToken) + return getSessionTokenId(client.sessionToken); } ) .then( function (sessionTokenId) { - return client.changePassword(newPassword, undefined, sessionTokenId) + return client.changePassword(newPassword, undefined, sessionTokenId); } ) .then( function (response) { // Verify correct change password response - assert.notEqual(response.sessionToken, originalSessionToken, 'session token has changed') - assert.ok(response.keyFetchToken, 'key fetch token returned') - assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed') + assert.notEqual(response.sessionToken, originalSessionToken, 'session token has changed'); + assert.ok(response.keyFetchToken, 'key fetch token returned'); + assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var subject = emailData.headers['subject'] - assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly') - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') + var subject = emailData.headers['subject']; + assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly'); + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { // Verify correct status - assert.equal(status.verified, false, 'account is unverified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, false, 'account session is unverified') + assert.equal(status.verified, false, 'account is unverified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, false, 'account session is unverified'); } ) .then( function () { - return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}) + return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.deepEqual(keys.kB, kB, 'kB is preserved') - assert.deepEqual(keys.kA, kA, 'kA is preserved') + assert.deepEqual(keys.kB, kB, 'kB is preserved'); + assert.deepEqual(keys.kA, kA, 'kA is preserved'); } - ) + ); } - ) + ); it( 'password change, with verified session', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'foobar' - var kB, kA, client, firstAuthPW, originalSessionToken + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'foobar'; + var kB, kA, client, firstAuthPW, originalSessionToken; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - originalSessionToken = client.sessionToken - firstAuthPW = x.authPW.toString('hex') - return client.keys() + client = x; + originalSessionToken = client.sessionToken; + firstAuthPW = x.authPW.toString('hex'); + return client.keys(); } ) .then( function (keys) { - kB = keys.kB - kA = keys.kA + kB = keys.kB; + kA = keys.kA; } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { - return getSessionTokenId(client.sessionToken) + return getSessionTokenId(client.sessionToken); } ) .then( function (sessionTokenId) { - return client.changePassword(newPassword, undefined, sessionTokenId) + return client.changePassword(newPassword, undefined, sessionTokenId); } ) .then( function (response) { - assert.notEqual(response.sessionToken, originalSessionToken, 'session token has changed') - assert.ok(response.keyFetchToken, 'key fetch token returned') - assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed') + assert.notEqual(response.sessionToken, originalSessionToken, 'session token has changed'); + assert.ok(response.keyFetchToken, 'key fetch token returned'); + assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var subject = emailData.headers['subject'] - assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly') - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') - assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip location data') + var subject = emailData.headers['subject']; + assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly'); + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); + assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip location data'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { - return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}) + return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.deepEqual(keys.kB, kB, 'kB is preserved') - assert.deepEqual(keys.kA, kA, 'kA is preserved') + assert.deepEqual(keys.kB, kB, 'kB is preserved'); + assert.deepEqual(keys.kA, kA, 'kA is preserved'); } - ) + ); } - ) + ); it( 'password change, with raw session data rather than session token id, return invalid token error', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'foobar' - var client + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'foobar'; + var client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') + assert.equal(status.verified, true, 'account is verified'); } ) .then( function () { - return client.changePassword(newPassword, undefined, client.sessionToken) + return client.changePassword(newPassword, undefined, client.sessionToken); } ) .then( function () { - assert(false) + assert(false); }, function (err) { - assert.equal(err.errno, 110, 'Invalid token error') - assert.equal(err.message, 'The authentication token could not be found') + assert.equal(err.errno, 110, 'Invalid token error'); + assert.equal(err.message, 'The authentication token could not be found'); } - ) + ); } - ) + ); it( 'password change w/o sessionToken', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'foobar' - var kB, kA, client, firstAuthPW + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'foobar'; + var kB, kA, client, firstAuthPW; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - firstAuthPW = x.authPW.toString('hex') - return client.keys() + client = x; + firstAuthPW = x.authPW.toString('hex'); + return client.keys(); } ) .then( function (keys) { - kB = keys.kB - kA = keys.kA + kB = keys.kB; + kA = keys.kA; } ) .then( function () { - return client.changePassword(newPassword) + return client.changePassword(newPassword); } ) .then( function (response) { - assert(! response.sessionToken, 'no session token returned') - assert(! response.keyFetchToken, 'no key fetch token returned') - assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed') + assert(! response.sessionToken, 'no session token returned'); + assert(! response.keyFetchToken, 'no key fetch token returned'); + assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed'); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var subject = emailData.headers['subject'] - assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly') - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') + var subject = emailData.headers['subject']; + assert.equal(subject, 'Your Firefox Account password has been changed', 'password email subject set correctly'); + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); } ) .then( function () { - return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}) + return Client.loginAndVerify(config.publicUrl, email, newPassword, server.mailbox, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.deepEqual(keys.kB, kB, 'kB is preserved') - assert.deepEqual(keys.kA, kA, 'kA is preserved') + assert.deepEqual(keys.kB, kB, 'kB is preserved'); + assert.deepEqual(keys.kA, kA, 'kA is preserved'); } - ) + ); } - ) + ); it( 'wrong password on change start', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function () { - client.authPW = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') - return client.changePassword('foobar') + client.authPW = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); + return client.changePassword('foobar'); } ) .then( () => assert(false), function (err) { - assert.equal(err.errno, 103, 'invalid password') + assert.equal(err.errno, 103, 'invalid password'); } - ) + ); } - ) + ); it('shouldn\'t change password on account with TOTP without passing sessionToken', () => { - const email = server.uniqueEmail() - const password = 'ok' - let client + const email = server.uniqueEmail(); + const password = 'ok'; + let client; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((res) => { - client = res + client = res; // Doesn't specify a sessionToken to use - return client.changePassword('foobar') + return client.changePassword('foobar'); }) .then(assert.fail, (err) => { - assert.equal(err.errno, 138, 'unverified session') - }) - }) + assert.equal(err.errno, 138, 'unverified session'); + }); + }); it('should change password on account with TOTP with verified TOTP sessionToken', () => { - const email = server.uniqueEmail() - const password = 'ok' - let client, firstAuthPW + const email = server.uniqueEmail(); + const password = 'ok'; + let client, firstAuthPW; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((res) => { - client = res - firstAuthPW = client.authPW.toString('hex') - return getSessionTokenId(client.sessionToken) + client = res; + firstAuthPW = client.authPW.toString('hex'); + return getSessionTokenId(client.sessionToken); }) .then((sessionTokenId) => { - return client.changePassword('foobar', undefined, sessionTokenId) + return client.changePassword('foobar', undefined, sessionTokenId); }) .then((response) => { - assert(response.sessionToken, 'session token returned') - assert(response.keyFetchToken, 'key fetch token returned') - assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed') - }) - }) + assert(response.sessionToken, 'session token returned'); + assert(response.keyFetchToken, 'key fetch token returned'); + assert.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed'); + }); + }); it('shouldn\'t change password on account with TOTP with unverified sessionToken', () => { - const email = server.uniqueEmail() - const password = 'ok' - let client + const email = server.uniqueEmail(); + const password = 'ok'; + let client; return Client.createAndVerifyAndTOTP(config.publicUrl, email, password, server.mailbox, {keys: true}) // Create new unverified client .then(() => Client.login(config.publicUrl, email, password, {keys:true})) .then((res) => { - client = res - return getSessionTokenId(client.sessionToken) + client = res; + return getSessionTokenId(client.sessionToken); }) .then((sessionTokenId) => { - return client.changePassword('foobar', undefined, sessionTokenId) + return client.changePassword('foobar', undefined, sessionTokenId); }) .then(assert.fail, (err) => { - assert.equal(err.errno, 138, 'unverified session') - }) - }) + assert.equal(err.errno, 138, 'unverified session'); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/password_forgot_tests.js b/test/remote/password_forgot_tests.js index 3eff5846..53a138df 100644 --- a/test/remote/password_forgot_tests.js +++ b/test/remote/password_forgot_tests.js @@ -2,450 +2,450 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var url = require('url') -const Client = require('../client')() -var TestServer = require('../test_server') -var crypto = require('crypto') -var base64url = require('base64url') +const { assert } = require('chai'); +var url = require('url'); +const Client = require('../client')(); +var TestServer = require('../test_server'); +var crypto = require('crypto'); +var base64url = require('base64url'); -var config = require('../../config').getProperties() -const mocks = require('../mocks') +var config = require('../../config').getProperties(); +const mocks = require('../mocks'); describe('remote password forgot', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts.enabled = true + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts.enabled = true; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'forgot password', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var newPassword = 'ez' - var wrapKb = null - var kA = null - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var newPassword = 'ez'; + var wrapKb = null; + var kA = null; + var client = null; var opts = { keys: true, metricsContext: mocks.generateMetricsContext() - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, opts) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - wrapKb = keys.wrapKb - kA = keys.kA - return client.forgotPassword() + wrapKb = keys.wrapKb; + kA = keys.kA; + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip location data') - assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') - assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') - assert.equal(emailData.headers['x-template-name'], 'recoveryEmail', 'correct template set') - return emailData.headers['x-recovery-code'] + assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip location data'); + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set'); + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set'); + assert.equal(emailData.headers['x-template-name'], 'recoveryEmail', 'correct template set'); + return emailData.headers['x-recovery-code']; } ) .then( function (code) { - assert.throws(function() { client.resetPassword(newPassword) }) - return resetPassword(client, code, newPassword, undefined, opts) + assert.throws(function() { client.resetPassword(newPassword); }); + return resetPassword(client, code, newPassword, undefined, opts); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.email, 'email is in the link') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.email, 'email is in the link'); - assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') - assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') - assert.equal(emailData.headers['x-template-name'], 'passwordResetEmail', 'correct template set') + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set'); + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set'); + assert.equal(emailData.headers['x-template-name'], 'passwordResetEmail', 'correct template set'); } ) .then( // make sure we can still login after password reset function () { - return Client.login(config.publicUrl, email, newPassword, {keys:true}) + return Client.login(config.publicUrl, email, newPassword, {keys:true}); } ) .then( function (x) { - client = x - return client.keys() + client = x; + return client.keys(); } ) .then( function (keys) { - assert.equal(typeof keys.wrapKb, 'string', 'yep, wrapKb') - assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset') - assert.equal(kA, keys.kA, 'kA was not reset') - assert.equal(typeof client.kB, 'string') - assert.equal(client.kB.length, 64, 'kB exists, has the right length') + assert.equal(typeof keys.wrapKb, 'string', 'yep, wrapKb'); + assert.notEqual(wrapKb, keys.wrapKb, 'wrapKb was reset'); + assert.equal(kA, keys.kA, 'kA was not reset'); + assert.equal(typeof client.kB, 'string'); + assert.equal(client.kB.length, 64, 'kB exists, has the right length'); } - ) + ); } - ) + ); it( 'forgot password limits verify attempts', () => { - var code = null - var email = server.uniqueEmail() - var password = 'hothamburger' - var client = null + var code = null; + var email = server.uniqueEmail(); + var password = 'hothamburger'; + var client = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function () { - client = new Client(config.publicUrl) - client.email = email - return client.forgotPassword() + client = new Client(config.publicUrl); + client.email = email; + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (c) { - code = c + code = c; } ) .then( function () { - return client.reforgotPassword() + return client.reforgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (c) { - assert.equal(code, c, 'same code as before') + assert.equal(code, c, 'same code as before'); } ) .then( function () { - return resetPassword(client, '00000000000000000000000000000000', 'password') + return resetPassword(client, '00000000000000000000000000000000', 'password'); } ) .then( function () { - assert(false, 'reset password with bad code') + assert(false, 'reset password with bad code'); }, function (err) { - assert.equal(err.tries, 2, 'used a try') - assert.equal(err.message, 'Invalid verification code', 'bad attempt 1') + assert.equal(err.tries, 2, 'used a try'); + assert.equal(err.message, 'Invalid verification code', 'bad attempt 1'); } ) .then( function () { - return resetPassword(client, '00000000000000000000000000000000', 'password') + return resetPassword(client, '00000000000000000000000000000000', 'password'); } ) .then( function () { - assert(false, 'reset password with bad code') + assert(false, 'reset password with bad code'); }, function (err) { - assert.equal(err.tries, 1, 'used a try') - assert.equal(err.message, 'Invalid verification code', 'bad attempt 2') + assert.equal(err.tries, 1, 'used a try'); + assert.equal(err.message, 'Invalid verification code', 'bad attempt 2'); } ) .then( function () { - return resetPassword(client, '00000000000000000000000000000000', 'password') + return resetPassword(client, '00000000000000000000000000000000', 'password'); } ) .then( function () { - assert(false, 'reset password with bad code') + assert(false, 'reset password with bad code'); }, function (err) { - assert.equal(err.tries, 0, 'used a try') - assert.equal(err.message, 'Invalid verification code', 'bad attempt 3') + assert.equal(err.tries, 0, 'used a try'); + assert.equal(err.message, 'Invalid verification code', 'bad attempt 3'); } ) .then( function () { - return resetPassword(client, '00000000000000000000000000000000', 'password') + return resetPassword(client, '00000000000000000000000000000000', 'password'); } ) .then( function () { - assert(false, 'reset password with invalid token') + assert(false, 'reset password with invalid token'); }, function (err) { - assert.equal(err.message, 'The authentication token could not be found', 'token is now invalid') + assert.equal(err.message, 'The authentication token could not be found', 'token is now invalid'); } - ) + ); } - ) + ); it( 'recovery email link', () => { - var email = server.uniqueEmail() - var password = 'something' - var client = null + var email = server.uniqueEmail(); + var password = 'something'; + var client = null; var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain + '/', service: 'sync' - } + }; return Client.create(config.publicUrl, email, password, options) .then( function (c) { - client = c + client = c; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function () { - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.token, 'uid is in link') - assert.ok(query.code, 'code is in link') - assert.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link') - assert.equal(query.service, options.service, 'service is in link') - assert.equal(query.email, email, 'email is in link') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.token, 'uid is in link'); + assert.ok(query.code, 'code is in link'); + assert.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link'); + assert.equal(query.service, options.service, 'service is in link'); + assert.equal(query.email, email, 'email is in link'); } - ) + ); } - ) + ); it( 'password forgot status with valid token', () => { - var email = server.uniqueEmail() - var password = 'something' + var email = server.uniqueEmail(); + var password = 'something'; return Client.create(config.publicUrl, email, password) .then( function (c) { return c.forgotPassword() .then( function () { - return c.api.passwordForgotStatus(c.passwordForgotToken) + return c.api.passwordForgotStatus(c.passwordForgotToken); } ) .then( function (x) { - assert.equal(x.tries, 3, 'three tries remaining') - assert.ok(x.ttl > 0 && x.ttl <= (60 * 60), 'ttl is ok') + assert.equal(x.tries, 3, 'three tries remaining'); + assert.ok(x.ttl > 0 && x.ttl <= (60 * 60), 'ttl is ok'); } - ) + ); } - ) + ); } - ) + ); it( 'password forgot status with invalid token', () => { - var client = new Client(config.publicUrl) + var client = new Client(config.publicUrl); return client.api.passwordForgotStatus('0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF') .then( () => assert(false), function (err) { - assert.equal(err.errno, 110, 'invalid token') + assert.equal(err.errno, 110, 'invalid token'); } - ) + ); } - ) + ); it( '/password/forgot/verify_code should set an unverified account as verified', () => { - var email = server.uniqueEmail() - var password = 'something' - var client = null + var email = server.uniqueEmail(); + var password = 'something'; + var client = null; return Client.create(config.publicUrl, email, password) - .then(function (c) { client = c }) + .then(function (c) { client = c; }) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'email unverified') + assert.equal(status.verified, false, 'email unverified'); } ) .then( function () { - return server.mailbox.waitForCode(email) // ignore this code + return server.mailbox.waitForCode(email); // ignore this code } ) .then( function () { - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - return client.verifyPasswordResetCode(code) + return client.verifyPasswordResetCode(code); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account unverified') + assert.equal(status.verified, true, 'account unverified'); } - ) + ); } - ) + ); it( 'forgot password with service query parameter', () => { - var email = server.uniqueEmail() + var email = server.uniqueEmail(); var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain + '/', serviceQuery: 'sync' - } - var client + }; + var client; return Client.create(config.publicUrl, email, 'wibble', options) .then(function (c) { - client = c + client = c; }) .then(function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then(function () { - return client.forgotPassword() + return client.forgotPassword(); }) .then(function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then(function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.equal(query.service, options.serviceQuery, 'service is in link') - }) + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.equal(query.service, options.serviceQuery, 'service is in link'); + }); } - ) + ); it( 'forgot password, then get device list', () => { - var email = server.uniqueEmail() - var newPassword = 'foo' - var client + var email = server.uniqueEmail(); + var newPassword = 'foo'; + var client; return Client.createAndVerify(config.publicUrl, email, 'bar', server.mailbox) .then( function (c) { - client = c + client = c; return client.updateDevice({ name: 'baz', type: 'mobile', pushCallback: 'https://updates.push.services.mozilla.com/qux', pushPublicKey: mocks.MOCK_PUSH_KEY, pushAuthKey: base64url(crypto.randomBytes(16)) - }) + }); } ) .then( function () { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 1, 'devices list contains 1 item') + assert.equal(devices.length, 1, 'devices list contains 1 item'); } ) .then( function () { - return client.forgotPassword() + return client.forgotPassword(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - return resetPassword(client, code, newPassword) + return resetPassword(client, code, newPassword); } ) .then( function () { - return Client.login(config.publicUrl, email, newPassword) + return Client.login(config.publicUrl, email, newPassword); } ) .then( function (client) { - return client.devices() + return client.devices(); } ) .then( function (devices) { - assert.equal(devices.length, 0, 'devices list is empty') + assert.equal(devices.length, 0, 'devices list is empty'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) + return TestServer.stop(server); + }); function resetPassword(client, code, newPassword, headers, options) { return client.verifyPasswordResetCode(code, headers, options) .then(function() { - return client.resetPassword(newPassword, {}, options) - }) + return client.resetPassword(newPassword, {}, options); + }); } -}) +}); diff --git a/test/remote/push_db_tests.js b/test/remote/push_db_tests.js index c949c0ef..514c90a7 100644 --- a/test/remote/push_db_tests.js +++ b/test/remote/push_db_tests.js @@ -2,29 +2,29 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var P = require('../../lib/promise') -var uuid = require('uuid') -var crypto = require('crypto') -var base64url = require('base64url') -var proxyquire = require('proxyquire') -const log = { trace () {}, info () {}, error () {} } +const { assert } = require('chai'); +var P = require('../../lib/promise'); +var uuid = require('uuid'); +var crypto = require('crypto'); +var base64url = require('base64url'); +var proxyquire = require('proxyquire'); +const log = { trace () {}, info () {}, error () {} }; -var config = require('../../config').getProperties() -var TestServer = require('../test_server') -var Token = require('../../lib/tokens')(log) +var config = require('../../config').getProperties(); +var TestServer = require('../test_server'); +var Token = require('../../lib/tokens')(log); const DB = require('../../lib/db')( config, log, Token -) +); -var zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex') -var zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex') +var zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex'); +var zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'); -var SESSION_TOKEN_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0' +var SESSION_TOKEN_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0'; var ACCOUNT = { uid: uuid.v4('binary').toString('hex'), email: 'push' + Math.random() + '@bar.com', @@ -36,7 +36,7 @@ var ACCOUNT = { kA: zeroBuffer32, wrapWrapKb: zeroBuffer32, tokenVerificationId: zeroBuffer16 -} +}; var mockLog = { error: function () { }, @@ -46,27 +46,27 @@ var mockLog = { }, info: function () { } -} +}; describe('remote push db', function() { - this.timeout(15000) + this.timeout(15000); - let dbServer, db + let dbServer, db; before(() => { return TestServer.start(config) .then(s => { - dbServer = s - return DB.connect(config[config.db.backend]) + dbServer = s; + return DB.connect(config[config.db.backend]); }) .then(x => { - db = x - }) - }) + db = x; + }); + }); it( 'push db tests', () => { - var sessionTokenId + var sessionTokenId; var deviceInfo = { id: crypto.randomBytes(16).toString('hex'), name: 'my push device', @@ -76,84 +76,84 @@ describe('remote push db', function() { pushPublicKey: base64url(Buffer.concat([Buffer.from('\x04'), crypto.randomBytes(64)])), pushAuthKey: base64url(crypto.randomBytes(16)), pushEndpointExpired: false - } + }; // two tests below, first for unknown 400 level error the device push info will stay the same // second, for a known 400 error we reset the device var mocksKnown400 = { 'web-push': { sendNotification: function (endpoint, params) { - var err = new Error('Failed 400 level') - err.statusCode = 410 - return P.reject(err) + var err = new Error('Failed 400 level'); + err.statusCode = 410; + return P.reject(err); } } - } + }; var mocksUnknown400 = { 'web-push': { sendNotification: function (endpoint, params) { - var err = new Error('Failed 429 level') - err.statusCode = 429 - return P.reject(err) + var err = new Error('Failed 429 level'); + err.statusCode = 429; + return P.reject(err); } } - } + }; return db.createAccount(ACCOUNT) .then(function() { - return db.emailRecord(ACCOUNT.email) + return db.emailRecord(ACCOUNT.email); }) .then(function(emailRecord) { - emailRecord.createdAt = Date.now() - return db.createSessionToken(emailRecord, SESSION_TOKEN_UA) + emailRecord.createdAt = Date.now(); + return db.createSessionToken(emailRecord, SESSION_TOKEN_UA); }) .then(function (sessionToken) { - sessionTokenId = sessionToken.id - deviceInfo.sessionTokenId = sessionTokenId - return db.createDevice(ACCOUNT.uid, deviceInfo) + sessionTokenId = sessionToken.id; + deviceInfo.sessionTokenId = sessionTokenId; + return db.createDevice(ACCOUNT.uid, deviceInfo); }) .then(function (device) { - assert.equal(device.name, deviceInfo.name) - assert.equal(device.pushCallback, deviceInfo.pushCallback) - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey) - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey) + assert.equal(device.name, deviceInfo.name); + assert.equal(device.pushCallback, deviceInfo.pushCallback); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey); }) .then(function () { - return db.devices(ACCOUNT.uid) + return db.devices(ACCOUNT.uid); }) .then(devices => { - const pushWithUnknown400 = proxyquire('../../lib/push', mocksUnknown400)(mockLog, db, {}) - return pushWithUnknown400.sendPush(ACCOUNT.uid, devices, 'accountVerify') + const pushWithUnknown400 = proxyquire('../../lib/push', mocksUnknown400)(mockLog, db, {}); + return pushWithUnknown400.sendPush(ACCOUNT.uid, devices, 'accountVerify'); }) .then(function () { - return db.devices(ACCOUNT.uid) + return db.devices(ACCOUNT.uid); }) .then(function (devices) { - var device = devices[0] - assert.equal(device.name, deviceInfo.name) - assert.equal(device.pushCallback, deviceInfo.pushCallback) - assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct') - assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct') - assert.equal(device.pushEndpointExpired, deviceInfo.pushEndpointExpired, 'device.pushEndpointExpired is correct') + var device = devices[0]; + assert.equal(device.name, deviceInfo.name); + assert.equal(device.pushCallback, deviceInfo.pushCallback); + assert.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct'); + assert.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct'); + assert.equal(device.pushEndpointExpired, deviceInfo.pushEndpointExpired, 'device.pushEndpointExpired is correct'); - const pushWithKnown400 = proxyquire('../../lib/push', mocksKnown400)(mockLog, db, {}) - return pushWithKnown400.sendPush(ACCOUNT.uid, devices, 'accountVerify') + const pushWithKnown400 = proxyquire('../../lib/push', mocksKnown400)(mockLog, db, {}); + return pushWithKnown400.sendPush(ACCOUNT.uid, devices, 'accountVerify'); }) .then(function () { - return db.devices(ACCOUNT.uid) + return db.devices(ACCOUNT.uid); }) .then(function (devices) { - var device = devices[0] - assert.equal(device.name, deviceInfo.name) - assert.equal(device.pushEndpointExpired, true, 'device.pushEndpointExpired is correct') - }) + var device = devices[0]; + assert.equal(device.name, deviceInfo.name); + assert.equal(device.pushEndpointExpired, true, 'device.pushEndpointExpired is correct'); + }); } - ) + ); after(() => { return P.all([ TestServer.stop(dbServer), db.close() - ]) - }) -}) + ]); + }); +}); diff --git a/test/remote/recovery_code_tests.js b/test/remote/recovery_code_tests.js index 2899fb40..6d514a26 100644 --- a/test/remote/recovery_code_tests.js +++ b/test/remote/recovery_code_tests.js @@ -2,186 +2,186 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const config = require('../../config').getProperties() -const TestServer = require('../test_server') -const Client = require('../client')() -const otplib = require('otplib') -const BASE_36 = require('../../lib/routes/validators').BASE_36 +const { assert } = require('chai'); +const config = require('../../config').getProperties(); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const otplib = require('otplib'); +const BASE_36 = require('../../lib/routes/validators').BASE_36; describe('remote recovery codes', function () { - let server, client, email, recoveryCodes - const recoveryCodeCount = 9 - const password = 'pssssst' + let server, client, email, recoveryCodes; + const recoveryCodeCount = 9; + const password = 'pssssst'; const metricsContext = { flowBeginTime: Date.now(), flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' - } + }; - this.timeout(10000) + this.timeout(10000); otplib.authenticator.options = { encoding: 'hex', window: 10 - } + }; before(() => { - config.totp.recoveryCodes.count = recoveryCodeCount - config.totp.recoveryCodes.notifyLowCount = recoveryCodeCount - 2 + config.totp.recoveryCodes.count = recoveryCodeCount; + config.totp.recoveryCodes.notifyLowCount = recoveryCodeCount - 2; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); beforeEach(() => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((x) => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); return client.createTotpToken({metricsContext}) .then((result) => { otplib.authenticator.options = { secret: result.secret - } + }; // Verify TOTP token so that initial recovery codes are generated - const code = otplib.authenticator.generate() + const code = otplib.authenticator.generate(); return client.verifyTotpCode(code, {metricsContext}) .then((response) => { - assert.equal(response.success, true, 'totp codes match') + assert.equal(response.success, true, 'totp codes match'); - recoveryCodes = response.recoveryCodes - assert.equal(response.recoveryCodes.length, recoveryCodeCount, 'recovery codes returned') - return server.mailbox.waitForEmail(email) + recoveryCodes = response.recoveryCodes; + assert.equal(response.recoveryCodes.length, recoveryCodeCount, 'recovery codes returned'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postAddTwoStepAuthenticationEmail', 'correct template sent') - }) - }) - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postAddTwoStepAuthenticationEmail', 'correct template sent'); + }); + }); + }); + }); it('should create recovery codes', () => { - assert.ok(recoveryCodes) - assert.equal(recoveryCodes.length, recoveryCodeCount, 'recovery codes returned') + assert.ok(recoveryCodes); + assert.equal(recoveryCodes.length, recoveryCodeCount, 'recovery codes returned'); recoveryCodes.forEach((code) => { - assert.equal(code.length > 1, true, 'correct length') - assert.equal(BASE_36.test(code), true, 'code is hex') - }) - }) + assert.equal(code.length > 1, true, 'correct length'); + assert.equal(BASE_36.test(code), true, 'code is hex'); + }); + }); it('should replace recovery codes', () => { return client.replaceRecoveryCodes() .then((result) => { - assert.ok(result.recoveryCodes.length, recoveryCodeCount, 'recovery codes returned') - assert.notDeepEqual(result, recoveryCodes, 'recovery codes should not match') + assert.ok(result.recoveryCodes.length, recoveryCodeCount, 'recovery codes returned'); + assert.notDeepEqual(result, recoveryCodes, 'recovery codes should not match'); - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postNewRecoveryCodesEmail', 'correct template sent') - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postNewRecoveryCodesEmail', 'correct template sent'); + }); + }); describe('recovery code verification', () => { beforeEach(() => { // Create a new unverified session to test recovery codes return Client.login(config.publicUrl, email, password) .then((response) => { - client = response - return client.emailStatus() + client = response; + return client.emailStatus(); }) - .then((res) => assert.equal(res.sessionVerified, false, 'session not verified')) - }) + .then((res) => assert.equal(res.sessionVerified, false, 'session not verified')); + }); it('should fail to consume unknown recovery code', () => { return client.consumeRecoveryCode('1234abcd', {metricsContext}) .then(assert.fail, (err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 156, 'correct error errno') - }) - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 156, 'correct error errno'); + }); + }); it('should consume recovery code and verify session', () => { return client.consumeRecoveryCode(recoveryCodes[0], {metricsContext}) .then((res) => { - assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes') - return client.emailStatus() + assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes'); + return client.emailStatus(); }) .then((res) => { - assert.equal(res.sessionVerified, true, 'session verified') - return server.mailbox.waitForEmail(email) + assert.equal(res.sessionVerified, true, 'session verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent') - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent'); + }); + }); it('should consume recovery code and can remove TOTP token', () => { return client.consumeRecoveryCode(recoveryCodes[0], {metricsContext}) .then((res) => { - assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes') - return server.mailbox.waitForEmail(email) + assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent') - return client.deleteTotpToken() + assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent'); + return client.deleteTotpToken(); }) .then((result) => { - assert.ok(result, 'delete totp token successfully') - return server.mailbox.waitForEmail(email) + assert.ok(result, 'delete totp token successfully'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postRemoveTwoStepAuthenticationEmail', 'correct template sent') - }) - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postRemoveTwoStepAuthenticationEmail', 'correct template sent'); + }); + }); + }); describe('should notify user when recovery codes are low', () => { beforeEach(() => { // Create a new unverified session to test recovery codes return Client.login(config.publicUrl, email, password) .then((response) => { - client = response - return client.emailStatus() + client = response; + return client.emailStatus(); }) - .then((res) => assert.equal(res.sessionVerified, false, 'session not verified')) - }) + .then((res) => assert.equal(res.sessionVerified, false, 'session not verified')); + }); it('should consume recovery code and verify session', () => { return client.consumeRecoveryCode(recoveryCodes[0], {metricsContext}) .then((res) => { - assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes') - return server.mailbox.waitForEmail(email) + assert.equal(res.remaining, recoveryCodeCount - 1, 'correct remaining codes'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent') - return client.consumeRecoveryCode(recoveryCodes[1], {metricsContext}) + assert.equal(emailData.headers['x-template-name'], 'postConsumeRecoveryCodeEmail', 'correct template sent'); + return client.consumeRecoveryCode(recoveryCodes[1], {metricsContext}); }) .then((res) => { - assert.equal(res.remaining, recoveryCodeCount - 2, 'correct remaining codes') - return server.mailbox.waitForEmail(email) + assert.equal(res.remaining, recoveryCodeCount - 2, 'correct remaining codes'); + return server.mailbox.waitForEmail(email); }) .then((emails) => { // The order in which the emails are sent is not guaranteed, test for both possible templates - const email1 = emails[0].headers['x-template-name'] - const email2 = emails[1].headers['x-template-name'] + const email1 = emails[0].headers['x-template-name']; + const email2 = emails[1].headers['x-template-name']; if (email1 === 'postConsumeRecoveryCodeEmail') { - assert.equal(email2, 'lowRecoveryCodesEmail', 'correct template sent') + assert.equal(email2, 'lowRecoveryCodesEmail', 'correct template sent'); } if (email1 === 'lowRecoveryCodesEmail') { - assert.equal(email2, 'postConsumeRecoveryCodeEmail', 'correct template sent') + assert.equal(email2, 'postConsumeRecoveryCodeEmail', 'correct template sent'); } - }) - }) - }) + }); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/recovery_email_change_email.js b/test/remote/recovery_email_change_email.js index e7ea5682..7212ac38 100644 --- a/test/remote/recovery_email_change_email.js +++ b/test/remote/recovery_email_change_email.js @@ -2,327 +2,327 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); -let config, server, client, email, secondEmail -const password = 'allyourbasearebelongtous', newPassword = 'newpassword' +let config, server, client, email, secondEmail; +const password = 'allyourbasearebelongtous', newPassword = 'newpassword'; describe('remote change email', function () { - this.timeout(30000) + this.timeout(30000); before(() => { - config = require('../../config').getProperties() - config.securityHistory.ipProfiling = {} + config = require('../../config').getProperties(); + config.securityHistory.ipProfiling = {}; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); beforeEach(() => { - email = server.uniqueEmail() - secondEmail = server.uniqueEmail('@notrestmail.com') + email = server.uniqueEmail(); + secondEmail = server.uniqueEmail('@notrestmail.com'); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then(function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); }) .then(function () { - return client.emailStatus() + return client.emailStatus(); }) .then(function (status) { - assert.equal(status.verified, true, 'account is verified') - return client.createEmail(secondEmail) + assert.equal(status.verified, true, 'account is verified'); + return client.createEmail(secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail(emailCode, secondEmail) + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - return server.mailbox.waitForEmail(email) - }) - }) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + return server.mailbox.waitForEmail(email); + }); + }); describe('should change primary email', () => { it('fails to change email to an that is not owned by user', () => { - const userEmail2 = server.uniqueEmail() - const anotherEmail = server.uniqueEmail() + const userEmail2 = server.uniqueEmail(); + const anotherEmail = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, userEmail2, password, server.mailbox) .then(function (client2) { - return client2.createEmail(anotherEmail) + return client2.createEmail(anotherEmail); }) .then(function () { return client.setPrimaryEmail(anotherEmail) .then(() => { - assert.fail('Should not have set email that belongs to another account') - }) + assert.fail('Should not have set email that belongs to another account'); + }); }) .catch((err) => { - assert.equal(err.errno, 148, 'returns correct errno') - assert.equal(err.code, 400, 'returns correct error code') - }) - }) + assert.equal(err.errno, 148, 'returns correct errno'); + assert.equal(err.code, 400, 'returns correct error code'); + }); + }); it('fails to change email to unverified email', () => { - const someEmail = server.uniqueEmail() + const someEmail = server.uniqueEmail(); return client.createEmail(someEmail) .then(() => { return client.setPrimaryEmail(someEmail) .then(() => { - assert.fail('Should not have set email to an unverified email') - }) + assert.fail('Should not have set email to an unverified email'); + }); }) .catch((err) => { - assert.equal(err.errno, 147, 'returns correct errno') - assert.equal(err.code, 400, 'returns correct error code') - }) - }) + assert.equal(err.errno, 147, 'returns correct errno'); + assert.equal(err.code, 400, 'returns correct error code'); + }); + }); it('can change primary email', () => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[0].email, secondEmail, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') - assert.equal(res[1].email, email, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[0].email, secondEmail, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); + assert.equal(res[1].email, email, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); - return server.mailbox.waitForEmail(secondEmail) + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - assert.equal(emailData.headers['to'], secondEmail, 'to email set') - assert.equal(emailData.headers['cc'], email, 'cc emails set') - assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template') - }) - }) + assert.equal(emailData.headers['to'], secondEmail, 'to email set'); + assert.equal(emailData.headers['cc'], email, 'cc emails set'); + assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template'); + }); + }); it('can login', () => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') + assert.ok(res, 'ok response'); // Verify account can login with new primary email return Client.login(config.publicUrl, secondEmail, password) .then(() => { - assert.fail(new Error('Should have returned correct email for user to login')) - }) + assert.fail(new Error('Should have returned correct email for user to login')); + }); }) .catch((err) => { // Login should fail for this user and return the normalizedEmail used when // the account was created. We then attempt to re-login with this email and pass // the original email used to login - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 120, 'correct errno code') - assert.equal(err.email, email, 'correct hashed email returned') + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 120, 'correct errno code'); + assert.equal(err.email, email, 'correct hashed email returned'); - return Client.login(config.publicUrl, err.email, password, {originalLoginEmail: secondEmail}) + return Client.login(config.publicUrl, err.email, password, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can change password', () => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return Client.login(config.publicUrl, email, password, {originalLoginEmail: secondEmail}) + assert.ok(res, 'ok response'); + return Client.login(config.publicUrl, email, password, {originalLoginEmail: secondEmail}); }) .then((res) => { - client = res - return client.changePassword(newPassword) + client = res; + return client.changePassword(newPassword); }) .then((res) => { - assert.ok(res, 'ok response') - return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) + assert.ok(res, 'ok response'); + return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can reset password', () => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - assert.equal(emailData.headers['to'], secondEmail, 'to email set') - assert.equal(emailData.headers['cc'], email, 'cc emails set') - assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template') + assert.equal(emailData.headers['to'], secondEmail, 'to email set'); + assert.equal(emailData.headers['cc'], email, 'cc emails set'); + assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template'); - client.email = secondEmail - return client.forgotPassword() + client.email = secondEmail; + return client.forgotPassword(); }) .then(() => { - return server.mailbox.waitForCode(secondEmail) + return server.mailbox.waitForCode(secondEmail); }) .then((code) => { - assert.ok(code, 'code is set') - return resetPassword(client, code, newPassword, undefined, {emailToHashWith: email}) + assert.ok(code, 'code is set'); + return resetPassword(client, code, newPassword, undefined, {emailToHashWith: email}); }).then((res) => { - assert.ok(res, 'ok response') - return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) + assert.ok(res, 'ok response'); + return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can delete account', () => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.destroyAccount() + assert.ok(res, 'ok response'); + return client.destroyAccount(); }) .then(() => { return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) .then(() => { - assert.fail('Should not have been able to login after deleting account') + assert.fail('Should not have been able to login after deleting account'); }) .catch((err) => { - assert.equal(err.errno, 102, 'unknown account error code') - assert.equal(err.email, email, 'returns correct email') - }) - }) - }) - }) + assert.equal(err.errno, 102, 'unknown account error code'); + assert.equal(err.email, email, 'returns correct email'); + }); + }); + }); + }); describe('change primary email, deletes old primary', () => { beforeEach(() => { return client.setPrimaryEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - assert.equal(emailData.headers['to'], secondEmail, 'to email set') - assert.equal(emailData.headers['cc'], email, 'cc emails set') - assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template') - return client.deleteEmail(email) + assert.equal(emailData.headers['to'], secondEmail, 'to email set'); + assert.equal(emailData.headers['cc'], email, 'cc emails set'); + assert.equal(emailData.headers['x-template-name'], 'postChangePrimaryEmail', 'returns correct template'); + return client.deleteEmail(email); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, secondEmail, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, secondEmail, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); // Primary account is notified that secondary email has been removed - return server.mailbox.waitForEmail(secondEmail) + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set') - }) - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set'); + }); + }); it('can login', () => { // Verify account can still login with new primary email return Client.login(config.publicUrl, secondEmail, password) .then(() => { - assert.fail(new Error('Should have returned correct email for user to login')) + assert.fail(new Error('Should have returned correct email for user to login')); }) .catch((err) => { // Login should fail for this user and return the normalizedEmail used when // the account was created. We then attempt to re-login with this email and pass // the original email used to login - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 120, 'correct errno code') - assert.equal(err.email, email, 'correct hashed email returned') + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 120, 'correct errno code'); + assert.equal(err.email, email, 'correct hashed email returned'); - return Client.login(config.publicUrl, err.email, password, {originalLoginEmail: secondEmail}) + return Client.login(config.publicUrl, err.email, password, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can change password', () => { return Client.login(config.publicUrl, email, password, {originalLoginEmail: secondEmail}) .then((res) => { - client = res - return client.changePassword(newPassword) + client = res; + return client.changePassword(newPassword); }) .then((res) => { - assert.ok(res, 'ok response') - return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) + assert.ok(res, 'ok response'); + return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can reset password', () => { - client.email = secondEmail + client.email = secondEmail; return client.forgotPassword() .then(() => { - return server.mailbox.waitForCode(secondEmail) + return server.mailbox.waitForCode(secondEmail); }) .then((code) => { - assert.ok(code, 'code is set') - return resetPassword(client, code, newPassword, undefined, {emailToHashWith: email}) + assert.ok(code, 'code is set'); + return resetPassword(client, code, newPassword, undefined, {emailToHashWith: email}); }).then((res) => { - assert.ok(res, 'ok response') - return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) + assert.ok(res, 'ok response'); + return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}); }) .then((res) => { - assert.ok(res, 'ok response') - }) - }) + assert.ok(res, 'ok response'); + }); + }); it('can delete account', () => { return client.destroyAccount() .then(() => { return Client.login(config.publicUrl, email, newPassword, {originalLoginEmail: secondEmail}) .then(() => { - assert.fail('Should not have been able to login after deleting account') + assert.fail('Should not have been able to login after deleting account'); }) .catch((err) => { - assert.equal(err.errno, 102, 'unknown account error code') - assert.equal(err.email, email, 'returns correct email') - }) - }) - }) - }) + assert.equal(err.errno, 102, 'unknown account error code'); + assert.equal(err.email, email, 'returns correct email'); + }); + }); + }); + }); after(() => { - return TestServer.stop(server) - }) + return TestServer.stop(server); + }); function resetPassword(client, code, newPassword, headers, options) { return client.verifyPasswordResetCode(code, headers, options) .then(function () { - return client.resetPassword(newPassword, {}, options) - }) + return client.resetPassword(newPassword, {}, options); + }); } -}) +}); diff --git a/test/remote/recovery_email_emails.js b/test/remote/recovery_email_emails.js index 3d3e0f8e..61cceb88 100644 --- a/test/remote/recovery_email_emails.js +++ b/test/remote/recovery_email_emails.js @@ -2,135 +2,135 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const ERRNO = require('../../lib/error').ERRNO +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const ERRNO = require('../../lib/error').ERRNO; -let config, server, client, email -const password = 'allyourbasearebelongtous' +let config, server, client, email; +const password = 'allyourbasearebelongtous'; function includes(haystack, needle) { - return (haystack.indexOf(needle) > -1) + return (haystack.indexOf(needle) > -1); } describe('remote emails', function () { - this.timeout(30000) + this.timeout(30000); before(() => { - config = require('../../config').getProperties() - config.securityHistory.ipProfiling = {} - config.signinConfirmation.skipForNewAccounts.enabled = false + config = require('../../config').getProperties(); + config.securityHistory.ipProfiling = {}; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); beforeEach(() => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then(function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); }) .then(function () { - return client.emailStatus() + return client.emailStatus(); }) .then(function (status) { - assert.equal(status.verified, true, 'account is verified') - }) - }) + assert.equal(status.verified, true, 'account is verified'); + }); + }); describe('should create and get additional email', () => { it( 'can create', () => { - const secondEmail = server.uniqueEmail() - const thirdEmail = server.uniqueEmail() + const secondEmail = server.uniqueEmail(); + const thirdEmail = server.uniqueEmail(); return client.accountEmails() .then((res) => { - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, email, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') - return client.createEmail(secondEmail) + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, email, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); + return client.createEmail(secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, false, 'returns correct verified') - return client.createEmail(thirdEmail) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, false, 'returns correct verified'); + return client.createEmail(thirdEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 3, 'returns number of emails') - assert.equal(res[2].email, thirdEmail, 'returns correct email') - assert.equal(res[2].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[2].verified, false, 'returns correct verified') + assert.equal(res.length, 3, 'returns number of emails'); + assert.equal(res[2].email, thirdEmail, 'returns correct email'); + assert.equal(res[2].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[2].verified, false, 'returns correct verified'); }) .catch((err) => { - assert.fail(err) - }) + assert.fail(err); + }); } - ) + ); it('can create email if email is unverified on another account', () => { - let client2 - const clientEmail = server.uniqueEmail() - const secondEmail = server.uniqueEmail() + let client2; + const clientEmail = server.uniqueEmail(); + const secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then(() => { - return client.accountEmails() + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, false, 'returns correct verified') + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, false, 'returns correct verified'); return Client.createAndVerify(config.publicUrl, clientEmail, password, server.mailbox) - .catch(assert.fail) + .catch(assert.fail); }) .then((x) => { - client2 = x - assert.equal(client2.email, clientEmail, 'account created with email') - return client2.createEmail(secondEmail) + client2 = x; + assert.equal(client2.email, clientEmail, 'account created with email'); + return client2.createEmail(secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { // Secondary email on first account should have been deleted - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, client.email, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') - return client2.accountEmails() + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, client.email, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); + return client2.accountEmails(); }) .then((res) => { // Secondary email should be on the second account - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, false, 'returns correct verified') - }) - }) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, false, 'returns correct verified'); + }); + }); it( @@ -139,304 +139,304 @@ describe('remote emails', function () { return client.createEmail(email) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 139, 'email already exists errno') - assert.equal(err.code, 400, 'email already exists code') - assert.equal(err.message, 'Can not add secondary email that is same as your primary', 'correct error message') - }) + assert.equal(err.errno, 139, 'email already exists errno'); + assert.equal(err.code, 400, 'email already exists code'); + assert.equal(err.message, 'Can not add secondary email that is same as your primary', 'correct error message'); + }); } - ) + ); it( 'fails create when email exists in user emails', () => { - const secondEmail = server.uniqueEmail() + const secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.createEmail(secondEmail) + assert.ok(res, 'ok response'); + return client.createEmail(secondEmail); }) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 136, 'email already exists errno') - assert.equal(err.code, 400, 'email already exists code') - assert.equal(err.message, 'Email already exists', 'correct error message') - }) + assert.equal(err.errno, 136, 'email already exists errno'); + assert.equal(err.code, 400, 'email already exists code'); + assert.equal(err.message, 'Email already exists', 'correct error message'); + }); } - ) + ); it( 'fails create when verified secondary email exists in other user account', () => { - const anotherUserEmail = server.uniqueEmail() - const anotherUserSecondEmail = server.uniqueEmail() - let anotherClient + const anotherUserEmail = server.uniqueEmail(); + const anotherUserSecondEmail = server.uniqueEmail(); + let anotherClient; return Client.createAndVerify(config.publicUrl, anotherUserEmail, password, server.mailbox) .then((x) => { - anotherClient = x - assert.ok(client.authAt, 'authAt was set') - return anotherClient.createEmail(anotherUserSecondEmail) + anotherClient = x; + assert.ok(client.authAt, 'authAt was set'); + return anotherClient.createEmail(anotherUserSecondEmail); }) .then(() => { - return server.mailbox.waitForEmail(anotherUserSecondEmail) + return server.mailbox.waitForEmail(anotherUserSecondEmail); }) .then((emailData) => { - const emailCode = emailData['headers']['x-verify-code'] - return anotherClient.verifySecondaryEmail(emailCode, anotherUserSecondEmail) + const emailCode = emailData['headers']['x-verify-code']; + return anotherClient.verifySecondaryEmail(emailCode, anotherUserSecondEmail); }) .then((res) => { - assert.ok(res, 'ok response') + assert.ok(res, 'ok response'); return client.createEmail(anotherUserSecondEmail) - .then(assert.fail) + .then(assert.fail); }) .catch((err) => { - assert.equal(err.errno, 136, 'email already exists errno') - assert.equal(err.code, 400, 'email already exists code') - assert.equal(err.message, 'Email already exists', 'correct error message') - }) + assert.equal(err.errno, 136, 'email already exists errno'); + assert.equal(err.code, 400, 'email already exists code'); + assert.equal(err.message, 'Email already exists', 'correct error message'); + }); } - ) + ); it( 'fails for unverified session', () => { - const secondEmail = server.uniqueEmail() + const secondEmail = server.uniqueEmail(); return client.login() .then(() => { - return client.accountEmails() + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, email, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, email, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); return client.createEmail(secondEmail) .then(() => { - assert.fail(new Error('Should not have created email')) - }) + assert.fail(new Error('Should not have created email')); + }); }) .catch((err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 138, 'correct error errno unverified session') - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 138, 'correct error errno unverified session'); + }); } - ) + ); it( 'fails create when email is another users verified primary', () => { - const anotherUserEmail = server.uniqueEmail() + const anotherUserEmail = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, anotherUserEmail, password, server.mailbox) .then(() => { - return client.createEmail(anotherUserEmail) + return client.createEmail(anotherUserEmail); }) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 140, 'email already exists errno') - assert.equal(err.code, 400, 'email already exists code') - assert.equal(err.message, 'Email already exists', 'correct error message') - }) + assert.equal(err.errno, 140, 'email already exists errno'); + assert.equal(err.code, 400, 'email already exists code'); + assert.equal(err.message, 'Email already exists', 'correct error message'); + }); } - ) - }) + ); + }); describe('should verify additional email', () => { - let secondEmail + let secondEmail; beforeEach(() => { - secondEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, false, 'returns correct verified') - }) - }) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, false, 'returns correct verified'); + }); + }); it( 'can verify', () => { return server.mailbox.waitForEmail(secondEmail) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - const verifyLink = emailData['headers']['x-link'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + const verifyLink = emailData['headers']['x-link']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); - assert.equal(includes(verifyLink, 'type=secondary'), true, 'contains type=secondary') - const secondaryEmailParam = 'secondary_email_verified=' + encodeURIComponent(secondEmail) - assert.equal(includes(verifyLink, secondaryEmailParam), true, 'contains correct secondary_email_verified') + assert.equal(includes(verifyLink, 'type=secondary'), true, 'contains type=secondary'); + const secondaryEmailParam = 'secondary_email_verified=' + encodeURIComponent(secondEmail); + assert.equal(includes(verifyLink, secondaryEmailParam), true, 'contains correct secondary_email_verified'); - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail(emailCode, secondEmail) + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - return server.mailbox.waitForEmail(email) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set') - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set'); + }); } - ) + ); it( 'does not verify on random email code', () => { return server.mailbox.waitForEmail(secondEmail) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail('d092f3155ec8d534a7ee7f53b68e9e8b', secondEmail) + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail('d092f3155ec8d534a7ee7f53b68e9e8b', secondEmail); }) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 105, 'correct error errno') - assert.equal(err.code, 400, 'correct error code') - }) + assert.equal(err.errno, 105, 'correct error errno'); + assert.equal(err.code, 400, 'correct error code'); + }); } - ) + ); it( 'can resend verify email code for added address', () => { - var emailCode + var emailCode; return server.mailbox.waitForEmail(secondEmail) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - client.options.email = secondEmail - return client.requestVerifyEmail() + const templateName = emailData['headers']['x-template-name']; + emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + client.options.email = secondEmail; + return client.requestVerifyEmail(); }) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const resendEmailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.equal(resendEmailCode, emailCode, 'emailCode matches') - return client.verifySecondaryEmail(emailCode, secondEmail) + const templateName = emailData['headers']['x-template-name']; + const resendEmailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.equal(resendEmailCode, emailCode, 'emailCode matches'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - }) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + }); } - ) - }) + ); + }); describe('should delete additional email', () => { - let secondEmail + let secondEmail; beforeEach(() => { - secondEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then(() => { - return server.mailbox.waitForEmail(secondEmail) + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - return client.verifySecondaryEmail(emailCode, secondEmail) + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); }) .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set') - }) - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set'); + }); + }); it( 'can delete', () => { return client.deleteEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, email, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, email, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); // Primary account is notified that secondary email has been removed - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set') - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set'); + }); } - ) + ); it( 'resets account tokens when deleting an email', () => { - let code + let code; return client.forgotPassword() .then(() => { - return server.mailbox.waitForEmail(secondEmail) + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - code = emailData.headers['x-recovery-code'] - assert.ok(code, 'recovery code was sent the secondary email') + code = emailData.headers['x-recovery-code']; + assert.ok(code, 'recovery code was sent the secondary email'); }) .then((res) => { - return client.deleteEmail(secondEmail) + return client.deleteEmail(secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 1, 'the secondary email was deleted') - return client.verifyPasswordResetCode(code) + assert.equal(res.length, 1, 'the secondary email was deleted'); + return client.verifyPasswordResetCode(code); }) .then( - () => { assert.fail('password recovery code shoud not have been accepted') }, + () => { assert.fail('password recovery code shoud not have been accepted'); }, (err) => { - assert.equal(err.errno, ERRNO.INVALID_TOKEN, 'token was invalidated') + assert.equal(err.errno, ERRNO.INVALID_TOKEN, 'token was invalidated'); } - ) + ); } - ) + ); it( 'silient fail on delete non-existent email', @@ -444,10 +444,10 @@ describe('remote emails', function () { return client.deleteEmail('fill@yourboots.com') .then((res) => { // User is attempting to delete an email that doesn't exist, make sure nothing blew up - assert.ok(res, 'ok response') - }) + assert.ok(res, 'ok response'); + }); } - ) + ); it( 'fails on delete primary account email', @@ -455,457 +455,457 @@ describe('remote emails', function () { return client.deleteEmail(email) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 137, 'correct error errno') - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.message, 'Can not delete primary email', 'correct error message') - }) + assert.equal(err.errno, 137, 'correct error errno'); + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.message, 'Can not delete primary email', 'correct error message'); + }); } - ) + ); it( 'fails for unverified session', () => { return client.login() .then(() => { - return client.accountEmails() + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); return client.deleteEmail(secondEmail) .then(() => { - assert.fail(new Error('Should not have deleted email')) + assert.fail(new Error('Should not have deleted email')); }, (err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 138, 'correct error errno unverified session') - }) - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 138, 'correct error errno unverified session'); + }); + }); } - ) - }) + ); + }); describe('should receive emails on verified secondary emails', () => { - let secondEmail - let thirdEmail + let secondEmail; + let thirdEmail; beforeEach(() => { - secondEmail = server.uniqueEmail() - thirdEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); + thirdEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail(emailCode, secondEmail) + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - return server.mailbox.waitForEmail(email) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set') + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postVerifySecondaryEmail', 'email template name set'); // Create a third email but don't verify it. This should not appear in the cc-list - return client.createEmail(thirdEmail) - }) - }) + return client.createEmail(thirdEmail); + }); + }); it( 'receives sign-in confirmation email', () => { - let emailCode + let emailCode; return client.login({keys: true}) .then((res) => { - assert.ok(res) - return server.mailbox.waitForEmail(email) + assert.ok(res); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifyLoginEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - return client.requestVerifyEmail() + const templateName = emailData['headers']['x-template-name']; + emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifyLoginEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + return client.requestVerifyEmail(); }) .then((res) => { - assert.ok(res) - return server.mailbox.waitForEmail(email) + assert.ok(res); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const anotherEmailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifyLoginEmail', 'email template name set') - assert.equal(emailCode, anotherEmailCode, 'emailCodes match') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - }) + const templateName = emailData['headers']['x-template-name']; + const anotherEmailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifyLoginEmail', 'email template name set'); + assert.equal(emailCode, anotherEmailCode, 'emailCodes match'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + }); } - ) + ); it( 'receives sign-in unblock email', () => { - let unblockCode + let unblockCode; return client.sendUnblockCode(email) .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - unblockCode = emailData['headers']['x-unblock-code'] - assert.equal(templateName, 'unblockCodeEmail', 'email template name set') - assert.ok(unblockCode, 'code set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - return client.sendUnblockCode(email) + const templateName = emailData['headers']['x-template-name']; + unblockCode = emailData['headers']['x-unblock-code']; + assert.equal(templateName, 'unblockCodeEmail', 'email template name set'); + assert.ok(unblockCode, 'code set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + return client.sendUnblockCode(email); }) .then((res) => { - assert.ok(res) - return server.mailbox.waitForEmail(email) + assert.ok(res); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const anotherUnblockCode = emailData['headers']['x-unblock-code'] - assert.equal(templateName, 'unblockCodeEmail', 'email template name set') - assert.ok(unblockCode, anotherUnblockCode, 'unblock codes match set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - }) + const templateName = emailData['headers']['x-template-name']; + const anotherUnblockCode = emailData['headers']['x-unblock-code']; + assert.equal(templateName, 'unblockCodeEmail', 'email template name set'); + assert.ok(unblockCode, anotherUnblockCode, 'unblock codes match set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + }); } - ) + ); it( 'receives password reset email', () => { return client.forgotPassword() .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'recoveryEmail', 'email template name set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - return emailData.headers['x-recovery-code'] - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'recoveryEmail', 'email template name set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + return emailData.headers['x-recovery-code']; + }); } - ) + ); it( 'receives change password notification', () => { return client.changePassword('password1', undefined) .then((res) => { - assert.ok(res) - return server.mailbox.waitForEmail(email) + assert.ok(res); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'passwordChangedEmail', 'email template name set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'passwordChangedEmail', 'email template name set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + }); } - ) + ); it( 'receives password reset notification', () => { return client.forgotPassword() .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - return emailData.headers['x-recovery-code'] + return emailData.headers['x-recovery-code']; }) .then((code) => { - return resetPassword(client, code, 'password1', undefined, undefined) + return resetPassword(client, code, 'password1', undefined, undefined); }) .then((res) => { - assert.ok(res) - return server.mailbox.waitForEmail(email) + assert.ok(res); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'passwordResetEmail', 'email template name set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - return client.login({keys: true}) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'passwordResetEmail', 'email template name set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + return client.login({keys: true}); }) .then((x) => { - client = x - return client.accountEmails() + client = x; + return client.accountEmails(); }) .then((res) => { // Emails maintain there verification status through the password reset - assert.equal(res.length, 3, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - assert.equal(res[2].email, thirdEmail, 'returns correct email') - assert.equal(res[2].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[2].verified, false, 'returns correct verified') - }) + assert.equal(res.length, 3, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + assert.equal(res[2].email, thirdEmail, 'returns correct email'); + assert.equal(res[2].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[2].verified, false, 'returns correct verified'); + }); } - ) + ); it( 'receives secondary email removed notification', () => { - const fourthEmail = server.uniqueEmail() + const fourthEmail = server.uniqueEmail(); return client.createEmail(fourthEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(fourthEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(fourthEmail); }) .then((emailData) => { - const emailCode = emailData['headers']['x-verify-code'] - return client.verifySecondaryEmail(emailCode, fourthEmail) + const emailCode = emailData['headers']['x-verify-code']; + return client.verifySecondaryEmail(emailCode, fourthEmail); }) .then(() => { // Clear email added template - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then(() => { - return client.deleteEmail(fourthEmail) + return client.deleteEmail(fourthEmail); }) .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'postRemoveSecondaryEmail', 'email template name set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + }); } - ) + ); it( 'receives new device sign-in email', () => { - config.signinConfirmation.skipForNewAccounts.enabled = true + config.signinConfirmation.skipForNewAccounts.enabled = true; return TestServer.start(config) .then(s => { - server = s - email = server.uniqueEmail() - secondEmail = server.uniqueEmail() - thirdEmail = server.uniqueEmail() - return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) + server = s; + email = server.uniqueEmail(); + secondEmail = server.uniqueEmail(); + thirdEmail = server.uniqueEmail(); + return Client.createAndVerify(config.publicUrl, email, password, server.mailbox); }) .then((x) => { - client = x - return client.createEmail(secondEmail) + client = x; + return client.createEmail(secondEmail); }) .then(() => { - return server.mailbox.waitForCode(secondEmail) + return server.mailbox.waitForCode(secondEmail); }) .then((code) => { return client.verifySecondaryEmail(code, secondEmail) .then(() => { // Clear add secondary email notification - return server.mailbox.waitForEmail(email) - }) + return server.mailbox.waitForEmail(email); + }); }) .then(() => { // Create unverified email - return client.createEmail(thirdEmail) + return client.createEmail(thirdEmail); }) .then(() => { - return client.login({keys: true}) + return client.login({keys: true}); }) .then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - assert.equal(templateName, 'newDeviceLoginEmail', 'email template name set') - assert.equal(emailData.cc.length, 1) - assert.equal(emailData.cc[0].address, secondEmail) - }) + const templateName = emailData['headers']['x-template-name']; + assert.equal(templateName, 'newDeviceLoginEmail', 'email template name set'); + assert.equal(emailData.cc.length, 1); + assert.equal(emailData.cc[0].address, secondEmail); + }); } - ) - }) + ); + }); describe('shouldn\'t be able to initiate account reset from secondary email', () => { - let secondEmail + let secondEmail; beforeEach(() => { - secondEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const emailCode = emailData['headers']['x-verify-code'] - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail(emailCode, secondEmail) - }) - }) + const emailCode = emailData['headers']['x-verify-code']; + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail(emailCode, secondEmail); + }); + }); it( 'fails to initiate account reset with known secondary email', () => { - client.email = secondEmail + client.email = secondEmail; return client.forgotPassword() .then(() => { - assert.fail(new Error('should not have been able to initiate reset password')) + assert.fail(new Error('should not have been able to initiate reset password')); }) .catch((err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 145, 'correct errno code') - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 145, 'correct errno code'); + }); } - ) + ); it( 'returns account unknown error when using unknown email', () => { - client.email = 'unknown@email.com' + client.email = 'unknown@email.com'; return client.forgotPassword() .then(() => { - assert.fail(new Error('should not have been able to initiate reset password')) + assert.fail(new Error('should not have been able to initiate reset password')); }) .catch((err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 102, 'correct errno code') - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 102, 'correct errno code'); + }); } - ) - }) + ); + }); describe('shouldn\'t be able to login with secondary email', () => { - let secondEmail + let secondEmail; beforeEach(() => { - secondEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - const templateName = emailData['headers']['x-template-name'] - const emailCode = emailData['headers']['x-verify-code'] - assert.equal(templateName, 'verifySecondaryEmail', 'email template name set') - assert.ok(emailCode, 'emailCode set') - return client.verifySecondaryEmail(emailCode, secondEmail) + const templateName = emailData['headers']['x-template-name']; + const emailCode = emailData['headers']['x-verify-code']; + assert.equal(templateName, 'verifySecondaryEmail', 'email template name set'); + assert.ok(emailCode, 'emailCode set'); + return client.verifySecondaryEmail(emailCode, secondEmail); }) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - return server.mailbox.waitForEmail(email) - }) - }) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + return server.mailbox.waitForEmail(email); + }); + }); it( 'fails to login', () => { return Client.login(config.publicUrl, secondEmail, password, {}) .then(() => { - assert.fail(new Error('should not have been able to login')) + assert.fail(new Error('should not have been able to login')); }) .catch((err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 142, 'correct errno code') - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 142, 'correct errno code'); + }); } - ) - }) + ); + }); describe('should handle account creation', () => { - let secondEmail - let emailCode + let secondEmail; + let emailCode; beforeEach(() => { - secondEmail = server.uniqueEmail() + secondEmail = server.uniqueEmail(); return client.createEmail(secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return server.mailbox.waitForEmail(secondEmail) + assert.ok(res, 'ok response'); + return server.mailbox.waitForEmail(secondEmail); }) .then((emailData) => { - emailCode = emailData['headers']['x-verify-code'] - }) - }) + emailCode = emailData['headers']['x-verify-code']; + }); + }); it('fails to create account using verified secondary email', () => { return client.verifySecondaryEmail(emailCode, secondEmail) .then((res) => { - assert.ok(res, 'ok response') - return client.accountEmails() + assert.ok(res, 'ok response'); + return client.accountEmails(); }) .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, true, 'returns correct verified') - return server.mailbox.waitForEmail(email) + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, true, 'returns correct verified'); + return server.mailbox.waitForEmail(email); }) .then(() => { return Client.create(config.publicUrl, secondEmail, password, {}) .then(assert.fail) .catch((err) => { - assert.equal(err.errno, 144, 'return correct errno') - assert.equal(err.code, 400, 'return correct code') - }) - }) - }) + assert.equal(err.errno, 144, 'return correct errno'); + assert.equal(err.code, 400, 'return correct code'); + }); + }); + }); it('should create account with unverified secondary email and delete unverified secondary email', () => { - let client2 + let client2; return client.accountEmails() .then((res) => { - assert.equal(res.length, 2, 'returns number of emails') - assert.equal(res[1].email, secondEmail, 'returns correct email') - assert.equal(res[1].isPrimary, false, 'returns correct isPrimary') - assert.equal(res[1].verified, false, 'returns correct verified') + assert.equal(res.length, 2, 'returns number of emails'); + assert.equal(res[1].email, secondEmail, 'returns correct email'); + assert.equal(res[1].isPrimary, false, 'returns correct isPrimary'); + assert.equal(res[1].verified, false, 'returns correct verified'); return Client.createAndVerify(config.publicUrl, secondEmail, password, server.mailbox) - .catch(assert.fail) + .catch(assert.fail); }) .then((x) => { - client2 = x - assert.equal(client2.email, secondEmail, 'account created with secondary email address') - return client.accountEmails() + client2 = x; + assert.equal(client2.email, secondEmail, 'account created with secondary email address'); + return client.accountEmails(); }) .then((res) => { // Secondary email on first account should have been deleted - assert.equal(res.length, 1, 'returns number of emails') - assert.equal(res[0].email, client.email, 'returns correct email') - assert.equal(res[0].isPrimary, true, 'returns correct isPrimary') - assert.equal(res[0].verified, true, 'returns correct verified') - }) - }) - }) + assert.equal(res.length, 1, 'returns number of emails'); + assert.equal(res[0].email, client.email, 'returns correct email'); + assert.equal(res[0].isPrimary, true, 'returns correct isPrimary'); + assert.equal(res[0].verified, true, 'returns correct verified'); + }); + }); + }); after(() => { - return TestServer.stop(server) - }) + return TestServer.stop(server); + }); function resetPassword(client, code, newPassword, headers, options) { return client.verifyPasswordResetCode(code, headers, options) .then(function () { - return client.resetPassword(newPassword, {}, options) - }) + return client.resetPassword(newPassword, {}, options); + }); } -}) +}); diff --git a/test/remote/recovery_email_resend_code_tests.js b/test/remote/recovery_email_resend_code_tests.js index 8bfbaed6..d1cab1ed 100644 --- a/test/remote/recovery_email_resend_code_tests.js +++ b/test/remote/recovery_email_resend_code_tests.js @@ -2,201 +2,201 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const Client = require('../client')() -var TestServer = require('../test_server') -const P = require('../../lib/promise') +const { assert } = require('chai'); +const Client = require('../client')(); +var TestServer = require('../test_server'); +const P = require('../../lib/promise'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote recovery email resend code', function () { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { - config.securityHistory.ipProfiling.allowedRecency = 0 - config.signinConfirmation.skipForNewAccounts.enabled = false + config.securityHistory.ipProfiling.allowedRecency = 0; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it('sign-in verification resend email verify code', () => { - const email = server.uniqueEmail() - const password = 'something' - let verifyEmailCode = '' - let client = null + const email = server.uniqueEmail(); + const password = 'something'; + let verifyEmailCode = ''; + let client = null; const options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain, service: 'sync', resume: 'resumeToken', keys: true - } + }; return Client.create(config.publicUrl, email, password, server.mailbox, options) .then((c) => { - client = c + client = c; // Clear first account create email and login again return server.mailbox.waitForEmail(email) .then(() => Client.login(config.publicUrl, email, password, server.mailbox, options)) - .then((c) => client = c) + .then((c) => client = c); }) .then(() => server.mailbox.waitForCode(email)) .then((code) => { - verifyEmailCode = code - return client.requestVerifyEmail() + verifyEmailCode = code; + return client.requestVerifyEmail(); }) .then(() => server.mailbox.waitForCode(email)) .then((code) => { - assert.equal(code, verifyEmailCode, 'code equal to verify email code') - return client.verifyEmail(code) + assert.equal(code, verifyEmailCode, 'code equal to verify email code'); + return client.verifyEmail(code); }) .then(() => client.emailStatus()) .then((status) => { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') - }) - }) + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); + }); + }); it( 'sign-in verification resend login verify code', () => { - var email = server.uniqueEmail() - var password = 'something' - var verifyEmailCode = '' - var client2 = null + var email = server.uniqueEmail(); + var password = 'something'; + var verifyEmailCode = ''; + var client2 = null; var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain, service: 'sync', resume: 'resumeToken', keys: true - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, options) .then( function () { // Attempt to login from new location - return Client.login(config.publicUrl, email, password, server.mailbox, options) + return Client.login(config.publicUrl, email, password, server.mailbox, options); } ) .then( function (c) { - client2 = c + client2 = c; } ) .then( function () { - return client2.login(options) + return client2.login(options); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - verifyEmailCode = code - return client2.requestVerifyEmail() + verifyEmailCode = code; + return client2.requestVerifyEmail(); } ) .then( function () { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); } ) .then( function (code) { - assert.equal(code, verifyEmailCode, 'code equal to verify email code') - return client2.verifyEmail(code) + assert.equal(code, verifyEmailCode, 'code equal to verify email code'); + return client2.verifyEmail(code); } ) .then( function () { - return client2.emailStatus() + return client2.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'account email is verified') - assert.equal(status.sessionVerified, true, 'account session is verified') + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'account email is verified'); + assert.equal(status.sessionVerified, true, 'account session is verified'); } - ) + ); } - ) + ); it('fail when resending verification email when not owned by account', () => { - const email = server.uniqueEmail() - const secondEmail = server.uniqueEmail() - const password = 'something' - let client = null + const email = server.uniqueEmail(); + const secondEmail = server.uniqueEmail(); + const password = 'something'; + let client = null; const options = { keys: true - } + }; return P.all([ Client.createAndVerify(config.publicUrl, email, password, server.mailbox, options), Client.create(config.publicUrl, secondEmail, password, server.mailbox, options) ]) .then((res) => { // Login with `email` and attempt to resend verification code for `secondEmail` - client = res[0] + client = res[0]; client.options = { email: secondEmail - } + }; return client.requestVerifyEmail() .then(() => { - assert.fail('Should not have succeeded in sending verification code') - }) + assert.fail('Should not have succeeded in sending verification code'); + }); }) .catch((err) => { - assert.equal(err.code, 400) - assert.equal(err.errno, 150) - }) - }) + assert.equal(err.code, 400); + assert.equal(err.errno, 150); + }); + }); it('should be able to upgrade unverified session to verified session', () => { - const email = server.uniqueEmail() - const password = 'something' - let client = null + const email = server.uniqueEmail(); + const password = 'something'; + let client = null; const options = { keys: false - } + }; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, options) .then((c) => { - client = c + client = c; // Create an unverified session return client.login() .then((c) => { - client = c + client = c; // Clear the verify account email - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); }) - .then(() => client.sessionStatus()) + .then(() => client.sessionStatus()); }) .then((result) => { - assert.equal(result.state, 'unverified', 'session is unverified') + assert.equal(result.state, 'unverified', 'session is unverified'); // set the type of code to receive - client.options.type = 'upgradeSession' - return client.requestVerifyEmail() + client.options.type = 'upgradeSession'; + return client.requestVerifyEmail(); }) .then(() => server.mailbox.waitForEmail(email)) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyPrimaryEmail', 'correct template set') - const code = emailData.headers['x-verify-code'] - assert.ok(code, 'code set') - assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip address') - return client.verifyEmail(code) + assert.equal(emailData.headers['x-template-name'], 'verifyPrimaryEmail', 'correct template set'); + const code = emailData.headers['x-verify-code']; + assert.ok(code, 'code set'); + assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip address'); + return client.verifyEmail(code); }) .then(() => client.sessionStatus()) .then((result) => { - assert.equal(result.state, 'verified', 'session is verified') - }) - }) + assert.equal(result.state, 'verified', 'session is verified'); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/recovery_email_verify_tests.js b/test/remote/recovery_email_verify_tests.js index 91519645..23f1df61 100644 --- a/test/remote/recovery_email_verify_tests.js +++ b/test/remote/recovery_email_verify_tests.js @@ -2,108 +2,108 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var url = require('url') -const Client = require('../client')() -var TestServer = require('../test_server') +const { assert } = require('chai'); +var url = require('url'); +const Client = require('../client')(); +var TestServer = require('../test_server'); -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); describe('remote recovery email verify', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'create account verify with incorrect code', () => { - var email = server.uniqueEmail() - var password = 'allyourbasearebelongtous' - var client = null + var email = server.uniqueEmail(); + var password = 'allyourbasearebelongtous'; + var client = null; return Client.create(config.publicUrl, email, password) .then( function (x) { - client = x + client = x; } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'new account is not verified') + assert.equal(status.verified, false, 'new account is not verified'); } ) .then( function () { - return client.verifyEmail('00000000000000000000000000000000') + return client.verifyEmail('00000000000000000000000000000000'); } ) .then( function () { - assert(false, 'verified email with bad code') + assert(false, 'verified email with bad code'); }, function (err) { - assert.equal(err.message.toString(), 'Invalid verification code', 'bad attempt') + assert.equal(err.message.toString(), 'Invalid verification code', 'bad attempt'); } ) .then( function () { - return client.emailStatus() + return client.emailStatus(); } ) .then( function (status) { - assert.equal(status.verified, false, 'account not verified') + assert.equal(status.verified, false, 'account not verified'); } - ) + ); } - ) + ); it( 'verification email link', () => { - var email = server.uniqueEmail() - var password = 'something' - var client = null // eslint-disable-line no-unused-vars + var email = server.uniqueEmail(); + var password = 'something'; + var client = null; // eslint-disable-line no-unused-vars var options = { redirectTo: 'https://sync.' + config.smtp.redirectDomain + '/', service: 'sync' - } + }; return Client.create(config.publicUrl, email, password, options) .then( function (c) { - client = c + client = c; } ) .then( function () { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); } ) .then( function (emailData) { - var link = emailData.headers['x-link'] - var query = url.parse(link, true).query - assert.ok(query.uid, 'uid is in link') - assert.ok(query.code, 'code is in link') - assert.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link') - assert.equal(query.service, options.service, 'service is in link') + var link = emailData.headers['x-link']; + var query = url.parse(link, true).query; + assert.ok(query.uid, 'uid is in link'); + assert.ok(query.code, 'code is in link'); + assert.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link'); + assert.equal(query.service, options.service, 'service is in link'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/recovery_key_tests.js b/test/remote/recovery_key_tests.js index be0592ce..74a74396 100644 --- a/test/remote/recovery_key_tests.js +++ b/test/remote/recovery_key_tests.js @@ -2,90 +2,90 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const config = require('../../config').getProperties() -const crypto = require('crypto') -const TestServer = require('../test_server') -const Client = require('../client')() -const P = require('bluebird') +const { assert } = require('chai'); +const config = require('../../config').getProperties(); +const crypto = require('crypto'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const P = require('bluebird'); describe('remote recovery keys', function () { - this.timeout(10000) + this.timeout(10000); - let server, client, email - const password = '(-.-)Zzz...' + let server, client, email; + const password = '(-.-)Zzz...'; - let recoveryKeyId - let recoveryData - let keys + let recoveryKeyId; + let recoveryData; + let keys; function createMockRecoveryKey() { // The auth-server does not care about the encryption details of the recovery data. // To simplify things, we can mock out some random bits to be stored. Check out // /docs/recovery_keys.md for more details on the encryption that a client // could perform. - const recoveryCode = crypto.randomBytes(16).toString('hex') - const recoveryKeyId = crypto.randomBytes(16).toString('hex') - const recoveryKey = crypto.randomBytes(16).toString('hex') - const recoveryData = crypto.randomBytes(32).toString('hex') + const recoveryCode = crypto.randomBytes(16).toString('hex'); + const recoveryKeyId = crypto.randomBytes(16).toString('hex'); + const recoveryKey = crypto.randomBytes(16).toString('hex'); + const recoveryData = crypto.randomBytes(32).toString('hex'); return P.resolve({ recoveryCode, recoveryData, recoveryKeyId, recoveryKey - }) + }); } before(() => { return TestServer.start(config) - .then(s => server = s) - }) + .then(s => server = s); + }); beforeEach(() => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((x) => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); - return client.keys() + return client.keys(); }) .then((result) => { - keys = result + keys = result; return createMockRecoveryKey(client.uid, keys.kB) .then((result) => { - recoveryKeyId = result.recoveryKeyId - recoveryData = result.recoveryData + recoveryKeyId = result.recoveryKeyId; + recoveryData = result.recoveryData; // Should create recovery key return client.createRecoveryKey(result.recoveryKeyId, result.recoveryData) .then((res) => assert.ok(res, 'empty response')) .then(() => server.mailbox.waitForEmail(email)) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postAddAccountRecoveryEmail', 'correct template sent') - }) - }) - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postAddAccountRecoveryEmail', 'correct template sent'); + }); + }); + }); + }); it('should get recovery key', () => { return getAccountResetToken(client, server, email) .then(() => client.getRecoveryKey(recoveryKeyId)) .then((res) => { - assert.equal(res.recoveryData, recoveryData, 'recoveryData returned') - }) - }) + assert.equal(res.recoveryData, recoveryData, 'recoveryData returned'); + }); + }); it('should fail to get unknown recovery key', () => { return getAccountResetToken(client, server, email) .then(() => client.getRecoveryKey('abce1234567890')) .then(assert.fail, (err) => { - assert.equal(err.errno, 159, 'recovery key is not valid') - }) - }) + assert.equal(err.errno, 159, 'recovery key is not valid'); + }); + }); it('should fail if recoveryKeyId is missing', () => { return getAccountResetToken(client, server, email) @@ -93,9 +93,9 @@ describe('remote recovery keys', function () { .then((res) => assert.equal(res.recoveryData, recoveryData, 'recoveryData returned')) .then(() => client.resetAccountWithRecoveryKey('newpass', keys.kB, undefined, {}, {keys: true})) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'invalid param') - }) - }) + assert.equal(err.errno, 107, 'invalid param'); + }); + }); it('should fail if wrapKb is missing', () => { return getAccountResetToken(client, server, email) @@ -103,9 +103,9 @@ describe('remote recovery keys', function () { .then((res) => assert.equal(res.recoveryData, recoveryData, 'recoveryData returned')) .then(() => client.resetAccountWithRecoveryKey('newpass', keys.kB, recoveryKeyId, {}, {keys: true, undefinedWrapKb: true})) .then(assert.fail, (err) => { - assert.equal(err.errno, 107, 'invalid param') - }) - }) + assert.equal(err.errno, 107, 'invalid param'); + }); + }); it('should change password and keep kB', () => { return getAccountResetToken(client, server, email) @@ -113,110 +113,110 @@ describe('remote recovery keys', function () { .then((res) => assert.equal(res.recoveryData, recoveryData, 'recoveryData returned')) .then(() => client.resetAccountWithRecoveryKey('newpass', keys.kB, recoveryKeyId, {}, {keys: true})) .then((res) => { - assert.equal(res.uid, client.uid, 'uid returned') - assert.ok(res.sessionToken, 'sessionToken return') - return server.mailbox.waitForEmail(email) + assert.equal(res.uid, client.uid, 'uid returned'); + assert.ok(res.sessionToken, 'sessionToken return'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'passwordResetAccountRecoveryEmail', 'correct template sent') - return client.keys() + assert.equal(emailData.headers['x-template-name'], 'passwordResetAccountRecoveryEmail', 'correct template sent'); + return client.keys(); }) .then((res) => { - assert.equal(res.kA, keys.kA, 'kA are equal returned') - assert.equal(res.kB, keys.kB, 'kB are equal returned') + assert.equal(res.kA, keys.kA, 'kA are equal returned'); + assert.equal(res.kB, keys.kB, 'kB are equal returned'); // Login with new password and check to see kB hasn't changed return Client.login(config.publicUrl, email, 'newpass', {keys: true}) .then((c) => { - assert.ok(c.sessionToken, 'sessionToken returned') - return c.keys() + assert.ok(c.sessionToken, 'sessionToken returned'); + return c.keys(); }) .then((res) => { - assert.equal(res.kA, keys.kA, 'kA are equal returned') - assert.equal(res.kB, keys.kB, 'kB are equal returned') - }) - }) - }) + assert.equal(res.kA, keys.kA, 'kA are equal returned'); + assert.equal(res.kB, keys.kB, 'kB are equal returned'); + }); + }); + }); it('should delete recovery key', () => { return client.deleteRecoveryKey() .then((res) => { - assert.ok(res, 'empty response') + assert.ok(res, 'empty response'); return client.getRecoveryKeyExists() .then((result) => { - assert.equal(result.exists, false, 'recovery key deleted') + assert.equal(result.exists, false, 'recovery key deleted'); }) .then(() => server.mailbox.waitForEmail(email)) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postRemoveAccountRecoveryEmail', 'correct template sent') - }) - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postRemoveAccountRecoveryEmail', 'correct template sent'); + }); + }); + }); it('should fail to create recovery key when one already exists', () => { return createMockRecoveryKey(client.uid, keys.kB) .then((result) => { - recoveryKeyId = result.recoveryKeyId - recoveryData = result.recoveryData + recoveryKeyId = result.recoveryKeyId; + recoveryData = result.recoveryData; return client.createRecoveryKey(result.recoveryKeyId, result.recoveryData) .then(assert.fail, (err) => { - assert.equal(err.errno, 161, 'correct errno') + assert.equal(err.errno, 161, 'correct errno'); }); - }) - }) + }); + }); describe('check recovery key status', () => { describe('with sessionToken', () => { it('should return true if recovery key exists', () => { return client.getRecoveryKeyExists() .then((res) => { - assert.equal(res.exists, true, 'recovery key exists') - }) - }) + assert.equal(res.exists, true, 'recovery key exists'); + }); + }); it('should return false if recovery key doesn\'t exist', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((c) => { - client = c - return client.getRecoveryKeyExists() + client = c; + return client.getRecoveryKeyExists(); }) .then((res) => { - assert.equal(res.exists, false, 'recovery key doesnt exists') - }) - }) - }) + assert.equal(res.exists, false, 'recovery key doesnt exists'); + }); + }); + }); describe('with email', () => { it('should return true if recovery key exists', () => { return client.getRecoveryKeyExists(email) .then((res) => { - assert.equal(res.exists, true, 'recovery key exists') - }) - }) + assert.equal(res.exists, true, 'recovery key exists'); + }); + }); it('should return false if recovery key doesn\'t exist', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys: true}) .then((c) => { - client = c - return client.getRecoveryKeyExists(email) + client = c; + return client.getRecoveryKeyExists(email); }) .then((res) => { - assert.equal(res.exists, false, 'recovery key doesn\'t exist') - }) - }) - }) + assert.equal(res.exists, false, 'recovery key doesn\'t exist'); + }); + }); + }); - }) + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); function getAccountResetToken(client, server, email) { return client.forgotPassword() .then(() => server.mailbox.waitForCode(email)) - .then((code) => client.verifyPasswordResetCode(code, {}, {accountResetWithRecoveryKey: true})) + .then((code) => client.verifyPasswordResetCode(code, {}, {accountResetWithRecoveryKey: true})); } diff --git a/test/remote/redis_tests.js b/test/remote/redis_tests.js index f28d01a6..4ad312f7 100644 --- a/test/remote/redis_tests.js +++ b/test/remote/redis_tests.js @@ -2,230 +2,230 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const config = require(`${ROOT_DIR}/config`).getProperties() -const P = require(`${ROOT_DIR}/lib/promise`) +const { assert } = require('chai'); +const config = require(`${ROOT_DIR}/config`).getProperties(); +const P = require(`${ROOT_DIR}/lib/promise`); -const log = { info () {}, warn () {}, error () {} } +const log = { info () {}, warn () {}, error () {} }; const redis = require(`${ROOT_DIR}/lib/redis`)({ enabled: true, ...config.redis, ...config.redis.sessionTokens -}, log) +}, log); describe('redis.set:', () => { before(() => { - return redis.set('foo', 'bar') - }) + return redis.set('foo', 'bar'); + }); it('redis.get reads data', () => { return P.all([ redis.get('foo'), redis.get('foo') ]) - .then(results => results.forEach(result => assert.equal(result, 'bar'))) - }) -}) + .then(results => results.forEach(result => assert.equal(result, 'bar'))); + }); +}); describe('concurrent sets:', () => { before(() => { - return P.all([ redis.set('foo', '1'), redis.set('foo', '2') ]) - }) + return P.all([ redis.set('foo', '1'), redis.set('foo', '2') ]); + }); it('data was set', () => { return redis.get('foo') - .then(result => assert.ok(result === '1' || result === '2')) - }) -}) + .then(result => assert.ok(result === '1' || result === '2')); + }); +}); describe('redis.del:', () => { before(() => { - return redis.del('foo') - }) + return redis.del('foo'); + }); it('data was deleted', () => { return redis.get('foo') - .then(result => assert.ok(result === null)) - }) -}) + .then(result => assert.ok(result === null)); + }); +}); describe('redis.update:', () => { before(() => { return redis.set('foo', 'bar') - .then(() => redis.update('foo', oldValue => `${oldValue}2`)) - }) + .then(() => redis.update('foo', oldValue => `${oldValue}2`)); + }); it('data was set', () => { return redis.get('foo') - .then(result => assert.equal(result, 'bar2')) - }) -}) + .then(result => assert.equal(result, 'bar2')); + }); +}); describe('update non-existent key:', () => { before(() => { return redis.del('wibble') - .then(() => redis.update('wibble', () => 'blee')) - }) + .then(() => redis.update('wibble', () => 'blee')); + }); it('data was set', () => { return redis.get('wibble') - .then(result => assert.equal(result, 'blee')) - }) -}) + .then(result => assert.equal(result, 'blee')); + }); +}); describe('update existing key to falsey value:', () => { before(() => { - return redis.update('wibble', () => '') - }) + return redis.update('wibble', () => ''); + }); it('data was deleted', () => { return redis.get('wibble') - .then(result => assert.ok(result === null)) - }) -}) + .then(result => assert.ok(result === null)); + }); +}); describe('concurrent updates of the same key:', () => { - const errors = [] - let winner + const errors = []; + let winner; before(() => { - let resolve, sum = 0 - const synchronisationPromise = new P(r => resolve = r) + let resolve, sum = 0; + const synchronisationPromise = new P(r => resolve = r); return P.all([ 1, 2 ].map(value => { return redis.update('foo', createUpdateHandler(value)) .then(() => winner = value) - .catch(error => errors.push(error)) - })) + .catch(error => errors.push(error)); + })); function createUpdateHandler (value) { return () => { - sum += value + sum += value; if (sum === 3) { - resolve() + resolve(); } return synchronisationPromise - .then(() => value) - } + .then(() => value); + }; } - }) + }); it('one update failed', () => { - assert.equal(errors.length, 1) - assert.equal(errors[0].message, 'Redis WATCH detected a conflicting update') - assert.equal(errors[0].errno, 165) - }) + assert.equal(errors.length, 1); + assert.equal(errors[0].message, 'Redis WATCH detected a conflicting update'); + assert.equal(errors[0].errno, 165); + }); it('the other update completed successfully', () => { return redis.get('foo') - .then(result => assert.equal(result, winner)) - }) -}) + .then(result => assert.equal(result, winner)); + }); +}); describe('concurrent updates of different keys:', () => { before(() => { - let resolve, values = '' - const synchronisationPromise = new P(r => resolve = r) + let resolve, values = ''; + const synchronisationPromise = new P(r => resolve = r); return P.all([ redis.update('foo', createUpdateHandler('bar')), redis.update('baz', createUpdateHandler('qux')) - ]) + ]); function createUpdateHandler (value) { return () => { - values += value + values += value; if (values.length === 6) { - resolve() + resolve(); } return synchronisationPromise - .then(() => value) - } + .then(() => value); + }; } - }) + }); it('first update completed successfully', () => { return redis.get('foo') - .then(result => assert.equal(result, 'bar')) - }) + .then(result => assert.equal(result, 'bar')); + }); it('second update completed successfully', () => { return redis.get('baz') - .then(result => assert.equal(result, 'qux')) - }) -}) + .then(result => assert.equal(result, 'qux')); + }); +}); describe('reentrant updates of different keys:', () => { - let error + let error; before(() => { const redisPool = require('fxa-shared/redis/pool')({ ...config.redis, ...config.redis.sessionTokens - }, log) - const redisConnection = redisPool.acquire() + }, log); + const redisConnection = redisPool.acquire(); return P.using( redisConnection, connection => connection.update('foo', oldFoo => { return connection.update('baz', oldBaz => `${oldBaz}2`) .catch(e => error = e) - .then(() => `${oldFoo}2`) + .then(() => `${oldFoo}2`); }) - ) - }) + ); + }); it('first update completed successfully', () => { return redis.get('foo') - .then(result => assert.equal(result, 'bar2')) - }) + .then(result => assert.equal(result, 'bar2')); + }); it('second update failed', () => { - assert.instanceOf(error, Error) - assert.equal(error.message, 'redis.update.conflict') + assert.instanceOf(error, Error); + assert.equal(error.message, 'redis.update.conflict'); return redis.get('baz') - .then(result => assert.equal(result, 'qux')) - }) -}) + .then(result => assert.equal(result, 'qux')); + }); +}); describe('set concurrently with update:', () => { - let error + let error; before(() => { return redis.update('foo', () => redis.set('foo', 'blee').then(() => 'wibble')) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('update failed', () => { - assert.ok(error) - assert.equal(error.message, 'Redis WATCH detected a conflicting update') - assert.equal(error.errno, 165) - }) + assert.ok(error); + assert.equal(error.message, 'Redis WATCH detected a conflicting update'); + assert.equal(error.errno, 165); + }); it('data was set', () => { return redis.get('foo') - .then(result => assert.equal(result, 'blee')) - }) -}) + .then(result => assert.equal(result, 'blee')); + }); +}); describe('del concurrently with update:', () => { - let error + let error; before(() => { return redis.set('foo', 'bar') .then(() => redis.update('foo', () => redis.del('foo').then(() => 'baz'))) - .catch(e => error = e) - }) + .catch(e => error = e); + }); it('update failed', () => { - assert.ok(error) - assert.equal(error.message, 'Redis WATCH detected a conflicting update') - assert.equal(error.errno, 165) - }) + assert.ok(error); + assert.equal(error.message, 'Redis WATCH detected a conflicting update'); + assert.equal(error.errno, 165); + }); it('data was deleted', () => { return redis.get('foo') - .then(result => assert.ok(result === null)) - }) -}) + .then(result => assert.ok(result === null)); + }); +}); diff --git a/test/remote/session_tests.js b/test/remote/session_tests.js index 269cadd1..67928927 100644 --- a/test/remote/session_tests.js +++ b/test/remote/session_tests.js @@ -2,434 +2,434 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const P = require('../../lib/promise') -const jwtool = require('fxa-jwtool') -const config = require('../../config').getProperties() -const pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile) -const duration = 1000 * 60 * 60 * 24 +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const P = require('../../lib/promise'); +const jwtool = require('fxa-jwtool'); +const config = require('../../config').getProperties(); +const pubSigKey = jwtool.JWK.fromFile(config.publicKeyFile); +const duration = 1000 * 60 * 60 * 24; const publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' -} +}; describe('remote session', function() { - this.timeout(15000) - let server - config.signinConfirmation.skipForNewAccounts.enabled = false + this.timeout(15000); + let server; + config.signinConfirmation.skipForNewAccounts.enabled = false; before(() => { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); describe('destroy', () => { it( 'deletes a valid session', () => { - var email = server.uniqueEmail() - var password = 'foobar' - var client = null - var sessionToken = null + var email = server.uniqueEmail(); + var password = 'foobar'; + var client = null; + var sessionToken = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - return client.sessionStatus() + client = x; + return client.sessionStatus(); } ) .then( function () { - sessionToken = client.sessionToken - return client.destroySession() + sessionToken = client.sessionToken; + return client.destroySession(); } ) .then( function () { - assert.equal(client.sessionToken, null, 'session token deleted') - client.sessionToken = sessionToken - return client.sessionStatus() + assert.equal(client.sessionToken, null, 'session token deleted'); + client.sessionToken = sessionToken; + return client.sessionStatus(); } ) .then( function (status) { - assert(false, 'got status with destroyed session') + assert(false, 'got status with destroyed session'); }, function (err) { - assert.equal(err.errno, 110, 'session is invalid') + assert.equal(err.errno, 110, 'session is invalid'); } - ) + ); } - ) + ); it( 'deletes a different custom token', () => { - var email = server.uniqueEmail() - var password = 'foobar' - var client = null - var tokenId = null - var sessionTokenCreate = null - var sessionTokenLogin = null + var email = server.uniqueEmail(); + var password = 'foobar'; + var client = null; + var tokenId = null; + var sessionTokenCreate = null; + var sessionTokenLogin = null; return Client.create(config.publicUrl, email, password) .then((x) => { - client = x - sessionTokenCreate = client.sessionToken - return client.api.sessions(sessionTokenCreate) + client = x; + sessionTokenCreate = client.sessionToken; + return client.api.sessions(sessionTokenCreate); }) .then((sessions) => { - tokenId = sessions[0].id - return client.login() + tokenId = sessions[0].id; + return client.login(); }) .then((c) => { - sessionTokenLogin = c.sessionToken - return client.api.sessionStatus(sessionTokenCreate) + sessionTokenLogin = c.sessionToken; + return client.api.sessionStatus(sessionTokenCreate); }) .then((status) => { - assert.ok(status.uid, 'got valid session') + assert.ok(status.uid, 'got valid session'); return client.api.sessionDestroy(sessionTokenLogin, { customSessionToken: tokenId - }) + }); }) .then(() => { - return client.api.sessionStatus(sessionTokenCreate) + return client.api.sessionStatus(sessionTokenCreate); }) .then((status) => { - assert(false, 'got status with destroyed session') + assert(false, 'got status with destroyed session'); },(err) => { - assert.equal(err.code, 401) - assert.equal(err.errno, 110, 'session is invalid') + assert.equal(err.code, 401); + assert.equal(err.errno, 110, 'session is invalid'); - }) + }); } - ) + ); it( 'fails with a bad custom token', () => { - var email = server.uniqueEmail() - var password = 'foobar' - var client = null - var sessionTokenCreate = null - var sessionTokenLogin = null + var email = server.uniqueEmail(); + var password = 'foobar'; + var client = null; + var sessionTokenCreate = null; + var sessionTokenLogin = null; return Client.create(config.publicUrl, email, password) .then((x) => { - client = x - sessionTokenCreate = client.sessionToken - return client.login() + client = x; + sessionTokenCreate = client.sessionToken; + return client.login(); }) .then((c) => { - sessionTokenLogin = c.sessionToken - return client.api.sessionStatus(sessionTokenCreate) + sessionTokenLogin = c.sessionToken; + return client.api.sessionStatus(sessionTokenCreate); }) .then(() => { return client.api.sessionDestroy(sessionTokenLogin, { customSessionToken: 'eff779f59ab974f800625264145306ce53185bb22ee01fe80280964ff2766504' - }) + }); }) .then(() => { - return client.api.sessionStatus(sessionTokenCreate) + return client.api.sessionStatus(sessionTokenCreate); }) .then( function (status) { - assert(false, 'got status with destroyed session') + assert(false, 'got status with destroyed session'); }, function (err) { - assert.equal(err.code, 401) - assert.equal(err.errno, 110, 'session is invalid') - assert.equal(err.error, 'Unauthorized') - assert.equal(err.message, 'The authentication token could not be found') + assert.equal(err.code, 401); + assert.equal(err.errno, 110, 'session is invalid'); + assert.equal(err.error, 'Unauthorized'); + assert.equal(err.message, 'The authentication token could not be found'); } - ) + ); } - ) + ); - }) + }); describe('duplicate', () => { it( 'duplicates a valid session into a new, independent session', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client1, client2 + const email = server.uniqueEmail(); + const password = 'foobar'; + let client1, client2; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((x) => { - client1 = x - return client1.duplicate() + client1 = x; + return client1.duplicate(); }).then((x) => { - client2 = x - assert.notEqual(client1.sessionToken, client2.sessionToken, 'generated a new sessionToken') - return client1.api.sessionDestroy(client1.sessionToken) + client2 = x; + assert.notEqual(client1.sessionToken, client2.sessionToken, 'generated a new sessionToken'); + return client1.api.sessionDestroy(client1.sessionToken); }).then(() => { - return client1.sessionStatus() + return client1.sessionStatus(); }).then( - () => { assert.fail('client1 session should have been destroyed') }, + () => { assert.fail('client1 session should have been destroyed'); }, (err) => { - assert.equal(err.code, 401) - assert.equal(err.errno, 110) + assert.equal(err.code, 401); + assert.equal(err.errno, 110); } ).then(() => { - return client2.sessionStatus() + return client2.sessionStatus(); }).then((status) => { - assert.ok(status, 'client2 session is still alive') - return client2.api.sessionDestroy(client2.sessionToken) + assert.ok(status, 'client2 session is still alive'); + return client2.api.sessionDestroy(client2.sessionToken); }).then(() => { - return client2.sessionStatus() + return client2.sessionStatus(); }).then( - () => { assert.fail('client2 session should have been destroyed') }, + () => { assert.fail('client2 session should have been destroyed'); }, (err) => { - assert.equal(err.code, 401) - assert.equal(err.errno, 110) + assert.equal(err.code, 401); + assert.equal(err.errno, 110); } - ) + ); } - ) + ); it( 'creates independent verification state for the new token', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client1, client2, client3 + const email = server.uniqueEmail(); + const password = 'foobar'; + let client1, client2, client3; return Client.create(config.publicUrl, email, password, server.mailbox) .then((x) => { - client1 = x - return client1.duplicate() + client1 = x; + return client1.duplicate(); }).then((x) => { - client2 = x - assert.ok(! client1.verified, 'client1 session is not verified') - assert.ok(! client2.verified, 'client2 session is not verified') - return server.mailbox.waitForCode(email) + client2 = x; + assert.ok(! client1.verified, 'client1 session is not verified'); + assert.ok(! client2.verified, 'client2 session is not verified'); + return server.mailbox.waitForCode(email); }).then((code) => { - return client1.verifyEmail(code) + return client1.verifyEmail(code); }).then(() => { - return client1.sessionStatus() + return client1.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'verified', 'client1 session has become verified') - return client2.sessionStatus() + assert.equal(status.state, 'verified', 'client1 session has become verified'); + return client2.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'unverified', 'client2 session has remained unverified') - return client2.duplicate() + assert.equal(status.state, 'unverified', 'client2 session has remained unverified'); + return client2.duplicate(); }).then((x) => { - client3 = x - return client2.requestVerifyEmail() + client3 = x; + return client2.requestVerifyEmail(); }).then(() => { - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); }).then((code) => { - return client2.verifyEmail(code) + return client2.verifyEmail(code); }).then(() => { - return client2.sessionStatus() + return client2.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'verified', 'client2 session has become verified') - return client3.sessionStatus() + assert.equal(status.state, 'verified', 'client2 session has become verified'); + return client3.sessionStatus(); }).then((status) => { - assert.ok(status.state, 'unverified', 'client3 session has remained unverified') - }) + assert.ok(status.state, 'unverified', 'client3 session has remained unverified'); + }); } - ) - }) + ); + }); describe('reauth', () => { it( 'allocates a new keyFetchToken', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client, kA, kB + const email = server.uniqueEmail(); + const password = 'foobar'; + let client, kA, kB; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, { keys: true }) .then(x => { - client = x - return client.keys() + client = x; + return client.keys(); }).then(keys => { - kA = keys.kA - kB = keys.kB - assert.equal(client.keyFetchToken, null, 'keyFetchToken was consumed') - return client.reauth({ keys: true }) + kA = keys.kA; + kB = keys.kB; + assert.equal(client.keyFetchToken, null, 'keyFetchToken was consumed'); + return client.reauth({ keys: true }); }).then(() => { - assert.ok(client.keyFetchToken, 'got a new keyFetchToken') - return client.keys() + assert.ok(client.keyFetchToken, 'got a new keyFetchToken'); + return client.keys(); }).then(keys => { - assert.equal(keys.kA, kA, 'kA was fetched successfully') - assert.equal(keys.kB, kB, 'kB was fetched successfully') - assert.equal(client.keyFetchToken, null, 'keyFetchToken was consumed') - }) + assert.equal(keys.kA, kA, 'kA was fetched successfully'); + assert.equal(keys.kB, kB, 'kB was fetched successfully'); + assert.equal(client.keyFetchToken, null, 'keyFetchToken was consumed'); + }); } - ) + ); it( 'updates the last-auth time', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client, lastAuth1, lastAuth2 + const email = server.uniqueEmail(); + const password = 'foobar'; + let client, lastAuth1, lastAuth2; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then(x => { - client = x + client = x; }).then(() => { - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); }).then(cert => { - const payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - lastAuth1 = payload['fxa-lastAuthAt'] - return P.delay(1000) + const payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + lastAuth1 = payload['fxa-lastAuthAt']; + return P.delay(1000); }).then(() => { - return client.reauth() + return client.reauth(); }).then(() => { - return client.sign(publicKey, duration) + return client.sign(publicKey, duration); }).then(cert => { - const payload = jwtool.verify(cert, pubSigKey.pem) - assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid') - lastAuth2 = payload['fxa-lastAuthAt'] - assert.ok(lastAuth1 < lastAuth2, 'last-auth timestamp increased') - }) + const payload = jwtool.verify(cert, pubSigKey.pem); + assert.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid'); + lastAuth2 = payload['fxa-lastAuthAt']; + assert.ok(lastAuth1 < lastAuth2, 'last-auth timestamp increased'); + }); } - ) + ); it( 'rejects incorrect passwords', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client + const email = server.uniqueEmail(); + const password = 'foobar'; + let client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then(x => { - client = x + client = x; }).then(() => { - return client.setupCredentials(email, 'fiibar') + return client.setupCredentials(email, 'fiibar'); }).then(() => { - return client.reauth() + return client.reauth(); }).then( - () => { assert.fail('password should have been rejected') }, + () => { assert.fail('password should have been rejected'); }, (err) => { - assert.equal(err.code, 400) - assert.equal(err.errno, 103) + assert.equal(err.code, 400); + assert.equal(err.errno, 103); } - ) + ); } - ) + ); it( 'has sane account-verification behaviour', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client + const email = server.uniqueEmail(); + const password = 'foobar'; + let client; return Client.create(config.publicUrl, email, password, server.mailbox) .then(x => { - client = x - assert.ok(! client.verified, 'account is not verified') + client = x; + assert.ok(! client.verified, 'account is not verified'); // Clear the verification email, without verifying. - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); }).then(() => { - return client.reauth() + return client.reauth(); }).then(() => { - return client.sessionStatus() + return client.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'unverified', 'client session is still unverified') + assert.equal(status.state, 'unverified', 'client session is still unverified'); }).then(() => { // The reauth should have triggerd a second email. - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); }).then((code) => { - return client.verifyEmail(code) + return client.verifyEmail(code); }).then(() => { - return client.sessionStatus() + return client.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'verified', 'client session has become verified') - }) + assert.equal(status.state, 'verified', 'client session has become verified'); + }); } - ) + ); it( 'has sane session-verification behaviour', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client + const email = server.uniqueEmail(); + const password = 'foobar'; + let client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, { keys: false }) .then(() => { - return Client.login(config.publicUrl, email, password, { keys: false }) + return Client.login(config.publicUrl, email, password, { keys: false }); }).then(x => { - client = x - return client.sessionStatus() + client = x; + return client.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'unverified', 'client session reports unverified') - return client.emailStatus() + assert.equal(status.state, 'unverified', 'client session reports unverified'); + return client.emailStatus(); }).then((status) => { - assert.equal(status.verified, true, 'email status reports verified, because mustVerify=false') - return client.reauth({ keys: true }) + assert.equal(status.verified, true, 'email status reports verified, because mustVerify=false'); + return client.reauth({ keys: true }); }).then(() => { - return client.sessionStatus() + return client.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'unverified', 'client session still reports unverified') - return client.emailStatus() + assert.equal(status.state, 'unverified', 'client session still reports unverified'); + return client.emailStatus(); }).then((status) => { - assert.equal(status.verified, false, 'email status now reports unverified, because mustVerify=true') + assert.equal(status.verified, false, 'email status now reports unverified, because mustVerify=true'); // The reauth should have triggerd a verification email. - return server.mailbox.waitForCode(email) + return server.mailbox.waitForCode(email); }).then((code) => { - return client.verifyEmail(code) + return client.verifyEmail(code); }).then(() => { - return client.sessionStatus() + return client.sessionStatus(); }).then((status) => { - assert.equal(status.state, 'verified', 'client session has become verified') - return client.emailStatus() + assert.equal(status.state, 'verified', 'client session has become verified'); + return client.emailStatus(); }).then((status) => { - assert.equal(status.verified, true, 'email status is now verified, because session is verified') - }) + assert.equal(status.verified, true, 'email status is now verified, because session is verified'); + }); } - ) + ); it( 'does not send notification emails on verified sessions', () => { - const email = server.uniqueEmail() - const password = 'foobar' - let client + const email = server.uniqueEmail(); + const password = 'foobar'; + let client; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, { keys: true }) .then(x => { - client = x - return client.reauth({ keys: true }) + client = x; + return client.reauth({ keys: true }); }).then(() => { // Send some other type of email, and assert that it's the one we get back. // If the above sent a "new login" notification, we would get that instead. - return client.forgotPassword() + return client.forgotPassword(); }).then(() => { - return server.mailbox.waitForEmail(email) + return server.mailbox.waitForEmail(email); }).then(msg => { - assert.ok(msg.headers['x-recovery-code'], 'the next email was the password-reset email') - }) + assert.ok(msg.headers['x-recovery-code'], 'the next email was the password-reset email'); + }); } - ) - }) + ); + }); describe('status', () => { it( 'succeeds with valid token', () => { - var email = server.uniqueEmail() - var password = 'testx' - var uid = null + var email = server.uniqueEmail(); + var password = 'testx'; + var uid = null; return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (c) { - uid = c.uid + uid = c.uid; return c.login() .then( function () { - return c.api.sessionStatus(c.sessionToken) + return c.api.sessionStatus(c.sessionToken); } - ) + ); } ) .then( @@ -437,29 +437,29 @@ describe('remote session', function() { assert.deepEqual(x, { state: 'unverified', uid: uid - }) + }); } - ) + ); } - ) + ); it( 'errors with invalid token', () => { - var client = new Client(config.publicUrl) + var client = new Client(config.publicUrl); return client.api.sessionStatus('0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF') .then( () => assert(false), function (err) { - assert.equal(err.errno, 110, 'invalid token') + assert.equal(err.errno, 110, 'invalid token'); } - ) + ); } - ) + ); - }) + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/sign_key_tests.js b/test/remote/sign_key_tests.js index f5d21b06..d6a8d4d8 100644 --- a/test/remote/sign_key_tests.js +++ b/test/remote/sign_key_tests.js @@ -2,40 +2,40 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const P = require('../../lib/promise') -const TestServer = require('../test_server') -const request = P.promisify(require('request'), { multiArgs: true }) -const path = require('path') +const { assert } = require('chai'); +const P = require('../../lib/promise'); +const TestServer = require('../test_server'); +const request = P.promisify(require('request'), { multiArgs: true }); +const path = require('path'); describe('remote sign key', function() { - this.timeout(15000) - let server + this.timeout(15000); + let server; before(() => { - var config = require('../../config').getProperties() - config.oldPublicKeyFile = path.resolve(__dirname, '../../config/public-key.json') + var config = require('../../config').getProperties(); + config.oldPublicKeyFile = path.resolve(__dirname, '../../config/public-key.json'); return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( '.well-known/browserid has keys', () => { return request('http://127.0.0.1:9000/.well-known/browserid') .spread((res, body) => { - assert.equal(res.statusCode, 200) - var json = JSON.parse(body) - assert.equal(json.authentication, '/.well-known/browserid/nonexistent.html') - assert.equal(json.keys.length, 2) - }) + assert.equal(res.statusCode, 200); + var json = JSON.parse(body); + assert.equal(json.authentication, '/.well-known/browserid/nonexistent.html'); + assert.equal(json.keys.length, 2); + }); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/signin_code_tests.js b/test/remote/signin_code_tests.js index 3389a208..0913a43b 100644 --- a/test/remote/signin_code_tests.js +++ b/test/remote/signin_code_tests.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const error = require('../../lib/error') -const config = require('../../config').getProperties() -const crypto = require('crypto') +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const error = require('../../lib/error'); +const config = require('../../config').getProperties(); +const crypto = require('crypto'); -const SMS_SIGNIN_CODE = /https:\/\/accounts\.firefox\.com\/m\/([A-Za-z0-9_-]+)/ +const SMS_SIGNIN_CODE = /https:\/\/accounts\.firefox\.com\/m\/([A-Za-z0-9_-]+)/; describe('remote signinCodes', function () { - let server + let server; - this.timeout(10000) + this.timeout(10000); before(() => { // We have to send an SMS to get a valid signinCode - config.sms.enabled = true - config.sms.useMock = true + config.sms.enabled = true; + config.sms.useMock = true; return TestServer.start(config) .then(result => { - server = result - }) - }) + server = result; + }); + }); it('POST /signinCodes/consume invalid code', () => { - const client = new Client(config.publicUrl) + const client = new Client(config.publicUrl); return client.consumeSigninCode( crypto.randomBytes(config.signinCodeSize) .toString('base64') @@ -46,15 +46,15 @@ describe('remote signinCodes', function () { ) .then(result => assert.fail('/signinCodes/consume should fail')) .catch(err => { - assert.ok(err) - assert.equal(err.code, 400) - assert.equal(err.errno, error.ERRNO.INVALID_SIGNIN_CODE) - assert.equal(err.message, 'Invalid signin code') - }) - }) + assert.ok(err); + assert.equal(err.code, 400); + assert.equal(err.errno, error.ERRNO.INVALID_SIGNIN_CODE); + assert.equal(err.message, 'Invalid signin code'); + }); + }); it('POST /signinCodes/consume valid code', () => { - const email = server.uniqueEmail() + const email = server.uniqueEmail(); return Client.create(config.publicUrl, email, 'wibble') .then(client => { return client.smsSend('+18885083401', 1, [ 'signinCodes' ], server.mailbox) @@ -64,14 +64,14 @@ describe('remote signinCodes', function () { flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', flowBeginTime: Date.now() } - }) + }); }) - .then(result => assert.deepEqual(result, { email }, '/signinCodes/consume should return the email address')) - }) - }) + .then(result => assert.deepEqual(result, { email }, '/signinCodes/consume should return the email address')); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/sms_tests.js b/test/remote/sms_tests.js index 69a6a6ad..e09be691 100644 --- a/test/remote/sms_tests.js +++ b/test/remote/sms_tests.js @@ -2,37 +2,37 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const TestServer = require('../test_server') -const Client = require('../client')() -const config = require('../../config').getProperties() -const error = require('../../lib/error') +const { assert } = require('chai'); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const config = require('../../config').getProperties(); +const error = require('../../lib/error'); describe('remote sms without the signinCodes feature included in the payload', function() { - this.timeout(10000) - let server + this.timeout(10000); + let server; before(() => { - config.sms.enabled = true - config.sms.isStatusGeoEnabled = true + config.sms.enabled = true; + config.sms.isStatusGeoEnabled = true; // /sms endpoints need creds and spend money unless the SMS provider is mocked - config.sms.useMock = true + config.sms.useMock = true; return TestServer.start(config) .then(result => { - server = result - }) - }) + server = result; + }); + }); it('POST /sms success', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') .then(client => { return client.smsSend('+18885083401', 1) - .then(result => assert.ok(result)) - }) - }) + .then(result => assert.ok(result)); + }); + }); it('POST /sms with invalid phone number', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') @@ -40,13 +40,13 @@ describe('remote sms without the signinCodes feature included in the payload', f return client.smsSend('+15551234567', 1) .then(() => assert.fail('request should have failed')) .catch(err => { - assert.ok(err) - assert.equal(err.code, 400) - assert.equal(err.errno, error.ERRNO.INVALID_PHONE_NUMBER) - assert.equal(err.message, 'Invalid phone number') - }) - }) - }) + assert.ok(err); + assert.equal(err.code, 400); + assert.equal(err.errno, error.ERRNO.INVALID_PHONE_NUMBER); + assert.equal(err.message, 'Invalid phone number'); + }); + }); + }); it('POST /sms with invalid region', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') @@ -54,14 +54,14 @@ describe('remote sms without the signinCodes feature included in the payload', f return client.smsSend('+886287861100', 1) .then(() => assert.fail('request should have failed')) .catch(err => { - assert.ok(err) - assert.equal(err.code, 400) - assert.equal(err.errno, error.ERRNO.INVALID_REGION) - assert.equal(err.message, 'Invalid region') - assert.equal(err.region, 'TW') - }) - }) - }) + assert.ok(err); + assert.equal(err.code, 400); + assert.equal(err.errno, error.ERRNO.INVALID_REGION); + assert.equal(err.message, 'Invalid region'); + assert.equal(err.region, 'TW'); + }); + }); + }); it('POST /sms with invalid message id', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') @@ -69,56 +69,56 @@ describe('remote sms without the signinCodes feature included in the payload', f return client.smsSend('+18885083401', 2) .then(() => assert.fail('request should have failed')) .catch(err => { - assert.ok(err) - assert.equal(err.code, 400) - assert.equal(err.errno, error.ERRNO.INVALID_MESSAGE_ID) - assert.equal(err.message, 'Invalid message id') - }) - }) - }) + assert.ok(err); + assert.equal(err.code, 400); + assert.equal(err.errno, error.ERRNO.INVALID_MESSAGE_ID); + assert.equal(err.message, 'Invalid message id'); + }); + }); + }); it('GET /sms/status', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') .then(client => { return client.smsStatus() .then(status => { - assert.ok(status) - assert.equal(typeof status.ok, 'boolean') - assert.equal(status.country, 'US') - }) - }) - }) + assert.ok(status); + assert.equal(typeof status.ok, 'boolean'); + assert.equal(status.country, 'US'); + }); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); describe('remote sms with the signinCodes feature included in the payload', function() { - this.timeout(10000) - let server + this.timeout(10000); + let server; before(() => { - config.sms.enabled = true - config.sms.isStatusGeoEnabled = true + config.sms.enabled = true; + config.sms.isStatusGeoEnabled = true; // /sms endpoints need creds and spend money unless the SMS provider is mocked - config.sms.useMock = true + config.sms.useMock = true; return TestServer.start(config) .then(result => { - server = result - }) - }) + server = result; + }); + }); it('POST /sms success', () => { return Client.create(config.publicUrl, server.uniqueEmail(), 'wibble') .then(client => { return client.smsSend('+18885083401', 1, [ 'signinCodes' ]) - .then(result => assert.ok(result)) - }) - }) + .then(result => assert.ok(result)); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/token_code_tests.js b/test/remote/token_code_tests.js index 988f74e7..dd35cf26 100644 --- a/test/remote/token_code_tests.js +++ b/test/remote/token_code_tests.js @@ -2,35 +2,35 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const config = require('../../config').getProperties() -const TestServer = require('../test_server') -const Client = require('../client')() -const error = require('../../lib/error') +const { assert } = require('chai'); +const config = require('../../config').getProperties(); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const error = require('../../lib/error'); describe('remote tokenCodes', function () { - let server, client, email, code - const password = 'pssssst' + let server, client, email, code; + const password = 'pssssst'; - this.timeout(10000) + this.timeout(10000); before(function () { return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); beforeEach(() => { - email = server.uniqueEmail('@mozilla.com') + email = server.uniqueEmail('@mozilla.com'); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then(function (x) { - client = x - assert.ok(client.authAt, 'authAt was set') - }) - }) + client = x; + assert.ok(client.authAt, 'authAt was set'); + }); + }); it('should error with invalid code', () => { return Client.login(config.publicUrl, email, password, { @@ -38,22 +38,22 @@ describe('remote tokenCodes', function () { keys: true }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method') - return client.verifyTokenCode('011001') + client = res; + assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method'); + return client.verifyTokenCode('011001'); }) .then(() => { - assert.fail('consumed invalid code') + assert.fail('consumed invalid code'); }, (err) => { - assert.equal(err.errno, error.ERRNO.INVALID_TOKEN_VERIFICATION_CODE, 'correct errno') - return client.emailStatus() + assert.equal(err.errno, error.ERRNO.INVALID_TOKEN_VERIFICATION_CODE, 'correct errno'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - }) - }) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + }); + }); it('should error with invalid request param when using wrong code format', () => { return Client.login(config.publicUrl, email, password, { @@ -61,22 +61,22 @@ describe('remote tokenCodes', function () { keys: true }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method') - return client.verifyTokenCode('Cool Runnings 4 u') + client = res; + assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method'); + return client.verifyTokenCode('Cool Runnings 4 u'); }) .then(() => { - assert.fail('consumed invalid code') + assert.fail('consumed invalid code'); }, (err) => { - assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'correct errno') - return client.emailStatus() + assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'correct errno'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - }) - }) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + }); + }); it('should consume valid code', () => { return Client.login(config.publicUrl, email, password, { @@ -84,32 +84,32 @@ describe('remote tokenCodes', function () { keys: true }) .then((res) => { - client = res - assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method') - return client.emailStatus() + client = res; + assert.equal(res.verificationMethod, 'email-2fa', 'sets correct verification method'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, false, 'account is not verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - return server.mailbox.waitForEmail(email) + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent') - code = emailData.headers['x-signin-verify-code'] - assert.ok(code, 'code is sent') - return client.verifyTokenCode(code) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyTokenCode(code); }) .then((res) => { - assert.ok(res, 'verified successful response') - return client.emailStatus() + assert.ok(res, 'verified successful response'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, true, 'session is verified') - }) - }) + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, true, 'session is verified'); + }); + }); it('should accept optional uid parameter in request body', () => { return Client.login(config.publicUrl, email, password, { @@ -117,60 +117,60 @@ describe('remote tokenCodes', function () { keys: true }) .then((res) => { - client = res - return server.mailbox.waitForEmail(email) + client = res; + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent') - code = emailData.headers['x-signin-verify-code'] - assert.ok(code, 'code is sent') - return client.verifyTokenCode(code, { uid: client.uid }) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyTokenCode(code, { uid: client.uid }); }) .then((res) => { - assert.ok(res, 'verified successful response') - return client.emailStatus() + assert.ok(res, 'verified successful response'); + return client.emailStatus(); }) .then((status) => { - assert.equal(status.verified, true, 'account is verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, true, 'session is verified') - }) - }) + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, true, 'session is verified'); + }); + }); it('should reject mismatched uid parameter in request body', () => { - const uid1 = client.uid - const email2 = server.uniqueEmail('@mozilla.com') + const uid1 = client.uid; + const email2 = server.uniqueEmail('@mozilla.com'); return Client.createAndVerify(config.publicUrl, email2, password, server.mailbox) .then(() => { return Client.login(config.publicUrl, email2, password, { verificationMethod: 'email-2fa', keys: true - }) + }); }) .then((res) => { - client = res - assert.notEqual(uid1, client.uid, 'new account has a different uid') - return server.mailbox.waitForEmail(email2) + client = res; + assert.notEqual(uid1, client.uid, 'new account has a different uid'); + return server.mailbox.waitForEmail(email2); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent') - code = emailData.headers['x-signin-verify-code'] - assert.ok(code, 'code is sent') - return client.verifyTokenCode(code, { uid: uid1 }) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyTokenCode(code, { uid: uid1 }); }) .then( - () => { assert.fail('using a mismatched uid should have failed') }, + () => { assert.fail('using a mismatched uid should have failed'); }, err => { - assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'uid parameter was rejected') - return client.emailStatus() + assert.equal(err.errno, error.ERRNO.INVALID_PARAMETER, 'uid parameter was rejected'); + return client.emailStatus(); } ) .then((status) => { - assert.equal(status.verified, false, 'account is verified') - assert.equal(status.emailVerified, true, 'email is verified') - assert.equal(status.sessionVerified, false, 'session is not verified') - }) - }) + assert.equal(status.verified, false, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, false, 'session is not verified'); + }); + }); it('should retrieve account keys', () => { return Client.login(config.publicUrl, email, password, { @@ -178,29 +178,29 @@ describe('remote tokenCodes', function () { keys: true }) .then((res) => { - client = res - return server.mailbox.waitForEmail(email) + client = res; + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent') - code = emailData.headers['x-signin-verify-code'] - assert.ok(code, 'code is sent') - return client.verifyTokenCode(code) + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCodeEmail', 'sign-in code sent'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyTokenCode(code); }) .then((res) => { - assert.ok(res, 'verified successful response') + assert.ok(res, 'verified successful response'); - return client.keys() + return client.keys(); }) .then((keys) => { - assert.ok(keys.kA, 'has kA keys') - assert.ok(keys.kB, 'has kB keys') - assert.ok(keys.wrapKb, 'has wrapKb keys') - }) - }) + assert.ok(keys.kA, 'has kA keys'); + assert.ok(keys.kB, 'has kB keys'); + assert.ok(keys.wrapKb, 'has wrapKb keys'); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/token_expiry_tests.js b/test/remote/token_expiry_tests.js index e314b0ec..68a004a6 100644 --- a/test/remote/token_expiry_tests.js +++ b/test/remote/token_expiry_tests.js @@ -2,64 +2,64 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); -function fail() { throw new Error() } +function fail() { throw new Error(); } describe('remote token expiry', function() { - this.timeout(15000) - let server, config + this.timeout(15000); + let server, config; before(() => { - config = require('../../config').getProperties() - config.tokenLifetimes.passwordChangeToken = 1 + config = require('../../config').getProperties(); + config.tokenLifetimes.passwordChangeToken = 1; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); it( 'token expiry', () => { // FYI config.tokenLifetimes.passwordChangeToken = 1 - var email = Math.random() + '@example.com' - var password = 'ok' + var email = Math.random() + '@example.com'; + var password = 'ok'; return Client.create(config.publicUrl, email, password, { preVerified: true }) .then( function (c) { - return c.changePassword('hello') + return c.changePassword('hello'); } ) .then( fail, function (err) { - assert.equal(err.errno, 110, 'invalid token') + assert.equal(err.errno, 110, 'invalid token'); } - ) + ); } - ) + ); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); describe('remote session token expiry', function () { - this.timeout(15000) - let server, config + this.timeout(15000); + let server, config; before(() => { - config = require('../../config').getProperties() - config.tokenLifetimes.sessionTokenWithoutDevice = 1 + config = require('../../config').getProperties(); + config.tokenLifetimes.sessionTokenWithoutDevice = 1; return TestServer.start(config) - .then(result => server = result) - }) + .then(result => server = result); + }); it('session token expires', () => { return Client.createAndVerify(config.publicUrl, `${Math.random()}@example.com`, 'wibble', server.mailbox) @@ -69,8 +69,8 @@ describe('remote session token expiry', function () { () => assert.ok(false, 'client.sessionStatus should have failed'), err => assert.equal(err.errno, 110, 'client.sessionStatus returned the correct error') ) - ) - }) + ); + }); - after(() => TestServer.stop(server)) -}) + after(() => TestServer.stop(server)); +}); diff --git a/test/remote/totp_tests.js b/test/remote/totp_tests.js index 6d99f240..7f43aba6 100644 --- a/test/remote/totp_tests.js +++ b/test/remote/totp_tests.js @@ -2,299 +2,299 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const crypto = require('crypto') -const config = require('../../config').getProperties() -const TestServer = require('../test_server') -const Client = require('../client')() -const otplib = require('otplib') +const { assert } = require('chai'); +const crypto = require('crypto'); +const config = require('../../config').getProperties(); +const TestServer = require('../test_server'); +const Client = require('../client')(); +const otplib = require('otplib'); describe('remote totp', function () { - let server, client, email, totpToken, authenticator - const password = 'pssssst' + let server, client, email, totpToken, authenticator; + const password = 'pssssst'; const metricsContext = { flowBeginTime: Date.now(), flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' - } + }; - this.timeout(10000) + this.timeout(10000); otplib.authenticator.options = { crypto: crypto, encoding: 'hex', window: 10 - } + }; before(() => { - config.securityHistory.ipProfiling = {} - config.signinConfirmation.skipForNewAccounts.enabled = false + config.securityHistory.ipProfiling = {}; + config.signinConfirmation.skipForNewAccounts.enabled = false; return TestServer.start(config) .then(s => { - server = s - }) - }) + server = s; + }); + }); beforeEach(() => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((x) => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); return client.createTotpToken({metricsContext}) .then((result) => { - authenticator = new otplib.authenticator.Authenticator() - authenticator.options = Object.assign({}, otplib.authenticator.options, {secret: result.secret}) - totpToken = result + authenticator = new otplib.authenticator.Authenticator(); + authenticator.options = Object.assign({}, otplib.authenticator.options, {secret: result.secret}); + totpToken = result; // Verify TOTP token - const code = authenticator.generate() - return client.verifyTotpCode(code, {metricsContext, service: 'sync'}) + const code = authenticator.generate(); + return client.verifyTotpCode(code, {metricsContext, service: 'sync'}); }) .then((response) => { - assert.equal(response.success, true, 'totp codes match') - assert.equal(response.recoveryCodes.length > 1, true, 'recovery codes returned') - return server.mailbox.waitForEmail(email) + assert.equal(response.success, true, 'totp codes match'); + assert.equal(response.recoveryCodes.length > 1, true, 'recovery codes returned'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postAddTwoStepAuthenticationEmail', 'correct template sent') - }) - }) - }) + assert.equal(emailData.headers['x-template-name'], 'postAddTwoStepAuthenticationEmail', 'correct template sent'); + }); + }); + }); it('should create totp token', () => { - assert.ok(totpToken) - assert.ok(totpToken.qrCodeUrl) - }) + assert.ok(totpToken); + assert.ok(totpToken.qrCodeUrl); + }); it('should check if totp token exists for user', () => { return client.checkTotpTokenExists() .then((response) => { - assert.equal(response.exists, true, 'token exists') - }) - }) + assert.equal(response.exists, true, 'token exists'); + }); + }); it('should fail check for totp token if in unverified session', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return client.login() .then(() => client.sessionStatus()) .then((result) => { - assert.equal(result.state, 'unverified', 'session is unverified') - return client.checkTotpTokenExists() + assert.equal(result.state, 'unverified', 'session is unverified'); + return client.checkTotpTokenExists(); }) .then(assert.fail, (err) => { - assert.equal(err.errno, 138, 'correct unverified session errno') - }) - }) + assert.equal(err.errno, 138, 'correct unverified session errno'); + }); + }); it('should fail to create second totp token for same user', () => { return client.createTotpToken() .then(assert.fail, (err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 154, 'correct error errno') - }) - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 154, 'correct error errno'); + }); + }); it('should not fail to delete unknown totp token', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((x) => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); return client.deleteTotpToken() - .then((result) => assert.ok(result, 'delete totp token successfully')) - }) - }) + .then((result) => assert.ok(result, 'delete totp token successfully')); + }); + }); it('should delete totp token', () => { return client.deleteTotpToken() .then((result) => { - assert.ok(result, 'delete totp token successfully') - return server.mailbox.waitForEmail(email) + assert.ok(result, 'delete totp token successfully'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'postRemoveTwoStepAuthenticationEmail', 'correct template sent') + assert.equal(emailData.headers['x-template-name'], 'postRemoveTwoStepAuthenticationEmail', 'correct template sent'); // Can create a new token return client.checkTotpTokenExists() .then((result) => { - assert.equal(result.exists, false, 'token does not exist') - }) - }) - }) + assert.equal(result.exists, false, 'token does not exist'); + }); + }); + }); it('should request `totp-2fa` on login if user has verified totp token', () => { return Client.login(config.publicUrl, email, password) .then((response) => { - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') - }) - }) + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); + }); + }); it('should not have `totp-2fa` verification if user has unverified totp token', () => { return client.deleteTotpToken() .then(() => client.createTotpToken()) .then(() => Client.login(config.publicUrl, email, password)) .then((response) => { - assert.notEqual(response.verificationMethod, 'totp-2fa', 'verification method not set to `totp-2fa`') - assert.equal(response.verificationReason, 'login', 'verification reason set to `login`') - }) - }) + assert.notEqual(response.verificationMethod, 'totp-2fa', 'verification method not set to `totp-2fa`'); + assert.equal(response.verificationReason, 'login', 'verification reason set to `login`'); + }); + }); it('should not bypass `totp-2fa` by resending sign-in confirmation code', () => { return Client.login(config.publicUrl, email, password) .then((response) => { - client = response - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') + client = response; + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); return client.requestVerifyEmail() .then((res) => { - assert.deepEqual(res, {}, 'returns empty response') - }) - }) - }) + assert.deepEqual(res, {}, 'returns empty response'); + }); + }); + }); it('should not bypass `totp-2fa` by signing a cert with an unverified session', () => { return Client.login(config.publicUrl, email, password, { keys: false }) .then((response) => { - client = response - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') + client = response; + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); const publicKey = { 'algorithm': 'RS', 'n': '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862' + '993582789079872007974809511698859885077002492642203267408776123', 'e': '65537' - } + }; return client.sign(publicKey, 600) .then(() => { - assert.fail('should not have succeeded') + assert.fail('should not have succeeded'); }, (err) => { - assert.equal(err.errno, 138, 'should have failed due to unverified session') - }) - }) - }) + assert.equal(err.errno, 138, 'should have failed due to unverified session'); + }); + }); + }); it('should not bypass `totp-2fa` by when using session reauth', () => { return Client.login(config.publicUrl, email, password) .then((response) => { - client = response - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') + client = response; + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); // Lets attempt to sign-in reusing session reauth return client.reauth() .then((response) => { - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') - }) - }) - }) + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); + }); + }); + }); it('should not create verified session after account reset with totp', () => { - const newPassword = 'anotherPassword' + const newPassword = 'anotherPassword'; return Client.login(config.publicUrl, email, password) .then((response) => { - client = response - assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set') - assert.equal(response.verificationReason, 'login', 'verification reason set') + client = response; + assert.equal(response.verificationMethod, 'totp-2fa', 'verification method set'); + assert.equal(response.verificationReason, 'login', 'verification reason set'); - return client.forgotPassword() + return client.forgotPassword(); }) .then(() => server.mailbox.waitForCode(email)) .then((code) => client.verifyPasswordResetCode(code)) .then(() => client.resetPassword(newPassword, {}, {keys: true})) .then((res) => { - assert.equal(res.verificationMethod, 'totp-2fa', 'verificationMethod set') - assert.equal(res.verificationReason, 'login', 'verificationMethod set') - assert.equal(res.verified, false) - assert.ok(res.keyFetchToken) - assert.ok(res.sessionToken) - assert.ok(res.authAt) - }) - }) + assert.equal(res.verificationMethod, 'totp-2fa', 'verificationMethod set'); + assert.equal(res.verificationReason, 'login', 'verificationMethod set'); + assert.equal(res.verified, false); + assert.ok(res.keyFetchToken); + assert.ok(res.sessionToken); + assert.ok(res.authAt); + }); + }); describe('totp code verification', () => { beforeEach(() => { // Create a new unverified session to test totp codes return Client.login(config.publicUrl, email, password) - .then((response) => client = response) - }) + .then((response) => client = response); + }); it('should fail to verify totp code', () => { - const code = authenticator.generate() - const incorrectCode = code === '123456' ? '123455' : '123456' + const code = authenticator.generate(); + const incorrectCode = code === '123456' ? '123455' : '123456'; return client.verifyTotpCode(incorrectCode, {metricsContext, service: 'sync'}) .then((result) => { - assert.equal(result.success, false, 'failed') - }) - }) + assert.equal(result.success, false, 'failed'); + }); + }); it('should reject non-numeric codes', () => { return client.verifyTotpCode('wrong', {metricsContext, service: 'sync'}) .then( assert.fail, (err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 107, 'correct error errno') - }) - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 107, 'correct error errno'); + }); + }); it('should fail to verify totp code that does not have totp token', () => { - email = server.uniqueEmail() + email = server.uniqueEmail(); return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then((x) => { - client = x - assert.ok(client.authAt, 'authAt was set') + client = x; + assert.ok(client.authAt, 'authAt was set'); return client.verifyTotpCode('123456', {metricsContext, service: 'sync'}) .then(assert.fail, (err) => { - assert.equal(err.code, 400, 'correct error code') - assert.equal(err.errno, 155, 'correct error errno') - }) - }) - }) + assert.equal(err.code, 400, 'correct error code'); + assert.equal(err.errno, 155, 'correct error errno'); + }); + }); + }); it('should verify totp code', () => { - const code = authenticator.generate() + const code = authenticator.generate(); return client.verifyTotpCode(code, {metricsContext, service: 'sync'}) .then((response) => { - assert.equal(response.success, true, 'totp codes match') - return server.mailbox.waitForEmail(email) + assert.equal(response.success, true, 'totp codes match'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'newDeviceLoginEmail', 'correct template sent') - }) - }) + assert.equal(emailData.headers['x-template-name'], 'newDeviceLoginEmail', 'correct template sent'); + }); + }); it('should verify totp code from previous code window', () => { - const futureAuthenticator = new otplib.Authenticator() - futureAuthenticator.options = Object.assign({}, authenticator.options, {epoch: Date.now() / 1000 - 30}) - const code = futureAuthenticator.generate() + const futureAuthenticator = new otplib.Authenticator(); + futureAuthenticator.options = Object.assign({}, authenticator.options, {epoch: Date.now() / 1000 - 30}); + const code = futureAuthenticator.generate(); return client.verifyTotpCode(code, {metricsContext, service: 'sync'}) .then((response) => { - assert.equal(response.success, true, 'totp codes match') - return server.mailbox.waitForEmail(email) + assert.equal(response.success, true, 'totp codes match'); + return server.mailbox.waitForEmail(email); }) .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'newDeviceLoginEmail', 'correct template sent') - }) - }) + assert.equal(emailData.headers['x-template-name'], 'newDeviceLoginEmail', 'correct template sent'); + }); + }); it('should not verify totp code from future code window', () => { - const futureAuthenticator = new otplib.Authenticator() - futureAuthenticator.options = Object.assign({}, authenticator.options, {epoch: Date.now() / 1000 + 3000}) - const code = futureAuthenticator.generate() + const futureAuthenticator = new otplib.Authenticator(); + futureAuthenticator.options = Object.assign({}, authenticator.options, {epoch: Date.now() / 1000 + 3000}); + const code = futureAuthenticator.generate(); return client.verifyTotpCode(code, {metricsContext, service: 'sync'}) .then((response) => { - assert.equal(response.success, false, 'totp codes do not match') - }) - }) - }) + assert.equal(response.success, false, 'totp codes do not match'); + }); + }); + }); after(() => { - return TestServer.stop(server) - }) -}) + return TestServer.stop(server); + }); +}); diff --git a/test/remote/verifier_upgrade_tests.js b/test/remote/verifier_upgrade_tests.js index fc6cc372..6fe7fbc7 100644 --- a/test/remote/verifier_upgrade_tests.js +++ b/test/remote/verifier_upgrade_tests.js @@ -2,17 +2,17 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -var TestServer = require('../test_server') -const Client = require('../client')() -var createDBServer = require('fxa-auth-db-mysql') -var log = { trace() {}, info() {} } +const { assert } = require('chai'); +var TestServer = require('../test_server'); +const Client = require('../client')(); +var createDBServer = require('fxa-auth-db-mysql'); +var log = { trace() {}, info() {} }; -var config = require('../../config').getProperties() +var config = require('../../config').getProperties(); -var Token = require('../../lib/tokens')(log) +var Token = require('../../lib/tokens')(log); var DB = require('../../lib/db')( config, log, @@ -22,26 +22,26 @@ var DB = require('../../lib/db')( Token.AccountResetToken, Token.PasswordForgotToken, Token.PasswordChangeToken -) +); describe('remote verifier upgrade', function() { - this.timeout(30000) + this.timeout(30000); before(() => { - config.verifierVersion = 0 - config.securityHistory.ipProfiling.allowedRecency = 0 - }) + config.verifierVersion = 0; + config.securityHistory.ipProfiling.allowedRecency = 0; + }); it( 'upgrading verifierVersion upgrades the account on password change', () => { return createDBServer().then(function (db_server) { - db_server.listen(config.httpdb.url.split(':')[2]) - db_server.on('error', function () {}) + db_server.listen(config.httpdb.url.split(':')[2]); + db_server.on('error', function () {}); - var email = Math.random() + '@example.com' - var password = 'ok' - var uid = null + var email = Math.random() + '@example.com'; + var password = 'ok'; + var uid = null; return TestServer.start(config) .then( @@ -49,10 +49,10 @@ describe('remote verifier upgrade', function() { return Client.create(config.publicUrl, email, password, { preVerified: true, keys: true }) .then( function (c) { - uid = c.uid - return server.stop() + uid = c.uid; + return server.stop(); } - ) + ); } ) .then( @@ -63,39 +63,39 @@ describe('remote verifier upgrade', function() { return db.account(uid) .then( function (account) { - assert.equal(account.verifierVersion, 0, 'wrong version') + assert.equal(account.verifierVersion, 0, 'wrong version'); } ) .then( function () { - return db.close() + return db.close(); } - ) + ); } - ) + ); } ) .then( function () { - config.verifierVersion = 1 - return TestServer.start(config) + config.verifierVersion = 1; + return TestServer.start(config); } ) .then( function (server) { - var client + var client; return Client.login(config.publicUrl, email, password, server.mailbox) .then( function (x) { - client = x - return client.changePassword(password) + client = x; + return client.changePassword(password); } ) .then( function () { - return server.stop() + return server.stop(); } - ) + ); } ) .then( @@ -106,29 +106,29 @@ describe('remote verifier upgrade', function() { return db.account(uid) .then( function (account) { - assert.equal(account.verifierVersion, 1, 'wrong upgrade version') + assert.equal(account.verifierVersion, 1, 'wrong upgrade version'); } ) .then( function () { - return db.close() + return db.close(); } - ) + ); } - ) + ); } ) .then( function () { try { - db_server.close() + db_server.close(); } catch (e) { // This connection may already be dead if a real mysql server is // already bound to :8000. } } - ) - }) + ); + }); } - ) -}) + ); +}); diff --git a/test/routes_helpers.js b/test/routes_helpers.js index 3d8672df..d978e56c 100644 --- a/test/routes_helpers.js +++ b/test/routes_helpers.js @@ -2,25 +2,25 @@ * 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/. */ -'use strict' +'use strict'; exports.getRoute = function (routes, path, method) { - var route = null + var route = null; routes.some(function (r) { if (r.path === path) { - route = r + route = r; if (method) { if (r.method === method) { - return true + return true; } - return false + return false; } - return true + return true; } - }) + }); - return route -} + return route; +}; diff --git a/test/scripts/bulk-mailer.js b/test/scripts/bulk-mailer.js index 8050ba2b..eb9826d7 100644 --- a/test/scripts/bulk-mailer.js +++ b/test/scripts/bulk-mailer.js @@ -2,33 +2,33 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const { assert } = require('chai') -const cp = require('child_process') -const fs = require('fs') -const mkdirp = require('mkdirp') -const mocks = require(`${ROOT_DIR}/test/mocks`) -const path = require('path') -const P = require('bluebird') -const rimraf = require('rimraf') +const { assert } = require('chai'); +const cp = require('child_process'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const mocks = require(`${ROOT_DIR}/test/mocks`); +const path = require('path'); +const P = require('bluebird'); +const rimraf = require('rimraf'); -const cwd = path.resolve(__dirname, ROOT_DIR) -cp.execAsync = P.promisify(cp.exec) +const cwd = path.resolve(__dirname, ROOT_DIR); +cp.execAsync = P.promisify(cp.exec); -const log = mocks.mockLog() -const config = require('../../config').getProperties() -const Token = require('../../lib/tokens')(log, config) -const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength) -const TestServer = require('../test_server') +const log = mocks.mockLog(); +const config = require('../../config').getProperties(); +const Token = require('../../lib/tokens')(log, config); +const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength); +const TestServer = require('../test_server'); -const OUTPUT_DIRECTORY = path.resolve(__dirname, './test_output') -const USER_DUMP_PATH = path.join(OUTPUT_DIRECTORY, 'user_dump.json') +const OUTPUT_DIRECTORY = path.resolve(__dirname, './test_output'); +const USER_DUMP_PATH = path.join(OUTPUT_DIRECTORY, 'user_dump.json'); -const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex') -const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex') +const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex'); +const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'); function createAccount(email, uid, locale='en') { return { @@ -43,116 +43,116 @@ function createAccount(email, uid, locale='en') { verifierVersion: 1, verifyHash: zeroBuffer32, wrapWrapKb: zeroBuffer32, - } + }; } -const account1Mock = createAccount('user1@test.com', 'f9916686c226415abd06ae550f073cec', 'en') -const account2Mock = createAccount('user2@test.com', 'f9916686c226415abd06ae550f073ced', 'es') +const account1Mock = createAccount('user1@test.com', 'f9916686c226415abd06ae550f073cec', 'en'); +const account2Mock = createAccount('user2@test.com', 'f9916686c226415abd06ae550f073ced', 'es'); const DB = require('../../lib/db')( config, log, Token, UnblockCode -) +); describe('scripts/bulk-mailer', function () { - this.timeout(10000) + this.timeout(10000); - let db, server + let db, server; before(() => { - rimraf.sync(OUTPUT_DIRECTORY) - mkdirp.sync(OUTPUT_DIRECTORY) + rimraf.sync(OUTPUT_DIRECTORY); + mkdirp.sync(OUTPUT_DIRECTORY); return TestServer.start(config) .then(s => { - server = s - return DB.connect(config[config.db.backend]) + server = s; + return DB.connect(config[config.db.backend]); }) .then(_db => { db = _db; - return P.all([db.createAccount(account1Mock), db.createAccount(account2Mock)]) + return P.all([db.createAccount(account1Mock), db.createAccount(account2Mock)]); }) .then(() => { - return cp.execAsync(`node scripts/dump-users --uids ${account1Mock.uid},${account2Mock.uid} > ${USER_DUMP_PATH}`, { cwd }) - }) - }) + return cp.execAsync(`node scripts/dump-users --uids ${account1Mock.uid},${account2Mock.uid} > ${USER_DUMP_PATH}`, { cwd }); + }); + }); after(() => { return P.all([db.deleteAccount(account1Mock), db.deleteAccount(account2Mock)]) .then(() => TestServer.stop(server)) - .then(() => rimraf.sync(OUTPUT_DIRECTORY)) - }) + .then(() => rimraf.sync(OUTPUT_DIRECTORY)); + }); it('fails if --input missing', () => { return cp.execAsync('node scripts/bulk-mailer --method sendVerifyCode', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --input file missing', () => { return cp.execAsync('node scripts/bulk-mailer --input does_not_exist --method sendVerifyCode', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --method missing', () => { return cp.execAsync('node scripts/bulk-mailer --input ${USER_DUMP_PATH}', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --method is invalid', () => { return cp.execAsync('node scripts/bulk-mailer --input ${USER_DUMP_PATH} --method doesNotExist', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('succeeds with valid input file and method, writing files to disk', () => { return cp.execAsync(`node scripts/bulk-mailer --input ${USER_DUMP_PATH} --method sendVerifyCode --write ${OUTPUT_DIRECTORY}`, { cwd }) .then(() => { - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.headers'))) - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html'))) - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt'))) + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.headers'))); + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html'))); + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt'))); // emails are in english - const test1Html = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html')).toString() - assert.include(test1Html, 'This is an automated email') - const test1Text = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt')).toString() - assert.include(test1Text, 'This is an automated email') + const test1Html = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html')).toString(); + assert.include(test1Html, 'This is an automated email'); + const test1Text = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt')).toString(); + assert.include(test1Text, 'This is an automated email'); - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.headers'))) - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html'))) - assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt'))) + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.headers'))); + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.html'))); + assert.isTrue(fs.existsSync(path.join(OUTPUT_DIRECTORY, 'user1@test.com.txt'))); // emails are in spanish - const test2Html = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user2@test.com.html')).toString() - assert.include(test2Html, 'Este es un correo automatizado') - const test2Text = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user2@test.com.txt')).toString() - assert.include(test2Text, 'Este es un correo automatizado') - }) - }) + const test2Html = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user2@test.com.html')).toString(); + assert.include(test2Html, 'Este es un correo automatizado'); + const test2Text = fs.readFileSync(path.join(OUTPUT_DIRECTORY, 'user2@test.com.txt')).toString(); + assert.include(test2Text, 'Este es un correo automatizado'); + }); + }); it('succeeds with valid input file and method, writing emails to stdout', () => { return cp.execAsync(`node scripts/bulk-mailer --input ${USER_DUMP_PATH} --method sendVerifyCode`, { cwd }) .then(result => { - assert.include(result, account1Mock.uid) - assert.include(result, account1Mock.email) - assert.include(result, 'This is an automated email') + assert.include(result, account1Mock.uid); + assert.include(result, account1Mock.email); + assert.include(result, 'This is an automated email'); - assert.include(result, account2Mock.uid) - assert.include(result, account2Mock.email) - assert.include(result, 'Este es un correo automatizado') - }) - }) + assert.include(result, account2Mock.uid); + assert.include(result, account2Mock.email); + assert.include(result, 'Este es un correo automatizado'); + }); + }); it('succeeds with valid input file and method, sends', () => { - return cp.execAsync(`node scripts/bulk-mailer --input ${USER_DUMP_PATH} --method sendVerifyCode --send`, { cwd }) - }) -}) + return cp.execAsync(`node scripts/bulk-mailer --input ${USER_DUMP_PATH} --method sendVerifyCode --send`, { cwd }); + }); +}); diff --git a/test/scripts/bulk-mailer/index.js b/test/scripts/bulk-mailer/index.js index e4725c99..096e4f58 100644 --- a/test/scripts/bulk-mailer/index.js +++ b/test/scripts/bulk-mailer/index.js @@ -2,85 +2,85 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const path = require('path') -const proxyquire = require('proxyquire') -const rimraf = require('rimraf') -const sinon = require('sinon') +const { assert } = require('chai'); +const path = require('path'); +const proxyquire = require('proxyquire'); +const rimraf = require('rimraf'); +const sinon = require('sinon'); -const OUTPUT_DIR = path.resolve(__dirname, 'test_output') +const OUTPUT_DIR = path.resolve(__dirname, 'test_output'); describe('bulk-mailer', () => { - const userRecords = ['a','b','c'] - const normalizedUserRecords = ['a','b','c'] + const userRecords = ['a','b','c']; + const normalizedUserRecords = ['a','b','c']; const normalizerStub = { normalize: sinon.spy(() => normalizedUserRecords) - } + }; const UserRecordNormalizerMock = function () { - return normalizerStub - } - const readUserRecordsSpy = sinon.spy(() => userRecords) - const sendEmailBatchesSpy = sinon.spy(function () {}) + return normalizerStub; + }; + const readUserRecordsSpy = sinon.spy(() => userRecords); + const sendEmailBatchesSpy = sinon.spy(function () {}); const sendersMock = { email: { sendVerifyCode: sinon.spy() } - } + }; const createSendersSpy = sinon.spy(function () { - return Promise.resolve(sendersMock) - }) + return Promise.resolve(sendersMock); + }); before(() => { - rimraf.sync(OUTPUT_DIR) + rimraf.sync(OUTPUT_DIR); const bulkMailer = proxyquire('../../../scripts/bulk-mailer/index', { './read-user-records': readUserRecordsSpy, './normalize-user-records': UserRecordNormalizerMock, './send-email-batches': sendEmailBatchesSpy, '../../lib/senders': createSendersSpy - }) + }); - return bulkMailer('input.json', 'sendVerifyCode', 2, 1000, false, OUTPUT_DIR) - }) + return bulkMailer('input.json', 'sendVerifyCode', 2, 1000, false, OUTPUT_DIR); + }); after(() => { - rimraf.sync(OUTPUT_DIR) - }) + rimraf.sync(OUTPUT_DIR); + }); it('calls readUserRecords as expected', () => { - assert.isTrue(readUserRecordsSpy.calledOnce) - assert.equal(readUserRecordsSpy.args[0][0], 'input.json') + assert.isTrue(readUserRecordsSpy.calledOnce); + assert.equal(readUserRecordsSpy.args[0][0], 'input.json'); }); it('calls normalize as expected', () => { - assert.isTrue(normalizerStub.normalize.calledOnce) - assert.strictEqual(normalizerStub.normalize.args[0][0], userRecords) - assert.ok(normalizerStub.normalize.args[0][1]) - }) + assert.isTrue(normalizerStub.normalize.calledOnce); + assert.strictEqual(normalizerStub.normalize.args[0][0], userRecords); + assert.ok(normalizerStub.normalize.args[0][1]); + }); it('calls sendEmailBatches as expected', () => { assert.isTrue(sendEmailBatchesSpy.called); - const expectedBatches = [['a','b'], ['c']] - assert.deepEqual(sendEmailBatchesSpy.args[0][0], expectedBatches) - assert.equal(sendEmailBatchesSpy.args[0][1], 1000) - assert.isFunction(sendEmailBatchesSpy.args[0][2]) - assert.isObject(sendEmailBatchesSpy.args[0][3]) - assert.isTrue(sendEmailBatchesSpy.args[0][4]) - }) + const expectedBatches = [['a','b'], ['c']]; + assert.deepEqual(sendEmailBatchesSpy.args[0][0], expectedBatches); + assert.equal(sendEmailBatchesSpy.args[0][1], 1000); + assert.isFunction(sendEmailBatchesSpy.args[0][2]); + assert.isObject(sendEmailBatchesSpy.args[0][3]); + assert.isTrue(sendEmailBatchesSpy.args[0][4]); + }); it('sendDelegate is hooked up correctly', () => { - const userInfo = { emails: ['a'] } - sendEmailBatchesSpy.args[0][2](userInfo) - assert.isTrue(sendersMock.email.sendVerifyCode.calledOnce) - assert.deepEqual(sendersMock.email.sendVerifyCode.args[0][0], ['a']) - assert.strictEqual(sendersMock.email.sendVerifyCode.args[0][1], userInfo) - }) -}) + const userInfo = { emails: ['a'] }; + sendEmailBatchesSpy.args[0][2](userInfo); + assert.isTrue(sendersMock.email.sendVerifyCode.calledOnce); + assert.deepEqual(sendersMock.email.sendVerifyCode.args[0][0], ['a']); + assert.strictEqual(sendersMock.email.sendVerifyCode.args[0][1], userInfo); + }); +}); diff --git a/test/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js b/test/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js index 11d3ee93..66a9ea61 100644 --- a/test/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js +++ b/test/scripts/bulk-mailer/nodemailer-mocks/stream-output-mock.js @@ -2,42 +2,42 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') -const StreamOutputMock = require('../../../../scripts/bulk-mailer/nodemailer-mocks/stream-output-mock') +const { assert } = require('chai'); +const sinon = require('sinon'); +const StreamOutputMock = require('../../../../scripts/bulk-mailer/nodemailer-mocks/stream-output-mock'); describe('stdout-mock', () => { - let stdoutMock - let streamMock + let stdoutMock; + let streamMock; before(() => { streamMock = { write: sinon.spy() - } + }; stdoutMock = new StreamOutputMock({ failureRate: 0, stream: streamMock - }) - }) + }); + }); it('writes to the stream', (done) => { stdoutMock.sendMail({ to: 'testuser@testuser.com' }, (err, result) => { try { - assert.isNull(err) - assert.ok(result) + assert.isNull(err); + assert.ok(result); // don't really care how many times it's called. - assert.isTrue(streamMock.write.called) + assert.isTrue(streamMock.write.called); - done() + done(); } catch (err) { - done(err) + done(err); } - }) - }) -}) + }); + }); +}); diff --git a/test/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js b/test/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js index 6bba9038..d50241fa 100644 --- a/test/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js +++ b/test/scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock.js @@ -2,30 +2,30 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const fs = require('fs') -const path = require('path') -const rimraf = require('rimraf') -const WriteToDiskMock = require('../../../../scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock') +const { assert } = require('chai'); +const fs = require('fs'); +const path = require('path'); +const rimraf = require('rimraf'); +const WriteToDiskMock = require('../../../../scripts/bulk-mailer/nodemailer-mocks/write-to-disk-mock'); -const OUTPUT_DIR = path.resolve(__dirname, 'test_output') +const OUTPUT_DIR = path.resolve(__dirname, 'test_output'); describe('stdout-mock', () => { - let writeToDiskMock + let writeToDiskMock; before(() => { - rimraf.sync(OUTPUT_DIR) + rimraf.sync(OUTPUT_DIR); writeToDiskMock = new WriteToDiskMock({ failureRate: 0, outputDir: OUTPUT_DIR - }) - }) + }); + }); after(() => { - rimraf.sync(OUTPUT_DIR) - }) + rimraf.sync(OUTPUT_DIR); + }); it('writes to the output directory', (done) => { writeToDiskMock.sendMail({ @@ -33,17 +33,17 @@ describe('stdout-mock', () => { to: 'testuser@testuser.com' }, (err, result) => { try { - assert.isNull(err) - assert.ok(result) + assert.isNull(err); + assert.ok(result); - assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.txt'))) - assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.html'))) - assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.headers'))) + assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.txt'))); + assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.html'))); + assert.ok(fs.readFileSync(path.join(OUTPUT_DIR, 'testuser@testuser.com.headers'))); - done() + done(); } catch (err) { - done(err) + done(err); } - }) - }) -}) + }); + }); +}); diff --git a/test/scripts/bulk-mailer/normalize-user-records.js b/test/scripts/bulk-mailer/normalize-user-records.js index de7cc476..c8fa21f6 100644 --- a/test/scripts/bulk-mailer/normalize-user-records.js +++ b/test/scripts/bulk-mailer/normalize-user-records.js @@ -2,97 +2,97 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sinon = require('sinon') -const UserRecordNormalizer = require('../../../scripts/bulk-mailer/normalize-user-records') +const { assert } = require('chai'); +const sinon = require('sinon'); +const UserRecordNormalizer = require('../../../scripts/bulk-mailer/normalize-user-records'); describe('normalize-user-records', () => { - let normalizer + let normalizer; before(() => { - normalizer = new UserRecordNormalizer() - }) + normalizer = new UserRecordNormalizer(); + }); describe('normalizeAcceptLanguage', () => { it('uses acceptLanguage if available', () => { - const userRecord = { acceptLanguage: 'es' } - normalizer.normalizeAcceptLanguage(userRecord) + const userRecord = { acceptLanguage: 'es' }; + normalizer.normalizeAcceptLanguage(userRecord); - assert.equal(userRecord.acceptLanguage, 'es') - }) + assert.equal(userRecord.acceptLanguage, 'es'); + }); it('uses the locale if acceptLanguage not set', () => { - const userRecord = { locale: 'es' } - normalizer.normalizeAcceptLanguage(userRecord) + const userRecord = { locale: 'es' }; + normalizer.normalizeAcceptLanguage(userRecord); - assert.equal(userRecord.acceptLanguage, 'es') - }) + assert.equal(userRecord.acceptLanguage, 'es'); + }); it('converts zh-tw locale to zh-cn', () => { - const userRecord = { locale: 'zh-tw' } - normalizer.normalizeAcceptLanguage(userRecord) + const userRecord = { locale: 'zh-tw' }; + normalizer.normalizeAcceptLanguage(userRecord); - assert.equal(userRecord.acceptLanguage, 'zh-cn') - }) - }) + assert.equal(userRecord.acceptLanguage, 'zh-cn'); + }); + }); describe('normalizeLanguage', () => { it('updates the record language to what the translator says is best', () => { - const userRecord = { acceptLanguage: 'es,de' } + const userRecord = { acceptLanguage: 'es,de' }; const translator = { getTranslator: sinon.spy(function () { - return { language: 'de' } + return { language: 'de' }; }) - } - normalizer.normalizeLanguage(userRecord, translator) + }; + normalizer.normalizeLanguage(userRecord, translator); - assert.isTrue(translator.getTranslator.calledOnce) - assert.equal(translator.getTranslator.args[0][0], 'es,de') - assert.equal(userRecord.language, 'de') - }) - }) + assert.isTrue(translator.getTranslator.calledOnce); + assert.equal(translator.getTranslator.args[0][0], 'es,de'); + assert.equal(userRecord.language, 'de'); + }); + }); describe('normalizeLocationTimestamp', () => { - const date = new Date() - date.setUTCFullYear(2017) - date.setUTCMonth(1) - date.setUTCDate(2) - date.setUTCHours(3) - date.setUTCMinutes(4) - date.setUTCSeconds(5) - date.setUTCMilliseconds(678) + const date = new Date(); + date.setUTCFullYear(2017); + date.setUTCMonth(1); + date.setUTCDate(2); + date.setUTCHours(3); + date.setUTCMinutes(4); + date.setUTCSeconds(5); + date.setUTCMilliseconds(678); const expectedTimestamp = '2017-01-02 @ 03:04 UTC'; describe('with timestamp', () => { it('formats as expected', () => { - const location = { timestamp: date } - normalizer.normalizeLocationTimestamp(location) - assert.equal(location.timestamp, expectedTimestamp) - }) - }) + const location = { timestamp: date }; + normalizer.normalizeLocationTimestamp(location); + assert.equal(location.timestamp, expectedTimestamp); + }); + }); describe('with date', () => { it('formats as expected', () => { - const location = { date } - normalizer.normalizeLocationTimestamp(location) - assert.equal(location.timestamp, expectedTimestamp) - }) - }) - }) + const location = { date }; + normalizer.normalizeLocationTimestamp(location); + assert.equal(location.timestamp, expectedTimestamp); + }); + }); + }); describe('normalizeLocationName', () => { it('uses location if available', () => { const location = { location: 'London, United Kingdom' - } + }; - normalizer.normalizeLocationName(location) - assert.equal(location.location, 'London, United Kingdom') - }) + normalizer.normalizeLocationName(location); + assert.equal(location.location, 'London, United Kingdom'); + }); it('converts citynames, countrynames to location', () => { const location = { @@ -104,58 +104,58 @@ describe('normalize-user-records', () => { en: 'England', es: 'Ingleterra' } - } + }; - normalizer.normalizeLocationName(location, 'es') - assert.equal(location.location, 'Londres, Ingleterra') - }) + normalizer.normalizeLocationName(location, 'es'); + assert.equal(location.location, 'Londres, Ingleterra'); + }); it('uses locality as a fallback', () => { const location = { locality: 'Barcelona, Spain' - } + }; - normalizer.normalizeLocationName(location, 'es') - assert.equal(location.location, 'Barcelona, Spain') - }) - }) + normalizer.normalizeLocationName(location, 'es'); + assert.equal(location.location, 'Barcelona, Spain'); + }); + }); describe('normalizeUserRecord', () => { - let translator + let translator; const userRecord = { locale: 'zh-tw', locations: [] - } + }; before(() => { - translator = sinon.spy(language => ({ language })) + translator = sinon.spy(language => ({ language })); - sinon.stub(normalizer, 'normalizeAcceptLanguage') - sinon.stub(normalizer, 'normalizeLanguage') - sinon.stub(normalizer, 'normalizeLocations') + sinon.stub(normalizer, 'normalizeAcceptLanguage'); + sinon.stub(normalizer, 'normalizeLanguage'); + sinon.stub(normalizer, 'normalizeLocations'); - normalizer.normalizeUserRecord(userRecord, translator) - }) + normalizer.normalizeUserRecord(userRecord, translator); + }); it('calls normalizeAcceptLanguage', () => { - assert.isTrue(normalizer.normalizeAcceptLanguage.calledOnce) - assert.equal(normalizer.normalizeAcceptLanguage.args[0][0], userRecord) - }) + assert.isTrue(normalizer.normalizeAcceptLanguage.calledOnce); + assert.equal(normalizer.normalizeAcceptLanguage.args[0][0], userRecord); + }); it('calls normalizeLanguage', () => { - assert.isTrue(normalizer.normalizeLanguage.calledOnce) - assert.equal(normalizer.normalizeLanguage.args[0][0], userRecord) - assert.equal(normalizer.normalizeLanguage.args[0][1], translator) - }) + assert.isTrue(normalizer.normalizeLanguage.calledOnce); + assert.equal(normalizer.normalizeLanguage.args[0][0], userRecord); + assert.equal(normalizer.normalizeLanguage.args[0][1], translator); + }); it('calls normalizeLocations', () => { - assert.isTrue(normalizer.normalizeLocations.calledOnce) - assert.equal(normalizer.normalizeLocations.args[0][0], userRecord) - }) - }) + assert.isTrue(normalizer.normalizeLocations.calledOnce); + assert.equal(normalizer.normalizeLocations.args[0][0], userRecord); + }); + }); describe('normalize', () => { - let translator + let translator; const userRecords = [ { location: 'dropped, no email' @@ -172,15 +172,15 @@ describe('normalize-user-records', () => { before(() => { - translator = sinon.spy(language => ({ language })) - sinon.stub(normalizer, 'normalizeUserRecord') + translator = sinon.spy(language => ({ language })); + sinon.stub(normalizer, 'normalizeUserRecord'); - normalizer.normalize(userRecords, translator) - }) + normalizer.normalize(userRecords, translator); + }); it('calls normalizeUserRecord the expected number of times', () => { - assert.equal(normalizer.normalizeUserRecord.callCount, 2) - }) - }) -}) + assert.equal(normalizer.normalizeUserRecord.callCount, 2); + }); + }); +}); diff --git a/test/scripts/bulk-mailer/read-user-records.js b/test/scripts/bulk-mailer/read-user-records.js index 963fd4a9..06d71f4a 100644 --- a/test/scripts/bulk-mailer/read-user-records.js +++ b/test/scripts/bulk-mailer/read-user-records.js @@ -2,37 +2,37 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const path = require('path') -const readUserRecords = require('../../../scripts/bulk-mailer/read-user-records') +const { assert } = require('chai'); +const path = require('path'); +const readUserRecords = require('../../../scripts/bulk-mailer/read-user-records'); describe('read-user-records', () => { it('throws if user records file not found', () => { return readUserRecords('not-found.json').then(assert.fail, err => { - assert.ok(/Cannot find module/.test(err.message)) - }) - }) + assert.ok(/Cannot find module/.test(err.message)); + }); + }); it('throws if user records file is empty', () => { return readUserRecords(path.resolve(__dirname, './fixtures/empty.json')) .then(assert.fail, err => { - assert.include(err.message, 'Unexpected end of JSON input') - }) - }) + assert.include(err.message, 'Unexpected end of JSON input'); + }); + }); it('throws if user records array is empty', () => { return readUserRecords(path.resolve(__dirname, './fixtures/empty-array.json')) .then(assert.fail, err => { - assert.equal(err.message, 'No records found') - }) - }) + assert.equal(err.message, 'No records found'); + }); + }); it('returns the records otherwise', () => { return readUserRecords(path.resolve(__dirname, './fixtures/good-input.json')) .then(records => { - assert.lengthOf(records, 2) - }) - }) -}) + assert.lengthOf(records, 2); + }); + }); +}); diff --git a/test/scripts/bulk-mailer/send-email-batch.js b/test/scripts/bulk-mailer/send-email-batch.js index bfb0f160..7704698f 100644 --- a/test/scripts/bulk-mailer/send-email-batch.js +++ b/test/scripts/bulk-mailer/send-email-batch.js @@ -2,44 +2,44 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const sendEmailBatch = require('../../../scripts/bulk-mailer/send-email-batch') -const sinon = require('sinon') +const { assert } = require('chai'); +const sendEmailBatch = require('../../../scripts/bulk-mailer/send-email-batch'); +const sinon = require('sinon'); describe('send-email-batch', () => { - const batch = ['a', 'b', 'c'] + const batch = ['a', 'b', 'c']; const sender = sinon.spy((userRecord) => { if (userRecord === 'c') { - return Promise.reject(new Error('problem sending')) + return Promise.reject(new Error('problem sending')); } else { - return Promise.resolve() + return Promise.resolve(); } - }) + }); const log = { error: sinon.spy(), info: sinon.spy() - } + }; before(() => { - return sendEmailBatch(batch, sender, log) - }) + return sendEmailBatch(batch, sender, log); + }); it('calls log as expected', () => { - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[0][0], 'send.success') - assert.equal(log.info.args[1][0], 'send.success') + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[0][0], 'send.success'); + assert.equal(log.info.args[1][0], 'send.success'); - assert.isTrue(log.error.calledOnce) - assert.equal(log.error.args[0][0], 'send.error') - }) + assert.isTrue(log.error.calledOnce); + assert.equal(log.error.args[0][0], 'send.error'); + }); it('calls the sender as expected', () => { - assert.equal(sender.callCount, 3) - assert.equal(sender.args[0][0], 'a') - assert.equal(sender.args[1][0], 'b') - assert.equal(sender.args[2][0], 'c') - }) -}) + assert.equal(sender.callCount, 3); + assert.equal(sender.args[0][0], 'a'); + assert.equal(sender.args[1][0], 'b'); + assert.equal(sender.args[2][0], 'c'); + }); +}); diff --git a/test/scripts/bulk-mailer/send-email-batches.js b/test/scripts/bulk-mailer/send-email-batches.js index e73c825d..7c3c4ea7 100644 --- a/test/scripts/bulk-mailer/send-email-batches.js +++ b/test/scripts/bulk-mailer/send-email-batches.js @@ -2,28 +2,28 @@ * 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/. */ -'use strict' +'use strict'; -const { assert } = require('chai') -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const { assert } = require('chai'); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); describe('send-email-batches', () => { const batches = [ ['a', 'b'], ['c', 'd'] - ] + ]; const log = { error: sinon.spy(), info: sinon.spy() - } - let sendEmailBatchSpy - const sender = {} + }; + let sendEmailBatchSpy; + const sender = {}; - let totalTimeMS + let totalTimeMS; - const DELAY_BETWEEN_BATCHES_MS = 100 + const DELAY_BETWEEN_BATCHES_MS = 100; before(() => { sendEmailBatchSpy = sinon.spy(function (batch) { @@ -31,49 +31,49 @@ describe('send-email-batches', () => { return Promise.resolve({ errorCount: 1, successCount: batch.length - 1, - }) + }); } else { return Promise.resolve({ errorCount: 0, successCount: batch.length, - }) + }); } - }) + }); const sendEmailBatches = proxyquire('../../../scripts/bulk-mailer/send-email-batches', { './send-email-batch': sendEmailBatchSpy - }) + }); - const startTime = Date.now() + const startTime = Date.now(); return sendEmailBatches(batches, DELAY_BETWEEN_BATCHES_MS, sender, log, false) .then(() => { - totalTimeMS = Date.now() - startTime - }) - }) + totalTimeMS = Date.now() - startTime; + }); + }); it('calls log as expected', () => { - assert.equal(log.info.callCount, 2) - assert.equal(log.info.args[0][0], 'send.begin') + assert.equal(log.info.callCount, 2); + assert.equal(log.info.args[0][0], 'send.begin'); - assert.equal(log.info.args[1][0], 'send.complete') - assert.equal(log.info.args[1][1].count, 4) - assert.equal(log.info.args[1][1].successCount, 3) - assert.equal(log.info.args[1][1].errorCount, 1) - assert.equal(log.info.args[1][1].unsentCount, 0) - }) + assert.equal(log.info.args[1][0], 'send.complete'); + assert.equal(log.info.args[1][1].count, 4); + assert.equal(log.info.args[1][1].successCount, 3); + assert.equal(log.info.args[1][1].errorCount, 1); + assert.equal(log.info.args[1][1].unsentCount, 0); + }); it('calls sendEmailBatchSpy as expected', () => { - assert.equal(sendEmailBatchSpy.callCount, 2) + assert.equal(sendEmailBatchSpy.callCount, 2); - assert.deepEqual(sendEmailBatchSpy.args[0][0], ['a', 'b']) - assert.strictEqual(sendEmailBatchSpy.args[0][1], sender) - assert.strictEqual(sendEmailBatchSpy.args[0][2], log) + assert.deepEqual(sendEmailBatchSpy.args[0][0], ['a', 'b']); + assert.strictEqual(sendEmailBatchSpy.args[0][1], sender); + assert.strictEqual(sendEmailBatchSpy.args[0][2], log); - assert.deepEqual(sendEmailBatchSpy.args[1][0], ['c', 'd']) - assert.strictEqual(sendEmailBatchSpy.args[1][1], sender) - }) + assert.deepEqual(sendEmailBatchSpy.args[1][0], ['c', 'd']); + assert.strictEqual(sendEmailBatchSpy.args[1][1], sender); + }); it('uses a delay between batches', () => { - assert.isAbove(totalTimeMS, 100) - }) -}) + assert.isAbove(totalTimeMS, 100); + }); +}); diff --git a/test/scripts/dump-users.js b/test/scripts/dump-users.js index bcfc6161..e6b89be6 100644 --- a/test/scripts/dump-users.js +++ b/test/scripts/dump-users.js @@ -2,27 +2,27 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' +const ROOT_DIR = '../..'; -const cp = require('child_process') -const { assert } = require('chai') -const path = require('path') -const P = require('bluebird') -const mocks = require(`${ROOT_DIR}/test/mocks`) +const cp = require('child_process'); +const { assert } = require('chai'); +const path = require('path'); +const P = require('bluebird'); +const mocks = require(`${ROOT_DIR}/test/mocks`); -const cwd = path.resolve(__dirname, ROOT_DIR) -cp.execAsync = P.promisify(cp.exec) +const cwd = path.resolve(__dirname, ROOT_DIR); +cp.execAsync = P.promisify(cp.exec); -const log = mocks.mockLog() -const config = require('../../config').getProperties() -const Token = require('../../lib/tokens')(log, config) -const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength) -const TestServer = require('../test_server') +const log = mocks.mockLog(); +const config = require('../../config').getProperties(); +const Token = require('../../lib/tokens')(log, config); +const UnblockCode = require('../../lib/crypto/random').base32(config.signinUnblock.codeLength); +const TestServer = require('../test_server'); -const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex') -const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex') +const zeroBuffer16 = Buffer.from('00000000000000000000000000000000', 'hex').toString('hex'); +const zeroBuffer32 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex').toString('hex'); function createAccount(email, uid) { return { @@ -36,181 +36,181 @@ function createAccount(email, uid) { kA: zeroBuffer32, wrapWrapKb: zeroBuffer32, tokenVerificationId: zeroBuffer16 - } + }; } -const account1Mock = createAccount('user1@test.com', 'f9916686c226415abd06ae550f073cec') -const account2Mock = createAccount('user2@test.com', 'f9916686c226415abd06ae550f073ced') +const account1Mock = createAccount('user1@test.com', 'f9916686c226415abd06ae550f073cec'); +const account2Mock = createAccount('user2@test.com', 'f9916686c226415abd06ae550f073ced'); const DB = require('../../lib/db')( config, log, Token, UnblockCode -) +); describe('scripts/dump-users', function () { - this.timeout(10000) + this.timeout(10000); - let db, server + let db, server; before(() => { return TestServer.start(config) .then(s => { - server = s - return DB.connect(config[config.db.backend]) + server = s; + return DB.connect(config[config.db.backend]); }) .then(_db => { db = _db; - return P.all([db.createAccount(account1Mock), db.createAccount(account2Mock)]) - }) - }) + return P.all([db.createAccount(account1Mock), db.createAccount(account2Mock)]); + }); + }); after(() => { return P.all([db.deleteAccount(account1Mock), db.deleteAccount(account2Mock)]) - .then(() => TestServer.stop(server)) - }) + .then(() => TestServer.stop(server)); + }); it('fails if neither --emails nor --uids is specified', () => { return cp.execAsync('node scripts/dump-users', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if both --emails nor --uids are specified', () => { return cp.execAsync('node scripts/dump-users --emails --uids', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --emails specified w/o list of emails or --input', () => { return cp.execAsync('node scripts/dump-users --emails', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --uids specified w/o list of uids or --input', () => { return cp.execAsync('node scripts/dump-users --uids', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('fails if --uids w/ invalid uid', () => { return cp.execAsync('node scripts/dump-users --uids deadbeef', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('succeeds with --uids and 1 valid uid1', () => { return cp.execAsync(`node scripts/dump-users --uids ${account1Mock.uid}`, { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 1) + const result = JSON.parse(output); + assert.lengthOf(result, 1); - assert.equal(result[0].email, account1Mock.email) - assert.equal(result[0].uid, account1Mock.uid) - }) - }) + assert.equal(result[0].email, account1Mock.email); + assert.equal(result[0].uid, account1Mock.uid); + }); + }); it('succeeds with --uids and 2 valid uids', () => { return cp.execAsync(`node scripts/dump-users --uids ${account1Mock.uid},${account2Mock.uid}`, { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 2) + const result = JSON.parse(output); + assert.lengthOf(result, 2); - assert.equal(result[0].email, account1Mock.email) - assert.equal(result[0].uid, account1Mock.uid) + assert.equal(result[0].email, account1Mock.email); + assert.equal(result[0].uid, account1Mock.uid); - assert.equal(result[1].email, account2Mock.email) - assert.equal(result[1].uid, account2Mock.uid) - }) - }) + assert.equal(result[1].email, account2Mock.email); + assert.equal(result[1].uid, account2Mock.uid); + }); + }); it('succeeds with --uids and --input containing 1 uid', () => { return cp.execAsync('node scripts/dump-users --uids --input ../test/scripts/fixtures/one_uid.txt', { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 1) + const result = JSON.parse(output); + assert.lengthOf(result, 1); - assert.equal(result[0].email, 'user1@test.com') - assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec') - }) - }) + assert.equal(result[0].email, 'user1@test.com'); + assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec'); + }); + }); it('succeeds with --uids and --input containing 2 uids', () => { return cp.execAsync('node scripts/dump-users --uids --input ../test/scripts/fixtures/two_uids.txt', { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 2) + const result = JSON.parse(output); + assert.lengthOf(result, 2); - assert.equal(result[0].email, 'user1@test.com') - assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec') + assert.equal(result[0].email, 'user1@test.com'); + assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec'); - assert.equal(result[1].email, 'user2@test.com') - assert.equal(result[1].uid, 'f9916686c226415abd06ae550f073ced') - }) - }) + assert.equal(result[1].email, 'user2@test.com'); + assert.equal(result[1].uid, 'f9916686c226415abd06ae550f073ced'); + }); + }); it('fails if --emails w/ invalid emails', () => { return cp.execAsync('node scripts/dump-users --emails user3@test.com', { cwd }) .then(() => assert(false, 'script should have failed'), (err) => { - assert.include(err.message, 'Command failed') - }) - }) + assert.include(err.message, 'Command failed'); + }); + }); it('succeeds with --emails and 1 valid email', () => { return cp.execAsync(`node scripts/dump-users --emails ${account1Mock.email}`, { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 1) + const result = JSON.parse(output); + assert.lengthOf(result, 1); - assert.equal(result[0].email, account1Mock.email) - assert.equal(result[0].uid, account1Mock.uid) - }) - }) + assert.equal(result[0].email, account1Mock.email); + assert.equal(result[0].uid, account1Mock.uid); + }); + }); it('succeeds with --emails and 2 valid emails', () => { return cp.execAsync(`node scripts/dump-users --emails ${account1Mock.email},${account2Mock.email}`, { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 2) + const result = JSON.parse(output); + assert.lengthOf(result, 2); - assert.equal(result[0].email, account1Mock.email) - assert.equal(result[0].uid, account1Mock.uid) + assert.equal(result[0].email, account1Mock.email); + assert.equal(result[0].uid, account1Mock.uid); - assert.equal(result[1].email, account2Mock.email) - assert.equal(result[1].uid, account2Mock.uid) - }) - }) + assert.equal(result[1].email, account2Mock.email); + assert.equal(result[1].uid, account2Mock.uid); + }); + }); it('succeeds with --emails and --input containing 1 email', () => { return cp.execAsync('node scripts/dump-users --emails --input ../test/scripts/fixtures/one_email.txt', { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 1) + const result = JSON.parse(output); + assert.lengthOf(result, 1); - assert.equal(result[0].email, 'user1@test.com') - assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec') - }) - }) + assert.equal(result[0].email, 'user1@test.com'); + assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec'); + }); + }); it('succeeds with --emails and --input containing 2 email', () => { return cp.execAsync('node scripts/dump-users --emails --input ../test/scripts/fixtures/two_emails.txt', { cwd }) .then(output => { - const result = JSON.parse(output) - assert.lengthOf(result, 2) + const result = JSON.parse(output); + assert.lengthOf(result, 2); - assert.equal(result[0].email, 'user1@test.com') - assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec') + assert.equal(result[0].email, 'user1@test.com'); + assert.equal(result[0].uid, 'f9916686c226415abd06ae550f073cec'); - assert.equal(result[1].email, 'user2@test.com') - assert.equal(result[1].uid, 'f9916686c226415abd06ae550f073ced') - }) - }) -}) + assert.equal(result[1].email, 'user2@test.com'); + assert.equal(result[1].uid, 'f9916686c226415abd06ae550f073ced'); + }); + }); +}); diff --git a/test/scripts/email-config.js b/test/scripts/email-config.js index e4750a69..288e05da 100644 --- a/test/scripts/email-config.js +++ b/test/scripts/email-config.js @@ -2,150 +2,150 @@ * 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/. */ -'use strict' +'use strict'; -const ROOT_DIR = '../..' -const LIB_DIR = `${ROOT_DIR}/lib` +const ROOT_DIR = '../..'; +const LIB_DIR = `${ROOT_DIR}/lib`; -const { assert } = require('chai') -const cp = require('child_process') -const mocks = require(`${ROOT_DIR}/test/mocks`) -const P = require('bluebird') -const path = require('path') +const { assert } = require('chai'); +const cp = require('child_process'); +const mocks = require(`${ROOT_DIR}/test/mocks`); +const P = require('bluebird'); +const path = require('path'); -cp.execAsync = P.promisify(cp.exec) +cp.execAsync = P.promisify(cp.exec); -const config = require(`${ROOT_DIR}/config`).getProperties() +const config = require(`${ROOT_DIR}/config`).getProperties(); const redis = require(`${LIB_DIR}/redis`)({ ...config.redis, ...config.redis.email -}, mocks.mockLog()) +}, mocks.mockLog()); -const cwd = path.resolve(__dirname, ROOT_DIR) +const cwd = path.resolve(__dirname, ROOT_DIR); const KEYS = { current: 'config', previous: 'config.previous' -} +}; describe('scripts/email-config:', () => { - let current, previous + let current, previous; beforeEach(() => { return redis.get(KEYS.current) .then(result => { - current = result - return redis.get(KEYS.previous) + current = result; + return redis.get(KEYS.previous); }) .then(result => { - previous = result - return redis.del(KEYS.current) + previous = result; + return redis.del(KEYS.current); }) - .then(() => redis.del(KEYS.previous)) - }) + .then(() => redis.del(KEYS.previous)); + }); afterEach(() => { return P.resolve() .then(() => { if (current) { - return redis.set(KEYS.current, current) + return redis.set(KEYS.current, current); } - return redis.del(KEYS.current) + return redis.del(KEYS.current); }) .then(() => { if (previous) { - return redis.set(KEYS.previous, previous) + return redis.set(KEYS.previous, previous); } - return redis.del(KEYS.previous) - }) - }) + return redis.del(KEYS.previous); + }); + }); it('read does not fail', () => { - return cp.execAsync('node scripts/email-config read', { cwd }) - }) + return cp.execAsync('node scripts/email-config read', { cwd }); + }); it('write does not fail', () => { - return cp.execAsync('echo \'{"sendgrid":{"percentage":100,"regex":".*"}}\' | node scripts/email-config write', { cwd }) - }) + return cp.execAsync('echo \'{"sendgrid":{"percentage":100,"regex":".*"}}\' | node scripts/email-config write', { cwd }); + }); it('write fails if stdin is not valid JSON', () => { return cp.execAsync('echo "wibble" | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if stdin is empty object', () => { return cp.execAsync('echo "{}" | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if stdin contains unexpected key', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":1,"regx":".*"}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if percentage is greater than 100', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":100.1,"regex":".*"}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if percentage is less than 0', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":-0.1,"regex":".*"}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if regex is not string', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":1,"regex":{}}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if regex contains quote character', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":1,"regex":".*\\""}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write fails if regex is unsafe', () => { return cp.execAsync('echo \'{"sendgrid":{"percentage":1,"regex":"(.*)*"}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write does not fail if percentage is missing', () => { - return cp.execAsync('echo \'{"sendgrid":{"regex":".*"}}\' | node scripts/email-config write', { cwd }) - }) + return cp.execAsync('echo \'{"sendgrid":{"regex":".*"}}\' | node scripts/email-config write', { cwd }); + }); it('write fails if service is invalid', () => { return cp.execAsync('echo \'{"sendgri":{"percentage":1,"regex":{}}}\' | node scripts/email-config write', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); it('write does not fail if regex is missing', () => { - return cp.execAsync('echo \'{"sendgrid":{"percentage":1}}\' | node scripts/email-config write', { cwd }) - }) + return cp.execAsync('echo \'{"sendgrid":{"percentage":1}}\' | node scripts/email-config write', { cwd }); + }); it('write does not fail if service is ses', () => { - return cp.execAsync('echo \'{"ses":{"percentage":1}}\' | node scripts/email-config write', { cwd }) - }) + return cp.execAsync('echo \'{"ses":{"percentage":1}}\' | node scripts/email-config write', { cwd }); + }); it('write does not fail if service is socketlabs', () => { - return cp.execAsync('echo \'{"socketlabs":{"percentage":1}}\' | node scripts/email-config write', { cwd }) - }) + return cp.execAsync('echo \'{"socketlabs":{"percentage":1}}\' | node scripts/email-config write', { cwd }); + }); it('revert does not fail', () => { - return cp.execAsync('node scripts/email-config revert', { cwd }) - }) + return cp.execAsync('node scripts/email-config revert', { cwd }); + }); it('check does not fail', () => { - return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }) - }) + return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }); + }); it('check fails without argument', () => { return cp.execAsync('node scripts/email-config check', { cwd }) - .then(() => assert(false, 'script should have failed'), () => {}) - }) + .then(() => assert(false, 'script should have failed'), () => {}); + }); describe('write config with regex:', () => { - let config + let config; beforeEach(() => { config = { @@ -153,126 +153,126 @@ describe('scripts/email-config:', () => { regex: 'foo', percentage: 42 } - } - return cp.execAsync(`echo '${JSON.stringify(config)}' | node scripts/email-config write`, { cwd }) - }) + }; + return cp.execAsync(`echo '${JSON.stringify(config)}' | node scripts/email-config write`, { cwd }); + }); it('read prints the config to stdout', () => { return cp.execAsync('node scripts/email-config read', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check matching email prints the config to stdout', () => { return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check non-matching email does not print', () => { return cp.execAsync('node scripts/email-config check bar@example.com', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, '{}\n') - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, '{}\n'); + assert.equal(stderr, undefined); + }); + }); describe('write config without regex:', () => { beforeEach(() => { config.socketlabs = { percentage: 10 - } - return cp.execAsync(`echo '${JSON.stringify(config)}' | node scripts/email-config write`, { cwd }) - }) + }; + return cp.execAsync(`echo '${JSON.stringify(config)}' | node scripts/email-config write`, { cwd }); + }); it('read prints the config to stdout', () => { return cp.execAsync('node scripts/email-config read', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check matching email prints the both configs to stdout', () => { return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, `${JSON.stringify(config, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check non-matching email prints one config to stdout', () => { return cp.execAsync('node scripts/email-config check bar@example.com', { cwd }) .then((stdout, stderr) => { const expected = { socketlabs: config.socketlabs - } - assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + }; + assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); describe('revert:', () => { beforeEach(() => { - return cp.execAsync('node scripts/email-config revert', { cwd }) - }) + return cp.execAsync('node scripts/email-config revert', { cwd }); + }); it('read prints the previous config to stdout', () => { return cp.execAsync('node scripts/email-config read', { cwd }) .then((stdout, stderr) => { const expected = { sendgrid: config.sendgrid - } - assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + }; + assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check matching email prints the previous config to stdout', () => { return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }) .then((stdout, stderr) => { const expected = { sendgrid: config.sendgrid - } - assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`) - assert.equal(stderr, undefined) - }) - }) + }; + assert.equal(stdout, `${JSON.stringify(expected, null, ' ')}\n`); + assert.equal(stderr, undefined); + }); + }); it('check non-matching email does not print', () => { return cp.execAsync('node scripts/email-config check bar@example.com', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, '{}\n') - assert.equal(stderr, undefined) - }) - }) - }) - }) + assert.equal(stdout, '{}\n'); + assert.equal(stderr, undefined); + }); + }); + }); + }); describe('revert:', () => { beforeEach(() => { - return cp.execAsync('node scripts/email-config revert', { cwd }) - }) + return cp.execAsync('node scripts/email-config revert', { cwd }); + }); it('read does not print', () => { return cp.execAsync('node scripts/email-config read', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, '') - assert.equal(stderr, undefined) - }) - }) + assert.equal(stdout, ''); + assert.equal(stderr, undefined); + }); + }); it('check matching email does not print', () => { return cp.execAsync('node scripts/email-config check foo@example.com', { cwd }) .then((stdout, stderr) => { - assert.equal(stdout, '') - assert.equal(stderr, undefined) - }) - }) - }) - }) -}) + assert.equal(stdout, ''); + assert.equal(stderr, undefined); + }); + }); + }); + }); +}); diff --git a/test/test_mailer_server.js b/test/test_mailer_server.js index 89445432..60d60556 100644 --- a/test/test_mailer_server.js +++ b/test/test_mailer_server.js @@ -2,30 +2,30 @@ * 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/. */ -'use strict' +'use strict'; -var createDBServer = require('fxa-auth-db-mysql') +var createDBServer = require('fxa-auth-db-mysql'); function TestServer(config) { - this.config = config - this.server = null + this.config = config; + this.server = null; } TestServer.start = function (config, printLogs) { return createDBServer().then( function (db) { - db.listen(config.httpdb.url.split(':')[2]) - db.on('error', function () {}) - var testServer = new TestServer(config) - testServer.db = db - return testServer + db.listen(config.httpdb.url.split(':')[2]); + db.on('error', function () {}); + var testServer = new TestServer(config); + testServer.db = db; + return testServer; } - ) -} + ); +}; TestServer.prototype.stop = function () { - try { this.db.close() } catch (e) {} -} + try { this.db.close(); } catch (e) {} +}; -module.exports = TestServer +module.exports = TestServer; diff --git a/test/test_server.js b/test/test_server.js index ecb1c7dc..946b7f7e 100644 --- a/test/test_server.js +++ b/test/test_server.js @@ -2,113 +2,113 @@ * 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/. */ -'use strict' +'use strict'; -var crypto = require('crypto') -const EventEmitter = require('events') -var P = require('../lib/promise') -var mailbox = require('./mailbox') -var createDBServer = require('fxa-auth-db-mysql') -var createAuthServer = require('../bin/key_server') -var createMailHelper = require('./mail_helper') -var createOauthHelper = require('./oauth_helper') +var crypto = require('crypto'); +const EventEmitter = require('events'); +var P = require('../lib/promise'); +var mailbox = require('./mailbox'); +var createDBServer = require('fxa-auth-db-mysql'); +var createAuthServer = require('../bin/key_server'); +var createMailHelper = require('./mail_helper'); +var createOauthHelper = require('./oauth_helper'); -let currentServer +let currentServer; /* eslint-disable no-console */ function TestServer(config, printLogs, options = {}) { - currentServer = this + currentServer = this; if (printLogs === undefined) { // Issue where debugger does not attach if // child process output is not piped to console - printLogs = (process.env.REMOTE_TEST_LOGS === 'true' || process.env.REMOTE_TEST_LOGS === '1') + printLogs = (process.env.REMOTE_TEST_LOGS === 'true' || process.env.REMOTE_TEST_LOGS === '1'); } if (printLogs) { - config.log.level = 'debug' + config.log.level = 'debug'; } else { - config.log.level = 'critical' - config.log.stdout = new EventEmitter() - config.log.stdout.write = function () {} + config.log.level = 'critical'; + config.log.stdout = new EventEmitter(); + config.log.stdout.write = function () {}; } - this.printLogs = printLogs - this.config = config - this.server = null - this.mail = null - this.oauth = options.oauthServer - this.mailbox = mailbox(config.smtp.api.host, config.smtp.api.port, this.printLogs) + this.printLogs = printLogs; + this.config = config; + this.server = null; + this.mail = null; + this.oauth = options.oauthServer; + this.mailbox = mailbox(config.smtp.api.host, config.smtp.api.port, this.printLogs); } TestServer.start = function (config, printLogs, options) { return TestServer.stop().then(() => { - return createDBServer() + return createDBServer(); }).then((db) => { - db.listen(config.httpdb.url.split(':')[2]) - db.on('error', function () {}) - var testServer = new TestServer(config, printLogs, options) - testServer.db = db - return testServer.start().then(() => testServer) - }) -} + db.listen(config.httpdb.url.split(':')[2]); + db.on('error', function () {}); + var testServer = new TestServer(config, printLogs, options); + testServer.db = db; + return testServer.start().then(() => testServer); + }); +}; TestServer.prototype.start = function () { const promises = [ createAuthServer(this.config), createMailHelper(this.printLogs) - ] + ]; if (this.config.oauth.url && ! this.oauth) { - promises.push(createOauthHelper()) + promises.push(createOauthHelper()); } return P.all(promises) .spread((auth, mail, oauth) => { - this.server = auth - this.mail = mail - this.oauth = oauth - }) -} + this.server = auth; + this.mail = mail; + this.oauth = oauth; + }); +}; TestServer.stop = function (maybeServer) { if (maybeServer) { - return maybeServer.stop() + return maybeServer.stop(); } else if (currentServer) { - return currentServer.stop() + return currentServer.stop(); } else { - return P.resolve() + return P.resolve(); } -} +}; TestServer.prototype.stop = function () { - currentServer = undefined - try { this.db.close() } catch (e) {} + currentServer = undefined; + try { this.db.close(); } catch (e) {} if (this.server) { const doomed = [ this.server.close(), this.mail.close() - ] + ]; if (this.oauth) { - doomed.push(this.oauth.close()) + doomed.push(this.oauth.close()); } - return P.all(doomed) + return P.all(doomed); } else { - return P.resolve() + return P.resolve(); } -} +}; TestServer.prototype.uniqueEmail = function (domain) { if (! domain) { - domain = '@restmail.net' + domain = '@restmail.net'; } - return crypto.randomBytes(10).toString('hex') + domain -} + return crypto.randomBytes(10).toString('hex') + domain; +}; TestServer.prototype.uniqueUnicodeEmail = function () { return crypto.randomBytes(10).toString('hex') + String.fromCharCode(1234) + '@' + String.fromCharCode(5678) + - 'restmail.net' -} + 'restmail.net'; +}; -module.exports = TestServer +module.exports = TestServer;