Files
paperclip/server/src/middleware/board-mutation-guard.ts
Forgotten e1f2be7ecf feat(server): integrate Better Auth, access control, and deployment mode startup
Wire up Better Auth for session-based authentication. Add actor middleware
that resolves local_trusted mode to an implicit board actor and authenticated
mode to Better Auth sessions. Add access service with membership, permission,
invite, and join-request management. Register access routes for member/invite/
join-request CRUD. Update health endpoint to report deployment mode and
bootstrap status. Enforce tasks:assign and agents:create permissions in issue
and agent routes. Add deployment mode validation at startup with guardrails
(loopback-only for local_trusted, auth config required for authenticated).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:40:32 -06:00

70 lines
1.8 KiB
TypeScript

import type { Request, RequestHandler } from "express";
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
const DEFAULT_DEV_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://localhost:3100",
"http://127.0.0.1:3100",
];
function parseOrigin(value: string | undefined) {
if (!value) return null;
try {
const url = new URL(value);
return `${url.protocol}//${url.host}`.toLowerCase();
} catch {
return null;
}
}
function trustedOriginsForRequest(req: Request) {
const origins = new Set(DEFAULT_DEV_ORIGINS.map((value) => value.toLowerCase()));
const host = req.header("host")?.trim();
if (host) {
origins.add(`http://${host}`.toLowerCase());
origins.add(`https://${host}`.toLowerCase());
}
return origins;
}
function isTrustedBoardMutationRequest(req: Request) {
const allowedOrigins = trustedOriginsForRequest(req);
const origin = parseOrigin(req.header("origin"));
if (origin && allowedOrigins.has(origin)) return true;
const refererOrigin = parseOrigin(req.header("referer"));
if (refererOrigin && allowedOrigins.has(refererOrigin)) return true;
return false;
}
export function boardMutationGuard(): RequestHandler {
return (req, res, next) => {
if (SAFE_METHODS.has(req.method.toUpperCase())) {
next();
return;
}
if (req.actor.type !== "board") {
next();
return;
}
// Local-trusted mode uses an implicit board actor for localhost-only development.
// In this mode, origin/referer headers can be omitted by some clients for multipart
// uploads; do not block those mutations.
if (req.actor.source === "local_implicit") {
next();
return;
}
if (!isTrustedBoardMutationRequest(req)) {
res.status(403).json({ error: "Board mutation requires trusted browser origin" });
return;
}
next();
};
}