import type { FastifyReply, FastifyRequest } from "fastify"; import type { Permission } from "./types.js"; import { getPrincipal } from "./auth.js"; import type { PrincipalContext } from "./principal.js"; export function hasPermission(principal: PrincipalContext, permission: Permission): boolean { return principal.permissions.includes(permission); } export function isPrincipalContext( value: PrincipalContext | FastifyReply, ): value is PrincipalContext { return "issuer" in value; } export function requirePermission( request: FastifyRequest, reply: FastifyReply, permission: Permission, ): PrincipalContext | FastifyReply { const principal = getPrincipal(request); if (hasPermission(principal, permission)) return principal; return reply.code(403).send({ code: "auth_forbidden", error: "This operation is not permitted" }); } /** * A resolved cookie session is populated only by the central auth boundary, after its * request-snapshot Origin and CSRF checks. Route-specific legacy guards must not reinterpret * the internal transport host/protocol for that already-authorized browser request. */ export function hasCookieBackedAuthSession(request: FastifyRequest): boolean { return request.authSession !== undefined; } /** Permit non-browser clients and browsers whose declared origin matches the request host. */ export function requireSameOriginOrNonBrowser( request: FastifyRequest, reply: FastifyReply, ): FastifyReply | undefined { if (hasCookieBackedAuthSession(request)) return undefined; const origin = request.headers.origin; if (origin === undefined) return undefined; if (typeof origin !== "string" || typeof request.headers.host !== "string") { return reply.code(403).send({ code: "auth_forbidden", error: "This operation is not permitted" }); } try { const supplied = new URL(origin); const expected = new URL(`${request.protocol}://${request.headers.host}`); if (supplied.origin === expected.origin) return undefined; } catch { // Invalid browser origins are forbidden below. } return reply.code(403).send({ code: "auth_forbidden", error: "This operation is not permitted" }); }