@@ -16,6 +16,7 @@ import type {
1616 AuthenticateWithPopupParams ,
1717 AuthenticateWithRedirectParams ,
1818 AuthenticateWithWeb3Params ,
19+ CaptchaWidgetType ,
1920 ClientTrustState ,
2021 CreateEmailLinkFlowReturn ,
2122 EmailCodeConfig ,
@@ -82,6 +83,7 @@ import {
8283 _futureAuthenticateWithPopup ,
8384 wrapWithPopupRoutes ,
8485} from '../../utils/authenticateWithPopup' ;
86+ import { CaptchaChallenge } from '../../utils/captcha/CaptchaChallenge' ;
8587import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask' ;
8688import { loadZxcvbn } from '../../utils/zxcvbn' ;
8789import {
@@ -164,12 +166,34 @@ export class SignIn extends BaseResource implements SignInResource {
164166 this . fromJSON ( data ) ;
165167 }
166168
167- create = ( params : SignInCreateParams ) : Promise < SignInResource > => {
169+ create = async ( params : SignInCreateParams ) : Promise < SignInResource > => {
168170 debugLogger . debug ( 'SignIn.create' , { id : this . id , strategy : 'strategy' in params ? params . strategy : undefined } ) ;
169- const locale = getBrowserLocale ( ) ;
171+
172+ let body : Record < string , unknown > = { ...params } ;
173+
174+ // Inject browser locale
175+ const browserLocale = getBrowserLocale ( ) ;
176+ if ( browserLocale ) {
177+ body . locale = browserLocale ;
178+ }
179+
180+ if (
181+ this . shouldRequireCaptcha ( params ) &&
182+ ! __BUILD_DISABLE_RHC__ &&
183+ ! this . clientBypass ( ) &&
184+ ! this . shouldBypassCaptchaForAttempt ( params )
185+ ) {
186+ const captchaChallenge = new CaptchaChallenge ( SignIn . clerk ) ;
187+ const captchaParams = await captchaChallenge . managedOrInvisible ( { action : 'signin' } ) ;
188+ if ( ! captchaParams ) {
189+ throw new ClerkRuntimeError ( '' , { code : 'captcha_unavailable' } ) ;
190+ }
191+ body = { ...body , ...captchaParams } ;
192+ }
193+
170194 return this . _basePost ( {
171195 path : this . pathRoot ,
172- body : locale ? { locale , ... params } : params ,
196+ body : body ,
173197 } ) ;
174198 } ;
175199
@@ -576,6 +600,43 @@ export class SignIn extends BaseResource implements SignInResource {
576600 return this ;
577601 }
578602
603+ private clientBypass ( ) {
604+ return SignIn . clerk . client ?. captchaBypass ;
605+ }
606+
607+ /**
608+ * Determines whether captcha is required for sign in based on the provided params.
609+ * Add new conditions here as captcha requirements evolve.
610+ */
611+ private shouldRequireCaptcha ( params : SignInCreateParams ) : boolean {
612+ if ( 'signUpIfMissing' in params && params . signUpIfMissing ) {
613+ return true ;
614+ }
615+
616+ return false ;
617+ }
618+
619+ /**
620+ * We delegate bot detection to the following providers, instead of relying on turnstile exclusively
621+ */
622+ protected shouldBypassCaptchaForAttempt ( params : SignInCreateParams ) {
623+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
624+ const captchaOauthBypass = SignIn . clerk . __internal_environment ! . displayConfig . captchaOauthBypass ;
625+
626+ // Check transfer strategy against bypass list
627+ if ( params . transfer && SignIn . clerk . client ?. signUp ?. verifications ?. externalAccount ?. status === 'transferable' ) {
628+ const signUpStrategy = SignIn . clerk . client . signUp . verifications . externalAccount . strategy ;
629+ return signUpStrategy ? captchaOauthBypass . some ( strategy => strategy === signUpStrategy ) : false ;
630+ }
631+
632+ // Check direct strategy against bypass list
633+ if ( 'strategy' in params && params . strategy ) {
634+ return captchaOauthBypass . some ( strategy => strategy === params . strategy ) ;
635+ }
636+
637+ return false ;
638+ }
639+
579640 public __internal_updateFromJSON ( data : SignInJSON | SignInJSONSnapshot | null ) : this {
580641 return this . fromJSON ( data ) ;
581642 }
@@ -814,11 +875,80 @@ class SignInFuture implements SignInFutureResource {
814875 } ) ;
815876 }
816877
878+ /**
879+ * Determines whether captcha is required for sign in based on the provided params.
880+ * Add new conditions here as captcha requirements evolve.
881+ */
882+ private shouldRequireCaptcha ( params : { signUpIfMissing ?: boolean } ) : boolean {
883+ if ( params . signUpIfMissing ) {
884+ return true ;
885+ }
886+
887+ return false ;
888+ }
889+
890+ private shouldBypassCaptchaForAttempt ( params : { strategy ?: string ; transfer ?: boolean } ) {
891+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
892+ const captchaOauthBypass = SignIn . clerk . __internal_environment ! . displayConfig . captchaOauthBypass ;
893+
894+ // Check transfer strategy against bypass list
895+ if ( params . transfer && SignIn . clerk . client ?. signUp ?. verifications ?. externalAccount ?. status === 'transferable' ) {
896+ const signUpStrategy = SignIn . clerk . client . signUp . verifications . externalAccount . strategy ;
897+ return signUpStrategy ? captchaOauthBypass . some ( strategy => strategy === signUpStrategy ) : false ;
898+ }
899+
900+ // Check direct strategy against bypass list
901+ if ( params . strategy ) {
902+ return captchaOauthBypass . some ( strategy => strategy === params . strategy ) ;
903+ }
904+
905+ return false ;
906+ }
907+
908+ private async getCaptchaToken (
909+ params : { strategy ?: string ; transfer ?: boolean ; signUpIfMissing ?: boolean } = { } ,
910+ ) : Promise < {
911+ captchaToken ?: string ;
912+ captchaWidgetType ?: CaptchaWidgetType ;
913+ captchaError ?: unknown ;
914+ } > {
915+ if (
916+ ! this . shouldRequireCaptcha ( params ) ||
917+ __BUILD_DISABLE_RHC__ ||
918+ SignIn . clerk . client ?. captchaBypass ||
919+ this . shouldBypassCaptchaForAttempt ( params )
920+ ) {
921+ return {
922+ captchaToken : undefined ,
923+ captchaWidgetType : undefined ,
924+ captchaError : undefined ,
925+ } ;
926+ }
927+
928+ const captchaChallenge = new CaptchaChallenge ( SignIn . clerk ) ;
929+ const response = await captchaChallenge . managedOrInvisible ( { action : 'signin' } ) ;
930+ if ( ! response ) {
931+ throw new Error ( 'Captcha challenge failed' ) ;
932+ }
933+
934+ const { captchaError, captchaToken, captchaWidgetType } = response ;
935+ return { captchaToken, captchaWidgetType, captchaError } ;
936+ }
937+
817938 private async _create ( params : SignInFutureCreateParams ) : Promise < void > {
818- const locale = getBrowserLocale ( ) ;
939+ const { captchaToken, captchaWidgetType, captchaError } = await this . getCaptchaToken ( params ) ;
940+
941+ const body : Record < string , unknown > = {
942+ ...params ,
943+ captchaToken,
944+ captchaWidgetType,
945+ captchaError,
946+ locale : getBrowserLocale ( ) || undefined ,
947+ } ;
948+
819949 await this . #resource. __internal_basePost ( {
820950 path : this . #resource. pathRoot ,
821- body : locale ? { locale , ... params } : params ,
951+ body,
822952 } ) ;
823953 }
824954
0 commit comments