Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hide-reverification-empty-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Hide "Use another method" during reverification when the current factor is the only available method.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ const AlternativeMethodsList = (props: AlternativeMethodListProps) => {
const { onBackLinkClick, onHavingTroubleClick, onFactorSelected } = props;
const card = useCardState();
const { data } = useUserVerificationSession();
const { firstPartyFactors, hasAnyStrategy } = useReverificationAlternativeStrategies<SessionVerificationFirstFactor>({
filterOutFactor: props?.currentFactor,
supportedFirstFactors: data?.supportedFirstFactors,
});
const { firstPartyFactors, hasAlternativeStrategies } =
useReverificationAlternativeStrategies<SessionVerificationFirstFactor>({
filterOutFactor: props?.currentFactor,
supportedFirstFactors: data?.supportedFirstFactors,
});

return (
<Flow.Part part={'alternativeMethods'}>
Expand All @@ -54,7 +55,7 @@ const AlternativeMethodsList = (props: AlternativeMethodListProps) => {
gap={6}
>
<Col gap={4}>
{hasAnyStrategy && (
{hasAlternativeStrategies && (
<Flex
elementDescriptor={descriptors.alternativeMethods}
direction='col'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Col, descriptors, localizationKeys } from '../../customizables';
import { useAfterVerification } from './use-after-verification';

type UVFactorTwoBackupCodeCardProps = {
onShowAlternativeMethodsClicked: React.MouseEventHandler;
onShowAlternativeMethodsClicked?: React.MouseEventHandler;
};

export const UVFactorTwoBackupCodeCard = (props: UVFactorTwoBackupCodeCardProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function UserVerificationFactorOneInternal(): JSX.Element | null {
prevCurrentFactor: undefined,
}));

const { hasAnyStrategy, hasFirstParty } = useReverificationAlternativeStrategies({
const { hasAlternativeStrategies } = useReverificationAlternativeStrategies({
filterOutFactor: currentFactor,
supportedFirstFactors: availableFactors,
});
Expand All @@ -74,7 +74,7 @@ export function UserVerificationFactorOneInternal(): JSX.Element | null {
() => !currentFactor || !factorHasLocalStrategy(currentFactor),
);

const toggleAllStrategies = hasAnyStrategy
const toggleAllStrategies = hasAlternativeStrategies
? () => {
card.setError(undefined);
setShowAllStrategies(s => !s);
Expand Down Expand Up @@ -140,7 +140,7 @@ export function UserVerificationFactorOneInternal(): JSX.Element | null {
onFactorPrepare={handleFactorPrepare}
onShowAlternativeMethodsClicked={toggleAllStrategies}
factor={currentFactor}
showAlternativeMethods={hasFirstParty}
showAlternativeMethods={hasAlternativeStrategies}
/>
);
case 'phone_code':
Expand All @@ -150,7 +150,7 @@ export function UserVerificationFactorOneInternal(): JSX.Element | null {
onFactorPrepare={handleFactorPrepare}
onShowAlternativeMethodsClicked={toggleAllStrategies}
factor={currentFactor}
showAlternativeMethods={hasFirstParty}
showAlternativeMethods={hasAlternativeStrategies}
/>
);
case 'passkey':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ export function UserVerificationFactorTwoComponent(): JSX.Element {
/>
);
case 'backup_code':
return <UVFactorTwoBackupCodeCard onShowAlternativeMethodsClicked={toggleAllStrategies} />;
return (
<UVFactorTwoBackupCodeCard
onShowAlternativeMethodsClicked={hasAlternativeStrategies ? toggleAllStrategies : undefined}
/>
);
default:
return <LoadingCard />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,45 @@ describe('UserVerificationFactorOne', () => {
});

describe('Use another method', () => {
it('does not show use another method when password is the only first factor', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
});
fixtures.session?.startVerification.mockResolvedValue({
status: 'needs_first_factor',
supportedFirstFactors: [{ strategy: 'password' }],
});

const { findByText, queryByText } = render(<UserVerificationFactorOne />, { wrapper });

await findByText('Verification required');
expect(queryByText('Use another method')).toBeNull();
await findByText('Get help');
});

it('does not show use another method when email is the only first factor', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
f.withPreferredSignInStrategy({ strategy: 'otp' });
});
fixtures.session?.startVerification.mockResolvedValue({
status: 'needs_first_factor',
supportedFirstFactors: [
{
strategy: 'email_code',
emailAddressId: 'email_1',
safeIdentifier: 'xxx@hello.com',
},
],
});
fixtures.session?.prepareFirstFactorVerification.mockResolvedValue({});

const { findByText, queryByText } = render(<UserVerificationFactorOne />, { wrapper });

await findByText('Verification required');
expect(queryByText('Use another method')).toBeNull();
});

it('should list enabled first factor methods without the current one', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ describe('UserVerificationFactorTwo', () => {
});

describe('Use another second factor method', () => {
it('does not show use another method when totp is the only second factor', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
});
vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
status: 'needs_second_factor',
supportedSecondFactors: [{ strategy: 'totp' }],
} as any);

vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({
status: 'needs_second_factor',
supportedSecondFactors: [{ strategy: 'totp' }],
} as any);

const { findByText, queryByText } = render(<UserVerificationFactorTwo />, { wrapper });

await findByText('Verification required');
expect(queryByText('Use another method')).toBeNull();
});

it('does not show use another method when backup code is the only second factor', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
});
vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
status: 'needs_second_factor',
supportedSecondFactors: [{ strategy: 'backup_code' }],
} as any);

const { findByText, queryByText } = render(<UserVerificationFactorTwo />, { wrapper });

await findByText('Enter a backup code');
expect(queryByText('Use another method')).toBeNull();
});

it('should list enabled second factor methods without the current one', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withUser({ username: 'clerkuser' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useMemo } from 'react';

import { allStrategiesButtonsComparator } from '@/ui/utils/factorSorting';

import { factorHasLocalStrategy, isResetPasswordStrategy } from '../SignIn/utils';
import { factorHasLocalStrategy } from '../SignIn/utils';

const firstFactorsAreEqual = (a: SignInFactor | null | undefined, b: SignInFactor | null | undefined) => {
if (!a || !b) {
Expand Down Expand Up @@ -44,11 +44,6 @@ export function useReverificationAlternativeStrategies<T = SignInFirstFactor>({
filterOutFactor: SignInFactor | null | undefined;
supportedFirstFactors: SignInFirstFactor[] | null | undefined;
}) {
const firstFactors = supportedFirstFactors
? supportedFirstFactors.filter(f => !isResetPasswordStrategy(f.strategy))
: [];
const shouldAllowForAlternativeStrategies = firstFactors && firstFactors.length > 0;

const firstPartyFactors = useMemo(
() =>
supportedFirstFactors
Expand All @@ -65,8 +60,7 @@ export function useReverificationAlternativeStrategies<T = SignInFirstFactor>({
);

return {
hasAnyStrategy: shouldAllowForAlternativeStrategies,
hasFirstParty: firstPartyFactors && firstPartyFactors.length > 0,
hasAlternativeStrategies: firstPartyFactors.length > 0,
firstPartyFactors,
};
}
Loading