diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 2a170150d23da..0a9c8efd01354 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -5948,6 +5948,8 @@ const CONST = {
ENABLED: 'ENABLED',
DISABLED: 'DISABLED',
DISABLE: 'DISABLE',
+ REPLACE_VERIFY_OLD: 'REPLACE_VERIFY_OLD',
+ REPLACE_VERIFY_NEW: 'REPLACE_VERIFY_NEW',
},
MERGE_ACCOUNT_RESULTS: {
SUCCESS: 'success',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 23188706a6e21..4d964e634d74f 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -609,6 +609,8 @@ const ROUTES = {
},
SETTINGS_2FA_DISABLED: 'settings/security/two-factor-auth/disabled',
SETTINGS_2FA_DISABLE: 'settings/security/two-factor-auth/disable',
+ SETTINGS_2FA_REPLACE_VERIFY_OLD: 'settings/security/two-factor-auth/replace/verify-old',
+ SETTINGS_2FA_REPLACE_VERIFY_NEW: 'settings/security/two-factor-auth/replace/verify-new',
SETTINGS_STATUS: 'settings/profile/status',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 6c2599a91dc50..54bc9eb9bb18d 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -254,6 +254,8 @@ const SCREENS = {
SUCCESS: 'Settings_TwoFactorAuth_Success',
DISABLED: 'Settings_TwoFactorAuth_Disabled',
DISABLE: 'Settings_TwoFactorAuth_Disable',
+ REPLACE_VERIFY_OLD: 'Settings_TwoFactorAuth_Replace_VerifyOld',
+ REPLACE_VERIFY_NEW: 'Settings_TwoFactorAuth_Replace_VerifyNew',
},
SAVE_THE_WORLD: {
ROOT: 'SaveTheWorld_Root',
diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx
index e630688391372..d493d43e88ec1 100644
--- a/src/components/Pressable/PressableWithDelayToggle.tsx
+++ b/src/components/Pressable/PressableWithDelayToggle.tsx
@@ -166,17 +166,17 @@ function PressableWithDelayToggle({
>
{({hovered, pressed}) => (
<>
- {!inline && displayLabelText}
{shouldShowIcon && (
)}
+ {!inline && displayLabelText}
>
)}
diff --git a/src/languages/de.ts b/src/languages/de.ts
index f84f0da1aba12..9f79f4495b71e 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -2123,6 +2123,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Ihr Unternehmen verlangt eine Zwei-Faktor-Authentifizierung.',
twoFactorAuthCannotDisable: '2FA kann nicht deaktiviert werden',
twoFactorAuthRequired: 'Die Zwei-Faktor-Authentifizierung (2FA) ist für Ihre Xero-Verbindung erforderlich und kann nicht deaktiviert werden.',
+ replaceDevice: 'Gerät ersetzen',
+ replaceDeviceTitle: 'Zwei-Faktor-Gerät ersetzen',
+ verifyOldDeviceTitle: 'Altes Gerät verifizieren',
+ verifyOldDeviceDescription: 'Geben Sie den sechsstelligen Code aus Ihrer aktuellen Authentifizierungs-App ein, um zu bestätigen, dass Sie Zugriff darauf haben.',
+ verifyNewDeviceTitle: 'Neues Gerät einrichten',
+ verifyNewDeviceDescription: 'Scannen Sie den QR-Code mit Ihrem neuen Gerät und geben Sie dann den Code ein, um die Einrichtung abzuschließen.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/en.ts b/src/languages/en.ts
index c92d5bd001659..a16df4d35c13e 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2175,6 +2175,12 @@ const translations = {
twoFactorAuthIsRequiredCompany: 'Your company requires two-factor authentication.',
twoFactorAuthCannotDisable: 'Cannot disable 2FA',
twoFactorAuthRequired: 'Two-factor authentication (2FA) is required for your Xero connection and cannot be disabled.',
+ replaceDevice: 'Replace device',
+ replaceDeviceTitle: 'Replace two-factor device',
+ verifyOldDeviceTitle: 'Verify old device',
+ verifyOldDeviceDescription: 'Enter the six-digit code from your current authenticator app to confirm you have access to it.',
+ verifyNewDeviceTitle: 'Set up new device',
+ verifyNewDeviceDescription: 'Scan the QR code with your new device, then enter the code to complete setup.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 1baa6afa156d0..231378cba9af5 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2000,6 +2000,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Tu empresa requiere el uso de autenticación de dos factores. Por favor, habilítala para seguir usando Expensify.',
twoFactorAuthCannotDisable: 'No se puede desactivar la autenticación de dos factores (2FA)',
twoFactorAuthRequired: 'La autenticación de dos factores (2FA) es obligatoria para tu conexión a Xero y no se puede desactivar.',
+ replaceDevice: 'Reemplazar dispositivo',
+ replaceDeviceTitle: 'Reemplazar dispositivo de autenticación de dos factores',
+ verifyOldDeviceTitle: 'Verificar dispositivo anterior',
+ verifyOldDeviceDescription: 'Introduce el código de seis dígitos de tu aplicación de autenticación actual para confirmar que tienes acceso a ella.',
+ verifyNewDeviceTitle: 'Configurar nuevo dispositivo',
+ verifyNewDeviceDescription: 'Escanea el código QR con tu nuevo dispositivo y luego introduce el código para completar la configuración.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index b65c07543413a..d8a0a4bd749f6 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -2129,6 +2129,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Votre entreprise exige l’authentification à deux facteurs.',
twoFactorAuthCannotDisable: 'Impossible de désactiver la 2FA',
twoFactorAuthRequired: 'L’authentification à deux facteurs (2FA) est requise pour votre connexion Xero et ne peut pas être désactivée.',
+ replaceDevice: 'Remplacer l’appareil',
+ replaceDeviceTitle: 'Remplacer l’appareil d’authentification à deux facteurs',
+ verifyOldDeviceTitle: 'Vérifier l’ancien appareil',
+ verifyOldDeviceDescription: 'Saisissez le code à six chiffres de votre application d’authentification actuelle pour confirmer que vous y avez accès.',
+ verifyNewDeviceTitle: 'Configurer un nouvel appareil',
+ verifyNewDeviceDescription: 'Scannez le code QR avec votre nouvel appareil, puis saisissez le code pour terminer la configuration.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index babe2339b9f66..2773f9ec3ff0f 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -2120,6 +2120,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'La tua azienda richiede l’autenticazione a due fattori.',
twoFactorAuthCannotDisable: "Impossibile disabilitare l'autenticazione a due fattori",
twoFactorAuthRequired: 'Per la connessione a Xero è richiesta l’autenticazione a due fattori (2FA) e non può essere disattivata.',
+ replaceDevice: 'Sostituisci dispositivo',
+ replaceDeviceTitle: 'Sostituisci dispositivo a due fattori',
+ verifyOldDeviceTitle: 'Verifica il vecchio dispositivo',
+ verifyOldDeviceDescription: 'Inserisci il codice a sei cifre dalla tua attuale app di autenticazione per confermare che hai accesso ad essa.',
+ verifyNewDeviceTitle: 'Configura nuovo dispositivo',
+ verifyNewDeviceDescription: 'Scansiona il codice QR con il tuo nuovo dispositivo, poi inserisci il codice per completare la configurazione.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index b3a7c6691b6c9..6cafa9c92353f 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -2107,6 +2107,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'あなたの会社では、2 要素認証が必須です。',
twoFactorAuthCannotDisable: '2要素認証を無効にできません',
twoFactorAuthRequired: 'Xero 連携には二要素認証(2FA)が必須で、無効にすることはできません。',
+ replaceDevice: 'デバイスを交換',
+ replaceDeviceTitle: '2 要素認証デバイスを変更',
+ verifyOldDeviceTitle: '古い端末を確認',
+ verifyOldDeviceDescription: '現在使用している認証アプリに表示されている6桁のコードを入力して、アクセスできることを確認してください。',
+ verifyNewDeviceTitle: '新しいデバイスを設定',
+ verifyNewDeviceDescription: '新しいデバイスでQRコードをスキャンし、表示されたコードを入力して設定を完了してください。',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index be5b70b72ab6f..40218171baaf1 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -2117,6 +2117,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Je bedrijf vereist tweefactorauthenticatie.',
twoFactorAuthCannotDisable: 'Kan 2FA niet uitschakelen',
twoFactorAuthRequired: 'Tweestapsverificatie (2FA) is vereist voor je Xero-verbinding en kan niet worden uitgeschakeld.',
+ replaceDevice: 'Apparaat vervangen',
+ replaceDeviceTitle: 'Tweefactorauthenticatie-apparaat vervangen',
+ verifyOldDeviceTitle: 'Oud apparaat verifiëren',
+ verifyOldDeviceDescription: 'Voer de zescijferige code uit je huidige authenticator-app in om te bevestigen dat je daar toegang toe hebt.',
+ verifyNewDeviceTitle: 'Nieuw apparaat instellen',
+ verifyNewDeviceDescription: 'Scan de QR-code met je nieuwe apparaat en voer daarna de code in om de installatie te voltooien.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 2fb31cbf66bd7..ce4095974281a 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -2117,6 +2117,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Twoetapowe uwierzytelnianie jest wymagane przez Twoją firmę.',
twoFactorAuthCannotDisable: 'Nie można wyłączyć 2FA',
twoFactorAuthRequired: 'Dla połączenia z Xero wymagana jest weryfikacja dwuetapowa (2FA) i nie można jej wyłączyć.',
+ replaceDevice: 'Zastąp urządzenie',
+ replaceDeviceTitle: 'Zastąp urządzenie dwuetapowej weryfikacji',
+ verifyOldDeviceTitle: 'Zweryfikuj stare urządzenie',
+ verifyOldDeviceDescription: 'Wpisz sześciocyfrowy kod z bieżącej aplikacji uwierzytelniającej, żeby potwierdzić, że masz do niej dostęp.',
+ verifyNewDeviceTitle: 'Skonfiguruj nowe urządzenie',
+ verifyNewDeviceDescription: 'Zeskanuj kod QR nowym urządzeniem, a następnie wpisz ten kod, aby zakończyć konfigurację.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index eaf08826159ef..6c7b4931b6d82 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -2113,6 +2113,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: 'Sua empresa exige autenticação em duas etapas.',
twoFactorAuthCannotDisable: 'Não é possível desativar a 2FA',
twoFactorAuthRequired: 'A autenticação em duas etapas (2FA) é obrigatória para sua conexão com o Xero e não pode ser desativada.',
+ replaceDevice: 'Substituir dispositivo',
+ replaceDeviceTitle: 'Substituir dispositivo de dois fatores',
+ verifyOldDeviceTitle: 'Verificar dispositivo antigo',
+ verifyOldDeviceDescription: 'Digite o código de seis dígitos do seu aplicativo autenticador atual para confirmar que você tem acesso a ele.',
+ verifyNewDeviceTitle: 'Configurar novo dispositivo',
+ verifyNewDeviceDescription: 'Escaneie o código QR com seu novo dispositivo e depois insira o código para concluir a configuração.',
},
recoveryCodeForm: {
error: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index b4f23ecbec1c9..cd612d7b440ac 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -2079,6 +2079,12 @@ const translations: TranslationDeepObject = {
twoFactorAuthIsRequiredCompany: '您的公司要求使用双重身份验证。',
twoFactorAuthCannotDisable: '无法禁用双重验证',
twoFactorAuthRequired: '您的 Xero 连接需要启用双重身份验证(2FA),且无法将其禁用。',
+ replaceDevice: '更换设备',
+ replaceDeviceTitle: '更换双重验证设备',
+ verifyOldDeviceTitle: '验证旧设备',
+ verifyOldDeviceDescription: '请输入您当前身份验证器应用中的六位数验证码,以确认您仍可访问该应用。',
+ verifyNewDeviceTitle: '设置新设备',
+ verifyNewDeviceDescription: '使用新设备扫描二维码,然后输入代码完成设置。',
},
recoveryCodeForm: {
error: {
diff --git a/src/libs/API/parameters/ReplaceTwoFactorDeviceParams.ts b/src/libs/API/parameters/ReplaceTwoFactorDeviceParams.ts
new file mode 100644
index 0000000000000..5b4db968abb30
--- /dev/null
+++ b/src/libs/API/parameters/ReplaceTwoFactorDeviceParams.ts
@@ -0,0 +1,6 @@
+type ReplaceTwoFactorDeviceParams = {
+ step: 'verify_old' | 'verify_new';
+ twoFactorAuthCode: string;
+};
+
+export default ReplaceTwoFactorDeviceParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index f18688d3cee34..090511f4294ff 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -115,6 +115,7 @@ export type {default as ValidateLoginParams} from './ValidateLoginParams';
export type {default as ValidateSecondaryLoginParams} from './ValidateSecondaryLoginParams';
export type {default as ValidateTwoFactorAuthParams} from './ValidateTwoFactorAuthParams';
export type {default as DisableTwoFactorAuthParams} from './DisableTwoFactorAuthParams';
+export type {default as ReplaceTwoFactorDeviceParams} from './ReplaceTwoFactorDeviceParams';
export type {default as VerifyIdentityForBankAccountParams} from './VerifyIdentityForBankAccountParams';
export type {default as AnswerQuestionsForWalletParams} from './AnswerQuestionsForWalletParams';
export type {default as AddCommentOrAttachmentParams} from './AddCommentOrAttachmentParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 21c6fda54a0af..7cb35b11c1276 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -111,6 +111,7 @@ const WRITE_COMMANDS = {
UNLINK_LOGIN: 'UnlinkLogin',
ENABLE_TWO_FACTOR_AUTH: 'EnableTwoFactorAuth',
DISABLE_TWO_FACTOR_AUTH: 'DisableTwoFactorAuth',
+ REPLACE_TWO_FACTOR_DEVICE: 'ReplaceTwoFactorDevice',
ADD_COMMENT: 'AddComment',
ADD_ATTACHMENT: 'AddAttachment',
ADD_TEXT_AND_ATTACHMENT: 'AddTextAndAttachment',
@@ -664,6 +665,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UNLINK_LOGIN]: Parameters.UnlinkLoginParams;
[WRITE_COMMANDS.ENABLE_TWO_FACTOR_AUTH]: null;
[WRITE_COMMANDS.DISABLE_TWO_FACTOR_AUTH]: Parameters.DisableTwoFactorAuthParams;
+ [WRITE_COMMANDS.REPLACE_TWO_FACTOR_DEVICE]: Parameters.ReplaceTwoFactorDeviceParams;
[WRITE_COMMANDS.ADD_COMMENT]: Parameters.AddCommentOrAttachmentParams;
[WRITE_COMMANDS.ADD_ATTACHMENT]: Parameters.AddCommentOrAttachmentParams;
[WRITE_COMMANDS.CREATE_APP_REPORT]: Parameters.CreateAppReportParams;
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 30d5ba47887e8..b4a9c4321f7d6 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -930,6 +930,8 @@ const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator require('../../../../pages/settings/Security/TwoFactorAuth/DisabledPage').default,
[SCREENS.TWO_FACTOR_AUTH.DISABLE]: () => require('../../../../pages/settings/Security/TwoFactorAuth/DisablePage').default,
[SCREENS.TWO_FACTOR_AUTH.SUCCESS]: () => require('../../../../pages/settings/Security/TwoFactorAuth/SuccessPage').default,
+ [SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_OLD]: () => require('../../../../pages/settings/Security/TwoFactorAuth/ReplaceDeviceVerifyOldPage').default,
+ [SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_NEW]: () => require('../../../../pages/settings/Security/TwoFactorAuth/ReplaceDeviceVerifyNewPage').default,
});
const SearchRouterModalStackNavigator = createModalStackNavigator({
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
index 44b4a085a26c1..e6cbd85ac07a9 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
@@ -89,6 +89,8 @@ const SETTINGS_TO_RHP: Partial['config'] = {
path: ROUTES.SETTINGS_2FA_DISABLE,
exact: true,
},
+ [SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_OLD]: {
+ path: ROUTES.SETTINGS_2FA_REPLACE_VERIFY_OLD,
+ exact: true,
+ },
+ [SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_NEW]: {
+ path: ROUTES.SETTINGS_2FA_REPLACE_VERIFY_NEW,
+ exact: true,
+ },
},
},
[SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: {
diff --git a/src/libs/TwoFactorAuthUtils.ts b/src/libs/TwoFactorAuthUtils.ts
new file mode 100644
index 0000000000000..0680cf9e30cfe
--- /dev/null
+++ b/src/libs/TwoFactorAuthUtils.ts
@@ -0,0 +1,20 @@
+/**
+ * Splits the two-factor auth secret key in 4 chunks of 4 characters each
+ */
+function splitSecretInChunks(secret: string): string {
+ if (secret.length !== 16) {
+ return secret;
+ }
+
+ return `${secret.slice(0, 4)} ${secret.slice(4, 8)} ${secret.slice(8, 12)} ${secret.slice(12, secret.length)}`;
+}
+
+/**
+ * Builds the URL string to generate the QRCode, using the otpauth:// protocol,
+ * so it can be detected by authenticator apps
+ */
+function buildAuthenticatorUrl(contactMethod: string, secretKey: string): string {
+ return `otpauth://totp/Expensify:${contactMethod}?secret=${secretKey}&issuer=Expensify`;
+}
+
+export {splitSecretInChunks, buildAuthenticatorUrl};
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index 7ebdac9a989fc..bbb2a9cb089ec 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -16,6 +16,7 @@ import type {
BeginSignInParams,
DisableTwoFactorAuthParams,
LogOutParams,
+ ReplaceTwoFactorDeviceParams,
RequestNewValidateCodeParams,
RequestUnlinkValidationLinkParams,
ResetSMSDeliveryFailureStatusParams,
@@ -1273,6 +1274,9 @@ function validateTwoFactorAuth(twoFactorAuthCode: string, shouldClearData: boole
key: ONYXKEYS.ACCOUNT,
value: {
isLoading: false,
+ // Clear the secret key once we know we no longer need to show it
+ // This is necessary in case the user needs to complete the replaceTwoFactorDevice flow on this device at some point in the future - that flow uses the presence of this key to know when to navigate from one step to the next
+ twoFactorAuthSecretKey: null,
},
},
];
@@ -1306,6 +1310,54 @@ function validateTwoFactorAuth(twoFactorAuthCode: string, shouldClearData: boole
});
}
+function replaceTwoFactorDevice(step: 'verify_old' | 'verify_new', twoFactorAuthCode: string) {
+ const optimisticData: Array> = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: true,
+ errors: null,
+ },
+ },
+ ];
+
+ const successData: Array> = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ errors: null,
+ // clear out the secret key to signal to the view that the call succeeded
+ ...(step === 'verify_new' ? {twoFactorAuthSecretKey: null} : {}),
+ },
+ },
+ ];
+
+ const failureData: Array> = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ },
+ },
+ ];
+
+ const params: ReplaceTwoFactorDeviceParams = {step, twoFactorAuthCode};
+
+ return API.write(WRITE_COMMANDS.REPLACE_TWO_FACTOR_DEVICE, params, {optimisticData, successData, failureData});
+}
+
+/**
+ * Clears the two-factor auth secret key from account data.
+ * Used when starting the device replacement flow to ensure clean state.
+ */
+function clearTwoFactorAuthSecretKey() {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {twoFactorAuthSecretKey: null});
+}
+
/**
* Waits for a user to sign in.
*
@@ -1587,6 +1639,8 @@ export {
isAnonymousUser,
toggleTwoFactorAuth,
validateTwoFactorAuth,
+ replaceTwoFactorDevice,
+ clearTwoFactorAuthSecretKey,
waitForUserSignIn,
hasAuthToken,
isExpiredSession,
diff --git a/src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx b/src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx
index f5533c6de00ff..dcf81967b212b 100644
--- a/src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx
+++ b/src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx
@@ -26,7 +26,7 @@ import TwoFactorAuthWrapper from './TwoFactorAuthWrapper';
function EnabledPage() {
const theme = useTheme();
const styles = useThemeStyles();
- const icons = useMemoizedLazyExpensifyIcons(['Close']);
+ const icons = useMemoizedLazyExpensifyIcons(['Close', 'Sync']);
const {asset: ShieldYellow} = useMemoizedLazyAsset(() => loadIllustration('ShieldYellow' as IllustrationName));
const {login} = useCurrentUserPersonalDetails();
@@ -64,6 +64,11 @@ function EnabledPage() {
{translate('twoFactorAuth.whatIsTwoFactorAuth')}
+