UNPKG

@clerk/shared

Version:

Internal package utils used by the Clerk SDKs

229 lines (227 loc) • 10.1 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_constants = require('./constants.js'); const require_isomorphicAtob = require('./isomorphicAtob.js'); const require_isomorphicBtoa = require('./isomorphicBtoa.js'); //#region src/keys.ts /** Prefix used for production publishable keys */ const PUBLISHABLE_KEY_LIVE_PREFIX = "pk_live_"; /** Prefix used for development publishable keys */ const PUBLISHABLE_KEY_TEST_PREFIX = "pk_test_"; /** * Regular expression that matches development frontend API keys. * Matches patterns like: foo-bar-13.clerk.accounts.dev. */ const PUBLISHABLE_FRONTEND_API_DEV_REGEX = /^(([a-z]+)-){2}([0-9]{1,2})\.clerk\.accounts([a-z.]*)(dev|com)$/i; /** * Converts a frontend API URL into an unpadded base64-encoded publishable key. * * @param frontendApi - The frontend API URL (e.g., 'clerk.example.com'). * @returns An unpadded base64-encoded publishable key with appropriate prefix (pk_live_ or pk_test_). */ function buildPublishableKey(frontendApi) { return `${PUBLISHABLE_FRONTEND_API_DEV_REGEX.test(frontendApi) || frontendApi.startsWith("clerk.") && require_constants.LEGACY_DEV_INSTANCE_SUFFIXES.some((s) => frontendApi.endsWith(s)) ? PUBLISHABLE_KEY_TEST_PREFIX : PUBLISHABLE_KEY_LIVE_PREFIX}${require_isomorphicBtoa.isomorphicBtoa(`${frontendApi}$`).replace(/=+$/, "")}`; } /** * Derives a publishable key from the current hostname. Intended for multi-domain * setups (e.g. custom domains on top of a default domain) where the correct key * must be resolved per request. * * Pass the configured publishable key as `fallbackKey` so that development * instances (pk_test_) are returned as-is instead of being incorrectly derived * from the host (e.g. localhost). * * @example * // React (use window.location.hostname, not window.location.host, to avoid including the port) * <ClerkProvider publishableKey={publishableKeyFromHost(window.location.hostname, import.meta.env.VITE_CLERK_PUBLISHABLE_KEY)}> * * @example * // Express (inside clerkMiddleware callback) * // Validate req.hostname against a known allowlist before passing it in. * // When `trust proxy` is enabled, req.hostname reads from X-Forwarded-Host * // and can be spoofed if your proxy is not properly configured. * const ALLOWED_HOSTS = ['domain-a.com', 'domain-b.com']; * clerkMiddleware((req) => { * if (!ALLOWED_HOSTS.includes(req.hostname)) throw new Error('Unknown host'); * return { publishableKey: publishableKeyFromHost(req.hostname, process.env.CLERK_PUBLISHABLE_KEY) }; * }) */ function publishableKeyFromHost(host, fallbackKey) { if (fallbackKey && isDevelopmentFromPublishableKey(fallbackKey)) return fallbackKey; const hostname = host.toLowerCase().replace(/:\d+$/, ""); if (!hostname) throw new Error("Host must not be empty."); return buildPublishableKey(`clerk.${hostname}`); } /** * Validates that a decoded publishable key has the correct format. * The decoded value should be a frontend API followed by exactly one '$' at the end. * * @param decoded - The decoded publishable key string to validate. * @returns `true` if the decoded key has valid format, `false` otherwise. */ function isValidDecodedPublishableKey(decoded) { if (!decoded.endsWith("$")) return false; const withoutTrailing = decoded.slice(0, -1); if (withoutTrailing.includes("$")) return false; return withoutTrailing.includes("."); } /** * Parses and validates a publishable key, extracting the frontend API and instance type. * * @param key - The publishable key to parse. * @param options - Configuration options for parsing. * @param options.fatal * @param options.domain * @param options.proxyUrl * @param options.isSatellite * @returns Parsed publishable key object with instanceType and frontendApi, or null if invalid. * * @throws {Error} When options.fatal is true and key is missing or invalid. */ function parsePublishableKey(key, options = {}) { key = key || ""; if (!key || !isPublishableKey(key)) { if (options.fatal && !key) throw new Error("Publishable key is missing. Ensure that your publishable key is correctly configured. Double-check your environment configuration for your keys, or access them here: https://dashboard.clerk.com/last-active?path=api-keys"); if (options.fatal && !isPublishableKey(key)) throw new Error("Publishable key not valid."); return null; } const instanceType = key.startsWith(PUBLISHABLE_KEY_LIVE_PREFIX) ? "production" : "development"; let decodedFrontendApi; try { decodedFrontendApi = require_isomorphicAtob.isomorphicAtob(key.split("_")[2]); } catch { if (options.fatal) throw new Error("Publishable key not valid: Failed to decode key."); return null; } if (!isValidDecodedPublishableKey(decodedFrontendApi)) { if (options.fatal) throw new Error("Publishable key not valid: Decoded key has invalid format."); return null; } let frontendApi = decodedFrontendApi.slice(0, -1); if (options.proxyUrl) frontendApi = options.proxyUrl; else if (instanceType !== "development" && options.domain && options.isSatellite) frontendApi = `clerk.${options.domain}`; return { instanceType, frontendApi }; } /** * Checks if the provided key is a valid publishable key. * * @param key - The key to be checked. Defaults to an empty string if not provided. * @returns `true` if 'key' is a valid publishable key, `false` otherwise. */ function isPublishableKey(key = "") { try { if (!(key.startsWith(PUBLISHABLE_KEY_LIVE_PREFIX) || key.startsWith(PUBLISHABLE_KEY_TEST_PREFIX))) return false; const parts = key.split("_"); if (parts.length !== 3) return false; const encodedPart = parts[2]; if (!encodedPart) return false; return isValidDecodedPublishableKey(require_isomorphicAtob.isomorphicAtob(encodedPart)); } catch { return false; } } /** * Creates a memoized cache for checking if URLs are development or staging environments. * Uses a Map to cache results for better performance on repeated checks. * * @returns An object with an isDevOrStagingUrl method that checks if a URL is dev/staging. */ function createDevOrStagingUrlCache() { const devOrStagingUrlCache = /* @__PURE__ */ new Map(); return { /** * Checks if a URL is a development or staging environment. * * @param url - The URL to check (string or URL object). * @returns `true` if the URL is a development or staging environment, `false` otherwise. */ isDevOrStagingUrl: (url) => { if (!url) return false; const hostname = typeof url === "string" ? url : url.hostname; let res = devOrStagingUrlCache.get(hostname); if (res === void 0) { res = require_constants.DEV_OR_STAGING_SUFFIXES.some((s) => hostname.endsWith(s)); devOrStagingUrlCache.set(hostname, res); } return res; } }; } /** * Checks if a publishable key is for a development environment. * Supports both legacy format (test_) and new format (pk_test_). * * @param apiKey - The API key to check. * @returns `true` if the key is for development, `false` otherwise. */ function isDevelopmentFromPublishableKey(apiKey) { return apiKey.startsWith("test_") || apiKey.startsWith("pk_test_"); } /** * Checks if a publishable key is for a production environment. * Supports both legacy format (live_) and new format (pk_live_). * * @param apiKey - The API key to check. * @returns `true` if the key is for production, `false` otherwise. */ function isProductionFromPublishableKey(apiKey) { return apiKey.startsWith("live_") || apiKey.startsWith("pk_live_"); } /** * Checks if a secret key is for a development environment. * Supports both legacy format (test_) and new format (sk_test_). * * @param apiKey - The secret key to check. * @returns `true` if the key is for development, `false` otherwise. */ function isDevelopmentFromSecretKey(apiKey) { return apiKey.startsWith("test_") || apiKey.startsWith("sk_test_"); } /** * Checks if a secret key is for a production environment. * Supports both legacy format (live_) and new format (sk_live_). * * @param apiKey - The secret key to check. * @returns `true` if the key is for production, `false` otherwise. */ function isProductionFromSecretKey(apiKey) { return apiKey.startsWith("live_") || apiKey.startsWith("sk_live_"); } /** * Generates a unique cookie suffix based on the publishable key using SHA-1 hashing. * The suffix is base64-encoded and URL-safe (+ and / characters are replaced). * * @param publishableKey - The publishable key to generate suffix from. * @param subtle - The SubtleCrypto interface to use for hashing (defaults to globalThis.crypto.subtle). * @returns A promise that resolves to an 8-character URL-safe base64 string. */ async function getCookieSuffix(publishableKey, subtle = globalThis.crypto.subtle) { const data = new TextEncoder().encode(publishableKey); const digest = await subtle.digest("sha-1", data); return require_isomorphicBtoa.isomorphicBtoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/gi, "-").replace(/\//gi, "_").substring(0, 8); } /** * Creates a suffixed cookie name by appending the cookie suffix to the base name. * Used to create unique cookie names based on the publishable key. * * @param cookieName - The base cookie name. * @param cookieSuffix - The suffix to append (typically generated by getCookieSuffix). * @returns The suffixed cookie name in format: `${cookieName}_${cookieSuffix}`. */ const getSuffixedCookieName = (cookieName, cookieSuffix) => { return `${cookieName}_${cookieSuffix}`; }; //#endregion exports.buildPublishableKey = buildPublishableKey; exports.createDevOrStagingUrlCache = createDevOrStagingUrlCache; exports.getCookieSuffix = getCookieSuffix; exports.getSuffixedCookieName = getSuffixedCookieName; exports.isDevelopmentFromPublishableKey = isDevelopmentFromPublishableKey; exports.isDevelopmentFromSecretKey = isDevelopmentFromSecretKey; exports.isProductionFromPublishableKey = isProductionFromPublishableKey; exports.isProductionFromSecretKey = isProductionFromSecretKey; exports.isPublishableKey = isPublishableKey; exports.parsePublishableKey = parsePublishableKey; exports.publishableKeyFromHost = publishableKeyFromHost; //# sourceMappingURL=keys.js.map