Skip to content

Commit e06cbd7

Browse files
committed
feat(emails): send email after sms code used as 2fa in reset
1 parent 91c0ef3 commit e06cbd7

12 files changed

Lines changed: 171 additions & 2 deletions

File tree

packages/fxa-auth-server/lib/metrics/amplitude.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const EMAIL_TYPES = {
6767
postConsumeRecoveryCode: '2fa',
6868
postNewRecoveryCodes: '2fa',
6969
postAddRecoveryPhone: '2fa',
70+
passwordResetRecoveryPhone: 'account_recovery',
7071
postRemoveRecoveryPhone: '2fa',
7172
postChangeRecoveryPhone: '2fa',
7273
postSigninRecoveryPhone: 'login',

packages/fxa-auth-server/lib/routes/recovery-phone.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,32 @@ class RecoveryPhoneHandler {
565565
request,
566566
});
567567

568-
// @TODO send email in FXA-11600
568+
const account = await this.db.account(uid);
569+
const { acceptLanguage, geo, ua } = request.app;
570+
571+
try {
572+
await this.mailer.sendPasswordResetRecoveryPhoneEmail(
573+
account.emails,
574+
account,
575+
{
576+
acceptLanguage,
577+
timeZone: geo.timeZone,
578+
uaBrowser: ua.browser,
579+
uaBrowserVersion: ua.browserVersion,
580+
uaOS: ua.os,
581+
uaOSVersion: ua.osVersion,
582+
uaDeviceType: ua.deviceType,
583+
uid,
584+
}
585+
);
586+
} catch (error) {
587+
this.log.error(
588+
'account.recoveryPhone.phonePasswordResetNotification.error',
589+
{
590+
error,
591+
}
592+
);
593+
}
569594

570595
return { status: RecoveryPhoneStatus.SUCCESS };
571596
}

packages/fxa-auth-server/lib/senders/email.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ module.exports = function (log, config, bounces, statsd) {
7575
passwordForgotOtp: 'password-forgot-otp',
7676
passwordReset: 'password-reset-success',
7777
passwordResetAccountRecovery: 'password-reset-account-recovery-success',
78+
passwordResetRecoveryPhone: 'password-reset-recovery-phone',
7879
passwordResetWithRecoveryKeyPrompt: 'password-reset-w-recovery-key-prompt',
7980
postAddLinkedAccount: 'account-linked',
8081
postRemoveSecondary: 'account-email-removed',
@@ -136,6 +137,7 @@ module.exports = function (log, config, bounces, statsd) {
136137
passwordForgotOtp: 'password-reset',
137138
passwordReset: 'password-reset',
138139
passwordResetAccountRecovery: 'manage-account',
140+
passwordResetRecoveryPhone: 'manage-account',
139141
passwordResetWithRecoveryKeyPrompt: 'create-recovery-key',
140142
postAddLinkedAccount: 'manage-account',
141143
postRemoveSecondary: 'account-email-removed',
@@ -1696,6 +1698,38 @@ module.exports = function (log, config, bounces, statsd) {
16961698
});
16971699
};
16981700

