@clerk/express
Version:
Clerk server SDK for usage with Express
317 lines (306 loc) • 12.7 kB
JavaScript
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