diff --git a/.changeset/tidy-device-trust-methods.md b/.changeset/tidy-device-trust-methods.md new file mode 100644 index 00000000000..0b3908e7a77 --- /dev/null +++ b/.changeset/tidy-device-trust-methods.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Hide the “Use another method” action during Device Trust and second-factor verification when no alternative verification method is available. diff --git a/integration/tests/client-trust.test.ts b/integration/tests/client-trust.test.ts index 7cf5e377f21..9817b5b0135 100644 --- a/integration/tests/client-trust.test.ts +++ b/integration/tests/client-trust.test.ts @@ -38,6 +38,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withNeedsClientTrust] })( // Should contain the new device verification notice await expect(u.page.getByText("You're signing in from a new device.")).toBeVisible(); + await expect(u.page.getByRole('link', { name: 'Use another method' })).toBeHidden(); // User should not be signed in yet since client trust step is required await u.po.expect.toBeSignedOut(); diff --git a/packages/ui/src/components/SignIn/SignInClientTrust.tsx b/packages/ui/src/components/SignIn/SignInClientTrust.tsx index ca3273fc2ff..aa14fd77fb1 100644 --- a/packages/ui/src/components/SignIn/SignInClientTrust.tsx +++ b/packages/ui/src/components/SignIn/SignInClientTrust.tsx @@ -19,6 +19,8 @@ function SignInClientTrustInternal(): JSX.Element { showAllStrategies, toggleAllStrategies, } = useSecondFactorSelection(signIn.supportedSecondFactors); + const onShowAlternativeMethodsClicked = + signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined; if (!currentFactor) { return ; @@ -41,7 +43,7 @@ function SignInClientTrustInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); case 'email_code': @@ -51,7 +53,7 @@ function SignInClientTrustInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); case 'email_link': @@ -61,7 +63,7 @@ function SignInClientTrustInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); default: diff --git a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx index 90bfddd6df7..1af218d771c 100644 --- a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx +++ b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx @@ -28,6 +28,8 @@ function SignInFactorTwoInternal(): JSX.Element { showAllStrategies, toggleAllStrategies, } = useSecondFactorSelection(signIn.supportedSecondFactors); + const onShowAlternativeMethodsClicked = + signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined; React.useEffect(() => { if (clerk.__internal_setActiveInProgress) { @@ -69,7 +71,7 @@ function SignInFactorTwoInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); case 'totp': @@ -78,18 +80,18 @@ function SignInFactorTwoInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); case 'backup_code': - return ; + return ; case 'email_code': return ( ); case 'email_link': @@ -98,7 +100,7 @@ function SignInFactorTwoInternal(): JSX.Element { factorAlreadyPrepared={factorAlreadyPrepared} onFactorPrepare={handleFactorPrepare} factor={currentFactor} - onShowAlternativeMethodsClicked={toggleAllStrategies} + onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} /> ); default: diff --git a/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx b/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx index ddd37690b03..3ffe439aa36 100644 --- a/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx +++ b/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx @@ -19,7 +19,7 @@ import { navigateOnSignInProtectGate } from './handleProtectCheck'; import { isResetPasswordStrategy } from './utils'; type SignInFactorTwoBackupCodeCardProps = { - onShowAlternativeMethodsClicked: React.MouseEventHandler; + onShowAlternativeMethodsClicked?: React.MouseEventHandler; }; export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCardProps) => { diff --git a/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx new file mode 100644 index 00000000000..029f51e9619 --- /dev/null +++ b/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx @@ -0,0 +1,35 @@ +import type { SignInResource } from '@clerk/shared/types'; +import { describe, expect, it } from 'vitest'; + +import { bindCreateFixtures } from '@/test/create-fixtures'; +import { render, screen } from '@/test/utils'; + +import { SignInClientTrust } from '../SignInClientTrust'; + +const { createFixtures } = bindCreateFixtures('SignIn'); + +describe('SignInClientTrust', () => { + describe('Use another method', () => { + it('does not render when only one verification factor is available', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.startSignInClientTrust({ supportPhoneCode: true }); + }); + + fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource); + render(, { wrapper }); + + expect(screen.queryByText('Use another method')).not.toBeInTheDocument(); + }); + + it('renders when multiple verification factors are available', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.startSignInClientTrust({ supportPhoneCode: true, supportEmailCode: true }); + }); + + fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource); + render(, { wrapper }); + + expect(screen.getByText('Use another method')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx index 3d944954df9..287e934288c 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx @@ -407,6 +407,28 @@ describe('SignInFactorTwo', () => { }); describe('Use another method', () => { + it('does not render when only one verification factor is available', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.startSignInFactorTwo({ supportPhoneCode: true }); + }); + + fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource); + render(, { wrapper }); + + expect(screen.queryByText('Use another method')).not.toBeInTheDocument(); + }); + + it('renders when multiple verification factors are available', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.startSignInFactorTwo({ supportPhoneCode: true, supportEmailCode: true }); + }); + + fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource); + render(, { wrapper }); + + expect(screen.getByText('Use another method')).toBeInTheDocument(); + }); + it('renders the other authentication methods list component when clicking on "Use another method"', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); diff --git a/packages/ui/src/test/fixture-helpers.ts b/packages/ui/src/test/fixture-helpers.ts index 9508c2ffe75..51320d1077f 100644 --- a/packages/ui/src/test/fixture-helpers.ts +++ b/packages/ui/src/test/fixture-helpers.ts @@ -119,6 +119,7 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => { type SignInFactorTwoParams = { identifier?: string; supportPhoneCode?: boolean; + supportEmailCode?: boolean; supportTotp?: boolean; supportBackupCode?: boolean; supportResetPasswordEmail?: boolean; @@ -185,17 +186,21 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => { } as SignInJSON; }; - const startSignInFactorTwo = (params?: SignInFactorTwoParams) => { + const startSignInVerification = ( + status: Extract, + params?: SignInFactorTwoParams, + ) => { const { identifier = '+30 691 1111111', supportPhoneCode = true, + supportEmailCode, supportTotp, supportBackupCode, supportResetPasswordEmail, supportResetPasswordPhone, } = params || {}; baseClient.sign_in = { - status: 'needs_second_factor', + status, identifier, ...(supportResetPasswordEmail ? { @@ -216,6 +221,7 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => { supported_identifiers: ['email_address', 'phone_number'], supported_second_factors: [ ...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ...(supportEmailCode ? [{ strategy: 'email_code', safe_identifier: 'n*****@clerk.com' }] : []), ...(supportTotp ? [{ strategy: 'totp', safe_identifier: identifier || 'n*****@clerk.com' }] : []), ...(supportBackupCode ? [{ strategy: 'backup_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), ], @@ -223,6 +229,12 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => { } as SignInJSON; }; + const startSignInFactorTwo = (params?: SignInFactorTwoParams) => + startSignInVerification('needs_second_factor', params); + + const startSignInClientTrust = (params?: SignInFactorTwoParams) => + startSignInVerification('needs_client_trust', params); + const startSignInWithProtectCheck = (params?: { expiresAt?: number; uiHints?: Record; @@ -249,7 +261,13 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => { } as SignInJSON; }; - return { startSignInWithEmailAddress, startSignInWithPhoneNumber, startSignInFactorTwo, startSignInWithProtectCheck }; + return { + startSignInWithEmailAddress, + startSignInWithPhoneNumber, + startSignInFactorTwo, + startSignInClientTrust, + startSignInWithProtectCheck, + }; }; const createSignUpFixtureHelpers = (baseClient: ClientJSON) => {