зеркало из https://github.com/mozilla/fxa.git
feat(emails): Send `New Device Login` email on all new logins
This commit is contained in:
Родитель
42811d1693
Коммит
df857c569d
|
@ -17,7 +17,8 @@
|
|||
"format": "yarn workspaces foreach run format",
|
||||
"ports": "pm2 jlist | json -a -c 'this.pm2_env.env.PORT' pm2_env.env.PORT name",
|
||||
"heroku-postbuild": "yarn workspaces foreach --verbose --include 123done install",
|
||||
"mysql": "docker exec -it $(docker container ls | grep mysql | cut -d' ' -f1) mysql"
|
||||
"mysql": "docker exec -it $(docker container ls | grep mysql | cut -d' ' -f1) mysql",
|
||||
"firefox": "./packages/fxa-dev-launcher/bin/fxa-dev-launcher.mjs"
|
||||
},
|
||||
"homepage": "https://github.com/mozilla/fxa",
|
||||
"bugs": {
|
||||
|
|
|
@ -1011,50 +1011,48 @@ export class AccountHandler {
|
|||
verificationMethod
|
||||
);
|
||||
|
||||
// For new sync logins that don't send some other sort of email,
|
||||
// For new logins that don't send some other sort of email,
|
||||
// send an after-the-fact notification email so that the user
|
||||
// is aware that something happened on their account.
|
||||
if (accountRecord.primaryEmail.isVerified) {
|
||||
if (sessionToken.tokenVerified || !sessionToken.mustVerify) {
|
||||
if (requestHelper.wantsKeys(request)) {
|
||||
const geoData = request.app.geo;
|
||||
const service =
|
||||
(request.payload as any).service || request.query.service;
|
||||
const ip = request.app.clientAddress;
|
||||
const { deviceId, flowId, flowBeginTime } = await request.app
|
||||
.metricsContext;
|
||||
const geoData = request.app.geo;
|
||||
const service =
|
||||
(request.payload as any).service || request.query.service;
|
||||
const ip = request.app.clientAddress;
|
||||
const { deviceId, flowId, flowBeginTime } = await request.app
|
||||
.metricsContext;
|
||||
|
||||
try {
|
||||
await this.mailer.sendNewDeviceLoginEmail(
|
||||
accountRecord.emails,
|
||||
accountRecord,
|
||||
{
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
deviceId,
|
||||
flowId,
|
||||
flowBeginTime,
|
||||
ip,
|
||||
location: geoData.location,
|
||||
service,
|
||||
timeZone: geoData.timeZone,
|
||||
uaBrowser: request.app.ua.browser,
|
||||
uaBrowserVersion: request.app.ua.browserVersion,
|
||||
uaOS: request.app.ua.os,
|
||||
uaOSVersion: request.app.ua.osVersion,
|
||||
uaDeviceType: request.app.ua.deviceType,
|
||||
uid: sessionToken.uid,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
// If we couldn't email them, no big deal. Log
|
||||
// and pretend everything worked.
|
||||
this.log.trace(
|
||||
'Account.login.sendNewDeviceLoginNotification.error',
|
||||
{
|
||||
error: err,
|
||||
}
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.mailer.sendNewDeviceLoginEmail(
|
||||
accountRecord.emails,
|
||||
accountRecord,
|
||||
{
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
deviceId,
|
||||
flowId,
|
||||
flowBeginTime,
|
||||
ip,
|
||||
location: geoData.location,
|
||||
service,
|
||||
timeZone: geoData.timeZone,
|
||||
uaBrowser: request.app.ua.browser,
|
||||
uaBrowserVersion: request.app.ua.browserVersion,
|
||||
uaOS: request.app.ua.os,
|
||||
uaOSVersion: request.app.ua.osVersion,
|
||||
uaDeviceType: request.app.ua.deviceType,
|
||||
uid: sessionToken.uid,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
// If we couldn't email them, no big deal. Log
|
||||
// and pretend everything worked.
|
||||
this.log.trace(
|
||||
'Account.login.sendNewDeviceLoginNotification.error',
|
||||
{
|
||||
error: err,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -335,21 +335,17 @@ module.exports = (log, db, mailer, customs, config) => {
|
|||
};
|
||||
|
||||
// Check to see if this token was just verified, if it is, then this means
|
||||
// the user has enabled two step authentication, otherwise send new device
|
||||
// the user has enabled two-step authentication, otherwise send new device
|
||||
// login email.
|
||||
if (isValidCode && !tokenVerified) {
|
||||
return mailer.sendPostAddTwoStepAuthenticationEmail(
|
||||
account.emails,
|
||||
account,
|
||||
emailOptions
|
||||
);
|
||||
}
|
||||
if (isValidCode) {
|
||||
if (!tokenVerified) {
|
||||
return mailer.sendPostAddTwoStepAuthenticationEmail(
|
||||
account.emails,
|
||||
account,
|
||||
emailOptions
|
||||
);
|
||||
}
|
||||
|
||||
// All accounts that have a TOTP token, force the session to be verified, therefore
|
||||
// we can not check `session.mustVerify=true` to determine sending the new device
|
||||
// login email. Instead, lets perform a basic check that the service is `sync`, otherwise
|
||||
// don't send.
|
||||
if (isValidCode && service === 'sync') {
|
||||
return mailer.sendNewDeviceLoginEmail(
|
||||
account.emails,
|
||||
account,
|
||||
|
|
|
@ -12,7 +12,7 @@ export default {
|
|||
|
||||
const createStory = storyWithProps(
|
||||
'newDeviceLogin',
|
||||
'Sent to notify the account that a new device has signed in.',
|
||||
'Sent to notify the account that a new device or service has signed in.',
|
||||
{
|
||||
...MOCK_USER_INFO,
|
||||
clientName: 'Firefox',
|
||||
|
|
|
@ -2575,9 +2575,7 @@ describe('/account/login', () => {
|
|||
tokenData.tokenVerificationId,
|
||||
'sessionToken was created unverified'
|
||||
);
|
||||
// Note that *neither* email is sent in this case,
|
||||
// since it can't have been a new device connection.
|
||||
assert.equal(mockMailer.sendNewDeviceLoginEmail.callCount, 0);
|
||||
assert.equal(mockMailer.sendNewDeviceLoginEmail.callCount, 1);
|
||||
assert.equal(
|
||||
mockMailer.sendVerifyLoginEmail.callCount,
|
||||
0,
|
||||
|
|
|
@ -380,7 +380,7 @@ describe('totp', () => {
|
|||
);
|
||||
|
||||
// correct emails sent
|
||||
assert.equal(mailer.sendNewDeviceLoginEmail.callCount, 0);
|
||||
assert.equal(mailer.sendNewDeviceLoginEmail.callCount, 1);
|
||||
assert.equal(mailer.sendPostAddTwoStepAuthenticationEmail.callCount, 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -331,6 +331,10 @@ describe('remote account signin verification', function () {
|
|||
.then((c) => {
|
||||
client2 = c;
|
||||
})
|
||||
.then(() => {
|
||||
// Clears inbox of new signin email
|
||||
return server.mailbox.waitForEmail(email);
|
||||
})
|
||||
.then(() => {
|
||||
return client2.login(options);
|
||||
})
|
||||
|
|
|
@ -109,6 +109,10 @@ describe('remote recovery email resend code', function () {
|
|||
.then((c) => {
|
||||
client2 = c;
|
||||
})
|
||||
.then(() => {
|
||||
// Clears inbox of new signin email
|
||||
return server.mailbox.waitForEmail(email);
|
||||
})
|
||||
.then(() => {
|
||||
return client2.login(options);
|
||||
})
|
||||
|
|
|
@ -447,6 +447,10 @@ describe('remote session', function () {
|
|||
})
|
||||
.then((x) => {
|
||||
client = x;
|
||||
// Clears inbox of new signin email
|
||||
return server.mailbox.waitForEmail(email);
|
||||
})
|
||||
.then(() => {
|
||||
return client.sessionStatus();
|
||||
})
|
||||
.then((status) => {
|
||||
|
|
|
@ -303,7 +303,7 @@ registerSuite('oauth reset password with TOTP', {
|
|||
tests: {
|
||||
'reset password, verify same browser same tab': function () {
|
||||
return this.remote
|
||||
.then(openVerificationLinkInSameTab(email, 1))
|
||||
.then(openVerificationLinkInSameTab(email, 2))
|
||||
.then(fillOutCompleteResetPassword(PASSWORD, PASSWORD))
|
||||
|
||||
.then(testElementExists(selectors.TOTP_SIGNIN.HEADER))
|
||||
|
@ -320,7 +320,7 @@ registerSuite('oauth reset password with TOTP', {
|
|||
|
||||
'reset password, verify same browser different tab': function () {
|
||||
return this.remote
|
||||
.then(openVerificationLinkInNewTab(email, 1))
|
||||
.then(openVerificationLinkInNewTab(email, 2))
|
||||
.then(switchToWindow(1))
|
||||
.then(fillOutCompleteResetPassword(PASSWORD, PASSWORD))
|
||||
|
||||
|
@ -342,7 +342,7 @@ registerSuite('oauth reset password with TOTP', {
|
|||
function () {
|
||||
return (
|
||||
this.remote
|
||||
.then(openPasswordResetLinkInDifferentBrowser(email, PASSWORD, 1))
|
||||
.then(openPasswordResetLinkInDifferentBrowser(email, PASSWORD, 2))
|
||||
|
||||
// user verified in a new browser, they have to enter
|
||||
// their password in the original tab.
|
||||
|
@ -373,7 +373,7 @@ registerSuite('oauth reset password with TOTP', {
|
|||
this.remote
|
||||
// clear all browser state, simulate opening in a new browser
|
||||
.then(clearBrowserState({ forceAll: true }))
|
||||
.then(openVerificationLinkInSameTab(email, 1))
|
||||
.then(openVerificationLinkInSameTab(email, 2))
|
||||
.then(fillOutCompleteResetPassword(PASSWORD, PASSWORD))
|
||||
|
||||
// this tab's success is seeing the reset password complete header.
|
||||
|
|
|
@ -144,7 +144,7 @@ registerSuite('signin to Sync after OAuth', {
|
|||
.then(click(selectors.SIGNIN_PASSWORD.SUBMIT))
|
||||
|
||||
.then(testElementExists(selectors.SIGNIN_TOKEN_CODE.HEADER))
|
||||
.then(fillOutSignInTokenCode(email, 0))
|
||||
.then(fillOutSignInTokenCode(email, 1))
|
||||
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER));
|
||||
},
|
||||
},
|
||||
|
|
|
@ -304,7 +304,7 @@ registerSuite('Recovery key - unverified session', {
|
|||
.then(click(selectors.RECOVERY_KEY.GENERATE_KEY_BUTTON))
|
||||
// if the session is unverified, then the modal will be shown.
|
||||
.then(testElementExists('[data-testid=modal-verify-session]'))
|
||||
.then(fillOutVerificationCode(email, 0))
|
||||
.then(fillOutVerificationCode(email, 1))
|
||||
.then(
|
||||
testElementExists(
|
||||
selectors.SETTINGS.SECURITY.RECOVERY_KEY.PASSWORD_TEXTBOX_LABEL
|
||||
|
|
|
@ -311,7 +311,7 @@ registerSuite('TOTP - unverified session', {
|
|||
.then(testElementExists(selectors.TOTP.UNLOCK_SEND_VERIFY))
|
||||
|
||||
// send and open verification in same tab
|
||||
.then(fillOutVerificationCode(email, 0))
|
||||
.then(fillOutVerificationCode(email, 1))
|
||||
|
||||
// panel becomes verified and can be opened
|
||||
.then(
|
||||
|
|
|
@ -399,7 +399,7 @@ registerSuite('signin blocked', {
|
|||
)
|
||||
|
||||
.then(testElementExists(selectors.SIGNIN_UNBLOCK.HEADER))
|
||||
.then(fillOutSignInUnblock(email, 2))
|
||||
.then(fillOutSignInUnblock(email, 3))
|
||||
|
||||
.then(testElementExists(selectors.SETTINGS.HEADER))
|
||||
);
|
||||
|
|
|
@ -388,7 +388,7 @@ registerSuite('cached signin', {
|
|||
selectors.SIGNIN_TOKEN_CODE.HEADER
|
||||
)
|
||||
)
|
||||
.then(fillOutSignInTokenCode(email, 0))
|
||||
.then(fillOutSignInTokenCode(email, 1))
|
||||
|
||||
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
|
||||
.then(testIsBrowserNotified('fxaccounts:login'))
|
||||
|
|
Загрузка…
Ссылка в новой задаче