diff --git a/.changeset/rude-jobs-yawn.md b/.changeset/rude-jobs-yawn.md new file mode 100644 index 0000000000..10de869026 --- /dev/null +++ b/.changeset/rude-jobs-yawn.md @@ -0,0 +1,9 @@ +--- +'@clerk/nextjs': patch +'@clerk/remix': patch +'gatsby-plugin-clerk': patch +'@clerk/shared': patch +'@clerk/fastify': patch +--- + +Improve the default value for `CLERK_API_URL` by utilizing the publishable key to differentiate between local, staging and prod environments. diff --git a/packages/clerk-js/src/utils/url.ts b/packages/clerk-js/src/utils/url.ts index fd3a991c5d..61b787d53c 100644 --- a/packages/clerk-js/src/utils/url.ts +++ b/packages/clerk-js/src/utils/url.ts @@ -1,5 +1,7 @@ -import { camelToSnake, createDevOrStagingUrlCache } from '@clerk/shared'; import { globs } from '@clerk/shared/globs'; +import { createDevOrStagingUrlCache } from '@clerk/shared/keys'; +import { camelToSnake } from '@clerk/shared/underscore'; +import { isCurrentDevAccountPortalOrigin, isLegacyDevAccountPortalOrigin } from '@clerk/shared/url'; import type { SignUpResource } from '@clerk/types'; import { joinPaths } from './path'; @@ -16,21 +18,6 @@ declare global { // This is used as a dummy base when we need to invoke "new URL()" but we don't care about the URL origin. const DUMMY_URL_BASE = 'http://clerk-dummy'; -export const DEV_OR_STAGING_SUFFIXES = [ - '.lcl.dev', - '.stg.dev', - '.lclstage.dev', - '.stgstage.dev', - '.dev.lclclerk.com', - '.stg.lclclerk.com', - '.accounts.lclclerk.com', - 'accountsstage.dev', - 'accounts.dev', -]; - -export const LEGACY_DEV_SUFFIXES = ['.lcl.dev', '.lclstage.dev', '.lclclerk.com']; -export const CURRENT_DEV_SUFFIXES = ['.accounts.dev', '.accountsstage.dev', '.accounts.lclclerk.com']; - const BANNED_URI_PROTOCOLS = ['javascript:'] as const; const { isDevOrStagingUrl } = createDevOrStagingUrlCache(); @@ -52,28 +39,6 @@ export function isDevAccountPortalOrigin(hostname: string = window.location.host return res; } -// Returns true for hosts such as: -// * accounts.foo.bar-13.lcl.dev -// * accounts.foo.bar-13.lclstage.dev -// * accounts.foo.bar-13.dev.lclclerk.com -function isLegacyDevAccountPortalOrigin(host: string): boolean { - return LEGACY_DEV_SUFFIXES.some(legacyDevSuffix => { - return host.startsWith('accounts.') && host.endsWith(legacyDevSuffix); - }); -} - -// Returns true for hosts such as: -// * foo-bar-13.accounts.dev -// * foo-bar-13.accountsstage.dev -// * foo-bar-13.accounts.lclclerk.com -// But false for: -// * foo-bar-13.clerk.accounts.lclclerk.com -function isCurrentDevAccountPortalOrigin(host: string): boolean { - return CURRENT_DEV_SUFFIXES.some(currentDevSuffix => { - return host.endsWith(currentDevSuffix) && !host.endsWith('.clerk' + currentDevSuffix); - }); -} - export function getETLDPlusOneFromFrontendApi(frontendApi: string): string { return frontendApi.replace('clerk.', ''); } diff --git a/packages/fastify/src/constants.ts b/packages/fastify/src/constants.ts index 1fab328e6e..6699d20ee0 100644 --- a/packages/fastify/src/constants.ts +++ b/packages/fastify/src/constants.ts @@ -1,9 +1,10 @@ import { constants } from '@clerk/backend'; +import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; -export const API_URL = process.env.CLERK_API_URL || 'https://api.clerk.com'; export const API_VERSION = process.env.CLERK_API_VERSION || 'v1'; export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; export const PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY || ''; +export const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); export const JWT_KEY = process.env.CLERK_JWT_KEY || ''; export const { Cookies, Headers } = constants; diff --git a/packages/gatsby-plugin-clerk/src/constants.ts b/packages/gatsby-plugin-clerk/src/constants.ts index c736e8e0fc..00d3f788c5 100644 --- a/packages/gatsby-plugin-clerk/src/constants.ts +++ b/packages/gatsby-plugin-clerk/src/constants.ts @@ -1,7 +1,9 @@ -export const API_URL = process.env.CLERK_API_URL || 'https://api.clerk.com'; +import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; + +export const PUBLISHABLE_KEY = process.env.GATSBY_CLERK_PUBLISHABLE_KEY || ''; +export const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); export const API_VERSION = process.env.CLERK_API_VERSION || 'v1'; export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; -export const PUBLISHABLE_KEY = process.env.GATSBY_CLERK_PUBLISHABLE_KEY || ''; export const CLERK_JS = process.env.GATSBY_CLERK_JS; export const PROXY_URL = process.env.GATSBY_CLERK_PROXY_URL; diff --git a/packages/nextjs/src/server/constants.ts b/packages/nextjs/src/server/constants.ts index 774d7c8de5..00b7dcbe77 100644 --- a/packages/nextjs/src/server/constants.ts +++ b/packages/nextjs/src/server/constants.ts @@ -1,11 +1,12 @@ +import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; import { isTruthy } from '@clerk/shared/underscore'; export const CLERK_JS_VERSION = process.env.NEXT_PUBLIC_CLERK_JS_VERSION || ''; export const CLERK_JS_URL = process.env.NEXT_PUBLIC_CLERK_JS || ''; -export const API_URL = process.env.CLERK_API_URL || 'https://api.clerk.com'; export const API_VERSION = process.env.CLERK_API_VERSION || 'v1'; export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; export const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || ''; +export const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); export const DOMAIN = process.env.NEXT_PUBLIC_CLERK_DOMAIN || ''; export const PROXY_URL = process.env.NEXT_PUBLIC_CLERK_PROXY_URL || ''; export const IS_SATELLITE = isTruthy(process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE) || false; diff --git a/packages/nextjs/src/server/url.ts b/packages/nextjs/src/server/url.ts index a659c3fbcd..f8dd3e2dff 100644 --- a/packages/nextjs/src/server/url.ts +++ b/packages/nextjs/src/server/url.ts @@ -1,8 +1,4 @@ -// TODO: This is a partial duplicate of part of packages/clerk-js/src/utils/url.ts -// TODO: To be removed when we can extract this utility to @clerk/shared - -export const LEGACY_DEV_SUFFIXES = ['.lcl.dev', '.lclstage.dev', '.lclclerk.com']; -export const CURRENT_DEV_SUFFIXES = ['.accounts.dev', '.accountsstage.dev', '.accounts.lclclerk.com']; +import { isCurrentDevAccountPortalOrigin, isLegacyDevAccountPortalOrigin } from '@clerk/shared/url'; const accountPortalCache = new Map(); @@ -20,25 +16,3 @@ export function isDevAccountPortalOrigin(hostname: string): boolean { return res; } - -// Returns true for hosts such as: -// * accounts.foo.bar-13.lcl.dev -// * accounts.foo.bar-13.lclstage.dev -// * accounts.foo.bar-13.dev.lclclerk.com -function isLegacyDevAccountPortalOrigin(host: string): boolean { - return LEGACY_DEV_SUFFIXES.some(legacyDevSuffix => { - return host.startsWith('accounts.') && host.endsWith(legacyDevSuffix); - }); -} - -// Returns true for hosts such as: -// * foo-bar-13.accounts.dev -// * foo-bar-13.accountsstage.dev -// * foo-bar-13.accounts.lclclerk.com -// But false for: -// * foo-bar-13.clerk.accounts.lclclerk.com -function isCurrentDevAccountPortalOrigin(host: string): boolean { - return CURRENT_DEV_SUFFIXES.some(currentDevSuffix => { - return host.endsWith(currentDevSuffix) && !host.endsWith('.clerk' + currentDevSuffix); - }); -} diff --git a/packages/remix/src/ssr/authenticateRequest.ts b/packages/remix/src/ssr/authenticateRequest.ts index 4e3fca229c..7ba1180b03 100644 --- a/packages/remix/src/ssr/authenticateRequest.ts +++ b/packages/remix/src/ssr/authenticateRequest.ts @@ -1,5 +1,6 @@ import type { RequestState } from '@clerk/backend'; import { buildRequestUrl, Clerk } from '@clerk/backend'; +import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy'; @@ -36,7 +37,7 @@ export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoad const jwtKey = opts.jwtKey || getEnvVariable('CLERK_JWT_KEY', context); - const apiUrl = getEnvVariable('CLERK_API_URL', context); + const apiUrl = getEnvVariable('CLERK_API_URL', context) || apiUrlFromPublishableKey(publishableKey); const domain = handleValueOrFn(opts.domain, new URL(request.url)) || getEnvVariable('CLERK_DOMAIN', context) || ''; diff --git a/packages/shared/.gitignore b/packages/shared/.gitignore index 45af1b0ba8..e729b74667 100644 --- a/packages/shared/.gitignore +++ b/packages/shared/.gitignore @@ -16,4 +16,6 @@ poller proxy underscore url -react \ No newline at end of file +react +constants +apiUrlFromPublishableKey \ No newline at end of file diff --git a/packages/shared/package.json b/packages/shared/package.json index 20015baaaf..f016e2fd62 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -63,7 +63,9 @@ "proxy", "underscore", "url", - "react" + "react", + "constants", + "apiUrlFromPublishableKey" ], "scripts": { "build": "tsup", diff --git a/packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts b/packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts new file mode 100644 index 0000000000..0783340956 --- /dev/null +++ b/packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts @@ -0,0 +1,18 @@ +import { apiUrlFromPublishableKey } from '../apiUrlFromPublishableKey'; + +describe('apiUrlFromPublishableKey', () => { + test('returns the prod api url when given a prod publishable key', async () => { + const apiUrl = apiUrlFromPublishableKey('pk_test_bWFueS1zZWFsLTkwLmNsZXJrLmFjY291bnRzLmRldiQ'); + expect(apiUrl).toBe('https://api.clerk.com'); + }); + + test('returns the prod api url when given a staging publishable key', async () => { + const apiUrl = apiUrlFromPublishableKey('pk_test_aW1tdW5lLWhhd2stNjUuY2xlcmsuYWNjb3VudHNzdGFnZS5kZXYk'); + expect(apiUrl).toBe('https://api.clerkstage.dev'); + }); + + test('returns the prod api url when given a local publishable key', async () => { + const apiUrl = apiUrlFromPublishableKey('pk_test_cGF0aWVudC1nb29zZS01LmNsZXJrLmFjY291bnRzLmxjbGNsZXJrLmNvbSQ'); + expect(apiUrl).toBe('https://api.lclclerk.com'); + }); +}); diff --git a/packages/shared/src/apiUrlFromPublishableKey.ts b/packages/shared/src/apiUrlFromPublishableKey.ts new file mode 100644 index 0000000000..432e952d2b --- /dev/null +++ b/packages/shared/src/apiUrlFromPublishableKey.ts @@ -0,0 +1,13 @@ +import { LOCAL_API_URL, LOCAL_ENV_SUFFIXES, PROD_API_URL, STAGING_API_URL, STAGING_ENV_SUFFIXES } from './constants'; +import { parsePublishableKey } from './keys'; + +export const apiUrlFromPublishableKey = (publishableKey: string) => { + const frontendApi = parsePublishableKey(publishableKey)?.frontendApi; + if (LOCAL_ENV_SUFFIXES.some(suffix => frontendApi?.endsWith(suffix))) { + return LOCAL_API_URL; + } + if (STAGING_ENV_SUFFIXES.some(suffix => frontendApi?.endsWith(suffix))) { + return STAGING_API_URL; + } + return PROD_API_URL; +}; diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts new file mode 100644 index 0000000000..bf3fc97840 --- /dev/null +++ b/packages/shared/src/constants.ts @@ -0,0 +1,18 @@ +export const LEGACY_DEV_INSTANCE_SUFFIXES = ['.lcl.dev', '.lclstage.dev', '.lclclerk.com']; +export const CURRENT_DEV_INSTANCE_SUFFIXES = ['.accounts.dev', '.accountsstage.dev', '.accounts.lclclerk.com']; +export const DEV_OR_STAGING_SUFFIXES = [ + '.lcl.dev', + '.stg.dev', + '.lclstage.dev', + '.stgstage.dev', + '.dev.lclclerk.com', + '.stg.lclclerk.com', + '.accounts.lclclerk.com', + 'accountsstage.dev', + 'accounts.dev', +]; +export const LOCAL_ENV_SUFFIXES = ['.lcl.dev', 'lclstage.dev', '.lclclerk.com', '.accounts.lclclerk.com']; +export const STAGING_ENV_SUFFIXES = ['.accountsstage.dev']; +export const LOCAL_API_URL = 'https://api.lclclerk.com'; +export const STAGING_API_URL = 'https://api.clerkstage.dev'; +export const PROD_API_URL = 'https://api.clerk.com'; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b5d8d07b76..e78f16b5cc 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -10,10 +10,11 @@ export * from './utils'; -export { createWorkerTimers } from './workerTimers'; +export { apiUrlFromPublishableKey } from './apiUrlFromPublishableKey'; export * from './browser'; export { callWithRetry } from './callWithRetry'; export * from './color'; +export * from './constants'; export * from './date'; export * from './deprecated'; export * from './error'; @@ -27,3 +28,4 @@ export * from './poller'; export * from './proxy'; export * from './underscore'; export * from './url'; +export { createWorkerTimers } from './workerTimers'; diff --git a/packages/shared/src/keys.ts b/packages/shared/src/keys.ts index d2f4848f12..83f889075b 100644 --- a/packages/shared/src/keys.ts +++ b/packages/shared/src/keys.ts @@ -1,5 +1,6 @@ import type { PublishableKey } from '@clerk/types'; +import { DEV_OR_STAGING_SUFFIXES } from './constants'; import { isomorphicAtob } from './isomorphicAtob'; const PUBLISHABLE_KEY_LIVE_PREFIX = 'pk_live_'; @@ -55,19 +56,6 @@ export function isLegacyFrontendApiKey(key: string) { } export function createDevOrStagingUrlCache() { - // TODO: Check if we can merge it with `./instance.ts#isStaging()` - const DEV_OR_STAGING_SUFFIXES = [ - '.lcl.dev', - '.stg.dev', - '.lclstage.dev', - '.stgstage.dev', - '.dev.lclclerk.com', - '.stg.lclclerk.com', - '.accounts.lclclerk.com', - 'accountsstage.dev', - 'accounts.dev', - ]; - const devOrStagingUrlCache = new Map(); return { diff --git a/packages/shared/src/url.ts b/packages/shared/src/url.ts index dfadc27ed7..3ce2b373a3 100644 --- a/packages/shared/src/url.ts +++ b/packages/shared/src/url.ts @@ -1,3 +1,4 @@ +import { CURRENT_DEV_INSTANCE_SUFFIXES, LEGACY_DEV_INSTANCE_SUFFIXES } from './constants'; import { isStaging } from './utils/instance'; export function parseSearchParams(queryString = ''): URLSearchParams { @@ -64,3 +65,25 @@ export const getScriptUrl = ( const major = getClerkJsMajorVersionOrTag(frontendApi, pkgVersion); return `https://${noSchemeFrontendApi}/npm/@clerk/clerk-js@${clerkJSVersion || major}/dist/clerk.browser.js`; }; + +// Returns true for hosts such as: +// * accounts.foo.bar-13.lcl.dev +// * accounts.foo.bar-13.lclstage.dev +// * accounts.foo.bar-13.dev.lclclerk.com +export function isLegacyDevAccountPortalOrigin(host: string): boolean { + return LEGACY_DEV_INSTANCE_SUFFIXES.some(legacyDevSuffix => { + return host.startsWith('accounts.') && host.endsWith(legacyDevSuffix); + }); +} + +// Returns true for hosts such as: +// * foo-bar-13.accounts.dev +// * foo-bar-13.accountsstage.dev +// * foo-bar-13.accounts.lclclerk.com +// But false for: +// * foo-bar-13.clerk.accounts.lclclerk.com +export function isCurrentDevAccountPortalOrigin(host: string): boolean { + return CURRENT_DEV_INSTANCE_SUFFIXES.some(currentDevSuffix => { + return host.endsWith(currentDevSuffix) && !host.endsWith('.clerk' + currentDevSuffix); + }); +} diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 8838c7ed4b..6e35eadacd 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -1,6 +1,6 @@ export * from './createDeferredPromise'; export { isStaging } from './instance'; +export { logErrorInDevMode } from './logErrorInDevMode'; export { noop } from './noop'; -export * from './runtimeEnvironment'; export * from './runWithExponentialBackOff'; -export { logErrorInDevMode } from './logErrorInDevMode'; +export * from './runtimeEnvironment'; diff --git a/packages/shared/subpaths.mjs b/packages/shared/subpaths.mjs index 6a1c22d8ec..56240619cb 100644 --- a/packages/shared/subpaths.mjs +++ b/packages/shared/subpaths.mjs @@ -20,6 +20,8 @@ export const subpathNames = [ 'proxy', 'underscore', 'url', + 'constants', + 'apiUrlFromPublishableKey', ]; export const subpathFoldersBarrel = ['react'];