diff --git a/.changeset/violet-lobsters-scream.md b/.changeset/violet-lobsters-scream.md new file mode 100644 index 00000000000..f533bcefc06 --- /dev/null +++ b/.changeset/violet-lobsters-scream.md @@ -0,0 +1,5 @@ +--- +'@clerk/elements': patch +--- + +Fixes a bug where multiple verification codes were sent at once. 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 9be31fab59a..d439ddb8f02 100644 --- a/packages/elements/src/internals/machines/sign-in/verification.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/verification.machine.ts @@ -34,9 +34,11 @@ type Parent = ActorRefFrom; export type PrepareFirstFactorInput = WithParams & { parent: Parent; + resendable: boolean; }; export type PrepareSecondFactorInput = WithParams & { parent: Parent; + resendable: boolean; }; export type AttemptFirstFactorInput = { parent: Parent; fields: FormFields; currentFactor: SignInFirstFactor | null }; @@ -171,6 +173,7 @@ const SignInVerificationMachine = setup({ src: 'prepare', input: ({ context }) => ({ parent: context.parent, + resendable: context.resendable, params: context.currentFactor as SignInFirstFactor | null, }), onDone: { @@ -267,10 +270,14 @@ const SignInVerificationMachine = setup({ export const SignInFirstFactorMachine = SignInVerificationMachine.provide({ actors: { prepare: fromPromise(async ({ input }) => { - const { params, parent } = input; + const { params, parent, resendable } = input; const clerk = parent.getSnapshot().context.clerk; - if (!params?.strategy || params.strategy === 'password') { + // If a prepare call has already been fired recently, don't re-send + const currentVerificationExpiration = clerk.client.signIn.firstFactorVerification.expireAt; + const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date(); + + if (!params?.strategy || params.strategy === 'password' || !needsPrepare) { return Promise.resolve(clerk.client.signIn); } @@ -359,12 +366,16 @@ export const SignInFirstFactorMachine = SignInVerificationMachine.provide({ export const SignInSecondFactorMachine = SignInVerificationMachine.provide({ actors: { prepare: fromPromise(({ input }) => { - const { params, parent } = input; + const { params, parent, resendable } = input; const clerk = parent.getSnapshot().context.clerk; + // If a prepare call has already been fired recently, don't re-send + const currentVerificationExpiration = clerk.client.signIn.secondFactorVerification.expireAt; + const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date(); + assertIsDefined(params); - if (params.strategy !== 'phone_code') { + if (params.strategy !== 'phone_code' || !needsPrepare) { return Promise.resolve(clerk.client.signIn); }