/** * CORS for Bearer-token API callers — registered OUTSIDE every other * middleware so it can answer preflights and decorate responses. * * Same-origin policy exists to protect AMBIENT credentials (cookies). A * deliberately attached Bearer token is not ambient — the token itself is the * gate — so browser cross-origin access is safe to grant, but only when the * token's row explicitly opts in (`cors = 1`, set at mint time): * * - OPTIONS preflights to /_emdash/api/* are answered permissively. A * preflight carries no credentials and grants nothing but the right to * attempt the real request, which still authenticates normally. * - Responses get `Access-Control-Allow-Origin: ` ONLY when the * request authenticated via a Bearer token whose row carries the flag * (auth middleware sets locals.tokenCors). Cookie-authenticated requests * never receive CORS headers and `Access-Control-Allow-Credentials` is * never sent, so no ambient authority is ever exposed cross-origin. */ import { defineMiddleware } from "astro:middleware"; const API_PREFIX = "/_emdash/api/"; const PREFLIGHT_HEADERS: Record = { "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Authorization, Content-Type, Accept, X-EmDash-Request", "Access-Control-Max-Age": "86400", Vary: "Origin", }; export const onRequest = defineMiddleware(async (context, next) => { const origin = context.request.headers.get("Origin"); if ( origin && context.request.method === "OPTIONS" && context.url.pathname.startsWith(API_PREFIX) && context.request.headers.has("Access-Control-Request-Method") ) { return new Response(null, { status: 204, headers: { ...PREFLIGHT_HEADERS, "Access-Control-Allow-Origin": origin }, }); } const response = await next(); if (origin && context.locals.tokenAuth && context.locals.tokenCors) { try { response.headers.set("Access-Control-Allow-Origin", origin); response.headers.append("Vary", "Origin"); } catch { // Immutable headers (e.g. a passed-through upstream response): // return a mutable copy instead. const copy = new Response(response.body, response); copy.headers.set("Access-Control-Allow-Origin", origin); copy.headers.append("Vary", "Origin"); return copy; } } return response; });