evOo-local/apps/api/src/request-body-limit.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

64 lines
2.2 KiB
TypeScript

import { LOCAL_SETTINGS_RPC } from "@rakazo/contracts";
import type { Hono, MiddlewareHandler } from "hono";
import { cancelBody } from "./http-body.js";
export const MAX_AUTH_REQUEST_BYTES = 64 * 1024;
export const MAX_RPC_REQUEST_BYTES = 16 * 1024 * 1024;
/** Bound JSON entry points before their framework parsers buffer the request. */
export function requestBodyLimit(maxSize: number): MiddlewareHandler {
if (!Number.isSafeInteger(maxSize) || maxSize <= 0) {
throw new Error("Request body limit must be a positive integer");
}
return async (c, next) => {
const request = c.req.raw;
if (!request.body) return next();
const contentLength = request.headers.get("content-length");
if (contentLength !== null && !request.headers.has("transfer-encoding")) {
const declared = /^[0-9]+$/.test(contentLength) ? Number(contentLength) : Number.NaN;
if (!Number.isSafeInteger(declared) || declared < 0 || declared > maxSize) {
cancelBody(request.body);
return c.json({ error: "Request body is too large." }, 413);
}
return next();
}
const reader = request.body.getReader();
const chunks: Uint8Array[] = [];
let size = 0;
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > maxSize) {
cancelBody(reader);
return c.json({ error: "Request body is too large." }, 413);
}
chunks.push(value);
}
} finally {
reader.releaseLock();
}
c.req.raw = new Request(request, {
body: new ReadableStream({
start(controller) {
for (const chunk of chunks) controller.enqueue(chunk);
controller.close();
},
}),
duplex: "half",
} as RequestInit & { duplex: "half" });
return next();
};
}
/** Install limits only on framework-parsed JSON surfaces with known payload contracts. */
export function mountApiRequestBodyLimits(app: Hono): void {
app.use("/api/auth/*", requestBodyLimit(MAX_AUTH_REQUEST_BYTES));
app.use(`${LOCAL_SETTINGS_RPC}/*`, requestBodyLimit(MAX_RPC_REQUEST_BYTES));
app.use("/rpc/*", requestBodyLimit(MAX_RPC_REQUEST_BYTES));
}