diff --git a/.changeset/tasty-dots-guess.md b/.changeset/tasty-dots-guess.md new file mode 100644 index 00000000000..99c9ca555db --- /dev/null +++ b/.changeset/tasty-dots-guess.md @@ -0,0 +1,8 @@ +--- +"@clerk/clerk-js": patch +"@clerk/elements": patch +"@clerk/types": patch +"@clerk/ui": patch +--- + +In certain situations the Frontend API response contains [`supported_first_factors`](https://clerk.com/docs/reference/frontend-api/tag/Sign-Ins#operation/createSignIn!c=200&path=response/supported_first_factors&t=response) with a `null` value while the current code always assumed to receive an array. `SignInResource['supportedFirstFactors']` has been updated to account for that and any code accessing this value has been made more resilient against `null` values. diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 77562861556..93d66052fda 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -56,7 +56,7 @@ export class SignIn extends BaseResource implements SignInResource { id?: string; status: SignInStatus | null = null; supportedIdentifiers: SignInIdentifier[] = []; - supportedFirstFactors: SignInFirstFactor[] = []; + supportedFirstFactors: SignInFirstFactor[] | null = []; supportedSecondFactors: SignInSecondFactor[] | null = null; firstFactorVerification: VerificationResource = new Verification(null); secondFactorVerification: VerificationResource = new Verification(null); @@ -230,7 +230,7 @@ export class SignIn extends BaseResource implements SignInResource { await this.create({ identifier }); - const web3FirstFactor = this.supportedFirstFactors.find( + const web3FirstFactor = this.supportedFirstFactors?.find( f => f.strategy === 'web3_metamask_signature', ) as Web3SignatureFactor; @@ -338,7 +338,7 @@ export class SignIn extends BaseResource implements SignInResource { this.status = data.status; this.supportedIdentifiers = data.supported_identifiers; this.identifier = data.identifier; - this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[]; + this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null; this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null; this.firstFactorVerification = new Verification(data.first_factor_verification); this.secondFactorVerification = new Verification(data.second_factor_verification); diff --git a/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx b/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx index bbae862f450..9285b08905d 100644 --- a/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/AlternativeMethods.tsx @@ -83,22 +83,23 @@ const AlternativeMethodsList = (props: AlternativeMethodListProps) => { enableWeb3Providers enableOAuthProviders /> - {firstPartyFactors.map((factor, i) => ( - { - card.setError(undefined); - onFactorSelected(factor); - }} - /> - ))} + {firstPartyFactors && + firstPartyFactors.map((factor, i) => ( + { + card.setError(undefined); + onFactorSelected(factor); + }} + /> + ))} )} {onBackLinkClick && ( diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index 754432c67bd..d2b6f838356 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -263,7 +263,7 @@ export function _SignInStart(): JSX.Element { * For SAML enabled instances, perform sign in with password only when it is allowed for the identified user. */ const passwordField = fields.find(f => f.name === 'password')?.value; - if (!passwordField || signInResource.supportedFirstFactors.some(ff => ff.strategy === 'saml')) { + if (!passwordField || signInResource.supportedFirstFactors?.some(ff => ff.strategy === 'saml')) { return signInResource; } return signInResource.attemptFirstFactor({ strategy: 'password', password: passwordField }); @@ -277,7 +277,7 @@ export function _SignInStart(): JSX.Element { switch (res.status) { case 'needs_identifier': // Check if we need to initiate a saml flow - if (res.supportedFirstFactors.some(ff => ff.strategy === 'saml')) { + if (res.supportedFirstFactors?.some(ff => ff.strategy === 'saml')) { await authenticateWithSaml(); } break; diff --git a/packages/clerk-js/src/ui/components/SignIn/useResetPasswordFactor.tsx b/packages/clerk-js/src/ui/components/SignIn/useResetPasswordFactor.tsx index d1387bc630f..3674dec86f7 100644 --- a/packages/clerk-js/src/ui/components/SignIn/useResetPasswordFactor.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/useResetPasswordFactor.tsx @@ -6,7 +6,7 @@ import { isResetPasswordStrategy } from './utils'; export function useResetPasswordFactor() { const signIn = useCoreSignIn(); - return signIn.supportedFirstFactors.find(({ strategy }) => isResetPasswordStrategy(strategy)) as + return signIn.supportedFirstFactors?.find(({ strategy }) => isResetPasswordStrategy(strategy)) as | ResetPasswordCodeFactor | undefined; } diff --git a/packages/clerk-js/src/ui/components/SignIn/utils.ts b/packages/clerk-js/src/ui/components/SignIn/utils.ts index 60c84bf828b..47b3f116a77 100644 --- a/packages/clerk-js/src/ui/components/SignIn/utils.ts +++ b/packages/clerk-js/src/ui/components/SignIn/utils.ts @@ -103,7 +103,7 @@ function determineStrategyWhenOTPIsPreferred(factors: SignInFactor[], identifier // The algorithm can be found at // https://www.notion.so/clerkdev/Implement-sign-in-alt-methods-e6e60ffb644645b3a0553b50556468ce export function determineStartingSignInFactor( - firstFactors: SignInFactor[], + firstFactors: SignInFactor[] | null, identifier: string | null, preferredSignInStrategy: PreferredSignInStrategy, ): SignInFactor | null | undefined { diff --git a/packages/clerk-js/src/ui/hooks/useAlternativeStrategies.ts b/packages/clerk-js/src/ui/hooks/useAlternativeStrategies.ts index 15e87769a99..0ff31c27cf1 100644 --- a/packages/clerk-js/src/ui/hooks/useAlternativeStrategies.ts +++ b/packages/clerk-js/src/ui/hooks/useAlternativeStrategies.ts @@ -11,14 +11,14 @@ export function useAlternativeStrategies({ filterOutFactor }: { filterOutFactor: const { strategies: OAuthStrategies } = useEnabledThirdPartyProviders(); - const firstFactors = supportedFirstFactors.filter( + const firstFactors = supportedFirstFactors?.filter( f => f.strategy !== filterOutFactor?.strategy && !isResetPasswordStrategy(f.strategy), ); - const shouldAllowForAlternativeStrategies = firstFactors.length + OAuthStrategies.length > 0; + const shouldAllowForAlternativeStrategies = firstFactors && firstFactors.length + OAuthStrategies.length > 0; const firstPartyFactors = supportedFirstFactors - .filter(f => !f.strategy.startsWith('oauth_') && !(f.strategy === filterOutFactor?.strategy)) + ?.filter(f => !f.strategy.startsWith('oauth_') && !(f.strategy === filterOutFactor?.strategy)) .filter(factor => factorHasLocalStrategy(factor)) // Only include passkey if the device supports it. // @ts-ignore Types are not public yet. diff --git a/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts b/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts index 62e424d9a38..1035720cef7 100644 --- a/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts +++ b/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts @@ -56,7 +56,7 @@ const findFactorForIdentifier = (i: string | null) => (f: SignInFactor) => { // The algorithm can be found at // https://www.notion.so/clerkdev/Implement-sign-in-alt-methods-e6e60ffb644645b3a0553b50556468ce export function determineStartingSignInFactor( - firstFactors: SignInFirstFactor[], + firstFactors: SignInFirstFactor[] | null, identifier: string | null, preferredSignInStrategy?: PreferredSignInStrategy, ) { diff --git a/packages/elements/src/internals/machines/sign-in/verification.machine.ts b/packages/elements/src/internals/machines/sign-in/verification.machine.ts index 259390fa14b..9f64b1342c7 100644 --- a/packages/elements/src/internals/machines/sign-in/verification.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/verification.machine.ts @@ -96,6 +96,7 @@ const SignInVerificationMachine = setup({ // Only show these warnings in development! if (process.env.NODE_ENV === 'development') { if ( + clerk.client.signIn.supportedFirstFactors && !clerk.client.signIn.supportedFirstFactors.every(factor => context.registeredStrategies.has(factor.strategy)) ) { console.warn( @@ -121,7 +122,7 @@ const SignInVerificationMachine = setup({ } const strategiesUsedButNotActivated = Array.from(context.registeredStrategies).filter( - strategy => !clerk.client.signIn.supportedFirstFactors.some(supported => supported.strategy === strategy), + strategy => !clerk.client.signIn.supportedFirstFactors?.some(supported => supported.strategy === strategy), ); if (strategiesUsedButNotActivated.length > 0) { diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index ddd6323b396..39a6026d3ab 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -72,7 +72,7 @@ export interface SignInResource extends ClerkResource { * @deprecated This attribute will be removed in the next major version */ supportedIdentifiers: SignInIdentifier[]; - supportedFirstFactors: SignInFirstFactor[]; + supportedFirstFactors: SignInFirstFactor[] | null; supportedSecondFactors: SignInSecondFactor[] | null; firstFactorVerification: VerificationResource; secondFactorVerification: VerificationResource; diff --git a/packages/ui/src/hooks/use-reset-password-factor.ts b/packages/ui/src/hooks/use-reset-password-factor.ts index 3a293d2c289..502027be2ed 100644 --- a/packages/ui/src/hooks/use-reset-password-factor.ts +++ b/packages/ui/src/hooks/use-reset-password-factor.ts @@ -8,7 +8,7 @@ export const isResetPasswordStrategy = (strategy: SignInStrategy | string | null export function useResetPasswordFactor() { const clerk = useClerk(); - return clerk.client.signIn.supportedFirstFactors.find(({ strategy }) => isResetPasswordStrategy(strategy)) as + return clerk.client.signIn.supportedFirstFactors?.find(({ strategy }) => isResetPasswordStrategy(strategy)) as | ResetPasswordCodeFactor | undefined; }