fix(server): hide session token lastAccessTime updates behind a flag
This commit is contained in:
Родитель
1c9c864033
Коммит
51d7cdd081
|
@ -5,12 +5,12 @@
|
|||
var config = require('../config').getProperties()
|
||||
var log = require('../lib/log')(config.log.level, 'fxa-email-bouncer')
|
||||
var error = require('../lib/error')
|
||||
var Token = require('../lib/tokens')(log, config.tokenLifetimes)
|
||||
var Token = require('../lib/tokens')(log, config)
|
||||
var SQSReceiver = require('../lib/sqs')(log)
|
||||
var bounces = require('../lib/bounces')(log, error)
|
||||
|
||||
var DB = require('../lib/db')(
|
||||
config.db.backend,
|
||||
config,
|
||||
log,
|
||||
error,
|
||||
Token.SessionToken,
|
||||
|
|
|
@ -34,7 +34,7 @@ function main() {
|
|||
}
|
||||
|
||||
var error = require('../lib/error')
|
||||
var Token = require('../lib/tokens')(log, config.tokenLifetimes)
|
||||
var Token = require('../lib/tokens')(log, config)
|
||||
var Password = require('../lib/crypto/password')(log, config)
|
||||
|
||||
var signer = require('../lib/signer')(config.secretKeyFile, config.domain)
|
||||
|
@ -79,7 +79,7 @@ function main() {
|
|||
mailer = m
|
||||
|
||||
var DB = require('../lib/db')(
|
||||
config.db.backend,
|
||||
config,
|
||||
log,
|
||||
error,
|
||||
Token.SessionToken,
|
||||
|
|
|
@ -433,11 +433,13 @@ var conf = convict({
|
|||
signinConfirmation: {
|
||||
enabled: {
|
||||
doc: 'enable signin confirmation',
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'SIGNIN_CONFIRMATION_ENABLED'
|
||||
},
|
||||
sample_rate: {
|
||||
doc: 'signin confirmation sample rate, between 0.0 and 1.0',
|
||||
format: Number,
|
||||
default: 1.0,
|
||||
env: 'SIGNIN_CONFIRMATION_RATE'
|
||||
},
|
||||
|
@ -461,7 +463,7 @@ var conf = convict({
|
|||
doc: 'If feature enabled, force sign-in confirmation for email addresses matching this regex.',
|
||||
format: Array,
|
||||
default: [
|
||||
'.+@mozilla\.com$'
|
||||
'.+@mozilla\\.com$'
|
||||
],
|
||||
env: 'SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX'
|
||||
}
|
||||
|
@ -472,6 +474,25 @@ var conf = convict({
|
|||
default: true,
|
||||
env: 'SECURITY_HISTORY_ENABLED'
|
||||
}
|
||||
},
|
||||
lastAccessTimeUpdates: {
|
||||
enabled: {
|
||||
doc: 'enable updates to the lastAccessTime session token property',
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'LASTACCESSTIME_UPDATES_ENABLED'
|
||||
},
|
||||
sampleRate: {
|
||||
doc: 'sample rate for updates to the lastAccessTime session token property, in the range 0..1',
|
||||
format: Number,
|
||||
default: 1,
|
||||
env: 'LASTACCESSTIME_UPDATES_SAMPLE_RATE'
|
||||
},
|
||||
enabledEmailAddresses: {
|
||||
doc: 'regex matching enabled email addresses for updates to the lastAccessTime session token property',
|
||||
default: '.+@mozilla\\.com$',
|
||||
env: 'LASTACCESSTIME_UPDATES_EMAIL_ADDRESSES'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
17
lib/db.js
17
lib/db.js
|
@ -12,7 +12,7 @@ var unbuffer = butil.unbuffer
|
|||
var bufferize = butil.bufferize
|
||||
|
||||
module.exports = function (
|
||||
backend,
|
||||
config,
|
||||
log,
|
||||
error,
|
||||
SessionToken,
|
||||
|
@ -21,6 +21,8 @@ module.exports = function (
|
|||
PasswordForgotToken,
|
||||
PasswordChangeToken) {
|
||||
|
||||
const features = require('./features')(config)
|
||||
|
||||
function DB(options) {
|
||||
this.pool = new Pool(options.url)
|
||||
}
|
||||
|
@ -435,7 +437,7 @@ module.exports = function (
|
|||
return bufferize({
|
||||
id: item.id,
|
||||
sessionToken: item.sessionTokenId,
|
||||
lastAccessTime: item.lastAccessTime,
|
||||
lastAccessTime: marshallLastAccessTime(item.lastAccessTime, uid, item.email),
|
||||
name: item.name,
|
||||
type: item.type,
|
||||
pushCallback: item.callbackURL,
|
||||
|
@ -463,6 +465,17 @@ module.exports = function (
|
|||
)
|
||||
}
|
||||
|
||||
function marshallLastAccessTime (lastAccessTime, uid, email) {
|
||||
// Updating lastAccessTime on session tokens may not be enabled.
|
||||
// If it isn't, return null instead of the timestamp so that the
|
||||
// content server knows not to display it to users.
|
||||
if (features.isLastAccessTimeEnabledForUser(uid, email)) {
|
||||
return lastAccessTime
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
DB.prototype.sessionWithDevice = function (id) {
|
||||
log.trace({ op: 'DB.sessionWithDevice', id: id })
|
||||
return this.pool.get('/sessionToken/' + id.toString('hex') + '/device')
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/* 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'
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
module.exports = config => {
|
||||
const lastAccessTimeUpdates = config.lastAccessTimeUpdates
|
||||
const signinConfirmation = config.signinConfirmation
|
||||
|
||||
return {
|
||||
/**
|
||||
* Predicate that indicates whether updates to sessionToken.lastAccessTime
|
||||
* are enabled for a given user, based on their uid and email address.
|
||||
*
|
||||
* @param uid Buffer or String
|
||||
* @param email String
|
||||
*/
|
||||
isLastAccessTimeEnabledForUser (uid, email) {
|
||||
return lastAccessTimeUpdates.enabled && (
|
||||
isSampledUser(lastAccessTimeUpdates.sampleRate, uid, 'lastAccessTimeUpdates') ||
|
||||
new RegExp(lastAccessTimeUpdates.enabledEmailAddresses).test(email)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Predicate that indicates whether sign-in confirmation is enabled
|
||||
* for a given user, based on their uid and email address.
|
||||
*
|
||||
* @param uid Buffer or String
|
||||
* @param email String
|
||||
*/
|
||||
isSigninConfirmationEnabledForUser (uid, email, request) {
|
||||
if (! signinConfirmation.enabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Always create unverified tokens if customs-server
|
||||
// has said the request is suspicious.
|
||||
if (request.app.isSuspiciousRequest) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Or if the email address matches one of these regexes.
|
||||
if (signinConfirmation.forceEmailRegex.some(regex => new RegExp(regex).test(email))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// While we're testing this feature, there may be some funky
|
||||
// edge-cases in device login flows that haven't been fully tested.
|
||||
// Temporarily avoid them for regular users by checking the `context` flag,
|
||||
// and create pre-verified sessions for unsupported clients.
|
||||
// This check will go away in the final version of this feature.
|
||||
const context = request.payload &&
|
||||
request.payload.metricsContext &&
|
||||
request.payload.metricsContext.context
|
||||
if (signinConfirmation.supportedClients.indexOf(context) === -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check to see if user in roll-out cohort.
|
||||
return isSampledUser(signinConfirmation.sample_rate, uid, 'signinConfirmation')
|
||||
},
|
||||
|
||||
/**
|
||||
* Predicate that indicates whether a user belongs to the sampled cohort,
|
||||
* based on a sample rate, their uid and a key that identifies the feature.
|
||||
*
|
||||
* @param sampleRate Number in the range 0..1
|
||||
* @param uid Buffer or String
|
||||
* @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)
|
||||
|
||||
function isSampledUser (sampleRate, uid, key) {
|
||||
if (sampleRate === 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (sampleRate === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(uid)) {
|
||||
uid = uid.toString('hex')
|
||||
}
|
||||
|
||||
// Extract the maximum entropy we can safely handle as a number then reduce
|
||||
// it to a value between 0 and 1 for comparison against the sample rate.
|
||||
const cohort = parseInt(
|
||||
hash(uid, key).substr(HASH_LENGTH - MAX_SAFE_HEX_LENGTH),
|
||||
16
|
||||
) / 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')
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ module.exports = function (
|
|||
supportedLanguages: config.i18n.supportedLanguages,
|
||||
defaultLanguage: config.i18n.defaultLanguage
|
||||
})
|
||||
const features = require('../features')(config)
|
||||
|
||||
var securityHistoryEnabled = config.securityHistory && config.securityHistory.enabled
|
||||
|
||||
|
@ -211,7 +212,8 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function createSessionToken () {
|
||||
var enableTokenVerification = requestHelper.shouldEnableTokenVerification(account, config, request)
|
||||
const enableTokenVerification =
|
||||
features.isSigninConfirmationEnabledForUser(account.uid, account.email, request)
|
||||
|
||||
// Verified sessions should only be created for preverified tokens
|
||||
// and when sign-in confirmation is disabled or not needed.
|
||||
|
@ -419,7 +421,7 @@ module.exports = function (
|
|||
// * the request wants keys, since unverified sessions are fine to use for e.g. oauth login.
|
||||
// * the email is verified, since content-server triggers a resend of the verification
|
||||
// email on unverified accounts, which doubles as sign-in confirmation.
|
||||
if (! requestHelper.shouldEnableTokenVerification(emailRecord, config, request)) {
|
||||
if (! features.isSigninConfirmationEnabledForUser(emailRecord.uid, emailRecord.email, request)) {
|
||||
tokenVerificationId = undefined
|
||||
mustVerifySession = false
|
||||
doSigninConfirmation = false
|
||||
|
|
|
@ -12,56 +12,6 @@ function wantsKeys (request) {
|
|||
return request.query && request.query.keys === 'true'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to use token-verification feature on a request.
|
||||
*
|
||||
* @param account
|
||||
* @param config
|
||||
* @param request
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldEnableTokenVerification(account, config, request) {
|
||||
|
||||
var confirmLogin = config.signinConfirmation && config.signinConfirmation.enabled
|
||||
if (!confirmLogin) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Always create unverified tokens if customs-server
|
||||
// has said the request is suspicious.
|
||||
if (request.app.isSuspiciousRequest) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Or if the email address matching one of these regexes.
|
||||
var email = account.email
|
||||
var isValidEmail = config.signinConfirmation.forceEmailRegex.some(function (reg) {
|
||||
var emailReg = new RegExp(reg)
|
||||
return emailReg.test(email)
|
||||
})
|
||||
|
||||
if (isValidEmail) {
|
||||
return true
|
||||
}
|
||||
|
||||
// While we're testing this feature, there may be some funky
|
||||
// edge-cases in device login flows that haven't been fully tested.
|
||||
// Temporarily avoid them for regular users by checking the `context` flag,
|
||||
// and create pre-verified sessions for unsupported clients.
|
||||
// This check will go away in the final version of this feature.
|
||||
var context = request.payload && request.payload.metricsContext && request.payload.metricsContext.context
|
||||
var isValidContext = context && (config.signinConfirmation.supportedClients.indexOf(context) > -1)
|
||||
if (!isValidContext) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check to see if user in roll-out cohort.
|
||||
// Cohort is determined by user's uid.
|
||||
var uid = account.uid.toString('hex')
|
||||
var uidNum = parseInt(uid.substr(0, 4), 16) % 100
|
||||
return uidNum < (config.signinConfirmation.sample_rate * 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to send the verify account email on a login
|
||||
* attempt. This never sends a verification email to an already verified email.
|
||||
|
@ -82,6 +32,5 @@ function shouldSendVerifyAccountEmail(account, request) {
|
|||
|
||||
module.exports = {
|
||||
wantsKeys: wantsKeys,
|
||||
shouldEnableTokenVerification: shouldEnableTokenVerification,
|
||||
shouldSendVerifyAccountEmail: shouldSendVerifyAccountEmail
|
||||
}
|
||||
|
|
|
@ -2,34 +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/. */
|
||||
|
||||
var crypto = require('crypto')
|
||||
var inherits = require('util').inherits
|
||||
const crypto = require('crypto')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
var P = require('../promise')
|
||||
var hkdf = require('../crypto/hkdf')
|
||||
var butil = require('../crypto/butil')
|
||||
const P = require('../promise')
|
||||
const hkdf = require('../crypto/hkdf')
|
||||
const butil = require('../crypto/butil')
|
||||
|
||||
var error = require('../error')
|
||||
const error = require('../error')
|
||||
|
||||
module.exports = function (log, lifetimes) {
|
||||
lifetimes = lifetimes || {
|
||||
module.exports = (log, config) => {
|
||||
config = config || {}
|
||||
const lifetimes = config.tokenLifetimes || {
|
||||
accountResetToken: 1000 * 60 * 15,
|
||||
passwordChangeToken: 1000 * 60 * 15,
|
||||
passwordForgotToken: 1000 * 60 * 15
|
||||
}
|
||||
var Bundle = require('./bundle')(crypto, P, hkdf, butil, error)
|
||||
var Token = require('./token')(log, crypto, P, hkdf, Bundle, error)
|
||||
const Bundle = require('./bundle')(crypto, P, hkdf, butil, error)
|
||||
const Token = require('./token')(log, crypto, P, hkdf, Bundle, error)
|
||||
|
||||
var KeyFetchToken = require('./key_fetch_token')(log, inherits, Token, P, error)
|
||||
var AccountResetToken = require('./account_reset_token')(
|
||||
const KeyFetchToken = require('./key_fetch_token')(log, inherits, Token, P, error)
|
||||
const AccountResetToken = require('./account_reset_token')(
|
||||
log,
|
||||
inherits,
|
||||
Token,
|
||||
crypto,
|
||||
lifetimes.accountResetToken
|
||||
)
|
||||
var SessionToken = require('./session_token')(log, inherits, Token)
|
||||
var PasswordForgotToken = require('./password_forgot_token')(
|
||||
const SessionToken = require('./session_token')(log, inherits, Token, config)
|
||||
const PasswordForgotToken = require('./password_forgot_token')(
|
||||
log,
|
||||
inherits,
|
||||
Token,
|
||||
|
@ -37,7 +38,7 @@ module.exports = function (log, lifetimes) {
|
|||
lifetimes.passwordForgotToken
|
||||
)
|
||||
|
||||
var PasswordChangeToken = require('./password_change_token')(
|
||||
const PasswordChangeToken = require('./password_change_token')(
|
||||
log,
|
||||
inherits,
|
||||
Token,
|
||||
|
|
|
@ -10,7 +10,8 @@ var ONE_HOUR = 60 * 60 * 1000
|
|||
// See https://github.com/mozilla/fxa-auth-server/pull/1169
|
||||
var TOKEN_FRESHNESS_THRESHOLD = 50 * 365 * 24 * ONE_HOUR // 50 years or post Y2038 ;-)
|
||||
|
||||
module.exports = function (log, inherits, Token) {
|
||||
module.exports = (log, inherits, Token, config) => {
|
||||
const features = require('../features')(config)
|
||||
|
||||
function SessionToken(keys, details) {
|
||||
Token.call(this, keys, details)
|
||||
|
@ -82,7 +83,10 @@ module.exports = function (log, inherits, Token) {
|
|||
this.uaOS === freshData.uaOS &&
|
||||
this.uaOSVersion === freshData.uaOSVersion &&
|
||||
this.uaDeviceType === freshData.uaDeviceType &&
|
||||
this.lastAccessTime + TOKEN_FRESHNESS_THRESHOLD > freshData.lastAccessTime
|
||||
(
|
||||
! features.isLastAccessTimeEnabledForUser(this.uid, this.email) ||
|
||||
this.lastAccessTime + TOKEN_FRESHNESS_THRESHOLD > freshData.lastAccessTime
|
||||
)
|
||||
|
||||
log.info({
|
||||
op: 'SessionToken.isFresh',
|
||||
|
|
|
@ -7,7 +7,7 @@ const crypto = require('crypto')
|
|||
const commander = require('commander')
|
||||
|
||||
const P = require('../../lib/promise')
|
||||
const Client = require('../../test/client')
|
||||
const Client = require('../../test/client')()
|
||||
const mailbox = require('../../test/mailbox')
|
||||
const validateEmail = require('./validate-email')
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ var error = require('../lib/error')
|
|||
var log = require('../lib/log')(config.log.level)
|
||||
var P = require('../lib/promise')
|
||||
var path = require('path')
|
||||
var Token = require('../lib/tokens')(log, config.tokenLifetimes)
|
||||
var Token = require('../lib/tokens')(log, config)
|
||||
|
||||
commandLineOptions
|
||||
.option('-i, --input <filename>', 'JSON input file')
|
||||
|
@ -37,7 +37,7 @@ requiredOptions.forEach(checkRequiredOption)
|
|||
|
||||
|
||||
var DB = require('../lib/db')(
|
||||
config.db.backend,
|
||||
config,
|
||||
log,
|
||||
error,
|
||||
Token.SessionToken,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-disable no-console */
|
||||
var Client = require('../client')
|
||||
var Client = require('../client')()
|
||||
|
||||
var config = {
|
||||
origin: 'http://127.0.0.1:9000',
|
||||
|
|
1078
test/client/api.js
1078
test/client/api.js
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -2,286 +2,287 @@
|
|||
* 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/. */
|
||||
|
||||
var P = require('../../lib/promise')
|
||||
var ClientApi = require('./api')
|
||||
var butil = require('../../lib/crypto/butil')
|
||||
var pbkdf2 = require('../../lib/crypto/pbkdf2')
|
||||
var hkdf = require('../../lib/crypto/hkdf')
|
||||
var tokens = require('../../lib/tokens')({ trace: function () {}})
|
||||
module.exports = config => {
|
||||
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.options = {}
|
||||
}
|
||||
|
||||
Client.Api = ClientApi
|
||||
|
||||
Client.prototype.setupCredentials = function (email, password) {
|
||||
this.email = email
|
||||
return pbkdf2.derive(Buffer(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)
|
||||
}.bind(this)
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (unwrapBKey) {
|
||||
this.unwrapBKey = unwrapBKey
|
||||
return this
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.create = function (origin, email, password, options) {
|
||||
var c = new Client(origin)
|
||||
c.options = options || {}
|
||||
|
||||
return c.setupCredentials(email, password)
|
||||
.then(
|
||||
function() {
|
||||
return c.create()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.login = function (origin, email, password, opts) {
|
||||
var c = new Client(origin)
|
||||
|
||||
return c.setupCredentials(email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
return c.auth(opts)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.changePassword = function (origin, email, oldPassword, newPassword, headers) {
|
||||
var c = new Client(origin)
|
||||
|
||||
return c.setupCredentials(email, oldPassword)
|
||||
.then(
|
||||
function () {
|
||||
return c.changePassword(newPassword, headers)
|
||||
.then(
|
||||
function () {
|
||||
return c
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.createAndVerify = function (origin, email, password, mailbox, options) {
|
||||
return Client.create(origin, email, password, options)
|
||||
.then(
|
||||
function (client) {
|
||||
return mailbox.waitForCode(email)
|
||||
.then(
|
||||
function (code) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.loginAndVerify = function (origin, email, password, mailbox, options) {
|
||||
if (! options ) {
|
||||
options = {}
|
||||
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.options = {}
|
||||
}
|
||||
|
||||
options.keys = options.keys || true
|
||||
Client.Api = ClientApi
|
||||
|
||||
return Client.login(origin, email, password, options)
|
||||
.then(
|
||||
function (client) {
|
||||
return mailbox.waitForCode(email)
|
||||
.then(
|
||||
function (code) {
|
||||
return client.verifyEmail(code, options)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.create = function () {
|
||||
return this.api.accountCreate(
|
||||
this.email,
|
||||
this.authPW,
|
||||
this.options
|
||||
)
|
||||
.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
|
||||
}.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
|
||||
}
|
||||
|
||||
Client.prototype.stringify = function () {
|
||||
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
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.login = function (opts) {
|
||||
return this.auth(opts)
|
||||
}
|
||||
|
||||
Client.prototype.destroySession = function () {
|
||||
var p = P.resolve(null)
|
||||
if (this.sessionToken) {
|
||||
p = this.api.sessionDestroy(this.sessionToken)
|
||||
Client.prototype.setupCredentials = function (email, password) {
|
||||
this.email = email
|
||||
return pbkdf2.derive(Buffer(password), hkdf.KWE('quickStretch', email), 1000, 32)
|
||||
.then(
|
||||
function () {
|
||||
this.sessionToken = null
|
||||
return {}
|
||||
function (stretch) {
|
||||
return hkdf(stretch, 'authPW', null, 32)
|
||||
.then(
|
||||
function (authPW) {
|
||||
this.authPW = authPW
|
||||
return hkdf(stretch, 'unwrapBKey', null, 32)
|
||||
}.bind(this)
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (unwrapBKey) {
|
||||
this.unwrapBKey = unwrapBKey
|
||||
return this
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
Client.prototype.verifyEmail = function (code, options) {
|
||||
return this.api.recoveryEmailVerifyCode(this.uid, code, options)
|
||||
}
|
||||
Client.create = function (origin, email, password, options) {
|
||||
var c = new Client(origin)
|
||||
c.options = options || {}
|
||||
|
||||
Client.prototype.emailStatus = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
return c.setupCredentials(email, password)
|
||||
.then(
|
||||
function() {
|
||||
return c.create()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.login = function (origin, email, password, opts) {
|
||||
var c = new Client(origin)
|
||||
|
||||
return c.setupCredentials(email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
return c.auth(opts)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.changePassword = function (origin, email, oldPassword, newPassword, headers) {
|
||||
var c = new Client(origin)
|
||||
|
||||
return c.setupCredentials(email, oldPassword)
|
||||
.then(
|
||||
function () {
|
||||
return c.changePassword(newPassword, headers)
|
||||
.then(
|
||||
function () {
|
||||
return c
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.createAndVerify = function (origin, email, password, mailbox, options) {
|
||||
return Client.create(origin, email, password, options)
|
||||
.then(
|
||||
function (client) {
|
||||
return mailbox.waitForCode(email)
|
||||
.then(
|
||||
function (code) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.loginAndVerify = function (origin, email, password, mailbox, options) {
|
||||
if (! options ) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
options.keys = options.keys || true
|
||||
|
||||
return Client.login(origin, email, password, options)
|
||||
.then(
|
||||
function (client) {
|
||||
return mailbox.waitForCode(email)
|
||||
.then(
|
||||
function (code) {
|
||||
return client.verifyEmail(code, options)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.create = function () {
|
||||
return this.api.accountCreate(
|
||||
this.email,
|
||||
this.authPW,
|
||||
this.options
|
||||
)
|
||||
.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
|
||||
}.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
|
||||
}
|
||||
|
||||
Client.prototype.stringify = function () {
|
||||
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
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.login = function (opts) {
|
||||
return this.auth(opts)
|
||||
}
|
||||
|
||||
Client.prototype.destroySession = function () {
|
||||
var p = P.resolve(null)
|
||||
if (this.sessionToken) {
|
||||
p = this.api.sessionDestroy(this.sessionToken)
|
||||
.then(
|
||||
function () {
|
||||
this.sessionToken = null
|
||||
return {}
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
Client.prototype.verifyEmail = function (code, options) {
|
||||
return this.api.recoveryEmailVerifyCode(this.uid, code, options)
|
||||
}
|
||||
|
||||
Client.prototype.emailStatus = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.recoveryEmailStatus(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Client.prototype.requestVerifyEmail = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.recoveryEmailResendCode(this.sessionToken, this.options)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
Client.prototype.requestVerifyEmail = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
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()
|
||||
return o.then(
|
||||
Client.prototype.sign = function (publicKey, duration, locale, options) {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.certificateSign(this.sessionToken, publicKey, duration, locale, options)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
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()
|
||||
}.bind(this)
|
||||
function (x) {
|
||||
return x.cert
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (/* keys */) {
|
||||
return this.setupCredentials(this.email, newPassword)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
this.wrapKb = butil.xorBuffers(this.kB, this.unwrapBKey)
|
||||
return this.api.passwordChangeFinish(this.passwordChangeToken, this.authPW, this.wrapKb, headers, sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (res) {
|
||||
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
|
||||
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()
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (/* keys */) {
|
||||
return this.setupCredentials(this.email, newPassword)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
this.wrapKb = butil.xorBuffers(this.kB, this.unwrapBKey)
|
||||
return this.api.passwordChangeFinish(this.passwordChangeToken, this.authPW, this.wrapKb, headers, sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (res) {
|
||||
this._clear()
|
||||
|
||||
return res
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
// 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
|
||||
|
||||
Client.prototype.keys = function () {
|
||||
var o = this.keyFetchToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
return res
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.keys = function () {
|
||||
var o = this.keyFetchToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountKeys(this.keyFetchToken)
|
||||
}.bind(this)
|
||||
|
@ -309,136 +310,135 @@ Client.prototype.keys = function () {
|
|||
throw err
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Client.prototype.devices = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountDevices(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
Client.prototype.devices = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountDevices(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.updateDevice = function (info) {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountDevice(this.sessionToken, info)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (device) {
|
||||
if (! this.device || this.device.id === device.id) {
|
||||
this.device = device
|
||||
}
|
||||
return device
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
Client.prototype.updateDevice = function (info) {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountDevice(this.sessionToken, info)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function (device) {
|
||||
if (! this.device || this.device.id === device.id) {
|
||||
this.device = device
|
||||
}
|
||||
return device
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.destroyDevice = function (id) {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.deviceDestroy(this.sessionToken, id)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
delete this.sessionToken
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
Client.prototype.destroyDevice = function (id) {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.deviceDestroy(this.sessionToken, id)
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
delete this.sessionToken
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.sessionStatus = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
Client.prototype.sessionStatus = function () {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.sessionStatus(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.accountProfile = function (oauthToken) {
|
||||
if (oauthToken) {
|
||||
return this.api.accountProfile(null, { Authorization: 'Bearer ' + oauthToken })
|
||||
} else {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountProfile(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Client.prototype.destroyAccount = function () {
|
||||
return this.api.accountDestroy(this.email, this.authPW)
|
||||
.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
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.reforgotPassword = function () {
|
||||
return this.api.passwordForgotResendCode(this.passwordForgotToken, this.email)
|
||||
}
|
||||
|
||||
Client.prototype.verifyPasswordResetCode = function (code, headers) {
|
||||
return this.api.passwordForgotVerifyCode(this.passwordForgotToken, code, headers)
|
||||
.then(
|
||||
function (result) {
|
||||
this.accountResetToken = result.accountResetToken
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.lockAccount = function () {
|
||||
return this.api.accountLock(this.email, this.authPW)
|
||||
}
|
||||
|
||||
Client.prototype.resendAccountUnlockCode = function (lang) {
|
||||
return this.api.accountUnlockResendCode(this.email, this.options, lang)
|
||||
}
|
||||
|
||||
Client.prototype.verifyAccountUnlockCode = function (uid, code) {
|
||||
return this.api.accountUnlockVerifyCode(uid, code)
|
||||
}
|
||||
|
||||
Client.prototype.resetPassword = function (newPassword, headers, options) {
|
||||
if (!this.accountResetToken) {
|
||||
throw new Error('call verifyPasswordResetCode before calling resetPassword')
|
||||
Client.prototype.accountProfile = function (oauthToken) {
|
||||
if (oauthToken) {
|
||||
return this.api.accountProfile(null, { Authorization: 'Bearer ' + oauthToken })
|
||||
} else {
|
||||
var o = this.sessionToken ? P.resolve(null) : this.login()
|
||||
return o.then(
|
||||
function () {
|
||||
return this.api.accountProfile(this.sessionToken)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
// this will generate a new wrapKb on the server
|
||||
return this.setupCredentials(this.email, newPassword)
|
||||
.then(
|
||||
function (/* bundle */) {
|
||||
return this.api.accountReset(
|
||||
this.accountResetToken,
|
||||
this.authPW,
|
||||
headers,
|
||||
options
|
||||
)
|
||||
.then(function (response) {
|
||||
// Update to the new verified tokens
|
||||
this.sessionToken = response.sessionToken
|
||||
this.keyFetchToken = response.keyFetchToken
|
||||
|
||||
return response
|
||||
})
|
||||
Client.prototype.destroyAccount = function () {
|
||||
return this.api.accountDestroy(this.email, this.authPW)
|
||||
.then(this._clear.bind(this))
|
||||
}
|
||||
|
||||
}.bind(this)
|
||||
)
|
||||
Client.prototype.forgotPassword = function (lang) {
|
||||
|
||||
return this.api.passwordForgotSendCode(this.email, this.options, lang)
|
||||
.then(
|
||||
function (x) {
|
||||
this.passwordForgotToken = x.passwordForgotToken
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.reforgotPassword = function () {
|
||||
return this.api.passwordForgotResendCode(this.passwordForgotToken, this.email)
|
||||
}
|
||||
|
||||
Client.prototype.verifyPasswordResetCode = function (code, headers) {
|
||||
return this.api.passwordForgotVerifyCode(this.passwordForgotToken, code, headers)
|
||||
.then(
|
||||
function (result) {
|
||||
this.accountResetToken = result.accountResetToken
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.lockAccount = function () {
|
||||
return this.api.accountLock(this.email, this.authPW)
|
||||
}
|
||||
|
||||
Client.prototype.resendAccountUnlockCode = function (lang) {
|
||||
return this.api.accountUnlockResendCode(this.email, this.options, lang)
|
||||
}
|
||||
|
||||
Client.prototype.verifyAccountUnlockCode = function (uid, code) {
|
||||
return this.api.accountUnlockVerifyCode(uid, code)
|
||||
}
|
||||
|
||||
Client.prototype.resetPassword = function (newPassword, headers, options) {
|
||||
if (!this.accountResetToken) {
|
||||
throw new Error('call verifyPasswordResetCode before calling resetPassword')
|
||||
}
|
||||
// this will generate a new wrapKb on the server
|
||||
return this.setupCredentials(this.email, newPassword)
|
||||
.then(
|
||||
function (/* bundle */) {
|
||||
return this.api.accountReset(
|
||||
this.accountResetToken,
|
||||
this.authPW,
|
||||
headers,
|
||||
options
|
||||
)
|
||||
.then(function (response) {
|
||||
// Update to the new verified tokens
|
||||
this.sessionToken = response.sessionToken
|
||||
this.keyFetchToken = response.keyFetchToken
|
||||
|
||||
return response
|
||||
})
|
||||
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
return Client
|
||||
}
|
||||
|
||||
//TODO recovery methods, session status/destroy
|
||||
|
||||
module.exports = Client
|
||||
|
|
|
@ -34,6 +34,8 @@ var makeRoutes = function (options, requireMocks) {
|
|||
supportedLanguages: ['en'],
|
||||
defaultLanguage: 'en'
|
||||
}
|
||||
config.lastAccessTimeUpdates = {}
|
||||
config.signinConfirmation = config.signinConfirmation || {}
|
||||
|
||||
var log = options.log || mocks.mockLog()
|
||||
var Password = options.Password || require('../../lib/crypto/password')(log, config)
|
||||
|
@ -709,7 +711,8 @@ test('/account/login', function (t) {
|
|||
newLoginNotificationEnabled: true,
|
||||
securityHistory: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
signinConfirmation: {}
|
||||
}
|
||||
var mockRequest = mocks.mockRequest({
|
||||
query: {
|
||||
|
@ -914,12 +917,10 @@ test('/account/login', function (t) {
|
|||
})
|
||||
|
||||
t.test('sign-in confirmation enabled', function (t) {
|
||||
t.plan(13)
|
||||
config.signinConfirmation = {
|
||||
enabled: true,
|
||||
supportedClients: [ 'fx_desktop_v3' ],
|
||||
forceEmailRegex: [ '.+@mozilla\.com$', 'fennec@fire.fox' ]
|
||||
}
|
||||
t.plan(10)
|
||||
config.signinConfirmation.enabled = true
|
||||
config.signinConfirmation.supportedClients = [ 'fx_desktop_v3' ]
|
||||
config.signinConfirmation.forceEmailRegex = [ '.+@mozilla\\.com$', 'fennec@fire.fox' ]
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
|
@ -945,7 +946,6 @@ test('/account/login', function (t) {
|
|||
t.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified')
|
||||
t.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called')
|
||||
t.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called')
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called')
|
||||
t.notOk(response.verified, 'response indicates account is not verified')
|
||||
t.equal(response.verificationMethod, 'email', 'verificationMethod is email')
|
||||
t.equal(response.verificationReason, 'login', 'verificationReason is login')
|
||||
|
@ -959,15 +959,7 @@ test('/account/login', function (t) {
|
|||
t.deepEqual(args[1], 'account.confirmed', 'second argument was event name')
|
||||
t.equal(args[2], mockRequest.payload.metricsContext, 'third argument was metrics context')
|
||||
t.deepEqual(mockMetricsContext.stash.args[2][1], 'account.keyfetch', 'third call was for account.keyfetch')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('location data is present in sign-in confirmation email', function (t) {
|
||||
return runTest(route, mockRequest, function (response) {
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called')
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.city, 'Mountain View')
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.getCall(0).args[2].location.country, 'United States')
|
||||
|
@ -975,48 +967,7 @@ test('/account/login', function (t) {
|
|||
}).then(function () {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('on for sample', function (t) {
|
||||
// Force uid to '01...'
|
||||
uid.fill(0, 0, 1)
|
||||
uid.fill(1, 1, 2)
|
||||
config.signinConfirmation.sample_rate = 0.02
|
||||
|
||||
return runTest(route, mockRequest, function (response) {
|
||||
t.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called')
|
||||
var tokenData = mockDB.createSessionToken.getCall(0).args[0]
|
||||
t.ok(tokenData.mustVerify, 'sessionToken must be verified before use')
|
||||
t.ok(tokenData.tokenVerificationId, 'sessionToken was created unverified')
|
||||
t.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called')
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called')
|
||||
t.notOk(response.verified, 'response indicates account is not verified')
|
||||
t.equal(response.verificationMethod, 'email', 'verificationMethod is email')
|
||||
t.equal(response.verificationReason, 'login', 'verificationReason is login')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('off for sample', function (t) {
|
||||
config.signinConfirmation.sample_rate = 0.01
|
||||
|
||||
return runTest(route, mockRequest, function (response) {
|
||||
t.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called')
|
||||
var tokenData = mockDB.createSessionToken.getCall(0).args[0]
|
||||
t.notOk(tokenData.mustVerify, 'mustVerify was not set')
|
||||
t.notOk(tokenData.tokenVerificationId, 'sessionToken was created verified')
|
||||
t.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyCode was not called')
|
||||
t.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called')
|
||||
t.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called')
|
||||
t.ok(response.verified, 'response indicates account is verified')
|
||||
t.notOk(response.verificationMethod, 'verificationMethod doesn\'t exist')
|
||||
t.notOk(response.verificationReason, 'verificationReason doesn\'t exist')
|
||||
}).then(function () {
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1120,6 +1071,7 @@ test('/account/login', function (t) {
|
|||
})
|
||||
|
||||
t.test('off for email regex mismatch', function (t) {
|
||||
config.signinConfirmation.sample_rate = 0
|
||||
mockRequest.payload.email = 'moz@fire.fox'
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/* 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'
|
||||
|
||||
const test = require('../ptaptest')
|
||||
const sinon = require('sinon')
|
||||
const proxyquire = require('proxyquire')
|
||||
|
||||
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: {}
|
||||
}
|
||||
|
||||
const features = proxyquire('../../lib/features', {
|
||||
crypto: crypto
|
||||
})(config)
|
||||
|
||||
test(
|
||||
'interface is correct',
|
||||
t => {
|
||||
t.equal(typeof features, 'object', 'object type should be exported')
|
||||
t.equal(Object.keys(features).length, 3, 'object should have two properties')
|
||||
t.equal(typeof features.isSampledUser, 'function', 'isSampledUser should be function')
|
||||
t.equal(typeof features.isLastAccessTimeEnabledForUser, 'function', 'isLastAccessTimeEnabledForUser should be function')
|
||||
t.equal(typeof features.isSigninConfirmationEnabledForUser, 'function', 'isSigninConfirmationEnabledForUser should be function')
|
||||
|
||||
t.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once on require')
|
||||
let args = crypto.createHash.args[0]
|
||||
t.equal(args.length, 1, 'crypto.createHash should have been passed one argument')
|
||||
t.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1')
|
||||
|
||||
t.equal(hash.update.callCount, 2, 'hash.update should have been called twice on require')
|
||||
args = hash.update.args[0]
|
||||
t.equal(args.length, 1, 'hash.update should have been passed one argument first time')
|
||||
t.equal(typeof args[0], 'string', 'hash.update data should have been a string first time')
|
||||
args = hash.update.args[1]
|
||||
t.equal(args.length, 1, 'hash.update should have been passed one argument second time')
|
||||
t.equal(typeof args[0], 'string', 'hash.update data should have been a string second time')
|
||||
|
||||
t.equal(hash.digest.callCount, 1, 'hash.digest should have been called once on require')
|
||||
args = hash.digest.args[0]
|
||||
t.equal(args.length, 1, 'hash.digest should have been passed one argument')
|
||||
t.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex')
|
||||
|
||||
crypto.createHash.reset()
|
||||
hash.update.reset()
|
||||
hash.digest.reset()
|
||||
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'isSampledUser',
|
||||
t => {
|
||||
let uid = Buffer.alloc(32, 0xff)
|
||||
let sampleRate = 1
|
||||
hashResult = Array(40).fill('f').join('')
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should always return true if sample rate is 1')
|
||||
|
||||
t.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called')
|
||||
t.equal(hash.update.callCount, 0, 'hash.update should not have been called')
|
||||
t.equal(hash.digest.callCount, 0, 'hash.digest should not have been called')
|
||||
|
||||
sampleRate = 0
|
||||
hashResult = Array(40).fill('0').join('')
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should always return false if sample rate is 0')
|
||||
|
||||
t.equal(crypto.createHash.callCount, 0, 'crypto.createHash should not have been called')
|
||||
t.equal(hash.update.callCount, 0, 'hash.update should not have been called')
|
||||
t.equal(hash.digest.callCount, 0, 'hash.digest should not have been called')
|
||||
|
||||
sampleRate = 0.05
|
||||
// First 27 characters are ignored, last 13 are 0.04 * 0xfffffffffffff
|
||||
hashResult = '0000000000000000000000000000a3d70a3d70a6'
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'foo'), true, 'should return true if sample rate is greater than the extracted cohort value')
|
||||
|
||||
t.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once')
|
||||
let args = crypto.createHash.args[0]
|
||||
t.equal(args.length, 1, 'crypto.createHash should have been passed one argument')
|
||||
t.equal(args[0], 'sha1', 'crypto.createHash algorithm should have been sha1')
|
||||
|
||||
t.equal(hash.update.callCount, 2, 'hash.update should have been called twice')
|
||||
args = hash.update.args[0]
|
||||
t.equal(args.length, 1, 'hash.update should have been passed one argument first time')
|
||||
t.equal(args[0], uid.toString('hex'), 'hash.update data should have been stringified uid first time')
|
||||
args = hash.update.args[1]
|
||||
t.equal(args.length, 1, 'hash.update should have been passed one argument second time')
|
||||
t.equal(args[0], 'foo', 'hash.update data should have been key second time')
|
||||
|
||||
t.equal(hash.digest.callCount, 1, 'hash.digest should have been called once')
|
||||
args = hash.digest.args[0]
|
||||
t.equal(args.length, 1, 'hash.digest should have been passed one argument')
|
||||
t.equal(args[0], 'hex', 'hash.digest ecnoding should have been hex')
|
||||
|
||||
crypto.createHash.reset()
|
||||
hash.update.reset()
|
||||
hash.digest.reset()
|
||||
|
||||
sampleRate = 0.04
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'bar'), false, 'should return false if sample rate is equal to the extracted cohort value')
|
||||
|
||||
t.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once')
|
||||
t.equal(hash.update.callCount, 2, 'hash.update should have been called twice')
|
||||
t.equal(hash.update.args[0][0], uid.toString('hex'), 'hash.update data should have been stringified uid first time')
|
||||
t.equal(hash.update.args[1][0], 'bar', 'hash.update data should have been key second time')
|
||||
t.equal(hash.digest.callCount, 1, 'hash.digest should have been called once')
|
||||
|
||||
crypto.createHash.reset()
|
||||
hash.update.reset()
|
||||
hash.digest.reset()
|
||||
|
||||
sampleRate = 0.03
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'foo'), false, 'should return false if sample rate is less than the extracted cohort value')
|
||||
|
||||
crypto.createHash.reset()
|
||||
hash.update.reset()
|
||||
hash.digest.reset()
|
||||
|
||||
uid = Array(64).fill('7').join('')
|
||||
sampleRate = 0.03
|
||||
// First 27 characters are ignored, last 13 are 0.02 * 0xfffffffffffff
|
||||
hashResult = '000000000000000000000000000051eb851eb852'
|
||||
|
||||
t.equal(features.isSampledUser(sampleRate, uid, 'wibble'), true, 'should return true if sample rate is greater than the extracted cohort value')
|
||||
|
||||
t.equal(hash.update.callCount, 2, 'hash.update should have been called twice')
|
||||
t.equal(hash.update.args[0][0], uid, 'hash.update data should have been stringified uid first time')
|
||||
t.equal(hash.update.args[1][0], 'wibble', 'hash.update data should have been key second time')
|
||||
|
||||
crypto.createHash.reset()
|
||||
hash.update.reset()
|
||||
hash.digest.reset()
|
||||
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'isLastAccessTimeEnabledForUser',
|
||||
t => {
|
||||
const uid = 'foo'
|
||||
const email = 'bar@mozilla.com'
|
||||
// First 27 characters are ignored, last 13 are 0.02 * 0xfffffffffffff
|
||||
hashResult = '000000000000000000000000000051eb851eb852'
|
||||
|
||||
config.lastAccessTimeUpdates.enabled = true
|
||||
config.lastAccessTimeUpdates.sampleRate = 0
|
||||
config.lastAccessTimeUpdates.enabledEmailAddresses = '.+@mozilla\\.com$'
|
||||
t.equal(features.isLastAccessTimeEnabledForUser(uid, email), true, 'should return true when email address matches')
|
||||
|
||||
config.lastAccessTimeUpdates.enabledEmailAddresses = '.+@mozilla\\.org$'
|
||||
t.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when email address does not match')
|
||||
|
||||
config.lastAccessTimeUpdates.sampleRate = 0.03
|
||||
t.equal(features.isLastAccessTimeEnabledForUser(uid, email), true, 'should return true when sample rate matches')
|
||||
|
||||
config.lastAccessTimeUpdates.sampleRate = 0.02
|
||||
t.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when sample rate does not match')
|
||||
|
||||
config.lastAccessTimeUpdates.enabled = false
|
||||
config.lastAccessTimeUpdates.sampleRate = 0.03
|
||||
config.lastAccessTimeUpdates.enabledEmailAddresses = '.+@mozilla\\.com$'
|
||||
t.equal(features.isLastAccessTimeEnabledForUser(uid, email), false, 'should return false when feature is disabled')
|
||||
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'isSigninConfirmationEnabledForUser',
|
||||
t => {
|
||||
const uid = 'wibble'
|
||||
const email = 'blee@mozilla.com'
|
||||
const request = {
|
||||
app: {
|
||||
isSuspiciousRequest: true
|
||||
},
|
||||
payload: {
|
||||
metricsContext: {
|
||||
context: 'iframe'
|
||||
}
|
||||
}
|
||||
}
|
||||
// First 27 characters are ignored, last 13 are 0.02 * 0xfffffffffffff
|
||||
hashResult = '000000000000000000000000000051eb851eb852'
|
||||
|
||||
config.signinConfirmation.enabled = true
|
||||
config.signinConfirmation.sample_rate = 0.03
|
||||
config.signinConfirmation.forceEmailRegex = [ 'wibble', '.+@mozilla\\.com$' ]
|
||||
config.signinConfirmation.supportedClients = [ 'wibble', 'iframe' ]
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when request is suspicious')
|
||||
|
||||
config.signinConfirmation.sample_rate = 0.02
|
||||
request.app.isSuspiciousRequest = false
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when email address matches')
|
||||
|
||||
config.signinConfirmation.forceEmailRegex[1] = '.+@mozilla\\.org$'
|
||||
request.payload.metricsContext.context = 'iframe'
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), false, 'should return false when email address and sample rate do not match')
|
||||
|
||||
config.signinConfirmation.sample_rate = 0.03
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when sample rate and context match')
|
||||
|
||||
request.payload.metricsContext.context = ''
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), false, 'should return false when context does not match')
|
||||
|
||||
config.signinConfirmation.enabled = false
|
||||
config.signinConfirmation.forceEmailRegex[1] = '.+@mozilla\\.com$'
|
||||
request.payload.metricsContext.context = 'iframe'
|
||||
t.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), false, 'should return false when feature is disabled')
|
||||
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
|
@ -7,7 +7,10 @@ var test = require('../ptaptest')
|
|||
var log = { trace: function() {}, info: function () {} }
|
||||
var crypto = require('crypto')
|
||||
|
||||
var tokens = require('../../lib/tokens')(log)
|
||||
const config = {
|
||||
lastAccessTimeUpdates: {}
|
||||
}
|
||||
const tokens = require('../../lib/tokens')(log, config)
|
||||
var SessionToken = tokens.SessionToken
|
||||
|
||||
var TOKEN_FRESHNESS_THRESHOLD = require('../../lib/tokens/session_token').TOKEN_FRESHNESS_THREADHOLD
|
||||
|
@ -153,8 +156,11 @@ test(
|
|||
)
|
||||
|
||||
test(
|
||||
'SessionToken.isFresh',
|
||||
function (t) {
|
||||
'SessionToken.isFresh with lastAccessTime updates enabled',
|
||||
t => {
|
||||
config.lastAccessTimeUpdates.enabled = true
|
||||
config.lastAccessTimeUpdates.sampleRate = 1
|
||||
config.lastAccessTimeUpdates.enabledEmailAddresses = '.+'
|
||||
return SessionToken.create({
|
||||
uaBrowser: 'foo',
|
||||
uaBrowserVersion: 'bar',
|
||||
|
@ -162,7 +168,7 @@ test(
|
|||
uaOSVersion: 'qux',
|
||||
uaDeviceType: 'wibble',
|
||||
lastAccessTime: 0
|
||||
}).then(function (token) {
|
||||
}).then(token => {
|
||||
t.equal(token.isFresh({
|
||||
uaBrowser: 'foo',
|
||||
uaBrowserVersion: 'bar',
|
||||
|
@ -231,6 +237,30 @@ test(
|
|||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'SessionToken.isFresh with lastAccessTime updates disabled',
|
||||
t => {
|
||||
config.lastAccessTimeUpdates.enabled = false
|
||||
return SessionToken.create({
|
||||
uaBrowser: 'foo',
|
||||
uaBrowserVersion: 'bar',
|
||||
uaOS: 'baz',
|
||||
uaOSVersion: 'qux',
|
||||
uaDeviceType: 'wibble',
|
||||
lastAccessTime: 0
|
||||
}).then(token => {
|
||||
t.equal(token.isFresh({
|
||||
uaBrowser: 'foo',
|
||||
uaBrowserVersion: 'bar',
|
||||
uaOS: 'baz',
|
||||
uaOSVersion: 'qux',
|
||||
uaDeviceType: 'wibble',
|
||||
lastAccessTime: TOKEN_FRESHNESS_THRESHOLD
|
||||
}), true, 'returns true when lastAccessTime is TOKEN_FRESHNESS_THRESHOLD milliseconds newer')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'SessionToken.update on fresh token',
|
||||
function (t) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var config = require('../../config').getProperties()
|
||||
// XXX: update this later to avoid issues.
|
||||
process.env.NODE_ENV = 'dev'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
var key = {
|
||||
|
|
|
@ -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/. */
|
||||
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var crypto = require('crypto')
|
||||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var JWTool = require('fxa-jwtool')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
var path = require('path')
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
process.env.CONFIG_FILES = path.join(__dirname, '../config/mock_oauth.json')
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var url = require('url')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var TestServer = require('../test_server')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
test(
|
||||
'signin confirmation can be disabled',
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var config = require('../../config').getProperties()
|
||||
var url = require('url')
|
||||
var jwtool = require('fxa-jwtool')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ process.env.PUBLIC_URL = 'http://127.0.0.1:9000/auth'
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var P = require('../../lib/promise')
|
||||
var request = require('request')
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var jwtool = require('fxa-jwtool')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var P = require('../../lib/promise')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -11,9 +11,16 @@ var log = { trace: console.log, info: console.log } // eslint-disable-line no-co
|
|||
var config = require('../../config').getProperties()
|
||||
var P = require('../../lib/promise')
|
||||
var TestServer = require('../test_server')
|
||||
var Token = require('../../lib/tokens')(log)
|
||||
var DB = require('../../lib/db')(
|
||||
config.db.backend,
|
||||
const lastAccessTimeUpdates = {
|
||||
enabled: true,
|
||||
enabledEmailAddresses: '.*',
|
||||
sampleRate: 1
|
||||
}
|
||||
const Token = require('../../lib/tokens')(log, {
|
||||
lastAccessTimeUpdates: lastAccessTimeUpdates
|
||||
})
|
||||
const DB = require('../../lib/db')(
|
||||
{ lastAccessTimeUpdates: lastAccessTimeUpdates },
|
||||
log,
|
||||
Token.error,
|
||||
Token.SessionToken,
|
||||
|
@ -227,10 +234,12 @@ test(
|
|||
return db.emailRecord(ACCOUNT.email)
|
||||
.then(function (emailRecord) {
|
||||
emailRecord.tokenVerificationId = ACCOUNT.tokenVerificationId
|
||||
// Create a session token
|
||||
return db.createSessionToken(emailRecord, 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0')
|
||||
})
|
||||
.then(function (result) {
|
||||
sessionToken = result
|
||||
// Attempt to update a non-existent device
|
||||
return db.updateDevice(ACCOUNT.uid, sessionToken.tokenId, deviceInfo)
|
||||
.then(function () {
|
||||
t.fail('updating a non-existent device should have failed')
|
||||
|
@ -240,6 +249,7 @@ test(
|
|||
})
|
||||
})
|
||||
.then(function () {
|
||||
// Attempt to delete a non-existent device
|
||||
return db.deleteDevice(ACCOUNT.uid, deviceInfo.id)
|
||||
.then(function () {
|
||||
t.fail('deleting a non-existent device should have failed')
|
||||
|
@ -249,6 +259,7 @@ test(
|
|||
})
|
||||
})
|
||||
.then(function () {
|
||||
// Fetch all of the devices for the account
|
||||
return db.devices(ACCOUNT.uid)
|
||||
.catch(function () {
|
||||
t.fail('getting devices should not have failed')
|
||||
|
@ -257,6 +268,7 @@ test(
|
|||
.then(function (devices) {
|
||||
t.ok(Array.isArray(devices), 'devices is array')
|
||||
t.equal(devices.length, 0, 'devices array is empty')
|
||||
// Create a device
|
||||
return db.createDevice(ACCOUNT.uid, sessionToken.tokenId, deviceInfo)
|
||||
.catch(function (err) {
|
||||
t.fail('adding a new device should not have failed')
|
||||
|
@ -270,6 +282,7 @@ test(
|
|||
t.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct')
|
||||
t.equal(device.pushPublicKey, deviceInfo.pushPublicKey, 'device.pushPublicKey is correct')
|
||||
t.equal(device.pushAuthKey, deviceInfo.pushAuthKey, 'device.pushAuthKey is correct')
|
||||
// Attempt to create a device with a duplicate session token
|
||||
return db.createDevice(ACCOUNT.uid, sessionToken.tokenId, deviceInfo)
|
||||
.then(function () {
|
||||
t.fail('adding a device with a duplicate session token should have failed')
|
||||
|
@ -279,6 +292,7 @@ test(
|
|||
})
|
||||
})
|
||||
.then(function () {
|
||||
// Fetch all of the devices for the account
|
||||
return db.devices(ACCOUNT.uid)
|
||||
})
|
||||
.then(function (devices) {
|
||||
|
@ -304,6 +318,7 @@ test(
|
|||
deviceInfo.pushCallback = ''
|
||||
deviceInfo.pushPublicKey = ''
|
||||
deviceInfo.pushAuthKey = ''
|
||||
// Update the device and the session token
|
||||
return P.all([
|
||||
db.updateDevice(ACCOUNT.uid, sessionToken.tokenId, deviceInfo),
|
||||
db.updateSessionToken(sessionToken, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:44.0) Gecko/20100101 Firefox/44.0')
|
||||
|
@ -313,6 +328,7 @@ test(
|
|||
})
|
||||
})
|
||||
.then(function (device) {
|
||||
// Fetch all of the devices for the account
|
||||
return db.devices(ACCOUNT.uid)
|
||||
})
|
||||
.then(function (devices) {
|
||||
|
@ -320,6 +336,7 @@ test(
|
|||
return devices[0]
|
||||
})
|
||||
.then(function (device) {
|
||||
t.ok(device.lastAccessTime > 0, 'device.lastAccessTime is set')
|
||||
t.equal(device.name, deviceInfo.name, 'device.name is correct')
|
||||
t.equal(device.type, deviceInfo.type, 'device.type is correct')
|
||||
t.equal(device.pushCallback, deviceInfo.pushCallback, 'device.pushCallback is correct')
|
||||
|
@ -330,12 +347,24 @@ test(
|
|||
t.equal(device.uaOS, 'Mac OS X', 'device.uaOS is correct')
|
||||
t.equal(device.uaOSVersion, '10.10', 'device.uaOSVersion is correct')
|
||||
t.equal(device.uaDeviceType, null, 'device.uaDeviceType is correct')
|
||||
// Disable the lastAccessTime property
|
||||
lastAccessTimeUpdates.enabled = false
|
||||
// Fetch all of the devices for the account
|
||||
return db.devices(ACCOUNT.uid)
|
||||
})
|
||||
.then(function(devices) {
|
||||
t.equal(devices.length, 1, 'devices array still contains one item')
|
||||
t.equal(devices[0].lastAccessTime, null, 'device.lastAccessTime should be null')
|
||||
// Reinstate the lastAccessTime property
|
||||
lastAccessTimeUpdates.enabled = true
|
||||
// Delete the device
|
||||
return db.deleteDevice(ACCOUNT.uid, deviceInfo.id)
|
||||
.catch(function () {
|
||||
t.fail('deleting a device should not have failed')
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
// Fetch all of the devices for the account
|
||||
return db.devices(ACCOUNT.uid)
|
||||
})
|
||||
.then(function (devices) {
|
||||
|
|
|
@ -4,12 +4,17 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var config = require('../../config').getProperties()
|
||||
var crypto = require('crypto')
|
||||
var base64url = require('base64url')
|
||||
var P = require('../../lib/promise')
|
||||
|
||||
// HACK: Force-enable devices.lastAccessTime in the spawned server process
|
||||
process.env.LASTACCESSTIME_UPDATES_ENABLED = 'true'
|
||||
process.env.LASTACCESSTIME_UPDATES_EMAIL_ADDRESSES = '.*'
|
||||
process.env.LASTACCESSTIME_UPDATES_SAMPLE_RATE = '1'
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
test(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var P = require('../../lib/promise')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var test = require('tap').test
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var TestServer = require('../test_server')
|
||||
var jwtool = require('fxa-jwtool')
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var P = require('../../lib/promise')
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
|
|
|
@ -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/. */
|
||||
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var config = require('../../config').getProperties()
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var url = require('url')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var base64url = require('base64url')
|
||||
|
|
|
@ -14,8 +14,8 @@ var log = { trace: console.log, info: console.log } // eslint-disable-line no-co
|
|||
var config = require('../../config').getProperties()
|
||||
var TestServer = require('../test_server')
|
||||
var Token = require('../../lib/tokens')(log)
|
||||
var DB = require('../../lib/db')(
|
||||
config.db.backend,
|
||||
const DB = require('../../lib/db')(
|
||||
config,
|
||||
log,
|
||||
Token.error,
|
||||
Token.SessionToken,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var test = require('../ptaptest')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var TestServer = require('../test_server')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var url = require('url')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var TestServer = require('../test_server')
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
var config = require('../../config').getProperties()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('../ptaptest')
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
|
||||
process.env.PASSWORD_CHANGE_TOKEN_TTL = '1'
|
||||
var config = require('../../config').getProperties()
|
||||
|
|
|
@ -11,7 +11,7 @@ var config = require('../../config').getProperties()
|
|||
var TestServer = require('../test_server')
|
||||
var Token = require('../../lib/tokens')(log)
|
||||
var DB = require('../../lib/db')(
|
||||
config.db.backend,
|
||||
config,
|
||||
log,
|
||||
Token.error,
|
||||
Token.SessionToken,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var test = require('tap').test
|
||||
var TestServer = require('../test_server')
|
||||
var Client = require('../client')
|
||||
const Client = require('../client')()
|
||||
var createDBServer = require('fxa-auth-db-mysql')
|
||||
var log = { trace: console.log } // eslint-disable-line no-console
|
||||
|
||||
|
@ -15,7 +15,7 @@ process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
|||
|
||||
var Token = require('../../lib/tokens')(log)
|
||||
var DB = require('../../lib/db')(
|
||||
config.db.backend,
|
||||
config,
|
||||
log,
|
||||
Token.error,
|
||||
Token.SessionToken,
|
||||
|
|
Загрузка…
Ссылка в новой задаче