diff --git a/.changeset/odd-squids-dress.md b/.changeset/odd-squids-dress.md new file mode 100644 index 00000000000..04e8745bdee --- /dev/null +++ b/.changeset/odd-squids-dress.md @@ -0,0 +1,7 @@ +--- +"@clerk/clerk-js": patch +--- + +Improve bot detection by loading the Turnstile SDK directly from CloudFlare. + +If loading fails due to CSP rules, load it through FAPI instead. diff --git a/packages/clerk-js/src/utils/captcha/turnstile.ts b/packages/clerk-js/src/utils/captcha/turnstile.ts index c286638e8ce..78e8229a4d4 100644 --- a/packages/clerk-js/src/utils/captcha/turnstile.ts +++ b/packages/clerk-js/src/utils/captcha/turnstile.ts @@ -3,6 +3,8 @@ import type { CaptchaWidgetType } from '@clerk/types'; import { CAPTCHA_ELEMENT_ID, CAPTCHA_INVISIBLE_CLASSNAME } from './constants'; +const CLOUDFLARE_TURNSTILE_ORIGINAL_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; + interface RenderOptions { /** * Every widget has a sitekey. This sitekey is associated with the corresponding widget configuration and is created upon the widget creation. @@ -69,21 +71,31 @@ export const shouldRetryTurnstileErrorCode = (errorCode: string) => { return !!codesWithRetries.find(w => errorCode.startsWith(w)); }; -async function loadCaptcha(url: string) { +async function loadCaptcha(fallbackUrl: string) { if (!window.turnstile) { - try { - await loadScript(url, { defer: true }); - } catch { - // Rethrow with specific message - console.error('Clerk: Failed to load the CAPTCHA script from the URL: ', url); - throw { - captchaError: 'captcha_script_failed_to_load', - }; - } + await loadCaptchaFromCloudflareURL() + .catch(() => loadCaptchaFromFAPIProxiedURL(fallbackUrl)) + .catch(() => { + throw { captchaError: 'captcha_script_failed_to_load' }; + }); } return window.turnstile; } +async function loadCaptchaFromCloudflareURL() { + return await loadScript(CLOUDFLARE_TURNSTILE_ORIGINAL_URL, { defer: true }); +} + +async function loadCaptchaFromFAPIProxiedURL(fallbackUrl: string) { + try { + return await loadScript(fallbackUrl, { defer: true }); + } catch (err) { + // Rethrow with specific message + console.error('Clerk: Failed to load the CAPTCHA script from the URL: ', fallbackUrl); + throw err; + } +} + /* * How this function works: * The widgetType is either 'invisible' or 'smart'.