diff --git a/apps/login/acceptance/tests/code-screen.ts b/apps/login/acceptance/tests/code-screen.ts index 3ab9dad26..1889d3cdd 100644 --- a/apps/login/acceptance/tests/code-screen.ts +++ b/apps/login/acceptance/tests/code-screen.ts @@ -1,4 +1,5 @@ import { expect, Page } from "@playwright/test"; +import en from "../../locales/en.json"; const codeTextInput = "code-text-input"; @@ -8,5 +9,5 @@ export async function codeScreen(page: Page, code: string) { export async function codeScreenExpect(page: Page, code: string) { await expect(page.getByTestId(codeTextInput)).toHaveValue(code); - await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code"); + await expect(page.getByTestId("error").locator("div")).toContainText(en.otp.errors.couldNotVerifyCode); } diff --git a/apps/login/locales/ar.json b/apps/login/locales/ar.json index 80775be85..c9d4bf2c3 100644 --- a/apps/login/locales/ar.json +++ b/apps/login/locales/ar.json @@ -152,7 +152,8 @@ "failedPrecondition": "فشل الشرط المسبق", "sessionNotValid": "الجلسة غير صالحة", "passwordVerificationMissing": "التحقق من كلمة المرور مفقود.", - "passwordVerificationTooOld": "التحقق من كلمة المرور قديم جدًا. يرجى التحقق من كلمة المرور مرة أخرى." + "passwordVerificationTooOld": "التحقق من كلمة المرور قديم جدًا. يرجى التحقق من كلمة المرور مرة أخرى.", + "passwordResetNotAllowed": "إعادة تعيين كلمة المرور غير مسموح بها. اتصل بالمسؤول لمزيد من المعلومات." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "هذا الحقل مطلوب" } + }, + "errors": { + "couldNotRequestChallenge": "تعذر طلب تحدي OTP", + "couldNotVerifyCode": "تعذر التحقق من رمز OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "حدد الطريقة التي ترغب في المصادقة بها", "noMethodsAvailable": "لا توجد طرق مصادقة متاحة", "allSetup": "لقد قمت بالفعل بإعداد مصادق!", - "linkWithIDP": "أو الربط مع مزود هوية" + "linkWithIDP": "أو الربط مع مزود هوية", + "methods": { + "totp": "تطبيق المصادقة", + "u2f": "العامل الثاني العالمي", + "otpEmail": "رمز عبر البريد الإلكتروني", + "otpSms": "رمز عبر الرسائل القصيرة", + "passkey": "مفاتيح المرور", + "password": "كلمة المرور" + } }, "device": { "usercode": { diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index d106f768f..9c3f23f15 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -152,7 +152,8 @@ "failedPrecondition": "Vorbedingung fehlgeschlagen", "sessionNotValid": "Sitzung ist nicht gültig", "passwordVerificationMissing": "Passwort-Verifizierung fehlt.", - "passwordVerificationTooOld": "Passwort-Verifizierung ist zu alt. Bitte verifizieren Sie Ihr Passwort erneut." + "passwordVerificationTooOld": "Passwort-Verifizierung ist zu alt. Bitte verifizieren Sie Ihr Passwort erneut.", + "passwordResetNotAllowed": "Das Zurücksetzen des Passworts ist nicht zulässig. Wenden Sie sich für weitere Informationen an Ihren Administrator." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Dieses Feld ist erforderlich" } + }, + "errors": { + "couldNotRequestChallenge": "OTP-Challenge konnte nicht angefordert werden", + "couldNotVerifyCode": "OTP-Code konnte nicht verifiziert werden" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten.", "noMethodsAvailable": "Keine Authentifizierungsmethoden verfügbar", "allSetup": "Sie haben bereits einen Authentifikator eingerichtet!", - "linkWithIDP": "oder verknüpfe mit einem Identitätsanbieter" + "linkWithIDP": "oder verknüpfe mit einem Identitätsanbieter", + "methods": { + "totp": "Authentifizierungs-App", + "u2f": "Universeller zweiter Faktor", + "otpEmail": "Code per E-Mail", + "otpSms": "Code per SMS", + "passkey": "Passkeys", + "password": "Passwort" + } }, "device": { "usercode": { diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 512e160f8..22452f6bd 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -152,7 +152,8 @@ "failedPrecondition": "Failed precondition", "sessionNotValid": "Session is not valid", "passwordVerificationMissing": "Password verification is missing.", - "passwordVerificationTooOld": "Password verification is too old. Please verify your password again." + "passwordVerificationTooOld": "Password verification is too old. Please verify your password again.", + "passwordResetNotAllowed": "Password reset is not allowed. Contact your administrator for more information." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "This field is required" } + }, + "errors": { + "couldNotRequestChallenge": "Could not request OTP challenge", + "couldNotVerifyCode": "Could not verify OTP code" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Select the method you would like to authenticate", "noMethodsAvailable": "No authentication methods available", "allSetup": "You have already setup an authenticator!", - "linkWithIDP": "or link with an Identity Provider" + "linkWithIDP": "or link with an Identity Provider", + "methods": { + "totp": "Authenticator App", + "u2f": "Universal Second Factor", + "otpEmail": "Code via Email", + "otpSms": "Code via SMS", + "passkey": "Passkeys", + "password": "Password" + } }, "device": { "usercode": { diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 0b3f7c932..efa02b806 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -152,7 +152,8 @@ "failedPrecondition": "Precondición fallida", "sessionNotValid": "La sesión no es válida", "passwordVerificationMissing": "Falta la verificación de la contraseña.", - "passwordVerificationTooOld": "La verificación de la contraseña es demasiado antigua. Por favor, verifique su contraseña de nuevo." + "passwordVerificationTooOld": "La verificación de la contraseña es demasiado antigua. Por favor, verifique su contraseña de nuevo.", + "passwordResetNotAllowed": "El restablecimiento de contraseña no está permitido. Contacte con su administrador para más información." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Este campo es obligatorio" } + }, + "errors": { + "couldNotRequestChallenge": "No se pudo solicitar el desafío OTP", + "couldNotVerifyCode": "No se pudo verificar el código OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Selecciona el método con el que deseas autenticarte", "noMethodsAvailable": "No hay métodos de autenticación disponibles", "allSetup": "¡Ya has configurado un autenticador!", - "linkWithIDP": "o vincúlalo con un proveedor de identidad" + "linkWithIDP": "o vincúlalo con un proveedor de identidad", + "methods": { + "totp": "Aplicación de autenticación", + "u2f": "Segundo factor universal", + "otpEmail": "Código por correo electrónico", + "otpSms": "Código por SMS", + "passkey": "Clave de acceso", + "password": "Contraseña" + } }, "device": { "usercode": { diff --git a/apps/login/locales/fr.json b/apps/login/locales/fr.json index 7ff211c51..1b34302e1 100644 --- a/apps/login/locales/fr.json +++ b/apps/login/locales/fr.json @@ -152,7 +152,8 @@ "failedPrecondition": "Pré-condition non remplie", "sessionNotValid": "La session n'est pas valide", "passwordVerificationMissing": "La vérification du mot de passe est manquante.", - "passwordVerificationTooOld": "La vérification du mot de passe est trop ancienne. Veuillez vérifier à nouveau votre mot de passe." + "passwordVerificationTooOld": "La vérification du mot de passe est trop ancienne. Veuillez vérifier à nouveau votre mot de passe.", + "passwordResetNotAllowed": "La réinitialisation du mot de passe n'est pas autorisée. Contactez votre administrateur pour plus d'informations." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Ce champ est requis" } + }, + "errors": { + "couldNotRequestChallenge": "Impossible de demander le challenge OTP", + "couldNotVerifyCode": "Impossible de vérifier le code OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Sélectionnez la méthode que vous souhaitez utiliser pour vous authentifier", "noMethodsAvailable": "Aucune méthode d'authentification disponible", "allSetup": "Vous avez déjà configuré un authentificateur !", - "linkWithIDP": "ou lier avec un fournisseur d'identité" + "linkWithIDP": "ou lier avec un fournisseur d'identité", + "methods": { + "totp": "Application d'authentification", + "u2f": "Second facteur universel", + "otpEmail": "Code par e-mail", + "otpSms": "Code par SMS", + "passkey": "Clés d'accès", + "password": "Mot de passe" + } }, "device": { "usercode": { diff --git a/apps/login/locales/hu.json b/apps/login/locales/hu.json index 2e5d77a9f..0d1af266b 100644 --- a/apps/login/locales/hu.json +++ b/apps/login/locales/hu.json @@ -152,7 +152,8 @@ "failedPrecondition": "Az előfeltétel nem teljesült", "sessionNotValid": "A munkamenet nem érvényes", "passwordVerificationMissing": "A jelszó hitelesítés hiányzik.", - "passwordVerificationTooOld": "A jelszó hitelesítés túl régi. Kérjük, erősítsd meg újra a jelszavadat." + "passwordVerificationTooOld": "A jelszó hitelesítés túl régi. Kérjük, erősítsd meg újra a jelszavadat.", + "passwordResetNotAllowed": "A jelszó-visszaállítás nem engedélyezett. További információért fordulj a rendszergazdához." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Ez a mező kötelező" } + }, + "errors": { + "couldNotRequestChallenge": "Nem sikerült OTP-kihívást kérni", + "couldNotVerifyCode": "Nem sikerült ellenőrizni az OTP-kódot" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Válaszd ki, milyen módszerrel szeretnél hitelesíteni", "noMethodsAvailable": "Nincs elérhető hitelesítési módszer", "allSetup": "Már beállítottál egy hitelesítőt!", - "linkWithIDP": "vagy kapcsold össze egy azonosítószolgáltatóval" + "linkWithIDP": "vagy kapcsold össze egy azonosítószolgáltatóval", + "methods": { + "totp": "Hitelesítő alkalmazás", + "u2f": "Univerzális második faktor", + "otpEmail": "Kód e-mailben", + "otpSms": "Kód SMS-ben", + "passkey": "Jelkulcs", + "password": "Jelszó" + } }, "device": { "usercode": { diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index e8d973031..8012bad25 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -152,7 +152,8 @@ "failedPrecondition": "Precondizione non soddisfatta", "sessionNotValid": "La sessione non è valida", "passwordVerificationMissing": "Manca la verifica della password.", - "passwordVerificationTooOld": "La verifica della password è troppo vecchia. Si prega di verificare nuovamente la password." + "passwordVerificationTooOld": "La verifica della password è troppo vecchia. Si prega di verificare nuovamente la password.", + "passwordResetNotAllowed": "Il ripristino della password non è consentito. Contatta il tuo amministratore per ulteriori informazioni." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Questo campo è obbligatorio" } + }, + "errors": { + "couldNotRequestChallenge": "Impossibile richiedere la challenge OTP", + "couldNotVerifyCode": "Impossibile verificare il codice OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Seleziona il metodo con cui desideri autenticarti", "noMethodsAvailable": "Nessun metodo di autenticazione disponibile", "allSetup": "Hai già configurato un autenticatore!", - "linkWithIDP": "o collega con un Identity Provider" + "linkWithIDP": "o collega con un Identity Provider", + "methods": { + "totp": "App di autenticazione", + "u2f": "Secondo fattore universale", + "otpEmail": "Codice via email", + "otpSms": "Codice via SMS", + "passkey": "Passkey", + "password": "Password" + } }, "device": { "usercode": { diff --git a/apps/login/locales/ja.json b/apps/login/locales/ja.json index caaa4124c..33a6d0629 100644 --- a/apps/login/locales/ja.json +++ b/apps/login/locales/ja.json @@ -135,7 +135,8 @@ "couldNotLoadSession": "セッションを読み込めませんでした", "couldNotLoadAuthMethods": "認証方法を読み込めませんでした", "failedPrecondition": "前提条件に失敗しました", - "sessionNotValid": "セッションが無効です" + "sessionNotValid": "セッションが無効です", + "passwordResetNotAllowed": "パスワードのリセットは許可されていません。詳細については管理者に問い合わせてください。" }, "complexity": { "length": "{minLength} 文字以上である必要があります。", @@ -254,6 +255,10 @@ "required": { "code": "必須項目です" } + }, + "errors": { + "couldNotRequestChallenge": "OTPチャレンジをリクエストできませんでした", + "couldNotVerifyCode": "OTPコードを確認できませんでした" } }, "passkey": { @@ -415,7 +420,15 @@ "description": "認証に使用する方法を選択してください", "noMethodsAvailable": "使用可能な認証方法がありません", "allSetup": "認証の設定は既に完了しています!", - "linkWithIDP": "またはIDプロバイダーと連携" + "linkWithIDP": "またはIDプロバイダーと連携", + "methods": { + "totp": "認証アプリ", + "u2f": "ユニバーサルセカンドファクター", + "otpEmail": "メールでコードを受け取る", + "otpSms": "SMSでコードを受け取る", + "passkey": "パスキー", + "password": "パスワード" + } }, "device": { "usercode": { diff --git a/apps/login/locales/nl.json b/apps/login/locales/nl.json index 2e0f4fc32..6236c700a 100644 --- a/apps/login/locales/nl.json +++ b/apps/login/locales/nl.json @@ -152,7 +152,8 @@ "failedPrecondition": "Voorwaarde mislukt", "sessionNotValid": "Sessie is niet geldig", "passwordVerificationMissing": "Wachtwoordverificatie ontbreekt.", - "passwordVerificationTooOld": "Wachtwoordverificatie is te oud. Verifieer je wachtwoord opnieuw." + "passwordVerificationTooOld": "Wachtwoordverificatie is te oud. Verifieer je wachtwoord opnieuw.", + "passwordResetNotAllowed": "Wachtwoord resetten is niet toegestaan. Neem contact op met uw beheerder voor meer informatie." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Dit veld is verplicht" } + }, + "errors": { + "couldNotRequestChallenge": "Kon OTP-uitdaging niet aanvragen", + "couldNotVerifyCode": "Kon OTP-code niet verifiëren" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Selecteer de methode waarmee u wilt verifiëren", "noMethodsAvailable": "Geen authenticatiemethoden beschikbaar", "allSetup": "U heeft al een authenticator ingesteld!", - "linkWithIDP": "of link met een identiteitsprovider" + "linkWithIDP": "of link met een identiteitsprovider", + "methods": { + "totp": "Authenticator-app", + "u2f": "Universele tweede factor", + "otpEmail": "Code via e-mail", + "otpSms": "Code via sms", + "passkey": "Passkeys", + "password": "Wachtwoord" + } }, "device": { "usercode": { diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json index 059c95f14..067f8fc97 100644 --- a/apps/login/locales/pl.json +++ b/apps/login/locales/pl.json @@ -152,7 +152,8 @@ "failedPrecondition": "Nie spełniono warunków wstępnych", "sessionNotValid": "Sesja jest nieważna", "passwordVerificationMissing": "Brak weryfikacji hasła.", - "passwordVerificationTooOld": "Weryfikacja hasła jest przestarzała. Proszę zweryfikować hasło ponownie." + "passwordVerificationTooOld": "Weryfikacja hasła jest przestarzała. Proszę zweryfikować hasło ponownie.", + "passwordResetNotAllowed": "Resetowanie hasła jest niedozwolone. Skontaktuj się z administratorem, aby uzyskać więcej informacji." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "To pole jest wymagane" } + }, + "errors": { + "couldNotRequestChallenge": "Nie można zażądać wyzwania OTP", + "couldNotVerifyCode": "Nie można zweryfikować kodu OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Wybierz metodę, której chcesz użyć do uwierzytelnienia.", "noMethodsAvailable": "Brak dostępnych metod uwierzytelniania", "allSetup": "Już skonfigurowałeś metodę uwierzytelniania!", - "linkWithIDP": "lub połącz z dostawcą tożsamości" + "linkWithIDP": "lub połącz z dostawcą tożsamości", + "methods": { + "totp": "Aplikacja uwierzytelniająca", + "u2f": "Uniwersalny drugi składnik", + "otpEmail": "Kod przez e-mail", + "otpSms": "Kod przez SMS", + "passkey": "Klucz dostępu", + "password": "Hasło" + } }, "device": { "usercode": { diff --git a/apps/login/locales/pt.json b/apps/login/locales/pt.json index 9f8446fea..49729310a 100644 --- a/apps/login/locales/pt.json +++ b/apps/login/locales/pt.json @@ -152,7 +152,8 @@ "failedPrecondition": "Pré-condição falhou", "sessionNotValid": "A sessão não é válida", "passwordVerificationMissing": "A verificação de senha está ausente.", - "passwordVerificationTooOld": "A verificação de senha expirou. Por favor, verifique sua senha novamente." + "passwordVerificationTooOld": "A verificação de senha expirou. Por favor, verifique sua senha novamente.", + "passwordResetNotAllowed": "A redefinição de senha não é permitida. Entre em contato com o administrador para mais informações." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Este campo é obrigatório" } + }, + "errors": { + "couldNotRequestChallenge": "Não foi possível solicitar o desafio OTP", + "couldNotVerifyCode": "Não foi possível verificar o código OTP" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Selecione o método de autenticação desejado", "noMethodsAvailable": "Nenhum método de autenticação disponível", "allSetup": "Você já configurou um autenticador!", - "linkWithIDP": "ou vincule com um Provedor de Identidade" + "linkWithIDP": "ou vincule com um Provedor de Identidade", + "methods": { + "totp": "Aplicativo autenticador", + "u2f": "Segundo fator universal", + "otpEmail": "Código via e-mail", + "otpSms": "Código via SMS", + "passkey": "Chave de Acesso", + "password": "Senha" + } }, "device": { "usercode": { diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json index 7cf13a994..ea46f778e 100644 --- a/apps/login/locales/ru.json +++ b/apps/login/locales/ru.json @@ -152,7 +152,8 @@ "failedPrecondition": "Нарушено предварительное условие", "sessionNotValid": "Сессия недействительна", "passwordVerificationMissing": "Отсутствует проверка пароля.", - "passwordVerificationTooOld": "Проверка пароля устарела. Пожалуйста, подтвердите пароль еще раз." + "passwordVerificationTooOld": "Проверка пароля устарела. Пожалуйста, подтвердите пароль еще раз.", + "passwordResetNotAllowed": "Сброс пароля запрещен. Обратитесь к администратору за дополнительной информацией." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Это поле обязательно для заполнения" } + }, + "errors": { + "couldNotRequestChallenge": "Не удалось запросить OTP-челлендж", + "couldNotVerifyCode": "Не удалось проверить OTP-код" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Выберите предпочитаемый метод аутентификации", "noMethodsAvailable": "Нет доступных методов аутентификации", "allSetup": "Аутентификатор уже настроен!", - "linkWithIDP": "или привязать через Identity Provider" + "linkWithIDP": "или привязать через Identity Provider", + "methods": { + "totp": "Приложение-аутентификатор", + "u2f": "Универсальный второй фактор", + "otpEmail": "Код по email", + "otpSms": "Код по SMS", + "passkey": "Пасскей", + "password": "Пароль" + } }, "device": { "usercode": { diff --git a/apps/login/locales/tr.json b/apps/login/locales/tr.json index 3627841de..aa43acb75 100644 --- a/apps/login/locales/tr.json +++ b/apps/login/locales/tr.json @@ -152,7 +152,8 @@ "failedPrecondition": "Ön koşul başarısız", "sessionNotValid": "Oturum geçerli değil", "passwordVerificationMissing": "Şifre doğrulaması eksik.", - "passwordVerificationTooOld": "Şifre doğrulaması çok eski. Lütfen şifrenizi tekrar doğrulayın." + "passwordVerificationTooOld": "Şifre doğrulaması çok eski. Lütfen şifrenizi tekrar doğrulayın.", + "passwordResetNotAllowed": "Şifre sıfırlamaya izin verilmiyor. Daha fazla bilgi için yöneticinizle iletişime geçin." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Bu alan zorunludur" } + }, + "errors": { + "couldNotRequestChallenge": "OTP meydan okuması istenemedi", + "couldNotVerifyCode": "OTP kodu doğrulanamadı" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Kimlik doğrulaması yapmak istediğiniz yöntemi seçin", "noMethodsAvailable": "Kullanılabilir kimlik doğrulama yöntemi yok", "allSetup": "Zaten bir kimlik doğrulayıcı kurdunuz!", - "linkWithIDP": "veya bir Kimlik Sağlayıcı ile bağlantı kurun" + "linkWithIDP": "veya bir Kimlik Sağlayıcı ile bağlantı kurun", + "methods": { + "totp": "Kimlik doğrulayıcı uygulaması", + "u2f": "Evrensel ikinci faktör", + "otpEmail": "E-posta ile kod", + "otpSms": "SMS ile kod", + "passkey": "Passkey", + "password": "Şifre" + } }, "device": { "usercode": { diff --git a/apps/login/locales/uk.json b/apps/login/locales/uk.json index 0c39e35b5..ca6201479 100644 --- a/apps/login/locales/uk.json +++ b/apps/login/locales/uk.json @@ -152,7 +152,8 @@ "failedPrecondition": "Не виконано передумову", "sessionNotValid": "Сеанс недійсний", "passwordVerificationMissing": "Відсутня перевірка пароля.", - "passwordVerificationTooOld": "Перевірка пароля застаріла. Будь ласка, перевірте пароль ще раз." + "passwordVerificationTooOld": "Перевірка пароля застаріла. Будь ласка, перевірте пароль ще раз.", + "passwordResetNotAllowed": "Скидання пароля заборонено. Зверніться до адміністратора за додатковою інформацією." } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "Це поле обов'язкове" } + }, + "errors": { + "couldNotRequestChallenge": "Не вдалося запросити OTP-виклик", + "couldNotVerifyCode": "Не вдалося перевірити OTP-код" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "Виберіть метод автентифікації", "noMethodsAvailable": "Немає доступних методів автентифікації", "allSetup": "Ви вже налаштували автентифікатор!", - "linkWithIDP": "або прив'яжіть до постачальника ідентичності" + "linkWithIDP": "або прив'яжіть до постачальника ідентичності", + "methods": { + "totp": "Додаток автентифікації", + "u2f": "Універсальний другий фактор", + "otpEmail": "Код через електронну пошту", + "otpSms": "Код через SMS", + "passkey": "Ключ доступу", + "password": "Пароль" + } }, "device": { "usercode": { diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index 03b674631..17f4fd3fd 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -152,7 +152,8 @@ "failedPrecondition": "前置条件失败", "sessionNotValid": "会话无效", "passwordVerificationMissing": "缺少密码验证。", - "passwordVerificationTooOld": "密码验证已过期。请重新通过验证。" + "passwordVerificationTooOld": "密码验证已过期。请重新通过验证。", + "passwordResetNotAllowed": "不允许重置密码。请联系您的管理员以获取更多信息。" } }, "idp": { @@ -261,6 +262,10 @@ "required": { "code": "此字段为必填项" } + }, + "errors": { + "couldNotRequestChallenge": "无法请求 OTP 质询", + "couldNotVerifyCode": "无法验证 OTP 验证码" } }, "passkey": { @@ -422,7 +427,15 @@ "description": "选择您想使用的认证方法", "noMethodsAvailable": "没有可用的认证方法", "allSetup": "您已经设置好了一个认证器!", - "linkWithIDP": "或将其与身份提供者关联" + "linkWithIDP": "或将其与身份提供者关联", + "methods": { + "totp": "认证器应用", + "u2f": "通用第二因素", + "otpEmail": "通过电子邮件接收验证码", + "otpSms": "通过短信接收验证码", + "passkey": "密钥", + "password": "密码" + } }, "device": { "usercode": { diff --git a/apps/login/src/components/auth-methods.test.tsx b/apps/login/src/components/auth-methods.test.tsx new file mode 100644 index 000000000..461ce4ff9 --- /dev/null +++ b/apps/login/src/components/auth-methods.test.tsx @@ -0,0 +1,59 @@ +import { cleanup, render, screen } from "@testing-library/react"; +import type { AnchorHTMLAttributes } from "react"; +import { afterEach, describe, expect, test, vi } from "vitest"; +import { EMAIL, PASSKEYS, PASSWORD, SMS, TOTP, U2F } from "./auth-methods"; + +vi.mock("next-intl", () => ({ + useTranslations: (namespace?: string) => (key: string) => `${namespace ? `${namespace}.` : ""}${key}`, +})); + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: AnchorHTMLAttributes & { href: string }) => ( + + {children} + + ), +})); + +describe("auth-methods", () => { + afterEach(cleanup); + + test.each([ + { + name: "TOTP", + card: TOTP, + translatedText: "authenticator.methods.totp", + }, + { + name: "U2F", + card: U2F, + translatedText: "authenticator.methods.u2f", + }, + { + name: "EMAIL", + card: EMAIL, + translatedText: "authenticator.methods.otpEmail", + }, + { + name: "SMS", + card: SMS, + translatedText: "authenticator.methods.otpSms", + }, + { + name: "PASSKEYS", + card: PASSKEYS, + translatedText: "authenticator.methods.passkey", + }, + { + name: "PASSWORD", + card: PASSWORD, + translatedText: "authenticator.methods.password", + }, + ])("renders $name label from translation key", ({ card, translatedText }) => { + render(card(false, "/auth/method")); + + const translatedLabel = screen.getByText(translatedText); + expect(translatedLabel).toBeInTheDocument(); + expect(translatedLabel).toHaveAttribute("data-i18n-key", translatedText); + }); +}); diff --git a/apps/login/src/components/auth-methods.tsx b/apps/login/src/components/auth-methods.tsx index a536077af..9fd36ef10 100644 --- a/apps/login/src/components/auth-methods.tsx +++ b/apps/login/src/components/auth-methods.tsx @@ -3,6 +3,7 @@ import { clsx } from "clsx"; import Link from "next/link"; import { ReactNode } from "react"; import { BadgeState, StateBadge } from "./state-badge"; +import { Translated } from "./translated"; const cardClasses = (alreadyAdded: boolean) => clsx( @@ -32,7 +33,7 @@ export const TOTP = (alreadyAdded: boolean, link: string) => { timer-lock-outline {" "} - Authenticator App + {alreadyAdded && ( <> @@ -61,7 +62,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => { d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33" /> - Universal Second Factor + {alreadyAdded && ( <> @@ -91,7 +92,7 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => { /> - Code via Email + {alreadyAdded && ( <> @@ -120,7 +121,7 @@ export const SMS = (alreadyAdded: boolean, link: string) => { d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" /> - Code via SMS + {alreadyAdded && ( <> @@ -149,7 +150,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => { d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33" /> - Passkeys + {alreadyAdded && ( <> @@ -168,7 +169,7 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => { form-textbox-password - Password + {alreadyAdded && ( <> diff --git a/apps/login/src/components/login-otp.test.tsx b/apps/login/src/components/login-otp.test.tsx index 6d43ab233..ddc9da020 100644 --- a/apps/login/src/components/login-otp.test.tsx +++ b/apps/login/src/components/login-otp.test.tsx @@ -1,5 +1,5 @@ -import { cleanup, render } from "@testing-library/react"; -import { afterEach, describe, expect, test, vi } from "vitest"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { LoginOTP } from "./login-otp"; vi.mock("next/navigation", () => ({ @@ -20,10 +20,50 @@ vi.mock("@/lib/client", () => ({ })); describe("LoginOTP", () => { + let mockUpdateOrCreateSession: ReturnType; + + beforeEach(async () => { + vi.clearAllMocks(); + const { updateOrCreateSession } = await import("@/lib/server/session"); + mockUpdateOrCreateSession = vi.mocked(updateOrCreateSession); + }); + afterEach(cleanup); test("should autofocus the code input on mount", () => { const { getByTestId } = render(); expect(getByTestId("code-text-input")).toHaveFocus(); }); + + test("should display translated fallback error when OTP verification request rejects", async () => { + mockUpdateOrCreateSession.mockRejectedValueOnce(new Error("network down")); + + render(); + + const input = screen.getByTestId("code-text-input"); + fireEvent.change(input, { target: { value: "123456" } }); + + const submitButton = screen.getByTestId("submit-button"); + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); + + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.getByText("errors.couldNotVerifyCode")).toBeInTheDocument(); + }); + }); + + test("should display translated fallback error when OTP challenge request rejects", async () => { + mockUpdateOrCreateSession.mockRejectedValueOnce(new Error("challenge request failed")); + + render(); + + expect(screen.getByRole("button", { name: "verify.resendCode" })).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText("errors.couldNotRequestChallenge")).toBeInTheDocument(); + }); + }); }); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index c6cb50036..0ec8a3ee1 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -94,7 +94,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization, requestId, }); } catch { - return { error: "Could not request OTP challenge" }; + return { error: t("errors.couldNotRequestChallenge") }; } if (response && "error" in response && response.error) { @@ -102,7 +102,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization, } return response; - }, [method, host, requestId, loginName, sessionId, organization]); + }, [method, host, requestId, loginName, sessionId, organization, t]); useEffect(() => { if (!initialized.current && ["email", "sms"].includes(method) && !code) { @@ -162,7 +162,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization, requestId, }) .catch(() => { - setError("Could not verify OTP code"); + setError(t("errors.couldNotVerifyCode")); return; }) .finally(() => { @@ -220,7 +220,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,