55 lines
2.1 KiB
TypeScript
55 lines
2.1 KiB
TypeScript
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" });
|
|
}
|