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 {