From f785fce905b1cee8ad5ec92f99ca3f5a2c87955f Mon Sep 17 00:00:00 2001 From: brunol95 Date: Fri, 23 Oct 2026 15:02:25 -0500 Subject: [PATCH 1/4] feat(shared,clerk-js,ui): let users with a breached password reverify by resetting it When reverification rejected a password as found in a breach or compromised, the modal only showed the error. Users whose password was the only way to verify couldn't make any protected change, including changing that password. The modal now offers the reset sign-in already has: a "Password compromised" screen with "Reset your password" and the other methods, then a new password, then a code sent to the user's email or phone. The new password replaces the breached one when the reverification completes, after a second factor if one is required. From UserProfile's change password form, the modal reuses the password the user already entered, so they only enter the code, and the form skips its automatic retry so the password changes once. session.prepareFirstFactorVerification() and attemptFirstFactorVerification() accept reset_password_email_code and reset_password_phone_code, so custom flows can build the same reset. --- .../reverification-reset-breached-password.md | 12 + .../clerk-js/src/core/resources/Session.ts | 6 + .../core/resources/__tests__/Session.test.ts | 76 ++++++ packages/localizations/src/ar-SA.ts | 25 ++ packages/localizations/src/be-BY.ts | 25 ++ packages/localizations/src/bg-BG.ts | 25 ++ packages/localizations/src/bn-IN.ts | 25 ++ packages/localizations/src/ca-ES.ts | 25 ++ packages/localizations/src/cs-CZ.ts | 25 ++ packages/localizations/src/da-DK.ts | 25 ++ packages/localizations/src/de-DE.ts | 25 ++ packages/localizations/src/el-GR.ts | 25 ++ packages/localizations/src/en-GB.ts | 25 ++ packages/localizations/src/en-US.ts | 27 ++ packages/localizations/src/es-CR.ts | 25 ++ packages/localizations/src/es-ES.ts | 25 ++ packages/localizations/src/es-MX.ts | 25 ++ packages/localizations/src/es-UY.ts | 25 ++ packages/localizations/src/fa-IR.ts | 25 ++ packages/localizations/src/fi-FI.ts | 25 ++ packages/localizations/src/fr-FR.ts | 25 ++ packages/localizations/src/he-IL.ts | 25 ++ packages/localizations/src/hi-IN.ts | 25 ++ packages/localizations/src/hr-HR.ts | 25 ++ packages/localizations/src/hu-HU.ts | 25 ++ packages/localizations/src/id-ID.ts | 25 ++ packages/localizations/src/is-IS.ts | 25 ++ packages/localizations/src/it-IT.ts | 25 ++ packages/localizations/src/ja-JP.ts | 25 ++ packages/localizations/src/kk-KZ.ts | 25 ++ packages/localizations/src/ko-KR.ts | 25 ++ packages/localizations/src/mn-MN.ts | 25 ++ packages/localizations/src/ms-MY.ts | 25 ++ packages/localizations/src/nb-NO.ts | 25 ++ packages/localizations/src/nl-BE.ts | 25 ++ packages/localizations/src/nl-NL.ts | 25 ++ packages/localizations/src/pl-PL.ts | 25 ++ packages/localizations/src/pt-BR.ts | 25 ++ packages/localizations/src/pt-PT.ts | 25 ++ packages/localizations/src/ro-RO.ts | 25 ++ packages/localizations/src/ru-RU.ts | 25 ++ packages/localizations/src/sk-SK.ts | 25 ++ packages/localizations/src/sr-RS.ts | 25 ++ packages/localizations/src/sv-SE.ts | 25 ++ packages/localizations/src/ta-IN.ts | 25 ++ packages/localizations/src/te-IN.ts | 25 ++ packages/localizations/src/th-TH.ts | 25 ++ packages/localizations/src/tr-TR.ts | 25 ++ packages/localizations/src/uk-UA.ts | 25 ++ packages/localizations/src/vi-VN.ts | 25 ++ packages/localizations/src/zh-CN.ts | 25 ++ packages/localizations/src/zh-TW.ts | 25 ++ packages/shared/src/types/clerk.ts | 16 +- packages/shared/src/types/localization.ts | 25 ++ packages/shared/src/types/session.ts | 29 +++ .../shared/src/types/sessionVerification.ts | 4 + packages/ui/src/Components.tsx | 5 +- .../components/UserProfile/PasswordForm.tsx | 35 ++- .../__tests__/PasswordSection.test.tsx | 70 +++++- .../UserVerification/AlternativeMethods.tsx | 7 +- .../UserVerification/UVFactorOneCodeForm.tsx | 36 ++- .../UVFactorOneResetPasswordCodeCard.tsx | 67 +++++ .../UVPasswordPwnedMethods.tsx | 150 +++++++++++ .../UserVerification/UVResetPasswordCard.tsx | 154 ++++++++++++ .../UserVerificationFactorOne.tsx | 119 ++++++++- .../UserVerificationFactorOnePassword.tsx | 25 +- .../__tests__/UVFactorOne.test.tsx | 236 +++++++++++++++++- .../use-after-verification.ts | 5 +- 68 files changed, 2282 insertions(+), 22 deletions(-) create mode 100644 .changeset/reverification-reset-breached-password.md create mode 100644 packages/ui/src/components/UserVerification/UVFactorOneResetPasswordCodeCard.tsx create mode 100644 packages/ui/src/components/UserVerification/UVPasswordPwnedMethods.tsx create mode 100644 packages/ui/src/components/UserVerification/UVResetPasswordCard.tsx diff --git a/.changeset/reverification-reset-breached-password.md b/.changeset/reverification-reset-breached-password.md new file mode 100644 index 00000000000..a4c04250e0e --- /dev/null +++ b/.changeset/reverification-reset-breached-password.md @@ -0,0 +1,12 @@ +--- +'@clerk/shared': minor +'@clerk/clerk-js': minor +'@clerk/ui': minor +'@clerk/localizations': minor +--- + +Let users whose password was found in a data breach pass reverification by resetting it. Until now the reverification modal only showed the breached-password error, so these users couldn't make any change that needs reverification, including changing that password. The modal now offers "Reset your password": the user sets a new password and enters a code sent to their email or phone, as in sign-in. Their other verification methods stay available. + +From UserProfile's change password form, the modal reuses the new password the user already entered, so they only enter the code. + +`session.prepareFirstFactorVerification()` and `session.attemptFirstFactorVerification()` accept the `reset_password_email_code` and `reset_password_phone_code` strategies, so custom flows can build the same reset. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 0ac226923f8..59f9c90b4d5 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -269,6 +269,12 @@ export class Session extends BaseResource implements SessionResource { default: factor.default, } as PhoneCodeConfig; break; + case 'reset_password_email_code': + config = { emailAddressId: factor.emailAddressId }; + break; + case 'reset_password_phone_code': + config = { phoneNumberId: factor.phoneNumberId }; + break; case 'passkey': config = {}; break; diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index 551ed54c2cc..a08e13ac9b5 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -1210,6 +1210,82 @@ describe('Session', () => { }); }); + describe('reset password reverification', () => { + const sessionJSON = { + status: 'active', + id: 'session_1', + object: 'session', + user: createUser({}), + last_active_organization_id: null, + actor: null, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + } as SessionJSON; + + const verificationResponse = { + object: 'session_verification', + id: 'sessver_1', + status: 'needs_first_factor', + level: 'first_factor', + session: sessionJSON, + first_factor_verification: null, + second_factor_verification: null, + supported_first_factors: null, + supported_second_factors: null, + }; + + let requestSpy: Mock; + + beforeEach(() => { + BaseResource.clerk = clerkMock(); + requestSpy = BaseResource.clerk.getFapiClient().request as Mock; + requestSpy.mockResolvedValue({ payload: { response: verificationResponse }, status: 200 }); + }); + + afterEach(() => { + BaseResource.clerk = null as any; + }); + + it.each([ + [{ strategy: 'reset_password_email_code', emailAddressId: 'idn_1' }], + [{ strategy: 'reset_password_phone_code', phoneNumberId: 'idn_2' }], + ] as const)('prepares a first factor with %o', async factor => { + const session = new Session(sessionJSON); + + await session.prepareFirstFactorVerification(factor); + + expect(requestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + body: factor, + method: 'POST', + path: '/client/sessions/session_1/verify/prepare_first_factor', + }), + expect.anything(), + ); + }); + + it('attempts a first factor with the code and the new password', async () => { + const session = new Session(sessionJSON); + const attempt = { + strategy: 'reset_password_email_code', + code: '123456', + password: 'a-much-better-password-123', + signOutOfOtherSessions: true, + } as const; + + await session.attemptFirstFactorVerification(attempt); + + expect(requestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + body: attempt, + method: 'POST', + path: '/client/sessions/session_1/verify/attempt_first_factor', + }), + expect.anything(), + ); + }); + }); + describe('trusted device reverification', () => { const sessionJSON = { status: 'active', diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index c707911a13b..cc6b1774509 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -1635,6 +1635,16 @@ export const arSA: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1647,6 +1657,21 @@ export const arSA: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index db7b9ca5650..eddb95aec40 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -1642,6 +1642,16 @@ export const beBY: LocalizationResource = { subtitle: 'Калі вы памятаеце свой пароль, вы можаце ўвесці яго для завершэння верыфікацыі.', title: 'Падцвердзіць праз пароль', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Увядзіце код, які быў адпраўлены на ваш тэлефон.', resendButton: 'Пераадправіць код на тэлефон', @@ -1654,6 +1664,21 @@ export const beBY: LocalizationResource = { subtitle: 'Праверце смс-паведамленне для атрымання кода MFA.', title: 'Код MFA з тэлефона', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Увядзіце код TOTP з вашага прыкладання.', subtitle: 'Вы можаце выкарыстоўваць прыкладанне для атрымання аднаразовага кода.', diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index fc88d5a762e..2687e9ba946 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -1639,6 +1639,16 @@ export const bgBG: LocalizationResource = { subtitle: 'Enter your current password to continue using "{{applicationName}}"', title: 'Enter your password', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Enter the verification code sent to your phone number.', resendButton: "Didn't receive the code? Resend it.", @@ -1651,6 +1661,21 @@ export const bgBG: LocalizationResource = { subtitle: 'Verify using the code sent to your phone.', title: 'Phone number MFA', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Enter the verification code from your authenticator app.', subtitle: 'Use your authenticator app to get the code.', diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 7385f976b99..452d953a57d 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -1647,6 +1647,16 @@ export const bnIN: LocalizationResource = { subtitle: 'চালিয়ে যেতে আপনার বর্তমান পাসওয়ার্ড লিখুন', title: 'যাচাইকরণ প্রয়োজন', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'যাচাইকরণ কোড', resendButton: 'কোনো কোড পাননি? পুনরায় পাঠান', @@ -1659,6 +1669,21 @@ export const bnIN: LocalizationResource = { subtitle: 'চালিয়ে যেতে আপনার ফোনে পাঠানো কোড লিখুন', title: 'যাচাইকরণ প্রয়োজন', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'যাচাইকরণ কোড', subtitle: 'চালিয়ে যেতে আপনার অথেনটিকেটর অ্যাপ দ্বারা উৎপন্ন কোড লিখুন', diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index 9225a3c42c5..87bfb64d990 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -1647,6 +1647,16 @@ export const caES: LocalizationResource = { subtitle: 'Utilitza la teva contrasenya per verificar la teva identitat.', title: 'Verificació per contrasenya', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Introdueix el codi enviat al teu telèfon.', resendButton: 'Reenvia codi', @@ -1659,6 +1669,21 @@ export const caES: LocalizationResource = { subtitle: 'Introdueix el codi de verificació de dos factors enviat al teu telèfon.', title: 'Verificació per telèfon (2FA)', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Codi TOTP', subtitle: "Introdueix el codi d'autenticació TOTP per completar la verificació.", diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index cc16a2a3d70..4e09917024a 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -1645,6 +1645,16 @@ export const csCZ: LocalizationResource = { subtitle: 'Zadejte své aktuální heslo pro pokračování', title: 'Vyžadováno ověření', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Ověřovací kód', resendButton: 'Neobdrželi jste kód? Znovu poslat', @@ -1657,6 +1667,21 @@ export const csCZ: LocalizationResource = { subtitle: 'Zadejte kód odeslaný na váš telefon pro pokračování', title: 'Vyžadováno ověření', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Ověřovací kód', subtitle: 'Zadejte kód vygenerovaný vaší aplikací pro ověřování pro pokračování', diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 362723d2869..90f7dd6ece9 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -1637,6 +1637,16 @@ export const daDK: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1649,6 +1659,21 @@ export const daDK: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 52ecf5ecb8b..b373bd67cb1 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -1654,6 +1654,16 @@ export const deDE: LocalizationResource = { subtitle: 'Geben Sie Ihr Passwort ein, um fortzufahren.', title: 'Passwort-Verifizierung', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Geben Sie den Code ein, den wir an Ihre Telefonnummer gesendet haben.', resendButton: 'Code erneut senden', @@ -1666,6 +1676,21 @@ export const deDE: LocalizationResource = { subtitle: 'Überprüfen Sie Ihre SMS auf den Verifizierungscode.', title: 'SMS-Code (MFA) Verifizierung', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Geben Sie den Code aus Ihrer Authentifikator-App ein.', subtitle: 'Verwenden Sie die Authentifikator-App, die Sie eingerichtet haben.', diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 5013b35ca5b..5a3a63e6de4 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -1645,6 +1645,16 @@ export const elGR: LocalizationResource = { subtitle: 'για να συνεχίσετε', title: 'Εισάγετε τον κωδικό πρόσβασής σας', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Κωδικός επαλήθευσης', resendButton: 'Δεν λάβατε κωδικό; Επαναποστολή', @@ -1657,6 +1667,21 @@ export const elGR: LocalizationResource = { subtitle: 'για να συνεχίσετε', title: 'Ελέγξτε το τηλέφωνό σας', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Κωδικός επαλήθευσης', subtitle: 'για να συνεχίσετε', diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index a038681c897..29d72c596cf 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -1638,6 +1638,16 @@ export const enGB: LocalizationResource = { subtitle: 'Enter your current password to continue', title: 'Verification required', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verification code', resendButton: "Didn't receive a code? Resend", @@ -1650,6 +1660,21 @@ export const enGB: LocalizationResource = { subtitle: 'Enter the code sent to your phone to continue', title: 'Verification required', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verification code', subtitle: 'Enter the code generated by your authenticator app to continue', diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index b307ffe16d6..251f0bfc4b6 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -1686,6 +1686,18 @@ export const enUS: LocalizationResource = { subtitle: 'Enter your current password to continue', title: 'Verification required', }, + passwordCompromised: { + subtitle__saveNewPassword: + "Your current password can't be used anymore. Confirm it's you to save your new password.", + title: 'Password compromised', + }, + passwordPwned: { + blockButton__resetPassword: 'Reset your password', + label__alternativeMethods: 'Or, verify with another method', + subtitle__saveNewPassword: + "Your current password was found in a data breach. Confirm it's you to save your new password.", + title: 'Password compromised', + }, phoneCode: { formTitle: 'Verification code', resendButton: "Didn't receive a code? Resend", @@ -1698,6 +1710,21 @@ export const enUS: LocalizationResource = { subtitle: 'Enter the code sent to your phone to continue', title: 'Verification required', }, + resetPassword: { + formButtonPrimary: 'Continue', + title: 'Set new password', + }, + resetPasswordCode: { + formTitle: 'Reset password code', + resendButton: "Didn't receive a code? Resend", + subtitle__saveNewPassword_email: 'Enter the code sent to your email address to save your new password', + subtitle__saveNewPassword_phone: 'Enter the code sent to your phone to save your new password', + subtitle_email: 'Enter the code sent to your email address to set your new password', + subtitle_phone: 'Enter the code sent to your phone to set your new password', + title: 'Reset password', + title__saveNewPassword_email: 'Check your email', + title__saveNewPassword_phone: 'Check your phone', + }, totpMfa: { formTitle: 'Verification code', subtitle: 'Enter the code generated by your authenticator app to continue', diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index ed06e9d92f1..dc0b282ad48 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -1643,6 +1643,16 @@ export const esCR: LocalizationResource = { subtitle: 'Ingresa tu contraseña actual para continuar', title: 'Verificación requerida', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Código de verificación', resendButton: '¿No recibiste un código? Reenviar', @@ -1655,6 +1665,21 @@ export const esCR: LocalizationResource = { subtitle: 'Ingresa el código enviado a tu teléfono para continuar', title: 'Verificación requerida', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código de verificación', subtitle: 'Ingresa el código generado por la aplicación de autenticación para continuar', diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index cff65d2b6a5..f62836601fd 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -1648,6 +1648,16 @@ export const esES: LocalizationResource = { subtitle: 'Usa tu contraseña para verificar tu identidad.', title: 'Verificación por contraseña', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Introduce el código enviado a tu teléfono.', resendButton: 'Reenviar código', @@ -1660,6 +1670,21 @@ export const esES: LocalizationResource = { subtitle: 'Introduce el código de verificación de dos factores enviado a tu teléfono.', title: 'Verificación por teléfono (2FA)', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código TOTP', subtitle: 'Introduce el código de autenticación TOTP para completar la verificación.', diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index 2e6d7e4256d..f227a1ed75c 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -1644,6 +1644,16 @@ export const esMX: LocalizationResource = { subtitle: 'Ingresa tu contraseña actual para continuar', title: 'Verificación requerida', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Código de verificación', resendButton: '¿No recibiste un código? Reenviar', @@ -1656,6 +1666,21 @@ export const esMX: LocalizationResource = { subtitle: 'Ingresa el código enviado a tu teléfono para continuar', title: 'Verificación requerida', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código de verificación', subtitle: 'Ingresa el código generado por la aplicación de autenticación para continuar', diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 395d82d04e4..335f1b45250 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -1642,6 +1642,16 @@ export const esUY: LocalizationResource = { subtitle: 'Ingresá tu contraseña para continuar', title: 'Verificación requerida', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Código de verificación', resendButton: '¿No recibiste un código? Reenviar', @@ -1654,6 +1664,21 @@ export const esUY: LocalizationResource = { subtitle: 'Ingresá el código enviado a tu teléfono para continuar', title: 'Verificación requerida', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código de verificación', subtitle: 'Ingresá el código generado por tu aplicación autenticadora para continuar', diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 38ce6f62bba..d8c8d303e07 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -1647,6 +1647,16 @@ export const faIR: LocalizationResource = { subtitle: 'برای ادامه، رمز عبور فعلی خود را وارد کنید', title: 'تایید هویت الزامی است', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'کد تأیید', resendButton: 'کدی دریافت نکردید؟ ارسال دوباره', @@ -1659,6 +1669,21 @@ export const faIR: LocalizationResource = { subtitle: 'برای ادامه، کد ارسال شده به تلفن خود را وارد کنید', title: 'تایید هویت الزامی است', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'کد تأیید', subtitle: 'برای ادامه، کد تولید شده توسط برنامه تأیید هویت خود را وارد کنید', diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 5e68744f92e..42b5c0cbcbe 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -1649,6 +1649,16 @@ export const fiFI: LocalizationResource = { subtitle: 'Syötä nykyinen salasanasi jatkaaksesi', title: 'Vahvistus vaaditaan', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Vahvistuskoodi', resendButton: 'Etkö saanut koodia? Lähetä uudelleen', @@ -1661,6 +1671,21 @@ export const fiFI: LocalizationResource = { subtitle: 'Syötä puhelimeesi lähetetty koodi jatkaaksesi', title: 'Vahvistus vaaditaan', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Vahvistuskoodi', subtitle: 'Syötä todennussovelluksesi luoma koodi jatkaaksesi', diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index b10aa5e3a60..f42434662f5 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -1655,6 +1655,16 @@ export const frFR: LocalizationResource = { subtitle: 'Entrez votre mot de passe pour continuer.', title: 'Vérification par mot de passe', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Entrez le code de vérification', resendButton: 'Renvoyer le code', @@ -1667,6 +1677,21 @@ export const frFR: LocalizationResource = { subtitle: 'Un code a été envoyé à votre téléphone pour vérification.', title: 'Vérification par téléphone', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Entrez le code de vérification', subtitle: "Entrez le code généré par votre application d'authentification.", diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 76d4e71749c..b02bf8cdf81 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -1632,6 +1632,16 @@ export const heIL: LocalizationResource = { subtitle: 'הכנס את הסיסמה המקושרת עם חשבונך', title: 'הכנס סיסמה', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'קוד אימות', resendButton: 'לא קיבלת קוד? שלח שוב', @@ -1644,6 +1654,21 @@ export const heIL: LocalizationResource = { subtitle: 'להמשך, אנא הכנס את קוד האימות שנשלח לטלפון שלך', title: 'בדוק את הטלפון שלך', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'קוד אימות', subtitle: 'להמשך, אנא הכנס את קוד האימות שנוצר על ידי אפליקציית האימות שלך', diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index a15280fb922..22ea2e5e268 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -1647,6 +1647,16 @@ export const hiIN: LocalizationResource = { subtitle: 'जारी रखने के लिए अपना वर्तमान पासवर्ड दर्ज करें', title: 'सत्यापन आवश्यक है', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'सत्यापन कोड', resendButton: 'कोड नहीं मिला? फिर से भेजें', @@ -1659,6 +1669,21 @@ export const hiIN: LocalizationResource = { subtitle: 'जारी रखने के लिए अपने फोन पर भेजे गए कोड को दर्ज करें', title: 'सत्यापन आवश्यक है', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'सत्यापन कोड', subtitle: 'जारी रखने के लिए अपने प्रमाणकर्ता ऐप द्वारा जनरेट किए गए कोड को दर्ज करें', diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index 898e1668daf..2749cb2caa0 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -1648,6 +1648,16 @@ export const hrHR: LocalizationResource = { subtitle: 'Unesite svoju trenutnu lozinku za nastavak', title: 'Potrebna verifikacija', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verifikacijski kod', resendButton: 'Niste primili kod? Pošalji ponovno', @@ -1660,6 +1670,21 @@ export const hrHR: LocalizationResource = { subtitle: 'Unesite kod poslan na vaš telefon za nastavak', title: 'Potrebna verifikacija', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verifikacijski kod', subtitle: 'Unesite kod generiran vašom aplikacijom za autentifikaciju za nastavak', diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index ba75bd80414..2c312eb2fb9 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -1650,6 +1650,16 @@ export const huHU: LocalizationResource = { subtitle: 'Add meg a jelenlegi jelszavadat a folytatáshoz', title: 'Ellenőrzés szükséges', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Visszaigazoló kód', resendButton: 'Nem kaptad meg a kódot? Újraküldés', @@ -1662,6 +1672,21 @@ export const huHU: LocalizationResource = { subtitle: 'Írd be a telefonodra küldött kódot a folytatáshoz', title: 'Ellenőrzés szükséges', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Visszaigazoló kód', subtitle: 'Írd be a hitelesítő alkalmazás által generált kódot a folytatáshoz', diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 7b35077a8b8..2db87ff8bfb 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -1641,6 +1641,16 @@ export const idID: LocalizationResource = { subtitle: 'Masukkan kata sandi Anda untuk melanjutkan', title: 'Verifikasi diperlukan', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Kode verifikasi', resendButton: 'Tidak menerima kode? Kirim ulang', @@ -1653,6 +1663,21 @@ export const idID: LocalizationResource = { subtitle: 'Masukkan kode yang dikirim ke telepon Anda untuk melanjutkan', title: 'Verifikasi diperlukan', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Kode verifikasi', subtitle: 'Masukkan kode yang dihasilkan oleh aplikasi autentikator Anda untuk melanjutkan', diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index f015eed4f00..a6912e7340e 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -1649,6 +1649,16 @@ export const isIS: LocalizationResource = { subtitle: 'Sláðu inn núverandi lykilorð til að halda áfram', title: 'Staðfesting nauðsynleg', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Staðfestingarkóði', resendButton: 'Fékkstu ekki kóða? Senda aftur', @@ -1661,6 +1671,21 @@ export const isIS: LocalizationResource = { subtitle: 'Sláðu inn kóðann sem sendur var á símann þinn til að halda áfram', title: 'Staðfesting nauðsynleg', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Staðfestingarkóði', subtitle: 'Sláðu inn kóðann sem auðkennisforritið þitt bjó til til að halda áfram', diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 4044db1665a..e44bd7eee45 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -1647,6 +1647,16 @@ export const itIT: LocalizationResource = { subtitle: 'Inserisci la tua password per continuare.', title: 'Verifica con la password', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Inserisci il codice che ti è stato inviato via SMS.', resendButton: 'Invia di nuovo il codice', @@ -1659,6 +1669,21 @@ export const itIT: LocalizationResource = { subtitle: 'Controlla il tuo SMS per il codice di verifica.', title: 'Verifica SMS (MFA)', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Inserisci il codice dalla tua app di autenticazione.', subtitle: 'Usa l’app di autenticazione che hai configurato.', diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index e0298371964..7afbd09e4c8 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -1648,6 +1648,16 @@ export const jaJP: LocalizationResource = { subtitle: '続行するには現在のパスワードを入力してください', title: '確認が必要です', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: '検証コード', resendButton: 'コードが届いていませんか?再送信', @@ -1660,6 +1670,21 @@ export const jaJP: LocalizationResource = { subtitle: '続行するには、電話に送信されたコードを入力してください', title: '確認が必要です', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: '検証コード', subtitle: '続行するには、認証アプリで生成されたコードを入力してください', diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index 8a51cb18eca..8f286ce231d 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -1631,6 +1631,16 @@ export const kkKZ: LocalizationResource = { subtitle: 'Жалғастыру үшін құпия сөзді енгізіңіз', title: 'Растау қажет', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Растау коды', resendButton: 'Код алмадыңыз ба? Қайта жіберу', @@ -1643,6 +1653,21 @@ export const kkKZ: LocalizationResource = { subtitle: 'Жалғастыру үшін телефонға жіберілген кодты енгізіңіз', title: 'Растау қажет', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Растау коды', subtitle: 'Аутентификатор қолданбасындағы кодты енгізіңіз', diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index abec99a1dff..d9b6769888d 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -1636,6 +1636,16 @@ export const koKR: LocalizationResource = { subtitle: '계속하려면 현재 비밀번호를 입력하세요', title: '인증이 필요해요', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: '인증 코드', resendButton: '코드를 받지 못하셨나요? 다시 보내기', @@ -1648,6 +1658,21 @@ export const koKR: LocalizationResource = { subtitle: '휴대폰으로 전송된 코드를 입력해 주세요', title: '인증이 필요해요', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: '인증 코드', subtitle: '인증 앱에서 생성된 코드를 입력해 주세요', diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 64c73cef2a1..c8dbb1f57d5 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -1640,6 +1640,16 @@ export const mnMN: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1652,6 +1662,21 @@ export const mnMN: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 99c926f3155..fa8dc67366f 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -1651,6 +1651,16 @@ export const msMY: LocalizationResource = { subtitle: 'Masukkan kata laluan semasa anda untuk meneruskan', title: 'Pengesahan diperlukan', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Kod pengesahan', resendButton: 'Tidak menerima kod? Hantar semula', @@ -1663,6 +1673,21 @@ export const msMY: LocalizationResource = { subtitle: 'Masukkan kod yang dihantar ke telefon anda untuk meneruskan', title: 'Pengesahan diperlukan', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Kod pengesahan', subtitle: 'Masukkan kod yang dijana oleh aplikasi pengesah anda untuk meneruskan', diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 0ebbf9b3315..1bac6b0df3f 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -1650,6 +1650,16 @@ export const nbNO: LocalizationResource = { subtitle: 'Skriv inn ditt nåværende passord for å fortsette', title: 'Verifisering påkrevd', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verifiseringskode', resendButton: 'Ikke mottatt kode? Send på nytt', @@ -1662,6 +1672,21 @@ export const nbNO: LocalizationResource = { subtitle: 'Skriv inn koden sendt til telefonen din for å fortsette', title: 'Verifisering påkrevd', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verifiseringskode', subtitle: 'Skriv inn koden generert av autentiseringsappen din for å fortsette', diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 8ceaa13c70a..f2b77fa161a 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -1641,6 +1641,16 @@ export const nlBE: LocalizationResource = { subtitle: 'Voer het wachtwoord in dat bij je account hoort', title: 'Voer je wachtwoord in', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verificatiecode', resendButton: 'Niet ontvangen? Opnieuw verzenden', @@ -1653,6 +1663,21 @@ export const nlBE: LocalizationResource = { subtitle: 'om door te gaan naar {{applicationName}}', title: 'Controleer je telefoon', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verificatiecode', subtitle: 'om door te gaan naar {{applicationName}}', diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 762a4f68899..c08af76f163 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -1641,6 +1641,16 @@ export const nlNL: LocalizationResource = { subtitle: 'Voer het wachtwoord in dat bij je account hoort', title: 'Voer je wachtwoord in', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verificatiecode', resendButton: 'Niet ontvangen? Opnieuw verzenden', @@ -1653,6 +1663,21 @@ export const nlNL: LocalizationResource = { subtitle: 'om door te gaan naar {{applicationName}}', title: 'Controleer je telefoon', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verificatiecode', subtitle: 'om door te gaan naar {{applicationName}}', diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 19eed5d20d3..0d8cec63a37 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -1639,6 +1639,16 @@ export const plPL: LocalizationResource = { subtitle: 'Wprowadź hasło, aby kontynuować', title: 'Wymagana weryfikacja', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Kod weryfikacyjny', resendButton: 'Nie otrzymałeś kodu? Wyślij ponownie', @@ -1651,6 +1661,21 @@ export const plPL: LocalizationResource = { subtitle: 'Aby kontynuować, wprowadź kod weryfikacyjny wysłany na twój telefon', title: 'Wymagana weryfikacja', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Kod weryfikacyjny', subtitle: 'Aby kontynuować, wprowadź kod weryfikacyjny wygenerowany przez swoją aplikację uwierzytelniającą', diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 0b265682603..aa59a44fc41 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -1649,6 +1649,16 @@ export const ptBR: LocalizationResource = { subtitle: 'Digite sua senha para continuar.', title: 'Digite sua senha', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Código de verificação', resendButton: 'Reenviar código', @@ -1661,6 +1671,21 @@ export const ptBR: LocalizationResource = { subtitle: 'Verifique seu celular para o código de verificação.', title: 'Verifique seu celular', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código de verificação TOTP', subtitle: 'Digite o código de verificação gerado pelo seu aplicativo de autenticação.', diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 72e81ceabb0..4db4895db67 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -1650,6 +1650,16 @@ export const ptPT: LocalizationResource = { subtitle: 'Introduza a sua palavra-passe atual para continuar', title: 'Verificação necessária', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Código de verificação', resendButton: 'Não recebeu um código? Reenviar', @@ -1662,6 +1672,21 @@ export const ptPT: LocalizationResource = { subtitle: 'Introduza o código enviado para o seu telemóvel para continuar', title: 'Verificação necessária', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Código de verificação', subtitle: 'Introduza o código gerado pela sua aplicação autenticadora para continuar', diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index d8515f1b2ae..fa9c6d1e0f8 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -1650,6 +1650,16 @@ export const roRO: LocalizationResource = { subtitle: 'Introdu parola curentă pentru a continua', title: 'Verificare necesară', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Cod de verificare', resendButton: 'Nu ai primit un cod? Retrimite', @@ -1662,6 +1672,21 @@ export const roRO: LocalizationResource = { subtitle: 'Introdu codul trimis pe telefon pentru a continua', title: 'Verificare necesară', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Cod de verificare', subtitle: 'Introdu codul generat de aplicația ta de autentificare pentru a continua', diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 3af75fb03eb..4e71bb7cb84 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -1646,6 +1646,16 @@ export const ruRU: LocalizationResource = { subtitle: 'Введите пароль, связанный с вашей учетной записью', title: 'Введите ваш пароль', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Верификационный код', resendButton: 'Не получили код? Отправить повторно', @@ -1658,6 +1668,21 @@ export const ruRU: LocalizationResource = { subtitle: 'Для продолжения, введите верификационный код, отправленный на ваш телефон', title: 'Проверьте ваш телефон', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Верификационный код', subtitle: 'Чтобы продолжить, введите верификационный код, сгенерированный вашим приложением-аутентификатором', diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 1dcc2f679d6..2381c1a0c40 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -1640,6 +1640,16 @@ export const skSK: LocalizationResource = { subtitle: 'Pre pokračovanie vložte svoje aktuálne heslo', title: 'Vyžaduje sa overenie', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Overovací kód', resendButton: 'Nedostali ste kód? Znova odoslať', @@ -1652,6 +1662,21 @@ export const skSK: LocalizationResource = { subtitle: 'Pre pokračovanie vložte overovací kód odoslaný na vaše telefónne číslo', title: 'Vyžaduje sa overenie', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Overovací kód', subtitle: 'Vložte overovací kód z vašej autentifikačnej aplikácie', diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 77c45b2f883..870642c3dc5 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -1637,6 +1637,16 @@ export const srRS: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1649,6 +1659,21 @@ export const srRS: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index cec06c14be5..20192692c50 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -1639,6 +1639,16 @@ export const svSE: LocalizationResource = { subtitle: 'Ange lösenordet som är kopplat till ditt konto', title: 'Ange ditt lösenord', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Verifieringskod', resendButton: 'Fick du ingen kod? Skicka igen', @@ -1651,6 +1661,21 @@ export const svSE: LocalizationResource = { subtitle: 'För att fortsätta, vänligen ange verifieringskoden som skickats till din telefon', title: 'Kontrollera din telefon', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Verifieringskod', subtitle: 'För att fortsätta, vänligen ange verifieringskoden som genererats av din autentiseringsapp', diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 5dbb4287d9c..d6761a96a07 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -1653,6 +1653,16 @@ export const taIN: LocalizationResource = { subtitle: 'தொடர உங்கள் தற்போதைய கடவுச்சொல்லை உள்ளிடவும்', title: 'சரிபார்ப்பு தேவை', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'சரிபார்ப்புக் குறியீடு', resendButton: 'குறியீடு கிடைக்கவில்லையா? மீண்டும் அனுப்பு', @@ -1665,6 +1675,21 @@ export const taIN: LocalizationResource = { subtitle: 'தொடர உங்கள் தொலைபேசிக்கு அனுப்பப்பட்ட குறியீட்டை உள்ளிடவும்', title: 'சரிபார்ப்பு தேவை', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'சரிபார்ப்புக் குறியீடு', subtitle: 'தொடர உங்கள் அங்கீகாரி பயன்பாட்டால் உருவாக்கப்பட்ட குறியீட்டை உள்ளிடவும்', diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 341a072a8c1..8509df2a491 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -1650,6 +1650,16 @@ export const teIN: LocalizationResource = { subtitle: 'కొనసాగించడానికి మీ ప్రస్తుత పాస్‌వర్డ్‌ను నమోదు చేయండి', title: 'ధృవీకరణ అవసరం', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'ధృవీకరణ కోడ్', resendButton: 'కోడ్ అందలేదా? మళ్ళీ పంపండి', @@ -1662,6 +1672,21 @@ export const teIN: LocalizationResource = { subtitle: 'కొనసాగించడానికి మీ ఫోన్‌కి పంపిన కోడ్‌ను నమోదు చేయండి', title: 'ధృవీకరణ అవసరం', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'ధృవీకరణ కోడ్', subtitle: 'కొనసాగించడానికి మీ ప్రమాణీకరణ యాప్ ద్వారా రూపొందించిన కోడ్‌ను నమోదు చేయండి', diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 51dcfa5b0e9..4d55f2b443b 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -1639,6 +1639,16 @@ export const thTH: LocalizationResource = { subtitle: 'ใส่รหัสผ่านปัจจุบันของคุณเพื่อดำเนินการต่อ', title: 'ต้องการการยืนยัน', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'รหัสยืนยัน', resendButton: 'ไม่ได้รับรหัส? ส่งใหม่', @@ -1651,6 +1661,21 @@ export const thTH: LocalizationResource = { subtitle: 'ใส่รหัสที่ส่งไปยังโทรศัพท์ของคุณเพื่อดำเนินการต่อ', title: 'ต้องการการยืนยัน', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'รหัสยืนยัน', subtitle: 'ใส่รหัสที่สร้างโดยแอป Authenticator ของคุณเพื่อดำเนินการต่อ', diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index 50d01c57240..f27e2c04444 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -1638,6 +1638,16 @@ export const trTR: LocalizationResource = { subtitle: 'Şifrenizi girerek devam edebilirsiniz.', title: 'Şifre ile Giriş Yapın', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Telefon Kodu ile Doğrulama', resendButton: 'Telefon kodunu tekrar gönder', @@ -1650,6 +1660,21 @@ export const trTR: LocalizationResource = { subtitle: 'Telefonunuza gönderilen doğrulama kodunu girin.', title: 'Telefon Koduyla Doğrulama', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'TOTP ile MFA Doğrulaması', subtitle: 'Mobil uygulamanızdan TOTP kodunu girin.', diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 9125e2b878c..e167d90f3ee 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -1637,6 +1637,16 @@ export const ukUA: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1649,6 +1659,21 @@ export const ukUA: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 1b5a87abe44..32e310dfe7f 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -1647,6 +1647,16 @@ export const viVN: LocalizationResource = { subtitle: 'Nhập mật khẩu hiện tại để tiếp tục', title: 'Xác minh yêu cầu', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: 'Mã xác minh', resendButton: 'Không nhận được mã? Gửi lại', @@ -1659,6 +1669,21 @@ export const viVN: LocalizationResource = { subtitle: 'Nhập mã đã gửi đến điện thoại của bạn để tiếp tục', title: 'Xác minh yêu cầu', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: 'Mã xác minh', subtitle: 'Nhập mã đã được tạo bởi ứng dụng xác thực của bạn để tiếp tục', diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 3e65172457b..0ec86a03c90 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -1628,6 +1628,16 @@ export const zhCN: LocalizationResource = { subtitle: undefined, title: undefined, }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: undefined, resendButton: undefined, @@ -1640,6 +1650,21 @@ export const zhCN: LocalizationResource = { subtitle: undefined, title: undefined, }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: undefined, subtitle: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 3d905a96077..a77295dee01 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -1631,6 +1631,16 @@ export const zhTW: LocalizationResource = { subtitle: '請輸入您的密碼以繼續', title: '需要驗證您的身分', }, + passwordCompromised: { + subtitle__saveNewPassword: undefined, + title: undefined, + }, + passwordPwned: { + blockButton__resetPassword: undefined, + label__alternativeMethods: undefined, + subtitle__saveNewPassword: undefined, + title: undefined, + }, phoneCode: { formTitle: '驗證碼', resendButton: '沒收到代碼?重新傳送', @@ -1643,6 +1653,21 @@ export const zhTW: LocalizationResource = { subtitle: '請輸入已寄至您手機的驗證碼以繼續', title: '需要驗證您的身分', }, + resetPassword: { + formButtonPrimary: undefined, + title: undefined, + }, + resetPasswordCode: { + formTitle: undefined, + resendButton: undefined, + subtitle__saveNewPassword_email: undefined, + subtitle__saveNewPassword_phone: undefined, + subtitle_email: undefined, + subtitle_phone: undefined, + title: undefined, + title__saveNewPassword_email: undefined, + title__saveNewPassword_phone: undefined, + }, totpMfa: { formTitle: '驗證碼', subtitle: '請輸入您在設定兩步驟驗證時收到的驗證碼以繼續', diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 069bc460a14..4a4f72d628a 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -1972,7 +1972,7 @@ export type __internal_UserVerificationProps = RoutingOptions & { /** * Non-awaitable callback for when verification is completed successfully */ - afterVerification?: () => void; + afterVerification?: (result?: __internal_UserVerificationResult) => void; /** * Non-awaitable callback for when verification is cancelled, (i.e modal is closed) @@ -1987,12 +1987,26 @@ export type __internal_UserVerificationProps = RoutingOptions & { */ level?: SessionVerificationLevel; + /** + * A new password the caller already collected. When the user resets their + * password to verify, it's used instead of asking for one. + */ + resetPassword?: { + password: string; + signOutOfOtherSessions: boolean; + }; + /** * Customization options to fully match the Clerk components to your own brand. These options serve as overrides and will be merged with the global `appearance` configuration (if one is provided). See the [`Appearance`](https://clerk.com/docs/guides/customizing-clerk/appearance-prop/overview) docs for more information. */ appearance?: ClerkAppearanceTheme; }; +export type __internal_UserVerificationResult = { + /** True when the user reset their password to verify. */ + passwordReset?: boolean; +}; + export type __internal_UserVerificationModalProps = WithoutRouting<__internal_UserVerificationProps>; export type ProtectCheckFlow = 'signIn' | 'signUp'; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 446d328a8e5..294753e8e05 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -713,6 +713,31 @@ export type __internal_LocalizationResource = { subtitle: LocalizationValue; message: LocalizationValue; }; + passwordPwned: { + title: LocalizationValue; + subtitle__saveNewPassword: LocalizationValue; + blockButton__resetPassword: LocalizationValue; + label__alternativeMethods: LocalizationValue; + }; + passwordCompromised: { + title: LocalizationValue; + subtitle__saveNewPassword: LocalizationValue; + }; + resetPassword: { + title: LocalizationValue; + formButtonPrimary: LocalizationValue; + }; + resetPasswordCode: { + title: LocalizationValue; + title__saveNewPassword_email: LocalizationValue; + title__saveNewPassword_phone: LocalizationValue; + subtitle_email: LocalizationValue; + subtitle_phone: LocalizationValue; + subtitle__saveNewPassword_email: LocalizationValue; + subtitle__saveNewPassword_phone: LocalizationValue; + formTitle: LocalizationValue; + resendButton: LocalizationValue; + }; }; userProfile: { mobileButton__menu: LocalizationValue; diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index 7c9bf8cde50..5095a2e8fae 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -10,6 +10,8 @@ import type { PhoneCodeAttempt, PhoneCodeConfig, PhoneCodeSecondFactorConfig, + ResetPasswordEmailCodeFactorConfig, + ResetPasswordPhoneCodeFactorConfig, TOTPAttempt, TrustedDeviceAttempt, TrustedDeviceConfig, @@ -28,6 +30,7 @@ import type { SessionVerificationResource, } from './sessionVerification'; import type { SessionJSONSnapshot } from './snapshots'; +import type { ResetPasswordEmailCodeStrategy, ResetPasswordPhoneCodeStrategy } from './strategies'; import type { TokenResource } from './token'; import type { UserResource } from './user'; import type { Autocomplete } from './utils'; @@ -537,6 +540,8 @@ export type SessionVerifyPrepareFirstFactorParams = | EmailCodeConfig | PhoneCodeConfig | PassKeyConfig + | ResetPasswordEmailCodeFactorConfig + | ResetPasswordPhoneCodeFactorConfig /** * @experimental */ @@ -546,11 +551,35 @@ export type SessionVerifyPrepareFirstFactorParams = */ | TrustedDeviceConfig; +/** + * Reverifies with a reset password code. Unlike a sign-in's reset attempt, the + * new password is required: the code only counts together with it. + */ +export type SessionVerifyResetPasswordAttempt = { + /** + * The strategy type. + */ + strategy: ResetPasswordEmailCodeStrategy | ResetPasswordPhoneCodeStrategy; + /** + * The one-time code sent to the user's email address or phone number. + */ + code: string; + /** + * The new password, which replaces the current one. + */ + password: string; + /** + * Whether to sign the user out of their other sessions once the new password is set. + */ + signOutOfOtherSessions?: boolean; +}; + export type SessionVerifyAttemptFirstFactorParams = | EmailCodeAttempt | PhoneCodeAttempt | PasswordAttempt | PasskeyAttempt + | SessionVerifyResetPasswordAttempt /** * @experimental */ diff --git a/packages/shared/src/types/sessionVerification.ts b/packages/shared/src/types/sessionVerification.ts index bcbd347d5fd..e2123bf2387 100644 --- a/packages/shared/src/types/sessionVerification.ts +++ b/packages/shared/src/types/sessionVerification.ts @@ -5,6 +5,8 @@ import type { PasskeyFactor, PasswordFactor, PhoneCodeFactor, + ResetPasswordEmailCodeFactor, + ResetPasswordPhoneCodeFactor, TOTPFactor, } from './factors'; import type { ClerkResource } from './resource'; @@ -55,6 +57,8 @@ export type SessionVerificationFirstFactor = | PhoneCodeFactor | PasswordFactor | PasskeyFactor + | ResetPasswordEmailCodeFactor + | ResetPasswordPhoneCodeFactor /** * @experimental */ diff --git a/packages/ui/src/Components.tsx b/packages/ui/src/Components.tsx index 0fb3e678246..304d9683d2b 100644 --- a/packages/ui/src/Components.tsx +++ b/packages/ui/src/Components.tsx @@ -7,6 +7,7 @@ import type { __internal_ProtectCheckModalProps, __internal_SubscriptionDetailsProps, __internal_UserVerificationProps, + __internal_UserVerificationResult, Clerk, ClerkOptions, CreateOrganizationModalProps, @@ -468,8 +469,8 @@ const Components = (props: ComponentsProps) => { /** * When a UserVerification flow is completed, we need to close the modal without trigger a cancellation callback */ - afterVerification() { - props.afterVerification?.(); + afterVerification(result?: __internal_UserVerificationResult) { + props.afterVerification?.(result); componentsControls.closeModal(name, { notify: false }); }, }, diff --git a/packages/ui/src/components/UserProfile/PasswordForm.tsx b/packages/ui/src/components/UserProfile/PasswordForm.tsx index c9038469e86..37a7a28ba09 100644 --- a/packages/ui/src/components/UserProfile/PasswordForm.tsx +++ b/packages/ui/src/components/UserProfile/PasswordForm.tsx @@ -1,4 +1,4 @@ -import { useReverification, useSession, useUser } from '@clerk/shared/react'; +import { useClerk, useReverification, useSession, useUser } from '@clerk/shared/react'; import type { UserResource } from '@clerk/shared/types'; import { useRef } from 'react'; @@ -38,8 +38,35 @@ export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => const { t, locale } = useLocalizations(); const { onSuccess, onReset } = props; const { user } = useUser(); + const clerk = useClerk(); + // The new password the form is about to set. If reverification asks the user + // to reset a breached password, the modal uses it instead of asking again. + const pendingPasswordRef = useRef<{ password: string; signOutOfOtherSessions: boolean }>(); + // Set when the modal already changed the password, so the retry doesn't + // change it a second time. + const passwordResetDuringVerificationRef = useRef(false); const updatePasswordWithReverification = useReverification( - (user: UserResource, opts: Parameters) => user.updatePassword(...opts), + (user: UserResource, opts: Parameters) => { + if (passwordResetDuringVerificationRef.current) { + passwordResetDuringVerificationRef.current = false; + // The password already changed, so a failed refresh isn't a failed change. + return user.reload().catch(() => user); + } + return user.updatePassword(...opts); + }, + { + onNeedsReverification: ({ level, complete, cancel }) => { + clerk.__internal_openReverification({ + level, + resetPassword: pendingPasswordRef.current, + afterVerification: result => { + passwordResetDuringVerificationRef.current = !!result?.passwordReset; + complete(); + }, + afterVerificationCancelled: cancel, + }); + }, + }, ); if (!user) { @@ -125,6 +152,10 @@ export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => signOutOfOtherSessions: sessionsField.checked, currentPassword: currentPasswordRequired ? currentPasswordField.value : undefined, } satisfies Parameters[0]; + pendingPasswordRef.current = { + password: opts.newPassword, + signOutOfOtherSessions: !!opts.signOutOfOtherSessions, + }; await updatePasswordWithReverification(user, [opts]); onSuccess(); diff --git a/packages/ui/src/components/UserProfile/__tests__/PasswordSection.test.tsx b/packages/ui/src/components/UserProfile/__tests__/PasswordSection.test.tsx index 854fa978b27..af42a93aed9 100644 --- a/packages/ui/src/components/UserProfile/__tests__/PasswordSection.test.tsx +++ b/packages/ui/src/components/UserProfile/__tests__/PasswordSection.test.tsx @@ -1,7 +1,8 @@ -import { describe, expect, it } from 'vitest'; +import { ClerkAPIResponseError } from '@clerk/shared/error'; +import { describe, expect, it, vi } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; -import { fireEvent, render, screen, waitFor } from '@/test/utils'; +import { act, fireEvent, render, screen, waitFor } from '@/test/utils'; import { CardStateProvider } from '@/ui/elements/contexts'; import { PasswordSection } from '../PasswordSection'; @@ -348,6 +349,71 @@ describe('PasswordSection', () => { }); }); + describe('Reverification with a breached password', () => { + const reverificationRequired = () => + new ClerkAPIResponseError('Reverification required', { + status: 403, + data: [{ code: 'session_reverification_required', message: 'Reverification required' }], + }); + + // Submits the form, which needs reverification, and returns the props the modal was opened with. + const submitAndOpenReverification = async () => { + const config = createFixtures.config(f => { + f.withReverification(); + f.withPassword(); + f.withUser({ password_enabled: true }); + }); + const { wrapper, fixtures } = await createFixtures(config); + fixtures.clerk.user?.updatePassword.mockRejectedValueOnce(reverificationRequired()).mockResolvedValue({}); + const reload = vi.spyOn(fixtures.clerk.user!, 'reload').mockResolvedValue({} as any); + const openReverification = vi + .spyOn(fixtures.clerk, '__internal_openReverification') + .mockImplementation(() => {}); + + const { getByRole, userEvent, getByLabelText } = render(, { wrapper }); + await userEvent.click(getByRole('button', { name: /update password/i })); + await waitFor(() => getByRole('heading', { name: /update password/i })); + await userEvent.type(getByLabelText(/new password/i), 'testtest'); + await userEvent.type(getByLabelText(/confirm password/i), 'testtest'); + await userEvent.click(getByRole('button', { name: /save$/i })); + + await waitFor(() => expect(openReverification).toHaveBeenCalledTimes(1)); + return { fixtures, reload, modalProps: openReverification.mock.calls[0][0]! }; + }; + + it('passes the new password to the modal and skips the retry once the modal reset it', async () => { + const { fixtures, reload, modalProps } = await submitAndOpenReverification(); + expect(modalProps.resetPassword).toEqual({ password: 'testtest', signOutOfOtherSessions: true }); + + await act(() => modalProps.afterVerification?.({ passwordReset: true })); + + await waitFor(() => expect(reload).toHaveBeenCalledTimes(1)); + expect(fixtures.clerk.user?.updatePassword).toHaveBeenCalledTimes(1); + }); + + it('shows success when the modal reset the password but refreshing the user fails', async () => { + const { fixtures, reload, modalProps } = await submitAndOpenReverification(); + reload.mockRejectedValueOnce(new Error('network')); + + await act(() => modalProps.afterVerification?.({ passwordReset: true })); + + // Success closes the form instead of showing the refresh error on it + await waitFor(() => + expect(screen.queryByRole('heading', { name: /update password/i })).not.toBeInTheDocument(), + ); + expect(fixtures.clerk.user?.updatePassword).toHaveBeenCalledTimes(1); + }); + + it('retries the password change when the modal verified another way', async () => { + const { fixtures, reload, modalProps } = await submitAndOpenReverification(); + + await act(() => modalProps.afterVerification?.()); + + await waitFor(() => expect(fixtures.clerk.user?.updatePassword).toHaveBeenCalledTimes(2)); + expect(reload).not.toHaveBeenCalled(); + }); + }); + describe('with Enterprise SSO', () => { it('prevents changing a password if user has active enterprise connections', async () => { const emailAddress = 'george@jungle.com'; diff --git a/packages/ui/src/components/UserVerification/AlternativeMethods.tsx b/packages/ui/src/components/UserVerification/AlternativeMethods.tsx index a77193ea38f..60098232e85 100644 --- a/packages/ui/src/components/UserVerification/AlternativeMethods.tsx +++ b/packages/ui/src/components/UserVerification/AlternativeMethods.tsx @@ -10,7 +10,7 @@ import { formatSafeIdentifier } from '@/ui/utils/formatSafeIdentifier'; import type { LocalizationKey } from '../../customizables'; import { Col, descriptors, Flex, Flow, localizationKeys } from '../../customizables'; import { useCardState } from '../../elements/contexts'; -import { Building, Envelope, Fingerprint, Lock, SpeechBubble } from '../../icons'; +import { Building, Envelope, Fingerprint, Lock, LockArrow, SpeechBubble } from '../../icons'; import { useReverificationAlternativeStrategies } from './useReverificationAlternativeStrategies'; import { useUserVerificationSession } from './useUserVerificationSession'; import { withHavingTrouble } from './withHavingTrouble'; @@ -118,6 +118,9 @@ export function getButtonLabel(factor: SessionVerificationFirstFactor): Localiza return localizationKeys('reverification.alternativeMethods.blockButton__password'); case 'passkey': return localizationKeys('reverification.alternativeMethods.blockButton__passkey'); + case 'reset_password_email_code': + case 'reset_password_phone_code': + return localizationKeys('reverification.passwordPwned.blockButton__resetPassword'); default: throw new Error(`Invalid sign in strategy: "${(factor as any).strategy}"`); } @@ -130,6 +133,8 @@ export function getButtonIcon(factor: SessionVerificationFirstFactor) { password: Lock, passkey: Fingerprint, enterprise_sso: Building, + reset_password_email_code: LockArrow, + reset_password_phone_code: LockArrow, } as const; return icons[factor.strategy]; diff --git a/packages/ui/src/components/UserVerification/UVFactorOneCodeForm.tsx b/packages/ui/src/components/UserVerification/UVFactorOneCodeForm.tsx index 3f18d9fb364..15d27064433 100644 --- a/packages/ui/src/components/UserVerification/UVFactorOneCodeForm.tsx +++ b/packages/ui/src/components/UserVerification/UVFactorOneCodeForm.tsx @@ -1,5 +1,13 @@ import { useSession } from '@clerk/shared/react'; -import type { EmailCodeFactor, PhoneCodeFactor } from '@clerk/shared/types'; +import type { + EmailCodeAttempt, + EmailCodeFactor, + PhoneCodeAttempt, + PhoneCodeFactor, + ResetPasswordEmailCodeFactor, + ResetPasswordPhoneCodeFactor, + SessionVerificationResource, +} from '@clerk/shared/types'; import React from 'react'; import { useCardState } from '@/ui/elements/contexts'; @@ -8,13 +16,14 @@ import { VerificationCodeCard } from '@/ui/elements/VerificationCodeCard'; import { handleError } from '@/ui/utils/errorHandler'; import type { LocalizationKey } from '../../localization'; +import { localizationKeys } from '../../localization'; import { useAfterVerification } from './use-after-verification'; export type UVFactorOneCodeCard = Pick< VerificationCodeCardProps, 'onShowAlternativeMethodsClicked' | 'showAlternativeMethods' | 'onBackLinkClicked' > & { - factor: EmailCodeFactor | PhoneCodeFactor; + factor: EmailCodeFactor | PhoneCodeFactor | ResetPasswordEmailCodeFactor | ResetPasswordPhoneCodeFactor; factorAlreadyPrepared: boolean; onFactorPrepare: () => void; }; @@ -25,6 +34,14 @@ export type UVFactorOneCodeFormProps = UVFactorOneCodeCard & { inputLabel: LocalizationKey; resendButton: LocalizationKey; identityPreviewEditButtonAriaLabel: LocalizationKey; + /** + * Replaces the default attempt, which sends only the strategy and code. + */ + attempt?: (code: string) => Promise; + /** + * Handles a failed attempt. Defaults to showing the error on the code card. + */ + onAttemptError?: (err: unknown, reject: (err: unknown) => Promise) => void; }; export const UVFactorOneCodeForm = (props: UVFactorOneCodeFormProps) => { @@ -48,14 +65,20 @@ export const UVFactorOneCodeForm = (props: UVFactorOneCodeFormProps) => { }; const action: VerificationCodeCardProps['onCodeEntryFinishedAction'] = (code, resolve, reject) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - session! - .attemptFirstFactorVerification({ strategy: props.factor.strategy, code }) + // The reset password strategies pass their own attempt, which carries the new password. + const attempt = + props.attempt ?? + ((code: string) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + session!.attemptFirstFactorVerification({ strategy: props.factor.strategy, code } as + | EmailCodeAttempt + | PhoneCodeAttempt)); + attempt(code) .then(async res => { await resolve(); return handleVerificationResponse(res); }) - .catch(reject); + .catch(err => (props.onAttemptError ? props.onAttemptError(err, reject) : reject(err))); }; return ( @@ -72,6 +95,7 @@ export const UVFactorOneCodeForm = (props: UVFactorOneCodeFormProps) => { onShowAlternativeMethodsClicked={props.onShowAlternativeMethodsClicked} showAlternativeMethods={props.showAlternativeMethods} onBackLinkClicked={props.onBackLinkClicked} + backLinkLabel={localizationKeys('backButton')} /> ); }; diff --git a/packages/ui/src/components/UserVerification/UVFactorOneResetPasswordCodeCard.tsx b/packages/ui/src/components/UserVerification/UVFactorOneResetPasswordCodeCard.tsx new file mode 100644 index 00000000000..c6b876e168b --- /dev/null +++ b/packages/ui/src/components/UserVerification/UVFactorOneResetPasswordCodeCard.tsx @@ -0,0 +1,67 @@ +import { isClerkAPIResponseError } from '@clerk/shared/error'; +import { useSession } from '@clerk/shared/react'; +import type { ResetPasswordEmailCodeFactor, ResetPasswordPhoneCodeFactor } from '@clerk/shared/types'; + +import { Flow, localizationKeys } from '../../customizables'; +import type { UVFactorOneCodeCard } from './UVFactorOneCodeForm'; +import { UVFactorOneCodeForm } from './UVFactorOneCodeForm'; + +type UVFactorOneResetPasswordCodeCardProps = UVFactorOneCodeCard & { + factor: ResetPasswordEmailCodeFactor | ResetPasswordPhoneCodeFactor; + newPassword: string; + signOutOfOtherSessions: boolean; + /** The caller collected the new password, so the copy says it's being saved, not reset. */ + hasNewPassword: boolean; + /** The code was right but the new password was rejected; the code stays usable. */ + onNewPasswordRejected: (err: Error) => void; +}; + +/** + * Verifies with a reset password code. The attempt carries the new password, + * which replaces the current one once the verification completes. + */ +export const UVFactorOneResetPasswordCodeCard = (props: UVFactorOneResetPasswordCodeCardProps) => { + const { newPassword, signOutOfOtherSessions, hasNewPassword, onNewPasswordRejected, ...codeCardProps } = props; + const { session } = useSession(); + const isEmail = props.factor.strategy === 'reset_password_email_code'; + const channel = isEmail ? 'email' : 'phone'; + + return ( + + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + session!.attemptFirstFactorVerification({ + strategy: props.factor.strategy, + code, + password: newPassword, + signOutOfOtherSessions, + }) + } + onAttemptError={(err, reject) => { + if (isClerkAPIResponseError(err) && err.errors[0]?.meta?.paramName === 'password') { + onNewPasswordRejected(err); + return; + } + void reject(err); + }} + /> + + ); +}; diff --git a/packages/ui/src/components/UserVerification/UVPasswordPwnedMethods.tsx b/packages/ui/src/components/UserVerification/UVPasswordPwnedMethods.tsx new file mode 100644 index 00000000000..4eaa5299ac6 --- /dev/null +++ b/packages/ui/src/components/UserVerification/UVPasswordPwnedMethods.tsx @@ -0,0 +1,150 @@ +import type { + ResetPasswordEmailCodeFactor, + ResetPasswordPhoneCodeFactor, + SessionVerificationFirstFactor, + SignInFactor, +} from '@clerk/shared/types'; +import React from 'react'; + +import { ArrowBlockButton } from '@/ui/elements/ArrowBlockButton'; +import { BackLink } from '@/ui/elements/BackLink'; +import { Card } from '@/ui/elements/Card'; +import { Divider } from '@/ui/elements/Divider'; +import { Header } from '@/ui/elements/Header'; +import { formatSafeIdentifier } from '@/ui/utils/formatSafeIdentifier'; + +import { Button, Col, descriptors, Flex, Flow, localizationKeys } from '../../customizables'; +import { useCardState } from '../../elements/contexts'; +import { getButtonIcon, getButtonLabel } from './AlternativeMethods'; +import { useReverificationAlternativeStrategies } from './useReverificationAlternativeStrategies'; +import type { PasswordErrorReason } from './UserVerificationFactorOnePassword'; + +type UVPasswordPwnedMethodsProps = { + reason: PasswordErrorReason; + resetPasswordFactor: ResetPasswordEmailCodeFactor | ResetPasswordPhoneCodeFactor; + /** + * The caller already has the new password, so the user only confirms it's + * them with the code. Without it, the user resets the password here. + */ + hasNewPassword: boolean; + /** The factors this modal can verify with; others aren't offered. */ + supportedFirstFactors: SessionVerificationFirstFactor[] | null; + onResetPasswordClick: () => void; + onFactorSelected: (factor: SignInFactor) => void; + onBackLinkClick: React.MouseEventHandler; +}; + +/** + * Shown when the password is rejected as breached or compromised: the user + * can reset it, or verify with another method. + */ +export const UVPasswordPwnedMethods = (props: UVPasswordPwnedMethodsProps) => { + const { + reason, + resetPasswordFactor, + hasNewPassword, + supportedFirstFactors, + onResetPasswordClick, + onFactorSelected, + onBackLinkClick, + } = props; + const identifier = formatSafeIdentifier(resetPasswordFactor.safeIdentifier) || ''; + const card = useCardState(); + const { firstPartyFactors: alternativeFactors } = + useReverificationAlternativeStrategies({ + filterOutFactor: { strategy: 'password' } as SignInFactor, + supportedFirstFactors, + }); + // With a new password already chosen, the reset is just a code to the same + // address as the plain code method, so listing both would show the same button twice. + const firstPartyFactors = hasNewPassword + ? alternativeFactors.filter(f => !sendsCodeToSameIdentification(f, resetPasswordFactor)) + : alternativeFactors; + const hasAlternativeStrategies = firstPartyFactors.length > 0; + const copy = reason === 'pwned' ? 'passwordPwned' : 'passwordCompromised'; + + return ( + + + + + + {hasNewPassword && ( + + )} + + {card.error} + +