diff --git a/packages/backend/src/tokens/__tests__/authenticateContext.test.ts b/packages/backend/src/tokens/__tests__/authenticateContext.test.ts index 064d5e960c7..b640a07ea79 100644 --- a/packages/backend/src/tokens/__tests__/authenticateContext.test.ts +++ b/packages/backend/src/tokens/__tests__/authenticateContext.test.ts @@ -258,71 +258,6 @@ describe('AuthenticateContext', () => { }); }); - describe('auto-proxy for eligible hosts', () => { - const originalEnv = process.env; - - beforeEach(() => { - process.env = { - ...originalEnv, - VERCEL_TARGET_ENV: 'production', - VERCEL_PROJECT_PRODUCTION_URL: 'myapp-abc123.vercel.app', - }; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - it('auto-derives proxyUrl when Vercel env vars indicate production vercel.app', async () => { - const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); - const context = await createAuthenticateContext(clerkRequest, { - publishableKey: pkLive, - }); - - expect(context.proxyUrl).toBe('https://myapp-abc123.vercel.app/__clerk'); - }); - - it('does NOT auto-derive proxyUrl for development keys', async () => { - const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); - const context = await createAuthenticateContext(clerkRequest, { - publishableKey: pkTest, - }); - - expect(context.proxyUrl).toBeUndefined(); - }); - - it('does NOT auto-derive proxyUrl when Vercel env vars are absent', async () => { - delete process.env.VERCEL_TARGET_ENV; - delete process.env.VERCEL_PROJECT_PRODUCTION_URL; - const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); - const context = await createAuthenticateContext(clerkRequest, { - publishableKey: pkLive, - }); - - expect(context.proxyUrl).toBeUndefined(); - }); - - it('explicit proxyUrl takes precedence over auto-detection', async () => { - const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); - const context = await createAuthenticateContext(clerkRequest, { - publishableKey: pkLive, - proxyUrl: 'https://custom-proxy.example.com/__clerk', - }); - - expect(context.proxyUrl).toBe('https://custom-proxy.example.com/__clerk'); - }); - - it('explicit domain skips auto-detection', async () => { - const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); - const context = await createAuthenticateContext(clerkRequest, { - publishableKey: pkLive, - domain: 'clerk.myapp.com', - }); - - expect(context.proxyUrl).toBeUndefined(); - }); - }); - // Added these tests to verify that the generated sha-1 is the same as the one used in cookie assignment // Tests copied from packages/shared/src/__tests__/keys.test.ts describe('getCookieSuffix(publishableKey, subtle)', () => { diff --git a/packages/backend/src/tokens/authenticateContext.ts b/packages/backend/src/tokens/authenticateContext.ts index 794c9268874..19fb89001c0 100644 --- a/packages/backend/src/tokens/authenticateContext.ts +++ b/packages/backend/src/tokens/authenticateContext.ts @@ -1,5 +1,4 @@ import { buildAccountsBaseUrl } from '@clerk/shared/buildAccountsBaseUrl'; -import { getAutoProxyUrlFromEnvironment } from '@clerk/shared/proxy'; import type { Jwt } from '@clerk/shared/types'; import { isCurrentDevAccountPortalOrigin, isLegacyDevAccountPortalOrigin } from '@clerk/shared/url'; @@ -71,18 +70,6 @@ class AuthenticateContext implements AuthenticateContext { private clerkRequest: ClerkRequest, options: AuthenticateRequestOptions, ) { - // Auto-detect proxy for supported platform deployments using environment - // variables (e.g. VERCEL_TARGET_ENV, VERCEL_PROJECT_PRODUCTION_URL) instead - // of request headers, which avoids X-Forwarded-Host spoofing concerns. - const autoProxyPath = getAutoProxyUrlFromEnvironment({ - publishableKey: options.publishableKey ?? '', - hasProxyUrl: !!options.proxyUrl, - hasDomain: !!options.domain, - }); - if (autoProxyPath) { - options = { ...options, proxyUrl: `${clerkRequest.clerkUrl.origin}${autoProxyPath}` }; - } - if (options.acceptsToken === TokenType.M2MToken || options.acceptsToken === TokenType.ApiKey) { // For non-session tokens, we only want to set the header values. this.initHeaderValues(); diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 979cf6e24fa..4a539c55147 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -2516,86 +2516,6 @@ describe('Clerk singleton', () => { }); }); }); - - describe('auto-detection for eligible hosts', () => { - const originalLocation = window.location; - - afterEach(() => { - Object.defineProperty(window, 'location', { - value: originalLocation, - writable: true, - }); - }); - - test('auto-derives proxyUrl for production instances on eligible hosts', () => { - Object.defineProperty(window, 'location', { - value: { - ...originalLocation, - hostname: 'myapp-abc123.vercel.app', - origin: 'https://myapp-abc123.vercel.app', - href: 'https://myapp-abc123.vercel.app/dashboard', - }, - writable: true, - }); - - const sut = new Clerk(productionPublishableKey); - expect(sut.proxyUrl).toBe('https://myapp-abc123.vercel.app/__clerk'); - }); - - test('does NOT auto-derive proxyUrl for development instances on eligible hosts', () => { - Object.defineProperty(window, 'location', { - value: { - ...originalLocation, - hostname: 'myapp-abc123.vercel.app', - origin: 'https://myapp-abc123.vercel.app', - href: 'https://myapp-abc123.vercel.app/dashboard', - }, - writable: true, - }); - - const sut = new Clerk(developmentPublishableKey); - expect(sut.proxyUrl).toBe(''); - }); - - test('does NOT auto-derive proxyUrl for ineligible domains', () => { - const sut = new Clerk(productionPublishableKey); - expect(sut.proxyUrl).toBe(''); - }); - - test('explicit proxyUrl takes precedence over auto-detection', () => { - Object.defineProperty(window, 'location', { - value: { - ...originalLocation, - hostname: 'myapp-abc123.vercel.app', - origin: 'https://myapp-abc123.vercel.app', - href: 'https://myapp-abc123.vercel.app/dashboard', - }, - writable: true, - }); - - const sut = new Clerk(productionPublishableKey, { - proxyUrl: 'https://custom-proxy.example.com/__clerk', - }); - expect(sut.proxyUrl).toBe('https://custom-proxy.example.com/__clerk'); - }); - - test('explicit domain skips auto-detection', () => { - Object.defineProperty(window, 'location', { - value: { - ...originalLocation, - hostname: 'myapp-abc123.vercel.app', - origin: 'https://myapp-abc123.vercel.app', - href: 'https://myapp-abc123.vercel.app/dashboard', - }, - writable: true, - }); - - const sut = new Clerk(productionPublishableKey, { - domain: 'clerk.myapp.com', - }); - expect(sut.proxyUrl).toBe(''); - }); - }); }); describe('buildUrlWithAuth', () => { diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 47cbfb2515f..fe4da6a8e91 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -38,13 +38,7 @@ import { windowNavigate } from '@clerk/shared/internal/clerk-js/windowNavigate'; import { parsePublishableKey } from '@clerk/shared/keys'; import { logger } from '@clerk/shared/logger'; import { CLERK_NETLIFY_CACHE_BUST_PARAM } from '@clerk/shared/netlifyCacheHandler'; -import { - AUTO_PROXY_PATH, - isHttpOrHttps, - isValidProxyUrl, - proxyUrlToAbsoluteURL, - shouldAutoProxy, -} from '@clerk/shared/proxy'; +import { isHttpOrHttps, isValidProxyUrl, proxyUrlToAbsoluteURL } from '@clerk/shared/proxy'; import { eventPrebuiltComponentMounted, eventPrebuiltComponentOpened, @@ -367,14 +361,7 @@ export class Clerk implements ClerkInterface { if (!isValidProxyUrl(_unfilteredProxy)) { errorThrower.throwInvalidProxyUrl({ url: _unfilteredProxy }); } - const resolved = proxyUrlToAbsoluteURL(_unfilteredProxy); - if (resolved) { - return resolved; - } - // Auto-detect when no explicit proxy or domain is configured (production only) - if (!this.#domain && this.#instanceType === 'production' && shouldAutoProxy(window.location.hostname)) { - return `${window.location.origin}${AUTO_PROXY_PATH}`; - } + return proxyUrlToAbsoluteURL(_unfilteredProxy); } if (typeof this.#proxyUrl === 'function') { diff --git a/packages/nextjs/src/app-router/server/__tests__/DynamicClerkScripts.test.tsx b/packages/nextjs/src/app-router/server/__tests__/DynamicClerkScripts.test.tsx index 2e4a4111e40..e9c787bc1f8 100644 --- a/packages/nextjs/src/app-router/server/__tests__/DynamicClerkScripts.test.tsx +++ b/packages/nextjs/src/app-router/server/__tests__/DynamicClerkScripts.test.tsx @@ -86,24 +86,4 @@ describe('DynamicClerkScripts', () => { expect(html).not.toContain('nonce="test'); expect(html).not.toContain('nonce="csp'); }); - - it('renders initial script tags with relative proxied asset URLs', async () => { - mockHeaders.mockResolvedValue( - new Map([ - ['X-Nonce', null], - ['Content-Security-Policy', ''], - ]), - ); - - const html = await render( - DynamicClerkScripts({ - ...defaultProps, - proxyUrl: '/__clerk', - }), - ); - - expect(html).toContain('src="/__clerk/npm/@clerk/clerk-js@'); - expect(html).toContain('href="/__clerk/npm/@clerk/ui@'); - expect(html).toContain('data-clerk-proxy-url="/__clerk"'); - }); }); diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts index 31757419d0f..683eca9b11f 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts @@ -1326,40 +1326,6 @@ describe('frontendApiProxy multi-domain support', () => { }); }); -describe('auto-proxy for eligible hosts', () => { - const productionPublishableKey = 'pk_live_Y2xlcmsuaW5jbHVkZWQua2F0eWRpZC05Mi5sY2wuZGV2JA'; - - it('auto-intercepts /__clerk/* requests on eligible hostnames', async () => { - const req = new NextRequest(new URL('/__clerk/v1/client', 'https://myapp-abc123.vercel.app').toString(), { - method: 'GET', - headers: new Headers(), - }); - - const resp = await clerkMiddleware({ publishableKey: productionPublishableKey })(req, {} as NextFetchEvent); - - // Proxy should intercept the request — authenticateRequest should NOT be called - expect((await clerkClient()).authenticateRequest).not.toBeCalled(); - expect(resp?.status).toBeDefined(); - }); - - it('uses request.nextUrl for auto-detection', async () => { - const req = new NextRequest('http://127.0.0.1:3000/__clerk/v1/client', { - method: 'GET', - headers: new Headers(), - }); - - Object.defineProperty(req, 'nextUrl', { - value: new URL('https://myapp-abc123.vercel.app/__clerk/v1/client'), - configurable: true, - }); - - const resp = await clerkMiddleware({ publishableKey: productionPublishableKey })(req, {} as NextFetchEvent); - - expect((await clerkClient()).authenticateRequest).not.toBeCalled(); - expect(resp?.status).toBeDefined(); - }); -}); - describe('contentSecurityPolicy option', () => { it('forwards CSP headers as request headers when strict mode is enabled', async () => { const resp = await clerkMiddleware({ diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts index f8c3cd32da9..20a69a8c24e 100644 --- a/packages/nextjs/src/server/clerkMiddleware.ts +++ b/packages/nextjs/src/server/clerkMiddleware.ts @@ -21,10 +21,9 @@ import { TokenType, } from '@clerk/backend/internal'; import { clerkFrontendApiProxy, DEFAULT_PROXY_PATH, matchProxyPath } from '@clerk/backend/proxy'; -import { isProductionFromPublishableKey, parsePublishableKey } from '@clerk/shared/keys'; +import { parsePublishableKey } from '@clerk/shared/keys'; import { handleNetlifyCacheInDevInstance } from '@clerk/shared/netlifyCacheHandler'; import { isMalformedURLError } from '@clerk/shared/pathMatcher'; -import { shouldAutoProxy } from '@clerk/shared/proxy'; import { notFound as nextjsNotFound } from 'next/navigation'; import type { NextMiddleware, NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; @@ -36,7 +35,7 @@ import type { Logger, LoggerNoCommit } from '../utils/debugLogger'; import { withLogger } from '../utils/debugLogger'; import { canUseKeyless } from '../utils/feature-flags'; import { clerkClient } from './clerkClient'; -import { DOMAIN, PROXY_URL, PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from './constants'; +import { PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from './constants'; import { type ContentSecurityPolicyOptions, createContentSecurityPolicyHeaders } from './content-security-policy'; import { errorThrower } from './errorThrower'; import { getHeader } from './headers-utils'; @@ -162,20 +161,12 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl ); // Handle Frontend API proxy requests early, before authentication - const requestUrl = new URL(request.nextUrl.href); - let frontendApiProxyConfig = resolvedParams.frontendApiProxy; - - // Auto-detect when no explicit proxy or domain is configured - const hasExplicitProxyOrDomain = resolvedParams.proxyUrl || PROXY_URL || resolvedParams.domain || DOMAIN; - if (!frontendApiProxyConfig && !hasExplicitProxyOrDomain && isProductionFromPublishableKey(publishableKey)) { - if (shouldAutoProxy(requestUrl.hostname)) { - frontendApiProxyConfig = { enabled: true }; - } - } + const frontendApiProxyConfig = resolvedParams.frontendApiProxy; if (frontendApiProxyConfig) { const { enabled, path: proxyPath = DEFAULT_PROXY_PATH } = frontendApiProxyConfig; // Resolve enabled - either boolean or function + const requestUrl = new URL(request.url); const isEnabled = typeof enabled === 'function' ? enabled(requestUrl) : enabled; if (isEnabled && matchProxyPath(request, { proxyPath })) { diff --git a/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts b/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts deleted file mode 100644 index c83bdb54d0a..00000000000 --- a/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest'; - -import { mergeNextClerkPropsWithEnv } from '../mergeNextClerkPropsWithEnv'; - -const ORIGINAL_ENV = { ...process.env }; - -describe('mergeNextClerkPropsWithEnv', () => { - afterEach(() => { - process.env = { ...ORIGINAL_ENV }; - }); - - it('auto-derives a relative proxyUrl for Vercel production static generation', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; - process.env.VERCEL_TARGET_ENV = 'production'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; - - const result = mergeNextClerkPropsWithEnv({}); - - expect(result.proxyUrl).toBe('/__clerk'); - }); - - it('does not auto-derive proxyUrl for non-production Clerk keys', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_test_Zm9vLmNsZXJrLmFjY291bnRzLmRldiQ='; - process.env.VERCEL_TARGET_ENV = 'production'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; - - const result = mergeNextClerkPropsWithEnv({}); - - expect(result.proxyUrl).toBe(''); - }); - - it('does not auto-derive proxyUrl outside Vercel production deployments', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; - process.env.VERCEL_TARGET_ENV = 'preview'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; - - const result = mergeNextClerkPropsWithEnv({}); - - expect(result.proxyUrl).toBe(''); - }); - - it('does not auto-derive proxyUrl when the Vercel production hostname is not eligible', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; - process.env.VERCEL_TARGET_ENV = 'production'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.com'; - - const result = mergeNextClerkPropsWithEnv({}); - - expect(result.proxyUrl).toBe(''); - }); - - it('does not override an explicit proxyUrl', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; - process.env.VERCEL_TARGET_ENV = 'production'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; - - const result = mergeNextClerkPropsWithEnv({ - proxyUrl: 'https://custom-proxy.example.com/__clerk', - }); - - expect(result.proxyUrl).toBe('https://custom-proxy.example.com/__clerk'); - }); - - it('does not derive proxyUrl when an explicit domain is configured', () => { - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; - process.env.VERCEL_TARGET_ENV = 'production'; - process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; - - const result = mergeNextClerkPropsWithEnv({ - domain: 'clerk.myapp.com', - }); - - expect(result.proxyUrl).toBe(''); - }); -}); diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index 491e6cf810d..afb09022061 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -1,5 +1,4 @@ import type { InternalClerkScriptProps } from '@clerk/react/internal'; -import { getAutoProxyUrlFromEnvironment } from '@clerk/shared/proxy'; import { isTruthy } from '@clerk/shared/underscore'; import { SDK_METADATA } from '../server/constants'; @@ -23,26 +22,16 @@ function getPrefetchUIFromEnvAndProps(propsPrefetchUI: NextClerkProviderProps['p export const mergeNextClerkPropsWithEnv = ( props: Omit & InternalClerkScriptProps, ): any => { - const publishableKey = props.publishableKey || process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || ''; - const proxyUrl = props.proxyUrl || process.env.NEXT_PUBLIC_CLERK_PROXY_URL || ''; - const domain = props.domain || process.env.NEXT_PUBLIC_CLERK_DOMAIN || ''; - return { ...props, - publishableKey, + publishableKey: props.publishableKey || process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '', __internal_clerkJSUrl: props.__internal_clerkJSUrl || process.env.NEXT_PUBLIC_CLERK_JS_URL, __internal_clerkJSVersion: props.__internal_clerkJSVersion || process.env.NEXT_PUBLIC_CLERK_JS_VERSION, __internal_clerkUIUrl: props.__internal_clerkUIUrl || process.env.NEXT_PUBLIC_CLERK_UI_URL, __internal_clerkUIVersion: props.__internal_clerkUIVersion || process.env.NEXT_PUBLIC_CLERK_UI_VERSION, prefetchUI: getPrefetchUIFromEnvAndProps(props.prefetchUI), - proxyUrl: - proxyUrl || - getAutoProxyUrlFromEnvironment({ - hasDomain: !!domain, - hasProxyUrl: !!proxyUrl, - publishableKey, - }), - domain, + proxyUrl: props.proxyUrl || process.env.NEXT_PUBLIC_CLERK_PROXY_URL || '', + domain: props.domain || process.env.NEXT_PUBLIC_CLERK_DOMAIN || '', isSatellite: props.isSatellite || isTruthy(process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE), signInUrl: props.signInUrl || process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL || '', signUpUrl: props.signUpUrl || process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL || '', diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 81191d47072..69eeedf576e 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -169,11 +169,6 @@ describe('clerkJsScriptUrl()', () => { const result = clerkJsScriptUrl({ publishableKey: mockDevPublishableKey, __internal_clerkJSVersion: '6' }); expect(result).toContain('/npm/@clerk/clerk-js@6/'); }); - - test('constructs a relative proxied URL when proxyUrl is relative', () => { - const result = clerkJsScriptUrl({ publishableKey: mockDevPublishableKey, proxyUrl: '/__clerk' }); - expect(result).toBe(`/__clerk/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); - }); }); describe('buildScriptHost()', () => { @@ -233,26 +228,6 @@ describe('buildScriptHost()', () => { writable: true, }); }); - - test('falls back to frontendApi for relative proxyUrl when window is unavailable', () => { - const currentWindow = global.window; - - try { - Object.defineProperty(global, 'window', { - value: undefined, - configurable: true, - }); - - const result = buildScriptHost({ publishableKey: mockDevPublishableKey, proxyUrl: '/__clerk' }); - expect(result).toBe('foo-bar-13.clerk.accounts.dev'); - } finally { - Object.defineProperty(global, 'window', { - value: currentWindow, - writable: true, - configurable: true, - }); - } - }); }); describe('buildClerkJsScriptAttributes()', () => { @@ -450,11 +425,6 @@ describe('clerkUIScriptUrl()', () => { expect(uiResult).not.toContain('@clerk/clerk-js'); expect(jsResult).not.toContain('@clerk/ui'); }); - - test('constructs a relative proxied URL when proxyUrl is relative', () => { - const result = clerkUIScriptUrl({ publishableKey: mockDevPublishableKey, proxyUrl: '/__clerk' }); - expect(result).toBe(`/__clerk/npm/@clerk/ui@${uiPackageMajorVersion}/dist/ui.browser.js`); - }); }); describe('buildClerkUIScriptAttributes()', () => { diff --git a/packages/shared/src/__tests__/proxy.spec.ts b/packages/shared/src/__tests__/proxy.spec.ts index b09143e2f4a..4a898391ee6 100644 --- a/packages/shared/src/__tests__/proxy.spec.ts +++ b/packages/shared/src/__tests__/proxy.spec.ts @@ -1,13 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { - getAutoProxyUrlFromEnvironment, - isHttpOrHttps, - isProxyUrlRelative, - isValidProxyUrl, - proxyUrlToAbsoluteURL, - shouldAutoProxy, -} from '../proxy'; +import { isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from '../proxy'; describe('isValidProxyUrl(key)', () => { it('returns true if the proxyUrl is valid', () => { @@ -45,100 +38,6 @@ describe('isHttpOrHttps(key)', () => { }); }); -describe('shouldAutoProxy(hostname)', () => { - it('returns true for a .vercel.app subdomain', () => { - expect(shouldAutoProxy('myapp.vercel.app')).toBe(true); - }); - - it('returns true for a git branch preview subdomain', () => { - expect(shouldAutoProxy('myapp-git-branch.vercel.app')).toBe(true); - }); - - it('returns false for the bare vercel.app domain', () => { - expect(shouldAutoProxy('vercel.app')).toBe(false); - }); - - it('returns false for a custom domain', () => { - expect(shouldAutoProxy('myapp.com')).toBe(false); - }); - - it('returns false for a domain that contains vercel.app but is not a subdomain', () => { - expect(shouldAutoProxy('vercel.app.evil.com')).toBe(false); - }); -}); - -describe('getAutoProxyUrlFromEnvironment(options)', () => { - it('returns a relative proxy path for Vercel production deployments with production keys', () => { - expect( - getAutoProxyUrlFromEnvironment({ - publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', - VERCEL_TARGET_ENV: 'production', - }, - }), - ).toBe('/__clerk'); - }); - - it('returns empty string for non-production Clerk keys', () => { - expect( - getAutoProxyUrlFromEnvironment({ - publishableKey: 'pk_test_Zm9vLmNsZXJrLmFjY291bnRzLmRldiQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', - VERCEL_TARGET_ENV: 'production', - }, - }), - ).toBe(''); - }); - - it('returns empty string when an explicit domain or proxyUrl is configured', () => { - expect( - getAutoProxyUrlFromEnvironment({ - hasDomain: true, - publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', - VERCEL_TARGET_ENV: 'production', - }, - }), - ).toBe(''); - - expect( - getAutoProxyUrlFromEnvironment({ - hasProxyUrl: true, - publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', - VERCEL_TARGET_ENV: 'production', - }, - }), - ).toBe(''); - }); - - it('returns empty string for ineligible or non-production Vercel environments', () => { - expect( - getAutoProxyUrlFromEnvironment({ - publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.com', - VERCEL_TARGET_ENV: 'production', - }, - }), - ).toBe(''); - - expect( - getAutoProxyUrlFromEnvironment({ - publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', - environment: { - VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', - VERCEL_TARGET_ENV: 'preview', - }, - }), - ).toBe(''); - }); -}); - describe('proxyUrlToAbsoluteURL(url)', () => { const currentLocation = global.window.location; @@ -167,24 +66,6 @@ describe('proxyUrlToAbsoluteURL(url)', () => { it('returns the same value as the parameter given as it already an absolute URL', () => { expect(proxyUrlToAbsoluteURL('https://clerk.com/api/__clerk')).toBe('https://clerk.com/api/__clerk'); }); - - it('returns the relative URL unchanged when window is unavailable', () => { - const currentWindow = global.window; - - Object.defineProperty(global, 'window', { - value: undefined, - configurable: true, - }); - - expect(proxyUrlToAbsoluteURL('/api/__clerk')).toBe('/api/__clerk'); - - Object.defineProperty(global, 'window', { - value: currentWindow, - writable: true, - configurable: true, - }); - }); - it('returns empty string if parameter is undefined', () => { expect(proxyUrlToAbsoluteURL(undefined)).toBe(''); }); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 917c34268e1..96171f5c648 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -1,7 +1,7 @@ import { buildErrorThrower, ClerkRuntimeError } from './error'; import { createDevOrStagingUrlCache, parsePublishableKey } from './keys'; import { loadScript } from './loadScript'; -import { isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy'; +import { isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy'; import type { SDKMetadata } from './types'; import { addClerkPrefix } from './url'; import { versionSelector } from './versionSelector'; @@ -230,13 +230,8 @@ export const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => { return __internal_clerkJSUrl; } - const version = versionSelector(__internal_clerkJSVersion); - - if (proxyUrl && isProxyUrlRelative(proxyUrl)) { - return buildRelativeProxyScriptUrl(proxyUrl, 'clerk-js', version, 'clerk.browser.js'); - } - const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); + const version = versionSelector(__internal_clerkJSVersion); return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`; }; @@ -247,13 +242,8 @@ export const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => { return __internal_clerkUIUrl; } - const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION); - - if (proxyUrl && isProxyUrlRelative(proxyUrl)) { - return buildRelativeProxyScriptUrl(proxyUrl, 'ui', version, 'ui.browser.js'); - } - const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); + const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION); return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`; }; @@ -290,29 +280,11 @@ const applyAttributesToScript = (attributes: Record) => (script: } }; -const stripTrailingSlashes = (value: string) => { - while (value.endsWith('/')) { - value = value.slice(0, -1); - } - - return value; -}; - -const buildRelativeProxyScriptUrl = (proxyUrl: string, packageName: string, version: string, fileName: string) => { - return `${stripTrailingSlashes(proxyUrl)}/npm/@clerk/${packageName}@${version}/dist/${fileName}`; -}; - export const buildScriptHost = (opts: { publishableKey: string; proxyUrl?: string; domain?: string }) => { const { proxyUrl, domain, publishableKey } = opts; if (!!proxyUrl && isValidProxyUrl(proxyUrl)) { - const resolvedProxyUrl = proxyUrlToAbsoluteURL(proxyUrl); - - if (isProxyUrlRelative(resolvedProxyUrl)) { - return parsePublishableKey(publishableKey)?.frontendApi || ''; - } - - return resolvedProxyUrl.replace(/http(s)?:\/\//, ''); + return proxyUrlToAbsoluteURL(proxyUrl).replace(/http(s)?:\/\//, ''); } else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) { return addClerkPrefix(domain); } else { diff --git a/packages/shared/src/proxy.ts b/packages/shared/src/proxy.ts index 6413accf405..f7633ed1773 100644 --- a/packages/shared/src/proxy.ts +++ b/packages/shared/src/proxy.ts @@ -1,5 +1,3 @@ -import { isProductionFromPublishableKey } from './keys'; - /** * */ @@ -32,71 +30,7 @@ export function proxyUrlToAbsoluteURL(url: string | undefined): string { if (!url) { return ''; } - - if (!isProxyUrlRelative(url)) { - return url; - } - - if (typeof window === 'undefined' || !window.location?.origin) { - return url; - } - - return new URL(url, window.location.origin).toString(); -} - -const AUTO_PROXY_HOST_SUFFIXES = ['.vercel.app']; -export const AUTO_PROXY_PATH = '/__clerk'; - -export function shouldAutoProxy(hostname: string): boolean { - return AUTO_PROXY_HOST_SUFFIXES.some(hostSuffix => hostname?.endsWith(hostSuffix)) ?? false; -} - -function normalizeHostname(hostnameOrUrl: string): string { - if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) { - try { - return new URL(hostnameOrUrl).hostname; - } catch { - return ''; - } - } - - return hostnameOrUrl.split('/')[0] || ''; -} - -type GetAutoProxyUrlFromEnvironmentOptions = { - publishableKey: string; - hasDomain?: boolean; - hasProxyUrl?: boolean; - environment?: NodeJS.ProcessEnv; -}; - -/** - * Determines if the current Vercel environment should use auto-proxy. - * Note: This runs both at build time (static generation) and at runtime - * (server-side rendering) via mergeNextClerkPropsWithEnv in providers. - * The return value may become the proxyUrl or the script src prefix. - */ -export function getAutoProxyUrlFromEnvironment({ - publishableKey, - hasDomain = false, - hasProxyUrl = false, - environment = process.env, -}: GetAutoProxyUrlFromEnvironmentOptions): string { - if (hasProxyUrl || hasDomain || !isProductionFromPublishableKey(publishableKey)) { - return ''; - } - - if (environment.VERCEL_TARGET_ENV !== 'production') { - return ''; - } - - const vercelProductionHostname = environment.VERCEL_PROJECT_PRODUCTION_URL; - - if (!vercelProductionHostname || !shouldAutoProxy(normalizeHostname(vercelProductionHostname))) { - return ''; - } - - return AUTO_PROXY_PATH; + return isProxyUrlRelative(url) ? new URL(url, window.location.origin).toString() : url; } /** diff --git a/turbo.json b/turbo.json index 0c18ed94be5..6c977c64dc3 100644 --- a/turbo.json +++ b/turbo.json @@ -27,8 +27,6 @@ "RSDOCTOR", "TZ", "VERCEL", - "VERCEL_PROJECT_PRODUCTION_URL", - "VERCEL_TARGET_ENV", "VITE_CLERK_*" ], "globalPassThroughEnv": [