@beignet/core
Version:
Core framework primitives for Beignet
91 lines • 3.41 kB
JavaScript
/**
* CORS hook utilities for @beignet/core/server
*/
const DEFAULT_CORS = {
origins: "*",
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
headers: ["Content-Type", "Authorization"],
credentials: false,
};
function resolveCorsConfig(config) {
const resolved = {
origins: config.origins ?? DEFAULT_CORS.origins,
methods: config.methods ?? DEFAULT_CORS.methods,
headers: config.headers ?? DEFAULT_CORS.headers,
credentials: config.credentials ?? DEFAULT_CORS.credentials,
};
if (resolved.credentials &&
(resolved.origins === "*" ||
(Array.isArray(resolved.origins) && resolved.origins.includes("*")))) {
throw new Error('Credentialed CORS cannot use a wildcard origin. Use origins: ["https://app.example.com"] with credentials: true.');
}
return resolved;
}
function appendVaryOrigin(headers) {
const varyKey = Object.keys(headers).find((key) => key.toLowerCase() === "vary") ?? "Vary";
const current = headers[varyKey];
const values = current?.split(",").map((value) => value.trim().toLowerCase());
if (values?.includes("origin"))
return;
headers[varyKey] = current ? `${current}, Origin` : "Origin";
}
/**
* Apply CORS response headers to a mutable header record.
*
* Credentialed CORS rejects wildcard origins. Use an explicit origin allow-list
* when cookies or authorization headers are allowed cross-origin.
*/
export function applyCorsHeaders(headers, req, corsConfig) {
const { origins, methods, headers: allowedHeaders, credentials, } = resolveCorsConfig(corsConfig);
if (origins === "*") {
headers["Access-Control-Allow-Origin"] = "*";
}
else if (Array.isArray(origins)) {
const requestOrigin = req.headers.get("Origin");
if (requestOrigin && origins.includes(requestOrigin)) {
headers["Access-Control-Allow-Origin"] = requestOrigin;
appendVaryOrigin(headers);
}
}
headers["Access-Control-Allow-Methods"] = methods.join(", ");
headers["Access-Control-Allow-Headers"] = allowedHeaders.join(", ");
if (credentials) {
headers["Access-Control-Allow-Credentials"] = "true";
}
}
/**
* Create CORS hooks for preflight and regular responses.
*
* CORS preflight requests short-circuit with a 204 response. Explicit
* `OPTIONS` routes without `Access-Control-Request-Method` continue through the
* normal route pipeline. All responses are decorated in `beforeSend`.
*/
export function createCorsHooks(config) {
const corsConfig = resolveCorsConfig(config);
return {
name: "cors",
onRequest: ({ req }) => {
if (req.method !== "OPTIONS" ||
!req.headers.get("Origin") ||
!req.headers.get("Access-Control-Request-Method")) {
return undefined;
}
const headers = {};
applyCorsHeaders(headers, req, corsConfig);
return {
status: 204,
headers,
body: null,
};
},
beforeSend: ({ req, response }) => {
const headers = { ...(response.headers ?? {}) };
applyCorsHeaders(headers, req, corsConfig);
return {
...response,
headers,
};
},
};
}
//# sourceMappingURL=cors.js.map