1701+
Mailer.prototype.passwordResetRecoveryPhoneEmail = function (message) {
1702+
const templateName = 'passwordResetRecoveryPhone';
1703+
const links = this._generateSettingLinks(message, templateName);
1704+
const [time, date] = this._constructLocalTimeString(
1705+
message.timeZone,
1706+
message.acceptLanguage
1707+
);
1708+
1709+
const headers = {
1710+
'X-Link': links.link,
1711+
};
1712+
1713+
return this.send({
1714+
...message,
1715+
headers,
1716+
template: templateName,
1717+
templateValues: {
1718+
date,
1719+
device: this._formatUserAgentInfo(message),
1720+
privacyUrl: links.privacyUrl,
1721+
link: links.link,
1722+
resetLink: links.resetLink,
1723+
resetLinkAttributes: links.resetLinkAttributes,
1724+
supportLinkAttributes: links.supportLinkAttributes,
1725+
supportUrl: links.supportUrl,
1726+
twoFactorSettingsLink: links.twoFactorSettingsLink,
1727+
twoFactorSettingsLinkAttributes: links.twoFactorSettingsLinkAttributes,
1728+
time,
1729+
},
1730+
});
1731+
};
1732+
16991733
Mailer.prototype.postRemoveRecoveryPhoneEmail = function (message) {
17001734
const templateName = 'postRemoveRecoveryPhone';
17011735
const links = this._generateLinks(
@@ -3054,7 +3088,6 @@ module.exports = function (log, config, bounces, statsd) {
30543088
templateName,
30553089
content
30563090
) {
3057-
console.log(link);
30583091
const parsedLink = new URL(link);
30593092

30603093
Object.keys(query).forEach((key) => {

packages/fxa-auth-server/lib/senders/emails/templates/_versions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"passwordChangeRequired": 3,
2424
"passwordForgotOtp": 1,
2525
"passwordReset": 6,
26+
"passwordResetRecoveryPhone": 1,
2627
"postAddLinkedAccount": 1,
2728
"postChangePrimary": 5,
2829
"postRemoveSecondary": 5,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
passwordResetRecoveryPhone-subject = Recovery phone used
2+
passwordResetRecoveryPhone-preview = Check to make sure this was you
3+
passwordResetRecoveryPhone-title = Your recovery phone was used to confirm a password reset
4+
passwordResetRecoveryPhone-device = Recovery phone used from:
5+
passwordResetRecoveryPhone-action = Manage account
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"subject": {
3+
"id": "passwordResetRecoveryPhone-subject",
4+
"message": "Recovery phone used"
5+
},
6+
"action": {
7+
"id": "passwordResetRecoveryPhone-action",
8+
"message": "Manage account"
9+
},
10+
"preview": {
11+
"id": "passwordResetRecoveryPhone-preview",
12+
"message": "Check to make sure this was you"
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<%# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/. %>
4+
5+
<mj-section>
6+
<mj-column>
7+
<mj-text css-class="text-header">
8+
<span data-l10n-id="passwordResetRecoveryPhone-title">Your recovery phone was used to confirm a password reset</span>
9+
</mj-text>
10+
11+
<mj-text css-class="text-body">
12+
<span data-l10n-id="passwordResetRecoveryPhone-device">Recovery phone used from:</span>
13+
</mj-text>
14+
</mj-column>
15+
</mj-section>
16+
<%- include('/partials/userInfo/index.mjml') %>
17+
18+
<%- include('/partials/button/index.mjml', {
19+
buttonL10nId: "passwordResetRecoveryPhone-action",
20+
buttonText: "Manage account"
21+
}) %>
22+
23+
<%- include('/partials/automatedEmailResetPasswordTwoFactor/index.mjml') %>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { Meta } from '@storybook/html';
6+
import { storyWithProps } from '../../storybook-email';
7+
import { MOCK_USER_INFO } from '../../partials/userInfo/mocks';
8+
9+
export default {
10+
title: 'FxA Emails/Templates/passwordResetRecoveryPhone',
11+
} as Meta;
12+
13+
const createStory = storyWithProps(
14+
'passwordResetRecoveryPhone',
15+
'Sent when a user uses their recovery phone to reset their password as an alternative to their authenticator app.',
16+
{
17+
...MOCK_USER_INFO,
18+
link: 'http://localhost:3030/settings',
19+
resetLink: 'http://localhost:3030/reset_password',
20+
}
21+
);
22+
23+
export const passwordResetRecoveryPhone = createStory();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
passwordResetRecoveryPhone-title = "Your recovery phone was used to confirm a password reset"
2+
3+
passwordResetRecoveryPhone-device = "Recovery phone used from:"
4+
<%- include('/partials/userInfo/index.txt') %>
5+
6+
<%- include('/partials/manageAccount/index.txt') %>
7+
8+
<%- include('/partials/automatedEmailResetPasswordTwoFactor/index.txt') %>
9+
10+
<%- include('/partials/support/index.txt') %>

packages/fxa-auth-server/test/local/routes/recovery-phone.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ describe('/recovery_phone', () => {
743743
mockStatsd.increment,
744744
'account.resetPassword.recoveryPhone.success'
745745
);
746+
assert.calledOnce(mockMailer.sendPasswordResetRecoveryPhoneEmail);
746747
});
747748

748749
it('fails confirms a code during signin', async () => {

0 commit comments

Comments
 (0)