Skip to content
2 changes: 2 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',

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 @@ -2123,6 +2123,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: '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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,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 @@ -2129,6 +2129,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 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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2120,6 +2120,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 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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,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 @@ -2117,6 +2117,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: '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: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2117,6 +2117,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: '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: {
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 @@ -2113,6 +2113,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 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: {
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 @@ -2079,6 +2079,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 @@ -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;
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 @@ -89,6 +89,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 @@ -1361,6 +1361,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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-3 (docs)

The splitSecretInChunks and buildAuthenticatorUrl functions extracted here are exact duplicates of the local functions already defined in src/pages/settings/Security/TwoFactorAuth/VerifyPage.tsx (lines 61-75). The new TwoFactorAuthSecretDisplay component correctly imports from this shared utility, but VerifyPage.tsx was not updated to also use it, leaving duplicate implementations in the codebase.

Update VerifyPage.tsx to import from the new shared utility and remove its local copies:

import {buildAuthenticatorUrl, splitSecretInChunks} from '@libs/TwoFactorAuthUtils';

Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

if (secret.length !== 16) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-2 (docs)

The number 16 is a magic number representing the expected length of a TOTP secret key. It should be extracted to a named constant for clarity.

Define a constant in CONST or at the top of this file:

const TOTP_SECRET_KEY_LENGTH = 16;

function splitSecretInChunks(secret: string): string {
    if (secret.length !== TOTP_SECRET_KEY_LENGTH) {
        return secret;
    }
    // ...
}

Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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 @@ -68,7 +69,7 @@
import type Session from '@src/types/onyx/Session';
import type {AutoAuthState} from '@src/types/onyx/Session';
import pkg from '../../../../package.json';
import clearCache from './clearCache';

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

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected parent import '../Attachment'. Use '@userActions/Attachment' instead
import updateSessionAuthTokens from './updateSessionAuthTokens';

const INVALID_TOKEN = 'pizza';
Expand All @@ -80,7 +81,7 @@
let hasSwitchedAccountInHybridMode = false;

Onyx.connect({
key: ONYXKEYS.SESSION,

Check warning on line 84 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
callback: (value) => {
session = value ?? {};

Expand All @@ -105,19 +106,19 @@

let stashedSession: Session = {};
Onyx.connect({
key: ONYXKEYS.STASHED_SESSION,

Check warning on line 109 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
callback: (value) => (stashedSession = value ?? {}),
});

let credentials: Credentials = {};
Onyx.connect({
key: ONYXKEYS.CREDENTIALS,

Check warning on line 115 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
callback: (value) => (credentials = value ?? {}),
});

let stashedCredentials: Credentials = {};
Onyx.connect({
key: ONYXKEYS.STASHED_CREDENTIALS,

Check warning on line 121 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
callback: (value) => (stashedCredentials = value ?? {}),
});

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: null});
}

/**
* 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