diff --git a/lib/routes/account.js b/lib/routes/account.js index b1fc2cc4..22987ab8 100644 --- a/lib/routes/account.js +++ b/lib/routes/account.js @@ -276,14 +276,19 @@ module.exports = function ( if (! account.emailVerified) { return getGeoData(ip) .then(function (geoData) { - mailer.sendVerifyCode(account, account.emailCode, userAgent.call({ + mailer.sendVerifyCode(account, account.emailCode, { service: form.service || query.service, redirectTo: form.redirectTo, resume: form.resume, acceptLanguage: request.app.acceptLanguage, ip: ip, - location: geoData.location - }, request.headers['user-agent'], log)) + location: geoData.location, + uaBrowser: sessionToken.uaBrowser, + uaBrowserVersion: sessionToken.uaBrowserVersion, + uaOS: sessionToken.uaOS, + uaOSVersion: sessionToken.uaOSVersion, + uaDeviceType: sessionToken.uaDeviceType + }) .then(function () { // only create reminder if sendVerifyCode succeeds verificationReminder.create({ @@ -825,14 +830,19 @@ module.exports = function ( return getGeoData(ip) .then( function (geoData) { - return mailer.sendVerifyCode(emailRecord, emailCode, userAgent.call({ + return mailer.sendVerifyCode(emailRecord, emailCode, { service: service, redirectTo: redirectTo, resume: resume, acceptLanguage: request.app.acceptLanguage, ip: ip, - location: geoData.location - }, request.headers['user-agent'], log)) + location: geoData.location, + uaBrowser: sessionToken.uaBrowser, + uaBrowserVersion: sessionToken.uaBrowserVersion, + uaOS: sessionToken.uaOS, + uaOSVersion: sessionToken.uaOSVersion, + uaDeviceType: sessionToken.uaDeviceType + }) } ) .then(() => request.emitMetricsEvent('email.verification.sent')) @@ -855,12 +865,17 @@ module.exports = function ( function (geoData) { mailer.sendNewDeviceLoginNotification( emailRecord.email, - userAgent.call({ + { acceptLanguage: request.app.acceptLanguage, ip: ip, location: geoData.location, - timeZone: geoData.timeZone - }, request.headers['user-agent'], log) + timeZone: geoData.timeZone, + uaBrowser: sessionToken.uaBrowser, + uaBrowserVersion: sessionToken.uaBrowserVersion, + uaOS: sessionToken.uaOS, + uaOSVersion: sessionToken.uaOSVersion, + uaDeviceType: sessionToken.uaDeviceType + } ) } ) @@ -881,15 +896,20 @@ module.exports = function ( return mailer.sendVerifyLoginEmail( emailRecord, tokenVerificationId, - userAgent.call({ + { acceptLanguage: request.app.acceptLanguage, ip: ip, location: geoData.location, redirectTo: redirectTo, resume: resume, service: service, - timeZone: geoData.timeZone - }, request.headers['user-agent'], log) + timeZone: geoData.timeZone, + uaBrowser: sessionToken.uaBrowser, + uaBrowserVersion: sessionToken.uaBrowserVersion, + uaOS: sessionToken.uaOS, + uaOSVersion: sessionToken.uaOSVersion, + uaDeviceType: sessionToken.uaDeviceType + } ) } ) @@ -1557,13 +1577,18 @@ module.exports = function ( mailer, sessionToken, code, - userAgent.call({ + { service: service, timestamp: Date.now(), redirectTo: request.payload.redirectTo, resume: request.payload.resume, - acceptLanguage: request.app.acceptLanguage - }, request.headers['user-agent'], log) + acceptLanguage: request.app.acceptLanguage, + uaBrowser: sessionToken.uaBrowser, + uaBrowserVersion: sessionToken.uaBrowserVersion, + uaOS: sessionToken.uaOS, + uaOSVersion: sessionToken.uaOSVersion, + uaDeviceType: sessionToken.uaDeviceType + } )) .then(() => request.emitMetricsEvent(`email.${event}.resent`)) .done( diff --git a/test/local/account_routes.js b/test/local/account_routes.js index ec4a39e3..1af19a0b 100644 --- a/test/local/account_routes.js +++ b/test/local/account_routes.js @@ -245,7 +245,11 @@ describe('/recovery_email/resend_code', () => { uid: uuid.v4('binary').toString('hex'), email: TEST_EMAIL, emailVerified: false, - tokenVerified: false + tokenVerified: false, + uaBrowser: 'Firefox', + uaBrowserVersion: 52, + uaOS: 'Mac OS X', + uaOSVersion: '10.10' }, query: {}, payload: { @@ -260,6 +264,14 @@ describe('/recovery_email/resend_code', () => { return runTest(route, mockRequest, response => { assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once') assert.equal(mockLog.flowEvent.args[0][0], 'email.verification.resent') + + assert.equal(mockMailer.sendVerifyCode.callCount, 1) + const args = mockMailer.sendVerifyCode.args[0] + assert.equal(args[2].uaBrowser, 'Firefox') + assert.equal(args[2].uaBrowserVersion, '52') + assert.equal(args[2].uaOS, 'Mac OS X') + assert.equal(args[2].uaOSVersion, '10.10') + assert.strictEqual(args[2].uaDeviceType, undefined) }) }) @@ -271,7 +283,12 @@ describe('/recovery_email/resend_code', () => { uid: uuid.v4('binary').toString('hex'), email: TEST_EMAIL, emailVerified: true, - tokenVerified: false + tokenVerified: false, + uaBrowser: 'Firefox', + uaBrowserVersion: '50', + uaOS: 'Android', + uaOSVersion: '6', + uaDeviceType: 'tablet' }, query: {}, payload: { @@ -286,6 +303,14 @@ describe('/recovery_email/resend_code', () => { return runTest(route, mockRequest, response => { assert.equal(mockLog.flowEvent.callCount, 1, 'log.flowEvent called once') assert.equal(mockLog.flowEvent.args[0][0], 'email.confirmation.resent') + + assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1) + const args = mockMailer.sendVerifyLoginEmail.args[0] + assert.equal(args[2].uaBrowser, 'Firefox') + assert.equal(args[2].uaBrowserVersion, '50') + assert.equal(args[2].uaOS, 'Android') + assert.equal(args[2].uaOSVersion, '6') + assert.strictEqual(args[2].uaDeviceType, 'tablet') }) }) @@ -744,6 +769,10 @@ describe('/account/create', function () { emailVerified: false, keyFetchTokenId: keyFetchTokenId, sessionTokenId: sessionTokenId, + uaBrowser: 'Firefox', + uaBrowserVersion: 52, + uaOS: 'Mac OS X', + uaOSVersion: '10.10', uid: uid, wrapWrapKb: 'wibble' }, { @@ -827,8 +856,14 @@ describe('/account/create', function () { assert.equal(securityEvent.ipAddr, clientAddress) assert.equal(mockMailer.sendVerifyCode.callCount, 1, 'mailer.sendVerifyCode was called') - assert.equal(mockMailer.sendVerifyCode.getCall(0).args[2].location.city, 'Mountain View') - assert.equal(mockMailer.sendVerifyCode.getCall(0).args[2].location.country, 'United States') + args = mockMailer.sendVerifyCode.args[0] + assert.equal(args[2].location.city, 'Mountain View') + assert.equal(args[2].location.country, 'United States') + assert.equal(args[2].uaBrowser, 'Firefox') + assert.equal(args[2].uaBrowserVersion, '52') + assert.equal(args[2].uaOS, 'Mac OS X') + assert.equal(args[2].uaOSVersion, '10.10') + assert.strictEqual(args[2].uaDeviceType, undefined) }) }) }) @@ -926,6 +961,11 @@ describe('/account/login', function () { emailVerified: true, keyFetchTokenId: keyFetchTokenId, sessionTokenId: sessionTokenId, + uaBrowser: 'Firefox', + uaBrowserVersion: 50, + uaOS: 'Android', + uaOSVersion: '6', + uaDeviceType: 'mobile', uid: uid }) var mockMailer = mocks.mockMailer() @@ -1022,9 +1062,16 @@ describe('/account/login', function () { assert.deepEqual(args[0], 'account.signed', 'argument was event name') 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') + args = mockMailer.sendVerifyLoginEmail.args[0] + assert.equal(args[2].location.city, 'Mountain View') + assert.equal(args[2].location.country, 'United States') + assert.equal(args[2].timeZone, 'America/Los_Angeles') + assert.equal(args[2].uaBrowser, 'Firefox') + assert.equal(args[2].uaBrowserVersion, '50') + assert.equal(args[2].uaOS, 'Android') + assert.equal(args[2].uaOSVersion, '6') + assert.equal(args[2].uaDeviceType, 'mobile') + assert.equal(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') diff --git a/test/mocks.js b/test/mocks.js index d6bad043..90ea5b08 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -162,6 +162,11 @@ function mockDB (data, errors) { tokenId: data.sessionTokenId, tokenVerificationId: data.tokenVerificationId, tokenVerified: ! data.tokenVerificationId, + uaBrowser: data.uaBrowser, + uaBrowserVersion: data.uaBrowserVersion, + uaOS: data.uaOS, + uaOSVersion: data.uaOSVersion, + uaDeviceType: data.uaDeviceType, uid: data.uid }) }), @@ -200,7 +205,12 @@ function mockDB (data, errors) { }), sessionTokenWithVerificationStatus: sinon.spy(() => { return P.resolve({ - tokenVerified: true + tokenVerified: true, + uaBrowser: data.uaBrowser, + uaBrowserVersion: data.uaBrowserVersion, + uaOS: data.uaOS, + uaOSVersion: data.uaOSVersion, + uaDeviceType: data.uaDeviceType }) }), verifyTokens: sinon.spy(() => {