[Release 3.2] [Domain control] Enable, disable or reset their 2FA#82135
Conversation
…' into war-in/add-2FA-toggle-to-member-details # Conflicts: # src/ROUTES.ts # src/SCREENS.ts # src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx # src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts # src/libs/Navigation/linkingConfig/config.ts # src/libs/Navigation/types.ts # src/pages/domain/Members/DomainMembersPage.tsx
…to war-in/add-2FA-toggle-to-member-details # Conflicts: # src/languages/en.ts # src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts # src/libs/actions/Domain.ts # src/pages/domain/Admins/DomainAdminDetailsPage.tsx # src/pages/domain/BaseDomainMembersPage.tsx
…etails_page' into war-in/add-2FA-toggle-to-member-details # Conflicts: # src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx # src/pages/domain/Admins/DomainAdminDetailsPage.tsx # src/pages/domain/BaseDomainMemberDetailsComponent.tsx
…dd-2FA-toggle-to-member-details # Conflicts: # src/ROUTES.ts # src/SCREENS.ts # src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx # src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts # src/libs/Navigation/linkingConfig/config.ts # src/libs/Navigation/types.ts
…dd-2FA-toggle-to-member-details # Conflicts: # src/languages/en.ts # src/pages/domain/Members/DomainRequireTwoFactorAuthPage.tsx
…dd-2FA-toggle-to-member-details # Conflicts: # src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts
…dd-2FA-toggle-to-member-details
…dd-2FA-toggle-to-member-details # Conflicts: # src/ROUTES.ts # src/languages/en.ts # src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx # src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts # src/pages/domain/BaseDomainMembersPage.tsx # src/pages/domain/Members/DomainMembersPage.tsx # src/types/onyx/DomainErrors.ts # src/types/onyx/DomainPendingActions.ts
…dd-2FA-toggle-to-member-details # Conflicts: # src/pages/domain/Members/DomainMembersSettingsPage.tsx # src/pages/domain/Members/DomainRequireTwoFactorAuthPage.tsx
…dd-2FA-toggle-to-member-details
…dd-2FA-toggle-to-member-details
…dd-2FA-toggle-to-member-details # Conflicts: # src/libs/actions/Domain.ts # src/pages/domain/BaseDomainMembersPage.tsx # src/pages/domain/Members/DomainMemberDetailsPage.tsx # src/pages/domain/Members/DomainMembersPage.tsx
…etails # Conflicts: # src/pages/domain/Admins/DomainAdminsPage.tsx # src/pages/domain/Members/DomainMembersPage.tsx # src/pages/domain/Members/DomainMembersSettingsPage.tsx # src/pages/domain/Members/DomainRequireTwoFactorAuthPage.tsx
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
…etails # Conflicts: # src/pages/domain/BaseDomainMembersPage.tsx # src/pages/domain/Members/DomainMembersPage.tsx
|
@situchan fixed! and merged main |
|
job 7 is broken on main but job2 is related to this PR |
@situchan fixed! Screen.Recording.2026-02-25.at.10.43.51.mov |
There was a problem hiding this comment.
Pull request overview
Adds domain-admin controls for member-level 2FA management by introducing a per-member “Force two-factor authentication” toggle (via exemption list management) and a “Reset two-factor authentication” action, along with supporting API commands, navigation routes, Onyx types, translations, and unit tests.
Changes:
- Added new domain actions/API commands for managing 2FA-exempt emails and resetting a member’s 2FA, including optimistic/pending/error handling.
- Added new member 2FA modal pages and updated member details UI to expose the toggle + reset entry; refactored domain 2FA code-entry UI into a shared base page.
- Updated Onyx types and translations; added/expanded unit tests for selectors and domain actions.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/selectors/AccountTest.ts | Adds unit tests for the new requiresTwoFactorAuthSelector. |
| tests/actions/DomainTest.ts | Adds tests covering new domain actions for 2FA exemption + reset flows. |
| src/types/onyx/DomainPendingActions.ts | Extends pending actions to include member-level twoFactorAuthExemptEmails and makes base pendingAction optional. |
| src/types/onyx/DomainErrors.ts | Adds twoFactorAuthExemptEmailsError to member errors shape. |
| src/types/onyx/CardFeeds.ts | Extends domain settings type with twoFactorAuthExemptEmails. |
| src/selectors/Account.ts | Adds requiresTwoFactorAuthSelector. |
| src/pages/domain/Members/TwoFactorAuth/DomainMemberResetTwoFactorAuthPage.tsx | New page to submit admin 2FA code to reset a member’s 2FA. |
| src/pages/domain/Members/TwoFactorAuth/DomainMemberForceTwoFactorAuthPage.tsx | New page to submit admin 2FA code when exempting a member from forced 2FA. |
| src/pages/domain/Members/DomainRequireTwoFactorAuthPage.tsx | Refactors domain-level disable flow to reuse the new shared base 2FA-code page. |
| src/pages/domain/Members/DomainMembersSettingsPage.tsx | Switches translation keys to domain.common.* for the domain-level force-2FA toggle. |
| src/pages/domain/Members/DomainMembersPage.tsx | Ensures member-row error merging includes the new 2FA exemption error field. |
| src/pages/domain/Members/DomainMemberDetailsPage.tsx | Adds per-member force-2FA toggle and reset-2FA menu item in the member details RHP. |
| src/pages/domain/BaseDomainRequireTwoFactorAuthPage.tsx | Introduces shared 2FA-code entry page used by domain + member flows. |
| src/libs/actions/Domain.ts | Implements new write actions: set 2FA-exempt email, clear related errors, reset member 2FA; updates error translation key. |
| src/libs/Navigation/types.ts | Adds params for the new member 2FA screens. |
| src/libs/Navigation/linkingConfig/config.ts | Registers deeplink paths for new member 2FA routes. |
| src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts | Adds new member 2FA screens to the Domain→RHP relations. |
| src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx | Registers the new member 2FA pages in the settings modal stack. |
| src/libs/DomainUtils.ts | Updates domain-member details error detection to include 2FA exemption errors. |
| src/libs/API/types.ts | Adds new WRITE commands for exempt-email management and member 2FA reset. |
| src/libs/API/parameters/index.ts | Exports new API parameter types. |
| src/libs/API/parameters/SetTwoFactorAuthExemptEmailForDomainParams.ts | Defines request params for exempt-email toggle write. |
| src/libs/API/parameters/ResetDomainMemberTwoFactorAuthParams.ts | Defines request params for member 2FA reset write. |
| src/languages/zh-hans.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/pt-BR.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/pl.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/nl.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/ja.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/it.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/fr.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/es.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/en.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/languages/de.ts | Adds domain.common.resetTwoFactorAuth and moves force-2FA strings under domain.common. |
| src/SCREENS.ts | Adds new screen constants for member force/reset 2FA flows. |
| src/ROUTES.ts | Adds new routes for member force/reset 2FA flows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <HeaderWithBackButton | ||
| title={translate('twoFactorAuth.disableTwoFactorAuth')} | ||
| onBackButtonPress={onBackButtonPress} | ||
| shouldDisplayHelpButton={false} | ||
| /> |
There was a problem hiding this comment.
BaseDomainRequireTwoFactorAuthPage hardcodes the header title to twoFactorAuth.disableTwoFactorAuth, but this component is also used for member 2FA flows (force/reset). That will show an incorrect/misleading title in those contexts. Consider making the title configurable via props (e.g. a headerTitle/headerTitleTranslationKey prop) and passing the appropriate copy from each caller.
| <Button | ||
| success | ||
| large | ||
| text={translate('common.disable')} | ||
| isLoading={!!pendingAction} | ||
| onPress={() => baseTwoFactorAuthRef.current?.validateAndSubmitForm()} | ||
| /> |
There was a problem hiding this comment.
The submit button label is hardcoded to common.disable, which doesn’t fit all uses of this shared page (e.g. “Reset two-factor authentication”). Making the button text configurable (and optionally the button style) will avoid incorrect copy when reusing this component.
| it('adds targetEmail to exempt emails in optimisticData when force2FA is false and no twoFactorAuthCode is provided', () => { | ||
| const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve()); | ||
|
|
||
| setTwoFactorAuthExemptEmailForDomain(domainAccountID, accountID, exemptEmails, targetEmail, false); | ||
|
|
||
| expect(apiWriteSpy).toHaveBeenCalledWith( | ||
| WRITE_COMMANDS.SET_TWO_FACTOR_AUTH_EXEMPT_EMAIL_FOR_DOMAIN, | ||
| expect.objectContaining({enabled: true}), | ||
| expect.objectContaining({ | ||
| optimisticData: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, | ||
| value: {settings: {twoFactorAuthExemptEmails: [...exemptEmails, targetEmail]}}, | ||
| }), | ||
| ]), |
There was a problem hiding this comment.
This test currently expects targetEmail to be appended even though exemptEmails already contains it, which enshrines duplicate entries in twoFactorAuthExemptEmails. If the action is made idempotent by de-duping, update this expectation to assert the email is present only once.
| type ResetDomainMemberTwoFactorAuth = { | ||
| domainAccountID: number; | ||
| targetAccountID: number; | ||
| targetEmail: string; | ||
| twoFactorAuthCode: string; | ||
| }; | ||
|
|
||
| export default ResetDomainMemberTwoFactorAuth; |
There was a problem hiding this comment.
The exported default type name (ResetDomainMemberTwoFactorAuth) doesn’t match the filename / re-export name (ResetDomainMemberTwoFactorAuthParams). Renaming the type to ResetDomainMemberTwoFactorAuthParams will make TS errors and IDE symbol search clearer and keep parameter types consistent.
| type ResetDomainMemberTwoFactorAuth = { | |
| domainAccountID: number; | |
| targetAccountID: number; | |
| targetEmail: string; | |
| twoFactorAuthCode: string; | |
| }; | |
| export default ResetDomainMemberTwoFactorAuth; | |
| type ResetDomainMemberTwoFactorAuthParams = { | |
| domainAccountID: number; | |
| targetAccountID: number; | |
| targetEmail: string; | |
| twoFactorAuthCode: string; | |
| }; | |
| export default ResetDomainMemberTwoFactorAuthParams; |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @mountiny has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.3.26-0 🚀
|
|
Hi @war-in We don't see
|
@izarutskaya yes, you need to have 2FA enabled in the Settings -> Security -> Two-factor authentication. I'll add that to the test steps, sorry! |
|
Let's retest this once you are allowed |
|
From my testing on staging, it looks like the |
|
🚀 Deployed to production by https://github.com/puneetlath in version: 9.3.26-8 🚀
|


Explanation of Change
PR implements
Force two-factor authenticationtoggle andReset two-factor authenticationbuttonFixed Issues
$ #79569
PROPOSAL:
Tests
domain/<domainID>/membersForce two-factor authenticationtoggle andReset two-factor authenticationentries in RHP4.1 When toggle was disabled - verify it's now enabled
4.2 If toggle is enabled
Reset two-factor authenticationReset two-factor authenticationitem in member detailsOffline tests
QA Steps
domain/<domainID>/membersForce two-factor authenticationtoggle andReset two-factor authentication(if you have 2FA enabled) entries in RHP4.1 When toggle was disabled - verify it's now enabled
4.2 If toggle is enabled
Reset two-factor authenticationReset two-factor authenticationitem in member detailsPR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Reset 2FA flow:
Screen.Recording.2026-02-13.at.13.53.12.mov
Force 2FA flow:
Screen.Recording.2026-02-13.at.13.55.36.mov