Skip to content

Commit 6450ede

Browse files
authored
Merge branch 'main' into chris/mobile-405-react-native-components-release
2 parents eb3e46b + 06a633e commit 6450ede

32 files changed

Lines changed: 1217 additions & 81 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/upgrade': patch
3+
---
4+
5+
Improve CLI usability for monorepos: traverse parent directories for pnpm workspace detection and support named catalogs in version resolution

.changeset/pink-states-lead.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/shared': patch
3+
---
4+
5+
Fix TypeScript issue where `signIn.phoneCode.sendCode` expected an argument.

.changeset/proud-bats-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/ui': patch
3+
---
4+
5+
Fix "You must belong to an organization" screen showing after accepting an organization invitation

.changeset/strict-needles-taste.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/shared': minor
4+
---
5+
6+
Support `sign_up_if_missing` on SignIn.create, including captcha

packages/clerk-js/bundlewatch.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
{ "path": "./dist/clerk.chips.browser.js", "maxSize": "66KB" },
66
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "108KB" },
77
{ "path": "./dist/clerk.no-rhc.js", "maxSize": "307KB" },
8-
{ "path": "./dist/clerk.native.js", "maxSize": "65KB" },
8+
{ "path": "./dist/clerk.native.js", "maxSize": "66KB" },
99
{ "path": "./dist/vendors*.js", "maxSize": "7KB" },
1010
{ "path": "./dist/coinbase*.js", "maxSize": "36KB" },
1111
{ "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" },

packages/clerk-js/src/core/resources/SignIn.ts

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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';
8587
import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask';
8688
import { loadZxcvbn } from '../../utils/zxcvbn';
8789
import {
@@ -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

packages/clerk-js/src/core/resources/SignUp.ts

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,6 @@ export class SignUp extends BaseResource implements SignUpResource {
173173
finalParams = { ...finalParams, ...captchaParams };
174174
}
175175

176-
if (finalParams.transfer && this.shouldBypassCaptchaForAttempt(finalParams)) {
177-
const strategy = SignUp.clerk.client?.signIn.firstFactorVerification.strategy;
178-
if (strategy) {
179-
finalParams = { ...finalParams, strategy: strategy as SignUpCreateParams['strategy'] };
180-
}
181-
}
182-
183176
return this._basePost({
184177
path: this.pathRoot,
185178
body: normalizeUnsafeMetadata(finalParams),
@@ -561,22 +554,24 @@ export class SignUp extends BaseResource implements SignUpResource {
561554
* We delegate bot detection to the following providers, instead of relying on turnstile exclusively
562555
*/
563556
protected shouldBypassCaptchaForAttempt(params: SignUpCreateParams) {
564-
if (!params.strategy) {
565-
return false;
566-
}
567-
568557
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
569558
const captchaOauthBypass = SignUp.clerk.__internal_environment!.displayConfig.captchaOauthBypass;
570559

571-
if (captchaOauthBypass.some(strategy => strategy === params.strategy)) {
560+
// For transfers, inspect the SignIn strategy to determine bypass logic
561+
if (params.transfer && SignUp.clerk.client?.signIn?.firstFactorVerification?.status === 'transferable') {
562+
const signInStrategy = SignUp.clerk.client.signIn.firstFactorVerification.strategy;
563+
564+
// OAuth transfer: Check if strategy is in bypass list
565+
if (signInStrategy?.startsWith('oauth_')) {
566+
return captchaOauthBypass.some(strategy => strategy === signInStrategy);
567+
}
568+
569+
// Non-OAuth transfer (signUpIfMissing): Captcha already validated during SignIn
572570
return true;
573571
}
574572

575-
if (
576-
params.transfer &&
577-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
578-
captchaOauthBypass.some(strategy => strategy === SignUp.clerk.client!.signIn.firstFactorVerification.strategy)
579-
) {
573+
// For direct SignUp (not transfer), check OAuth bypass
574+
if (params.strategy && captchaOauthBypass.some(strategy => strategy === params.strategy)) {
580575
return true;
581576
}
582577

@@ -787,22 +782,24 @@ class SignUpFuture implements SignUpFutureResource {
787782
}
788783

789784
private shouldBypassCaptchaForAttempt(params: { strategy?: string; transfer?: boolean }) {
790-
if (!params.strategy) {
791-
return false;
792-
}
793-
794785
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
795786
const captchaOauthBypass = SignUp.clerk.__internal_environment!.displayConfig.captchaOauthBypass;
796787

797-
if (captchaOauthBypass.some(strategy => strategy === params.strategy)) {
788+
// For transfers, inspect the SignIn strategy to determine bypass logic
789+
if (params.transfer && SignUp.clerk.client?.signIn?.firstFactorVerification?.status === 'transferable') {
790+
const signInStrategy = SignUp.clerk.client.signIn.firstFactorVerification.strategy;
791+
792+
// OAuth transfer: Check if strategy is in bypass list
793+
if (signInStrategy?.startsWith('oauth_')) {
794+
return captchaOauthBypass.some(strategy => strategy === signInStrategy);
795+
}
796+
797+
// Non-OAuth transfer (signUpIfMissing): Captcha already validated during SignIn
798798
return true;
799799
}
800800

801-
if (
802-
params.transfer &&
803-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
804-
captchaOauthBypass.some(strategy => strategy === SignUp.clerk.client!.signIn.firstFactorVerification.strategy)
805-
) {
801+
// For direct SignUp (not transfer), check OAuth bypass
802+
if (params.strategy && captchaOauthBypass.some(strategy => strategy === params.strategy)) {
806803
return true;
807804
}
808805

0 commit comments

Comments
 (0)