From 88bdf8ecbcddbdcdec34e216807f1100f52ce03e Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 11 Aug 2026 23:13:52 -0700 Subject: [PATCH 01/16] fix: support email-link session reverification --- .../email-link-session-reverification.md | 8 ++ .../clerk-js/src/core/resources/Session.ts | 57 +++++++++++++ .../core/resources/__tests__/Session.test.ts | 53 +++++++++++- packages/localizations/src/en-US.ts | 24 ++++++ packages/shared/src/types/localization.ts | 24 ++++++ packages/shared/src/types/session.ts | 12 +++ .../shared/src/types/sessionVerification.ts | 2 + packages/ui/src/Components.tsx | 1 + .../UserVerification/AlternativeMethods.tsx | 5 ++ .../UVFactorOneEmailLinkCard.tsx | 80 +++++++++++++++++++ .../UserVerificationEmailLinkVerify.tsx | 41 ++++++++++ .../UserVerificationFactorOne.tsx | 10 +++ .../__tests__/UVFactorOne.test.tsx | 43 +++++++++- .../UserVerificationEmailLinkVerify.test.tsx | 26 ++++++ .../src/components/UserVerification/index.tsx | 4 + 15 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 .changeset/email-link-session-reverification.md create mode 100644 packages/ui/src/components/UserVerification/UVFactorOneEmailLinkCard.tsx create mode 100644 packages/ui/src/components/UserVerification/UserVerificationEmailLinkVerify.tsx create mode 100644 packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx diff --git a/.changeset/email-link-session-reverification.md b/.changeset/email-link-session-reverification.md new file mode 100644 index 00000000000..9e2ba6502fe --- /dev/null +++ b/.changeset/email-link-session-reverification.md @@ -0,0 +1,8 @@ +--- +'@clerk/shared': patch +'@clerk/clerk-js': patch +'@clerk/ui': patch +'@clerk/localizations': patch +--- + +Support email-link first factors in session reverification. The original tab waits for the link callback, then resumes the protected action without changing the configured authentication strategy. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 981a30a6f6c..bea8b11c493 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -13,13 +13,16 @@ import { serializePublicKeyCredentialAssertion, webAuthnGetCredential as webAuthnGetCredentialOnWindow, } from '@clerk/shared/internal/clerk-js/passkeys'; +import { Poller } from '@clerk/shared/poller'; import { retry } from '@clerk/shared/retry'; import type { ActClaim, AgentActClaim, CheckAuthorization, ClientResource, + CreateEmailLinkFlowReturn, EmailCodeConfig, + EmailLinkConfig, EnterpriseSSOConfig, GetToken, GetTokenOptions, @@ -27,6 +30,7 @@ import type { SessionJSON, SessionJSONSnapshot, SessionResource, + SessionStartEmailLinkFlowParams, SessionStatus, SessionTask, SessionTouchParams, @@ -261,6 +265,12 @@ export class Session extends BaseResource implements SessionResource { case 'email_code': config = { emailAddressId: factor.emailAddressId } as EmailCodeConfig; break; + case 'email_link': + config = { + emailAddressId: factor.emailAddressId, + redirectUrl: factor.redirectUrl, + } as EmailLinkConfig; + break; case 'phone_code': config = { phoneNumberId: factor.phoneNumberId, @@ -295,6 +305,53 @@ export class Session extends BaseResource implements SessionResource { return new SessionVerification(json); }; + createEmailLinkFlow = (): CreateEmailLinkFlowReturn => { + const { run, stop } = Poller(); + + const startEmailLinkFlow = async ({ + emailAddressId, + redirectUrl, + }: SessionStartEmailLinkFlowParams): Promise => { + await this.prepareFirstFactorVerification({ strategy: 'email_link', emailAddressId, redirectUrl }); + + return new Promise((resolve, reject) => { + void run(() => { + return this.#readVerification() + .then(res => { + const verificationStatus = res.firstFactorVerification.status; + if ( + res.status === 'complete' || + res.status === 'needs_second_factor' || + verificationStatus === 'verified' || + verificationStatus === 'expired' || + verificationStatus === 'failed' + ) { + stop(); + resolve(res); + } + }) + .catch(err => { + stop(); + reject(err); + }); + }); + }); + }; + + return { startEmailLinkFlow, cancelEmailLinkFlow: stop }; + }; + + #readVerification = async (): Promise => { + const json = ( + await BaseResource._fetch({ + method: 'GET', + path: `/client/sessions/${this.id}/verify`, + }) + )?.response as unknown as SessionVerificationJSON; + + return new SessionVerification(json); + }; + attemptFirstFactorVerification = async ( attemptFactor: SessionVerifyAttemptFirstFactorParams, ): Promise => { 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 33ce91597e1..452ea43e5d2 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -2,7 +2,7 @@ import { ClerkAPIResponseError, ClerkOfflineError } from '@clerk/shared/error'; import type { InstanceType, OrganizationJSON, SessionJSON } from '@clerk/shared/types'; import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; -import { clerkMock, createUser, mockFetch, mockJwt, mockNetworkFailedFetch } from '@/test/core-fixtures'; +import { clerkMock, createSession, createUser, mockFetch, mockJwt, mockNetworkFailedFetch } from '@/test/core-fixtures'; import { restoreDocument, setDocument, @@ -2522,4 +2522,55 @@ describe('Session', () => { }); }); }); + + describe('createEmailLinkFlow()', () => { + it('prepares email-link reverification and resolves after the callback completes the active step-up', async () => { + BaseResource.clerk = clerkMock(); + const sessionJSON = createSession({ id: 'session_1', factor_verification_age: [99999, -1] }); + const session = new Session(sessionJSON); + const fetchSpy = vi.spyOn(BaseResource, '_fetch'); + const response = (status: 'needs_first_factor' | 'complete', verificationStatus: 'unverified' | 'verified') => ({ + response: { + object: 'session_verification', + status, + level: 'first_factor', + session: sessionJSON, + first_factor_verification: { + object: 'verification_email_link', + strategy: 'email_link', + status: verificationStatus, + }, + second_factor_verification: null, + supported_first_factors: status === 'complete' ? null : [{ strategy: 'email_link' }], + supported_second_factors: null, + }, + }); + + fetchSpy + .mockResolvedValueOnce(response('needs_first_factor', 'unverified') as any) + .mockResolvedValueOnce(response('complete', 'verified') as any); + + const { startEmailLinkFlow } = session.createEmailLinkFlow(); + const result = await startEmailLinkFlow({ + emailAddressId: 'idn_email', + redirectUrl: 'https://app.example.com/protected-action', + }); + + expect(result.status).toBe('complete'); + expect(result.firstFactorVerification.strategy).toBe('email_link'); + expect(fetchSpy).toHaveBeenNthCalledWith(1, { + method: 'POST', + path: '/client/sessions/session_1/verify/prepare_first_factor', + body: { + emailAddressId: 'idn_email', + redirectUrl: 'https://app.example.com/protected-action', + strategy: 'email_link', + }, + }); + expect(fetchSpy).toHaveBeenNthCalledWith(2, { + method: 'GET', + path: '/client/sessions/session_1/verify', + }); + }); + }); }); diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index aa0385251de..7a560706ee7 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -1286,6 +1286,7 @@ export const enUS: LocalizationResource = { actionText: 'Don’t have any of these?', blockButton__backupCode: 'Use a backup code', blockButton__emailCode: 'Email code to {{identifier}}', + blockButton__emailLink: 'Email link to {{identifier}}', blockButton__passkey: 'Use your passkey', blockButton__password: 'Continue with your password', blockButton__phoneCode: 'Send SMS code to {{identifier}}', @@ -1309,6 +1310,29 @@ export const enUS: LocalizationResource = { subtitle: 'Enter the code sent to your email to continue', title: 'Verification required', }, + emailLink: { + clientMismatch: { + subtitle: 'Open the link in the same browser where you started verification.', + title: 'Verification link is invalid for this browser', + }, + expired: { + subtitle: 'Return to the original tab and request a new link.', + title: 'This verification link has expired', + }, + failed: { + subtitle: 'Return to the original tab and request a new link.', + title: 'This verification link is invalid', + }, + formSubtitle: 'Use the verification link sent to your email', + formTitle: 'Verification link', + resendButton: "Didn't receive a link? Resend", + subtitle: 'We sent a verification link to your email address', + title: 'Check your email', + verified: { + subtitle: 'Return to the original tab to continue.', + title: 'Verification complete', + }, + }, noAvailableMethods: { message: 'Cannot proceed with verification. No suitable authentication factor is configured', subtitle: 'An error occurred', diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 26b9452fc15..e6145585d3e 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -642,6 +642,29 @@ export type __internal_LocalizationResource = { formTitle: LocalizationValue; resendButton: LocalizationValue; }; + emailLink: { + title: LocalizationValue; + subtitle: LocalizationValue; + formTitle: LocalizationValue; + formSubtitle: LocalizationValue; + resendButton: LocalizationValue; + verified: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; + expired: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; + failed: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; + clientMismatch: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; + }; phoneCode: { title: LocalizationValue; subtitle: LocalizationValue; @@ -674,6 +697,7 @@ export type __internal_LocalizationResource = { actionLink: LocalizationValue; actionText: LocalizationValue; blockButton__emailCode: LocalizationValue<'identifier'>; + blockButton__emailLink: LocalizationValue<'identifier'>; blockButton__phoneCode: LocalizationValue<'identifier'>; blockButton__password: LocalizationValue; blockButton__totp: LocalizationValue; diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index 878fd6e8ecb..8a8fa79425a 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -3,6 +3,7 @@ import type { BackupCodeAttempt, EmailCodeAttempt, EmailCodeConfig, + EmailLinkConfig, EnterpriseSSOConfig, PasskeyAttempt, PassKeyConfig, @@ -29,6 +30,7 @@ import type { SessionJSONSnapshot } from './snapshots'; import type { TokenResource } from './token'; import type { UserResource } from './user'; import type { Autocomplete } from './utils'; +import type { CreateEmailLinkFlowReturn } from './verification'; /** * @inline @@ -317,6 +319,10 @@ export interface SessionResource extends ClerkResource { prepareFirstFactorVerification: ( factor: SessionVerifyPrepareFirstFactorParams, ) => Promise; + /** + * Creates an email-link reverification flow. The returned promise resolves in the original tab after the link callback completes the active session verification. + */ + createEmailLinkFlow: () => CreateEmailLinkFlowReturn; /** * Attempts to complete the [first factor verification](!first-factor-verification) process. * @returns A [`SessionVerification`](https://clerk.com/docs/reference/types/session-verification) instance with its status and supported factors. @@ -527,6 +533,7 @@ export type SessionVerifyCreateParams = { export type SessionVerifyPrepareFirstFactorParams = | EmailCodeConfig + | EmailLinkConfig | PhoneCodeConfig | PassKeyConfig /** @@ -534,6 +541,11 @@ export type SessionVerifyPrepareFirstFactorParams = */ | Omit; +export type SessionStartEmailLinkFlowParams = { + emailAddressId: string; + redirectUrl: string; +}; + export type SessionVerifyAttemptFirstFactorParams = | EmailCodeAttempt | PhoneCodeAttempt diff --git a/packages/shared/src/types/sessionVerification.ts b/packages/shared/src/types/sessionVerification.ts index 61af637ce2b..d4f2929ca1d 100644 --- a/packages/shared/src/types/sessionVerification.ts +++ b/packages/shared/src/types/sessionVerification.ts @@ -1,6 +1,7 @@ import type { BackupCodeFactor, EmailCodeFactor, + EmailLinkFactor, EnterpriseSSOFactor, PasskeyFactor, PasswordFactor, @@ -52,6 +53,7 @@ export type SessionVerificationAfterMinutes = number; export type SessionVerificationFirstFactor = | EmailCodeFactor + | EmailLinkFactor | PhoneCodeFactor | PasswordFactor | PasskeyFactor diff --git a/packages/ui/src/Components.tsx b/packages/ui/src/Components.tsx index ef830af901b..0baafe61e1c 100644 --- a/packages/ui/src/Components.tsx +++ b/packages/ui/src/Components.tsx @@ -292,6 +292,7 @@ const componentNodes = Object.freeze({ SignUp: 'signUpModal', SignIn: 'signInModal', UserProfile: 'userProfileModal', + UserVerification: 'userVerificationModal', OrganizationProfile: 'organizationProfileModal', CreateOrganization: 'createOrganizationModal', Waitlist: 'waitlistModal', diff --git a/packages/ui/src/components/UserVerification/AlternativeMethods.tsx b/packages/ui/src/components/UserVerification/AlternativeMethods.tsx index 6f47ecef5ec..6cbba5ba79b 100644 --- a/packages/ui/src/components/UserVerification/AlternativeMethods.tsx +++ b/packages/ui/src/components/UserVerification/AlternativeMethods.tsx @@ -109,6 +109,10 @@ export function getButtonLabel(factor: SessionVerificationFirstFactor): Localiza return localizationKeys('reverification.alternativeMethods.blockButton__emailCode', { identifier: formatSafeIdentifier(factor.safeIdentifier) || '', }); + case 'email_link': + return localizationKeys('reverification.alternativeMethods.blockButton__emailLink', { + identifier: formatSafeIdentifier(factor.safeIdentifier) || '', + }); case 'phone_code': return localizationKeys('reverification.alternativeMethods.blockButton__phoneCode', { identifier: formatSafeIdentifier(factor.safeIdentifier) || '', @@ -125,6 +129,7 @@ export function getButtonLabel(factor: SessionVerificationFirstFactor): Localiza export function getButtonIcon(factor: SessionVerificationFirstFactor) { const icons = { email_code: Envelope, + email_link: Envelope, phone_code: SpeechBubble, password: Lock, passkey: Fingerprint, diff --git a/packages/ui/src/components/UserVerification/UVFactorOneEmailLinkCard.tsx b/packages/ui/src/components/UserVerification/UVFactorOneEmailLinkCard.tsx new file mode 100644 index 00000000000..e06bea8a1d0 --- /dev/null +++ b/packages/ui/src/components/UserVerification/UVFactorOneEmailLinkCard.tsx @@ -0,0 +1,80 @@ +import { appendModalState } from '@clerk/shared/internal/clerk-js/queryStateParams'; +import { useSession } from '@clerk/shared/react'; +import type { EmailLinkFactor } from '@clerk/shared/types'; +import React from 'react'; + +import type { VerificationCodeCardProps } from '@/ui/elements/VerificationCodeCard'; +import { VerificationLinkCard } from '@/ui/elements/VerificationLinkCard'; +import { handleError } from '@/ui/utils/errorHandler'; + +import { Flow, localizationKeys, useLocalizations } from '../../customizables'; +import { useCardState } from '../../elements/contexts'; +import { useAfterVerification } from './use-after-verification'; + +type UVFactorOneEmailLinkCardProps = Pick & { + factor: EmailLinkFactor; + showAlternativeMethods: boolean; +}; + +export const UVFactorOneEmailLinkCard = (props: UVFactorOneEmailLinkCardProps) => { + const { session } = useSession(); + const { t } = useLocalizations(); + const card = useCardState(); + const { handleVerificationResponse } = useAfterVerification(); + const emailLinkFlow = React.useMemo(() => session?.createEmailLinkFlow(), [session]); + + const startVerification = () => { + if (!emailLinkFlow) { + return; + } + const redirectUrl = appendModalState({ + url: window.location.href, + componentName: 'UserVerification', + startPath: '/user-verification', + currentPath: '/verify', + }); + + emailLinkFlow + .startEmailLinkFlow({ emailAddressId: props.factor.emailAddressId, redirectUrl }) + .then(result => { + if (result.firstFactorVerification.status === 'expired') { + card.setError(t(localizationKeys('formFieldError__verificationLinkExpired'))); + return; + } + return handleVerificationResponse(result); + }) + .catch(err => handleError(err, [], card.setError)); + }; + + React.useEffect(() => { + void startVerification(); + return emailLinkFlow?.cancelEmailLinkFlow; + // The flow is tied to the mounted factor card. Factor changes remount this card. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const restartVerification = () => { + emailLinkFlow?.cancelEmailLinkFlow(); + card.setError(undefined); + void startVerification(); + }; + + return ( + + + + ); +}; diff --git a/packages/ui/src/components/UserVerification/UserVerificationEmailLinkVerify.tsx b/packages/ui/src/components/UserVerification/UserVerificationEmailLinkVerify.tsx new file mode 100644 index 00000000000..b3d24e5de5e --- /dev/null +++ b/packages/ui/src/components/UserVerification/UserVerificationEmailLinkVerify.tsx @@ -0,0 +1,41 @@ +import { getClerkQueryParam } from '@clerk/shared/internal/clerk-js/queryParams'; + +import { EmailLinkStatusCard } from '../../common'; +import type { EmailLinkUIStatus } from '../../common/EmailLinkStatusCard'; +import { localizationKeys } from '../../customizables'; +import { withCardStateProvider } from '../../elements/contexts'; + +const supportedStatuses = new Set(['verified', 'expired', 'failed', 'client_mismatch']); + +const texts = { + verified: { + title: localizationKeys('reverification.emailLink.verified.title'), + subtitle: localizationKeys('reverification.emailLink.verified.subtitle'), + }, + expired: { + title: localizationKeys('reverification.emailLink.expired.title'), + subtitle: localizationKeys('reverification.emailLink.expired.subtitle'), + }, + failed: { + title: localizationKeys('reverification.emailLink.failed.title'), + subtitle: localizationKeys('reverification.emailLink.failed.subtitle'), + }, + client_mismatch: { + title: localizationKeys('reverification.emailLink.clientMismatch.title'), + subtitle: localizationKeys('reverification.emailLink.clientMismatch.subtitle'), + }, +} as const; + +export const UserVerificationEmailLinkVerify = withCardStateProvider(() => { + const queryStatus = getClerkQueryParam('__clerk_status') as EmailLinkUIStatus | null; + const status = queryStatus && supportedStatuses.has(queryStatus) ? queryStatus : 'failed'; + const text = texts[status as keyof typeof texts]; + + return ( + + ); +}); diff --git a/packages/ui/src/components/UserVerification/UserVerificationFactorOne.tsx b/packages/ui/src/components/UserVerification/UserVerificationFactorOne.tsx index 14bc9867c0e..0346a517aa1 100644 --- a/packages/ui/src/components/UserVerification/UserVerificationFactorOne.tsx +++ b/packages/ui/src/components/UserVerification/UserVerificationFactorOne.tsx @@ -15,6 +15,7 @@ import { UserVerificationFactorOnePasswordCard } from './UserVerificationFactorO import { useUserVerificationSession, withUserVerificationSessionGuard } from './useUserVerificationSession'; import { sortByPrimaryFactor } from './utils'; import { UVFactorOneEmailCodeCard } from './UVFactorOneEmailCodeCard'; +import { UVFactorOneEmailLinkCard } from './UVFactorOneEmailLinkCard'; import { UVFactorOnePasskeysCard } from './UVFactorOnePasskeysCard'; import { UVFactorOnePhoneCodeCard } from './UVFactorOnePhoneCodeCard'; @@ -35,6 +36,7 @@ const factorKey = (factor: SignInFactor | null | undefined) => { const SUPPORTED_STRATEGIES: SessionVerificationFirstFactor['strategy'][] = [ 'password', 'email_code', + 'email_link', 'phone_code', 'passkey', ] as const; @@ -143,6 +145,14 @@ export function UserVerificationFactorOneInternal(): JSX.Element | null { showAlternativeMethods={hasFirstParty} /> ); + case 'email_link': + return ( + + ); case 'phone_code': return ( { expect(fixtures.session?.prepareFirstFactorVerification).toHaveBeenCalledOnce(); }); + it('prepares email-link reverification and preserves the protected action URL', async () => { + window.history.replaceState({}, '', '/account/billing?return=plans'); + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({ username: 'clerkuser' }); + }); + const startEmailLinkFlow = vi.fn().mockResolvedValue({ + status: 'complete', + session: { id: 'session_1' }, + firstFactorVerification: { status: 'verified' }, + }); + fixtures.session?.startVerification.mockResolvedValue({ + status: 'needs_first_factor', + supportedFirstFactors: [ + { + strategy: 'email_link', + emailAddressId: 'idn_email', + safeIdentifier: 'user@example.com', + }, + ], + }); + fixtures.session?.createEmailLinkFlow.mockReturnValue({ + startEmailLinkFlow, + cancelEmailLinkFlow: vi.fn(), + }); + + const { getByText } = render(, { wrapper }); + await waitFor(() => getByText('Check your email')); + await waitFor(() => expect(startEmailLinkFlow).toHaveBeenCalledOnce()); + + const redirectUrl = new URL(startEmailLinkFlow.mock.calls[0][0].redirectUrl); + expect(redirectUrl.pathname).toBe('/account/billing'); + expect(redirectUrl.searchParams.get('return')).toBe('plans'); + const modalState = JSON.parse(atob(redirectUrl.searchParams.get('__clerk_modal_state')!)); + expect(modalState).toMatchObject({ + componentName: 'UserVerification', + path: '/verify', + startPath: '/user-verification', + }); + await waitFor(() => expect(fixtures.clerk.setActive).toHaveBeenCalledWith({ session: 'session_1' })); + }); + describe('Submitting', () => { it('navigates to UserVerificationFactorTwo page when user submits first factor and second factor is enabled', async () => { const { wrapper, fixtures } = await createFixtures(f => { diff --git a/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx b/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx new file mode 100644 index 00000000000..63043451757 --- /dev/null +++ b/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx @@ -0,0 +1,26 @@ +import { afterEach, describe, it } from 'vitest'; + +import { bindCreateFixtures } from '@/test/create-fixtures'; +import { render, screen } from '@/test/utils'; + +import { UserVerificationEmailLinkVerify } from '../UserVerificationEmailLinkVerify'; + +const { createFixtures } = bindCreateFixtures('UserVerification'); + +describe('UserVerificationEmailLinkVerify', () => { + afterEach(() => { + window.history.replaceState({}, '', '/'); + }); + + it('tells the user to return to the original protected-action tab after verification', async () => { + window.history.replaceState({}, '', '/account/billing?__clerk_status=verified'); + const { wrapper } = await createFixtures(f => { + f.withUser({ username: 'clerkuser' }); + }); + + render(, { wrapper }); + + screen.getByText('Verification complete'); + screen.getByText('Return to the original tab to continue.'); + }); +}); diff --git a/packages/ui/src/components/UserVerification/index.tsx b/packages/ui/src/components/UserVerification/index.tsx index cc68ba080ee..59ddada503a 100644 --- a/packages/ui/src/components/UserVerification/index.tsx +++ b/packages/ui/src/components/UserVerification/index.tsx @@ -6,6 +6,7 @@ import { Flow } from '@/customizables'; import type { WithInternalRouting } from '@/internal'; import { Route, Switch } from '@/router'; +import { UserVerificationEmailLinkVerify } from './UserVerificationEmailLinkVerify'; import { UserVerificationFactorOne } from './UserVerificationFactorOne'; import { UserVerificationFactorTwo } from './UserVerificationFactorTwo'; import { useUserVerificationSession } from './useUserVerificationSession'; @@ -20,6 +21,9 @@ function UserVerificationRoutes(): JSX.Element { return ( + + + From 3cd248e71d5a266785521c26097fe5bcf52fbd20 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Mon, 17 Aug 2026 11:21:19 -0700 Subject: [PATCH 02/16] chore(localizations): generate email-link strings --- packages/localizations/src/ar-SA.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/be-BY.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/bg-BG.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/bn-IN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ca-ES.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/cs-CZ.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/da-DK.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/de-DE.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/el-GR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/en-GB.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/es-CR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/es-ES.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/es-MX.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/es-UY.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/fa-IR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/fi-FI.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/fr-FR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/he-IL.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/hi-IN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/hr-HR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/hu-HU.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/id-ID.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/is-IS.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/it-IT.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ja-JP.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/kk-KZ.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ko-KR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/mn-MN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ms-MY.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/nb-NO.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/nl-BE.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/nl-NL.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/pl-PL.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/pt-BR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/pt-PT.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ro-RO.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ru-RU.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/sk-SK.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/sr-RS.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/sv-SE.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/ta-IN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/te-IN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/th-TH.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/tr-TR.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/uk-UA.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/vi-VN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/zh-CN.ts | 24 ++++++++++++++++++++++++ packages/localizations/src/zh-TW.ts | 24 ++++++++++++++++++++++++ 48 files changed, 1152 insertions(+) diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 1a0e9542bce..16dc71e9506 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -1254,6 +1254,7 @@ export const arSA: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1276,6 +1277,29 @@ export const arSA: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index 87edd1c7956..232c07baba4 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -1261,6 +1261,7 @@ export const beBY: LocalizationResource = { actionText: 'Паспрабуйце іншы метад для верыфікацыі.', blockButton__backupCode: 'Увядзіце код з рэзервовага кода', blockButton__emailCode: 'Увядзіце код, адправлены на электронную пошту', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Увядзіце пароль', blockButton__phoneCode: 'Увядзіце код, адправлены на тэлефон', @@ -1283,6 +1284,29 @@ export const beBY: LocalizationResource = { subtitle: 'Калі ласка, праверце вашу электронную пошту для кода.', title: 'Код з email', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Няма даступных метадаў для верыфікацыі.', subtitle: 'Калі ласка, выберыце іншы метад або звярніцеся ў службу падтрымкі.', diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index d7c80b74640..83920eceeb9 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -1258,6 +1258,7 @@ export const bgBG: LocalizationResource = { actionText: "Don't have one of these?", blockButton__backupCode: 'Use backup code', blockButton__emailCode: 'Send code to email', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Login with password', blockButton__phoneCode: 'Send code to phone', @@ -1280,6 +1281,29 @@ export const bgBG: LocalizationResource = { subtitle: "We've sent a code to your email.", title: 'Email verification', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Unable to proceed. No available authentication methods.', subtitle: 'Something went wrong.', diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index cdf78289cad..bf96ee9e3fb 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -1264,6 +1264,7 @@ export const bnIN: LocalizationResource = { actionText: 'এর কোনটিই নেই?', blockButton__backupCode: 'একটি ব্যাকআপ কোড ব্যবহার করুন', blockButton__emailCode: '{{identifier}}-এ ইমেইল কোড পাঠান', + blockButton__emailLink: undefined, blockButton__passkey: 'আপনার পাসকি ব্যবহার করুন', blockButton__password: 'আপনার পাসওয়ার্ড দিয়ে চালিয়ে যান', blockButton__phoneCode: '{{identifier}}-এ এসএমএস কোড পাঠান', @@ -1287,6 +1288,29 @@ export const bnIN: LocalizationResource = { subtitle: 'চালিয়ে যেতে আপনার ইমেইলে পাঠানো কোড লিখুন', title: 'যাচাইকরণ প্রয়োজন', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'যাচাইকরণ চালিয়ে যাওয়া যাচ্ছে না। কোনো উপযুক্ত অথেনটিকেশন ফ্যাক্টর কনফিগার করা নেই', subtitle: 'একটি ত্রুটি ঘটেছে', diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index d761a145fb1..08e0392528d 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -1265,6 +1265,7 @@ export const caES: LocalizationResource = { actionText: 'No tens accés a aquest mètode? Prova una altra opció.', blockButton__backupCode: 'Utilitzar codi de seguretat', blockButton__emailCode: 'Utilitzar codi de correu electrònic', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Utilitzar contrasenya', blockButton__phoneCode: 'Utilitzar codi de telèfon', @@ -1288,6 +1289,29 @@ export const caES: LocalizationResource = { subtitle: "Comprova el codi de verificació a la teva bústia d'entrada.", title: 'Verificació per correu electrònic', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Ho sentim, no tens cap mètode de verificació disponible. Contacta amb suport.', subtitle: "No s'han trobat mètodes alternatius disponibles.", diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 3c3b8dc5443..a56883f71e6 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -1262,6 +1262,7 @@ export const csCZ: LocalizationResource = { actionText: 'Nemáte žádnou z těchto možností?', blockButton__backupCode: 'Použít záložní kód', blockButton__emailCode: 'Odeslat kód na e-mail {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Použít váš přístupový klíč', blockButton__password: 'Pokračovat s vaším heslem', blockButton__phoneCode: 'Odeslat SMS kód na {{identifier}}', @@ -1285,6 +1286,29 @@ export const csCZ: LocalizationResource = { subtitle: 'Zadejte kód odeslaný na váš e-mail pro pokračování', title: 'Vyžadováno ověření', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nelze pokračovat s ověřením. Není nakonfigurován žádný vhodný autentizační faktor', subtitle: 'Došlo k chybě', diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 92936019d80..6813b8fe091 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -1256,6 +1256,7 @@ export const daDK: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1278,6 +1279,29 @@ export const daDK: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 07f932898cf..71cd89bdf08 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -1271,6 +1271,7 @@ export const deDE: LocalizationResource = { actionText: 'Verwenden Sie eine alternative Verifizierungsmethode', blockButton__backupCode: 'Mit Wiederherstellungscode verifizieren', blockButton__emailCode: 'Mit E-Mail-Code verifizieren', + blockButton__emailLink: undefined, blockButton__passkey: 'Verwenden Sie Ihren Passkey', blockButton__password: 'Mit Passwort verifizieren', blockButton__phoneCode: 'Mit SMS-Code verifizieren', @@ -1294,6 +1295,29 @@ export const deDE: LocalizationResource = { subtitle: 'Überprüfen Sie Ihre E-Mail auf den Verifizierungscode.', title: 'E-Mail-Code Verifizierung', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Es sind keine Verifizierungsmethoden mehr verfügbar.', subtitle: 'Bitte kontaktieren Sie den Support, um Hilfe zu erhalten.', diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 02c1d49ec2e..7d867a25ffa 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -1261,6 +1261,7 @@ export const elGR: LocalizationResource = { actionText: 'Δεν έχετε κάποιο από αυτά;', blockButton__backupCode: 'Χρησιμοποιήστε εφεδρικό κωδικό', blockButton__emailCode: 'Αποστολή κωδικού στο {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Χρησιμοποιήστε το passkey σας', blockButton__password: 'Σύνδεση με τον κωδικό πρόσβασής σας', blockButton__phoneCode: 'Αποστολή κωδικού μέσω SMS στο {{identifier}}', @@ -1285,6 +1286,29 @@ export const elGR: LocalizationResource = { subtitle: 'για να συνεχίσετε', title: 'Ελέγξτε το email σας', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Δεν είναι δυνατή η συνέχεια. Δεν υπάρχει διαθέσιμος παράγοντας επαλήθευσης.', subtitle: 'Παρουσιάστηκε σφάλμα', diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 33071fdb142..ff61e104e50 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -1256,6 +1256,7 @@ export const enGB: LocalizationResource = { actionText: 'Don’t have any of these?', blockButton__backupCode: 'Use a backup code', blockButton__emailCode: 'Email code to {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Continue with your password', blockButton__phoneCode: 'Send SMS code to {{identifier}}', @@ -1279,6 +1280,29 @@ export const enGB: LocalizationResource = { subtitle: 'Enter the code sent to your email to continue', title: 'Verification required', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Cannot proceed with verification. No suitable authentication factor is configured', subtitle: 'An error occurred', diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index baeb439a0ac..8afea01a5b5 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -1261,6 +1261,7 @@ export const esCR: LocalizationResource = { actionText: '¿No posees ninguno de estos?', blockButton__backupCode: 'Utiliza un código de respaldo', blockButton__emailCode: 'Envía el código a {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Utiliza tu llave de acceso', blockButton__password: 'Continúa con tu contraseña', blockButton__phoneCode: 'Enviar un mensaje de texto a {{identifier}}', @@ -1283,6 +1284,29 @@ export const esCR: LocalizationResource = { subtitle: 'para continuar con {{applicationName}}', title: 'Revisa tu correo electrónico', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'No se puede proceder con el inicio de sesión. No hay ningún factor de autenticación disponible', subtitle: 'Ocurrió un error', diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index e4ecf17e7ee..3b3ed1412dd 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -1266,6 +1266,7 @@ export const esES: LocalizationResource = { actionText: '¿No tienes acceso a este método? Prueba otra opción.', blockButton__backupCode: 'Usar código de respaldo', blockButton__emailCode: 'Usar código de correo electrónico', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Usar contraseña', blockButton__phoneCode: 'Usar código de teléfono', @@ -1289,6 +1290,29 @@ export const esES: LocalizationResource = { subtitle: 'Revisa tu bandeja de entrada para el código de verificación.', title: 'Verificación por correo electrónico', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Lo sentimos, no tienes ningún método de verificación disponible. Contacta con soporte.', subtitle: 'No se encontraron métodos alternativos disponibles.', diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index 09d0fc1e04e..0cf28ddbbc5 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -1262,6 +1262,7 @@ export const esMX: LocalizationResource = { actionText: '¿No posees ninguno de estos?', blockButton__backupCode: 'Utiliza un código de respaldo', blockButton__emailCode: 'Envía el código a {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Utiliza tu llave de acceso', blockButton__password: 'Continúa con tu contraseña', blockButton__phoneCode: 'Enviar un mensaje de texto a {{identifier}}', @@ -1284,6 +1285,29 @@ export const esMX: LocalizationResource = { subtitle: 'para continuar con {{applicationName}}', title: 'Revisa tu correo electrónico', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'No se puede proceder con el inicio de sesión. No hay ningún factor de autenticación disponible', subtitle: 'Ocurrió un error', diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 6a799bdf3c5..fa2c6beb969 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -1260,6 +1260,7 @@ export const esUY: LocalizationResource = { actionText: '¿No tenés ninguno de estos?', blockButton__backupCode: 'Usar un código de respaldo', blockButton__emailCode: 'Enviar código por correo a {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Continuar con tu contraseña', blockButton__phoneCode: 'Enviar código SMS a {{identifier}}', @@ -1283,6 +1284,29 @@ export const esUY: LocalizationResource = { subtitle: 'Ingresá el código enviado a tu correo para continuar', title: 'Verificación requerida', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'No se puede proceder con la verificación. No hay un factor de autenticación adecuado configurado', subtitle: 'Ocurrió un error', diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 86beba5c4c9..c647baf31ee 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -1264,6 +1264,7 @@ export const faIR: LocalizationResource = { actionText: 'هیچ کدام از اینها را ندارید؟', blockButton__backupCode: 'از کد پشتیبان استفاده کنید', blockButton__emailCode: 'کد را به {{identifier}} ایمیل کنید', + blockButton__emailLink: undefined, blockButton__passkey: 'از کلید عبور خود استفاده کنید', blockButton__password: 'با رمز عبور خود ادامه دهید', blockButton__phoneCode: 'ارسال کد پیامکی به {{identifier}}', @@ -1287,6 +1288,29 @@ export const faIR: LocalizationResource = { subtitle: 'برای ادامه، کد ارسال شده به ایمیل خود را وارد کنید', title: 'تایید هویت الزامی است', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'امکان ادامه تأیید وجود ندارد. هیچ عامل احراز هویت مناسبی پیکربندی نشده است.', subtitle: 'خطایی رخ داد', diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index b1a1b5094d2..34b8652233a 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -1266,6 +1266,7 @@ export const fiFI: LocalizationResource = { actionText: 'Eikö mikään näistä ole käytettävissä?', blockButton__backupCode: 'Käytä varakoodia', blockButton__emailCode: 'Lähetä koodi sähköpostitse {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Käytä pääsyavaintasi', blockButton__password: 'Jatka salasanallasi', blockButton__phoneCode: 'Lähetä SMS-koodi {{identifier}}', @@ -1289,6 +1290,29 @@ export const fiFI: LocalizationResource = { subtitle: 'Syötä sähköpostiisi lähetetty koodi jatkaaksesi', title: 'Vahvistus vaaditaan', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Vahvistusta ei voida suorittaa. Sopivaa todennusmenetelmää ei ole määritetty.', subtitle: 'Tapahtui virhe', diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 583b57611f8..2648ad67383 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -1272,6 +1272,7 @@ export const frFR: LocalizationResource = { actionText: 'Vous ne pouvez pas accéder à votre compte ?', blockButton__backupCode: 'Utiliser un code de récupération', blockButton__emailCode: 'Recevoir un code par e-mail', + blockButton__emailLink: undefined, blockButton__passkey: 'Utiliser une clé de sécurité', blockButton__password: 'Utiliser le mot de passe', blockButton__phoneCode: 'Recevoir un code par téléphone', @@ -1295,6 +1296,29 @@ export const frFR: LocalizationResource = { subtitle: 'Un code a été envoyé à votre adresse e-mail.', title: 'Vérification par e-mail', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: "Aucune méthode de vérification n'est disponible.", subtitle: 'Impossible de procéder à la vérification.', diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index ae7a88af420..66d8444735a 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -1251,6 +1251,7 @@ export const heIL: LocalizationResource = { actionText: 'אין לך אף אחד מאלה?', blockButton__backupCode: 'השתמש בקוד גיבוי', blockButton__emailCode: 'קוד אימייל ל {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'המשך עם הסיסמה שלך', blockButton__phoneCode: 'שלח קוד SMS ל {{identifier}}', @@ -1273,6 +1274,29 @@ export const heIL: LocalizationResource = { subtitle: 'המשך ל {{applicationName}}', title: 'בדוק את האימייל שלך', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'לא ניתן להמשיך עם האימות. אין גורם אימות זמין', subtitle: 'קרתה תקלה', diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 9d04a365a23..654e95fad32 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -1264,6 +1264,7 @@ export const hiIN: LocalizationResource = { actionText: 'इनमें से कोई भी नहीं है?', blockButton__backupCode: 'बैकअप कोड का उपयोग करें', blockButton__emailCode: '{{identifier}} पर ईमेल कोड', + blockButton__emailLink: undefined, blockButton__passkey: 'अपनी पासकी का उपयोग करें', blockButton__password: 'अपने पासवर्ड के साथ जारी रखें', blockButton__phoneCode: '{{identifier}} पर SMS कोड भेजें', @@ -1287,6 +1288,29 @@ export const hiIN: LocalizationResource = { subtitle: 'जारी रखने के लिए अपने ईमेल पर भेजे गए कोड को दर्ज करें', title: 'सत्यापन आवश्यक है', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'सत्यापन जारी नहीं रख सकते। कोई उपयुक्त प्रमाणीकरण कारक कॉन्फ़िगर नहीं है', subtitle: 'एक त्रुटि हुई', diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index 34b0d179df7..e41604b1d43 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -1265,6 +1265,7 @@ export const hrHR: LocalizationResource = { actionText: 'Nemate ništa od ovoga?', blockButton__backupCode: 'Koristite rezervni kod', blockButton__emailCode: 'Pošalji kod e-poštom na {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Koristite svoj pristupni ključ', blockButton__password: 'Nastavite s vašom lozinkom', blockButton__phoneCode: 'Pošalji SMS kod na {{identifier}}', @@ -1288,6 +1289,29 @@ export const hrHR: LocalizationResource = { subtitle: 'Unesite kod poslan na vašu e-poštu za nastavak', title: 'Potrebna verifikacija', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Ne može se nastaviti s verifikacijom. Nema dostupnog faktora autentifikacije.', subtitle: 'Došlo je do pogreške', diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 56540481844..64ddc046428 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -1267,6 +1267,7 @@ export const huHU: LocalizationResource = { actionText: 'Nincs ezekből egyik sem?', blockButton__backupCode: 'Tartalék kód használata', blockButton__emailCode: 'Email kód küldése: {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Passkey használata', blockButton__password: 'Folytatás jelszóval', blockButton__phoneCode: 'SMS kód küldése: {{identifier}}', @@ -1290,6 +1291,29 @@ export const huHU: LocalizationResource = { subtitle: 'Írd be az e-mail címedre küldött kódot a folytatáshoz', title: 'Ellenőrzés szükséges', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nem lehet folytatni az ellenőrzést. Nincs konfigurált hitelesítési módszer.', subtitle: 'Hiba történt', diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index a66d1a4b145..4d9fd3d7a9a 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -1259,6 +1259,7 @@ export const idID: LocalizationResource = { actionText: 'Tidak memiliki salah satu dari ini?', blockButton__backupCode: 'Gunakan kode cadangan', blockButton__emailCode: 'Kirim kode ke {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Lanjutkan dengan kata sandi Anda', blockButton__phoneCode: 'Kirim kode SMS ke {{identifier}}', @@ -1282,6 +1283,29 @@ export const idID: LocalizationResource = { subtitle: 'Masukkan kode yang dikirim ke email Anda untuk melanjutkan', title: 'Verifikasi diperlukan', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Tidak dapat melanjutkan verifikasi. Tidak ada faktor autentikasi yang sesuai dikonfigurasi', subtitle: 'Terjadi kesalahan', diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 1fc6b0994ea..a886570e7cf 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -1266,6 +1266,7 @@ export const isIS: LocalizationResource = { actionText: 'Ertu ekki með neitt af þessu?', blockButton__backupCode: 'Nota öryggiskóða', blockButton__emailCode: 'Senda kóða á {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Nota lykilinn þinn', blockButton__password: 'Halda áfram með lykilorði', blockButton__phoneCode: 'Senda SMS kóða á {{identifier}}', @@ -1289,6 +1290,29 @@ export const isIS: LocalizationResource = { subtitle: 'Sláðu inn kóðann sem sendur var á netfangið þitt til að halda áfram', title: 'Staðfesting nauðsynleg', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Ekki er hægt að halda áfram með staðfestingu. Engin viðeigandi auðkenningaraðferð er stillt.', subtitle: 'Villa kom upp', diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 4c20d26e3c1..ef800feb3d8 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -1266,6 +1266,7 @@ export const itIT: LocalizationResource = { actionText: 'Usa un metodo di verifica alternativo', blockButton__backupCode: 'Verifica con il codice di backup', blockButton__emailCode: 'Verifica con il codice via email', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Verifica con la password', blockButton__phoneCode: 'Verifica con il codice SMS', @@ -1288,6 +1289,29 @@ export const itIT: LocalizationResource = { subtitle: 'Controlla la tua email per il codice di verifica.', title: 'Verifica con il codice email', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Non sono disponibili metodi di verifica.', subtitle: 'Contatta il supporto per assistenza.', diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 81068f420aa..74bf1bf8bc0 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -1265,6 +1265,7 @@ export const jaJP: LocalizationResource = { actionText: 'これらのいずれもお持ちでないですか?', blockButton__backupCode: 'バックアップコードを使用する', blockButton__emailCode: '{{identifier}} にメールコードを送信', + blockButton__emailLink: undefined, blockButton__passkey: 'パスキーを使用する', blockButton__password: 'パスワードで続行', blockButton__phoneCode: '{{identifier}} にSMSコードを送信', @@ -1288,6 +1289,29 @@ export const jaJP: LocalizationResource = { subtitle: '続行するには、メールに送信されたコードを入力してください', title: '確認が必要です', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: '確認を続行できません。利用可能な認証要素が設定されていません。', subtitle: 'エラーが発生しました', diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index ba47ffd33de..3cac3d8de1e 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -1249,6 +1249,7 @@ export const kkKZ: LocalizationResource = { actionText: 'Бұл опциялар жоқ па?', blockButton__backupCode: 'Сақтық кодын қолдану', blockButton__emailCode: '{{identifier}} электрондық поштасына код жіберу', + blockButton__emailLink: undefined, blockButton__passkey: 'Passkey қолдану', blockButton__password: 'Құпия сөзбен жалғастыру', blockButton__phoneCode: '{{identifier}} нөміріне SMS жіберу', @@ -1271,6 +1272,29 @@ export const kkKZ: LocalizationResource = { subtitle: 'Жалғастыру үшін электрондық поштаңызға жіберілген кодты енгізіңіз', title: 'Растау қажет', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Растау мүмкін емес. Ешбір аутентификация әдісі қосылмаған.', subtitle: 'Қате орын алды', diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 6fc94c87f5f..259fa2ac388 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -1255,6 +1255,7 @@ export const koKR: LocalizationResource = { actionText: '이 방법이 없나요?', blockButton__backupCode: '백업 코드 사용', blockButton__emailCode: '{{identifier}}로 이메일 코드 보내기', + blockButton__emailLink: undefined, blockButton__passkey: '패스키 사용하기', blockButton__password: '비밀번호로 계속', blockButton__phoneCode: '{{identifier}}로 SMS 코드 보내기', @@ -1277,6 +1278,29 @@ export const koKR: LocalizationResource = { subtitle: '이메일로 전송된 코드를 입력해 주세요', title: '인증이 필요해요', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: '인증을 진행할 수 없어요. 설정된 인증 수단이 없어요.', subtitle: '오류가 발생했어요', diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index ad8fcf996f1..cc9d8cf0fb4 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -1259,6 +1259,7 @@ export const mnMN: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1281,6 +1282,29 @@ export const mnMN: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 092c1db2245..d00dfc358a4 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -1268,6 +1268,7 @@ export const msMY: LocalizationResource = { actionText: 'Tidak mempunyai mana-mana ini?', blockButton__backupCode: 'Gunakan kod sandaran', blockButton__emailCode: 'E-mel kod ke {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Gunakan kunci pas anda', blockButton__password: 'Teruskan dengan kata laluan anda', blockButton__phoneCode: 'Hantar kod SMS ke {{identifier}}', @@ -1291,6 +1292,29 @@ export const msMY: LocalizationResource = { subtitle: 'Masukkan kod yang dihantar ke e-mel anda untuk meneruskan', title: 'Pengesahan diperlukan', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Tidak dapat meneruskan pengesahan. Tiada faktor pengesahan yang sesuai dikonfigurasi', subtitle: 'Ralat berlaku', diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 475ca454fd7..a345c3cb077 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -1267,6 +1267,7 @@ export const nbNO: LocalizationResource = { actionText: 'Har du ingen av disse?', blockButton__backupCode: 'Bruk en sikkerhetskode', blockButton__emailCode: 'Send e-postkode til {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Bruk passnøkkelen din', blockButton__password: 'Fortsett med passordet ditt', blockButton__phoneCode: 'Send SMS-kode til {{identifier}}', @@ -1290,6 +1291,29 @@ export const nbNO: LocalizationResource = { subtitle: 'Skriv inn koden sendt til e-posten din for å fortsette', title: 'Verifisering påkrevd', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Kan ikke fortsette med verifiseringen. Ingen passende autentiseringsfaktor er konfigurert', subtitle: 'En feil oppstod', diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 1f2f19eacea..e35010dc60e 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -1259,6 +1259,7 @@ export const nlBE: LocalizationResource = { actionText: 'Heb je geen van deze?', blockButton__backupCode: 'Backupcode gebruiken', blockButton__emailCode: 'Email code naar {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Doorgaan met je wachtwoord', blockButton__phoneCode: 'Verzend SMS code naar {{identifier}}', @@ -1282,6 +1283,29 @@ export const nlBE: LocalizationResource = { subtitle: 'om door te gaan naar {{applicationName}}', title: 'Controleer je email', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Kan niet verder gaan met verificatie. Er is geen beschikbare verificatiefactor.', subtitle: 'Er is een fout opgetreden', diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 00c4e01265b..b5342c56757 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -1259,6 +1259,7 @@ export const nlNL: LocalizationResource = { actionText: 'Heb je geen van deze?', blockButton__backupCode: 'Backupcode gebruiken', blockButton__emailCode: 'Email code naar {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Doorgaan met je wachtwoord', blockButton__phoneCode: 'Verzend SMS code naar {{identifier}}', @@ -1282,6 +1283,29 @@ export const nlNL: LocalizationResource = { subtitle: 'om door te gaan naar {{applicationName}}', title: 'Controleer je email', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Kan niet verder gaan met verificatie. Er is geen beschikbare verificatiefactor.', subtitle: 'Er is een fout opgetreden', diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 9a0741b215a..5e66b642f3b 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -1257,6 +1257,7 @@ export const plPL: LocalizationResource = { actionText: 'Nie używasz żadnej z tych metod?', blockButton__backupCode: 'Użyj kodu zapasowego', blockButton__emailCode: 'Wyślij kod e-mailem do {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Zaloguj się za pomocą hasła', blockButton__phoneCode: 'Wyślij kod SMS-em do {{identifier}}', @@ -1280,6 +1281,29 @@ export const plPL: LocalizationResource = { subtitle: 'Wprowadź kod wysłany na Twój adres e-mail, aby kontynuować', title: 'Wymagana weryfikacja', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nie można kontynuować weryfikacji. Brak dostępnych czynników uwierzytelniania.', subtitle: 'Wystąpił błąd', diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 9ae09c0eabd..55c418fc595 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -1267,6 +1267,7 @@ export const ptBR: LocalizationResource = { actionText: 'Não tem nenhum dos métodos? Tente outra forma.', blockButton__backupCode: 'Usar código de backup', blockButton__emailCode: 'Enviar código para {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Usar sua chave de acesso', blockButton__password: 'Usar senha', blockButton__phoneCode: 'Enviar código de telefone', @@ -1289,6 +1290,29 @@ export const ptBR: LocalizationResource = { subtitle: 'Verifique seu e-mail e insira o código para continuar.', title: 'Verifique seu e-mail', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nenhum método de verificação disponível. Entre em contato com o suporte.', subtitle: 'Não há métodos de verificação disponíveis no momento.', diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 8e370687ade..58636fa3417 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -1267,6 +1267,7 @@ export const ptPT: LocalizationResource = { actionText: 'Não tem nenhum destes métodos?', blockButton__backupCode: 'Utilizar um código de recuperação', blockButton__emailCode: 'Código por e-mail para {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Utilizar a sua chave de acesso', blockButton__password: 'Continuar com a sua palavra-passe', blockButton__phoneCode: 'Enviar código SMS para {{identifier}}', @@ -1290,6 +1291,29 @@ export const ptPT: LocalizationResource = { subtitle: 'Introduza o código enviado para o seu e-mail para continuar', title: 'Verificação necessária', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Não é possível prosseguir com a verificação. Não existe um fator de autenticação adequado configurado.', subtitle: 'Ocorreu um erro', diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index f8cccc3e9f8..38387d7e997 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -1267,6 +1267,7 @@ export const roRO: LocalizationResource = { actionText: 'Nu ai niciuna dintre acestea?', blockButton__backupCode: 'Folosește un cod de rezervă', blockButton__emailCode: 'Trimite cod pe email către {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Folosește cheia de acces', blockButton__password: 'Continuă cu parola', blockButton__phoneCode: 'Trimite cod prin SMS la {{identifier}}', @@ -1290,6 +1291,29 @@ export const roRO: LocalizationResource = { subtitle: 'Introdu codul trimis pe email pentru a continua', title: 'Verificare necesară', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nu se poate continua. Nu există un factor de autentificare configurat.', subtitle: 'A apărut o eroare', diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 32b4035047c..167b4ed689b 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -1264,6 +1264,7 @@ export const ruRU: LocalizationResource = { actionText: 'У вас нет ничего из этого?', blockButton__backupCode: 'Использовать резервный код', blockButton__emailCode: 'Отправить код на {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Продолжить с вашим паролем', blockButton__phoneCode: 'Отправить SMS код на {{identifier}}', @@ -1287,6 +1288,29 @@ export const ruRU: LocalizationResource = { subtitle: 'для продолжения {{applicationName}}', title: 'Проверьте вашу почту', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Невозможно продолжить верификацию. Нет доступного фактора аутентификации.', subtitle: 'Произошла ошибка', diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index dc36b79e866..0f47900e19c 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -1257,6 +1257,7 @@ export const skSK: LocalizationResource = { actionText: 'Nemáte prístup k žiadnej z týchto metód?', blockButton__backupCode: 'Použiť záložný kód', blockButton__emailCode: 'Odoslať overovací kód na email {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Použiť Passkey', blockButton__password: 'Pokračovať pomocou hesla', blockButton__phoneCode: 'Poslať SMS kód na telefónne číslo {{identifier}}', @@ -1280,6 +1281,29 @@ export const skSK: LocalizationResource = { subtitle: 'Pre pokračovanie vložte kód odoslaný na váš email', title: 'Vyžaduje sa overenie', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Nemožno pokračovať v overení. Nie je nastavená žiadna dostupná autentifikačná metóda.', subtitle: 'Došlo k chybe', diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 157efee229b..92d0d10a8b4 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -1256,6 +1256,7 @@ export const srRS: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1278,6 +1279,29 @@ export const srRS: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 747357f7ef7..905231824af 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -1257,6 +1257,7 @@ export const svSE: LocalizationResource = { actionText: 'Har du inget av dessa?', blockButton__backupCode: 'Använd en reservkod', blockButton__emailCode: 'Skicka kod via e-post till {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Fortsätt med ditt lösenord', blockButton__phoneCode: 'Skicka SMS-kod till {{identifier}}', @@ -1280,6 +1281,29 @@ export const svSE: LocalizationResource = { subtitle: 'för att fortsätta till {{applicationName}}', title: 'Kontrollera din e-post', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Kan inte fortsätta med verifieringen. Det finns ingen tillgänglig autentiseringsfaktor.', subtitle: 'Ett fel inträffade', diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 58024742b32..a811ac9789a 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -1270,6 +1270,7 @@ export const taIN: LocalizationResource = { actionText: 'இவற்றில் எதுவும் இல்லையா?', blockButton__backupCode: 'காப்புக் குறியீட்டைப் பயன்படுத்தவும்', blockButton__emailCode: '{{identifier}} க்கு மின்னஞ்சல் குறியீடு', + blockButton__emailLink: undefined, blockButton__passkey: 'உங்கள் பாஸ்கீயைப் பயன்படுத்தவும்', blockButton__password: 'உங்கள் கடவுச்சொல்லுடன் தொடரவும்', blockButton__phoneCode: '{{identifier}} க்கு SMS குறியீடு அனுப்பவும்', @@ -1293,6 +1294,29 @@ export const taIN: LocalizationResource = { subtitle: 'தொடர உங்கள் மின்னஞ்சலுக்கு அனுப்பப்பட்ட குறியீட்டை உள்ளிடவும்', title: 'சரிபார்ப்பு தேவை', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'சரிபார்ப்பைத் தொடர முடியாது. பொருத்தமான அங்கீகார காரணி எதுவும் கட்டமைக்கப்படவில்லை', subtitle: 'பிழை ஏற்பட்டது', diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 1dcefe67386..58b862a7ecf 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -1267,6 +1267,7 @@ export const teIN: LocalizationResource = { actionText: 'వీటిలో ఏవీ లేవా?', blockButton__backupCode: 'బ్యాకప్ కోడ్‌ను ఉపయోగించండి', blockButton__emailCode: '{{identifier}}కి ఇమెయిల్ కోడ్', + blockButton__emailLink: undefined, blockButton__passkey: 'మీ పాస్‌కీని ఉపయోగించండి', blockButton__password: 'మీ పాస్‌వర్డ్‌తో కొనసాగించండి', blockButton__phoneCode: '{{identifier}}కి SMS కోడ్‌ను పంపండి', @@ -1290,6 +1291,29 @@ export const teIN: LocalizationResource = { subtitle: 'కొనసాగించడానికి మీ ఇమెయిల్‌కి పంపిన కోడ్‌ను నమోదు చేయండి', title: 'ధృవీకరణ అవసరం', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'ధృవీకరణతో కొనసాగలేము. సరైన ప్రమాణీకరణ కారకం కాన్ఫిగర్ చేయబడలేదు', subtitle: 'లోపం సంభవించింది', diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index e67007a7c1e..ad2cccd21d3 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -1257,6 +1257,7 @@ export const thTH: LocalizationResource = { actionText: 'ไม่สามารถเข้าถึงวิธีเหล่านี้?', blockButton__backupCode: 'ใช้รหัสสำรอง', blockButton__emailCode: 'ส่งรหัสทางอีเมลไปยัง {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'ใช้พาสคีย์ของคุณ', blockButton__password: 'ดำเนินการต่อด้วยรหัสผ่านของคุณ', blockButton__phoneCode: 'ส่งรหัส SMS ไปยัง {{identifier}}', @@ -1280,6 +1281,29 @@ export const thTH: LocalizationResource = { subtitle: 'ใส่รหัสที่ส่งไปยังอีเมลของคุณเพื่อดำเนินการต่อ', title: 'ต้องการการยืนยัน', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'ไม่สามารถดำเนินการยืนยันได้ ไม่มีปัจจัยการยืนยันตัวตนที่เหมาะสมได้รับการกำหนดค่า', subtitle: 'เกิดข้อผิดพลาด', diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index b95612c1e19..70e98877429 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -1257,6 +1257,7 @@ export const trTR: LocalizationResource = { actionText: 'Alternatif doğrulama yöntemlerini kullanmak ister misiniz?', blockButton__backupCode: 'Yedek kodu kullan', blockButton__emailCode: 'E-posta kodu gönder', + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: 'Şifreyi gir', blockButton__phoneCode: 'Telefon kodu gönder', @@ -1279,6 +1280,29 @@ export const trTR: LocalizationResource = { subtitle: 'E-posta adresinize gönderilen doğrulama kodunu girin.', title: 'E-posta Kodunu Girin', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Alternatif doğrulama yöntemleri mevcut değil.', subtitle: 'Lütfen farklı bir doğrulama yöntemi deneyin.', diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 3f982bc72b8..6cfd8080dc7 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -1256,6 +1256,7 @@ export const ukUA: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1278,6 +1279,29 @@ export const ukUA: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index d848ef3838f..d5f9db820dc 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -1264,6 +1264,7 @@ export const viVN: LocalizationResource = { actionText: 'Không có bất kỳ phương thức nào?', blockButton__backupCode: 'Sử dụng mã dự phòng', blockButton__emailCode: 'Email mã đến {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: 'Đăng nhập với mã passkey', blockButton__password: 'Tiếp tục với mật khẩu của bạn', blockButton__phoneCode: 'Gửi mã SMS đến {{identifier}}', @@ -1287,6 +1288,29 @@ export const viVN: LocalizationResource = { subtitle: 'Nhập mã đã gửi đến email của bạn để tiếp tục', title: 'Xác minh yêu cầu', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: 'Không thể tiếp tục với xác minh. Không có yếu tố xác thực phù hợp được cấu hình', subtitle: 'Đã xảy ra lỗi', diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index dfbf68c8f6b..6d0bbd2b880 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -1247,6 +1247,7 @@ export const zhCN: LocalizationResource = { actionText: undefined, blockButton__backupCode: undefined, blockButton__emailCode: undefined, + blockButton__emailLink: undefined, blockButton__passkey: undefined, blockButton__password: undefined, blockButton__phoneCode: undefined, @@ -1269,6 +1270,29 @@ export const zhCN: LocalizationResource = { subtitle: undefined, title: undefined, }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: undefined, subtitle: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 6ca3ead8138..48f81303556 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -1250,6 +1250,7 @@ export const zhTW: LocalizationResource = { actionText: '沒有以上任何一種方式嗎?', blockButton__backupCode: '使用備用碼', blockButton__emailCode: '傳送驗證碼至 {{identifier}}', + blockButton__emailLink: undefined, blockButton__passkey: '使用您的金鑰', blockButton__password: '使用您的密碼登入', blockButton__phoneCode: '傳送簡訊代碼至 {{identifier}}', @@ -1272,6 +1273,29 @@ export const zhTW: LocalizationResource = { subtitle: '請輸入已寄至您電子郵件的驗證碼以繼續', title: '需要驗證您的身分', }, + emailLink: { + clientMismatch: { + subtitle: undefined, + title: undefined, + }, + expired: { + subtitle: undefined, + title: undefined, + }, + failed: { + subtitle: undefined, + title: undefined, + }, + formSubtitle: undefined, + formTitle: undefined, + resendButton: undefined, + subtitle: undefined, + title: undefined, + verified: { + subtitle: undefined, + title: undefined, + }, + }, noAvailableMethods: { message: '無法繼續驗證。沒有可用的驗證方式。', subtitle: '發生錯誤', From c188e0f9fdba50d5c30d3985c32f30de8ba5b1eb Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 16:59:15 -0700 Subject: [PATCH 03/16] test(ui): add email-link reverification journey --- .../sign-in-or-up-email-links-flow.test.ts | 185 +++++++++++++----- .../UserVerificationEmailLinkVerify.test.tsx | 12 ++ 2 files changed, 149 insertions(+), 48 deletions(-) diff --git a/integration/tests/sign-in-or-up-email-links-flow.test.ts b/integration/tests/sign-in-or-up-email-links-flow.test.ts index cb4c5edb4c2..e0abd1a70e5 100644 --- a/integration/tests/sign-in-or-up-email-links-flow.test.ts +++ b/integration/tests/sign-in-or-up-email-links-flow.test.ts @@ -1,65 +1,154 @@ import { expect, test } from '@playwright/test'; +import { appConfigs } from '../presets'; import type { FakeUser } from '../testUtils'; import { createTestUtils, testAgainstRunningApps } from '../testUtils'; -testAgainstRunningApps({ withEnv: [] })('sign-in-or-up email links flow', ({ app }) => { - test.describe.configure({ mode: 'serial' }); +testAgainstRunningApps({ withEnv: [appConfigs.envs.withSignInOrUpEmailLinksFlow] })( + '@nextjs sign-in-or-up email links flow', + ({ app }) => { + test.describe.configure({ mode: 'serial' }); - let fakeUser: FakeUser; + let fakeUser: FakeUser; + let emailLinkOnlyUser: FakeUser; + let emailLinkOnlyAddress: string; - test.beforeAll(() => { - const u = createTestUtils({ app }); - fakeUser = u.services.users.createFakeUser(test); - }); + test.beforeAll(async () => { + const u = createTestUtils({ app }); + fakeUser = u.services.users.createFakeUser(test); + emailLinkOnlyUser = u.services.users.createFakeUser(test, { + fictionalEmail: false, + withPassword: false, + }); + if (!emailLinkOnlyUser.email) { + throw new Error('Expected the email-link-only test user to have an email address'); + } + emailLinkOnlyAddress = emailLinkOnlyUser.email; + await u.services.users.createBapiUser(emailLinkOnlyUser); + }); - test.afterAll(async () => { - await app.teardown(); - }); + test.afterAll(async () => { + try { + await Promise.all([fakeUser.deleteIfExists(), emailLinkOnlyUser.deleteIfExists()]); + } finally { + await app.teardown(); + } + }); - test('sign up with email link', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo(); - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/create'); + test('sign up with email link', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.page.waitForAppUrl('/sign-in/create'); - const prefilledEmail = u.po.signUp.getEmailAddressInput(); - await expect(prefilledEmail).toHaveValue(fakeUser.email); + const prefilledEmail = u.po.signUp.getEmailAddressInput(); + await expect(prefilledEmail).toHaveValue(fakeUser.email); - await u.po.signUp.setPassword(fakeUser.password); - await u.po.signUp.continue(); + await u.po.signUp.setPassword(fakeUser.password); + await u.po.signUp.continue(); - await u.po.signUp.waitForEmailVerificationScreen(); - await u.tabs.runInNewTab(async u => { - const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email); + await u.po.signUp.waitForEmailVerificationScreen(); + await u.tabs.runInNewTab(async u => { + const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email); - await u.po.testingToken.setup(); - await u.page.goto(verificationLink); + await u.po.testingToken.setup(); + await u.page.goto(verificationLink); + await u.po.expect.toBeSignedIn(); + await u.page.close(); + }); await u.po.expect.toBeSignedIn(); - await u.page.close(); }); - await u.po.expect.toBeSignedIn(); - }); - - test('sign in with email link', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo(); - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/factor-one'); - // Defaults to password, so we need to switch to email link - await u.page.getByRole('link', { name: /Use another method/i }).click(); - await u.page.getByRole('button', { name: /Email link to/i }).click(); - await page.getByRole('heading', { name: /Check your email/i }).waitFor(); - await u.tabs.runInNewTab(async u => { - const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email); - await u.po.testingToken.setup(); - await u.page.goto(verificationLink); + + test('sign in with email link', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.page.waitForAppUrl('/sign-in/factor-one'); + // Defaults to password, so we need to switch to email link + await u.page.getByRole('link', { name: /Use another method/i }).click(); + await u.page.getByRole('button', { name: /Email link to/i }).click(); + await page.getByRole('heading', { name: /Check your email/i }).waitFor(); + await u.tabs.runInNewTab(async u => { + const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email); + await u.po.testingToken.setup(); + await u.page.goto(verificationLink); + await u.po.expect.toBeSignedIn(); + await u.page.close(); + }); + await u.po.expect.toBeSignedIn(); + await fakeUser.deleteIfExists(); + }); + + test('completes an expired-freshness protected action through a same-browser email link', async ({ + page, + context, + browser, + }) => { + test.setTimeout(300_000); + const u = createTestUtils({ app, page, context, browser }); + + await u.po.signIn.goTo(); + await u.po.signIn.setIdentifier(emailLinkOnlyAddress); + await u.po.signIn.continue(); + await u.page.getByRole('heading', { name: /Check your email/i }).waitFor(); + + await u.tabs.runInNewTab(async callback => { + const verificationLink = await callback.services.email.getVerificationLinkForEmailAddress(emailLinkOnlyAddress); + await callback.po.testingToken.setup(); + await callback.page.goto(verificationLink); + await callback.po.expect.toBeSignedIn(); + await callback.page.close(); + }); await u.po.expect.toBeSignedIn(); - await u.page.close(); + + await expect + .poll( + () => + page.evaluate(async () => { + const token = await window.Clerk.session?.getToken({ skipCache: true }); + if (!token) { + return -1; + } + const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'); + return JSON.parse(atob(payload)).fva?.[0] ?? -1; + }), + { intervals: [5_000], timeout: 120_000 }, + ) + .toBeGreaterThanOrEqual(1); + + const returnPath = '/action-with-use-reverification?return=protected-action'; + await u.page.goToRelative(returnPath); + await u.page.getByRole('button', { name: /LogUserId/i }).click(); + await u.po.userVerification.waitForMounted(); + await u.page.getByRole('heading', { name: /Check your email/i }).waitFor(); + + const mismatchedClientLink = await u.services.email.getVerificationLinkForEmailAddress(emailLinkOnlyAddress); + await u.tabs.runInNewBrowser(async callback => { + await callback.po.testingToken.setup(); + await callback.page.goto(mismatchedClientLink); + await callback.page.getByRole('heading', { name: /Verification link is invalid for this browser/i }).waitFor(); + await callback.page.close(); + }); + + await expect(u.page).toHaveURL(new RegExp(`${returnPath.replace('?', '\\?')}$`)); + await u.page.getByRole('button', { name: /Resend/i }).click(); + + await u.tabs.runInNewTab(async callback => { + const verificationLink = await callback.services.email.getVerificationLinkForEmailAddress(emailLinkOnlyAddress); + await callback.po.testingToken.setup(); + await callback.page.goto(verificationLink); + await callback.page.getByRole('heading', { name: /Verification complete/i }).waitFor(); + const callbackUrl = new URL(callback.page.url()); + expect(callbackUrl.pathname).toBe('/action-with-use-reverification'); + expect(callbackUrl.searchParams.get('return')).toBe('protected-action'); + await callback.page.close(); + }); + + await u.po.userVerification.waitForClosed(); + await expect(u.page).toHaveURL(new RegExp(`${returnPath.replace('?', '\\?')}$`)); + await expect(u.page.getByText(/\{\s*"userId"\s*:\s*"user_[^"]+"\s*\}/i)).toBeVisible(); }); - await u.po.expect.toBeSignedIn(); - await fakeUser.deleteIfExists(); - }); -}); + }, +); diff --git a/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx b/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx index 63043451757..303c7726452 100644 --- a/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx +++ b/packages/ui/src/components/UserVerification/__tests__/UserVerificationEmailLinkVerify.test.tsx @@ -23,4 +23,16 @@ describe('UserVerificationEmailLinkVerify', () => { screen.getByText('Verification complete'); screen.getByText('Return to the original tab to continue.'); }); + + it('tells the user to request a new link when the verification link has expired', async () => { + window.history.replaceState({}, '', '/account/billing?__clerk_status=expired'); + const { wrapper } = await createFixtures(f => { + f.withUser({ username: 'clerkuser' }); + }); + + render(, { wrapper }); + + screen.getByText('This verification link has expired'); + screen.getByText('Return to the original tab and request a new link.'); + }); }); From 9511445c5f76b33aafbce34ea1c0c5c9048c7c0b Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 17:29:06 -0700 Subject: [PATCH 04/16] test(ui): harden email-link reverification coverage --- integration/testUtils/emailService.ts | 21 +++++++++---------- .../core/resources/__tests__/Session.test.ts | 11 +++++++++- .../__tests__/UVFactorOne.test.tsx | 9 +++++++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index c1cb085494d..21457a2b7f1 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -1,5 +1,3 @@ -import { runWithExponentialBackOff } from '@clerk/shared/utils'; - type Message = { _id: string; subject: string; @@ -23,8 +21,8 @@ export const createEmailService = () => { } // Retry in case the email delivery is delayed await new Promise(res => setTimeout(res, 1500)); - return runWithExponentialBackOff( - async () => { + for (let attempt = 0; attempt < 5; attempt++) { + try { const res = await fetcher(url); const json = (await res.json()) as unknown as { messages: Message[] }; const message = json.messages[0]; @@ -32,13 +30,14 @@ export const createEmailService = () => { throw new Error('message not found'); } return message; - }, - { - firstDelay: 750, - timeMultiple: 2, - shouldRetry: (_, iterationsCount) => iterationsCount < 5, - }, - ); + } catch (error) { + if (attempt === 4) { + throw error; + } + await new Promise(res => setTimeout(res, 750 * 2 ** attempt)); + } + } + throw new Error('message not found'); }; const getMessagePlaintextForAddress = async (email: string, id: string) => { 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 452ea43e5d2..a6c0e95fa56 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -2547,14 +2547,19 @@ describe('Session', () => { }); fetchSpy + .mockResolvedValueOnce(response('needs_first_factor', 'unverified') as any) .mockResolvedValueOnce(response('needs_first_factor', 'unverified') as any) .mockResolvedValueOnce(response('complete', 'verified') as any); const { startEmailLinkFlow } = session.createEmailLinkFlow(); - const result = await startEmailLinkFlow({ + const resultPromise = startEmailLinkFlow({ emailAddressId: 'idn_email', redirectUrl: 'https://app.example.com/protected-action', }); + await vi.advanceTimersByTimeAsync(0); + expect(fetchSpy).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(1_000); + const result = await resultPromise; expect(result.status).toBe('complete'); expect(result.firstFactorVerification.strategy).toBe('email_link'); @@ -2571,6 +2576,10 @@ describe('Session', () => { method: 'GET', path: '/client/sessions/session_1/verify', }); + expect(fetchSpy).toHaveBeenNthCalledWith(3, { + method: 'GET', + path: '/client/sessions/session_1/verify', + }); }); }); }); diff --git a/packages/ui/src/components/UserVerification/__tests__/UVFactorOne.test.tsx b/packages/ui/src/components/UserVerification/__tests__/UVFactorOne.test.tsx index b3234a002f0..2a559e61d6c 100644 --- a/packages/ui/src/components/UserVerification/__tests__/UVFactorOne.test.tsx +++ b/packages/ui/src/components/UserVerification/__tests__/UVFactorOne.test.tsx @@ -1,5 +1,5 @@ import { waitFor } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; import { render, screen } from '@/test/utils'; @@ -10,13 +10,20 @@ import { UserVerificationFactorOne } from '../UserVerificationFactorOne'; const { createFixtures } = bindCreateFixtures('UserVerification'); describe('UserVerificationFactorOne', () => { + let initialUrl: string; + /** * `` internally uses useFetch which caches the results, be sure to clear the cache before each test */ beforeEach(() => { + initialUrl = window.location.href; clearFetchCache(); }); + afterEach(() => { + window.history.replaceState({}, '', initialUrl); + }); + it('renders the component for with strategy:password', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); From 2ff2ff189adb2defb7f6ed1179f5d57b2fc90c09 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 17:40:52 -0700 Subject: [PATCH 05/16] fix(integration): handle email inbox response --- integration/testUtils/emailService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 21457a2b7f1..ba1ae6358e8 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -24,8 +24,9 @@ export const createEmailService = () => { for (let attempt = 0; attempt < 5; attempt++) { try { const res = await fetcher(url); - const json = (await res.json()) as unknown as { messages: Message[] }; - const message = json.messages[0]; + const json = (await res.json()) as unknown as Message[] | { messages?: Message[] }; + const messages = Array.isArray(json) ? json : (json.messages ?? []); + const message = messages[0]; if (!message) { throw new Error('message not found'); } From c0b1c0bc985815ebf1eb79fca281dadb550f0681 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 17:44:08 -0700 Subject: [PATCH 06/16] test(clerk-js): type reverification responses --- .../core/resources/__tests__/Session.test.ts | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) 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 a6c0e95fa56..dea00a0e4ed 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -1,5 +1,5 @@ import { ClerkAPIResponseError, ClerkOfflineError } from '@clerk/shared/error'; -import type { InstanceType, OrganizationJSON, SessionJSON } from '@clerk/shared/types'; +import type { InstanceType, OrganizationJSON, SessionJSON, SessionVerificationJSON } from '@clerk/shared/types'; import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { clerkMock, createSession, createUser, mockFetch, mockJwt, mockNetworkFailedFetch } from '@/test/core-fixtures'; @@ -2531,25 +2531,41 @@ describe('Session', () => { const fetchSpy = vi.spyOn(BaseResource, '_fetch'); const response = (status: 'needs_first_factor' | 'complete', verificationStatus: 'unverified' | 'verified') => ({ response: { + id: 'session_verification_1', object: 'session_verification', status, level: 'first_factor', session: sessionJSON, first_factor_verification: { + id: 'verification_1', object: 'verification_email_link', strategy: 'email_link', status: verificationStatus, + verified_at_client: verificationStatus === 'verified' ? 'client_1' : '', + attempts: 0, + expire_at: Date.now() / 1_000 + 600, + error: { code: '', message: '' }, }, second_factor_verification: null, - supported_first_factors: status === 'complete' ? null : [{ strategy: 'email_link' }], + supported_first_factors: + status === 'complete' + ? null + : [ + { + strategy: 'email_link', + email_address_id: 'idn_email', + safe_identifier: 'test@example.com', + primary: true, + }, + ], supported_second_factors: null, - }, + } satisfies SessionVerificationJSON, }); fetchSpy - .mockResolvedValueOnce(response('needs_first_factor', 'unverified') as any) - .mockResolvedValueOnce(response('needs_first_factor', 'unverified') as any) - .mockResolvedValueOnce(response('complete', 'verified') as any); + .mockResolvedValueOnce(response('needs_first_factor', 'unverified')) + .mockResolvedValueOnce(response('needs_first_factor', 'unverified')) + .mockResolvedValueOnce(response('complete', 'verified')); const { startEmailLinkFlow } = session.createEmailLinkFlow(); const resultPromise = startEmailLinkFlow({ From f7253cf2f4ed55949edc2c17ac6b7387bf37f954 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 17:46:57 -0700 Subject: [PATCH 07/16] test(integration): validate email messages --- integration/testUtils/emailService.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index ba1ae6358e8..e5e1a09259e 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -3,6 +3,17 @@ type Message = { subject: string; }; +const isMessage = (value: unknown): value is Message => { + return ( + typeof value === 'object' && + value !== null && + '_id' in value && + typeof value._id === 'string' && + 'subject' in value && + typeof value.subject === 'string' + ); +}; + export const createEmailService = () => { const cleanEmail = (email: string) => { return email.replace(/\+.*@/, '@'); @@ -24,10 +35,14 @@ export const createEmailService = () => { for (let attempt = 0; attempt < 5; attempt++) { try { const res = await fetcher(url); - const json = (await res.json()) as unknown as Message[] | { messages?: Message[] }; - const messages = Array.isArray(json) ? json : (json.messages ?? []); + const json: unknown = await res.json(); + const messages = Array.isArray(json) + ? json + : typeof json === 'object' && json !== null && 'messages' in json && Array.isArray(json.messages) + ? json.messages + : []; const message = messages[0]; - if (!message) { + if (!isMessage(message)) { throw new Error('message not found'); } return message; From 7d8369a780913b2ffe65af3184f40550b9e06573 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 21:40:03 -0700 Subject: [PATCH 08/16] test(integration): use deliverable email address --- integration/tests/sign-in-or-up-email-links-flow.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/tests/sign-in-or-up-email-links-flow.test.ts b/integration/tests/sign-in-or-up-email-links-flow.test.ts index e0abd1a70e5..7a1951f092e 100644 --- a/integration/tests/sign-in-or-up-email-links-flow.test.ts +++ b/integration/tests/sign-in-or-up-email-links-flow.test.ts @@ -15,7 +15,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSignInOrUpEmailLinksFlow] test.beforeAll(async () => { const u = createTestUtils({ app }); - fakeUser = u.services.users.createFakeUser(test); + fakeUser = u.services.users.createFakeUser(test, { fictionalEmail: false }); emailLinkOnlyUser = u.services.users.createFakeUser(test, { fictionalEmail: false, withPassword: false, From bb90b716252195d7b8dcdc5a2cf114bc1ffafa01 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 21:49:49 -0700 Subject: [PATCH 09/16] test(integration): match email links by address --- integration/testUtils/emailService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index e5e1a09259e..ccc83a40767 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -76,7 +76,7 @@ export const createEmailService = () => { return code; }, getVerificationLinkForEmailAddress: async (email: string) => { - const message = await filterMessagesByAddress(email, 'link'); + const message = await filterMessagesByAddress(email); const body = await getMessagePlaintextForAddress(email, message._id); const link = (body.match(/https:\/\/.*\/verify\?.*/) || [''])[0].trim().replace(/&/g, '&'); void deleteMessage(email, message._id); From 55e02ddade26b7aad321bf647230247ec4eb4cd0 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 22:01:42 -0700 Subject: [PATCH 10/16] test(integration): filter shared email inbox --- integration/testUtils/emailService.ts | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index ccc83a40767..97110ad62cc 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -1,6 +1,7 @@ type Message = { _id: string; subject: string; + to: Array<{ address: string }>; }; const isMessage = (value: unknown): value is Message => { @@ -10,7 +11,16 @@ const isMessage = (value: unknown): value is Message => { '_id' in value && typeof value._id === 'string' && 'subject' in value && - typeof value.subject === 'string' + typeof value.subject === 'string' && + 'to' in value && + Array.isArray(value.to) && + value.to.every( + recipient => + typeof recipient === 'object' && + recipient !== null && + 'address' in recipient && + typeof recipient.address === 'string', + ) ); }; @@ -25,11 +35,7 @@ export const createEmailService = () => { }; const filterMessagesByAddress = async (email: string, sub?: string) => { - const url = new URL('https://mailsac.com/api/inbox-filter'); - url.searchParams.set('andTo', email); - if (sub) { - url.searchParams.set('andSubjectIncludes', sub); - } + const url = new URL(`https://mailsac.com/api/addresses/${cleanEmail(email)}/messages`); // Retry in case the email delivery is delayed await new Promise(res => setTimeout(res, 1500)); for (let attempt = 0; attempt < 5; attempt++) { @@ -41,8 +47,15 @@ export const createEmailService = () => { : typeof json === 'object' && json !== null && 'messages' in json && Array.isArray(json.messages) ? json.messages : []; - const message = messages[0]; - if (!isMessage(message)) { + const normalizedEmail = email.toLowerCase(); + const normalizedSubject = sub?.toLowerCase(); + const message = messages.find( + value => + isMessage(value) && + value.to.some(recipient => recipient.address.toLowerCase() === normalizedEmail) && + (!normalizedSubject || value.subject.toLowerCase().includes(normalizedSubject)), + ); + if (!message) { throw new Error('message not found'); } return message; From ee44e0a0b7c4ad21d1a9b925641226bb5646af6c Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 22:13:15 -0700 Subject: [PATCH 11/16] test(integration): match redirected inbox address --- integration/testUtils/emailService.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 97110ad62cc..974a9fadd46 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -1,7 +1,7 @@ type Message = { _id: string; + originalInbox: string; subject: string; - to: Array<{ address: string }>; }; const isMessage = (value: unknown): value is Message => { @@ -10,17 +10,10 @@ const isMessage = (value: unknown): value is Message => { value !== null && '_id' in value && typeof value._id === 'string' && + 'originalInbox' in value && + typeof value.originalInbox === 'string' && 'subject' in value && - typeof value.subject === 'string' && - 'to' in value && - Array.isArray(value.to) && - value.to.every( - recipient => - typeof recipient === 'object' && - recipient !== null && - 'address' in recipient && - typeof recipient.address === 'string', - ) + typeof value.subject === 'string' ); }; @@ -52,7 +45,7 @@ export const createEmailService = () => { const message = messages.find( value => isMessage(value) && - value.to.some(recipient => recipient.address.toLowerCase() === normalizedEmail) && + value.originalInbox.toLowerCase() === normalizedEmail && (!normalizedSubject || value.subject.toLowerCase().includes(normalizedSubject)), ); if (!message) { From db608df4307b22d0ebfe32721eb8c6a230679bdd Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 22:28:21 -0700 Subject: [PATCH 12/16] test(integration): authenticate email inbox reads --- .github/workflows/ci.yml | 1 + .github/workflows/nightly-checks.yml | 1 + integration/.env.local.sample | 1 + integration/README.md | 1 + integration/testUtils/emailService.ts | 2 ++ 5 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 916932893e1..2ad42c86e4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -531,6 +531,7 @@ jobs: E2E_NEXTJS_VERSION: ${{ matrix.next-version }} E2E_PROJECT: ${{ matrix.test-project }} INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} + MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/integration/certs/rootCA.pem VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} diff --git a/.github/workflows/nightly-checks.yml b/.github/workflows/nightly-checks.yml index 61ad42c4b23..a5e63cc82d8 100644 --- a/.github/workflows/nightly-checks.yml +++ b/.github/workflows/nightly-checks.yml @@ -80,6 +80,7 @@ jobs: E2E_REACT_DOM_VERSION: "19.2.3" E2E_REACT_VERSION: "19.2.3" INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} + MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} # Print error logs for immediate visibility in CI - name: Print App Error Logs diff --git a/integration/.env.local.sample b/integration/.env.local.sample index 8fdb1f0151c..9505f3baa4c 100644 --- a/integration/.env.local.sample +++ b/integration/.env.local.sample @@ -1,3 +1,4 @@ +MAILSAC_API_KEY= VERCEL_PROJECT_ID= VERCEL_ORG_ID= VERCEL_TOKEN= diff --git a/integration/README.md b/integration/README.md index cc74296bf82..7a1fd3b89e5 100644 --- a/integration/README.md +++ b/integration/README.md @@ -628,6 +628,7 @@ Before writing tests, it's important to understand how Playwright handles test i > [!NOTE] > The test suite also uses these environment variables to run some tests: > +> - `MAILSAC_API_KEY`: Used for [Mailsac](https://mailsac.com/) to retrieve email codes and magic links from temporary email addresses. > - `VERCEL_PROJECT_ID`: Only required if you plan on running deployment tests locally. This is the Vercel project ID, and it points to an application created via the Vercel dashboard. The easiest way to get access to it is by linking a local app to the Vercel project using the Vercel CLI, and then copying the values from the `.vercel` directory. > - `VERCEL_ORG_ID`: The organization that owns the Vercel project. See above for more details. > - `VERCEL_TOKEN`: A personal access token. This corresponds to a real user running the deployment command. Attention: Be extra careful with this token as it can't be scoped to a single Vercel project, meaning that the token has access to every project in the account it belongs to. diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 974a9fadd46..fd7819b9787 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -24,6 +24,8 @@ export const createEmailService = () => { const fetcher = async (url: string | URL, init?: RequestInit) => { const headers = new Headers(init?.headers || {}); + // eslint-disable-next-line turbo/no-undeclared-env-vars + headers.set('Mailsac-Key', process.env.MAILSAC_API_KEY as string); return fetch(url, { ...init, headers }); }; From 4a1115e2ac9cddb0fb6222a5b00184ffadc5a517 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 23:17:29 -0700 Subject: [PATCH 13/16] test(integration): filter authenticated email inbox --- integration/testUtils/emailService.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index fd7819b9787..03484480cf1 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -1,21 +1,18 @@ type Message = { _id: string; - originalInbox: string; subject: string; }; -const isMessage = (value: unknown): value is Message => { +function isMessage(value: unknown): value is Message { return ( typeof value === 'object' && value !== null && '_id' in value && typeof value._id === 'string' && - 'originalInbox' in value && - typeof value.originalInbox === 'string' && 'subject' in value && typeof value.subject === 'string' ); -}; +} export const createEmailService = () => { const cleanEmail = (email: string) => { @@ -30,7 +27,11 @@ export const createEmailService = () => { }; const filterMessagesByAddress = async (email: string, sub?: string) => { - const url = new URL(`https://mailsac.com/api/addresses/${cleanEmail(email)}/messages`); + const url = new URL('https://mailsac.com/api/inbox-filter'); + url.searchParams.set('andTo', email); + if (sub) { + url.searchParams.set('andSubjectIncludes', sub); + } // Retry in case the email delivery is delayed await new Promise(res => setTimeout(res, 1500)); for (let attempt = 0; attempt < 5; attempt++) { @@ -42,14 +43,7 @@ export const createEmailService = () => { : typeof json === 'object' && json !== null && 'messages' in json && Array.isArray(json.messages) ? json.messages : []; - const normalizedEmail = email.toLowerCase(); - const normalizedSubject = sub?.toLowerCase(); - const message = messages.find( - value => - isMessage(value) && - value.originalInbox.toLowerCase() === normalizedEmail && - (!normalizedSubject || value.subject.toLowerCase().includes(normalizedSubject)), - ); + const message = messages.find(isMessage); if (!message) { throw new Error('message not found'); } From 0ebced6605056d231e9374c1f6c50104f4d99264 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 23:30:18 -0700 Subject: [PATCH 14/16] test(integration): wait for email delivery --- integration/testUtils/emailService.ts | 7 +++++-- integration/tests/sign-in-or-up-email-links-flow.test.ts | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 03484480cf1..087b552fbc1 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -34,9 +34,12 @@ export const createEmailService = () => { } // Retry in case the email delivery is delayed await new Promise(res => setTimeout(res, 1500)); - for (let attempt = 0; attempt < 5; attempt++) { + for (let attempt = 0; attempt < 7; attempt++) { try { const res = await fetcher(url); + if (!res.ok) { + throw new Error(`Email inbox request failed with status ${res.status}`); + } const json: unknown = await res.json(); const messages = Array.isArray(json) ? json @@ -49,7 +52,7 @@ export const createEmailService = () => { } return message; } catch (error) { - if (attempt === 4) { + if (attempt === 6) { throw error; } await new Promise(res => setTimeout(res, 750 * 2 ** attempt)); diff --git a/integration/tests/sign-in-or-up-email-links-flow.test.ts b/integration/tests/sign-in-or-up-email-links-flow.test.ts index 7a1951f092e..8ff4dc0fc63 100644 --- a/integration/tests/sign-in-or-up-email-links-flow.test.ts +++ b/integration/tests/sign-in-or-up-email-links-flow.test.ts @@ -36,6 +36,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSignInOrUpEmailLinksFlow] }); test('sign up with email link', async ({ page, context }) => { + test.setTimeout(90_000); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.setIdentifier(fakeUser.email); @@ -61,6 +62,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSignInOrUpEmailLinksFlow] }); test('sign in with email link', async ({ page, context }) => { + test.setTimeout(90_000); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.setIdentifier(fakeUser.email); From 1d53bbcfc60aafc9c69c6a877ca6e1b76bf7427a Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 18 Aug 2026 23:51:09 -0700 Subject: [PATCH 15/16] test(integration): use isolated public inboxes --- .github/workflows/ci.yml | 1 - .github/workflows/nightly-checks.yml | 1 - integration/.env.local.sample | 1 - integration/README.md | 1 - integration/testUtils/emailService.ts | 77 +++++++++++++-------------- integration/testUtils/usersService.ts | 2 +- 6 files changed, 37 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad42c86e4c..916932893e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -531,7 +531,6 @@ jobs: E2E_NEXTJS_VERSION: ${{ matrix.next-version }} E2E_PROJECT: ${{ matrix.test-project }} INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} - MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/integration/certs/rootCA.pem VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} diff --git a/.github/workflows/nightly-checks.yml b/.github/workflows/nightly-checks.yml index a5e63cc82d8..61ad42c4b23 100644 --- a/.github/workflows/nightly-checks.yml +++ b/.github/workflows/nightly-checks.yml @@ -80,7 +80,6 @@ jobs: E2E_REACT_DOM_VERSION: "19.2.3" E2E_REACT_VERSION: "19.2.3" INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} - MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} # Print error logs for immediate visibility in CI - name: Print App Error Logs diff --git a/integration/.env.local.sample b/integration/.env.local.sample index 9505f3baa4c..8fdb1f0151c 100644 --- a/integration/.env.local.sample +++ b/integration/.env.local.sample @@ -1,4 +1,3 @@ -MAILSAC_API_KEY= VERCEL_PROJECT_ID= VERCEL_ORG_ID= VERCEL_TOKEN= diff --git a/integration/README.md b/integration/README.md index 7a1fd3b89e5..cc74296bf82 100644 --- a/integration/README.md +++ b/integration/README.md @@ -628,7 +628,6 @@ Before writing tests, it's important to understand how Playwright handles test i > [!NOTE] > The test suite also uses these environment variables to run some tests: > -> - `MAILSAC_API_KEY`: Used for [Mailsac](https://mailsac.com/) to retrieve email codes and magic links from temporary email addresses. > - `VERCEL_PROJECT_ID`: Only required if you plan on running deployment tests locally. This is the Vercel project ID, and it points to an application created via the Vercel dashboard. The easiest way to get access to it is by linking a local app to the Vercel project using the Vercel CLI, and then copying the values from the `.vercel` directory. > - `VERCEL_ORG_ID`: The organization that owns the Vercel project. See above for more details. > - `VERCEL_TOKEN`: A personal access token. This corresponds to a real user running the deployment command. Attention: Be extra careful with this token as it can't be scoped to a single Vercel project, meaning that the token has access to every project in the account it belongs to. diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 087b552fbc1..2ae8f45a2ed 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -1,55 +1,62 @@ type Message = { _id: string; + links: string[]; subject: string; }; +type InboxPageData = { + props?: { + pageProps?: { + seedInboxMessages?: unknown[]; + }; + }; +}; + +const consumedMessageIds = new Set(); + function isMessage(value: unknown): value is Message { return ( typeof value === 'object' && value !== null && '_id' in value && typeof value._id === 'string' && + 'links' in value && + Array.isArray(value.links) && + value.links.every(link => typeof link === 'string') && 'subject' in value && typeof value.subject === 'string' ); } export const createEmailService = () => { - const cleanEmail = (email: string) => { - return email.replace(/\+.*@/, '@'); - }; - - const fetcher = async (url: string | URL, init?: RequestInit) => { - const headers = new Headers(init?.headers || {}); - // eslint-disable-next-line turbo/no-undeclared-env-vars - headers.set('Mailsac-Key', process.env.MAILSAC_API_KEY as string); - return fetch(url, { ...init, headers }); - }; - const filterMessagesByAddress = async (email: string, sub?: string) => { - const url = new URL('https://mailsac.com/api/inbox-filter'); - url.searchParams.set('andTo', email); - if (sub) { - url.searchParams.set('andSubjectIncludes', sub); - } + const url = new URL(`https://mailsac.com/inbox/${encodeURIComponent(email)}`); // Retry in case the email delivery is delayed await new Promise(res => setTimeout(res, 1500)); for (let attempt = 0; attempt < 7; attempt++) { try { - const res = await fetcher(url); + const res = await fetch(url); if (!res.ok) { throw new Error(`Email inbox request failed with status ${res.status}`); } - const json: unknown = await res.json(); - const messages = Array.isArray(json) - ? json - : typeof json === 'object' && json !== null && 'messages' in json && Array.isArray(json.messages) - ? json.messages - : []; - const message = messages.find(isMessage); + const html = await res.text(); + const nextData = html.match(/