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/curly-lemons-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/elements": patch
---

Fixes issue where the incorrect sign in first factor strategy was being returned during sign in.
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
// These utilities are ported from: packages/clerk-js/src/ui/components/SignIn/utils.ts
// They should be functionally identical.
import { isWebAuthnSupported } from '@clerk/shared/webauthn';
import type { PreferredSignInStrategy, SignInFactor, SignInFirstFactor, SignInSecondFactor } from '@clerk/types';
import type {
PreferredSignInStrategy,
SignInFactor,
SignInFirstFactor,
SignInSecondFactor,
SignInStrategy,
} from '@clerk/types';

// Factor sorting - https://github.com/clerk/javascript/blob/5764e2911790051589bb5c4f3b1a2c79f7f30c7e/packages/clerk-js/src/ui/utils/factorSorting.ts
const makeSortingOrderMap = <T extends string>(arr: T[]): Record<T, number> =>
arr.reduce(
(acc, k, i) => {
acc[k] = i;
return acc;
},
{} as Record<T, number>,
);

const STRATEGY_SORT_ORDER_PASSWORD_PREF = makeSortingOrderMap([
'passkey',
'password',
'email_link',
'email_code',
'phone_code',
] as SignInStrategy[]);

const STRATEGY_SORT_ORDER_OTP_PREF = makeSortingOrderMap([
'email_code',
'email_link',
'phone_code',
'passkey',
'password',
] as SignInStrategy[]);

const makeSortingFunction =
(sortingMap: Record<SignInStrategy, number>) =>
(a: SignInFactor, b: SignInFactor): number => {
const orderA = sortingMap[a.strategy];
const orderB = sortingMap[b.strategy];
if (orderA === undefined || orderB === undefined) {
return 0;
}
return orderA - orderB;
};

const ORDER_WHEN_PASSWORD_PREFERRED = ['passkey', 'password', 'email_link', 'email_code', 'phone_code'] as const;
const ORDER_WHEN_OTP_PREFERRED = ['email_link', 'email_code', 'phone_code', 'passkey', 'password'] as const;
// const ORDER_ALL_STRATEGIES = ['email_link', 'email_code', 'phone_code', 'password'] as const;
const passwordPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_PASSWORD_PREF);
const otpPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_OTP_PREF);

const findFactorForIdentifier = (i: string | null) => (f: SignInFactor) => {
return 'safeIdentifier' in f && f.safeIdentifier === i;
Expand Down Expand Up @@ -43,27 +85,11 @@ function determineStrategyWhenPasswordIsPreferred(factors: SignInFirstFactor[],
if (passkeyFactor) {
return passkeyFactor;
}

// Prefer the password factor if it's available
const passwordFactor = factors.find(factor => factor.strategy === 'password');
if (passwordFactor) {
return passwordFactor;
}

// Otherwise, find the factor for the provided identifier, or the next factor based on the preference list
const factorForIdentifier = factors.find(findFactorForIdentifier(identifier));
if (factorForIdentifier) {
return factorForIdentifier;
}

for (const preferredFactor of ORDER_WHEN_PASSWORD_PREFERRED) {
const factor = factors.find(factor => factor.strategy === preferredFactor);
if (factor) {
return factor;
}
const selected = factors.sort(passwordPrefFactorComparator)[0];
if (selected.strategy === 'password') {
return selected;
}

return null;
return factors.find(findFactorForIdentifier(identifier)) || selected || null;
}

function determineStrategyWhenOTPIsPreferred(factors: SignInFirstFactor[], identifier: string | null) {
Expand All @@ -72,25 +98,16 @@ function determineStrategyWhenOTPIsPreferred(factors: SignInFirstFactor[], ident
return passkeyFactor;
}

const factorForIdentifier = factors.find(findFactorForIdentifier(identifier));
if (factorForIdentifier) {
return factorForIdentifier;
const sortedBasedOnPrefFactor = factors.sort(otpPrefFactorComparator);
const forIdentifier = sortedBasedOnPrefFactor.find(findFactorForIdentifier(identifier));
if (forIdentifier) {
return forIdentifier;
}

// Prefer the password factor if it's available
const emailLinkFactor = factors.find(factor => factor.strategy === 'email_link');
if (emailLinkFactor) {
return emailLinkFactor;
const firstBasedOnPref = sortedBasedOnPrefFactor[0];
if (firstBasedOnPref.strategy === 'email_link') {
return firstBasedOnPref;
}

for (const preferredFactor of ORDER_WHEN_OTP_PREFERRED) {
const factor = factors.find(factor => factor.strategy === preferredFactor);
if (factor) {
return factor;
}
}

return null;
return factors.find(findFactorForIdentifier(identifier)) || firstBasedOnPref || null;
}

// The priority of second factors is: TOTP -> Phone code -> any other factor
Expand Down