mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
136 lines
3.9 KiB
TypeScript
136 lines
3.9 KiB
TypeScript
/**
|
|
* Multi purpose CORS lib.
|
|
* Note: Based on the `cors` package in npm but using only web APIs.
|
|
* Taken from: https://github.com/vercel/examples/blob/main/edge-functions/cors/lib/cors.ts
|
|
*/
|
|
|
|
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
|
|
|
|
type OriginFn = (origin: string | undefined, req: Request) => StaticOrigin | Promise<StaticOrigin>;
|
|
|
|
interface CorsOptions {
|
|
origin?: StaticOrigin | OriginFn;
|
|
methods?: string | string[];
|
|
allowedHeaders?: string | string[];
|
|
exposedHeaders?: string | string[];
|
|
credentials?: boolean;
|
|
maxAge?: number;
|
|
preflightContinue?: boolean;
|
|
optionsSuccessStatus?: number;
|
|
}
|
|
|
|
const defaultOptions: CorsOptions = {
|
|
origin: '*',
|
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
|
preflightContinue: false,
|
|
optionsSuccessStatus: 204,
|
|
};
|
|
|
|
function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
|
|
return Array.isArray(allowed)
|
|
? allowed.some((o) => isOriginAllowed(origin, o))
|
|
: typeof allowed === 'string'
|
|
? origin === allowed
|
|
: allowed instanceof RegExp
|
|
? allowed.test(origin)
|
|
: !!allowed;
|
|
}
|
|
|
|
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
|
|
const headers = new Headers();
|
|
|
|
if (origin === '*') {
|
|
headers.set('Access-Control-Allow-Origin', '*');
|
|
} else if (typeof origin === 'string') {
|
|
headers.set('Access-Control-Allow-Origin', origin);
|
|
headers.append('Vary', 'Origin');
|
|
} else {
|
|
const allowed = isOriginAllowed(reqOrigin ?? '', origin);
|
|
|
|
if (allowed && reqOrigin) {
|
|
headers.set('Access-Control-Allow-Origin', reqOrigin);
|
|
}
|
|
headers.append('Vary', 'Origin');
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) {
|
|
const reqOrigin = req.headers.get('Origin') || undefined;
|
|
const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
|
|
|
|
if (!value) return;
|
|
return getOriginHeaders(reqOrigin, value);
|
|
}
|
|
|
|
function getAllowedHeaders(req: Request, allowed?: string | string[]) {
|
|
const headers = new Headers();
|
|
|
|
if (!allowed) {
|
|
allowed = req.headers.get('Access-Control-Request-Headers')!;
|
|
headers.append('Vary', 'Access-Control-Request-Headers');
|
|
} else if (Array.isArray(allowed)) {
|
|
allowed = allowed.join(',');
|
|
}
|
|
if (allowed) {
|
|
headers.set('Access-Control-Allow-Headers', allowed);
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
export default async function cors(req: Request, res: Response, options?: CorsOptions) {
|
|
const opts = { ...defaultOptions, ...options };
|
|
const { headers } = res;
|
|
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
|
|
const mergeHeaders = (v: string, k: string) => {
|
|
if (k === 'Vary') headers.append(k, v);
|
|
else headers.set(k, v);
|
|
};
|
|
|
|
// If there's no origin we won't touch the response
|
|
if (!originHeaders) return res;
|
|
|
|
originHeaders.forEach(mergeHeaders);
|
|
|
|
if (opts.credentials) {
|
|
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
}
|
|
|
|
const exposed = Array.isArray(opts.exposedHeaders)
|
|
? opts.exposedHeaders.join(',')
|
|
: opts.exposedHeaders;
|
|
|
|
if (exposed) {
|
|
headers.set('Access-Control-Expose-Headers', exposed);
|
|
}
|
|
|
|
// Handle the preflight request
|
|
if (req.method === 'OPTIONS') {
|
|
if (opts.methods) {
|
|
const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods;
|
|
|
|
headers.set('Access-Control-Allow-Methods', methods);
|
|
}
|
|
|
|
getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders);
|
|
|
|
if (typeof opts.maxAge === 'number') {
|
|
headers.set('Access-Control-Max-Age', String(opts.maxAge));
|
|
}
|
|
|
|
if (opts.preflightContinue) return res;
|
|
|
|
headers.set('Content-Length', '0');
|
|
return new Response(null, { status: opts.optionsSuccessStatus, headers });
|
|
}
|
|
|
|
// If we got here, it's a normal request
|
|
return res;
|
|
}
|
|
|
|
export function initCors(options?: CorsOptions) {
|
|
return async (req: Request, res: Response) => cors(req, res, options);
|
|
}
|