diff --git a/.changeset/keyless-removal-sweep.md b/.changeset/keyless-removal-sweep.md new file mode 100644 index 00000000000..0a17def5332 --- /dev/null +++ b/.changeset/keyless-removal-sweep.md @@ -0,0 +1,8 @@ +--- +'@clerk/astro': minor +'@clerk/nuxt': minor +'@clerk/react-router': minor +'@clerk/tanstack-react-start': minor +--- + +In development, missing Clerk keys no longer activate keyless mode. When the framework's publishable/secret key environment variables are not set, the SDK now throws an error directing you to run `npx clerk@latest init`, which provisions a Clerk application and writes the keys to your env file. Existing apps with configured keys are unaffected. diff --git a/integration/tests/astro/keyless.test.ts b/integration/tests/astro/keyless.test.ts index c0808437173..7e46c513aca 100644 --- a/integration/tests/astro/keyless.test.ts +++ b/integration/tests/astro/keyless.test.ts @@ -1,12 +1,7 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; import { appConfigs } from '../../presets'; -import { - testClaimedAppWithMissingKeys, - testKeylessRemovedAfterEnvAndRestart, - testToggleCollapsePopoverAndClaim, -} from '../../testUtils/keylessHelpers'; const commonSetup = appConfigs.astro.node.clone(); @@ -21,15 +16,11 @@ test.describe('Keyless mode @astro', () => { }); let app: Application; - let dashboardUrl = 'https://dashboard.clerk.com/'; test.beforeAll(async () => { app = await commonSetup.commit(); await app.setup(); await app.withEnv(appConfigs.envs.withKeyless); - if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) { - dashboardUrl = 'https://dashboard.clerkstage.dev/'; - } await app.dev(); }); @@ -37,18 +28,13 @@ test.describe('Keyless mode @astro', () => { await app?.teardown(); }); - test('Toggle collapse popover and claim.', async ({ page, context }) => { - await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'astro' }); - }); - - test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({ + test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({ page, - context, }) => { - await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl }); - }); - - test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => { - await testKeylessRemovedAfterEnvAndRestart({ page, context, app }); + const response = await page.goto(`${app.serverUrl}/`); + expect(response?.status()).toBe(500); + const content = await page.content(); + expect(content).toContain('Publishable key is missing'); + expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/integration/tests/nuxt/keyless.test.ts b/integration/tests/nuxt/keyless.test.ts index 6a2cad13033..638229f75e1 100644 --- a/integration/tests/nuxt/keyless.test.ts +++ b/integration/tests/nuxt/keyless.test.ts @@ -1,12 +1,7 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; import { appConfigs } from '../../presets'; -import { - testClaimedAppWithMissingKeys, - testKeylessRemovedAfterEnvAndRestart, - testToggleCollapsePopoverAndClaim, -} from '../../testUtils/keylessHelpers'; const commonSetup = appConfigs.nuxt.node.clone(); @@ -21,35 +16,25 @@ test.describe('Keyless mode @nuxt', () => { }); let app: Application; - let dashboardUrl = 'https://dashboard.clerk.com/'; test.beforeAll(async () => { app = await commonSetup.commit(); await app.setup(); await app.withEnv(appConfigs.envs.withKeyless); - if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) { - dashboardUrl = 'https://dashboard.clerkstage.dev/'; - } await app.dev(); }); test.afterAll(async () => { - // Keep files for debugging await app?.teardown(); }); - test('Toggle collapse popover and claim.', async ({ page, context }) => { - await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'nuxt' }); - }); - - test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({ + test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({ page, - context, }) => { - await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl }); - }); - - test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => { - await testKeylessRemovedAfterEnvAndRestart({ page, context, app }); + const response = await page.goto(`${app.serverUrl}/`); + expect(response?.status()).toBe(500); + const content = await page.content(); + expect(content).toContain('Publishable key is missing'); + expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/integration/tests/react-router/keyless.test.ts b/integration/tests/react-router/keyless.test.ts index a2605806778..21b4e1f6e18 100644 --- a/integration/tests/react-router/keyless.test.ts +++ b/integration/tests/react-router/keyless.test.ts @@ -1,12 +1,7 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; import { appConfigs } from '../../presets'; -import { - testClaimedAppWithMissingKeys, - testKeylessRemovedAfterEnvAndRestart, - testToggleCollapsePopoverAndClaim, -} from '../../testUtils/keylessHelpers'; const commonSetup = appConfigs.reactRouter.reactRouterNode.clone(); @@ -21,35 +16,25 @@ test.describe('Keyless mode @react-router', () => { }); let app: Application; - let dashboardUrl = 'https://dashboard.clerk.com/'; test.beforeAll(async () => { app = await commonSetup.commit(); await app.setup(); await app.withEnv(appConfigs.envs.withKeyless); - if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) { - dashboardUrl = 'https://dashboard.clerkstage.dev/'; - } await app.dev(); }); test.afterAll(async () => { - // Keep files for debugging await app?.teardown(); }); - test('Toggle collapse popover and claim.', async ({ page, context }) => { - await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'react-router' }); - }); - - test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({ + test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({ page, - context, }) => { - await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl }); - }); - - test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => { - await testKeylessRemovedAfterEnvAndRestart({ page, context, app }); + const response = await page.goto(`${app.serverUrl}/`); + expect(response?.status()).toBe(500); + const content = await page.content(); + expect(content).toContain('A secretKey must be provided'); + expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/integration/tests/tanstack-start/keyless.test.ts b/integration/tests/tanstack-start/keyless.test.ts index a2605806778..c0a6c1468ad 100644 --- a/integration/tests/tanstack-start/keyless.test.ts +++ b/integration/tests/tanstack-start/keyless.test.ts @@ -1,16 +1,11 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; import { appConfigs } from '../../presets'; -import { - testClaimedAppWithMissingKeys, - testKeylessRemovedAfterEnvAndRestart, - testToggleCollapsePopoverAndClaim, -} from '../../testUtils/keylessHelpers'; -const commonSetup = appConfigs.reactRouter.reactRouterNode.clone(); +const commonSetup = appConfigs.tanstack.reactStart.clone(); -test.describe('Keyless mode @react-router', () => { +test.describe('Keyless mode @tanstack-react-start', () => { test.describe.configure({ mode: 'serial' }); test.setTimeout(90_000); @@ -21,35 +16,25 @@ test.describe('Keyless mode @react-router', () => { }); let app: Application; - let dashboardUrl = 'https://dashboard.clerk.com/'; test.beforeAll(async () => { app = await commonSetup.commit(); await app.setup(); await app.withEnv(appConfigs.envs.withKeyless); - if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) { - dashboardUrl = 'https://dashboard.clerkstage.dev/'; - } await app.dev(); }); test.afterAll(async () => { - // Keep files for debugging await app?.teardown(); }); - test('Toggle collapse popover and claim.', async ({ page, context }) => { - await testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl, framework: 'react-router' }); - }); - - test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({ + test('Without keys, the app fails with the missing env vars error instead of keyless bootstrap.', async ({ page, - context, }) => { - await testClaimedAppWithMissingKeys({ page, context, app, dashboardUrl }); - }); - - test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => { - await testKeylessRemovedAfterEnvAndRestart({ page, context, app }); + const response = await page.goto(`${app.serverUrl}/`); + expect(response?.status()).toBe(500); + const content = await page.content(); + expect(content).toContain('no secret key provided'); + expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/packages/astro/src/env.d.ts b/packages/astro/src/env.d.ts index dc5c6d3303d..0544664af55 100644 --- a/packages/astro/src/env.d.ts +++ b/packages/astro/src/env.d.ts @@ -31,9 +31,6 @@ interface ImportMeta { declare namespace App { interface Locals { runtime?: { env: InternalEnv }; - keylessClaimUrl?: string; - keylessApiKeysUrl?: string; - keylessPublishableKey?: string; } } diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 73bd5d7dd9c..5c55708c0ac 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -9,7 +9,7 @@ import type { Ui } from '@clerk/ui/internal'; import { $clerkStore } from '../stores/external'; import { $clerk, $csrState } from '../stores/internal'; -import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions, InternalRuntimeOptions } from '../types'; +import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions } from '../types'; import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions'; import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components'; import { runOnce } from './run-once'; @@ -54,18 +54,12 @@ async function createClerkInstanceInternal(options?: AstroC $clerk.set(clerkJSInstance); } - const internalOptions = options as AstroClerkCreateInstanceParams & InternalRuntimeOptions; - const keylessClaimUrl = internalOptions.__internal_keylessClaimUrl; - const keylessApiKeysUrl = internalOptions.__internal_keylessApiKeysUrl; - const clerkOptions = { routerPush: createNavigationHandler(window.history.pushState.bind(window.history)), routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)), ...options, // Pass the clerk-ui constructor promise to clerk.load() ui: { ...options?.ui, ClerkUI }, - ...(keylessClaimUrl && { __internal_keyless_claimKeylessApplicationUrl: keylessClaimUrl }), - ...(keylessApiKeysUrl && { __internal_keyless_copyInstanceKeysUrl: keylessApiKeysUrl }), } as unknown as ClerkOptions; initOptions = clerkOptions; diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index 2350c34975b..db7ed797530 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -54,7 +54,6 @@ const mergeEnvVarsWithParams = ( isSatellite: paramSatellite || import.meta.env.PUBLIC_CLERK_IS_SATELLITE, proxyUrl: paramProxy || import.meta.env.PUBLIC_CLERK_PROXY_URL, domain: paramDomain || import.meta.env.PUBLIC_CLERK_DOMAIN, - // In keyless mode, use server-injected publishableKey from params publishableKey: paramPublishableKey || internalOptions?.publishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', __internal_clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, @@ -69,10 +68,6 @@ const mergeEnvVarsWithParams = ( unsafe_disableDevelopmentModeConsoleWarning: paramUnsafeDisableDevelopmentModeConsoleWarning ?? isTruthy(import.meta.env.PUBLIC_CLERK_UNSAFE_DISABLE_DEVELOPMENT_MODE_CONSOLE_WARNING), - // Read from params (server-injected via __CLERK_ASTRO_SAFE_VARS__) - // These are dynamically resolved by middleware, not from env vars - __internal_keylessClaimUrl: internalOptions?.keylessClaimUrl, - __internal_keylessApiKeysUrl: internalOptions?.keylessApiKeysUrl, ...rest, }; }; diff --git a/packages/astro/src/server/__tests__/get-safe-env.test.ts b/packages/astro/src/server/__tests__/get-safe-env.test.ts index 0a57f8ab8fe..1291a9bf5cd 100644 --- a/packages/astro/src/server/__tests__/get-safe-env.test.ts +++ b/packages/astro/src/server/__tests__/get-safe-env.test.ts @@ -211,20 +211,6 @@ describe('getSafeEnv', () => { expect(env.pk).toBeUndefined(); expect(env.sk).toBeUndefined(); }); - - it('prefers keylessPublishableKey over all env sources', () => { - process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process'; - - const locals = createLocals({ - runtime: { env: undefined as unknown as InternalEnv }, - keylessPublishableKey: 'pk_keyless', - }); - const env = getSafeEnv(locals); - - expect(env.pk).toBe('pk_keyless'); - - delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY; - }); }); describe('getClientSafeEnv', () => { diff --git a/packages/astro/src/server/clerk-middleware.ts b/packages/astro/src/server/clerk-middleware.ts index 6bbeb09f7dd..ba388175fc5 100644 --- a/packages/astro/src/server/clerk-middleware.ts +++ b/packages/astro/src/server/clerk-middleware.ts @@ -27,12 +27,10 @@ import type { APIContext } from 'astro'; import { authAsyncStorage } from '#async-local-storage'; -import { canUseKeyless } from '../utils/feature-flags'; import { buildClerkHotloadScript } from './build-clerk-hotload-script'; import { clerkClient } from './clerk-client'; import { createCurrentUser } from './current-user'; import { getClientSafeEnv, getSafeEnv, initCloudflareEnv } from './get-safe-env'; -import { resolveKeysWithKeylessFallback } from './keyless/utils'; import { serverRedirectWithAuth } from './server-redirect-with-auth'; import type { AstroMiddleware, @@ -87,42 +85,9 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => { const patchedRequest = patchRequest(context.request); const clerkRequest = createClerkRequest(patchedRequest); - // Resolve keyless URLs per-request in development - let keylessClaimUrl: string | undefined; - let keylessApiKeysUrl: string | undefined; - let keylessOptions = options; - - if (canUseKeyless) { - try { - const env = getSafeEnv(context); - const configuredPublishableKey = options?.publishableKey || env.pk; - const configuredSecretKey = options?.secretKey || env.sk; - - const keylessResult = await resolveKeysWithKeylessFallback( - configuredPublishableKey, - configuredSecretKey, - context, - ); - - keylessClaimUrl = keylessResult.claimUrl; - keylessApiKeysUrl = keylessResult.apiKeysUrl; - - // Override keys with keyless values if returned - if (keylessResult.publishableKey || keylessResult.secretKey) { - keylessOptions = { - ...options, - ...(keylessResult.publishableKey && { publishableKey: keylessResult.publishableKey }), - ...(keylessResult.secretKey && { secretKey: keylessResult.secretKey }), - }; - } - } catch { - // Silently fail - continue without keyless - } - } - const requestState = await clerkClient(context).authenticateRequest( clerkRequest, - createAuthenticateRequestOptions(clerkRequest, keylessOptions, context), + createAuthenticateRequestOptions(clerkRequest, options, context), ); const locationHeader = requestState.headers.get(constants.Headers.Location); @@ -145,16 +110,6 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => { decorateAstroLocal(clerkRequest, authObjectFn, context, requestState); - // Store keyless data for injection into client - if (keylessClaimUrl || keylessApiKeysUrl) { - context.locals.keylessClaimUrl = keylessClaimUrl; - context.locals.keylessApiKeysUrl = keylessApiKeysUrl; - // Also store the resolved publishable key so client can use it - if (keylessOptions?.publishableKey) { - context.locals.keylessPublishableKey = keylessOptions.publishableKey; - } - } - /** * ALS is crucial for guaranteeing SSR in UI frameworks like React. * This currently powers the `useAuth()` React hook and any other hook or Component that depends on it. diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index 1d918853592..99abf8b401c 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -73,14 +73,11 @@ function getContextEnvVar(envVarName: keyof InternalEnv, contextOrLocals: Contex * @internal */ function getSafeEnv(context: ContextOrLocals) { - const locals = 'locals' in context ? context.locals : context; - return { domain: getContextEnvVar('PUBLIC_CLERK_DOMAIN', context), isSatellite: getContextEnvVar('PUBLIC_CLERK_IS_SATELLITE', context) === 'true', proxyUrl: getContextEnvVar('PUBLIC_CLERK_PROXY_URL', context), - // Use keyless publishable key if available, otherwise read from env - pk: locals.keylessPublishableKey || getContextEnvVar('PUBLIC_CLERK_PUBLISHABLE_KEY', context), + pk: getContextEnvVar('PUBLIC_CLERK_PUBLISHABLE_KEY', context), sk: getContextEnvVar('CLERK_SECRET_KEY', context), machineSecretKey: getContextEnvVar('CLERK_MACHINE_SECRET_KEY', context), signInUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_IN_URL', context), @@ -94,9 +91,6 @@ function getSafeEnv(context: ContextOrLocals) { apiUrl: getContextEnvVar('CLERK_API_URL', context), telemetryDisabled: isTruthy(getContextEnvVar('PUBLIC_CLERK_TELEMETRY_DISABLED', context)), telemetryDebug: isTruthy(getContextEnvVar('PUBLIC_CLERK_TELEMETRY_DEBUG', context)), - // Read from locals (set by middleware) instead of env vars - keylessClaimUrl: locals.keylessClaimUrl, - keylessApiKeysUrl: locals.keylessApiKeysUrl, }; } @@ -108,19 +102,13 @@ function getSafeEnv(context: ContextOrLocals) { * This is a way to get around it. */ function getClientSafeEnv(context: ContextOrLocals) { - const locals = 'locals' in context ? context.locals : context; - return { domain: getContextEnvVar('PUBLIC_CLERK_DOMAIN', context), isSatellite: getContextEnvVar('PUBLIC_CLERK_IS_SATELLITE', context) === 'true', proxyUrl: getContextEnvVar('PUBLIC_CLERK_PROXY_URL', context), signInUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_IN_URL', context), signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), - // In keyless mode, pass the resolved publishable key to client - publishableKey: locals.keylessPublishableKey || getContextEnvVar('PUBLIC_CLERK_PUBLISHABLE_KEY', context), - // Read from locals (set by middleware) instead of env vars - keylessClaimUrl: locals.keylessClaimUrl, - keylessApiKeysUrl: locals.keylessApiKeysUrl, + publishableKey: getContextEnvVar('PUBLIC_CLERK_PUBLISHABLE_KEY', context), }; } diff --git a/packages/astro/src/server/keyless/file-storage.ts b/packages/astro/src/server/keyless/file-storage.ts deleted file mode 100644 index 329f39b1285..00000000000 --- a/packages/astro/src/server/keyless/file-storage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -import { createNodeFileStorage, type KeylessStorage } from '@clerk/shared/keyless'; - -export type { KeylessStorage }; - -export interface FileStorageOptions { - cwd?: () => string; -} - -export function createFileStorage(options: FileStorageOptions = {}): KeylessStorage { - const { cwd = () => process.cwd() } = options; - - return createNodeFileStorage(fs, path, { - cwd, - frameworkPackageName: '@clerk/astro', - }); -} diff --git a/packages/astro/src/server/keyless/index.ts b/packages/astro/src/server/keyless/index.ts deleted file mode 100644 index 15df38e46b6..00000000000 --- a/packages/astro/src/server/keyless/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createKeylessService } from '@clerk/shared/keyless'; -import type { APIContext } from 'astro'; - -import { clerkClient } from '../clerk-client'; -import { createFileStorage } from './file-storage.js'; - -// Lazily initialized keyless service singleton -let keylessServiceInstance: ReturnType | null = null; - -export function keyless(context: APIContext) { - if (!keylessServiceInstance) { - keylessServiceInstance = createKeylessService({ - storage: createFileStorage(), - api: { - async createAccountlessApplication(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient(context).__experimental_accountlessApplications.createAccountlessApplication({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - async completeOnboarding(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient( - context, - ).__experimental_accountlessApplications.completeAccountlessApplicationOnboarding({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - }, - framework: 'astro', - }); - } - return keylessServiceInstance; -} diff --git a/packages/astro/src/server/keyless/utils.ts b/packages/astro/src/server/keyless/utils.ts deleted file mode 100644 index f0f0d0e0898..00000000000 --- a/packages/astro/src/server/keyless/utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { resolveKeysWithKeylessFallback as sharedResolveKeysWithKeylessFallback } from '@clerk/shared/keyless'; -import type { APIContext } from 'astro'; -export type { KeylessResult } from '@clerk/shared/keyless'; - -import { canUseKeyless } from '../../utils/feature-flags'; -import { keyless } from './index'; - -/** - * Resolves Clerk keys, falling back to keyless mode in development if configured keys are missing. - */ -export async function resolveKeysWithKeylessFallback( - configuredPublishableKey: string | undefined, - configuredSecretKey: string | undefined, - context: APIContext, -) { - const keylessService = await keyless(context); - return sharedResolveKeysWithKeylessFallback( - configuredPublishableKey, - configuredSecretKey, - keylessService, - canUseKeyless, - ); -} diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index c6c563a68da..8e7207fffa3 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -45,29 +45,13 @@ type AstroClerkCreateInstanceParams = AstroClerkIntegration /** * @internal - * Internal runtime options injected by the server for keyless mode support. + * Internal runtime options injected by the server. */ export type InternalRuntimeOptions = { /** - * Server-injected publishable key from keyless mode or context.locals + * Server-injected publishable key from context.locals */ publishableKey?: string; - /** - * Keyless claim URL injected by middleware for the client-side banner - */ - keylessClaimUrl?: string; - /** - * Keyless API keys URL injected by middleware for the client-side banner - */ - keylessApiKeysUrl?: string; - /** - * Internal keyless claim URL passed to Clerk.load() - */ - __internal_keylessClaimUrl?: string; - /** - * Internal keyless API keys URL passed to Clerk.load() - */ - __internal_keylessApiKeysUrl?: string; }; // Copied from `@clerk/react` diff --git a/packages/astro/src/utils/__tests__/feature-flags.test.ts b/packages/astro/src/utils/__tests__/feature-flags.test.ts deleted file mode 100644 index 5baa1304a80..00000000000 --- a/packages/astro/src/utils/__tests__/feature-flags.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { automatedEnvironmentVariables } from '@clerk/shared/utils'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -async function loadCanUseKeyless() { - vi.resetModules(); - const { canUseKeyless } = await import('../feature-flags.js'); - return canUseKeyless; -} - -describe('canUseKeyless', () => { - beforeEach(() => { - vi.stubEnv('NODE_ENV', 'development'); - vi.stubEnv('PUBLIC_CLERK_KEYLESS_DISABLED', undefined); - vi.stubEnv('CLERK_KEYLESS_DISABLED', undefined); - automatedEnvironmentVariables.forEach(name => { - vi.stubEnv(name, undefined); - vi.stubGlobal(name, undefined); - }); - }); - - afterEach(() => { - vi.unstubAllEnvs(); - vi.unstubAllGlobals(); - vi.resetModules(); - }); - - it('enables keyless in development when automation signals are absent', async () => { - await expect(loadCanUseKeyless()).resolves.toBe(true); - }); - - it('disables keyless in CI even when the app runs in development mode', async () => { - vi.stubEnv('CI', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless outside development mode', async () => { - vi.stubEnv('NODE_ENV', 'production'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless when explicitly disabled', async () => { - vi.stubEnv('PUBLIC_CLERK_KEYLESS_DISABLED', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); -}); diff --git a/packages/astro/src/utils/feature-flags.ts b/packages/astro/src/utils/feature-flags.ts deleted file mode 100644 index c0131261a0f..00000000000 --- a/packages/astro/src/utils/feature-flags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getEnvVariable } from '@clerk/shared/getEnvVariable'; -import { isTruthy } from '@clerk/shared/underscore'; -import { isAutomatedEnvironment, isDevelopmentEnvironment } from '@clerk/shared/utils'; - -const KEYLESS_DISABLED = - isTruthy(getEnvVariable('PUBLIC_CLERK_KEYLESS_DISABLED')) || - isTruthy(getEnvVariable('CLERK_KEYLESS_DISABLED')) || - false; - -export const canUseKeyless = isDevelopmentEnvironment() && !isAutomatedEnvironment() && !KEYLESS_DISABLED; diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 2ca118a99b4..650358ef02c 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -4,20 +4,16 @@ import { clerkPlugin } from '@clerk/vue'; import { setErrorThrowerOptions } from '@clerk/vue/internal'; import { defineNuxtPlugin, navigateTo, useRuntimeConfig, useState } from 'nuxt/app'; -import type { ClerkKeylessContext } from './server/types'; - setErrorThrowerOptions({ packageName: PACKAGE_NAME }); setClerkJSLoadingErrorPackageName(PACKAGE_NAME); export default defineNuxtPlugin(nuxtApp => { // SSR-friendly shared state const initialState = useState('clerk-initial-state', () => undefined); - const keylessContext = useState('clerk-keyless-context', () => undefined); if (import.meta.server) { // Save the initial state from server and pass it to the plugin initialState.value = nuxtApp.ssrContext?.event.context.__clerk_initial_state; - keylessContext.value = nuxtApp.ssrContext?.event.context.__clerk_keyless; } const runtimeConfig = useRuntimeConfig(); @@ -38,12 +34,5 @@ export default defineNuxtPlugin(nuxtApp => { routerPush: (to: string) => navigateTo(to), routerReplace: (to: string) => navigateTo(to, { replace: true }), initialState: initialState.value, - // Add keyless mode props if present - ...(keylessContext.value - ? { - __internal_keyless_claimKeylessApplicationUrl: keylessContext.value.claimUrl, - __internal_keyless_copyInstanceKeysUrl: keylessContext.value.apiKeysUrl, - } - : {}), }); }); diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index aae8e02305f..5169fa2b8bd 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -7,9 +7,7 @@ import type { EventHandler } from 'h3'; import { createError, eventHandler, setResponseHeader, useRuntimeConfig } from '#imports'; -import { canUseKeyless } from '../utils/feature-flags'; import { clerkClient } from './clerkClient'; -import { resolveKeysWithKeylessFallback } from './keyless/utils'; import type { AuthFn, AuthOptions } from './types'; import { createInitialState, toWebRequest } from './utils'; @@ -86,35 +84,6 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { return eventHandler(async event => { const clerkRequest = toWebRequest(event); - // Resolve keyless in development if keys are missing - let keylessClaimUrl: string | undefined; - let keylessApiKeysUrl: string | undefined; - - if (canUseKeyless) { - try { - const runtimeConfig = useRuntimeConfig(event); - - const { publishableKey, secretKey, claimUrl, apiKeysUrl } = await resolveKeysWithKeylessFallback( - runtimeConfig.public.clerk.publishableKey, - runtimeConfig.clerk.secretKey, - event, - ); - - keylessClaimUrl = claimUrl; - keylessApiKeysUrl = apiKeysUrl; - - // Override runtime config with keyless values if returned - if (publishableKey) { - runtimeConfig.public.clerk.publishableKey = publishableKey; - } - if (secretKey) { - runtimeConfig.clerk.secretKey = secretKey; - } - } catch { - // Silently fail - continue without keyless - } - } - const requestState = await clerkClient(event).authenticateRequest(clerkRequest, { ...options, acceptsToken: 'any', @@ -150,14 +119,6 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { // Internal serializable state that will be passed to the client event.context.__clerk_initial_state = createInitialState(authObjectFn()); - // Store keyless mode URLs in separate context property - if (canUseKeyless && keylessClaimUrl) { - event.context.__clerk_keyless = { - claimUrl: keylessClaimUrl, - apiKeysUrl: keylessApiKeysUrl, - }; - } - try { await handler?.(event); } catch (e) { diff --git a/packages/nuxt/src/runtime/server/keyless/fileStorage.ts b/packages/nuxt/src/runtime/server/keyless/fileStorage.ts deleted file mode 100644 index 340b011dcef..00000000000 --- a/packages/nuxt/src/runtime/server/keyless/fileStorage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -import { createNodeFileStorage, type KeylessStorage } from '@clerk/shared/keyless'; - -export type { KeylessStorage }; - -export interface FileStorageOptions { - cwd?: () => string; -} - -export function createFileStorage(options: FileStorageOptions = {}): KeylessStorage { - const { cwd = () => process.cwd() } = options; - - return createNodeFileStorage(fs, path, { - cwd, - frameworkPackageName: '@clerk/nuxt', - }); -} diff --git a/packages/nuxt/src/runtime/server/keyless/index.ts b/packages/nuxt/src/runtime/server/keyless/index.ts deleted file mode 100644 index 14072688105..00000000000 --- a/packages/nuxt/src/runtime/server/keyless/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createKeylessService } from '@clerk/shared/keyless'; -import type { H3Event } from 'h3'; - -import { clerkClient } from '../clerkClient'; -import { createFileStorage } from './fileStorage'; - -// Lazily initialized keyless service singleton -let keylessServiceInstance: ReturnType | null = null; - -export function keyless(event: H3Event) { - if (!keylessServiceInstance) { - keylessServiceInstance = createKeylessService({ - storage: createFileStorage(), - api: { - async createAccountlessApplication(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient(event).__experimental_accountlessApplications.createAccountlessApplication({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - async completeOnboarding(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient( - event, - ).__experimental_accountlessApplications.completeAccountlessApplicationOnboarding({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - }, - framework: 'nuxt', - }); - } - return keylessServiceInstance; -} diff --git a/packages/nuxt/src/runtime/server/keyless/utils.ts b/packages/nuxt/src/runtime/server/keyless/utils.ts deleted file mode 100644 index 005f02242e2..00000000000 --- a/packages/nuxt/src/runtime/server/keyless/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { resolveKeysWithKeylessFallback as sharedResolveKeysWithKeylessFallback } from '@clerk/shared/keyless'; -import type { H3Event } from 'h3'; - -import { canUseKeyless } from '../../utils/feature-flags'; -import { keyless } from './index'; - -export type { KeylessResult } from '@clerk/shared/keyless'; - -/** - * Resolves Clerk keys, falling back to keyless mode in development if configured keys are missing. - */ -export async function resolveKeysWithKeylessFallback( - configuredPublishableKey: string | undefined, - configuredSecretKey: string | undefined, - event: H3Event, -) { - const keylessService = keyless(event); - return sharedResolveKeysWithKeylessFallback( - configuredPublishableKey, - configuredSecretKey, - keylessService, - canUseKeyless, - ); -} diff --git a/packages/nuxt/src/runtime/server/types.ts b/packages/nuxt/src/runtime/server/types.ts index 0700b3531f3..c4369d57e23 100644 --- a/packages/nuxt/src/runtime/server/types.ts +++ b/packages/nuxt/src/runtime/server/types.ts @@ -7,11 +7,3 @@ export type AuthOptions = PendingSessionOptions & Pick { - beforeEach(() => { - vi.stubEnv('NODE_ENV', 'development'); - vi.stubEnv('NUXT_PUBLIC_CLERK_KEYLESS_DISABLED', undefined); - vi.stubEnv('CLERK_KEYLESS_DISABLED', undefined); - automatedEnvironmentVariables.forEach(name => { - vi.stubEnv(name, undefined); - vi.stubGlobal(name, undefined); - }); - }); - - afterEach(() => { - vi.unstubAllEnvs(); - vi.unstubAllGlobals(); - vi.resetModules(); - }); - - it('enables keyless in development when automation signals are absent', async () => { - await expect(loadCanUseKeyless()).resolves.toBe(true); - }); - - it('disables keyless in CI even when the app runs in development mode', async () => { - vi.stubEnv('CI', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless outside development mode', async () => { - vi.stubEnv('NODE_ENV', 'production'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless when explicitly disabled', async () => { - vi.stubEnv('NUXT_PUBLIC_CLERK_KEYLESS_DISABLED', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); -}); diff --git a/packages/nuxt/src/runtime/utils/feature-flags.ts b/packages/nuxt/src/runtime/utils/feature-flags.ts deleted file mode 100644 index 35a2a9f09fa..00000000000 --- a/packages/nuxt/src/runtime/utils/feature-flags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getEnvVariable } from '@clerk/shared/getEnvVariable'; -import { isTruthy } from '@clerk/shared/underscore'; -import { isAutomatedEnvironment, isDevelopmentEnvironment } from '@clerk/shared/utils'; - -const KEYLESS_DISABLED = - isTruthy(getEnvVariable('NUXT_PUBLIC_CLERK_KEYLESS_DISABLED')) || - isTruthy(getEnvVariable('CLERK_KEYLESS_DISABLED')) || - false; - -export const canUseKeyless = isDevelopmentEnvironment() && !isAutomatedEnvironment() && !KEYLESS_DISABLED; diff --git a/packages/nuxt/tsdown.config.ts b/packages/nuxt/tsdown.config.ts index 877330e1cd1..99bfadcf4be 100644 --- a/packages/nuxt/tsdown.config.ts +++ b/packages/nuxt/tsdown.config.ts @@ -14,7 +14,6 @@ export default defineConfig(overrideOptions => { './src/runtime/composables/index.ts', './src/runtime/client/*.ts', './src/runtime/server/*.ts', - './src/runtime/server/keyless/*.ts', './src/runtime/utils/*.ts', './src/runtime/errors.ts', './src/runtime/webhooks.ts', diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index 3883b95ea55..380fc26087f 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -67,8 +67,6 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv __telemetryDisabled, __telemetryDebug, __unsafeDisableDevelopmentModeConsoleWarning, - __keylessClaimUrl, - __keylessApiKeysUrl, } = clerkState?.__internal_clerk_state || {}; React.useEffect(() => { @@ -106,13 +104,6 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv getPublicEnvVariables(undefined).unsafeDisableDevelopmentModeConsoleWarning, }; - const keylessProps = __keylessClaimUrl - ? { - __internal_keyless_claimKeylessApplicationUrl: __keylessClaimUrl, - __internal_keyless_copyInstanceKeysUrl: __keylessApiKeysUrl, - } - : {}; - return ( ({ children, ...rest }: ClerkProv initialState={__clerk_ssr_state} sdkMetadata={SDK_METADATA} {...mergedProps} - {...keylessProps} {...restProps} > {children} diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index c6d402f92a9..21dfbab6716 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -26,8 +26,6 @@ export type ClerkState = { __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; __unsafeDisableDevelopmentModeConsoleWarning: boolean | undefined; - __keylessClaimUrl?: string; - __keylessApiKeysUrl?: string; }; }; diff --git a/packages/react-router/src/server/clerkMiddleware.ts b/packages/react-router/src/server/clerkMiddleware.ts index 7b6e9a81413..54aedab4010 100644 --- a/packages/react-router/src/server/clerkMiddleware.ts +++ b/packages/react-router/src/server/clerkMiddleware.ts @@ -8,7 +8,6 @@ import type { MiddlewareFunction } from 'react-router'; import { createContext } from 'react-router'; import { clerkClient } from './clerkClient'; -import { resolveKeysWithKeylessFallback } from './keyless/utils'; import { loadOptions } from './loadOptions'; import type { AdditionalStateOptions, ClerkMiddlewareOptions } from './types'; @@ -36,20 +35,6 @@ export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareFun const clerkRequest = createClerkRequest(patchRequest(args.request)); const loadedOptions = loadOptions(args, options); - const { - publishableKey, - secretKey, - claimUrl: __keylessClaimUrl, - apiKeysUrl: __keylessApiKeysUrl, - } = await resolveKeysWithKeylessFallback(loadedOptions.publishableKey, loadedOptions.secretKey, args, options); - - if (publishableKey) { - loadedOptions.publishableKey = publishableKey; - } - if (secretKey) { - loadedOptions.secretKey = secretKey; - } - // Pick only the properties needed by authenticateRequest. // Used when manually providing options to the middleware. const { @@ -102,8 +87,6 @@ export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareFun args.context.set(requestStateContext, { requestState, additionalState: { - __keylessClaimUrl, - __keylessApiKeysUrl, signInForceRedirectUrl: loadedOptions.signInForceRedirectUrl, signUpForceRedirectUrl: loadedOptions.signUpForceRedirectUrl, signInFallbackRedirectUrl: loadedOptions.signInFallbackRedirectUrl, diff --git a/packages/react-router/src/server/keyless/fileStorage.ts b/packages/react-router/src/server/keyless/fileStorage.ts deleted file mode 100644 index 693fb110725..00000000000 --- a/packages/react-router/src/server/keyless/fileStorage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -import { createNodeFileStorage, type KeylessStorage } from '@clerk/shared/keyless'; - -export type { KeylessStorage }; - -export interface FileStorageOptions { - cwd?: () => string; -} - -export function createFileStorage(options: FileStorageOptions = {}): KeylessStorage { - const { cwd = () => process.cwd() } = options; - - return createNodeFileStorage(fs, path, { - cwd, - frameworkPackageName: '@clerk/react-router', - }); -} diff --git a/packages/react-router/src/server/keyless/index.ts b/packages/react-router/src/server/keyless/index.ts deleted file mode 100644 index b347fa888e1..00000000000 --- a/packages/react-router/src/server/keyless/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createKeylessService } from '@clerk/shared/keyless'; - -import { clerkClient } from '../clerkClient'; -import type { DataFunctionArgs } from '../loadOptions'; -import type { ClerkMiddlewareOptions } from '../types'; -import { createFileStorage } from './fileStorage'; - -// Lazily initialized keyless service singleton -let keylessServiceInstance: ReturnType | null = null; - -export function keyless(args: DataFunctionArgs, options?: ClerkMiddlewareOptions) { - if (!keylessServiceInstance) { - keylessServiceInstance = createKeylessService({ - storage: createFileStorage(), - api: { - async createAccountlessApplication(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient(args, options).__experimental_accountlessApplications.createAccountlessApplication( - { - requestHeaders, - source, - }, - ); - } catch { - return null; - } - }, - async completeOnboarding(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient( - args, - options, - ).__experimental_accountlessApplications.completeAccountlessApplicationOnboarding({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - }, - framework: 'react-router', - }); - } - return keylessServiceInstance; -} diff --git a/packages/react-router/src/server/keyless/utils.ts b/packages/react-router/src/server/keyless/utils.ts deleted file mode 100644 index 138a30d22cd..00000000000 --- a/packages/react-router/src/server/keyless/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { resolveKeysWithKeylessFallback as sharedResolveKeysWithKeylessFallback } from '@clerk/shared/keyless'; -export type { KeylessResult } from '@clerk/shared/keyless'; - -import { canUseKeyless } from '../../utils/feature-flags'; -import type { DataFunctionArgs } from '../loadOptions'; -import type { ClerkMiddlewareOptions } from '../types'; -import { keyless } from './index'; - -/** - * Resolves Clerk keys, falling back to keyless mode in development if configured keys are missing. - */ -export async function resolveKeysWithKeylessFallback( - configuredPublishableKey: string | undefined, - configuredSecretKey: string | undefined, - args: DataFunctionArgs, - options?: ClerkMiddlewareOptions, -) { - const keylessService = await keyless(args, options); - return sharedResolveKeysWithKeylessFallback( - configuredPublishableKey, - configuredSecretKey, - keylessService, - canUseKeyless, - ); -} diff --git a/packages/react-router/src/server/loadOptions.ts b/packages/react-router/src/server/loadOptions.ts index 50ffc136519..b6372b85dd8 100644 --- a/packages/react-router/src/server/loadOptions.ts +++ b/packages/react-router/src/server/loadOptions.ts @@ -9,7 +9,6 @@ import type { MiddlewareFunction } from 'react-router'; import { getPublicEnvVariables } from '../utils/env'; import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors'; -import { canUseKeyless } from '../utils/feature-flags'; import type { ClerkMiddlewareOptions } from './types'; export type DataFunctionArgs = Parameters>[0]; @@ -56,7 +55,7 @@ export const loadOptions = (args: DataFunctionArgs, overrides: ClerkMiddlewareOp proxyUrl = relativeOrAbsoluteProxyUrl; } - if (!secretKey && !canUseKeyless) { + if (!secretKey) { throw new Error(noSecretKeyError); } if (isSatellite && !proxyUrl && !domain) { diff --git a/packages/react-router/src/server/types.ts b/packages/react-router/src/server/types.ts index 9342f6b39af..b5008814e3d 100644 --- a/packages/react-router/src/server/types.ts +++ b/packages/react-router/src/server/types.ts @@ -63,16 +63,10 @@ export type RootAuthLoaderOptions = ClerkMiddlewareOptions & { loadOrganization?: boolean; }; -export interface KeylessUrls { - __keylessClaimUrl?: string; - __keylessApiKeysUrl?: string; -} - export type AdditionalStateOptions = SignInFallbackRedirectUrl & SignUpFallbackRedirectUrl & SignInForceRedirectUrl & - SignUpForceRedirectUrl & - KeylessUrls; + SignUpForceRedirectUrl; /** * @deprecated This type is no longer used internally. Use `AdditionalStateOptions` instead. @@ -81,8 +75,7 @@ export type RequestStateWithRedirectUrls = RequestState & SignInForceRedirectUrl & SignInFallbackRedirectUrl & SignUpForceRedirectUrl & - SignUpFallbackRedirectUrl & - KeylessUrls; + SignUpFallbackRedirectUrl; export type RootAuthLoaderCallback = ( args: LoaderFunctionArgsWithAuth, diff --git a/packages/react-router/src/server/utils.ts b/packages/react-router/src/server/utils.ts index 98904f11116..681b3abfa70 100644 --- a/packages/react-router/src/server/utils.ts +++ b/packages/react-router/src/server/utils.ts @@ -4,7 +4,6 @@ import { parse as parseCookie } from 'cookie'; import type { UNSAFE_DataWithResponseInit } from 'react-router'; import { getPublicEnvVariables } from '../utils/env'; -import { canUseKeyless } from '../utils/feature-flags'; import type { AdditionalStateOptions } from './types'; // AppLoadContext was removed from React Router v8. Keep a structural type for the context shape we use. @@ -94,7 +93,7 @@ export function getResponseClerkState( ) { const { reason, message, isSignedIn, ...rest } = requestState; const envVars = getPublicEnvVariables(context); - const { __keylessClaimUrl, __keylessApiKeysUrl, ...redirectUrlOptions } = additionalStateOptions; + const redirectUrlOptions = additionalStateOptions; const baseState: Record = { __clerk_ssr_state: rest.toAuth(), @@ -119,11 +118,6 @@ export function getResponseClerkState( __unsafeDisableDevelopmentModeConsoleWarning: envVars.unsafeDisableDevelopmentModeConsoleWarning, }; - if (canUseKeyless && __keylessClaimUrl) { - baseState.__keylessClaimUrl = __keylessClaimUrl; - baseState.__keylessApiKeysUrl = __keylessApiKeysUrl; - } - const clerkState = wrapWithClerkState(baseState); return { diff --git a/packages/react-router/src/utils/__tests__/feature-flags.test.ts b/packages/react-router/src/utils/__tests__/feature-flags.test.ts deleted file mode 100644 index 693278f61f9..00000000000 --- a/packages/react-router/src/utils/__tests__/feature-flags.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { automatedEnvironmentVariables } from '@clerk/shared/utils'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -async function loadCanUseKeyless() { - vi.resetModules(); - const { canUseKeyless } = await import('../feature-flags.js'); - return canUseKeyless; -} - -describe('canUseKeyless', () => { - beforeEach(() => { - vi.stubEnv('NODE_ENV', 'development'); - vi.stubEnv('VITE_CLERK_KEYLESS_DISABLED', undefined); - vi.stubEnv('CLERK_KEYLESS_DISABLED', undefined); - automatedEnvironmentVariables.forEach(name => { - vi.stubEnv(name, undefined); - vi.stubGlobal(name, undefined); - }); - }); - - afterEach(() => { - vi.unstubAllEnvs(); - vi.unstubAllGlobals(); - vi.resetModules(); - }); - - it('enables keyless in development when automation signals are absent', async () => { - await expect(loadCanUseKeyless()).resolves.toBe(true); - }); - - it('disables keyless in CI even when the app runs in development mode', async () => { - vi.stubEnv('CI', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless outside development mode', async () => { - vi.stubEnv('NODE_ENV', 'production'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless when explicitly disabled', async () => { - vi.stubEnv('VITE_CLERK_KEYLESS_DISABLED', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); -}); diff --git a/packages/react-router/src/utils/errors.ts b/packages/react-router/src/utils/errors.ts index 8e0a7682f2b..bab8a97ad57 100644 --- a/packages/react-router/src/utils/errors.ts +++ b/packages/react-router/src/utils/errors.ts @@ -73,7 +73,8 @@ export async function loader(args: Route.LoaderArgs) { `); export const noSecretKeyError = createErrorMessage(` -A secretKey must be provided in order to use SSR and the exports from @clerk/react-router/api.'); +A secretKey must be provided in order to use SSR and the exports from @clerk/react-router/api. +To set up Clerk for this project, run \`npx clerk@latest init\` — it is non-interactive, requires no Clerk account or login, and writes the required keys to your .env file. If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config. Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth. `); diff --git a/packages/react-router/src/utils/feature-flags.ts b/packages/react-router/src/utils/feature-flags.ts deleted file mode 100644 index f28932ab26a..00000000000 --- a/packages/react-router/src/utils/feature-flags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getEnvVariable } from '@clerk/shared/getEnvVariable'; -import { isTruthy } from '@clerk/shared/underscore'; -import { isAutomatedEnvironment, isDevelopmentEnvironment } from '@clerk/shared/utils'; - -const KEYLESS_DISABLED = - isTruthy(getEnvVariable('VITE_CLERK_KEYLESS_DISABLED')) || - isTruthy(getEnvVariable('CLERK_KEYLESS_DISABLED')) || - false; - -export const canUseKeyless = isDevelopmentEnvironment() && !isAutomatedEnvironment() && !KEYLESS_DISABLED; diff --git a/packages/tanstack-react-start/src/client/ClerkProvider.tsx b/packages/tanstack-react-start/src/client/ClerkProvider.tsx index 9231da2a554..f73a3310e1b 100644 --- a/packages/tanstack-react-start/src/client/ClerkProvider.tsx +++ b/packages/tanstack-react-start/src/client/ClerkProvider.tsx @@ -31,23 +31,13 @@ export function ClerkProvider({ const clerkInitState = isClient() ? (window as any).__clerk_init_state : clerkInitialState; - const { clerkSsrState, __keylessClaimUrl, __keylessApiKeysUrl, ...restInitState } = pickFromClerkInitState( - clerkInitState?.__internal_clerk_state, - ); + const { clerkSsrState, ...restInitState } = pickFromClerkInitState(clerkInitState?.__internal_clerk_state); const mergedProps = { ...mergeWithPublicEnvs(restInitState), ...providerProps, }; - // Add keyless mode props if present - const keylessProps = __keylessClaimUrl - ? { - __internal_keyless_claimKeylessApplicationUrl: __keylessClaimUrl, - __internal_keyless_copyInstanceKeysUrl: __keylessApiKeysUrl, - } - : {}; - return ( <> {`window.__clerk_init_state = ${htmlSafeJson(clerkInitialState)};`} @@ -74,7 +64,6 @@ export function ClerkProvider({ }); }} {...mergedProps} - {...keylessProps} > {children} diff --git a/packages/tanstack-react-start/src/client/utils.ts b/packages/tanstack-react-start/src/client/utils.ts index fd9bcea0afd..4198618b81e 100644 --- a/packages/tanstack-react-start/src/client/utils.ts +++ b/packages/tanstack-react-start/src/client/utils.ts @@ -10,8 +10,6 @@ export const pickFromClerkInitState = ( ): TanStackProviderAndInitialProps & InternalClerkScriptProps & { clerkSsrState: any; - __keylessClaimUrl?: string; - __keylessApiKeysUrl?: string; } => { const { __clerk_ssr_state, @@ -32,8 +30,6 @@ export const pickFromClerkInitState = ( __signUpForceRedirectUrl, __signInFallbackRedirectUrl, __signUpFallbackRedirectUrl, - __keylessClaimUrl, - __keylessApiKeysUrl, __prefetchUI, } = clerkInitState || {}; @@ -59,8 +55,6 @@ export const pickFromClerkInitState = ( signUpForceRedirectUrl: __signUpForceRedirectUrl, signInFallbackRedirectUrl: __signInFallbackRedirectUrl, signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, - __keylessClaimUrl, - __keylessApiKeysUrl, }; }; diff --git a/packages/tanstack-react-start/src/server/clerkMiddleware.ts b/packages/tanstack-react-start/src/server/clerkMiddleware.ts index 5ba42112a7c..71b32db568c 100644 --- a/packages/tanstack-react-start/src/server/clerkMiddleware.ts +++ b/packages/tanstack-react-start/src/server/clerkMiddleware.ts @@ -6,9 +6,7 @@ import type { PendingSessionOptions } from '@clerk/shared/types'; import type { AnyRequestMiddleware } from '@tanstack/react-start'; import { createMiddleware } from '@tanstack/react-start'; -import { canUseKeyless } from '../utils/feature-flags'; import { clerkClient } from './clerkClient'; -import { resolveKeysWithKeylessFallback } from './keyless/utils'; import { loadOptions } from './loadOptions'; import type { ClerkMiddlewareOptions, ClerkMiddlewareOptionsCallback } from './types'; import { getResponseClerkState } from './utils'; @@ -29,21 +27,6 @@ export const clerkMiddleware = ( secretKey: resolvedOptions?.secretKey, }); - // Get keys - either from options, env, or keyless mode - const { - publishableKey, - secretKey, - claimUrl: keylessClaimUrl, - apiKeysUrl: keylessApiKeysUrl, - } = await resolveKeysWithKeylessFallback(loadedOptions.publishableKey, loadedOptions.secretKey); - - if (publishableKey) { - loadedOptions.publishableKey = publishableKey; - } - if (secretKey) { - loadedOptions.secretKey = secretKey; - } - const requestState = await clerkClient().authenticateRequest(clerkRequest, { ...loadedOptions, acceptsToken: 'any', @@ -67,15 +50,6 @@ export const clerkMiddleware = ( const clerkInitialState = getResponseClerkState(requestState as RequestState, loadedOptions); - // Include keyless mode URLs if applicable - if (canUseKeyless && keylessClaimUrl) { - (clerkInitialState as Record).__internal_clerk_state = { - ...((clerkInitialState as Record).__internal_clerk_state as Record), - __keylessClaimUrl: keylessClaimUrl, - __keylessApiKeysUrl: keylessApiKeysUrl, - }; - } - const result = await next({ context: { clerkInitialState, diff --git a/packages/tanstack-react-start/src/server/keyless/fileStorage.ts b/packages/tanstack-react-start/src/server/keyless/fileStorage.ts deleted file mode 100644 index 24929cc7ebd..00000000000 --- a/packages/tanstack-react-start/src/server/keyless/fileStorage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -import { createNodeFileStorage, type KeylessStorage } from '@clerk/shared/keyless'; - -export type { KeylessStorage }; - -export interface FileStorageOptions { - cwd?: () => string; -} - -export function createFileStorage(options: FileStorageOptions = {}): KeylessStorage { - const { cwd = () => process.cwd() } = options; - - return createNodeFileStorage(fs, path, { - cwd, - frameworkPackageName: '@clerk/tanstack-react-start', - }); -} diff --git a/packages/tanstack-react-start/src/server/keyless/index.ts b/packages/tanstack-react-start/src/server/keyless/index.ts deleted file mode 100644 index bb91fbedfa9..00000000000 --- a/packages/tanstack-react-start/src/server/keyless/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createKeylessService } from '@clerk/shared/keyless'; - -import { clerkClient } from '../clerkClient'; -import { createFileStorage } from './fileStorage'; - -// Lazily initialized keyless service singleton -let keylessServiceInstance: ReturnType | null = null; - -export function keyless() { - if (!keylessServiceInstance) { - keylessServiceInstance = createKeylessService({ - storage: createFileStorage(), - api: { - async createAccountlessApplication(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient().__experimental_accountlessApplications.createAccountlessApplication({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - async completeOnboarding(requestHeaders?: Headers, source?: string) { - try { - return await clerkClient().__experimental_accountlessApplications.completeAccountlessApplicationOnboarding({ - requestHeaders, - source, - }); - } catch { - return null; - } - }, - }, - framework: 'tanstack-react-start', - }); - } - return keylessServiceInstance; -} diff --git a/packages/tanstack-react-start/src/server/keyless/utils.ts b/packages/tanstack-react-start/src/server/keyless/utils.ts deleted file mode 100644 index ab4896cadb5..00000000000 --- a/packages/tanstack-react-start/src/server/keyless/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { resolveKeysWithKeylessFallback as sharedResolveKeysWithKeylessFallback } from '@clerk/shared/keyless'; -export type { KeylessResult } from '@clerk/shared/keyless'; - -import { canUseKeyless } from '../../utils/feature-flags'; -import { keyless } from './index'; - -/** - * Resolves Clerk keys, falling back to keyless mode in development if configured keys are missing. - * - * @param configuredPublishableKey - The publishable key from options or environment - * @param configuredSecretKey - The secret key from options or environment - * @returns The resolved keys (either configured or from keyless mode) - */ -export function resolveKeysWithKeylessFallback( - configuredPublishableKey: string | undefined, - configuredSecretKey: string | undefined, -) { - return sharedResolveKeysWithKeylessFallback(configuredPublishableKey, configuredSecretKey, keyless(), canUseKeyless); -} diff --git a/packages/tanstack-react-start/src/server/loadOptions.ts b/packages/tanstack-react-start/src/server/loadOptions.ts index 694494a8c6d..e6bb8eb0a43 100644 --- a/packages/tanstack-react-start/src/server/loadOptions.ts +++ b/packages/tanstack-react-start/src/server/loadOptions.ts @@ -5,7 +5,6 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy'; import { errorThrower } from '../utils'; -import { canUseKeyless } from '../utils/feature-flags'; import { commonEnvs } from './constants'; import type { LoaderOptions } from './types'; @@ -30,10 +29,11 @@ export const loadOptions = (request: ClerkRequest, overrides: LoaderOptions = {} proxyUrl = relativeOrAbsoluteProxyUrl; } - // In keyless mode, don't throw if secretKey is missing - ClerkProvider will handle it - if (!secretKey && !canUseKeyless) { + if (!secretKey) { // eslint-disable-next-line @typescript-eslint/only-throw-error - throw errorThrower.throw('Clerk: no secret key provided'); + throw errorThrower.throw( + 'Clerk: no secret key provided. To set up Clerk for this project, run `npx clerk@latest init` — it is non-interactive, requires no Clerk account or login, and writes the required keys to your .env file.', + ); } if (isSatellite && !proxyUrl && !domain) { diff --git a/packages/tanstack-react-start/src/utils/__tests__/feature-flags.test.ts b/packages/tanstack-react-start/src/utils/__tests__/feature-flags.test.ts deleted file mode 100644 index 693278f61f9..00000000000 --- a/packages/tanstack-react-start/src/utils/__tests__/feature-flags.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { automatedEnvironmentVariables } from '@clerk/shared/utils'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -async function loadCanUseKeyless() { - vi.resetModules(); - const { canUseKeyless } = await import('../feature-flags.js'); - return canUseKeyless; -} - -describe('canUseKeyless', () => { - beforeEach(() => { - vi.stubEnv('NODE_ENV', 'development'); - vi.stubEnv('VITE_CLERK_KEYLESS_DISABLED', undefined); - vi.stubEnv('CLERK_KEYLESS_DISABLED', undefined); - automatedEnvironmentVariables.forEach(name => { - vi.stubEnv(name, undefined); - vi.stubGlobal(name, undefined); - }); - }); - - afterEach(() => { - vi.unstubAllEnvs(); - vi.unstubAllGlobals(); - vi.resetModules(); - }); - - it('enables keyless in development when automation signals are absent', async () => { - await expect(loadCanUseKeyless()).resolves.toBe(true); - }); - - it('disables keyless in CI even when the app runs in development mode', async () => { - vi.stubEnv('CI', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless outside development mode', async () => { - vi.stubEnv('NODE_ENV', 'production'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); - - it('disables keyless when explicitly disabled', async () => { - vi.stubEnv('VITE_CLERK_KEYLESS_DISABLED', 'true'); - - await expect(loadCanUseKeyless()).resolves.toBe(false); - }); -}); diff --git a/packages/tanstack-react-start/src/utils/feature-flags.ts b/packages/tanstack-react-start/src/utils/feature-flags.ts deleted file mode 100644 index 4d129d8887c..00000000000 --- a/packages/tanstack-react-start/src/utils/feature-flags.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getEnvVariable } from '@clerk/shared/getEnvVariable'; -import { isTruthy } from '@clerk/shared/underscore'; -import { isAutomatedEnvironment, isDevelopmentEnvironment } from '@clerk/shared/utils'; - -// Support both Vite-style and generic env var names for disabling keyless mode -const KEYLESS_DISABLED = - isTruthy(getEnvVariable('VITE_CLERK_KEYLESS_DISABLED')) || - isTruthy(getEnvVariable('CLERK_KEYLESS_DISABLED')) || - false; - -/** - * Whether keyless mode can be used in the current environment. - * Keyless mode is only available in development, when not explicitly disabled, - * and when not running in an automated/CI environment. - * - * To disable keyless mode, set either: - * - `VITE_CLERK_KEYLESS_DISABLED=1` (for Vite-based projects) - * - `CLERK_KEYLESS_DISABLED=1` (generic) - */ -export const canUseKeyless = isDevelopmentEnvironment() && !isAutomatedEnvironment() && !KEYLESS_DISABLED;