Files
blog-manager/lib/http.ts
2026-11-01 13:23:44 -05:00

41 lines
1.5 KiB
TypeScript

import { HttpError } from "./errors";
/* There is deliberately no authentication (access is controlled by firewall
* rules). The one guard: mutating requests from a browser must originate
* from this app, so a page on another site can't POST to the LAN service. */
function checkOrigin(req: Request): void {
const origin = req.headers.get("origin");
if (!origin) return; // curl and other non-browser clients
let originHost: string;
try {
originHost = new URL(origin).host;
} catch {
throw new HttpError(403, "bad origin");
}
if (originHost !== req.headers.get("host")) throw new HttpError(403, "cross-origin request refused");
}
/** Wraps a route handler: origin check for writes + HttpError -> JSON response. */
export function route<C>(handler: (req: Request, ctx: C) => Promise<Response>) {
return async (req: Request, ctx: C): Promise<Response> => {
try {
if (req.method !== "GET" && req.method !== "HEAD") checkOrigin(req);
return await handler(req, ctx);
} catch (err) {
if (err instanceof HttpError) {
return Response.json({ error: err.message, details: err.details }, { status: err.status });
}
console.error("[blog-manager]", err);
return Response.json({ error: "internal error" }, { status: 500 });
}
};
}
export async function readJson<T = Record<string, unknown>>(req: Request): Promise<T> {
try {
return (await req.json()) as T;
} catch {
throw new HttpError(400, "request body must be JSON");
}
}