Skip to content

feat(fxa-settings): Implement using recovery phone to sign in#18255

Merged
vpomerleau merged 1 commit intomainfrom
FXA-10374
Jan 28, 2025
Merged

feat(fxa-settings): Implement using recovery phone to sign in#18255
vpomerleau merged 1 commit intomainfrom
FXA-10374

Conversation

@vpomerleau
Copy link
Copy Markdown
Contributor

@vpomerleau vpomerleau commented Jan 20, 2025

Because

  • New feature: recovery phone as recovery method for 2FA during sign in

This pull request

  • Hook up new pages to choose recovery method and use recovery phone during sign in

Issue that this pull request solves

Closes: #FXA-10374

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

Notes for testing - options to manually test the flow until #18276 is merged: either manually add a recovery phone to an existing account with 2FA via mysql or cherry pick the latest commit for PR-18276 to use UI to add a phone number
generated codes can be retrieved with redis-commander (or cherry pick #18277 to use inbox logs)

Comment thread libs/accounts/recovery-phone/src/lib/sms.manager.ts Outdated
Comment thread packages/fxa-auth-client/lib/client.ts
@vpomerleau vpomerleau force-pushed the FXA-10374 branch 6 times, most recently from abe0538 to 406b221 Compare January 23, 2025 21:34
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ConfigService } from '@nestjs/config';
import { SmsManagerConfig } from './sms.manger.config';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread packages/fxa-content-server/app/scripts/lib/router.js Outdated
});

const mockAuthClient = new AuthClient('http://localhost:9000', {
keyStretchVersion: 1,
Copy link
Copy Markdown
Contributor

@dschom dschom Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use version 2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guilty of copy pasta from another test file - @dschom should this be updated to v2 in all auth client mocks?

Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.test.tsx Outdated
Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.tsx Outdated
@vpomerleau vpomerleau force-pushed the FXA-10374 branch 4 times, most recently from 1e644ab to a08013f Compare January 27, 2025 17:35
@vpomerleau vpomerleau changed the title WIP feat(fxa-settings): Implement using recovery phone to sign in feat(fxa-settings): Implement using recovery phone to sign in Jan 27, 2025
@vpomerleau vpomerleau marked this pull request as ready for review January 27, 2025 17:42
@vpomerleau vpomerleau requested review from a team as code owners January 27, 2025 17:42
Copy link
Copy Markdown
Contributor

@vbudhram vbudhram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vpomerleau Took a first pass at this. Gonna do some manual testing now

Comment thread packages/fxa-settings/src/components/App/index.tsx
user.type(screen.getByRole('textbox'), MOCK_RECOVERY_CODE)
);

expect(screen.getByRole('button', { name: /Confirm/i })).toHaveAttribute(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.test.tsx Outdated
return;
}
try {
await authClient.recoveryPhoneSigninSendCode(signinState.sessionToken);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, it seems odd to me to send the code from this page. I wonder if the recovery phone page should handle sending codes instead. Don't need to block on this though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree, but we'd have to review UX for error handling in that scenario because the designs currently have us showing an error on the choice screen if send fails for the regular path to recovery phone.

I'll file a follow up ticket to review if we should send the code before or after navigation.

Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryPhone/container.test.tsx Outdated
signin-recovery-code-instruction-v2 = Enter one of the one-time use backup authentication codes you saved during two-step authentication setup.
signin-recovery-code-input-label-v2 = Enter 10-character code
# codes here refers to backup authentication codes
signin-recovery-code-instruction-v3 = Enter one of the one-time-use codes you saved when you set up two-step authentication.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
signin-recovery-code-instruction-v3 = Enter one of the one-time-use codes you saved when you set up two-step authentication.
signin-recovery-code-instruction-v3 = Enter one of the one-time use codes you saved when you set up two-step authentication.

Nit, but aligns with previous string in terms of hyphenation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Content reviewed and recommended "one-time-use" codes, so this is an intentional update :)

Copy link
Copy Markdown
Contributor

@vbudhram vbudhram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vpomerleau Manual testing looks good! I think this will be ok to merge, I can still do some fixes while working on #18287.

:shipit: 🚀

'force_auth',
'signin_recovery_code',
'signin_recovery_choice',
'signin_recovery_phone',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably already know this but just to be clear / for others too: we only need to add routes to content-server this way if it's not behind /settings/* - content-server already points all settings routes to our React app if users hit them directly. Otherwise, without these content-server modifications, the server doesn't know what to do on a refresh of that page and it ends up blank, so this is probably preferred for routes not behind settings.

(I wanted to think that through myself too, so, 👍)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I wasn't 💯 sure on this so appreciate the confirmation!

Comment thread packages/fxa-settings/src/components/Settings/index.tsx Outdated
Comment thread packages/fxa-settings/src/components/Settings/index.tsx
}),
mockRecoveryPhoneGet = jest.fn().mockResolvedValue({
exists: true,
phoneNumber: '7890',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this will return MOCK_MASKED_PHONE_NUMBER FWIW, and not just the last 4, if the session isn't verified. I need to fill out details for FXA-11044.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I'll update to test that the masked number is handled properly!

const [loading, setLoading] = useState(true);

useEffect(() => {
if (!signinState || !signinState.sessionToken) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is sessionToken already in local storage at this point? Probably not here since I don't want you to have to refactor this all but I think we might as well just pull it from local storage when we can. (I'm not sure we should have ever passed it as part of signin state heh)

signinState.sessionToken
);
// TODO verify that recoveryPhoneGet returns a masked phone number (last four digits only)
phoneNumber && setLastFourPhoneDigits(phoneNumber.slice(-4));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might could create a little helper function for this:

{phoneNumber.includes('•')
? phoneNumber

See:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to be more consistent. Could we clean this up in the ticket you filled for the twilio national number format?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a note in FXA-11044

return;
}

if (phoneNumber && (!count || count === 0)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the type of this null | number? Not super nice that any of these returned values from authClient are any 😅

}, [authClient, lastFourPhoneDigits, signinState, navigateWithQuery]);

const handlePhoneChoice = async () => {
if (!signinState) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use the session token from signin state, you could curry this function with it so you don't have to recheck if (!signinState) {, but maybe later we should just pull from local storage instead and curry that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a note in FXA-9875 about reviewing what's included in signinState (vs local storage)

}

if (!signinState || !lastFourPhoneDigits) {
return <LoadingSpinner fullScreen />;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this where the redirect should happen instead of useEffect?

user.type(screen.getByRole('textbox'), MOCK_RECOVERY_CODE)
);

expect(screen.getByRole('button', { name: /Confirm/i })).toHaveAttribute(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryCode/index.tsx
Comment thread packages/fxa-settings/src/pages/Signin/SigninRecoveryCode/index.tsx
@@ -0,0 +1,99 @@
import React from 'react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing license

React.useState(false);
const ftlMsgResolver = useFtlMsgResolver();

const maskedPhoneNumber = `+1-•••-${lastFourPhoneDigits}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to update this to just the dots, for consistency across add/delete inside Settings. In that new ticket I referenced, I think we can iron this out with that national_format from Twilio if we want these formatted properly.

Because:

* New feature: recovery phone as recovery method for 2FA during sign in

This commit:

* Hook up new pages to choose recovery method and use recovery phone during sign in

Closes #FXA-10374
@vpomerleau vpomerleau merged commit 153efdd into main Jan 28, 2025
@vpomerleau vpomerleau deleted the FXA-10374 branch January 28, 2025 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants