Compare commits

..

4 Commits

Author SHA1 Message Date
Maurice 8f64431ff1 refactor(auth): one helper finds a backup code for login, reset and MFA disable
Keeps auth.service.ts inside its size baseline: the recovery-code check the
disable path gained is the lookup the password reset already spelled out.
2026-10-18 12:35:37 +02:00
Maurice c378722d63 test(auth): read the disabled MFA state through the ORM, formatted 2026-10-18 12:11:41 +02:00
Maurice bfec874d04 fix(i18n): mention backup codes in the disable-2FA hint for every locale 2026-10-18 11:47:11 +02:00
Jens Kowalsky a36019b7e2 fix(auth): allow backup codes for TOTP recovery 2026-10-18 11:47:10 +02:00
32 changed files with 108 additions and 38 deletions
@@ -308,9 +308,10 @@ export default function AccountTab(): React.ReactElement {
/>
<input
type="text"
inputMode="numeric"
inputMode="text"
autoComplete="one-time-code"
value={mfaDisableCode}
onChange={e => setMfaDisableCode(e.target.value.replace(/\D/g, '').slice(0, 8))}
onChange={e => setMfaDisableCode(e.target.value.replace(/[^a-zA-Z0-9-]/g, '').slice(0, 12))}
placeholder={t('settings.mfa.codePlaceholder')}
aria-label={t('settings.mfa.codePlaceholder')}
className={`${INPUT} font-geist tabular-nums`}
@@ -244,9 +244,10 @@ export default function MSettingsAccount() {
/>
<MSetInput
className="mt-2"
inputMode="numeric"
inputMode="text"
autoComplete="one-time-code"
value={mfaDisableCode}
onChange={(e) => setMfaDisableCode(e.target.value.replace(/\D/g, '').slice(0, 8))}
onChange={(e) => setMfaDisableCode(e.target.value.replace(/[^a-zA-Z0-9-]/g, '').slice(0, 12))}
placeholder={t('settings.mfa.codePlaceholder')}
/>
<MSetButton
+5
View File
@@ -135,3 +135,8 @@ export function generateBackupCodes(count = MFA_BACKUP_CODE_COUNT): string[] {
export function parseBackupCodeHashes(raw: string | null | undefined): string[] {
return decodeJson(USER_MFA_BACKUP_CODES, raw).filter((v): v is string => typeof v === 'string');
}
/** Where `plaintext` sits among a user's stored backup-code hashes, or -1 when it is none of them. */
export function backupCodeIndex(plaintext: string, raw: string | null | undefined): number {
return parseBackupCodeHashes(raw).findIndex((hash) => matchBackupCode(plaintext, hash));
}
+7 -7
View File
@@ -64,6 +64,7 @@ import {
generateBackupCodes,
hashBackupCodeBcrypt,
mask_stored_api_key,
backupCodeIndex,
matchBackupCode,
parseBackupCodeHashes,
stripUserForClient,
@@ -871,9 +872,10 @@ export class AuthService {
throw new DomainError(401, 'Incorrect password');
}
const secret = decryptMfaSecret(user.mfa_secret);
const tokenStr = String(code).replace(/\s/g, '');
const ok = authenticator.verify({ token: tokenStr, secret });
if (!ok) {
const tokenStr = String(code).trim();
const okTotp = authenticator.verify({ token: tokenStr.replace(/\s/g, ''), secret });
// A backup code proves the second factor as at login; disabling drops them all (#2665).
if (!okTotp && backupCodeIndex(tokenStr, user.mfa_backup_codes) === -1) {
throw new DomainError(401, 'Invalid verification code');
}
// Every other session ends with the second factor; this one just proved both.
@@ -1053,10 +1055,8 @@ export class AuthService {
const secret = decryptMfaSecret(user.mfa_secret);
const okTotp = authenticator.verify({ token: supplied.replace(/\s/g, ''), secret });
if (!okTotp) {
const hashes = parseBackupCodeHashes(user.mfa_backup_codes);
const idx = hashes.findIndex((h) => matchBackupCode(supplied, h));
if (idx === -1) throw new DomainError(401, 'Invalid MFA code');
backupCodeConsumedIndex = idx;
backupCodeConsumedIndex = backupCodeIndex(supplied, user.mfa_backup_codes);
if (backupCodeConsumedIndex === -1) throw new DomainError(401, 'Invalid MFA code');
}
}
+46
View File
@@ -517,6 +517,52 @@ describe('MFA', () => {
expect(disableRes.status).toBe(200);
expect(disableRes.body.mfa_enabled).toBe(false);
});
it('AUTH-019 — disable MFA with password and unused backup code', async () => {
const { hashBackupCodeBcrypt } = await import('../../src/nest/auth/auth.helpers');
const { user, password } = createUserWithMfa(testDb);
await updateRows(
orm,
Users,
{ id: user.id },
{
mfa_backup_codes: JSON.stringify([hashBackupCodeBcrypt('A1B2-C3D4')]),
},
);
const res = await request(app)
.post('/api/auth/mfa/disable')
.set('Cookie', authCookie(user.id))
.send({ password, code: 'a1b2-c3d4' });
expect(res.status).toBe(200);
expect(res.body.mfa_enabled).toBe(false);
const updated = await rowOf(Users, { id: user.id });
expect(updated?.mfa_enabled).toBe(0);
expect(updated?.mfa_secret).toBeNull();
expect(updated?.mfa_backup_codes).toBeNull();
});
it('AUTH-019 — reject invalid recovery code and incorrect password', async () => {
const { hashBackupCodeBcrypt } = await import('../../src/nest/auth/auth.helpers');
const { user, password } = createUserWithMfa(testDb);
await updateRows(
orm,
Users,
{ id: user.id },
{
mfa_backup_codes: JSON.stringify([hashBackupCodeBcrypt('A1B2-C3D4')]),
},
);
const badCode = await request(app)
.post('/api/auth/mfa/disable')
.set('Cookie', authCookie(user.id))
.send({ password, code: 'FFFF-FFFF' });
expect(badCode.status).toBe(401);
const badPassword = await request(app)
.post('/api/auth/mfa/disable')
.set('Cookie', authCookie(user.id))
.send({ password: 'invalid-password', code: 'A1B2-C3D4' });
expect(badPassword.status).toBe(401);
});
});
// ─────────────────────────────────────────────────────────────────────────────
+1 -1
View File
@@ -294,7 +294,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'تفعيل 2FA',
'settings.mfa.cancelSetup': 'إلغاء',
'settings.mfa.disableTitle': 'تعطيل 2FA',
'settings.mfa.disableHint': 'أدخل كلمة مرور حسابك ورمزًا حاليًا من المصادقة.',
'settings.mfa.disableHint': 'أدخل كلمة مرور حسابك ورمزًا حاليًا من المصادقة أو رمزًا احتياطيًا غير مستخدم.',
'settings.mfa.disable': 'تعطيل 2FA',
'settings.mfa.toastEnabled': 'تم تفعيل المصادقة الثنائية',
'settings.mfa.toastDisabled': 'تم تعطيل المصادقة الثنائية',
+2 -1
View File
@@ -390,7 +390,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FA-nı aktivləşdir',
'settings.mfa.cancelSetup': 'Ləğv et',
'settings.mfa.disableTitle': '2FA-nı deaktiv et',
'settings.mfa.disableHint': 'Hesab parolunuzu və autentifikatorunuzdakı cari kodu daxil edin.',
'settings.mfa.disableHint':
'Hesab parolunuzu və autentifikatorunuzdakı cari kodu və ya istifadə olunmamış ehtiyat kodu daxil edin.',
'settings.mfa.disable': '2FA-nı deaktiv et',
'settings.mfa.toastEnabled': 'İki mərhələli autentifikasiya aktivləşdirildi',
'settings.mfa.toastDisabled': 'İki mərhələli autentifikasiya deaktiv edildi',
+1 -1
View File
@@ -203,7 +203,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Ativar 2FA',
'settings.mfa.cancelSetup': 'Cancelar',
'settings.mfa.disableTitle': 'Desativar 2FA',
'settings.mfa.disableHint': 'Digite sua senha e um código atual do autenticador.',
'settings.mfa.disableHint': 'Digite sua senha e um código atual do autenticador ou um código de backup não usado.',
'settings.mfa.disable': 'Desativar 2FA',
'settings.mfa.toastEnabled': 'Autenticação em duas etapas ativada',
'settings.mfa.toastDisabled': 'Autenticação em duas etapas desativada',
+2 -1
View File
@@ -253,7 +253,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Activa el 2FA',
'settings.mfa.cancelSetup': 'Cancel·la',
'settings.mfa.disableTitle': 'Desactiva el 2FA',
'settings.mfa.disableHint': 'Introdueix la teva contrasenya i un codi actual del teu autenticador.',
'settings.mfa.disableHint':
'Introdueix la teva contrasenya i un codi actual del teu autenticador o un codi de seguretat no utilitzat.',
'settings.mfa.disable': 'Desactiva el 2FA',
'settings.mfa.toastEnabled': 'Autenticació de dos factors activada',
'settings.mfa.toastDisabled': 'Autenticació de dos factors desactivada',
+1 -1
View File
@@ -284,7 +284,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Zapnout 2FA',
'settings.mfa.cancelSetup': 'Zrušit',
'settings.mfa.disableTitle': 'Vypnout 2FA',
'settings.mfa.disableHint': 'Zadejte své heslo k účtu a aktuální kód z aplikace.',
'settings.mfa.disableHint': 'Zadejte své heslo k účtu a aktuální kód z aplikace nebo nepoužitý záložní kód.',
'settings.mfa.disable': 'Vypnout 2FA',
'settings.mfa.toastEnabled': 'Dvoufaktorové ověření bylo zapnuto',
'settings.mfa.toastDisabled': 'Dvoufaktorové ověření bylo vypnuto',
+2 -1
View File
@@ -292,7 +292,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FA aktivieren',
'settings.mfa.cancelSetup': 'Abbrechen',
'settings.mfa.disableTitle': '2FA deaktivieren',
'settings.mfa.disableHint': 'Passwort und einen aktuellen Code aus der Authenticator-App eingeben.',
'settings.mfa.disableHint':
'Passwort und einen aktuellen Code aus der Authenticator-App oder einen unbenutzten Backup-Code eingeben.',
'settings.mfa.disable': '2FA deaktivieren',
'settings.mfa.toastEnabled': 'Zwei-Faktor-Authentifizierung aktiviert',
'settings.mfa.toastDisabled': 'Zwei-Faktor-Authentifizierung deaktiviert',
+1 -1
View File
@@ -350,7 +350,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Enable 2FA',
'settings.mfa.cancelSetup': 'Cancel',
'settings.mfa.disableTitle': 'Disable 2FA',
'settings.mfa.disableHint': 'Enter your account password and a current code from your authenticator.',
'settings.mfa.disableHint': 'Enter your account password and a current authenticator code or an unused backup code.',
'settings.mfa.disable': 'Disable 2FA',
'settings.mfa.toastEnabled': 'Two-factor authentication enabled',
'settings.mfa.toastDisabled': 'Two-factor authentication disabled',
+2 -1
View File
@@ -270,7 +270,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Activar 2FA',
'settings.mfa.cancelSetup': 'Cancelar',
'settings.mfa.disableTitle': 'Desactivar 2FA',
'settings.mfa.disableHint': 'Introduce tu contraseña y un código actual de tu autenticador.',
'settings.mfa.disableHint':
'Introduce tu contraseña y un código actual de tu autenticador o un código de respaldo sin usar.',
'settings.mfa.disable': 'Desactivar 2FA',
'settings.mfa.toastEnabled': 'Autenticación de dos factores activada',
'settings.mfa.toastDisabled': 'Autenticación de dos factores desactivada',
+1 -1
View File
@@ -355,7 +355,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Luba 2FA',
'settings.mfa.cancelSetup': 'Tühista',
'settings.mfa.disableTitle': 'Keela 2FA',
'settings.mfa.disableHint': 'Sisesta oma konto parool ja autentimisrakenduse kehtiv kood.',
'settings.mfa.disableHint': 'Sisesta oma konto parool ja autentimisrakenduse kehtiv kood või kasutamata varukood.',
'settings.mfa.disable': 'Keela 2FA',
'settings.mfa.toastEnabled': 'Kaheastmeline autentimine lubatud',
'settings.mfa.toastDisabled': 'Kaheastmeline autentimine keelatud',
+2 -1
View File
@@ -284,7 +284,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Activer 2FA',
'settings.mfa.cancelSetup': 'Annuler',
'settings.mfa.disableTitle': 'Désactiver 2FA',
'settings.mfa.disableHint': 'Entrez votre mot de passe et un code actuel de votre authentificateur.',
'settings.mfa.disableHint':
'Entrez votre mot de passe et un code actuel de votre authentificateur ou un code de secours inutilisé.',
'settings.mfa.disable': 'Désactiver 2FA',
'settings.mfa.toastEnabled': 'Authentification à deux facteurs activée',
'settings.mfa.toastDisabled': 'Authentification à deux facteurs désactivée',
+1 -1
View File
@@ -354,7 +354,7 @@ const settings: TranslationStrings = {
'settings.mfa.cancelSetup': 'Ακύρωση',
'settings.mfa.disableTitle': 'Απενεργοποίηση 2FA',
'settings.mfa.disableHint':
'Εισάγετε τον κωδικό του λογαριασμού σας και έναν τρέχοντα κωδικό από τον authenticator σας.',
'Εισαγάγετε τον κωδικό πρόσβασης του λογαριασμού σας και έναν τρέχοντα κωδικό από την εφαρμογή ελέγχου ταυτότητας ή έναν αχρησιμοποίητο εφεδρικό κωδικό.',
'settings.mfa.disable': 'Απενεργοποίηση 2FA',
'settings.mfa.toastEnabled': 'Ο έλεγχος ταυτότητας δύο παραγόντων ενεργοποιήθηκε',
'settings.mfa.toastDisabled': 'Ο έλεγχος ταυτότητας δύο παραγόντων απενεργοποιήθηκε',
+2 -1
View File
@@ -289,7 +289,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FA engedélyezése',
'settings.mfa.cancelSetup': 'Mégse',
'settings.mfa.disableTitle': '2FA kikapcsolása',
'settings.mfa.disableHint': 'Add meg a fiókod jelszavát és a hitelesítő alkalmazás aktuális kódját.',
'settings.mfa.disableHint':
'Add meg a fiókod jelszavát és a hitelesítő alkalmazás aktuális kódját vagy egy fel nem használt tartalékkódot.',
'settings.mfa.disable': '2FA kikapcsolása',
'settings.mfa.toastEnabled': 'Kétfaktoros hitelesítés engedélyezve',
'settings.mfa.toastDisabled': 'Kétfaktoros hitelesítés kikapcsolva',
+2 -1
View File
@@ -340,7 +340,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Aktifkan 2FA',
'settings.mfa.cancelSetup': 'Batal',
'settings.mfa.disableTitle': 'Nonaktifkan 2FA',
'settings.mfa.disableHint': 'Masukkan kata sandi akun dan kode terkini dari aplikasi autentikatormu.',
'settings.mfa.disableHint':
'Masukkan kata sandi akun dan kode terkini dari aplikasi autentikatormu atau kode cadangan yang belum dipakai.',
'settings.mfa.disable': 'Nonaktifkan 2FA',
'settings.mfa.toastEnabled': 'Autentikasi dua faktor diaktifkan',
'settings.mfa.toastDisabled': 'Autentikasi dua faktor dinonaktifkan',
+2 -1
View File
@@ -288,7 +288,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Abilita 2FA',
'settings.mfa.cancelSetup': 'Annulla',
'settings.mfa.disableTitle': 'Disabilita 2FA',
'settings.mfa.disableHint': 'Inserisci la password del tuo account e un codice attuale dal tuo authenticator.',
'settings.mfa.disableHint':
'Inserisci la password del tuo account e un codice attuale dal tuo authenticator o un codice di backup non utilizzato.',
'settings.mfa.disable': 'Disabilita 2FA',
'settings.mfa.toastEnabled': 'Autenticazione a due fattori abilitata',
'settings.mfa.toastDisabled': 'Autenticazione a due fattori disabilitata',
+2 -1
View File
@@ -313,7 +313,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FAを有効化',
'settings.mfa.cancelSetup': 'キャンセル',
'settings.mfa.disableTitle': '2FAを無効化',
'settings.mfa.disableHint': 'パスワードと現在のコードを入力してください。',
'settings.mfa.disableHint':
'パスワードと、認証アプリの現在のコードまたは未使用のバックアップコードを入力してください。',
'settings.mfa.disable': '2FAを無効化',
'settings.mfa.toastEnabled': '2FAを有効にしました',
'settings.mfa.toastDisabled': '2FAを無効にしました',
+1 -1
View File
@@ -329,7 +329,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FA 활성화',
'settings.mfa.cancelSetup': '취소',
'settings.mfa.disableTitle': '2FA 비활성화',
'settings.mfa.disableHint': '계정 비밀번호와 인증 앱의 현재 코드를 입력하세요.',
'settings.mfa.disableHint': '계정 비밀번호와 인증 앱의 현재 코드 또는 사용하지 않은 백업 코드를 입력하세요.',
'settings.mfa.disable': '2FA 비활성화',
'settings.mfa.toastEnabled': '2단계 인증이 활성화되었습니다',
'settings.mfa.toastDisabled': '2단계 인증이 비활성화되었습니다',
+2 -1
View File
@@ -280,7 +280,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '2FA inschakelen',
'settings.mfa.cancelSetup': 'Annuleren',
'settings.mfa.disableTitle': '2FA uitschakelen',
'settings.mfa.disableHint': 'Voer je wachtwoord en een huidige code van je authenticator in.',
'settings.mfa.disableHint':
'Voer je wachtwoord en een huidige code van je authenticator of een ongebruikte back-upcode in.',
'settings.mfa.disable': '2FA uitschakelen',
'settings.mfa.toastEnabled': 'Tweefactorauthenticatie ingeschakeld',
'settings.mfa.toastDisabled': 'Tweefactorauthenticatie uitgeschakeld',
+2 -1
View File
@@ -285,7 +285,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Włącz 2FA',
'settings.mfa.cancelSetup': 'Anuluj',
'settings.mfa.disableTitle': 'Wyłącz 2FA',
'settings.mfa.disableHint': 'Podaj hasło do konta i aktualny kod z aplikacji uwierzytelniającej.',
'settings.mfa.disableHint':
'Podaj hasło do konta i aktualny kod z aplikacji uwierzytelniającej lub nieużyty kod zapasowy.',
'settings.mfa.disable': 'Wyłącz 2FA',
'settings.mfa.toastEnabled': 'Uwierzytelnianie dwuskładnikowe zostało włączone',
'settings.mfa.toastDisabled': 'Uwierzytelnianie dwuskładnikowe zostało wyłączone',
+2 -1
View File
@@ -279,7 +279,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Включить 2FA',
'settings.mfa.cancelSetup': 'Отмена',
'settings.mfa.disableTitle': 'Отключить 2FA',
'settings.mfa.disableHint': 'Введите пароль аккаунта и текущий код из аутентификатора.',
'settings.mfa.disableHint':
'Введите пароль аккаунта и текущий код из аутентификатора или неиспользованный резервный код.',
'settings.mfa.disable': 'Отключить 2FA',
'settings.mfa.toastEnabled': 'Двухфакторная аутентификация включена',
'settings.mfa.toastDisabled': 'Двухфакторная аутентификация отключена',
+2 -1
View File
@@ -353,7 +353,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Zapnúť 2FA',
'settings.mfa.cancelSetup': 'Zrušiť',
'settings.mfa.disableTitle': 'Vypnúť 2FA',
'settings.mfa.disableHint': 'Zadajte heslo k účtu a aktuálny kód z autentifikačnej aplikácie.',
'settings.mfa.disableHint':
'Zadajte heslo k účtu a aktuálny kód z autentifikačnej aplikácie alebo nepoužitý záložný kód.',
'settings.mfa.disable': 'Vypnúť 2FA',
'settings.mfa.toastEnabled': 'Dvojfaktorové overenie zapnuté',
'settings.mfa.toastDisabled': 'Dvojfaktorové overenie vypnuté',
+2 -1
View File
@@ -346,7 +346,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Aktivera 2FA',
'settings.mfa.cancelSetup': 'Avbryt',
'settings.mfa.disableTitle': 'Inaktivera 2FA',
'settings.mfa.disableHint': 'Ange ditt kontolösenord och en aktuell kod från din autentiseringsapp.',
'settings.mfa.disableHint':
'Ange ditt kontolösenord och en aktuell kod från din autentiseringsapp eller en oanvänd reservkod.',
'settings.mfa.disable': 'Inaktivera 2FA',
'settings.mfa.toastEnabled': 'Tvåfaktorsautentisering är aktiverad',
'settings.mfa.toastDisabled': 'Tvåfaktorsautentisering är inaktiverad',
+1 -1
View File
@@ -339,7 +339,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'เปิดใช้งาน 2FA',
'settings.mfa.cancelSetup': 'ยกเลิก',
'settings.mfa.disableTitle': 'ปิดการใช้งาน 2FA',
'settings.mfa.disableHint': 'ป้อนรหัสผ่านบัญชีของคุณและรหัสปัจจุบันจากตัวตรวจสอบสิทธิ์ของคุณ',
'settings.mfa.disableHint': 'ป้อนรหัสผ่านบัญชีของคุณและรหัสปัจจุบันจากตัวตรวจสอบสิทธิ์ หรือรหัสสำรองที่ยังไม่ได้ใช้',
'settings.mfa.disable': 'ปิดการใช้งาน 2FA',
'settings.mfa.toastEnabled': 'เปิดใช้งานการรับรองความถูกต้องด้วยสองปัจจัย',
'settings.mfa.toastDisabled': 'การรับรองความถูกต้องด้วยสองปัจจัยถูกปิดใช้งาน',
+2 -1
View File
@@ -335,7 +335,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': "2FA'yı Etkinleştir",
'settings.mfa.cancelSetup': 'İptal etmek',
'settings.mfa.disableTitle': "2FA'yı devre dışı bırak",
'settings.mfa.disableHint': 'Hesap şifrenizi ve kimlik doğrulayıcınızdan aldığınız geçerli kodu girin.',
'settings.mfa.disableHint':
'Hesap şifrenizi ve kimlik doğrulayıcınızdan aldığınız geçerli kodu veya kullanılmamış bir yedek kodu girin.',
'settings.mfa.disable': "2FA'yı devre dışı bırak",
'settings.mfa.toastEnabled': 'İki faktörlü kimlik doğrulama etkin',
'settings.mfa.toastDisabled': 'İki faktörlü kimlik doğrulama devre dışı',
+2 -1
View File
@@ -275,7 +275,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Увімкнути 2FA',
'settings.mfa.cancelSetup': 'Скасувати',
'settings.mfa.disableTitle': 'Вимкнути 2FA',
'settings.mfa.disableHint': 'Введіть пароль акаунта та поточний код з автентифікатора.',
'settings.mfa.disableHint':
'Введіть пароль акаунта та поточний код з автентифікатора або невикористаний резервний код.',
'settings.mfa.disable': 'Вимкнути 2FA',
'settings.mfa.toastEnabled': 'Двофакторна автентифікація увімкнена',
'settings.mfa.toastDisabled': 'Двофакторна автентифікація вимкнена',
+2 -1
View File
@@ -348,7 +348,8 @@ const settings: TranslationStrings = {
'settings.mfa.enable': 'Kích hoạt 2FA',
'settings.mfa.cancelSetup': 'Hủy',
'settings.mfa.disableTitle': 'Tắt 2FA',
'settings.mfa.disableHint': 'Nhập mật khẩu tài khoản của bạn và mã hiện tại từ trình xác thực của bạn.',
'settings.mfa.disableHint':
'Nhập mật khẩu tài khoản của bạn và mã hiện tại từ trình xác thực hoặc một mã dự phòng chưa dùng.',
'settings.mfa.disable': 'Tắt 2FA',
'settings.mfa.toastEnabled': 'Đã bật xác thực hai yếu tố',
'settings.mfa.toastDisabled': 'Xác thực hai yếu tố bị vô hiệu hóa',
+1 -1
View File
@@ -311,7 +311,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '啟用 2FA',
'settings.mfa.cancelSetup': '取消',
'settings.mfa.disableTitle': '停用 2FA',
'settings.mfa.disableHint': '輸入您的賬戶密碼和身份驗證器中的當前驗證碼。',
'settings.mfa.disableHint': '輸入您的賬戶密碼,以及身份驗證器中的當前驗證碼或未使用的備用碼。',
'settings.mfa.disable': '停用 2FA',
'settings.mfa.toastEnabled': '雙因素認證已啟用',
'settings.mfa.toastDisabled': '雙因素認證已停用',
+1 -1
View File
@@ -263,7 +263,7 @@ const settings: TranslationStrings = {
'settings.mfa.enable': '启用 2FA',
'settings.mfa.cancelSetup': '取消',
'settings.mfa.disableTitle': '停用 2FA',
'settings.mfa.disableHint': '输入您的账户密码和身份验证器中的当前验证码。',
'settings.mfa.disableHint': '输入您的账户密码,以及身份验证器中的当前验证码或未使用的备用码。',
'settings.mfa.disable': '停用 2FA',
'settings.mfa.toastEnabled': '双因素认证已启用',
'settings.mfa.toastDisabled': '双因素认证已停用',