-
Notifications
You must be signed in to change notification settings - Fork 4k
redirect 2FA in incognito while 2FA is enabled #93656
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
Changes from all commits
049d508
703a615
20e3941
b6cc47c
65638bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,12 +7,13 @@ import JustSignedInModal from '@components/ValidateCode/JustSignedInModal'; | |
| import ValidateCodeModal from '@components/ValidateCode/ValidateCodeModal'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import Log from '@libs/Log'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; | ||
| import {isValidValidateCode} from '@libs/ValidationUtils'; | ||
| import {handleExitToNavigation, initAutoAuthState, signInWithValidateCode} from '@userActions/Session'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
| import SCREENS from '@src/SCREENS'; | ||
| import type {Session as SessionType} from '@src/types/onyx'; | ||
| import type ValidateLoginPageProps from './types'; | ||
|
|
||
|
|
@@ -40,7 +41,20 @@ function ValidateLoginPage({ | |
| const effectiveAutoAuthState = hasInitialized ? autoAuthState : undefined; | ||
| const autoAuthStateWithDefault = effectiveAutoAuthState ?? CONST.AUTO_AUTH_STATE.NOT_STARTED; | ||
| const is2FARequired = !!account?.requiresTwoFactorAuth; | ||
| const cachedAccountID = credentials?.accountID; | ||
| // A magic-link sign-in that needs 2FA completes on the sign-in page: it reuses the stored | ||
| // `credentials.validateCode`, and SignInPage renders the authenticator-code stage once | ||
| // `requiresTwoFactorAuth` + that code are present. Send the user there to enter their code instead | ||
| // of the informational "2FA required" modal, which is a dead end. Require the cached credentials to | ||
| // match THIS link — `signInWithValidateCode` only caches the code on a successful 2FA-required | ||
| // response, so a stale code left over from an earlier attempt can't redirect a later failed/expired | ||
| // link. `exitTo` deep links route here too; the effect keeps the deferred `exitTo` navigation | ||
| // pending so the user reaches their destination after 2FA completes. | ||
| const canCompleteTwoFactorOnSignIn = | ||
| is2FARequired && | ||
| !isSignedIn && | ||
| credentials?.validateCode === validateCode && | ||
| credentials?.accountID === Number(accountID) && | ||
| (autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN || autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.FAILED); | ||
| const isUserClickedSignIn = !login && isSignedIn && (autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.SIGNING_IN || autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN); | ||
| const shouldStartSignInWithValidateCode = !isUserClickedSignIn && !isSignedIn && (!!login || !!exitTo) && isValidValidateCode(validateCode); | ||
| const isNavigatingToExitTo = isSignedIn && !!exitTo; | ||
|
|
@@ -94,18 +108,37 @@ function ValidateLoginPage({ | |
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (!!login || !cachedAccountID || !is2FARequired) { | ||
| if (exitTo) { | ||
| handleExitToNavigation(exitTo); | ||
| } | ||
| return; | ||
| let ignore = false; | ||
| if (canCompleteTwoFactorOnSignIn) { | ||
| // Show the sign-in page so its ValidateCodeForm renders the authenticator-code stage. | ||
| // ROUTES.HOME ('home') is nested under the authenticated TAB_NAVIGATOR, so navigate/goBack | ||
| // to it no-op from the public /v/ route; reset the stack to SCREENS.HOME instead — the same | ||
| // mechanism logout uses to surface the public SignInPage. The "2FA required" modal stays | ||
| // rendered as the fallback so a failed hand-off shows it, not a blank or an endless loader. | ||
| Navigation.isNavigationReady().then(() => { | ||
| // Bail if the effect re-ran (e.g. `isSignedIn` flipped true) before this resolved, so a | ||
| // stale callback can't reset the stack out from under the new state. | ||
| if (ignore) { | ||
| return; | ||
| } | ||
|
Comment on lines
+119
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each effect run closes over its own |
||
| navigationRef.reset({index: 0, routes: [{name: SCREENS.HOME}]}); | ||
| }); | ||
| } | ||
|
|
||
| // Register the deferred `exitTo` destination navigation while still unauthenticated. | ||
| // `handleExitToNavigation` waits for the authToken, so for a 2FA account it fires only after the | ||
| // user enters their code on the sign-in page above (the token lands and resolves the shared | ||
| // sign-in promise) — landing them on their destination instead of Home. For a non-2FA account | ||
| // it's the normal post-sign-in handoff. The `!isSignedIn` guard registers it once and never | ||
| // re-fires after sign-in, so the effect re-running can't queue a duplicate navigation. | ||
| if (exitTo && !isSignedIn) { | ||
| handleExitToNavigation(exitTo); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now also called when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question — it's safe (the reset unmounts this page before 2FA completes, and the only double-call is at mount where both share the single |
||
| } | ||
|
|
||
| // The user clicked the option to sign in the current tab | ||
| Navigation.isNavigationReady().then(() => { | ||
| Navigation.goBack(); | ||
| }); | ||
| }, [login, cachedAccountID, is2FARequired, exitTo]); | ||
| return () => { | ||
| ignore = true; | ||
| }; | ||
| }, [canCompleteTwoFactorOnSignIn, exitTo, isSignedIn]); | ||
|
|
||
| // waitForProtectedRoutes()/authToken can hang (lazy AuthScreens chunk fails, token | ||
| // never lands). We can't recover the consumed code here, but surface a stuck sign-in to | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.