Skip to content

Commit

Permalink
fix(elements): Correct TS types for verification machine (#3671)
Browse files Browse the repository at this point in the history
  • Loading branch information
LekoArts authored Jul 9, 2024
1 parent 553fddb commit bb3c625
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .changeset/ninety-geese-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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 } from '@clerk/types';
import type { PreferredSignInStrategy, SignInFactor, SignInFirstFactor, SignInSecondFactor } from '@clerk/types';

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;
Expand All @@ -14,10 +14,10 @@ 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: SignInFactor[],
firstFactors: SignInFirstFactor[],
identifier: string | null,
preferredSignInStrategy?: PreferredSignInStrategy,
): SignInFactor | null {
) {
if (!firstFactors || firstFactors.length === 0) {
return null;
}
Expand All @@ -27,7 +27,7 @@ export function determineStartingSignInFactor(
: determineStrategyWhenOTPIsPreferred(firstFactors, identifier);
}

function findPasskeyStrategy(factors: SignInFactor[]): SignInFactor | null {
function findPasskeyStrategy(factors: SignInFirstFactor[]) {
if (isWebAuthnSupported()) {
const passkeyFactor = factors.find(({ strategy }) => strategy === 'passkey');

Expand All @@ -38,10 +38,7 @@ function findPasskeyStrategy(factors: SignInFactor[]): SignInFactor | null {
return null;
}

function determineStrategyWhenPasswordIsPreferred(
factors: SignInFactor[],
identifier: string | null,
): SignInFactor | null {
function determineStrategyWhenPasswordIsPreferred(factors: SignInFirstFactor[], identifier: string | null) {
const passkeyFactor = findPasskeyStrategy(factors);
if (passkeyFactor) {
return passkeyFactor;
Expand Down Expand Up @@ -69,7 +66,7 @@ function determineStrategyWhenPasswordIsPreferred(
return null;
}

function determineStrategyWhenOTPIsPreferred(factors: SignInFactor[], identifier: string | null): SignInFactor | null {
function determineStrategyWhenOTPIsPreferred(factors: SignInFirstFactor[], identifier: string | null) {
const passkeyFactor = findPasskeyStrategy(factors);
if (passkeyFactor) {
return passkeyFactor;
Expand Down Expand Up @@ -97,7 +94,7 @@ function determineStrategyWhenOTPIsPreferred(factors: SignInFactor[], identifier
}

// The priority of second factors is: TOTP -> Phone code -> any other factor
export function determineStartingSignInSecondFactor(secondFactors: SignInFactor[]): SignInFactor | null {
export function determineStartingSignInSecondFactor(secondFactors: SignInSecondFactor[]) {
if (!secondFactors || secondFactors.length === 0) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import type {
PasswordAttempt,
PhoneCodeAttempt,
PrepareFirstFactorParams,
PrepareSecondFactorParams,
ResetPasswordEmailCodeAttempt,
ResetPasswordPhoneCodeAttempt,
SignInFactor,
SignInFirstFactor,
SignInResource,
SignInSecondFactor,
Expand All @@ -19,7 +19,7 @@ import { assign, fromPromise, log, sendTo, setup } from 'xstate';
import { RESENDABLE_COUNTDOWN_DEFAULT } from '~/internals/constants';
import { ClerkElementsRuntimeError } from '~/internals/errors';
import type { FormFields } from '~/internals/machines/form';
import type { WithParams } from '~/internals/machines/shared';
import type { SignInStrategyName, WithParams } from '~/internals/machines/shared';
import { sendToLoading } from '~/internals/machines/shared';
import { determineStartingSignInFactor, determineStartingSignInSecondFactor } from '~/internals/machines/sign-in/utils';
import { assertActorEventError, assertIsDefined } from '~/internals/machines/utils/assert';
Expand All @@ -35,11 +35,11 @@ export type DetermineStartingFactorInput = {
parent: SignInRouterMachineActorRef;
};

export type PrepareFirstFactorInput = WithParams<SignInFirstFactor | null> & {
export type PrepareFirstFactorInput = WithParams<PrepareFirstFactorParams> & {
parent: SignInRouterMachineActorRef;
resendable: boolean;
};
export type PrepareSecondFactorInput = WithParams<SignInSecondFactor | null> & {
export type PrepareSecondFactorInput = WithParams<PrepareSecondFactorParams> & {
parent: SignInRouterMachineActorRef;
resendable: boolean;
};
Expand Down Expand Up @@ -67,8 +67,8 @@ export const SignInVerificationMachineId = 'SignInVerification';

const SignInVerificationMachine = setup({
actors: {
determineStartingFactor: fromPromise<SignInFactor | null, DetermineStartingFactorInput>(() =>
Promise.reject(new ClerkElementsRuntimeError('Actor `determineStartingFactor` must be overridden')),
determineStartingFactor: fromPromise<SignInFirstFactor | SignInSecondFactor | null, DetermineStartingFactorInput>(
() => Promise.reject(new ClerkElementsRuntimeError('Actor `determineStartingFactor` must be overridden')),
),
prepare: fromPromise<SignInResource, PrepareFirstFactorInput | PrepareSecondFactorInput>(() =>
Promise.reject(new ClerkElementsRuntimeError('Actor `prepare` must be overridden')),
Expand Down Expand Up @@ -96,10 +96,7 @@ const SignInVerificationMachine = setup({
// Only show these warnings in development!
if (process.env.NODE_ENV === 'development') {
if (
!clerk.client.signIn.supportedFirstFactors.every(factor =>
// TODO: These "as SignInFactor" assertions are incorrect, factor.strategy is SignInFactor['strategy']. This needs to be fixed together with SignInVerificationStrategyRegisterEvent and SignInStrategy React component
context.registeredStrategies.has(factor.strategy as unknown as SignInFactor),
)
!clerk.client.signIn.supportedFirstFactors.every(factor => context.registeredStrategies.has(factor.strategy))
) {
console.warn(
`Clerk: Your instance is configured to support these strategies: ${clerk.client.signIn.supportedFirstFactors
Expand All @@ -112,9 +109,7 @@ const SignInVerificationMachine = setup({

if (
clerk.client.signIn.supportedSecondFactors &&
!clerk.client.signIn.supportedSecondFactors.every(factor =>
context.registeredStrategies.has(factor.strategy as unknown as SignInFactor),
)
!clerk.client.signIn.supportedSecondFactors.every(factor => context.registeredStrategies.has(factor.strategy))
) {
console.warn(
`Clerk: Your instance is configured to support these 2FA strategies: ${clerk.client.signIn.supportedSecondFactors
Expand All @@ -125,26 +120,17 @@ const SignInVerificationMachine = setup({
);
}

// TODO: These "as SignInFactor" assertions are incorrect, factor.strategy is SignInFactor['strategy']. This needs to be fixed together with SignInVerificationStrategyRegisterEvent and SignInStrategy React component
// This type should also probably be SignInFirstFactor['strategy'] instead of SignInFactor['strategy'] !!!
const strategiesUsedButNotActivated = Array.from(context.registeredStrategies).filter(
strategy =>
!clerk.client.signIn.supportedFirstFactors.some(
supported => (supported.strategy as unknown as SignInFactor) === strategy,
),
) as unknown as Array<SignInFactor['strategy']>;
strategy => !clerk.client.signIn.supportedFirstFactors.some(supported => supported.strategy === strategy),
);

if (strategiesUsedButNotActivated.length > 0) {
console.warn(
`Clerk: These rendered strategies are not configured for your instance: ${strategiesUsedButNotActivated.join(', ')}. If this is unexpected, make sure to enable them in your Clerk dashboard: https://dashboard.clerk.com/last-active?path=/user-authentication/email-phone-username`,
);
}

if (
context.currentFactor?.strategy &&
// TODO: These "as SignInFactor" assertions are incorrect, factor.strategy is SignInFactor['strategy']. This needs to be fixed together with SignInVerificationStrategyRegisterEvent and SignInStrategy React component
!context.registeredStrategies.has(context.currentFactor?.strategy as unknown as SignInFactor)
) {
if (context.currentFactor?.strategy && !context.registeredStrategies.has(context.currentFactor?.strategy)) {
throw new ClerkElementsRuntimeError(
`Your sign-in attempt is missing a ${context.currentFactor?.strategy} strategy. Make sure <Strategy name="${context.currentFactor?.strategy}"> is rendered in your flow. More information: https://clerk.com/docs/elements/reference/sign-in#strategy`,
);
Expand Down Expand Up @@ -195,7 +181,7 @@ const SignInVerificationMachine = setup({
formRef: input.formRef,
loadingStep: 'verifications',
parent: input.parent,
registeredStrategies: new Set<SignInFactor>(),
registeredStrategies: new Set<SignInStrategyName>(),
resendable: false,
resendableAfter: RESENDABLE_COUNTDOWN_DEFAULT,
}),
Expand Down Expand Up @@ -250,7 +236,7 @@ const SignInVerificationMachine = setup({
input: ({ context }) => ({
parent: context.parent,
resendable: context.resendable,
params: context.currentFactor as SignInFirstFactor | null,
params: context.currentFactor as PrepareFirstFactorParams,
}),
onDone: {
actions: 'resendableReset',
Expand Down Expand Up @@ -348,7 +334,7 @@ const SignInVerificationMachine = setup({
src: 'attempt',
input: ({ context }) => ({
parent: context.parent,
currentFactor: context.currentFactor as SignInFirstFactor,
currentFactor: context.currentFactor as SignInFirstFactor | null,
fields: context.formRef.getSnapshot().context.fields,
}),
onDone: {
Expand Down Expand Up @@ -380,7 +366,7 @@ export const SignInFirstFactorMachine = SignInVerificationMachine.provide({
);
}),
prepare: fromPromise(async ({ input }) => {
const { params, parent, resendable } = input;
const { params, parent, resendable } = input as PrepareFirstFactorInput;
const clerk = parent.getSnapshot().context.clerk;

// If a prepare call has already been fired recently, don't re-send
Expand All @@ -393,7 +379,7 @@ export const SignInFirstFactorMachine = SignInVerificationMachine.provide({

assertIsDefined(params, 'First factor params');

return await clerk.client.signIn.prepareFirstFactor(params as PrepareFirstFactorParams);
return await clerk.client.signIn.prepareFirstFactor(params);
}),
attempt: fromPromise(async ({ input }) => {
const { currentFactor, fields, parent } = input as AttemptFirstFactorInput;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SignInFactor } from '@clerk/types';
import type { ActorRefFrom, ErrorActorEvent } from 'xstate';

import type { FormMachine } from '~/internals/machines/form';
import type { SignInStrategyName } from '~/internals/machines/shared';

import type { SignInRouterMachineActorRef } from './router.types';

Expand All @@ -21,8 +22,8 @@ export type SignInVerificationTags =
export type SignInVerificationSubmitEvent = { type: 'SUBMIT' };
export type SignInVerificationFactorUpdateEvent = { type: 'STRATEGY.UPDATE'; factor: SignInFactor | undefined };
export type SignInVerificationRetryEvent = { type: 'RETRY' };
export type SignInVerificationStrategyRegisterEvent = { type: 'STRATEGY.REGISTER'; factor: SignInFactor };
export type SignInVerificationStrategyUnregisterEvent = { type: 'STRATEGY.UNREGISTER'; factor: SignInFactor };
export type SignInVerificationStrategyRegisterEvent = { type: 'STRATEGY.REGISTER'; factor: SignInStrategyName };
export type SignInVerificationStrategyUnregisterEvent = { type: 'STRATEGY.UNREGISTER'; factor: SignInStrategyName };

export type SignInVerificationEvents =
| ErrorActorEvent
Expand All @@ -47,7 +48,7 @@ export interface SignInVerificationContext {
formRef: ActorRefFrom<typeof FormMachine>;
parent: SignInRouterMachineActorRef;
loadingStep: 'verifications';
registeredStrategies: Set<SignInFactor>;
registeredStrategies: Set<SignInStrategyName>;
resendable: boolean;
resendableAfter: number;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/elements/src/react/sign-in/verifications.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignInFactor, SignInStrategy as ClerkSignInStrategy } from '@clerk/types';
import type { SignInStrategy as ClerkSignInStrategy } from '@clerk/types';
import { useSelector } from '@xstate/react';
import { useCallback, useEffect } from 'react';
import type { ActorRefFrom, SnapshotFrom } from 'xstate';
Expand Down Expand Up @@ -76,12 +76,12 @@ export function SignInStrategy({ children, name }: SignInStrategyProps) {

useEffect(() => {
if (factorCtx) {
factorCtx.send({ type: 'STRATEGY.REGISTER', factor: name as unknown as SignInFactor });
factorCtx.send({ type: 'STRATEGY.REGISTER', factor: name });
}

return () => {
if (factorCtx?.getSnapshot().status === 'active') {
factorCtx.send({ type: 'STRATEGY.UNREGISTER', factor: name as unknown as SignInFactor });
factorCtx.send({ type: 'STRATEGY.UNREGISTER', factor: name });
}
};
}, [factorCtx, name]);
Expand Down

0 comments on commit bb3c625

Please sign in to comment.