UNPKG

@clerk/express

Version:

Clerk server SDK for usage with Express

317 lines (306 loc) • 12.7 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_utils = require('./utils-ChU4G25X.js'); let _clerk_backend = require("@clerk/backend"); let stream = require("stream"); let _clerk_backend_internal = require("@clerk/backend/internal"); let _clerk_backend_proxy = require("@clerk/backend/proxy"); let _clerk_shared_keys = require("@clerk/shared/keys"); let _clerk_shared_logger = require("@clerk/shared/logger"); let _clerk_shared_proxy = require("@clerk/shared/proxy"); let _clerk_shared_utils = require("@clerk/shared/utils"); let _clerk_shared_deprecated = require("@clerk/shared/deprecated"); //#region src/clerkClient.ts let clerkClientSingleton = {}; const clerkClient = new Proxy(clerkClientSingleton, { get(_target, property) { if (property in clerkClientSingleton) return clerkClientSingleton[property]; const env = { ...require_utils.loadApiEnv(), ...require_utils.loadClientEnv() }; const client = (0, _clerk_backend.createClerkClient)({ ...env, userAgent: `@clerk/express@2.1.40` }); if (env.secretKey) clerkClientSingleton = client; return client[property]; }, set() { return false; } }); //#endregion //#region src/errors.ts const createErrorMessage = (msg) => { return `🔒 Clerk: ${msg.trim()} For more info, check out the docs: https://clerk.com/docs, or come say hi in our discord server: https://clerk.com/discord `; }; const middlewareRequired = (fnName) => createErrorMessage(`The "clerkMiddleware" should be registered before using "${fnName}". Example: import express from 'express'; import { clerkMiddleware } from '@clerk/express'; const app = express(); app.use(clerkMiddleware()); `); const satelliteAndMissingProxyUrlAndDomain = "Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl"; const satelliteAndMissingSignInUrl = ` Invalid signInUrl. A satellite application requires a signInUrl for development instances. Check if signInUrl is missing from your configuration or if it is not an absolute URL.`; //#endregion //#region src/authenticateRequest.ts /** * @internal * Authenticates an Express request by wrapping clerkClient.authenticateRequest and * converts the express request object into a standard web request object * * @param opts - Configuration options for request authentication * @param opts.clerkClient - The Clerk client instance to use for authentication * @param opts.request - The Express request object to authenticate * @param opts.options - Optional middleware configuration options */ const authenticateRequest = (opts) => { const { clerkClient, request, options } = opts; const { clerkClient: _clerkClient, debug: _debug, frontendApiProxy: _frontendApiProxy, isSatellite: isSatelliteInput, domain: domainInput, signInUrl: signInUrlInput, proxyUrl: proxyUrlInput, secretKey: secretKeyInput, machineSecretKey: machineSecretKeyInput, publishableKey: publishableKeyInput, ...restOptions } = options || {}; const clerkRequest = (0, _clerk_backend_internal.createClerkRequest)(require_utils.incomingMessageToRequest(request)); const env = { ...require_utils.loadApiEnv(), ...require_utils.loadClientEnv() }; const secretKey = secretKeyInput || env.secretKey; const machineSecretKey = machineSecretKeyInput || env.machineSecretKey; const publishableKey = publishableKeyInput || env.publishableKey; const isSatellite = (0, _clerk_shared_utils.handleValueOrFn)(isSatelliteInput, clerkRequest.clerkUrl, env.isSatellite); const domain = (0, _clerk_shared_utils.handleValueOrFn)(domainInput, clerkRequest.clerkUrl) || env.domain; const signInUrl = signInUrlInput || env.signInUrl; const proxyUrl = absoluteProxyUrl((0, _clerk_shared_utils.handleValueOrFn)(proxyUrlInput, clerkRequest.clerkUrl, env.proxyUrl), clerkRequest.clerkUrl.toString()); if (isSatellite && !proxyUrl && !domain) throw new Error(satelliteAndMissingProxyUrlAndDomain); if (isSatellite && !(0, _clerk_shared_proxy.isHttpOrHttps)(signInUrl) && (0, _clerk_shared_keys.isDevelopmentFromSecretKey)(secretKey || "")) throw new Error(satelliteAndMissingSignInUrl); return clerkClient.authenticateRequest(clerkRequest, { ...restOptions, secretKey, machineSecretKey, publishableKey, proxyUrl, isSatellite, domain, signInUrl }); }; const setResponseHeaders = (requestState, res) => { if (requestState.headers) requestState.headers.forEach((value, key) => res.appendHeader(key, value)); return setResponseForHandshake(requestState, res); }; /** * Depending on the auth state of the request, handles applying redirects and validating that a handshake state was properly handled. * * Returns an error if state is handshake without a redirect, otherwise returns undefined. res.writableEnded should be checked after this method is called. */ const setResponseForHandshake = (requestState, res) => { if (requestState.headers.get("location")) { res.status(307).end(); return; } if (requestState.status === _clerk_backend_internal.AuthStatus.Handshake) return /* @__PURE__ */ new Error("Clerk: unexpected handshake without redirect"); }; const absoluteProxyUrl = (relativeOrAbsoluteUrl, baseUrl) => { if (!relativeOrAbsoluteUrl || !(0, _clerk_shared_proxy.isValidProxyUrl)(relativeOrAbsoluteUrl) || !(0, _clerk_shared_proxy.isProxyUrlRelative)(relativeOrAbsoluteUrl)) return relativeOrAbsoluteUrl; return new URL(relativeOrAbsoluteUrl, baseUrl).toString(); }; const resolveDefaultClerkClient = (options) => { if (!options.apiUrl && !options.apiVersion) return clerkClient; return (0, _clerk_backend.createClerkClient)({ ...require_utils.loadApiEnv(), ...require_utils.loadClientEnv(), ...options.apiUrl ? { apiUrl: options.apiUrl } : {}, ...options.apiVersion ? { apiVersion: options.apiVersion } : {}, userAgent: `@clerk/express@2.1.40` }); }; const authenticateAndDecorateRequest = (options = {}) => { const clerkClient = options.clerkClient || resolveDefaultClerkClient(options); const frontendApiProxy = options.frontendApiProxy; const proxyPath = (0, _clerk_backend_proxy.stripTrailingSlashes)(frontendApiProxy?.path ?? _clerk_backend_proxy.DEFAULT_PROXY_PATH) || _clerk_backend_proxy.DEFAULT_PROXY_PATH; const middleware = async (request, response, next) => { if (require_utils.requestHasAuthObject(request)) return next(); if ("auth" in request) _clerk_shared_logger.logger.warnOnce("Clerk: another middleware has already set `req.auth` on this request. Clerk authentication will run anyway and overwrite it. To use another auth library alongside Clerk, configure it to store its state on a different request property."); const env = { ...require_utils.loadApiEnv(), ...require_utils.loadClientEnv() }; const publishableKey = options.publishableKey || env.publishableKey; const secretKey = options.secretKey || env.secretKey; if (frontendApiProxy) { const requestUrl = new URL(request.originalUrl || request.url, `http://${request.headers.host}`); if ((typeof frontendApiProxy.enabled === "function" ? frontendApiProxy.enabled(requestUrl) : frontendApiProxy.enabled) && (requestUrl.pathname === proxyPath || requestUrl.pathname.startsWith(proxyPath + "/"))) { const proxyResponse = await (0, _clerk_backend_proxy.clerkFrontendApiProxy)(require_utils.requestToProxyRequest(request), { proxyPath, publishableKey, secretKey }); response.status(proxyResponse.status); proxyResponse.headers.forEach((value, key) => { response.setHeader(key, value); }); if (proxyResponse.body) { const reader = proxyResponse.body.getReader(); new stream.Readable({ async read() { try { const { done, value } = await reader.read(); if (done) this.push(null); else this.push(Buffer.from(value)); } catch (error) { this.destroy(error instanceof Error ? error : new Error(String(error))); } } }).pipe(response); } else response.end(); return; } } let resolvedOptions = options; if (frontendApiProxy && !options.proxyUrl) { const requestUrl = new URL(request.originalUrl || request.url, `http://${request.headers.host}`); if (typeof frontendApiProxy.enabled === "function" ? frontendApiProxy.enabled(requestUrl) : frontendApiProxy.enabled) resolvedOptions = { ...options, proxyUrl: proxyPath }; } try { const requestState = await authenticateRequest({ clerkClient, request, options: resolvedOptions }); const err = setResponseHeaders(requestState, response); if (err) return next(err); if (response.writableEnded) return; const auth = require_utils.brandRequestAuth((opts) => requestState.toAuth(opts)); Object.assign(request, { auth }); next(); } catch (err) { next(err); } }; return middleware; }; //#endregion //#region src/clerkMiddleware.ts /** * Middleware that integrates Clerk authentication into your Express application. * It checks the request's cookies and headers for a session JWT and, if found, * attaches the Auth object to the request object under the `auth` key. * * Accepts either a static options object or a callback that receives the request * and returns options. The callback form is useful for multi-domain setups where * the publishable key differs per domain. * * @example * app.use(clerkMiddleware(options)); * * @example * const clerkClient = createClerkClient({ ... }); * app.use(clerkMiddleware({ clerkClient })); * * @example * app.use(clerkMiddleware()); * * @example * // Dynamic keys per domain * app.use(clerkMiddleware((req) => ({ * publishableKey: req.hostname === 'example.com' ? PK_A : PK_B, * }))); */ const clerkMiddleware = (options = {}) => { if (typeof options !== "function") { const authMiddleware = authenticateAndDecorateRequest({ ...options, acceptsToken: "any" }); return (request, response, next) => { authMiddleware(request, response, next); }; } return async (request, response, next) => { try { authenticateAndDecorateRequest({ ...await options(request), acceptsToken: "any" })(request, response, next); } catch (err) { next(err); } }; }; //#endregion //#region src/getAuth.ts /** * Retrieves the Clerk AuthObject using the current request object. * * @param {GetAuthOptions} options - Optional configuration for retrieving auth object. * @returns {AuthObject} Object with information about the request state and claims. * @throws {Error} `clerkMiddleware` or `requireAuth` is required to be set in the middleware chain before this util is used. */ const getAuth = ((req, options) => { if (!require_utils.requestHasAuthObject(req)) throw new Error(middlewareRequired("getAuth")); return (0, _clerk_backend_internal.getAuthObjectForAcceptedToken)({ authObject: req.auth(options), acceptsToken: options?.acceptsToken }); }); //#endregion //#region src/requireAuth.ts /** * Middleware to require authentication for user requests. * Redirects unauthenticated requests to the sign-in url. * * @deprecated Use `clerkMiddleware()` with `getAuth()` instead. * `requireAuth` will be removed in the next major version. * * @example * // Before (deprecated) * import { requireAuth } from '@clerk/express' * router.get('/path', requireAuth(), getHandler) * * @example * // After (recommended) * import { clerkMiddleware, getAuth } from '@clerk/express' * * app.use(clerkMiddleware()) * * app.get('/api/protected', (req, res) => { * const { userId } = getAuth(req); * if (!userId) { * return res.status(401).json({ error: 'Unauthorized' }); * } * // handle authenticated request * }) */ const requireAuth = (options = {}) => { const authMiddleware = authenticateAndDecorateRequest({ ...options, acceptsToken: "any" }); return (request, response, next) => { (0, _clerk_shared_deprecated.deprecated)("requireAuth", "Use `clerkMiddleware()` with `getAuth()` instead. `requireAuth` will be removed in the next major version."); authMiddleware(request, response, (err) => { if (err) return next(err); const signInUrl = options.signInUrl || process.env.CLERK_SIGN_IN_URL || "/"; if (!request.auth()?.userId) return response.redirect(signInUrl); next(); }); }; }; //#endregion exports.authenticateRequest = authenticateRequest; exports.clerkClient = clerkClient; exports.clerkMiddleware = clerkMiddleware; exports.getAuth = getAuth; exports.requireAuth = requireAuth; Object.keys(_clerk_backend).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return _clerk_backend[k]; } }); }); //# sourceMappingURL=index.js.map