diff --git a/.changeset/config.json b/.changeset/config.json
index ace011cb04b..bce23d1858d 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -10,7 +10,7 @@
"fixed": [],
"linked": [],
"access": "public",
- "baseBranch": "origin/main",
+ "baseBranch": "origin/release/core-2",
"updateInternalDependencies": "patch",
"snapshot": {
"useCalculatedVersion": true,
diff --git a/.changeset/every-badgers-wish.md b/.changeset/every-badgers-wish.md
new file mode 100644
index 00000000000..bbdf9c79003
--- /dev/null
+++ b/.changeset/every-badgers-wish.md
@@ -0,0 +1,5 @@
+---
+'@clerk/backend': patch
+---
+
+Updates `OrganizationInvitationStatus` to include `expired` to match the API updates.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 61953716e78..ad05f9acdc3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,9 +4,7 @@ on:
merge_group:
pull_request:
branches:
- - main
- - release/v4
- - vincent-and-the-doctor
+ - release/core-2
permissions:
contents: read
@@ -82,7 +80,7 @@ jobs:
echo 'Skipping';
exit 0;
else
- pnpm changeset status --since=origin/main;
+ pnpm changeset status --since=origin/release/core-2;
fi
build-packages:
diff --git a/.github/workflows/nightly-checks.yml b/.github/workflows/nightly-checks.yml
deleted file mode 100644
index b8edac5a23a..00000000000
--- a/.github/workflows/nightly-checks.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-name: Nightly upstream tests
-on:
- workflow_dispatch:
- schedule:
- - cron: '0 7 * * *'
-
-jobs:
- integration-tests:
- name: Integration Tests
- runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }}
- timeout-minutes: ${{ vars.TIMEOUT_MINUTES_EXTENDED && fromJSON(vars.TIMEOUT_MINUTES_EXTENDED) || 30 }}
-
- strategy:
- matrix:
- test-name: ['nextjs']
-
- steps:
- - name: Checkout Repo
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- show-progress: false
-
- - name: Setup
- id: config
- uses: ./.github/actions/init
- with:
- turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- turbo-team: ${{ vars.TURBO_TEAM }}
- turbo-token: ${{ secrets.TURBO_TOKEN }}
- playwright-enabled: true
-
- - name: Verdaccio
- uses: ./.github/actions/verdaccio
- with:
- publish-cmd: |
- if [ "$(npm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag; fi
-
- - name: Install @clerk/backend in /integration
- working-directory: ./integration
- run: pnpm init && pnpm add @clerk/backend
-
- - name: Install @clerk/clerk-js in os temp
- working-directory: ${{runner.temp}}
- run: mkdir clerk-js && cd clerk-js && pnpm init && pnpm add @clerk/clerk-js
-
- - name: Run Integration Tests
- id: integration_tests
- continue-on-error: true
- run: |
- # Capture the output and exit code
- OUTPUT_FILE="${{runner.temp}}/test-output.log"
- # Only run Typedoc tests for one matrix version
- if [ "${{ matrix.test-name }}" == "nextjs" ]; then
- E2E_DEBUG=1 E2E_APP_ID=quickstart.next.appRouter pnpm test:integration:base --grep @quickstart 2>&1 | tee "$OUTPUT_FILE"
- else
- E2E_DEBUG=1 pnpm turbo test:integration:${{ matrix.test-name }} $TURBO_ARGS --only 2>&1 | tee "$OUTPUT_FILE"
- fi
- echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
- env:
- E2E_APP_CLERK_JS_DIR: ${{runner.temp}}
- E2E_CLERK_VERSION: 'latest'
- E2E_NEXTJS_VERSION: 'canary'
- E2E_NPM_FORCE: 'true'
- E2E_REACT_DOM_VERSION: '19.1.0'
- E2E_REACT_VERSION: '19.1.0'
- INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }}
- MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }}
-
- # Upload test artifacts if tests failed
- - name: Upload Test Artifacts
- if: steps.integration_tests.outputs.exit_code != '0'
- uses: actions/upload-artifact@v4
- with:
- name: test-artifacts-${{ matrix.test-name }}
- path: |
- ${{runner.temp}}/test-output.log
- integration/test-results/
- integration/.next/
- ${{runner.temp}}/clerk-js/node_modules/
- retention-days: 7
-
- - name: Report Status
- if: always()
- uses: ravsamhq/notify-slack-action@v1
- with:
- status: ${{ steps.integration_tests.outputs.exit_code == '0' && 'success' || 'failure' }}
- notify_when: 'failure'
- notification_title: 'Integration Test Failure - ${{ matrix.test-name }}'
- message_format: |
- *Job:* ${{ github.workflow }} (${{ matrix.test-name }})
- *Status:* ${{ steps.integration_tests.outputs.exit_code == '0' && 'Success' || 'Failed' }}
- *Commit:* ${{ github.sha }}
- *PR:* ${{ github.event.pull_request.html_url }}
- *Artifacts:* ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_SDK_E2E_ALERTS_WEBHOOK_URL }}
-
- # Fail the workflow if tests failed
- - name: Check Test Status
- if: steps.integration_tests.outputs.exit_code != '0'
- run: exit 1
diff --git a/.github/workflows/preview.retheme.yml b/.github/workflows/preview.retheme.yml
deleted file mode 100644
index bd54bb93071..00000000000
--- a/.github/workflows/preview.retheme.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Deploy Retheme Preview
-run-name: Deploy Retheme Preview
-
-env:
- VERCEL_ORG_ID: ${{ secrets.VERCEL_CLERK_PROD_ORG_ID }}
- VERCEL_PROJECT_ID: ${{ secrets.VERCEL_JS_RETHEME_PROJECT_ID }}
-
-on:
- push:
- branches:
- - main
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.actor }}
- cancel-in-progress: true
-
-jobs:
- preview:
- runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }}
- timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }}
- permissions:
- contents: write
- pull-requests: write
- steps:
- - name: Checkout repo
- uses: actions/checkout@v4
-
- - name: Setup
- id: config
- uses: ./.github/actions/init
- with:
- turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- turbo-team: ${{ vars.TURBO_TEAM }}
- turbo-token: ${{ secrets.TURBO_TOKEN }}
- registry-url: 'https://registry.npmjs.org'
-
- - name: Build packages
- run: pnpm turbo build $TURBO_ARGS
-
- - name: Install site in isolation
- run: node scripts/install-site-in-isolation.mjs playground/nextjs
-
- - name: Install Vercel CLI
- run: pnpm add -g vercel@latest
-
- - name: Pull Vercel environment information
- run: |
- cd $FULL_TMP_FOLDER
- vercel pull --yes --environment=production --token=${{ secrets.VERCEL_CLERK_COOKIE_TOKEN }}
-
- - name: Copy clerk-js/dist into public/clerk-js of test site
- run: |
- cp -r $GITHUB_WORKSPACE/packages/clerk-js/dist $FULL_TMP_FOLDER/public/clerk-js
-
- - name: Build with Vercel
- run: |
- cd $FULL_TMP_FOLDER
- vercel build --yes --prod
- env:
- NEXT_PUBLIC_CLERK_JS_URL: /clerk-js/clerk.browser.js
-
- - name: Deploy to Vercel (prebuilt)
- id: vercel-deploy
- run: |
- cd $FULL_TMP_FOLDER
- vercel deploy --prebuilt --token=${{ secrets.VERCEL_CLERK_COOKIE_TOKEN }} --no-wait --prod > deployment_url.txt
- echo "url=$(cat deployment_url.txt)" >> $GITHUB_OUTPUT
diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml
deleted file mode 100644
index d62475ce52b..00000000000
--- a/.github/workflows/release-canary.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-name: Canary release
-run-name: Canary release from ${{ github.ref_name }}
-
-on:
- push:
- branches:
- - main
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- canary-release:
- if: ${{ github.repository == 'clerk/javascript' }}
- runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }}
- timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }}
- env:
- TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
- TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- TURBO_CACHE: remote:rw
- permissions:
- contents: read
- id-token: write
- steps:
- - name: Checkout repo
- uses: actions/checkout@v4
-
- - name: Setup
- id: config
- uses: ./.github/actions/init
- with:
- turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- turbo-team: ${{ vars.TURBO_TEAM }}
- turbo-token: ${{ secrets.TURBO_TOKEN }}
- playwright-enabled: true # Must be present to enable caching on branched workflows
- registry-url: "https://registry.npmjs.org"
-
- - name: Version packages for canary
- id: version-packages
- run: pnpm version-packages:canary | tail -1 >> "$GITHUB_OUTPUT"
-
- - name: Build release
- if: steps.version-packages.outputs.success == '1'
- run: pnpm turbo build $TURBO_ARGS
-
- - name: Canary release
- if: steps.version-packages.outputs.success == '1'
- run: pnpm release:canary
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- NPM_CONFIG_PROVENANCE: true
-
- - name: Trigger workflows on related repos
- uses: actions/github-script@v7
- with:
- result-encoding: string
- retries: 3
- retry-exempt-status-codes: 400,401
- github-token: ${{ secrets.CLERK_COOKIE_PAT }}
- script: |
- const clerkjsVersion = require('./packages/clerk-js/package.json').version;
- const nextjsVersion = require('./packages/nextjs/package.json').version;
-
- github.rest.actions.createWorkflowDispatch({
- owner: 'clerk',
- repo: 'sdk-infra-workers',
- workflow_id: 'update-pkg-versions.yml',
- ref: 'main',
- inputs: { clerkjsVersion: clerkjsVersion }
- })
-
- if (nextjsVersion.includes('canary')) {
- console.log('clerk/nextjs changed, will notify clerk/accounts');
- github.rest.actions.createWorkflowDispatch({
- owner: 'clerk',
- repo: 'accounts',
- workflow_id: 'release-staging.yml',
- ref: 'main',
- inputs: { version: nextjsVersion }
- })
- }
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8bb4c2020e7..cced56cad16 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,11 +1,10 @@
-name: Release
-run-name: Release
+name: Release (Core 2)
+run-name: Release (Core 2)
on:
push:
branches:
- - main
- - release/v4
+ - release/core-2
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -43,10 +42,10 @@ jobs:
id: config
uses: ./.github/actions/init
with:
- turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- turbo-team: ${{ vars.TURBO_TEAM }}
- turbo-token: ${{ secrets.TURBO_TOKEN }}
playwright-enabled: true # Must be present to enable caching on branched workflows
+ # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
+ # turbo-team: ${{ vars.TURBO_TEAM }}
+ # turbo-token: ${{ secrets.TURBO_TOKEN }}
- name: Build release
run: pnpm turbo build $TURBO_ARGS --force
@@ -55,8 +54,8 @@ jobs:
id: changesets
uses: changesets/action@v1
with:
- commit: "ci(repo): Version packages"
- title: "ci(repo): Version packages"
+ commit: "ci(repo): Version packages (Core 2)"
+ title: "ci(repo): Version packages (Core 2)"
publish: pnpm release
# Workaround for https://github.com/changesets/changesets/issues/421
version: pnpm version-packages
@@ -99,7 +98,7 @@ jobs:
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'clerk-docs',
- workflow_id: 'typedoc.yml',
+ workflow_id: 'typedoc-core-2.yml',
ref: 'main',
})
} else{
@@ -139,6 +138,9 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v4
with:
+ fetch-depth: 1
+ fetch-tags: false
+ filter: "blob:none"
show-progress: false
- name: Cache node_modules (Node v${{ matrix.version }})
diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs
index dbfca8f057f..233c6b7c39a 100644
--- a/.typedoc/custom-plugin.mjs
+++ b/.typedoc/custom-plugin.mjs
@@ -33,6 +33,8 @@ const FILES_WITHOUT_HEADINGS = [
'use-reverification-params.mdx',
'payment-element-provider-props.mdx',
'payment-element-props.mdx',
+ 'use-organization-creation-defaults-return.mdx',
+ 'use-organization-creation-defaults-params.mdx',
];
/**
@@ -71,6 +73,7 @@ const LINK_REPLACEMENTS = [
['phone-number', '/docs/reference/backend/types/backend-phone-number'],
['saml-account', '/docs/reference/backend/types/backend-saml-account'],
['web3-wallet', '/docs/reference/backend/types/backend-web3-wallet'],
+ ['invitation', '/docs/reference/backend/types/backend-invitation'],
['verify-token-options', '#verify-token-options'],
['localization-resource', '/docs/guides/customizing-clerk/localization'],
['confirm-checkout-params', '/docs/reference/javascript/types/billing-checkout-resource#parameters'],
@@ -90,6 +93,7 @@ const LINK_REPLACEMENTS = [
['billing-payment-resource', '/docs/reference/javascript/types/billing-payment-resource'],
['deleted-object-resource', '/docs/reference/javascript/types/deleted-object-resource'],
['use-checkout-return', '/docs/reference/hooks/use-checkout#returns'],
+ ['organization-creation-defaults-resource', '#organization-creation-defaults-resource'],
];
/**
diff --git a/integration/README.md b/integration/README.md
index e6165c54dd8..edbe148964c 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -63,7 +63,7 @@ Additionally, you can use two flags to configure how Playwright runs:
For example:
```shell
-pnpm test:integration:base -- --ui email.link.test.ts
+pnpm test:integration:base --ui -- email.link.test.ts
```
> [!TIP]
diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index d9a617739b3..0629ca25514 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -154,10 +154,15 @@ const withSessionTasks = base
const withSessionTasksResetPassword = base
.clone()
.setId('withSessionTasksResetPassword')
- .setEnvVariable('private', 'CLERK_API_URL', 'https://api.clerkstage.dev')
.setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-session-tasks-reset-password').sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-session-tasks-reset-password').pk);
+const withSessionTasksSetupMfa = base
+ .clone()
+ .setId('withSessionTasksSetupMfa')
+ .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-session-tasks-setup-mfa').sk)
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-session-tasks-setup-mfa').pk);
+
const withBillingJwtV2 = base
.clone()
.setId('withBillingJwtV2')
@@ -211,6 +216,7 @@ export const envs = {
withReverification,
withSessionTasks,
withSessionTasksResetPassword,
+ withSessionTasksSetupMfa,
withSignInOrUpEmailLinksFlow,
withSignInOrUpFlow,
withSignInOrUpwithRestrictedModeFlow,
diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts
index a5acc533fc6..bb4f485540b 100644
--- a/integration/presets/longRunningApps.ts
+++ b/integration/presets/longRunningApps.ts
@@ -32,6 +32,7 @@ export const createLongRunningApps = () => {
{ id: 'next.appRouter.withSignInOrUpEmailLinksFlow', config: next.appRouter, env: envs.withSignInOrUpEmailLinksFlow },
{ id: 'next.appRouter.withSessionTasks', config: next.appRouter, env: envs.withSessionTasks },
{ id: 'next.appRouter.withSessionTasksResetPassword', config: next.appRouter, env: envs.withSessionTasksResetPassword },
+ { id: 'next.appRouter.withSessionTasksSetupMfa', config: next.appRouter, env: envs.withSessionTasksSetupMfa },
{ id: 'next.appRouter.withLegalConsent', config: next.appRouter, env: envs.withLegalConsent },
/**
@@ -59,6 +60,7 @@ export const createLongRunningApps = () => {
{ id: 'react.vite.withEmailCodes', config: react.vite, env: envs.withEmailCodes },
{ id: 'react.vite.withEmailCodes_persist_client', config: react.vite, env: envs.withEmailCodes_destroy_client },
{ id: 'react.vite.withEmailLinks', config: react.vite, env: envs.withEmailLinks },
+ { id: 'react.vite.withLegalConsent', config: react.vite, env: envs.withLegalConsent },
{ id: 'vue.vite', config: vue.vite, env: envs.withCustomRoles },
/**
diff --git a/integration/templates/astro-node/src/pages/billing/billing-store.astro b/integration/templates/astro-node/src/pages/billing/billing-store.astro
new file mode 100644
index 00000000000..1d2c2a7bb20
--- /dev/null
+++ b/integration/templates/astro-node/src/pages/billing/billing-store.astro
@@ -0,0 +1,45 @@
+---
+import Layout from '../../layouts/Layout.astro';
+---
+
+
+
+
+
+
+
+
diff --git a/integration/templates/astro-node/src/pages/index.astro b/integration/templates/astro-node/src/pages/index.astro
index 089eac14653..2f773ce2930 100644
--- a/integration/templates/astro-node/src/pages/index.astro
+++ b/integration/templates/astro-node/src/pages/index.astro
@@ -64,6 +64,11 @@ import { SignedIn, SignedOut, SignOutButton, OrganizationSwitcher } from '@clerk
title='For members'
body='Learn how Astro works and explore the official API docs.'
/>
+
diff --git a/integration/templates/astro-node/src/pages/prerendered.astro b/integration/templates/astro-node/src/pages/prerendered.astro
new file mode 100644
index 00000000000..c9d79d9d9b1
--- /dev/null
+++ b/integration/templates/astro-node/src/pages/prerendered.astro
@@ -0,0 +1,25 @@
+---
+import { SignedIn, SignedOut } from '@clerk/astro/components';
+import Layout from '../layouts/Layout.astro';
+
+// This page is prerendered at build time despite output: 'server' mode
+export const prerender = true;
+---
+
+
+ Prerendered Page with Clerk Components
+
+ This page is statically generated at build time (prerender = true) in server output mode.
+
+
+
+
✅ You are signed in! (This content should be hidden initially and shown after client-side auth check)
+
+
+
+
+
+
🔒 You are signed out. (This content should be visible initially for signed-out users)
+
+
+
diff --git a/integration/templates/react-vite/src/buttons/index.tsx b/integration/templates/react-vite/src/buttons/index.tsx
index 5aa32d433cf..cb5e6de7be1 100644
--- a/integration/templates/react-vite/src/buttons/index.tsx
+++ b/integration/templates/react-vite/src/buttons/index.tsx
@@ -11,6 +11,15 @@ export default function Home() {
Sign in button (force)
+
+ Sign in button (force, popup)
+
+
,
},
+ {
+ path: '/sign-in-popup/*',
+ element: ,
+ },
{
path: '/sign-up/*',
element: ,
diff --git a/integration/templates/react-vite/src/sign-in-popup/index.tsx b/integration/templates/react-vite/src/sign-in-popup/index.tsx
new file mode 100644
index 00000000000..b97b5841563
--- /dev/null
+++ b/integration/templates/react-vite/src/sign-in-popup/index.tsx
@@ -0,0 +1,15 @@
+import { SignIn } from '@clerk/clerk-react';
+
+export default function Page() {
+ return (
+
+ Loading sign in>}
+ />
+
+ );
+}
diff --git a/integration/templates/tanstack-react-start/package.json b/integration/templates/tanstack-react-start/package.json
index 7e86c3cb0ab..7121e87e555 100644
--- a/integration/templates/tanstack-react-start/package.json
+++ b/integration/templates/tanstack-react-start/package.json
@@ -8,12 +8,11 @@
"start": "vite start --port=$PORT"
},
"dependencies": {
- "@tanstack/react-router": "1.132.47",
- "@tanstack/react-router-devtools": "1.132.51",
- "@tanstack/react-start": "1.132.51",
- "react": "18.3.1",
- "react-dom": "18.3.1",
- "srvx": "0.8.15",
+ "@tanstack/react-router": "1.157.16",
+ "@tanstack/react-router-devtools": "1.157.16",
+ "@tanstack/react-start": "1.157.16",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
"tailwind-merge": "^2.5.4"
},
"devDependencies": {
diff --git a/integration/templates/tanstack-react-start/src/routeTree.gen.ts b/integration/templates/tanstack-react-start/src/routeTree.gen.ts
index fd38a624939..20c9b2a203a 100644
--- a/integration/templates/tanstack-react-start/src/routeTree.gen.ts
+++ b/integration/templates/tanstack-react-start/src/routeTree.gen.ts
@@ -9,12 +9,13 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root';
-import { Route as SignInRouteImport } from './routes/sign-in';
+import { Route as UserRouteImport } from './routes/user';
import { Route as IndexRouteImport } from './routes/index';
+import { Route as SignInSplatRouteImport } from './routes/sign-in.$';
-const SignInRoute = SignInRouteImport.update({
- id: '/sign-in',
- path: '/sign-in',
+const UserRoute = UserRouteImport.update({
+ id: '/user',
+ path: '/user',
getParentRoute: () => rootRouteImport,
} as any);
const IndexRoute = IndexRouteImport.update({
@@ -22,35 +23,51 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any);
+const SignInSplatRoute = SignInSplatRouteImport.update({
+ id: '/sign-in/$',
+ path: '/sign-in/$',
+ getParentRoute: () => rootRouteImport,
+} as any);
export interface FileRoutesByFullPath {
'/': typeof IndexRoute;
- '/sign-in': typeof SignInRoute;
+ '/user': typeof UserRoute;
+ '/sign-in/$': typeof SignInSplatRoute;
}
export interface FileRoutesByTo {
'/': typeof IndexRoute;
- '/sign-in': typeof SignInRoute;
+ '/user': typeof UserRoute;
+ '/sign-in/$': typeof SignInSplatRoute;
}
export interface FileRoutesById {
__root__: typeof rootRouteImport;
'/': typeof IndexRoute;
- '/sign-in': typeof SignInRoute;
+ '/user': typeof UserRoute;
+ '/sign-in/$': typeof SignInSplatRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
- fullPaths: '/' | '/sign-in';
+ fullPaths: '/' | '/user' | '/sign-in/$';
fileRoutesByTo: FileRoutesByTo;
- to: '/' | '/sign-in';
- id: '__root__' | '/' | '/sign-in';
+ to: '/' | '/user' | '/sign-in/$';
+ id: '__root__' | '/' | '/user' | '/sign-in/$';
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
- SignInRoute: typeof SignInRoute;
+ UserRoute: typeof UserRoute;
+ SignInSplatRoute: typeof SignInSplatRoute;
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
+ '/user': {
+ id: '/user';
+ path: '/user';
+ fullPath: '/user';
+ preLoaderRoute: typeof UserRouteImport;
+ parentRoute: typeof rootRouteImport;
+ };
'/': {
id: '/';
path: '/';
@@ -58,11 +75,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport;
parentRoute: typeof rootRouteImport;
};
- '/sign-in': {
- id: '/sign-in';
- path: '/sign-in';
- fullPath: '/sign-in';
- preLoaderRoute: typeof SignInRouteImport;
+ '/sign-in/$': {
+ id: '/sign-in/$';
+ path: '/sign-in/$';
+ fullPath: '/sign-in/$';
+ preLoaderRoute: typeof SignInSplatRouteImport;
parentRoute: typeof rootRouteImport;
};
}
@@ -70,6 +87,17 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
- SignInRoute: SignInRoute,
+ UserRoute: UserRoute,
+ SignInSplatRoute: SignInSplatRoute,
};
export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes();
+
+import type { getRouter } from './router.tsx';
+import type { startInstance } from './start.ts';
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true;
+ router: Awaited>;
+ config: Awaited>;
+ }
+}
diff --git a/integration/templates/tanstack-react-start/src/routes/sign-in.tsx b/integration/templates/tanstack-react-start/src/routes/sign-in.$.tsx
similarity index 77%
rename from integration/templates/tanstack-react-start/src/routes/sign-in.tsx
rename to integration/templates/tanstack-react-start/src/routes/sign-in.$.tsx
index 353ccdcf24a..66cec812b17 100644
--- a/integration/templates/tanstack-react-start/src/routes/sign-in.tsx
+++ b/integration/templates/tanstack-react-start/src/routes/sign-in.$.tsx
@@ -1,7 +1,7 @@
import { SignIn } from '@clerk/tanstack-react-start';
import { createFileRoute } from '@tanstack/react-router';
-export const Route = createFileRoute('/sign-in')({
+export const Route = createFileRoute('/sign-in/$')({
component: Page,
});
diff --git a/integration/templates/tanstack-react-start/vite.config.ts b/integration/templates/tanstack-react-start/vite.config.ts
index bce0dc21dd9..18ebce4167f 100644
--- a/integration/templates/tanstack-react-start/vite.config.ts
+++ b/integration/templates/tanstack-react-start/vite.config.ts
@@ -13,4 +13,13 @@ export default defineConfig({
tailwindcss(),
viteReact(),
],
+ // See https://github.com/TanStack/router/issues/5738
+ resolve: {
+ alias: [
+ {
+ find: 'use-sync-external-store/shim/index.js',
+ replacement: 'react',
+ },
+ ],
+ },
});
diff --git a/integration/testUtils/usersService.ts b/integration/testUtils/usersService.ts
index 52814990c92..cb9abc22da3 100644
--- a/integration/testUtils/usersService.ts
+++ b/integration/testUtils/usersService.ts
@@ -62,7 +62,7 @@ export type FakeOrganization = {
export type FakeAPIKey = {
apiKey: APIKey;
secret: string;
- revoke: () => Promise;
+ revoke: (reason?: string | null) => Promise;
};
export type UserService = {
@@ -76,7 +76,7 @@ export type UserService = {
createFakeOrganization: (userId: string) => Promise;
getUser: (opts: { id?: string; email?: string }) => Promise;
createFakeAPIKey: (userId: string) => Promise;
- passwordCompromised: (userId: string) => Promise;
+ setPasswordCompromised: (userId: string) => Promise;
};
/**
@@ -208,11 +208,12 @@ export const createUserService = (clerkClient: ClerkClient) => {
return {
apiKey,
secret: apiKey.secret ?? '',
- revoke: () => clerkClient.apiKeys.revoke({ apiKeyId: apiKey.id, revocationReason: 'For testing purposes' }),
+ revoke: (reason?: string | null) =>
+ clerkClient.apiKeys.revoke({ apiKeyId: apiKey.id, revocationReason: reason }),
} satisfies FakeAPIKey;
},
- passwordCompromised: async (userId: string) => {
- await clerkClient.users.__experimental_passwordCompromised(userId);
+ setPasswordCompromised: async (userId: string) => {
+ await clerkClient.users.setPasswordCompromised(userId);
},
};
diff --git a/integration/tests/astro/billingStore.test.ts b/integration/tests/astro/billingStore.test.ts
new file mode 100644
index 00000000000..4f2adc5651d
--- /dev/null
+++ b/integration/tests/astro/billingStore.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from '@playwright/test';
+
+import type { Application } from '../../models/application';
+import { appConfigs } from '../../presets';
+import { createTestUtils } from '../../testUtils';
+
+test.describe('Astro billingStore @astro @billing', () => {
+ test.describe.configure({ mode: 'parallel' });
+ let app: Application;
+
+ test.beforeAll(async () => {
+ test.setTimeout(90_000); // Wait for app to be ready
+
+ app = await appConfigs.astro.node.clone().commit();
+
+ await app.setup();
+ await app.withEnv(appConfigs.envs.withBilling);
+ await app.dev();
+ });
+
+ test.afterAll(async () => {
+ await app.teardown();
+ });
+
+ test('should render plans from getPlans()', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/billing/billing-store');
+
+ await u.page.waitForClerkJsLoaded();
+
+ await expect(u.page.getByText('Free')).toBeVisible();
+ await expect(u.page.getByText('Plus')).toBeVisible();
+ await expect(u.page.getByText('Pro')).toBeVisible();
+ await expect(u.page.getByText('Trial')).toBeVisible();
+ });
+});
diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts
index 93e8f21b35b..77f76f15468 100644
--- a/integration/tests/astro/components.test.ts
+++ b/integration/tests/astro/components.test.ts
@@ -511,4 +511,34 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
// await expect(u.page.getByText('Loading')).toBeHidden();
await expect(u.page.getByText("I'm an admin")).toBeVisible();
});
+
+ test('prerendered page with control components works correctly', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ // Test while signed out
+ await u.page.goToRelative('/prerendered');
+ await u.page.waitForClerkJsLoaded();
+ await u.po.expect.toBeSignedOut();
+
+ // Verify SignedOut content is visible and SignedIn is hidden
+ await expect(u.page.locator('#signed-out-content')).toBeVisible();
+ await expect(u.page.locator('#signed-in-content')).toBeHidden();
+
+ // Sign in
+ await u.page.goToRelative('/sign-in');
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({
+ email: fakeAdmin.email,
+ password: fakeAdmin.password,
+ });
+ await u.po.expect.toBeSignedIn();
+
+ // Visit prerendered page again while signed in
+ await u.page.goToRelative('/prerendered');
+ await u.page.waitForClerkJsLoaded();
+
+ // Verify SignedIn content is visible and SignedOut is hidden
+ await expect(u.page.locator('#signed-in-content')).toBeVisible();
+ await expect(u.page.locator('#signed-out-content')).toBeHidden();
+ });
});
diff --git a/integration/tests/elements/otp.test.ts b/integration/tests/elements/otp.test.ts
index 59f63f3414f..ea9e5690a72 100644
--- a/integration/tests/elements/otp.test.ts
+++ b/integration/tests/elements/otp.test.ts
@@ -138,6 +138,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('OTP @elem
await otp.pressSequentially('12');
await otp.press('ArrowLeft');
+ await expect(page.getByTestId('segmented-otp-1')).toHaveAttribute('data-status', 'selected');
await otp.pressSequentially('1');
await expect(otp).toHaveValue('11');
});
@@ -200,10 +201,13 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('OTP @elem
await otp.press('ArrowLeft');
await otp.press('ArrowLeft');
+ // Wait for selection to update - cursor should be on index 2 (selecting "3")
+ await expect(page.getByTestId('segmented-otp-2')).toHaveAttribute('data-status', 'selected');
await otp.press('Delete');
await expect(otp).toHaveValue('124');
await otp.press('ArrowRight');
+ await expect(page.getByTestId('segmented-otp-2')).toHaveAttribute('data-status', 'selected');
await otp.press('Delete');
await expect(otp).toHaveValue('12');
});
diff --git a/integration/tests/machine-auth/api-keys.test.ts b/integration/tests/machine-auth/api-keys.test.ts
index 88697e43b08..73912c550e5 100644
--- a/integration/tests/machine-auth/api-keys.test.ts
+++ b/integration/tests/machine-auth/api-keys.test.ts
@@ -65,7 +65,7 @@ test.describe('Next.js API key auth within clerkMiddleware() @machine', () => {
});
test.afterAll(async () => {
- await fakeAPIKey.revoke();
+ await fakeAPIKey.revoke('Testing purposes within clerkMiddleware()');
await fakeUser.deleteIfExists();
await app.teardown();
});
diff --git a/integration/tests/machine-auth/oauth.test.ts b/integration/tests/machine-auth/oauth.test.ts
index 863c86ea212..a501a15bfad 100644
--- a/integration/tests/machine-auth/oauth.test.ts
+++ b/integration/tests/machine-auth/oauth.test.ts
@@ -50,7 +50,7 @@ test.describe('OAuth machine authentication @machine', () => {
.commit();
await app.setup();
- await app.withEnv(appConfigs.envs.withEmailCodes);
+ await app.withEnv(appConfigs.envs.withAPIKeys);
await app.dev();
// Test user that will authorize the OAuth application
diff --git a/integration/tests/middleware-placement.test.ts b/integration/tests/middleware-placement.test.ts
index 42bc1b7227f..c756d83374a 100644
--- a/integration/tests/middleware-placement.test.ts
+++ b/integration/tests/middleware-placement.test.ts
@@ -71,10 +71,16 @@ test.describe('next start - missing middleware @quickstart', () => {
});
test('Display error for missing middleware', async ({ page, context }) => {
+ const { version } = await detectNext(app);
+ const major = parseSemverMajor(version) ?? 0;
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
- expect(app.serveOutput).toContain('Your Middleware exists at ./src/middleware.(ts|js)');
+ const expectedMessage =
+ major >= 16
+ ? 'Your Middleware exists at ./src/middleware.(ts|js) or proxy.(ts|js)'
+ : 'Your Middleware exists at ./src/middleware.(ts|js)';
+ expect(app.serveOutput).toContain(expectedMessage);
});
});
@@ -105,10 +111,16 @@ test.describe('next start - invalid middleware at root on src/ @quickstart', ()
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
- expect(app.serveOutput).not.toContain('Your Middleware exists at ./src/middleware.(ts|js)');
- expect(app.serveOutput).toContain(
- 'Clerk: clerkMiddleware() was not run, your middleware file might be misplaced. Move your middleware file to ./src/middleware.ts. Currently located at ./middleware.ts',
- );
+ const expectedMessage =
+ major >= 16
+ ? 'Your Middleware exists at ./src/middleware.(ts|js) or proxy.(ts|js)'
+ : 'Your Middleware exists at ./src/middleware.(ts|js)';
+ expect(app.serveOutput).not.toContain(expectedMessage);
+ const expectedError =
+ major >= 16
+ ? 'Clerk: clerkMiddleware() was not run, your middleware or proxy file might be misplaced. Move your middleware or proxy file to ./src/middleware.ts. Currently located at ./middleware.ts'
+ : 'Clerk: clerkMiddleware() was not run, your middleware file might be misplaced. Move your middleware file to ./src/middleware.ts. Currently located at ./middleware.ts';
+ expect(app.serveOutput).toContain(expectedError);
});
test('Does not display misplaced middleware error on Next 16+', async ({ page, context }) => {
@@ -142,11 +154,19 @@ test.describe('next start - invalid middleware inside app on src/ @quickstart',
page,
context,
}) => {
+ const { version } = await detectNext(app);
+ const major = parseSemverMajor(version) ?? 0;
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
- expect(app.serveOutput).not.toContain('Your Middleware exists at ./src/middleware.(ts|js)');
- expect(app.serveOutput).toContain(
- 'Clerk: clerkMiddleware() was not run, your middleware file might be misplaced. Move your middleware file to ./src/middleware.ts. Currently located at ./src/app/middleware.ts',
- );
+ const expectedMessage =
+ major >= 16
+ ? 'Your Middleware exists at ./src/middleware.(ts|js) or proxy.(ts|js)'
+ : 'Your Middleware exists at ./src/middleware.(ts|js)';
+ expect(app.serveOutput).not.toContain(expectedMessage);
+ const expectedError =
+ major >= 16
+ ? 'Clerk: clerkMiddleware() was not run, your middleware or proxy file might be misplaced. Move your middleware or proxy file to ./src/middleware.ts. Currently located at ./src/app/middleware.ts'
+ : 'Clerk: clerkMiddleware() was not run, your middleware file might be misplaced. Move your middleware file to ./src/middleware.ts. Currently located at ./src/app/middleware.ts';
+ expect(app.serveOutput).toContain(expectedError);
});
});
diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts
index d143b60385e..76b0f2033a7 100644
--- a/integration/tests/next-quickstart-keyless.test.ts
+++ b/integration/tests/next-quickstart-keyless.test.ts
@@ -78,8 +78,6 @@ test.describe('Keyless mode @quickstart', () => {
await u.po.keylessPopover.waitForMounted();
- expect(await u.po.keylessPopover.isExpanded()).toBe(false);
- await u.po.keylessPopover.toggle();
expect(await u.po.keylessPopover.isExpanded()).toBe(true);
const claim = await u.po.keylessPopover.promptsToClaim();
@@ -89,20 +87,24 @@ test.describe('Keyless mode @quickstart', () => {
await newPage.waitForLoadState();
await newPage.waitForURL(url => {
- const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;
-
+ const signInForceRedirectUrl = url.searchParams.get('sign_in_force_redirect_url');
const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');
- const signUpForceRedirectUrlCheck =
- signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
- (signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
- signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));
+ // Backend adds framework=nextjs query param before token, so use .includes() instead of .startsWith()
+ const signInHasRequiredParams =
+ signInForceRedirectUrl?.includes(`${dashboardUrl}apps/claim`) && signInForceRedirectUrl?.includes('token=');
+
+ const signUpRegularCase =
+ signUpForceRedirectUrl?.includes(`${dashboardUrl}apps/claim`) && signUpForceRedirectUrl?.includes('token=');
+
+ const signUpPrepareAccountCase =
+ signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
+ signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim')) &&
+ signUpForceRedirectUrl?.includes(encodeURIComponent('token='));
+
+ const signUpHasRequiredParams = signUpRegularCase || signUpPrepareAccountCase;
- return (
- url.pathname === '/apps/claim/sign-in' &&
- url.searchParams.get('sign_in_force_redirect_url')?.startsWith(urlToReturnTo) &&
- signUpForceRedirectUrlCheck
- );
+ return url.pathname === '/apps/claim/sign-in' && signInHasRequiredParams && signUpHasRequiredParams;
});
});
diff --git a/integration/tests/oauth-flows.test.ts b/integration/tests/oauth-flows.test.ts
index 6ff96a3cea2..77e97d8f8e5 100644
--- a/integration/tests/oauth-flows.test.ts
+++ b/integration/tests/oauth-flows.test.ts
@@ -181,6 +181,63 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('oauth flo
});
});
+testAgainstRunningApps({ withPattern: ['react.vite.withLegalConsent'] })(
+ 'oauth popup with path-based routing @react',
+ ({ app }) => {
+ test.describe.configure({ mode: 'serial' });
+
+ let fakeUser: FakeUser;
+
+ test.beforeAll(async () => {
+ const client = createClerkClient({
+ secretKey: instanceKeys.get('oauth-provider').sk,
+ publishableKey: instanceKeys.get('oauth-provider').pk,
+ });
+ const users = createUserService(client);
+ fakeUser = users.createFakeUser({
+ withUsername: true,
+ });
+ await users.createBapiUser(fakeUser);
+ });
+
+ test.afterAll(async () => {
+ const u = createTestUtils({ app });
+ await fakeUser.deleteIfExists();
+ await u.services.users.deleteIfExists({ email: fakeUser.email });
+ await app.teardown();
+ });
+
+ test('popup OAuth navigates through sso-callback with path-based routing', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.page.goToRelative('/sign-in-popup');
+ await u.page.waitForClerkJsLoaded();
+ await u.po.signIn.waitForMounted();
+
+ const popupPromise = context.waitForEvent('page');
+ await u.page.getByRole('button', { name: 'E2E OAuth Provider' }).click();
+ const popup = await popupPromise;
+ const popupUtils = createTestUtils({ app, page: popup, context });
+ await popupUtils.page.getByText('Sign in to oauth-provider').waitFor();
+
+ // Complete OAuth in the popup
+ await popupUtils.po.signIn.setIdentifier(fakeUser.email);
+ await popupUtils.po.signIn.continue();
+ await popupUtils.po.signIn.enterTestOtpCode();
+
+ // Because the user is new to the app and legal consent is required,
+ // the sign-up can't complete in the popup. The popup sends return_url
+ // back to the parent, which navigates to /sso-callback via pushState.
+ await u.page.getByRole('heading', { name: 'Legal consent' }).waitFor();
+ await u.page.getByLabel(/I agree to the/).check();
+ await u.po.signIn.continue();
+
+ await u.page.waitForAppUrl('/protected');
+ await u.po.expect.toBeSignedIn();
+ });
+ },
+);
+
testAgainstRunningApps({ withEnv: [appConfigs.envs.withLegalConsent] })(
'oauth flows with legal consent @nextjs',
({ app }) => {
diff --git a/integration/tests/session-tasks-setup-mfa.test.ts b/integration/tests/session-tasks-setup-mfa.test.ts
new file mode 100644
index 00000000000..1bdb3c1e8f7
--- /dev/null
+++ b/integration/tests/session-tasks-setup-mfa.test.ts
@@ -0,0 +1,207 @@
+import { expect, test } from '@playwright/test';
+
+import { appConfigs } from '../presets';
+import { createTestUtils, testAgainstRunningApps } from '../testUtils';
+import { stringPhoneNumber } from '../testUtils/phoneUtils';
+import { fakerPhoneNumber } from '../testUtils/usersService';
+
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasksSetupMfa] })(
+ 'session tasks setup-mfa flow @nextjs',
+ ({ app }) => {
+ test.describe.configure({ mode: 'parallel' });
+
+ test.afterAll(async () => {
+ await app.teardown();
+ });
+
+ test.afterEach(async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.signOut();
+ await u.page.context().clearCookies();
+ });
+
+ test('setup MFA with new phone number - happy path', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const user = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(user);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: user.email, password: user.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+
+ await u.page.getByRole('button', { name: /sms code/i }).click();
+
+ const testPhoneNumber = fakerPhoneNumber();
+ await u.po.signIn.getPhoneNumberInput().fill(testPhoneNumber);
+ await u.page.getByRole('button', { name: /continue/i }).click();
+
+ await u.po.signIn.enterTestOtpCode();
+
+ await u.page.getByText(/save these backup codes/i).waitFor({ state: 'visible', timeout: 10000 });
+
+ await u.po.signIn.continue();
+
+ await u.page.waitForAppUrl('/page-protected');
+ await u.po.expect.toBeSignedIn();
+
+ await user.deleteIfExists();
+ });
+
+ test('setup MFA with existing phone number - happy path', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const user = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPhoneNumber: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(user);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: user.email, password: user.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+
+ await u.page.getByRole('button', { name: /sms code/i }).click();
+
+ const formattedPhoneNumber = stringPhoneNumber(user.phoneNumber);
+ await u.page
+ .getByRole('button', {
+ name: formattedPhoneNumber,
+ })
+ .click();
+
+ await u.page.getByText(/save these backup codes/i).waitFor({ state: 'visible', timeout: 10000 });
+
+ await u.po.signIn.continue();
+
+ await u.page.waitForAppUrl('/page-protected');
+ await u.po.expect.toBeSignedIn();
+
+ await user.deleteIfExists();
+ });
+
+ test('setup MFA with invalid phone number - error handling', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const user = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(user);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: user.email, password: user.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+
+ await u.page.getByRole('button', { name: /sms code/i }).click();
+
+ const invalidPhoneNumber = '123091293193091311';
+ await u.po.signIn.getPhoneNumberInput().fill(invalidPhoneNumber);
+ await u.po.signIn.continue();
+ // we need to improve this error message
+ await expect(u.page.getByTestId('form-feedback-error')).toBeVisible();
+
+ const validPhoneNumber = fakerPhoneNumber();
+ await u.po.signIn.getPhoneNumberInput().fill(validPhoneNumber);
+ await u.po.signIn.continue();
+
+ await u.po.signIn.enterTestOtpCode();
+
+ await u.page.getByText(/save these backup codes/i).waitFor({ state: 'visible', timeout: 10000 });
+
+ await u.po.signIn.continue();
+
+ await u.page.waitForAppUrl('/page-protected');
+ await u.po.expect.toBeSignedIn();
+
+ await user.deleteIfExists();
+ });
+
+ test('setup MFA with invalid verification code - error handling', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const user = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(user);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: user.email, password: user.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+
+ await u.page.getByRole('button', { name: /sms code/i }).click();
+
+ const testPhoneNumber = fakerPhoneNumber();
+ await u.po.signIn.getPhoneNumberInput().fill(testPhoneNumber);
+ await u.po.signIn.continue();
+
+ await u.po.signIn.enterOtpCode('111111', {
+ awaitPrepare: true,
+ awaitAttempt: true,
+ });
+
+ await expect(u.page.getByTestId('form-feedback-error')).toBeVisible();
+
+ await user.deleteIfExists();
+ });
+
+ test('can navigate back during MFA setup', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const user = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPhoneNumber: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(user);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.waitForMounted();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: user.email, password: user.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+
+ await u.page.getByRole('button', { name: /sms code/i }).click();
+
+ const formattedPhoneNumber = stringPhoneNumber(user.phoneNumber);
+ await u.page
+ .getByRole('button', {
+ name: formattedPhoneNumber,
+ })
+ .waitFor({ state: 'visible' });
+
+ await u.page
+ .getByRole('button', { name: /cancel/i })
+ .first()
+ .click();
+
+ await u.page.getByText(/set up two-step verification/i).waitFor({ state: 'visible' });
+ await u.page.getByRole('button', { name: /sms code/i }).waitFor({ state: 'visible' });
+
+ await user.deleteIfExists();
+ });
+ },
+);
diff --git a/integration/tests/session-tasks-sign-in-reset-password.test.ts b/integration/tests/session-tasks-sign-in-reset-password.test.ts
index cf82347cf09..2a61c446581 100644
--- a/integration/tests/session-tasks-sign-in-reset-password.test.ts
+++ b/integration/tests/session-tasks-sign-in-reset-password.test.ts
@@ -19,7 +19,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasksResetPassword
const user = u.services.users.createFakeUser();
const createdUser = await u.services.users.createBapiUser(user);
- await u.services.users.passwordCompromised(createdUser.id);
+ await u.services.users.setPasswordCompromised(createdUser.id);
// Performs sign-in
await u.po.signIn.goTo();
@@ -30,7 +30,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasksResetPassword
await expect(
u.page.getByText(
- "Your password appears to have been compromised or it's no longer trusted and cannot be used. Please use another method to continue.",
+ 'Your password may be compromised. To protect your account, please continue with an alternative sign-in method. You will be required to reset your password after signing in.',
),
).toBeVisible();
await u.po.signIn.getAltMethodsEmailCodeButton().click();
@@ -66,7 +66,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasksResetPassword
const user = u.services.users.createFakeUser();
const createdUser = await u.services.users.createBapiUser(user);
- await u.services.users.passwordCompromised(createdUser.id);
+ await u.services.users.setPasswordCompromised(createdUser.id);
const fakeOrganization = u.services.organizations.createFakeOrganization();
await u.services.organizations.createBapiOrganization({
name: fakeOrganization.name,
@@ -83,7 +83,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasksResetPassword
await expect(
u.page.getByText(
- "Your password appears to have been compromised or it's no longer trusted and cannot be used. Please use another method to continue.",
+ 'Your password may be compromised. To protect your account, please continue with an alternative sign-in method. You will be required to reset your password after signing in.',
),
).toBeVisible();
await u.po.signIn.getAltMethodsEmailCodeButton().click();
diff --git a/packages/agent-toolkit/CHANGELOG.md b/packages/agent-toolkit/CHANGELOG.md
index 55e6ff9f4c6..1c6c5ed3e5e 100644
--- a/packages/agent-toolkit/CHANGELOG.md
+++ b/packages/agent-toolkit/CHANGELOG.md
@@ -1,5 +1,148 @@
# @clerk/agent-toolkit
+## 0.2.25
+
+### Patch Changes
+
+- Updated dependencies [[`c00c524`](https://github.com/clerk/javascript/commit/c00c5246f340cf0339c5725cade90cfcd118727d), [`9c935ad`](https://github.com/clerk/javascript/commit/9c935adeda94af60219ed8b7c7f1f9c34fbd410d)]:
+ - @clerk/shared@3.47.0
+ - @clerk/backend@2.32.0
+ - @clerk/types@4.101.18
+
+## 0.2.24
+
+### Patch Changes
+
+- Updated dependencies [[`71bd53c`](https://github.com/clerk/javascript/commit/71bd53c67a5018bd7aa589c3baced2038123c228), [`935f780`](https://github.com/clerk/javascript/commit/935f780ab5b3871253da2ad46f0e44f9ce7e53e8), [`2471e31`](https://github.com/clerk/javascript/commit/2471e314b24eab485c78313d84d986ee30c63088)]:
+ - @clerk/shared@3.46.0
+ - @clerk/backend@2.31.2
+ - @clerk/types@4.101.17
+
+## 0.2.23
+
+### Patch Changes
+
+- Updated dependencies [[`b17e4bb`](https://github.com/clerk/javascript/commit/b17e4bbbbad173969523e5494f2d8447d1887b95)]:
+ - @clerk/shared@3.45.1
+ - @clerk/backend@2.31.1
+ - @clerk/types@4.101.16
+
+## 0.2.22
+
+### Patch Changes
+
+- Updated dependencies [[`35bcbd1`](https://github.com/clerk/javascript/commit/35bcbd11f5753ee396cd090d3dd1848f3f2727e0), [`5740640`](https://github.com/clerk/javascript/commit/57406404d516cf0fa8d3bb9b38a0d3d1d69dc88d), [`03c61c1`](https://github.com/clerk/javascript/commit/03c61c122cc1eb2cf35ecdc20586f2fbb0a1e7db)]:
+ - @clerk/shared@3.45.0
+ - @clerk/backend@2.31.0
+ - @clerk/types@4.101.15
+
+## 0.2.21
+
+### Patch Changes
+
+- Updated dependencies [[`a726252`](https://github.com/clerk/javascript/commit/a726252610ea0cbef2d971ec3ce8d0d4be3a3468)]:
+ - @clerk/backend@2.30.1
+
+## 0.2.20
+
+### Patch Changes
+
+- Updated dependencies [[`7917ff4`](https://github.com/clerk/javascript/commit/7917ff4214fc9e1001e2698c7241bbfa4b68e5af), [`b0d28c1`](https://github.com/clerk/javascript/commit/b0d28c14815a6136c67a719efb1dc5496ffb5c82)]:
+ - @clerk/backend@2.30.0
+
+## 0.2.19
+
+### Patch Changes
+
+- Updated dependencies [[`559cd84`](https://github.com/clerk/javascript/commit/559cd84a320a1d808fb38c404f31437046198123)]:
+ - @clerk/backend@2.29.7
+
+## 0.2.18
+
+### Patch Changes
+
+- Updated dependencies [[`64a35f7`](https://github.com/clerk/javascript/commit/64a35f79e9a49dfc140b4c8a8df517b74d46d6c6)]:
+ - @clerk/shared@3.44.0
+ - @clerk/backend@2.29.6
+ - @clerk/types@4.101.14
+
+## 0.2.17
+
+### Patch Changes
+
+- Updated dependencies [[`b7a4e1e`](https://github.com/clerk/javascript/commit/b7a4e1eabe7aa61e7d2cb7f27cbd22671c49f2b1)]:
+ - @clerk/shared@3.43.2
+ - @clerk/backend@2.29.5
+ - @clerk/types@4.101.13
+
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies [[`e995cc3`](https://github.com/clerk/javascript/commit/e995cc3572f85aa47bdee8f7b56130a383488a7f)]:
+ - @clerk/shared@3.43.1
+ - @clerk/backend@2.29.4
+ - @clerk/types@4.101.12
+
+## 0.2.15
+
+### Patch Changes
+
+- Updated dependencies [[`c3ff1f8`](https://github.com/clerk/javascript/commit/c3ff1f899098e235ff8651f9e31e2055fc43ba8e), [`271ddeb`](https://github.com/clerk/javascript/commit/271ddeb0b47357f7da316eef389ae46b180c36da)]:
+ - @clerk/backend@2.29.3
+ - @clerk/shared@3.43.0
+ - @clerk/types@4.101.11
+
+## 0.2.14
+
+### Patch Changes
+
+- Updated dependencies [[`6b26afc`](https://github.com/clerk/javascript/commit/6b26afcc784f6e8344cf6ff0b1ef69c14019fe66)]:
+ - @clerk/backend@2.29.2
+
+## 0.2.13
+
+### Patch Changes
+
+- Updated dependencies [[`9320c4f`](https://github.com/clerk/javascript/commit/9320c4f9dde7d9a4732cdb3a9ca71e8a720a8dea), [`a4e6932`](https://github.com/clerk/javascript/commit/a4e693262f734bfd3ab08ffac019168c874c2bd8)]:
+ - @clerk/backend@2.29.1
+ - @clerk/shared@3.42.0
+ - @clerk/types@4.101.10
+
+## 0.2.12
+
+### Patch Changes
+
+- Updated dependencies [[`ede3e2a`](https://github.com/clerk/javascript/commit/ede3e2a326c9cbbd4ab09375f4bb291483681892), [`03dd374`](https://github.com/clerk/javascript/commit/03dd37458eedf59198dc3574e12030b217efcb41)]:
+ - @clerk/backend@2.29.0
+ - @clerk/shared@3.41.1
+ - @clerk/types@4.101.9
+
+## 0.2.11
+
+### Patch Changes
+
+- Updated dependencies [[`79eb5af`](https://github.com/clerk/javascript/commit/79eb5afd91d7b002faafd2980850d944acb37917), [`5d25027`](https://github.com/clerk/javascript/commit/5d250277ea389695e82ec9471f1eadadf7cbc4c3), [`b3b02b4`](https://github.com/clerk/javascript/commit/b3b02b46dfa6d194ed12d2e6b9e332796ee73c4a), [`7b3024a`](https://github.com/clerk/javascript/commit/7b3024a71e6e45e926d83f1a9e887216e7c14424), [`2cd4da9`](https://github.com/clerk/javascript/commit/2cd4da9c72bc7385c0c7c71e2a7ca856d79ce630), [`d4e2739`](https://github.com/clerk/javascript/commit/d4e2739422bdeea44f240c9d7637f564dce5320f)]:
+ - @clerk/shared@3.41.0
+ - @clerk/backend@2.28.0
+ - @clerk/types@4.101.8
+
+## 0.2.10
+
+### Patch Changes
+
+- Updated dependencies [[`375a32d`](https://github.com/clerk/javascript/commit/375a32d0f44933605ffb513ff28f522ac5e851d6), [`175883b`](https://github.com/clerk/javascript/commit/175883b05228138c9ff55d0871cc1041bd68d7fe), [`43d3c3e`](https://github.com/clerk/javascript/commit/43d3c3eaff767054ef74fd3655e632caffeaaf33), [`f626046`](https://github.com/clerk/javascript/commit/f626046c589956022b1e1ac70382c986822f4733), [`14342d2`](https://github.com/clerk/javascript/commit/14342d2b34fe0882f7676195aefaaa17f034af70)]:
+ - @clerk/shared@3.40.0
+ - @clerk/backend@2.27.1
+ - @clerk/types@4.101.7
+
+## 0.2.9
+
+### Patch Changes
+
+- Updated dependencies [[`e448757`](https://github.com/clerk/javascript/commit/e448757cd3d24a509a3a312e3a376c235fba32a1)]:
+ - @clerk/backend@2.27.0
+
## 0.2.8
### Patch Changes
diff --git a/packages/agent-toolkit/package.json b/packages/agent-toolkit/package.json
index 2a5ff9188ec..b2be86a1ae8 100644
--- a/packages/agent-toolkit/package.json
+++ b/packages/agent-toolkit/package.json
@@ -1,6 +1,6 @@
{
"name": "@clerk/agent-toolkit",
- "version": "0.2.8",
+ "version": "0.2.25",
"description": "Clerk Toolkit for AI Agents",
"homepage": "https://clerk.com/",
"bugs": {
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 7d312d740ca..26b3adb9090 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -1,5 +1,156 @@
# @clerk/astro
+## 2.17.5
+
+### Patch Changes
+
+- Updated dependencies [[`c00c524`](https://github.com/clerk/javascript/commit/c00c5246f340cf0339c5725cade90cfcd118727d), [`9c935ad`](https://github.com/clerk/javascript/commit/9c935adeda94af60219ed8b7c7f1f9c34fbd410d)]:
+ - @clerk/shared@3.47.0
+ - @clerk/backend@2.32.0
+ - @clerk/types@4.101.18
+
+## 2.17.4
+
+### Patch Changes
+
+- Updated dependencies [[`71bd53c`](https://github.com/clerk/javascript/commit/71bd53c67a5018bd7aa589c3baced2038123c228), [`935f780`](https://github.com/clerk/javascript/commit/935f780ab5b3871253da2ad46f0e44f9ce7e53e8), [`2471e31`](https://github.com/clerk/javascript/commit/2471e314b24eab485c78313d84d986ee30c63088)]:
+ - @clerk/shared@3.46.0
+ - @clerk/backend@2.31.2
+ - @clerk/types@4.101.17
+
+## 2.17.3
+
+### Patch Changes
+
+- Updated dependencies [[`b17e4bb`](https://github.com/clerk/javascript/commit/b17e4bbbbad173969523e5494f2d8447d1887b95)]:
+ - @clerk/shared@3.45.1
+ - @clerk/backend@2.31.1
+ - @clerk/types@4.101.16
+
+## 2.17.2
+
+### Patch Changes
+
+- Fixed an issue when using `ClientRouter` where Clerk components don't load until navigation is performed. ([#7804](https://github.com/clerk/javascript/pull/7804)) by [@wobsoriano](https://github.com/wobsoriano)
+
+- Updated dependencies [[`35bcbd1`](https://github.com/clerk/javascript/commit/35bcbd11f5753ee396cd090d3dd1848f3f2727e0), [`5740640`](https://github.com/clerk/javascript/commit/57406404d516cf0fa8d3bb9b38a0d3d1d69dc88d), [`03c61c1`](https://github.com/clerk/javascript/commit/03c61c122cc1eb2cf35ecdc20586f2fbb0a1e7db)]:
+ - @clerk/shared@3.45.0
+ - @clerk/backend@2.31.0
+ - @clerk/types@4.101.15
+
+## 2.17.1
+
+### Patch Changes
+
+- Updated dependencies [[`a726252`](https://github.com/clerk/javascript/commit/a726252610ea0cbef2d971ec3ce8d0d4be3a3468)]:
+ - @clerk/backend@2.30.1
+
+## 2.17.0
+
+### Minor Changes
+
+- Add `$billingStore` for access to the `Clerk.billing` object containing various Clerk Billing methods. ([#7733](https://github.com/clerk/javascript/pull/7733)) by [@dstaley](https://github.com/dstaley)
+
+### Patch Changes
+
+- Updated dependencies [[`7917ff4`](https://github.com/clerk/javascript/commit/7917ff4214fc9e1001e2698c7241bbfa4b68e5af), [`b0d28c1`](https://github.com/clerk/javascript/commit/b0d28c14815a6136c67a719efb1dc5496ffb5c82)]:
+ - @clerk/backend@2.30.0
+
+## 2.16.17
+
+### Patch Changes
+
+- Fixed an error when using Control components (``, ``) in prerendered pages ([#7708](https://github.com/clerk/javascript/pull/7708)) by [@wobsoriano](https://github.com/wobsoriano)
+
+- Updated dependencies [[`559cd84`](https://github.com/clerk/javascript/commit/559cd84a320a1d808fb38c404f31437046198123)]:
+ - @clerk/backend@2.29.7
+
+## 2.16.16
+
+### Patch Changes
+
+- Updated dependencies [[`64a35f7`](https://github.com/clerk/javascript/commit/64a35f79e9a49dfc140b4c8a8df517b74d46d6c6)]:
+ - @clerk/shared@3.44.0
+ - @clerk/backend@2.29.6
+ - @clerk/types@4.101.14
+
+## 2.16.15
+
+### Patch Changes
+
+- Updated dependencies [[`b7a4e1e`](https://github.com/clerk/javascript/commit/b7a4e1eabe7aa61e7d2cb7f27cbd22671c49f2b1)]:
+ - @clerk/shared@3.43.2
+ - @clerk/backend@2.29.5
+ - @clerk/types@4.101.13
+
+## 2.16.14
+
+### Patch Changes
+
+- Updated dependencies [[`e995cc3`](https://github.com/clerk/javascript/commit/e995cc3572f85aa47bdee8f7b56130a383488a7f)]:
+ - @clerk/shared@3.43.1
+ - @clerk/backend@2.29.4
+ - @clerk/types@4.101.12
+
+## 2.16.13
+
+### Patch Changes
+
+- Updated dependencies [[`c3ff1f8`](https://github.com/clerk/javascript/commit/c3ff1f899098e235ff8651f9e31e2055fc43ba8e), [`271ddeb`](https://github.com/clerk/javascript/commit/271ddeb0b47357f7da316eef389ae46b180c36da)]:
+ - @clerk/backend@2.29.3
+ - @clerk/shared@3.43.0
+ - @clerk/types@4.101.11
+
+## 2.16.12
+
+### Patch Changes
+
+- Updated dependencies [[`6b26afc`](https://github.com/clerk/javascript/commit/6b26afcc784f6e8344cf6ff0b1ef69c14019fe66)]:
+ - @clerk/backend@2.29.2
+
+## 2.16.11
+
+### Patch Changes
+
+- Updated dependencies [[`9320c4f`](https://github.com/clerk/javascript/commit/9320c4f9dde7d9a4732cdb3a9ca71e8a720a8dea), [`a4e6932`](https://github.com/clerk/javascript/commit/a4e693262f734bfd3ab08ffac019168c874c2bd8)]:
+ - @clerk/backend@2.29.1
+ - @clerk/shared@3.42.0
+ - @clerk/types@4.101.10
+
+## 2.16.10
+
+### Patch Changes
+
+- Updated dependencies [[`ede3e2a`](https://github.com/clerk/javascript/commit/ede3e2a326c9cbbd4ab09375f4bb291483681892), [`03dd374`](https://github.com/clerk/javascript/commit/03dd37458eedf59198dc3574e12030b217efcb41)]:
+ - @clerk/backend@2.29.0
+ - @clerk/shared@3.41.1
+ - @clerk/types@4.101.9
+
+## 2.16.9
+
+### Patch Changes
+
+- Updated dependencies [[`79eb5af`](https://github.com/clerk/javascript/commit/79eb5afd91d7b002faafd2980850d944acb37917), [`5d25027`](https://github.com/clerk/javascript/commit/5d250277ea389695e82ec9471f1eadadf7cbc4c3), [`b3b02b4`](https://github.com/clerk/javascript/commit/b3b02b46dfa6d194ed12d2e6b9e332796ee73c4a), [`7b3024a`](https://github.com/clerk/javascript/commit/7b3024a71e6e45e926d83f1a9e887216e7c14424), [`2cd4da9`](https://github.com/clerk/javascript/commit/2cd4da9c72bc7385c0c7c71e2a7ca856d79ce630), [`d4e2739`](https://github.com/clerk/javascript/commit/d4e2739422bdeea44f240c9d7637f564dce5320f)]:
+ - @clerk/shared@3.41.0
+ - @clerk/backend@2.28.0
+ - @clerk/types@4.101.8
+
+## 2.16.8
+
+### Patch Changes
+
+- Updated dependencies [[`375a32d`](https://github.com/clerk/javascript/commit/375a32d0f44933605ffb513ff28f522ac5e851d6), [`175883b`](https://github.com/clerk/javascript/commit/175883b05228138c9ff55d0871cc1041bd68d7fe), [`43d3c3e`](https://github.com/clerk/javascript/commit/43d3c3eaff767054ef74fd3655e632caffeaaf33), [`f626046`](https://github.com/clerk/javascript/commit/f626046c589956022b1e1ac70382c986822f4733), [`14342d2`](https://github.com/clerk/javascript/commit/14342d2b34fe0882f7676195aefaaa17f034af70)]:
+ - @clerk/shared@3.40.0
+ - @clerk/backend@2.27.1
+ - @clerk/types@4.101.7
+
+## 2.16.7
+
+### Patch Changes
+
+- Updated dependencies [[`e448757`](https://github.com/clerk/javascript/commit/e448757cd3d24a509a3a312e3a376c235fba32a1)]:
+ - @clerk/backend@2.27.0
+
## 2.16.6
### Patch Changes
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 094ad371e7e..27127168236 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -1,6 +1,6 @@
{
"name": "@clerk/astro",
- "version": "2.16.6",
+ "version": "2.17.5",
"description": "Clerk SDK for Astro",
"keywords": [
"auth",
diff --git a/packages/astro/src/astro-components/control/Protect.astro b/packages/astro/src/astro-components/control/Protect.astro
index c7e039987f4..d2d2f2263f6 100644
--- a/packages/astro/src/astro-components/control/Protect.astro
+++ b/packages/astro/src/astro-components/control/Protect.astro
@@ -26,7 +26,10 @@ type Props = ProtectProps & {
const { isStatic, ...props } = Astro.props;
-const ProtectComponent = isStaticOutput(isStatic) ? ProtectCSR : ProtectSSR;
+// If user explicitly sets isStatic prop, honor it
+// Otherwise, detect based on runtime (whether auth function exists)
+const shouldUseCSR = isStatic !== undefined ? isStaticOutput(isStatic) : !Astro.locals.auth;
+const ProtectComponent = shouldUseCSR ? ProtectCSR : ProtectSSR;
// Note: Astro server islands also use a "fallback" slot for loading states
// See: https://docs.astro.build/en/guides/server-islands/#server-island-fallback-content
diff --git a/packages/astro/src/astro-components/control/ProtectCSR.astro b/packages/astro/src/astro-components/control/ProtectCSR.astro
index cee284935c5..e3aa5ca8f3c 100644
--- a/packages/astro/src/astro-components/control/ProtectCSR.astro
+++ b/packages/astro/src/astro-components/control/ProtectCSR.astro
@@ -44,7 +44,7 @@ const {