Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e87629e
WIP ReplaceTwoFactorDevice flow (verify step doesn't work)
chuckdries Jan 9, 2026
de2f21b
create TwoFactorAuthSecretDisplay and fixup navigation and error hand…
chuckdries Jan 9, 2026
5ef50af
add shouldEnableViewportOffsetTop to replace 2fa device pages
chuckdries Jan 12, 2026
f41d6cf
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Jan 12, 2026
dd04bac
Address PR feedback: visual change to secret display, don't allow rec…
chuckdries Jan 12, 2026
54fb6bd
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Jan 14, 2026
f6fc20b
Add spanish translations
chuckdries Jan 15, 2026
2bd915d
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Jan 20, 2026
49a6821
Apply translations
chuckdries Jan 20, 2026
8daed0f
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Jan 21, 2026
70c5c93
Fix lint error
chuckdries Jan 21, 2026
7e3fcb7
forceReplace when navigating to 2FA success in replace device flow
chuckdries Jan 27, 2026
0011051
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Jan 27, 2026
9343004
Run prettier
chuckdries Jan 27, 2026
8b5a8ae
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Mar 9, 2026
e4ec135
Merge remote-tracking branch 'origin/main' into chuckdries/ReplaceTwo…
chuckdries Mar 10, 2026
dacbcfb
Fix icon load strategy in EnabledPage and other merge conflict resolu…
chuckdries Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5888,6 +5888,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',
Expand Down
2 changes: 2 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,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',

Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions src/components/Pressable/PressableWithDelayToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,17 @@ function PressableWithDelayToggle({
>
{({hovered, pressed}) => (
<>
{!inline && displayLabelText}
{shouldShowIcon && (
<Icon
src={!isActive ? resolvedIconChecked : (icon ?? resolvedIconChecked)}
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, !isActive))}
additionalStyles={iconStyles}
additionalStyles={[styles.mr2, iconStyles]}
width={iconWidth}
height={iconHeight}
inline={inline}
/>
)}
{!inline && displayLabelText}
</>
)}
</PressableWithoutFeedback>
Expand Down
6 changes: 6 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2097,6 +2097,12 @@ const translations: TranslationDeepObject<typeof en> = {
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: 'Gib den sechsstelligen Code aus deiner aktuellen Authenticator-App ein, um zu bestätigen, dass du Zugriff darauf hast.',
verifyNewDeviceTitle: 'Neues Gerät einrichten',
verifyNewDeviceDescription: 'Scanne den QR-Code mit deinem neuen Gerät und gib dann den Code ein, um die Einrichtung abzuschließen.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,12 @@ const translations: TranslationDeepObject<typeof en> = {
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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,12 @@ const translations: TranslationDeepObject<typeof en> = {
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 depuis 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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,12 @@ const translations: TranslationDeepObject<typeof en> = {
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 per l'autenticazione a due fattori",
verifyOldDeviceTitle: 'Verifica dispositivo precedente',
verifyOldDeviceDescription: 'Inserisci il codice a sei cifre dalla tua attuale app di autenticazione per confermare di avere accesso ad essa.',
verifyNewDeviceTitle: 'Configura nuovo dispositivo',
verifyNewDeviceDescription: 'Scansiona il codice QR con il tuo nuovo dispositivo, quindi inserisci il codice per completare la configurazione.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,12 @@ const translations: TranslationDeepObject<typeof en> = {
twoFactorAuthIsRequiredCompany: 'あなたの会社では、2 要素認証が必須です。',
twoFactorAuthCannotDisable: '2要素認証を無効にできません',
twoFactorAuthRequired: 'Xero 連携には二要素認証(2FA)が必須で、無効にすることはできません。',
replaceDevice: 'デバイスを交換',
replaceDeviceTitle: '2 要素認証デバイスを交換',
verifyOldDeviceTitle: '古いデバイスを確認',
verifyOldDeviceDescription: '現在使用している認証アプリに表示されている6桁のコードを入力して、アクセスできることを確認してください。',
verifyNewDeviceTitle: '新しいデバイスをセットアップ',
verifyNewDeviceDescription: '新しいデバイスでQRコードをスキャンし、その後コードを入力して設定を完了してください。',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,12 @@ const translations: TranslationDeepObject<typeof en> = {
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: 'Twee-factorapparaat vervangen',
verifyOldDeviceTitle: 'Oud apparaat verifiëren',
verifyOldDeviceDescription: 'Voer de zescijferige code uit je huidige authenticator-app in om te bevestigen dat je er toegang toe hebt.',
verifyNewDeviceTitle: 'Nieuw apparaat instellen',
verifyNewDeviceDescription: 'Scan de QR-code met je nieuwe apparaat en voer vervolgens de code in om de installatie te voltooien.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,12 @@ const translations: TranslationDeepObject<typeof en> = {
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: 'Wymień urządzenie uwierzytelniania dwuskładnikowego',
verifyOldDeviceTitle: 'Zweryfikuj stare urządzenie',
verifyOldDeviceDescription: 'Wprowadź sześciocyfrowy kod z bieżącej aplikacji uwierzytelniającej, aby potwierdzić, że masz do niej dostęp.',
verifyNewDeviceTitle: 'Skonfiguruj nowe urządzenie',
verifyNewDeviceDescription: 'Zeskanuj kod QR swoim nowym urządzeniem, a następnie wprowadź kod, aby zakończyć konfigurację.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2087,6 +2087,12 @@ const translations: TranslationDeepObject<typeof en> = {
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 autenticação em duas etapas',
verifyOldDeviceTitle: 'Verificar dispositivo antigo',
verifyOldDeviceDescription: 'Insira 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, em seguida, insira o código para concluir a configuração.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,12 @@ const translations: TranslationDeepObject<typeof en> = {
twoFactorAuthIsRequiredCompany: '您的公司要求使用双重身份验证。',
twoFactorAuthCannotDisable: '无法禁用双重验证',
twoFactorAuthRequired: '您的 Xero 连接需要启用双重身份验证(2FA),且无法将其禁用。',
replaceDevice: '更换设备',
replaceDeviceTitle: '更换双重验证设备',
verifyOldDeviceTitle: '验证旧设备',
verifyOldDeviceDescription: '请输入您当前身份验证器应用中的六位数验证码,以确认您可以访问该应用。',
verifyNewDeviceTitle: '设置新设备',
verifyNewDeviceDescription: '使用新设备扫描二维码,然后输入代码以完成设置。',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/libs/API/parameters/ReplaceTwoFactorDeviceParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type ReplaceTwoFactorDeviceParams = {
step: 'verify_old' | 'verify_new';
twoFactorAuthCode: string;
};

export default ReplaceTwoFactorDeviceParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -662,6 +663,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,8 @@ const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator<EnablePay
[SCREENS.TWO_FACTOR_AUTH.DISABLED]: () => require<ReactComponentModule>('../../../../pages/settings/Security/TwoFactorAuth/DisabledPage').default,
[SCREENS.TWO_FACTOR_AUTH.DISABLE]: () => require<ReactComponentModule>('../../../../pages/settings/Security/TwoFactorAuth/DisablePage').default,
[SCREENS.TWO_FACTOR_AUTH.SUCCESS]: () => require<ReactComponentModule>('../../../../pages/settings/Security/TwoFactorAuth/SuccessPage').default,
[SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_OLD]: () => require<ReactComponentModule>('../../../../pages/settings/Security/TwoFactorAuth/ReplaceDeviceVerifyOldPage').default,
[SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_NEW]: () => require<ReactComponentModule>('../../../../pages/settings/Security/TwoFactorAuth/ReplaceDeviceVerifyNewPage').default,
});

const SearchRouterModalStackNavigator = createModalStackNavigator({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ const SETTINGS_TO_RHP: Partial<Record<keyof SettingsSplitNavigatorParamList, str
SCREENS.TWO_FACTOR_AUTH.SUCCESS,
SCREENS.TWO_FACTOR_AUTH.DISABLED,
SCREENS.TWO_FACTOR_AUTH.DISABLE,
SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_OLD,
SCREENS.TWO_FACTOR_AUTH.REPLACE_VERIFY_NEW,
SCREENS.SETTINGS.CLOSE,
SCREENS.SETTINGS.LOCK.LOCK_ACCOUNT,
SCREENS.SETTINGS.LOCK.UNLOCK_ACCOUNT,
Expand Down
8 changes: 8 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,14 @@ const config: LinkingOptions<RootNavigatorParamList>['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]: {
Expand Down
20 changes: 20 additions & 0 deletions src/libs/TwoFactorAuthUtils.ts
Original file line number Diff line number Diff line change
@@ -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};
54 changes: 54 additions & 0 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
BeginSignInParams,
DisableTwoFactorAuthParams,
LogOutParams,
ReplaceTwoFactorDeviceParams,
RequestNewValidateCodeParams,
RequestUnlinkValidationLinkParams,
ResetSMSDeliveryFailureStatusParams,
Expand Down Expand Up @@ -79,7 +80,7 @@
let isHybridAppSetupFinished = false;
let hasSwitchedAccountInHybridMode = false;

Onyx.connect({

Check warning on line 83 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
session = value ?? {};
Expand All @@ -104,25 +105,25 @@
});

let stashedSession: Session = {};
Onyx.connect({

Check warning on line 108 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_SESSION,
callback: (value) => (stashedSession = value ?? {}),
});

let credentials: Credentials = {};
Onyx.connect({

Check warning on line 114 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CREDENTIALS,
callback: (value) => (credentials = value ?? {}),
});

let stashedCredentials: Credentials = {};
Onyx.connect({

Check warning on line 120 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_CREDENTIALS,
callback: (value) => (stashedCredentials = value ?? {}),
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 126 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (newActivePolicyID) => {
activePolicyID = newActivePolicyID;
Expand Down Expand Up @@ -1273,6 +1274,9 @@
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,
},
},
];
Expand Down Expand Up @@ -1306,6 +1310,54 @@
});
}

function replaceTwoFactorDevice(step: 'verify_old' | 'verify_new', twoFactorAuthCode: string) {
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT>> = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {
isLoading: true,
errors: null,
},
},
];

const successData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT>> = [
{
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<OnyxUpdate<typeof ONYXKEYS.ACCOUNT>> = [
{
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: undefined});
}

/**
* Waits for a user to sign in.
*
Expand Down Expand Up @@ -1587,6 +1639,8 @@
isAnonymousUser,
toggleTwoFactorAuth,
validateTwoFactorAuth,
replaceTwoFactorDevice,
clearTwoFactorAuthSecretKey,
waitForUserSignIn,
hasAuthToken,
isExpiredSession,
Expand Down
Loading
Loading