feat(signin): Remove feature flag from sign-in confirmation. (#1530); r=vbudhram
It's now always enabled for all users, and we no longer have any backwards-compatibility paths that might fall back to it being disabled.
This commit is contained in:
Родитель
b23a531db7
Коммит
5f0f3ba550
2
.env.dev
2
.env.dev
|
@ -4,8 +4,6 @@ LOCKOUT_ENABLED=true
|
|||
LOG_FORMAT=pretty
|
||||
LOG_LEVEL=info
|
||||
RESEND_BLACKOUT_PERIOD=0
|
||||
SIGNIN_CONFIRMATION_ENABLED=true
|
||||
SIGNIN_CONFIRMATION_RATE=1
|
||||
SMTP_HOST=127.0.0.1
|
||||
SMTP_PORT=9999
|
||||
SMTP_SECURE=false
|
||||
|
|
|
@ -437,36 +437,8 @@ var conf = convict({
|
|||
default: 3
|
||||
},
|
||||
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'
|
||||
},
|
||||
supportedClients: {
|
||||
doc: 'support sign-in confirmation for only these clients',
|
||||
format: Array,
|
||||
default: [
|
||||
'iframe',
|
||||
'fx_firstrun_v1',
|
||||
'fx_firstrun_v2',
|
||||
'fx_desktop_v1',
|
||||
'fx_desktop_v2',
|
||||
'fx_desktop_v3',
|
||||
'fx_ios_v1',
|
||||
'fx_ios_v2',
|
||||
'fx_fennec_v1'
|
||||
],
|
||||
env: 'SIGNIN_CONFIRMATION_SUPPORTED_CLIENTS'
|
||||
},
|
||||
forcedEmailAddresses: {
|
||||
doc: 'If feature enabled, force sign-in confirmation for email addresses matching this regex.',
|
||||
doc: 'Force sign-in confirmation for email addresses matching this regex.',
|
||||
format: RegExp,
|
||||
default: /.+@mozilla\.com$/,
|
||||
env: 'SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX'
|
||||
|
|
|
@ -8,7 +8,6 @@ const crypto = require('crypto')
|
|||
|
||||
module.exports = config => {
|
||||
const lastAccessTimeUpdates = config.lastAccessTimeUpdates
|
||||
const signinConfirmation = config.signinConfirmation
|
||||
const signinUnblock = config.signinUnblock
|
||||
const securityHistory = config.securityHistory
|
||||
|
||||
|
@ -28,49 +27,10 @@ module.exports = config => {
|
|||
},
|
||||
|
||||
/**
|
||||
* Predicate that indicates whether sign-in confirmation is enabled
|
||||
* for a given user, based on their uid and email address.
|
||||
* Returns whether or not to use signin unblock feature on a request.
|
||||
*
|
||||
* @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 the regex.
|
||||
if (signinConfirmation.forcedEmailAddresses.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.
|
||||
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')
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not to use signin unblock feature on a request.
|
||||
*
|
||||
* @param account
|
||||
* @param config
|
||||
* @param request
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
@ -103,24 +63,28 @@ module.exports = config => {
|
|||
},
|
||||
|
||||
/**
|
||||
* Return whether or not this request should bypass sign-in confirmation. Currently,
|
||||
* just checks if user has had a verified security event in the past day.
|
||||
* Return whether tracking of security history events is enabled.
|
||||
*
|
||||
* @param verified
|
||||
* @param recency
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canBypassSiginConfirmation(email, verified, recency) {
|
||||
// If sign-in confirmation is forced for an email, it can't be bypassed.
|
||||
if (signinConfirmation.enabled && signinConfirmation.forcedEmailAddresses.test(email)) {
|
||||
isSecurityHistoryTrackingEnabled() {
|
||||
return securityHistory.enabled
|
||||
},
|
||||
|
||||
/**
|
||||
* Return whether or not we can bypass sign-in confirmation based
|
||||
* on previously seen security event history.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSecurityHistoryProfilingEnabled() {
|
||||
if (! securityHistory.enabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
// IP Profiling returns true if this user has verified a session
|
||||
// within the past day from this ip address.
|
||||
let ipProfilingEnabled = securityHistory.enabled && securityHistory.ipProfiling &&
|
||||
securityHistory.ipProfiling.enabled
|
||||
return ipProfilingEnabled && verified && recency === 'day'
|
||||
if (! securityHistory.ipProfiling || ! securityHistory.ipProfiling.enabled) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -60,7 +60,6 @@ module.exports = function (
|
|||
})
|
||||
const features = require('../features')(config)
|
||||
|
||||
const securityHistoryEnabled = config.securityHistory && config.securityHistory.enabled
|
||||
const unblockCodeLifetime = config.signinUnblock && config.signinUnblock.codeLifetime || 0
|
||||
const unblockCodeLen = config.signinUnblock && config.signinUnblock.codeLength || 0
|
||||
|
||||
|
@ -239,12 +238,8 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function createSessionToken () {
|
||||
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.
|
||||
if (preVerified || ! enableTokenVerification) {
|
||||
// Verified sessions should only be created for preverified accounts.
|
||||
if (preVerified) {
|
||||
tokenVerificationId = undefined
|
||||
}
|
||||
|
||||
|
@ -255,7 +250,7 @@ module.exports = function (
|
|||
emailVerified: account.emailVerified,
|
||||
verifierSetAt: account.verifierSetAt,
|
||||
createdAt: parseInt(query._createdAt),
|
||||
mustVerify: enableTokenVerification && requestHelper.wantsKeys(request),
|
||||
mustVerify: requestHelper.wantsKeys(request),
|
||||
tokenVerificationId: tokenVerificationId
|
||||
}, userAgentString)
|
||||
.then(
|
||||
|
@ -341,7 +336,7 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function recordSecurityEvent() {
|
||||
if (securityHistoryEnabled) {
|
||||
if (features.isSecurityHistoryTrackingEnabled()) {
|
||||
// don't block response recording db event
|
||||
db.securityEvent({
|
||||
name: 'account.create',
|
||||
|
@ -522,7 +517,7 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function checkSecurityHistory () {
|
||||
if (!securityHistoryEnabled) {
|
||||
if (!features.isSecurityHistoryTrackingEnabled()) {
|
||||
return
|
||||
}
|
||||
return db.securityEvents({
|
||||
|
@ -581,40 +576,35 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function checkEmailAndPassword () {
|
||||
// Session token verification is only enabled for certain users during phased rollout.
|
||||
//
|
||||
// If the user went through the sigin-unblock flow, they have already verified their email.
|
||||
// No need to also require confirmation afterwards.
|
||||
//
|
||||
// Even when it is enabled, we only do the email challenge if:
|
||||
// * 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.
|
||||
// * the login is flagged that it can be bypassed
|
||||
// All sessions are considered unverified by default.
|
||||
needsVerificationId = true
|
||||
|
||||
// Check to see if this login can bypass sign-in confirmation. Current scenarios include
|
||||
// * User has already logged in from this ip address and verified the sign-in
|
||||
let bypassSiginConfirmation = features.canBypassSiginConfirmation(emailRecord.email, securityEventVerified, securityEventRecency)
|
||||
if (bypassSiginConfirmation) {
|
||||
log.info({
|
||||
op: 'Account.ipprofiling.seenAddress',
|
||||
uid: emailRecord.uid.toString('hex')
|
||||
})
|
||||
}
|
||||
|
||||
if (didSigninUnblock || !features.isSigninConfirmationEnabledForUser(emailRecord.uid, emailRecord.email, request)
|
||||
|| bypassSiginConfirmation) {
|
||||
// However! To help simplify the login flow, we can use some heuristics to
|
||||
// decide whether to consider the session pre-verified. Some accounts
|
||||
// get excluded from this process, e.g. testing accounts where we want
|
||||
// to know for sure what flow they're going to see.
|
||||
if (! forceTokenVerification(request, emailRecord)) {
|
||||
if (skipTokenVerification(request, emailRecord)) {
|
||||
needsVerificationId = false
|
||||
mustVerifySession = false
|
||||
doSigninConfirmation = false
|
||||
} else {
|
||||
// The user doesn't *have* to verify their session if they're not requesting keys,
|
||||
// but we still create it with a non-null tokenVerificationId, so it will still
|
||||
// be considered unverified. This prevents the session from being used for sync
|
||||
// unless the user explicitly requests us to resend the confirmation email, and completes it.
|
||||
mustVerifySession = requestHelper.wantsKeys(request)
|
||||
doSigninConfirmation = mustVerifySession && emailRecord.emailVerified
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// If the request wants keys, the user *must* confirm their login session before they
|
||||
// can actually use it. If they dont want keys, they don't *have* to verify their
|
||||
// their session, but we still create it with a non-null tokenVerificationId, so it will
|
||||
// still be considered unverified. This prevents the session from being used for sync
|
||||
// unless the user explicitly requests us to resend the confirmation email, and completes it.
|
||||
mustVerifySession = needsVerificationId && requestHelper.wantsKeys(request)
|
||||
|
||||
// If the email itself is unverified, we'll re-send the "verify your account email" and
|
||||
// that will suffice to confirm the sign-in. No need for a separate confirmation email.
|
||||
doSigninConfirmation = mustVerifySession && emailRecord.emailVerified
|
||||
|
||||
let flowCompleteSignal
|
||||
if (service === 'sync') {
|
||||
|
@ -648,6 +638,40 @@ module.exports = function (
|
|||
)
|
||||
}
|
||||
|
||||
function forceTokenVerification (request, account) {
|
||||
// If there was anything suspicious about the request,
|
||||
// we should force token verification.
|
||||
if (request.app.isSuspiciousRequest) {
|
||||
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.email)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
if (features.isSecurityHistoryProfilingEnabled()) {
|
||||
if (securityEventVerified && securityEventRecency === 'day') {
|
||||
log.info({
|
||||
op: 'Account.ipprofiling.seenAddress',
|
||||
uid: account.uid.toString('hex')
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkNumberOfActiveSessions () {
|
||||
return db.sessions(emailRecord.uid)
|
||||
.then(
|
||||
|
@ -836,7 +860,7 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function recordSecurityEvent() {
|
||||
if (securityHistoryEnabled) {
|
||||
if (features.isSecurityHistoryTrackingEnabled()) {
|
||||
// don't block response recording db event
|
||||
db.securityEvent({
|
||||
name: 'account.login',
|
||||
|
@ -1920,7 +1944,7 @@ module.exports = function (
|
|||
}
|
||||
|
||||
function recordSecurityEvent() {
|
||||
if (securityHistoryEnabled) {
|
||||
if (features.isSecurityHistoryTrackingEnabled()) {
|
||||
// don't block response recording db event
|
||||
db.securityEvent({
|
||||
name: 'account.reset',
|
||||
|
|
|
@ -168,9 +168,8 @@ module.exports = function (
|
|||
}
|
||||
)
|
||||
} else {
|
||||
// To keep backwards compatibility, default to creating a verified
|
||||
// session if no sessionToken is passed
|
||||
verifiedStatus = true
|
||||
// Don't create a verified session unless they already had one.
|
||||
verifiedStatus = false
|
||||
return P.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,9 +83,7 @@ function runTest (route, request, assertions) {
|
|||
}
|
||||
|
||||
describe('/recovery_email/status', function () {
|
||||
var config = {
|
||||
signinConfirmation: {}
|
||||
}
|
||||
var config = {}
|
||||
var mockDB = mocks.mockDB()
|
||||
var pushCalled
|
||||
var mockLog = mocks.mockLog({
|
||||
|
@ -102,8 +100,12 @@ describe('/recovery_email/status', function () {
|
|||
})
|
||||
var route = getRoute(accountRoutes, '/recovery_email/status')
|
||||
|
||||
describe('sign-in confirmation disabled', function () {
|
||||
config.signinConfirmation.enabled = false
|
||||
var mockRequest = mocks.mockRequest({
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
email: TEST_EMAIL
|
||||
}
|
||||
})
|
||||
|
||||
describe('invalid email', function () {
|
||||
var mockRequest = mocks.mockRequest({
|
||||
|
@ -142,6 +144,7 @@ describe('/recovery_email/status', function () {
|
|||
})
|
||||
})
|
||||
|
||||
|
||||
it('valid email, verified account', function () {
|
||||
pushCalled = false
|
||||
var mockRequest = mocks.mockRequest({
|
||||
|
@ -167,17 +170,6 @@ describe('/recovery_email/status', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('sign-in confirmation enabled', function () {
|
||||
config.signinConfirmation.enabled = true
|
||||
config.signinConfirmation.sample_rate = 1
|
||||
var mockRequest = mocks.mockRequest({
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
email: TEST_EMAIL
|
||||
}
|
||||
})
|
||||
|
||||
it('verified account, verified session', function () {
|
||||
mockRequest.auth.credentials.emailVerified = true
|
||||
|
@ -223,12 +215,9 @@ describe('/recovery_email/status', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('/recovery_email/resend_code', () => {
|
||||
const config = {
|
||||
signinConfirmation: {}
|
||||
}
|
||||
const config = {}
|
||||
const mockDB = mocks.mockDB()
|
||||
const mockLog = mocks.mockLog()
|
||||
mockLog.flowEvent = sinon.spy(() => {
|
||||
|
@ -887,7 +876,7 @@ describe('/account/login', function () {
|
|||
query: {},
|
||||
payload: {
|
||||
authPW: crypto.randomBytes(32).toString('hex'),
|
||||
email: 'test@mozilla.com',
|
||||
email: TEST_EMAIL,
|
||||
unblockCode: 'ABCD1234',
|
||||
service: 'dcdb5ae7add825d2',
|
||||
reason: 'signin',
|
||||
|
@ -929,32 +918,27 @@ describe('/account/login', function () {
|
|||
|
||||
const defaultEmailRecord = mockDB.emailRecord
|
||||
|
||||
beforeEach(() => {
|
||||
afterEach(() => {
|
||||
mockLog.activityEvent.reset()
|
||||
mockLog.flowEvent.reset()
|
||||
mockLog.stdout.write.reset()
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockMailer.sendVerifyCode.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockDB.sessions.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
mockMetricsContext.validate.reset()
|
||||
mockMetricsContext.setFlowCompleteSignal.reset()
|
||||
})
|
||||
|
||||
describe('sign-in confirmation disabled', function () {
|
||||
|
||||
beforeEach(() => {
|
||||
mockDB.emailRecord = defaultEmailRecord
|
||||
mockDB.emailRecord.reset()
|
||||
mockRequest.payload.email = TEST_EMAIL
|
||||
})
|
||||
it('sign-in does not require verification', function () {
|
||||
|
||||
it('emits the correct series of calls and events', function () {
|
||||
return runTest(route, mockRequest, function (response) {
|
||||
assert.equal(mockDB.emailRecord.callCount, 1, 'db.emailRecord was called')
|
||||
assert.equal(mockDB.createSessionToken.callCount, 1, 'db.createSessionToken was called')
|
||||
var tokenData = mockDB.createSessionToken.getCall(0).args[0]
|
||||
assert.ok(!tokenData.mustVerify, 'sessionToken was created verified')
|
||||
assert.ok(!tokenData.tokenVerificationId, 'sessionToken was created verified')
|
||||
assert.equal(mockDB.sessions.callCount, 1, 'db.sessions was called')
|
||||
|
||||
assert.equal(mockLog.stdout.write.callCount, 1, 'an sqs event was logged')
|
||||
var eventData = JSON.parse(mockLog.stdout.write.getCall(0).args[0])
|
||||
|
@ -970,16 +954,18 @@ describe('/account/login', function () {
|
|||
assert.equal(args[1], mockRequest, 'second argument was request object')
|
||||
assert.deepEqual(args[2], {uid: uid.toString('hex')}, 'third argument contained uid')
|
||||
|
||||
assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent was called once')
|
||||
assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice')
|
||||
args = mockLog.flowEvent.args[0]
|
||||
assert.equal(args.length, 2, 'log.flowEvent was passed two arguments')
|
||||
assert.equal(args.length, 2, 'first log.flowEvent was passed two arguments')
|
||||
assert.equal(args[0], 'account.login', 'first argument was event name')
|
||||
assert.equal(args[1], mockRequest, 'second argument was request object')
|
||||
args = mockLog.flowEvent.args[1]
|
||||
assert.equal(args[0], 'email.confirmation.sent', 'second log.flowEvent was passed correct event name')
|
||||
|
||||
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, 2, 'metricsContext.stash was called twice')
|
||||
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')
|
||||
|
@ -989,6 +975,12 @@ describe('/account/login', function () {
|
|||
|
||||
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].tokenId, 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')
|
||||
|
@ -998,21 +990,14 @@ describe('/account/login', function () {
|
|||
assert.equal(args.length, 1, 'metricsContext.setFlowCompleteSignal was passed one argument')
|
||||
assert.deepEqual(args[0], 'account.signed', 'argument was event name')
|
||||
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.getCall(0).args[1].location.city, 'Mountain View')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.getCall(0).args[1].location.country, 'United States')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.getCall(0).args[1].timeZone, 'America/Los_Angeles')
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called')
|
||||
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')
|
||||
}).then(function () {
|
||||
mockLog.activityEvent.reset()
|
||||
mockLog.flowEvent.reset()
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
mockMetricsContext.setFlowCompleteSignal.reset()
|
||||
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.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')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1040,7 +1025,7 @@ describe('/account/login', function () {
|
|||
|
||||
// Verify that the email code was sent
|
||||
var verifyCallArgs = mockMailer.sendVerifyCode.getCall(0).args
|
||||
assert.equal(verifyCallArgs[1], emailCode, 'mailer.sendVerifyCode was called with emailCode')
|
||||
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], 'account.login', 'first event was login')
|
||||
assert.equal(mockLog.flowEvent.args[1][0], 'email.verification.sent', 'second event was sent')
|
||||
|
@ -1050,20 +1035,12 @@ describe('/account/login', function () {
|
|||
assert.equal(response.verificationMethod, 'email', 'verificationMethod is email')
|
||||
assert.equal(response.verificationReason, 'signup', 'verificationReason is signup')
|
||||
assert.equal(response.emailSent, true, 'email sent')
|
||||
}).then(function () {
|
||||
mockLog.flowEvent.reset()
|
||||
mockMailer.sendVerifyCode.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('sign-in confirmation enabled', function () {
|
||||
describe('sign-in confirmation', function () {
|
||||
before(() => {
|
||||
config.signinConfirmation.enabled = true
|
||||
config.signinConfirmation.supportedClients = [ 'fx_desktop_v3' ]
|
||||
config.signinConfirmation.forcedEmailAddresses = /.+@mozilla\.com$/
|
||||
|
||||
mockDB.emailRecord = function () {
|
||||
|
@ -1082,9 +1059,7 @@ describe('/account/login', function () {
|
|||
}
|
||||
})
|
||||
|
||||
it('always on', function () {
|
||||
config.signinConfirmation.sample_rate = 1
|
||||
|
||||
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]
|
||||
|
@ -1096,64 +1071,14 @@ describe('/account/login', function () {
|
|||
assert.equal(response.verificationMethod, 'email', 'verificationMethod is email')
|
||||
assert.equal(response.verificationReason, 'login', 'verificationReason is login')
|
||||
|
||||
assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice')
|
||||
assert.equal(mockLog.flowEvent.args[0][0], 'account.login', 'first event was login')
|
||||
assert.equal(mockLog.flowEvent.args[1][0], 'email.confirmation.sent', 'second event was sent')
|
||||
|
||||
assert.equal(mockMetricsContext.stash.callCount, 3, 'metricsContext.stash was called three times')
|
||||
var 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')
|
||||
assert.deepEqual(args[0].uid, uid, 'token.uid was correct')
|
||||
assert.equal(mockMetricsContext.stash.thisValues[1], mockRequest, 'this was request')
|
||||
|
||||
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')
|
||||
}).then(function () {
|
||||
mockLog.flowEvent.reset()
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.stash.reset()
|
||||
})
|
||||
})
|
||||
|
||||
it('on for email regex match, keys requested', function () {
|
||||
mockRequest.payload.email = 'test@mozilla.com'
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
data: crypto.randomBytes(32),
|
||||
email: 'test@mozilla.com',
|
||||
emailVerified: true,
|
||||
kA: crypto.randomBytes(32),
|
||||
lastAuthAt: function () {
|
||||
return Date.now()
|
||||
},
|
||||
uid: uid,
|
||||
wrapWrapKb: crypto.randomBytes(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.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called')
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was 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')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.setFlowCompleteSignal.reset()
|
||||
})
|
||||
})
|
||||
|
||||
it('off for email regex match, keys not requested', function () {
|
||||
it('does not require verification when keys are not requested', function () {
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
|
@ -1185,101 +1110,10 @@ describe('/account/login', function () {
|
|||
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')
|
||||
}).then(function () {
|
||||
mockDB.createSessionToken.reset()
|
||||
mockMetricsContext.setFlowCompleteSignal.reset()
|
||||
})
|
||||
})
|
||||
|
||||
it('off for email regex mismatch', function () {
|
||||
config.signinConfirmation.sample_rate = 0
|
||||
mockRequest.payload.email = 'moz@fire.fox'
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
data: crypto.randomBytes(32),
|
||||
email: 'moz@fire.fox',
|
||||
emailVerified: true,
|
||||
kA: crypto.randomBytes(32),
|
||||
lastAuthAt: function () {
|
||||
return Date.now()
|
||||
},
|
||||
uid: uid,
|
||||
wrapWrapKb: crypto.randomBytes(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 was created verified')
|
||||
assert.ok(!tokenData.tokenVerificationId, 'sessionToken was created verified')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called')
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called')
|
||||
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')
|
||||
}).then(function () {
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
})
|
||||
})
|
||||
|
||||
it('off for unsupported client', function () {
|
||||
config.signinConfirmation.supportedClients = [ 'fx_desktop_v999' ]
|
||||
|
||||
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 was created verified')
|
||||
assert.ok(!tokenData.tokenVerificationId, 'sessionToken was created verified')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called')
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0, 'mailer.sendVerifyLoginEmail was not called')
|
||||
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')
|
||||
}).then(function () {
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
})
|
||||
})
|
||||
|
||||
it('on for suspicious requests', function () {
|
||||
mockRequest.payload.email = 'dodgy@mcdodgeface.com'
|
||||
mockRequest.app.isSuspiciousRequest = true
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
data: crypto.randomBytes(32),
|
||||
email: 'dodgy@mcdodgeface.com',
|
||||
emailVerified: true,
|
||||
kA: crypto.randomBytes(32),
|
||||
lastAuthAt: function () {
|
||||
return Date.now()
|
||||
},
|
||||
uid: uid,
|
||||
wrapWrapKb: crypto.randomBytes(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.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called')
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was 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')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
delete mockRequest.app.isSuspiciousRequest
|
||||
})
|
||||
})
|
||||
|
||||
it('unverified account gets account confirmation email', function () {
|
||||
config.signinConfirmation.supportedClients = [ 'fx_desktop_v3' ]
|
||||
mockRequest.payload.email = 'test@mozilla.com'
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
|
@ -1307,49 +1141,12 @@ describe('/account/login', function () {
|
|||
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')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyCode.reset()
|
||||
mockDB.createSessionToken.reset()
|
||||
})
|
||||
})
|
||||
|
||||
describe('sign-in with unverified account', function () {
|
||||
before(() => {
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
data: crypto.randomBytes(32),
|
||||
email: 'test@mozilla.com',
|
||||
emailVerified: false,
|
||||
kA: crypto.randomBytes(32),
|
||||
lastAuthAt: function () {
|
||||
return Date.now()
|
||||
},
|
||||
uid: uid,
|
||||
wrapWrapKb: crypto.randomBytes(32)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('sends verify account email', function () {
|
||||
return runTest(route, mockRequest, function (response) {
|
||||
assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was 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(response.verified, false, 'response indicates account is unverified')
|
||||
assert.equal(response.verificationMethod, 'email', 'verificationMethod is email')
|
||||
assert.equal(response.verificationReason, 'signup', 'verificationReason is signup')
|
||||
assert.equal(response.emailSent, true, 'email not sent')
|
||||
}).then(function () {
|
||||
mockMailer.sendVerifyCode.reset()
|
||||
})
|
||||
assert.equal(response.emailSent, true, 'response indicates an email was sent')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creating too many sessions causes an error to be logged', function () {
|
||||
mockDB.emailRecord = defaultEmailRecord
|
||||
mockDB.emailRecord.reset()
|
||||
const oldSessions = mockDB.sessions
|
||||
mockDB.sessions = sinon.spy(function () {
|
||||
return P.resolve(new Array(200))
|
||||
|
@ -1519,16 +1316,6 @@ describe('/account/login', function () {
|
|||
})
|
||||
|
||||
describe('with unblock code', () => {
|
||||
mockLog.flowEvent.reset()
|
||||
|
||||
let previousEmailRecord
|
||||
before(() => {
|
||||
previousEmailRecord = mockDB.emailRecord
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockDB.emailRecord = previousEmailRecord
|
||||
})
|
||||
|
||||
it('invalid code', () => {
|
||||
mockDB.consumeUnblockCode = () => P.reject(error.invalidUnblockCode())
|
||||
|
@ -1567,7 +1354,7 @@ describe('/account/login', function () {
|
|||
it('valid code', () => {
|
||||
mockDB.consumeUnblockCode = () => P.resolve({ createdAt: Date.now() })
|
||||
return runTest(route, mockRequestWithUnblockCode, (res) => {
|
||||
assert.equal(mockLog.flowEvent.callCount, 4)
|
||||
assert.equal(mockLog.flowEvent.callCount, 3)
|
||||
assert.equal(mockLog.flowEvent.args[0][0], 'account.login.blocked', 'first event was account.login.blocked')
|
||||
assert.equal(mockLog.flowEvent.args[1][0], 'account.login.confirmedUnblockCode', 'second event was account.login.confirmedUnblockCode')
|
||||
assert.equal(mockLog.flowEvent.args[2][0], 'account.login', 'third event was account.login')
|
||||
|
|
|
@ -36,9 +36,9 @@ describe('features', () => {
|
|||
assert.equal(Object.keys(features).length, 5, 'object should have four 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.isSigninConfirmationEnabledForUser, 'function', 'isSigninConfirmationEnabledForUser should be function')
|
||||
assert.equal(typeof features.isSigninUnblockEnabledForUser, 'function', 'isSigninUnblockEnabledForUser should be function')
|
||||
assert.equal(typeof features.canBypassSiginConfirmation, 'function', 'canBypassSiginConfirmation should be function')
|
||||
assert.equal(typeof features.isSecurityHistoryTrackingEnabled, 'function', 'isSecurityHistoryTrackingEnabled should be function')
|
||||
assert.equal(typeof features.isSecurityHistoryProfilingEnabled, 'function', 'isSecurityHistoryProfilingEnabled should be function')
|
||||
|
||||
assert.equal(crypto.createHash.callCount, 1, 'crypto.createHash should have been called once on require')
|
||||
let args = crypto.createHash.args[0]
|
||||
|
@ -182,50 +182,6 @@ describe('features', () => {
|
|||
}
|
||||
)
|
||||
|
||||
it(
|
||||
'isSigninConfirmationEnabledForUser',
|
||||
() => {
|
||||
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.forcedEmailAddresses = /.+@mozilla\.com$/
|
||||
config.signinConfirmation.supportedClients = [ 'wibble', 'iframe' ]
|
||||
assert.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when request is suspicious')
|
||||
|
||||
config.signinConfirmation.sample_rate = 0.02
|
||||
request.app.isSuspiciousRequest = false
|
||||
assert.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when email address matches')
|
||||
|
||||
config.signinConfirmation.forcedEmailAddresses = /.+@mozilla\.org$/
|
||||
request.payload.metricsContext.context = 'iframe'
|
||||
assert.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
|
||||
assert.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), true, 'should return true when sample rate and context match')
|
||||
|
||||
request.payload.metricsContext.context = ''
|
||||
assert.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), false, 'should return false when context does not match')
|
||||
|
||||
config.signinConfirmation.enabled = false
|
||||
request.payload.metricsContext.context = 'iframe'
|
||||
assert.equal(features.isSigninConfirmationEnabledForUser(uid, email, request), false, 'should return false when feature is disabled')
|
||||
}
|
||||
)
|
||||
|
||||
it(
|
||||
'isSigninUnblockEnabledForUser',
|
||||
() => {
|
||||
|
@ -272,39 +228,36 @@ describe('features', () => {
|
|||
)
|
||||
|
||||
it(
|
||||
'canBypassSiginConfirmation',
|
||||
'isSecurityHistoryTrackingEnabled',
|
||||
() => {
|
||||
const request = {}
|
||||
const securityEvents = []
|
||||
const forceEmail = 'test@force.com'
|
||||
const email = 'test@notforce.com'
|
||||
config.securityHistory.enabled = true
|
||||
assert.equal(features.isSecurityHistoryTrackingEnabled(), true, 'should return true when enabled in config')
|
||||
|
||||
config.securityHistory.enabled = false
|
||||
assert.equal(features.isSecurityHistoryTrackingEnabled(), false, 'should return false when disabled in config')
|
||||
}
|
||||
)
|
||||
|
||||
it(
|
||||
'isSecurityHistoryProfilingEnabled',
|
||||
() => {
|
||||
config.securityHistory.enabled = true
|
||||
config.securityHistory.ipProfiling = {
|
||||
enabled: true
|
||||
}
|
||||
assert.equal(features.canBypassSiginConfirmation(email, true, 'day', securityEvents, request), true, 'should return true if verified and recency within day')
|
||||
assert.equal(features.isSecurityHistoryProfilingEnabled(), true, 'should return true when everything is enabled in config')
|
||||
|
||||
config.securityHistory.enabled = true
|
||||
config.securityHistory.ipProfiling = {
|
||||
enabled: false
|
||||
}
|
||||
assert.equal(features.canBypassSiginConfirmation(email, true, 'day', securityEvents, request), false, 'should return false if profiling disabled')
|
||||
assert.equal(features.isSecurityHistoryProfilingEnabled(), false, 'should return false when profiling is disabled in config')
|
||||
|
||||
config.securityHistory.enabled = true
|
||||
config.securityHistory.enabled = false
|
||||
config.securityHistory.ipProfiling = {
|
||||
enabled: true
|
||||
}
|
||||
assert.equal(features.canBypassSiginConfirmation(email, true, 'week', securityEvents, request), false, 'should return false if verified but not within day')
|
||||
|
||||
config.securityHistory.enabled = false
|
||||
assert.equal(features.canBypassSiginConfirmation(email, true, 'day', securityEvents, request), false, 'should return false if security events disabled')
|
||||
|
||||
config.signinConfirmation.enabled = true
|
||||
config.signinConfirmation.sample_rate = 1
|
||||
config.signinConfirmation.forcedEmailAddresses = /.+@force\.com$/
|
||||
config.securityHistory.enabled = true
|
||||
assert.equal(features.canBypassSiginConfirmation(forceEmail, true, 'day', securityEvents, request), false, 'should return false if sign-in confirmation forced email')
|
||||
assert.equal(features.isSecurityHistoryProfilingEnabled(), false, 'should return false when tracking is disabled in config')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
@ -92,10 +92,7 @@ var config = {
|
|||
}
|
||||
},
|
||||
signinConfirmation: {
|
||||
forcedEmailAddresses: /.+@mozilla\.com$/,
|
||||
enabled: true,
|
||||
sample_rate: 1,
|
||||
supportedClients: ['fx_desktop_v3']
|
||||
forcedEmailAddresses: /.+@mozilla\.com$/
|
||||
},
|
||||
signinUnblock: {
|
||||
enabled: false
|
||||
|
@ -347,6 +344,75 @@ describe('IP Profiling', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it(
|
||||
'previously verified session with suspicious request',
|
||||
() => {
|
||||
mockRequest.payload.email = TEST_EMAIL
|
||||
|
||||
var mockDB = mocks.mockDB({
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: true,
|
||||
keyFetchTokenId: keyFetchTokenId,
|
||||
sessionTokenId: sessionTokenId,
|
||||
uid: uid
|
||||
})
|
||||
|
||||
mockDB.emailRecord = function () {
|
||||
return P.resolve({
|
||||
authSalt: crypto.randomBytes(32),
|
||||
data: crypto.randomBytes(32),
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: true,
|
||||
kA: crypto.randomBytes(32),
|
||||
lastAuthAt: function () {
|
||||
return Date.now()
|
||||
},
|
||||
uid: uid,
|
||||
wrapWrapKb: crypto.randomBytes(32)
|
||||
})
|
||||
}
|
||||
|
||||
mockDB.securityEvents = function () {
|
||||
return P.resolve([
|
||||
{
|
||||
name: 'account.login',
|
||||
createdAt: Date.now(),
|
||||
verified: true
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
var accountRoutes = makeRoutes({
|
||||
checkPassword: function () {
|
||||
return P.resolve(true)
|
||||
},
|
||||
config: config,
|
||||
customs: mockCustoms,
|
||||
db: mockDB,
|
||||
log: mockLog,
|
||||
mailer: mockMailer,
|
||||
push: mockPush
|
||||
})
|
||||
|
||||
mockRequest.app = {
|
||||
isSuspiciousRequest: true
|
||||
}
|
||||
|
||||
route = getRoute(accountRoutes, '/account/login')
|
||||
|
||||
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)
|
||||
})
|
||||
.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')
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockMailer.sendVerifyLoginEmail.reset()
|
||||
mockMailer.sendNewDeviceLoginNotification.reset()
|
||||
|
|
|
@ -29,7 +29,6 @@ describe('remote account preverified token', function() {
|
|||
let server
|
||||
before(() => {
|
||||
process.env.TRUSTED_JKUS = 'http://127.0.0.1:9000/.well-known/public-keys'
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
|
||||
return TestServer.start(config)
|
||||
.then(s => {
|
||||
|
|
|
@ -15,7 +15,6 @@ describe('remote account reset', function() {
|
|||
this.timeout(15000)
|
||||
let server
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
return TestServer.start(config)
|
||||
.then(s => {
|
||||
server = s
|
||||
|
@ -227,7 +226,6 @@ describe('remote account reset', function() {
|
|||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLE
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
/* 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 assert = require('insist')
|
||||
var TestServer = require('../test_server')
|
||||
const Client = require('../client')()
|
||||
|
||||
describe('remote account signin verification enable', function() {
|
||||
this.timeout(30000)
|
||||
it(
|
||||
'signin confirmation can be disabled',
|
||||
() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
var config = require('../../config').getProperties()
|
||||
var server, email, client
|
||||
var password = 'allyourbasearebelongtous'
|
||||
|
||||
return TestServer.start(config)
|
||||
.then(function main(serverObj) {
|
||||
server = serverObj
|
||||
email = server.uniqueEmail()
|
||||
})
|
||||
.then(function() {
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true})
|
||||
})
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
assert.ok(client.authAt, 'authAt was set')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
assert.equal(status.verified, true, 'account is verified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.login({keys:true})
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (response) {
|
||||
assert.notEqual(response.verificationMethod, 'email', 'verification method not set')
|
||||
assert.notEqual(response.verificationReason, 'login', 'verification reason not set')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
assert.equal(status.verified, true, 'account is verified')
|
||||
}
|
||||
)
|
||||
.then(function() {
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it(
|
||||
'signin confirmation can be enabled',
|
||||
() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = true
|
||||
process.env.SIGNIN_CONFIRMATION_RATE = 1.0
|
||||
var config = require('../../config').getProperties()
|
||||
var server, email, client
|
||||
var password = 'allyourbasearebelongtous'
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(serverObj) {
|
||||
server = serverObj
|
||||
email = server.uniqueEmail()
|
||||
})
|
||||
.then(function() {
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true})
|
||||
})
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
assert.ok(client.authAt, 'authAt was set')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
assert.equal(status.verified, true, 'account is verified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.login({keys:true})
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (response) {
|
||||
assert.equal(response.verificationMethod, 'email', 'verification method set')
|
||||
assert.equal(response.verificationReason, 'login', 'verification reason set')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (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')
|
||||
}
|
||||
)
|
||||
.then(function() {
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
TestServer.stop()
|
||||
})
|
||||
})
|
|
@ -22,8 +22,6 @@ describe('remote account signin verification', function() {
|
|||
this.timeout(30000)
|
||||
let server
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = true
|
||||
process.env.SIGNIN_CONFIRMATION_RATE = 1.0
|
||||
process.env.IP_PROFILING_ENABLED = false
|
||||
|
||||
return TestServer.start(config)
|
||||
|
|
|
@ -18,7 +18,6 @@ describe('remote flow', function() {
|
|||
let server
|
||||
let email1
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
return TestServer.start(config)
|
||||
.then(s => {
|
||||
server = s
|
||||
|
@ -81,7 +80,7 @@ describe('remote flow', function() {
|
|||
'e': '65537'
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24 // 24 hours
|
||||
return Client.login(config.publicUrl, email, password, server.mailbox, {keys:true})
|
||||
return Client.loginAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true})
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
|
@ -112,7 +111,6 @@ describe('remote flow', function() {
|
|||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -24,8 +24,6 @@ describe('remote password change', function() {
|
|||
this.timeout(15000)
|
||||
let server
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = true
|
||||
process.env.SIGNIN_CONFIRMATION_RATE = 1.0
|
||||
process.env.IP_PROFILING_ENABLED = false
|
||||
|
||||
return TestServer.start(config)
|
||||
|
@ -395,8 +393,6 @@ describe('remote password change', function() {
|
|||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
delete process.env.SIGNIN_CONFIRMATION_RATE
|
||||
delete process.env.IP_PROFILING_ENABLED
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
|
|
|
@ -17,7 +17,6 @@ describe('remote password forgot', function() {
|
|||
this.timeout(15000)
|
||||
let server
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
return TestServer.start(config)
|
||||
.then(s => {
|
||||
server = s
|
||||
|
@ -423,7 +422,6 @@ describe('remote password forgot', function() {
|
|||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ describe('remote recovery email resend code', function() {
|
|||
this.timeout(15000)
|
||||
let server
|
||||
before(() => {
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = true
|
||||
process.env.SIGNIN_CONFIRMATION_RATE = 1.0
|
||||
process.env.IP_PROFILING_ENABLED = false
|
||||
|
||||
return TestServer.start(config)
|
||||
|
@ -149,8 +147,6 @@ describe('remote recovery email resend code', function() {
|
|||
)
|
||||
|
||||
after(() => {
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
delete process.env.SIGNIN_CONFIRMATION_RATE
|
||||
delete process.env.IP_PROFILING_ENABLED
|
||||
return TestServer.stop(server)
|
||||
})
|
||||
|
|
|
@ -29,7 +29,6 @@ describe('remote verifier upgrade', function() {
|
|||
|
||||
before(() => {
|
||||
process.env.VERIFIER_VERSION = '0'
|
||||
process.env.SIGNIN_CONFIRMATION_ENABLED = false
|
||||
})
|
||||
|
||||
it(
|
||||
|
@ -84,7 +83,7 @@ describe('remote verifier upgrade', function() {
|
|||
.then(
|
||||
function (server) {
|
||||
var client
|
||||
return Client.login(config.publicUrl, email, password, server.mailbox)
|
||||
return Client.loginAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
|
@ -139,6 +138,5 @@ describe('remote verifier upgrade', function() {
|
|||
|
||||
after(() => {
|
||||
delete process.env.VERIFIER_VERSION
|
||||
delete process.env.SIGNIN_CONFIRMATION_ENABLED
|
||||
})
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче