@@ -4,26 +4,30 @@ import type {
44 EnvironmentResource ,
55 OAuthStrategy ,
66 PrepareFirstFactorParams ,
7+ PrepareSecondFactorParams ,
78 SignInFactor ,
89 SignInFirstFactor ,
910 SignInResource ,
11+ SignInSecondFactor ,
1012 Web3Strategy ,
1113} from '@clerk/types' ;
1214import type { ActorRefFrom , ErrorActorEvent , MachineContext } from 'xstate' ;
13- import { and , assertEvent , assign , log , not , or , sendTo , setup } from 'xstate' ;
15+ import { and , assertEvent , assign , not , or , sendTo , setup } from 'xstate' ;
1416
1517import type { ClerkRouter } from '../router' ;
1618import type { FormMachine } from './form.machine' ;
1719import { waitForClerk } from './shared.actors' ;
1820import {
1921 attemptFirstFactor ,
22+ attemptSecondFactor ,
2023 authenticateWithRedirect ,
2124 createSignIn ,
2225 handleSSOCallback ,
2326 prepareFirstFactor ,
27+ prepareSecondFactor ,
2428} from './sign-in.actors' ;
2529import type { LoadedClerkWithEnv } from './sign-in.types' ;
26- import { determineStartingSignInFactor } from './sign-in.utils' ;
30+ import { determineStartingSignInFactor , determineStartingSignInSecondFactor } from './sign-in.utils' ;
2731import { assertActorEventError } from './utils/assert' ;
2832
2933export interface SignInMachineContext extends MachineContext {
@@ -72,8 +76,12 @@ export const SignInMachine = setup({
7276 createSignIn,
7377
7478 // First Factor
75- attemptFirstFactor,
7679 prepareFirstFactor,
80+ attemptFirstFactor,
81+
82+ // Second Factor
83+ prepareSecondFactor,
84+ attemptSecondFactor,
7785
7886 // SSO
7987 handleSSOCallback,
@@ -105,6 +113,7 @@ export const SignInMachine = setup({
105113 isServer : ( { context } ) => context . mode === 'server' ,
106114 isBrowser : ( { context } ) => context . mode === 'browser' ,
107115 isCurrentFactorPassword : ( { context } ) => context . currentFactor ?. strategy === 'password' ,
116+ isCurrentFactorTOTP : ( { context } ) => context . currentFactor ?. strategy === 'totp' ,
108117 isClerkLoaded : ( { context } ) => context . clerk . loaded ,
109118 isClerkEnvironmentLoaded : ( { context } ) => Boolean ( context . clerk . __unstable__environment ) ,
110119 isSignInComplete : ( { context } ) => context ?. resource ?. status === 'complete' ,
@@ -292,12 +301,6 @@ export const SignInMachine = setup({
292301 states : {
293302 DeterminingState : {
294303 always : [
295- {
296- description : 'If the current factor is not set, determine the starting factor details' ,
297- guard : not ( 'hasCurrentFactor' ) ,
298- target : 'DetermineStartingFactor' ,
299- reenter : true ,
300- } ,
301304 {
302305 description : 'If the current factor is not password, prepare the factor' ,
303306 guard : not ( 'isCurrentFactorPassword' ) ,
@@ -387,7 +390,7 @@ export const SignInMachine = setup({
387390 } ,
388391 {
389392 guard : 'needsSecondFactor' ,
390- target : '#SignIn.FirstFactor ' ,
393+ target : '#SignIn.SecondFactor ' ,
391394 } ,
392395 {
393396 target : '#SignIn.DeterminingState' ,
@@ -403,8 +406,104 @@ export const SignInMachine = setup({
403406 } ,
404407 } ,
405408 SecondFactor : {
406- type : 'final' ,
407- actions : [ log ( 'SecondFactor' ) , 'debug' ] ,
409+ initial : 'DeterminingState' ,
410+ entry : assign ( {
411+ currentFactor : ( { context } ) =>
412+ determineStartingSignInSecondFactor ( context . clerk . client . signIn . supportedSecondFactors ) ,
413+ } ) ,
414+ states : {
415+ DeterminingState : {
416+ always : [
417+ {
418+ description : 'If the current factor is not TOTP, prepare the factor' ,
419+ guard : not ( 'isCurrentFactorTOTP' ) ,
420+ target : 'Preparing' ,
421+ reenter : true ,
422+ } ,
423+ {
424+ description : 'Else, skip to awaiting input' ,
425+ target : 'AwaitingInput' ,
426+ reenter : true ,
427+ } ,
428+ ] ,
429+ } ,
430+ Preparing : {
431+ invoke : {
432+ id : 'prepareSecondFactor' ,
433+ src : 'prepareSecondFactor' ,
434+ input : ( { context } ) => {
435+ return {
436+ client : context . clerk . client ,
437+ params : ! context . currentFactor ? null : ( context . currentFactor as PrepareSecondFactorParams ) ,
438+ } ;
439+ } ,
440+ onDone : {
441+ target : 'AwaitingInput' ,
442+ actions : [
443+ assign ( {
444+ resource : ( { event } ) => event . output ,
445+ } ) ,
446+ ] ,
447+ } ,
448+ onError : {
449+ actions : 'setFormErrors' ,
450+ target : 'Failure' ,
451+ } ,
452+ } ,
453+ } ,
454+ AwaitingInput : {
455+ description : 'Waiting for user input' ,
456+ on : {
457+ SUBMIT : {
458+ target : 'Attempting' ,
459+ reenter : true ,
460+ } ,
461+ } ,
462+ } ,
463+ Attempting : {
464+ invoke : {
465+ id : 'attemptSecondFactor' ,
466+ src : 'attemptSecondFactor' ,
467+ input : ( { context } ) => ( {
468+ client : context . clerk . client ,
469+ params : {
470+ currentFactor : context . currentFactor as SignInSecondFactor ,
471+ fields : context . formRef . getSnapshot ( ) . context . fields ,
472+ } ,
473+ } ) ,
474+ onDone : {
475+ actions : [
476+ assign ( {
477+ resource : ( { event } ) => event . output ,
478+ } ) ,
479+ ] ,
480+ target : 'Success' ,
481+ } ,
482+ onError : {
483+ actions : 'setFormErrors' ,
484+ target : 'AwaitingInput' ,
485+ } ,
486+ } ,
487+ } ,
488+ Success : {
489+ type : 'final' ,
490+ always : [
491+ {
492+ guard : 'isSignInComplete' ,
493+ target : '#SignIn.Complete' ,
494+ } ,
495+ {
496+ target : '#SignIn.DeterminingState' ,
497+ reenter : true ,
498+ } ,
499+ ] ,
500+ } ,
501+ Failure : {
502+ type : 'final' ,
503+ target : '#SignIn.DeterminingState' ,
504+ reenter : true ,
505+ } ,
506+ } ,
408507 } ,
409508 SSOCallbackRunning : {
410509 invoke : {
0 commit comments