-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(fxa-settings): Implement using recovery phone to sign in #18255
Conversation
abe0538
to
406b221
Compare
@@ -3,7 +3,7 @@ | |||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |||
|
|||
import { ConfigService } from '@nestjs/config'; | |||
import { SmsManagerConfig } from './sms.manger.config'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
}); | ||
|
||
const mockAuthClient = new AuthClient('http://localhost:9000', { | ||
keyStretchVersion: 1, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.test.tsx
Outdated
Show resolved
Hide resolved
packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.tsx
Outdated
Show resolved
Hide resolved
1e644ab
to
a08013f
Compare
There was a problem hiding this 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
user.type(screen.getByRole('textbox'), MOCK_RECOVERY_CODE) | ||
); | ||
|
||
expect(screen.getByRole('button', { name: /Confirm/i })).toHaveAttribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.test.tsx
Outdated
Show resolved
Hide resolved
return; | ||
} | ||
try { | ||
await authClient.recoveryPhoneSigninSendCode(signinState.sessionToken); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
packages/fxa-settings/src/pages/Signin/SigninRecoveryPhone/container.test.tsx
Outdated
Show resolved
Hide resolved
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this 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.
🚀
@@ -82,6 +82,8 @@ const getReactRouteGroups = (showReactApp, reactRoute) => { | |||
'signin_unblock', | |||
'force_auth', | |||
'signin_recovery_code', | |||
'signin_recovery_choice', | |||
'signin_recovery_phone', |
There was a problem hiding this comment.
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, 👍)
There was a problem hiding this comment.
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!
}), | ||
mockRecoveryPhoneGet = jest.fn().mockResolvedValue({ | ||
exists: true, | ||
phoneNumber: '7890', |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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:
fxa/packages/fxa-settings/src/components/Settings/SubRow/index.tsx
Lines 240 to 241 in 9955924
{phoneNumber.includes('•') | |
? phoneNumber |
See:
fxa/libs/accounts/recovery-phone/src/lib/recovery-phone.service.ts
Lines 192 to 193 in 9955924
* e.g. If the phone number was +15005551234 and phoneNumberMask was 4, the result would be +*******1234. */ fxa/libs/accounts/recovery-phone/src/lib/recovery-phone.service.ts
Lines 259 to 260 in 9955924
const maskedPhoneNumber = phoneNumber .substring(phoneNumber.length - lastN)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
packages/fxa-settings/src/pages/Signin/SigninRecoveryChoice/container.tsx
Show resolved
Hide resolved
return; | ||
} | ||
|
||
if (phoneNumber && (!count || count === 0)) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 />; |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,99 @@ | |||
import React from 'react'; |
There was a problem hiding this comment.
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}`; |
There was a problem hiding this comment.
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
Because
This pull request
Issue that this pull request solves
Closes: #FXA-10374
Checklist
Put an
x
in the boxes that applyScreenshots (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)