Skip to content

Commit 2fcd519

Browse files
authored
Merge pull request #18848 from mozilla/FXA-11497
feat(recovery phone): add backend routes for sms code in password reset
2 parents df5cae1 + 9ef7d5c commit 2fcd519

17 files changed

Lines changed: 847 additions & 49 deletions

File tree

libs/accounts/recovery-phone/src/lib/recovery-phone.service.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ describe('RecoveryPhoneService', () => {
462462
it('can confirm valid sms code', async () => {
463463
mockRecoveryPhoneManager.getUnconfirmed.mockReturnValue({});
464464

465-
const result = await service.confirmSigninCode(uid, code);
465+
const result = await service.confirmCode(uid, code);
466466

467467
expect(result).toBeTruthy();
468468
expect(mockRecoveryPhoneManager.getUnconfirmed).toBeCalledWith(uid, code);
@@ -473,15 +473,15 @@ describe('RecoveryPhoneService', () => {
473473
isSetup: true,
474474
});
475475

476-
const result = await service.confirmSigninCode(uid, code);
476+
const result = await service.confirmCode(uid, code);
477477

478478
expect(result).toBeFalsy();
479479
});
480480

481481
it('will not confirm unknown sms code used', async () => {
482482
mockRecoveryPhoneManager.getUnconfirmed.mockReturnValue(null);
483483

484-
const result = await service.confirmSigninCode(uid, code);
484+
const result = await service.confirmCode(uid, code);
485485

486486
expect(result).toBeFalsy();
487487
});
@@ -692,7 +692,7 @@ describe('RecoveryPhoneService', () => {
692692
expect(service.confirmSetupCode(uid, '000000')).rejects.toEqual(
693693
new RecoveryPhoneNotEnabled()
694694
);
695-
expect(service.confirmSigninCode(uid, '000000')).rejects.toEqual(
695+
expect(service.confirmCode(uid, '000000')).rejects.toEqual(
696696
new RecoveryPhoneNotEnabled()
697697
);
698698
expect(service.sendCode(uid, mockGetFormattedMessage)).rejects.toEqual(

libs/accounts/recovery-phone/src/lib/recovery-phone.service.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,8 @@ export class RecoveryPhoneService {
224224
// OTP confirm page before then. "Basic lookups" from Twilio are free, so don't
225225
// bother persisting in redis.
226226
// https://www.twilio.com/en-us/user-authentication-identity/pricing/lookup
227-
const { nationalFormat } = await this.smsManager.phoneNumberLookup(
228-
phoneNumber
229-
);
227+
const { nationalFormat } =
228+
await this.smsManager.phoneNumberLookup(phoneNumber);
230229
return nationalFormat;
231230
}
232231

@@ -322,7 +321,7 @@ export class RecoveryPhoneService {
322321
* @param code A otp code
323322
* @returns True if successful
324323
*/
325-
public async confirmSigninCode(uid: string, code: string) {
324+
public async confirmCode(uid: string, code: string) {
326325
if (!this.config.enabled) {
327326
throw new RecoveryPhoneNotEnabled();
328327
}
@@ -353,9 +352,8 @@ export class RecoveryPhoneService {
353352
* @returns True if successful
354353
*/
355354
public async removePhoneNumber(uid: string) {
356-
const hasRecoveryCodes = await this.recoveryPhoneManager.hasRecoveryCodes(
357-
uid
358-
);
355+
const hasRecoveryCodes =
356+
await this.recoveryPhoneManager.hasRecoveryCodes(uid);
359357

360358
// TBD: Random, obs. why do we do this? It seems like a potential edge case, what if the user
361359
// consumes all the recovery codes? This could then return false...
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SET NAMES utf8mb4 COLLATE utf8mb4_bin;
2+
3+
CALL assertPatchLevel('168');
4+
5+
INSERT INTO securityEventNames(name) VALUES ('account.recovery_phone_reset_password_complete'), ('account.recovery_phone_reset_password_failed');
6+
7+
UPDATE dbMetadata SET value = '169' WHERE name = 'schema-patch-level';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- SET NAMES utf8mb4 COLLATE utf8mb4_bin;
2+
--
3+
-- DELETE FROM securityEventNames WHERE name = 'account.recovery_phone_reset_password_complete';
4+
-- DELETE FROM securityEventNames WHERE name = 'account.recovery_phone_reset_password_failed';
5+
--
6+
-- UPDATE dbMetadata SET value = '168' WHERE name = 'schema-patch-level';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"level": 168
2+
"level": 169
33
}

packages/fxa-auth-client/lib/client.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,6 +2227,51 @@ export default class AuthClient {
22272227
);
22282228
}
22292229

2230+
/**
2231+
* Sends a code to the users phone during password reset.
2232+
*
2233+
* @param passwordForgotToken
2234+
* @param headers
2235+
*/
2236+
async recoveryPhonePasswordResetSendCode(
2237+
passwordForgotToken: string,
2238+
headers?: Headers
2239+
) {
2240+
return this.hawkRequest(
2241+
'POST',
2242+
'/recovery_phone/reset_password/send_code',
2243+
passwordForgotToken,
2244+
tokenType.passwordForgotToken,
2245+
{},
2246+
headers
2247+
);
2248+
}
2249+
2250+
/**
2251+
* Confirms the code sent to the recovery phone during a password reset.
2252+
*
2253+
*
2254+
* @param passwordForgotToken
2255+
* @param code The otp code sent to the user's phone
2256+
* @param headers
2257+
*/
2258+
async recoveryPhoneResetPasswordConfirm(
2259+
passwordForgotToken: string,
2260+
code: string,
2261+
headers?: Headers
2262+
) {
2263+
return this.hawkRequest(
2264+
'POST',
2265+
'/recovery_phone/reset_password/confirm',
2266+
passwordForgotToken,
2267+
tokenType.passwordForgotToken,
2268+
{
2269+
code,
2270+
},
2271+
headers
2272+
);
2273+
}
2274+
22302275
/**
22312276
* Removes a recovery phone from the user's account
22322277
*

packages/fxa-auth-server/lib/l10n/server.ftl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ recovery-phone-signin-sms-body = { $code } is your { -brand-mozilla } recovery c
2525
# https://twiliodeved.github.io/message-segment-calculator/
2626
# Messages should be limited to one segment
2727
# $code - 6 digit code used to sign in with a recovery phone as backup for two-step authentication
28-
recovery-phone-signin-sms-short-body = { -brand-mozilla } code: { $code }
28+
recovery-phone-signin-sms-short-body = { -brand-mozilla } code: { $code }
29+
30+
# Message sent by SMS with limited character length, please test translation with the messaging segment calculator
31+
# https://twiliodeved.github.io/message-segment-calculator/
32+
# Messages should be limited to one segment
33+
# $code - 6 digit code used to sign in with a recovery phone as backup for account password reset
34+
recovery-phone-reset-password-sms-body = { $code } is your { -brand-mozilla } recovery code. Expires in 5 minutes.
35+
36+
# Shorter message sent by SMS with limited character length, please test translation with the messaging segment calculator
37+
# https://twiliodeved.github.io/message-segment-calculator/
38+
# Messages should be limited to one segment
39+
# $code - 6 digit code used to sign in with a recovery phone as backup for account password reset
40+
recovery-phone-reset-password-short-body = { -brand-mozilla } code: { $code }

packages/fxa-auth-server/lib/metrics/glean/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ export function gleanMetrics(config: ConfigType) {
254254
recoveryKeyCreatePasswordSuccess: createEventFn(
255255
'password_reset_recovery_key_create_success'
256256
),
257+
258+
recoveryPhoneCodeSent: createEventFn(
259+
'password_reset_recovery_phone_code_sent'
260+
),
261+
recoveryPhoneCodeSendError: createEventFn(
262+
'password_reset_recovery_phone_code_send_error'
263+
),
264+
recoveryPhoneCodeComplete: createEventFn(
265+
'password_reset_recovery_phone_code_complete'
266+
),
257267
},
258268

259269
oauth: {

0 commit comments

Comments
 (0)