-
Notifications
You must be signed in to change notification settings - Fork 4k
[ECUK In-App 3DS] MFA: isolate 3DS flow into self-contained modal navigator #89992
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
Merged
rafecolton
merged 76 commits into
Expensify:main
from
software-mansion-labs:dariusz-biela/feat/3ds/internal-navigation-v3
Jun 2, 2026
Merged
Changes from all commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
e0fe771
feat(mfa): use standalone overlay navigator instead of RHP
dariusz-biela 3eb973f
refactor(mfa): clean up overlay — remove dead code, simplify rendering
dariusz-biela 4b5cee2
refactor(mfa): separate modal close from state reset
dariusz-biela 9af6fb0
fix(mfa): add slide-out animation on overlay close
dariusz-biela 0e1fa5b
fix(mfa): resolve eslint errors across PR files
dariusz-biela c6abb7f
refactor(mfa): drop eslint-disables in overlay open/close effect
dariusz-biela 90efd4a
fix(mfa): unblock iOS overlay flow and stop close-effect crash
dariusz-biela a337316
refactor(mfa): align overlay web options with RHP
dariusz-biela 577a579
fix(mfa): guard process() during skip-outcome close window
dariusz-biela bb5cf61
fix(mfa): disable swipe-back gesture on overlay screens
dariusz-biela 509ade7
fix(mfa): reset hasInitialLaidOut on overlay close
dariusz-biela 6fa9cde
refactor(mfa): drop unnecessary double cast in scenario assignment
dariusz-biela 3360c23
refactor(mfa): share MfaOverlayInternalParamList between navigation a…
dariusz-biela 2315bcd
refactor(mfa): replace magic zIndex with named mfaOverlayZIndex constant
dariusz-biela 2692cc0
refactor(mfa): remove manual memoization from overlay component
dariusz-biela 91c6019
chore(mfa): set displayName on TransparentScreen
dariusz-biela 05928a2
refactor(mfa): annotate params type instead of casting in navigate()
dariusz-biela b93c440
refactor(mfa): move overlay root style into centralized theme styles
dariusz-biela 8c315be
refactor(mfa): rename overlay navigator to MultifactorAuthenticationM…
dariusz-biela 7ab2337
refactor(mfa): rename navigation helpers and backdrop shared value fo…
dariusz-biela 931abfd
feat(mfa): intercept browser/Android back with cancel-confirm modal
dariusz-biela efdbdff
refactor(mfa): centralize back handling via requestCancel context API
dariusz-biela a5ec489
refactor(mfa): address review — rename initial screen const and reuse…
dariusz-biela 8548639
Merge branch 'main' into dariusz-biela/feat/3ds/internal-navigation-v3
dariusz-biela 7def086
fix(mfa): close modal directly when cancelling offline
dariusz-biela 10264a7
fix(mfa): extract biometrics row to avoid pre-auth TestToolMenu crash
dariusz-biela 1cb7e05
chore(seatbelt): drop stale entry for deleted BiometricsTestPage
dariusz-biela 51ca9a9
fix(mfa): gate BackHandler subscription to Android in MFA sync hook
dariusz-biela 15b1f80
fix(mfa): compare MFA marker presence, not top position, in history sync
dariusz-biela e60510a
fix(mfa): guard executeScenario against rapid double-tap
dariusz-biela 25663e5
refactor(mfa): drop unused screen field and MultifactorAuthentication…
dariusz-biela 1590b83
refactor(mfa): drop redundant screen as string casts in navigate
dariusz-biela 333302e
fix(mfa): clear cancel-confirm flag on CLOSE_MODAL to avoid lingering…
dariusz-biela 3dadf13
refactor(mfa): drop unused cancel from MultifactorAuthenticationConte…
dariusz-biela 4d705e2
refactor(mfa): inline continuableError destructure in ValidateCodePage
dariusz-biela 42dc59e
docs(mfa): correct set-state-in-effect rationale in ValidateCodePage
dariusz-biela c017089
fix(mfa): close RHP from deny outcome screen on AuthorizeTransactionPage
dariusz-biela c1cd1bc
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela 640a91b
refactor(mfa): split Context module to break import cycle
dariusz-biela 80a5328
refactor(mfa): drop redundant useCallback for cancel handlers
dariusz-biela 033aeb5
fix(card): close RHP from ChangePINAtATMPage buttons
dariusz-biela 95e2d36
fix(mfa): guard INIT in reducer to prevent rapid double-tap race
dariusz-biela 3430e86
fix(mfa): preserve isModalOpen on REREGISTER to keep flow alive
dariusz-biela 75b98c7
fix(mfa): disable biometrics test tool buttons when offline
dariusz-biela 769d81b
fix(mfa): use modal interpolator on web to avoid lost slide animation
dariusz-biela 87b2fde
refactor(mfa): collapse modal lifecycle flags into phase state
dariusz-biela 6ebc52e
fix(mfa): close RHP after CHANGE-PIN scenario to restore card details
dariusz-biela a96a63c
fix(mfa): use goBack instead of closeRHPFlow after CHANGE-PIN
dariusz-biela dbd3517
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela e95e0b8
fix(mfa): defer Stack mount until SidePanel close transition ends
dariusz-biela 22ecc6a
fix(mfa): move setState out of useAwaitSidePanelClose effect
dariusz-biela 02e90a6
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela de770f8
fix(MFA): keep revealed PIN visible after REVEAL_PIN success
dariusz-biela 31330c4
fix(MFA): defer outcome-screen push until callback transitions finish
dariusz-biela 8100781
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela 9b7de81
fix(MFA): pass required enter prop to modalCardStyleInterpolator
dariusz-biela 2e4775c
fix(MFA): route TransitionTracker access through Navigation higher-le…
dariusz-biela 6779617
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela 4b497cc
fix(MFA): keep revealed PAN visible after REVEAL_CARD_DETAILS success
dariusz-biela 3a721b3
chore(eslint): drop stale react/jsx-props-no-spreading seatbelt entry
dariusz-biela 4b0769f
fix(MFA): keep marker browser entry aligned across Navigation.goBack
dariusz-biela d148bf4
fix(MFA): cancel pending marker re-attach on modal close
dariusz-biela 13fe014
fix(eslint): drop restricted TransitionTracker import in MFA marker h…
dariusz-biela c5ba52e
fix(MFA): clear cancel-confirm on flow complete to unblock outcome sc…
dariusz-biela 77075bb
refactor(MFA): replace close-timer with runAfterUpcomingTransition
dariusz-biela f595398
docs(MFA): name example RHP hosts for OutcomeScreenBase onClose
dariusz-biela 430013f
docs(MFA): expand executeScenario short-circuit rationale
dariusz-biela a89951e
docs(MFA): reflow INIT scenario-cast comment onto two lines
dariusz-biela 389caee
docs(MFA): correct CLOSE_MODAL cancel-confirm clear rationale
dariusz-biela 278ff9e
docs(MFA): document why MFA_INITIAL_SCREEN stays module-private
dariusz-biela abb7ab9
docs(MFA): convert hasInitialLaidOut explainer to JSDoc
dariusz-biela 22c4094
refactor(navigation): centralize themePreference→reactNavigation mapping
dariusz-biela 2e15215
docs(MFA): convert cardStyleInterpolator rationale to JSDoc + add mis…
dariusz-biela 09009e5
docs(MFA): rewrite CLOSE_MODAL cancel-confirm clear rationale
dariusz-biela 090875f
docs(MFA): pin CLOSE_MODAL cancel-confirm clear to flow-skipping path
dariusz-biela 857e445
fix(lint): use relative import for getNavigationBaseTheme in Navigati…
dariusz-biela File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import React, {useState} from 'react'; | ||
| import {View} from 'react-native'; | ||
| import useBiometricRegistrationStatus, {REGISTRATION_STATUS} from '@hooks/useBiometricRegistrationStatus'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useNetwork from '@hooks/useNetwork'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {revokeMultifactorAuthenticationCredentials} from '@libs/actions/MultifactorAuthentication'; | ||
| import CONST from '@src/CONST'; | ||
| import Button from './Button'; | ||
| import {useMultifactorAuthentication} from './MultifactorAuthentication/Context'; | ||
| import TestToolRow from './TestToolRow'; | ||
|
|
||
| function BiometricsTestToolRow() { | ||
| const styles = useThemeStyles(); | ||
| const {translate} = useLocalize(); | ||
| const {executeScenario} = useMultifactorAuthentication(); | ||
| const {localCredentialID, isCurrentDeviceRegistered, otherDeviceCount, registrationStatus} = useBiometricRegistrationStatus(); | ||
| const {isOffline} = useNetwork(); | ||
| const [isMFARevokeLoading, setIsMFARevokeLoading] = useState(false); | ||
|
|
||
| const statusTextMap = { | ||
| [REGISTRATION_STATUS.NEVER_REGISTERED]: translate('multifactorAuthentication.biometricsTest.statusNeverRegistered'), | ||
| [REGISTRATION_STATUS.NOT_REGISTERED]: translate('multifactorAuthentication.biometricsTest.statusNotRegistered'), | ||
| [REGISTRATION_STATUS.REGISTERED_OTHER_DEVICE]: translate('multifactorAuthentication.biometricsTest.statusRegisteredOtherDevice', {count: otherDeviceCount}), | ||
| [REGISTRATION_STATUS.REGISTERED_THIS_DEVICE]: translate('multifactorAuthentication.biometricsTest.statusRegisteredThisDevice'), | ||
| }; | ||
| const biometricsTitle = translate('multifactorAuthentication.biometricsTest.troubleshootBiometricsStatus', {status: statusTextMap[registrationStatus]}); | ||
|
|
||
| return ( | ||
| <TestToolRow title={biometricsTitle}> | ||
| <View style={[styles.flexRow, styles.gap2]}> | ||
| <Button | ||
| small | ||
| isDisabled={isOffline} | ||
| text={translate('multifactorAuthentication.biometricsTest.test')} | ||
| onPress={() => executeScenario(CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.BIOMETRICS_TEST)} | ||
| /> | ||
| {isCurrentDeviceRegistered && !!localCredentialID && ( | ||
| <Button | ||
| danger | ||
| isDisabled={isOffline} | ||
| isLoading={isMFARevokeLoading} | ||
| small | ||
| text={translate('multifactorAuthentication.revoke.revoke')} | ||
| onPress={async () => { | ||
| setIsMFARevokeLoading(true); | ||
| await revokeMultifactorAuthenticationCredentials({onlyKeyID: localCredentialID}); | ||
| setIsMFARevokeLoading(false); | ||
| }} | ||
| /> | ||
| )} | ||
| </View> | ||
| </TestToolRow> | ||
| ); | ||
| } | ||
|
|
||
| BiometricsTestToolRow.displayName = 'BiometricsTestToolRow'; | ||
|
|
||
| export default BiometricsTestToolRow; | ||
17 changes: 17 additions & 0 deletions
17
src/components/MultifactorAuthentication/Context/MultifactorAuthenticationActionsContext.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import {createContext, useContext} from 'react'; | ||
| import type {MultifactorAuthenticationActionsContextType} from './types'; | ||
|
|
||
| const MultifactorAuthenticationActionsContext = createContext<MultifactorAuthenticationActionsContextType | undefined>(undefined); | ||
|
|
||
| function useMultifactorAuthenticationActions(): MultifactorAuthenticationActionsContextType { | ||
| const context = useContext(MultifactorAuthenticationActionsContext); | ||
|
|
||
| if (!context) { | ||
| throw new Error('useMultifactorAuthenticationActions must be used within a MultifactorAuthenticationStateProvider'); | ||
| } | ||
|
|
||
| return context; | ||
| } | ||
|
|
||
| export default MultifactorAuthenticationActionsContext; | ||
| export {useMultifactorAuthenticationActions}; |
36 changes: 36 additions & 0 deletions
36
...s/MultifactorAuthentication/Context/MultifactorAuthenticationComposedContextProviders.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import React, {useMemo, useReducer} from 'react'; | ||
| import type {ReactNode} from 'react'; | ||
| import MultifactorAuthenticationActionsContext from './MultifactorAuthenticationActionsContext'; | ||
| import {MultifactorAuthenticationContextProvider} from './MultifactorAuthenticationMainContext'; | ||
| import MultifactorAuthenticationStateContext from './MultifactorAuthenticationStateContext'; | ||
| import {DEFAULT_STATE, stateReducer} from './stateReducer'; | ||
|
|
||
| type MultifactorAuthenticationProviderProps = { | ||
| children: ReactNode; | ||
| }; | ||
|
|
||
| function MultifactorAuthenticationStateProvider({children}: MultifactorAuthenticationProviderProps) { | ||
| const [state, dispatch] = useReducer(stateReducer, DEFAULT_STATE); | ||
| const actions = useMemo(() => ({dispatch}), [dispatch]); | ||
|
|
||
| return ( | ||
| <MultifactorAuthenticationStateContext.Provider value={state}> | ||
| <MultifactorAuthenticationActionsContext.Provider value={actions}>{children}</MultifactorAuthenticationActionsContext.Provider> | ||
| </MultifactorAuthenticationStateContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| MultifactorAuthenticationStateProvider.displayName = 'MultifactorAuthenticationStateProvider'; | ||
|
|
||
| function MultifactorAuthenticationContextProviders({children}: MultifactorAuthenticationProviderProps) { | ||
| return ( | ||
| <MultifactorAuthenticationStateProvider> | ||
| <MultifactorAuthenticationContextProvider>{children}</MultifactorAuthenticationContextProvider> | ||
| </MultifactorAuthenticationStateProvider> | ||
| ); | ||
| } | ||
|
|
||
| MultifactorAuthenticationContextProviders.displayName = 'MultifactorAuthenticationContextProviders'; | ||
|
|
||
| export default MultifactorAuthenticationContextProviders; | ||
| export {DEFAULT_STATE, MultifactorAuthenticationStateProvider}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.