/** * Shared server-function RPC helpers used by both the dev server (index.ts) * and the production server (prod.ts). Kept in their own module so prod.ts * can import them without creating a circular dependency on index.ts (which * itself re-exports `createProductionServer`/`createProductionHandlers` from * prod.ts). */ import { createContext, runWithRequestContext } from "@wrnexus/core"; /** * CSRF check for `/__wrnexus/rpc` (the server-function RPC endpoint the * browser runtime calls for `server.fn()`). Requires a same-origin request * carrying a matching CSRF cookie + header pair — the standard double-submit * cookie pattern. */ export function validateRpcCsrf(request: Request): boolean { if (request.headers.get("sec-fetch-site") === "cross-site") return false; const url = new URL(request.url); const origin = request.headers.get("origin"); if (origin && origin !== url.origin) return false; const cookieHeader = request.headers.get("cookie") ?? ""; const cookieToken = /(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ?? /(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1]; const headerToken = request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf"); if (!cookieToken || !headerToken) return false; let decodedToken: string; try { decodedToken = decodeURIComponent(cookieToken); } catch { return false; } let diff = decodedToken.length ^ headerToken.length; for (let i = 0; i < Math.max(decodedToken.length, headerToken.length); i++) { diff |= decodedToken.charCodeAt(i) ^ headerToken.charCodeAt(i); } return diff === 0; } /** * Wrap the server-function RPC handler so it runs inside the request's * AsyncLocalStorage context. `/__wrnexus/rpc` is intercepted BEFORE * `handlers.fetch` (fetchHandler) in both dev and production, so a server * function called via `server.fn()` from the browser runs entirely outside * fetchHandler's own context wrap. It runs user code directly, so — like * every other entry point that runs user server code — it needs the request * context too. */ export function withServerFnRequestContext( handler: (request: Request) => Promise, ): (request: Request) => Promise { return (request: Request) => { const url = new URL(request.url); const ctx = createContext(request, url); return runWithRequestContext(ctx, () => handler(request)); }; }