Skip to content

Commit 60a9c71

Browse files
authored
fix(ui): hide unavailable alternative verification methods (#9355)
1 parent b8810a9 commit 60a9c71

8 files changed

Lines changed: 97 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/ui': patch
3+
---
4+
5+
Hide the “Use another method” action during Device Trust and second-factor verification when no alternative verification method is available.

integration/tests/client-trust.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withNeedsClientTrust] })(
3838

3939
// Should contain the new device verification notice
4040
await expect(u.page.getByText("You're signing in from a new device.")).toBeVisible();
41+
await expect(u.page.getByRole('link', { name: 'Use another method' })).toBeHidden();
4142

4243
// User should not be signed in yet since client trust step is required
4344
await u.po.expect.toBeSignedOut();

packages/ui/src/components/SignIn/SignInClientTrust.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ function SignInClientTrustInternal(): JSX.Element {
1919
showAllStrategies,
2020
toggleAllStrategies,
2121
} = useSecondFactorSelection(signIn.supportedSecondFactors);
22+
const onShowAlternativeMethodsClicked =
23+
signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined;
2224

2325
if (!currentFactor) {
2426
return <LoadingCard />;
@@ -41,7 +43,7 @@ function SignInClientTrustInternal(): JSX.Element {
4143
factorAlreadyPrepared={factorAlreadyPrepared}
4244
onFactorPrepare={handleFactorPrepare}
4345
factor={currentFactor}
44-
onShowAlternativeMethodsClicked={toggleAllStrategies}
46+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
4547
/>
4648
);
4749
case 'email_code':
@@ -51,7 +53,7 @@ function SignInClientTrustInternal(): JSX.Element {
5153
factorAlreadyPrepared={factorAlreadyPrepared}
5254
onFactorPrepare={handleFactorPrepare}
5355
factor={currentFactor}
54-
onShowAlternativeMethodsClicked={toggleAllStrategies}
56+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
5557
/>
5658
);
5759
case 'email_link':
@@ -61,7 +63,7 @@ function SignInClientTrustInternal(): JSX.Element {
6163
factorAlreadyPrepared={factorAlreadyPrepared}
6264
onFactorPrepare={handleFactorPrepare}
6365
factor={currentFactor}
64-
onShowAlternativeMethodsClicked={toggleAllStrategies}
66+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
6567
/>
6668
);
6769
default:

packages/ui/src/components/SignIn/SignInFactorTwo.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ function SignInFactorTwoInternal(): JSX.Element {
2828
showAllStrategies,
2929
toggleAllStrategies,
3030
} = useSecondFactorSelection(signIn.supportedSecondFactors);
31+
const onShowAlternativeMethodsClicked =
32+
signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined;
3133

3234
React.useEffect(() => {
3335
if (clerk.__internal_setActiveInProgress) {
@@ -69,7 +71,7 @@ function SignInFactorTwoInternal(): JSX.Element {
6971
factorAlreadyPrepared={factorAlreadyPrepared}
7072
onFactorPrepare={handleFactorPrepare}
7173
factor={currentFactor}
72-
onShowAlternativeMethodsClicked={toggleAllStrategies}
74+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
7375
/>
7476
);
7577
case 'totp':
@@ -78,18 +80,18 @@ function SignInFactorTwoInternal(): JSX.Element {
7880
factorAlreadyPrepared={factorAlreadyPrepared}
7981
onFactorPrepare={handleFactorPrepare}
8082
factor={currentFactor}
81-
onShowAlternativeMethodsClicked={toggleAllStrategies}
83+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
8284
/>
8385
);
8486
case 'backup_code':
85-
return <SignInFactorTwoBackupCodeCard onShowAlternativeMethodsClicked={toggleAllStrategies} />;
87+
return <SignInFactorTwoBackupCodeCard onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked} />;
8688
case 'email_code':
8789
return (
8890
<SignInFactorTwoEmailCodeCard
8991
factorAlreadyPrepared={factorAlreadyPrepared}
9092
onFactorPrepare={handleFactorPrepare}
9193
factor={currentFactor}
92-
onShowAlternativeMethodsClicked={toggleAllStrategies}
94+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
9395
/>
9496
);
9597
case 'email_link':
@@ -98,7 +100,7 @@ function SignInFactorTwoInternal(): JSX.Element {
98100
factorAlreadyPrepared={factorAlreadyPrepared}
99101
onFactorPrepare={handleFactorPrepare}
100102
factor={currentFactor}
101-
onShowAlternativeMethodsClicked={toggleAllStrategies}
103+
onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
102104
/>
103105
);
104106
default:

packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { navigateOnSignInProtectGate } from './handleProtectCheck';
1919
import { isResetPasswordStrategy } from './utils';
2020

2121
type SignInFactorTwoBackupCodeCardProps = {
22-
onShowAlternativeMethodsClicked: React.MouseEventHandler;
22+
onShowAlternativeMethodsClicked?: React.MouseEventHandler;
2323
};
2424

2525
export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCardProps) => {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { SignInResource } from '@clerk/shared/types';
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { bindCreateFixtures } from '@/test/create-fixtures';
5+
import { render, screen } from '@/test/utils';
6+
7+
import { SignInClientTrust } from '../SignInClientTrust';
8+
9+
const { createFixtures } = bindCreateFixtures('SignIn');
10+
11+
describe('SignInClientTrust', () => {
12+
describe('Use another method', () => {
13+
it('does not render when only one verification factor is available', async () => {
14+
const { wrapper, fixtures } = await createFixtures(f => {
15+
f.startSignInClientTrust({ supportPhoneCode: true });
16+
});
17+
18+
fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
19+
render(<SignInClientTrust />, { wrapper });
20+
21+
expect(screen.queryByText('Use another method')).not.toBeInTheDocument();
22+
});
23+
24+
it('renders when multiple verification factors are available', async () => {
25+
const { wrapper, fixtures } = await createFixtures(f => {
26+
f.startSignInClientTrust({ supportPhoneCode: true, supportEmailCode: true });
27+
});
28+
29+
fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
30+
render(<SignInClientTrust />, { wrapper });
31+
32+
expect(screen.getByText('Use another method')).toBeInTheDocument();
33+
});
34+
});
35+
});

packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,28 @@ describe('SignInFactorTwo', () => {
407407
});
408408

409409
describe('Use another method', () => {
410+
it('does not render when only one verification factor is available', async () => {
411+
const { wrapper, fixtures } = await createFixtures(f => {
412+
f.startSignInFactorTwo({ supportPhoneCode: true });
413+
});
414+
415+
fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
416+
render(<SignInFactorTwo />, { wrapper });
417+
418+
expect(screen.queryByText('Use another method')).not.toBeInTheDocument();
419+
});
420+
421+
it('renders when multiple verification factors are available', async () => {
422+
const { wrapper, fixtures } = await createFixtures(f => {
423+
f.startSignInFactorTwo({ supportPhoneCode: true, supportEmailCode: true });
424+
});
425+
426+
fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
427+
render(<SignInFactorTwo />, { wrapper });
428+
429+
expect(screen.getByText('Use another method')).toBeInTheDocument();
430+
});
431+
410432
it('renders the other authentication methods list component when clicking on "Use another method"', async () => {
411433
const { wrapper, fixtures } = await createFixtures(f => {
412434
f.withEmailAddress();

packages/ui/src/test/fixture-helpers.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
119119
type SignInFactorTwoParams = {
120120
identifier?: string;
121121
supportPhoneCode?: boolean;
122+
supportEmailCode?: boolean;
122123
supportTotp?: boolean;
123124
supportBackupCode?: boolean;
124125
supportResetPasswordEmail?: boolean;
@@ -185,17 +186,21 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
185186
} as SignInJSON;
186187
};
187188

188-
const startSignInFactorTwo = (params?: SignInFactorTwoParams) => {
189+
const startSignInVerification = (
190+
status: Extract<SignInJSON['status'], 'needs_client_trust' | 'needs_second_factor'>,
191+
params?: SignInFactorTwoParams,
192+
) => {
189193
const {
190194
identifier = '+30 691 1111111',
191195
supportPhoneCode = true,
196+
supportEmailCode,
192197
supportTotp,
193198
supportBackupCode,
194199
supportResetPasswordEmail,
195200
supportResetPasswordPhone,
196201
} = params || {};
197202
baseClient.sign_in = {
198-
status: 'needs_second_factor',
203+
status,
199204
identifier,
200205
...(supportResetPasswordEmail
201206
? {
@@ -216,13 +221,20 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
216221
supported_identifiers: ['email_address', 'phone_number'],
217222
supported_second_factors: [
218223
...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
224+
...(supportEmailCode ? [{ strategy: 'email_code', safe_identifier: 'n*****@clerk.com' }] : []),
219225
...(supportTotp ? [{ strategy: 'totp', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
220226
...(supportBackupCode ? [{ strategy: 'backup_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
221227
],
222228
user_data: { ...(createUserFixture() as any) },
223229
} as SignInJSON;
224230
};
225231

232+
const startSignInFactorTwo = (params?: SignInFactorTwoParams) =>
233+
startSignInVerification('needs_second_factor', params);
234+
235+
const startSignInClientTrust = (params?: SignInFactorTwoParams) =>
236+
startSignInVerification('needs_client_trust', params);
237+
226238
const startSignInWithProtectCheck = (params?: {
227239
expiresAt?: number;
228240
uiHints?: Record<string, string>;
@@ -249,7 +261,13 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
249261
} as SignInJSON;
250262
};
251263

252-
return { startSignInWithEmailAddress, startSignInWithPhoneNumber, startSignInFactorTwo, startSignInWithProtectCheck };
264+
return {
265+
startSignInWithEmailAddress,
266+
startSignInWithPhoneNumber,
267+
startSignInFactorTwo,
268+
startSignInClientTrust,
269+
startSignInWithProtectCheck,
270+
};
253271
};
254272

255273
const createSignUpFixtureHelpers = (baseClient: ClientJSON) => {

0 commit comments

Comments
 (0)