Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/server/request-origin.ts
T

53 lines
1.8 KiB
TypeScript

const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
export type MutationOriginDecision =
| { allowed: true; reason: "safe-method" | "same-origin" | "non-browser" }
| { allowed: false; reason: "cross-site" | "origin-mismatch" | "browser-origin-missing" };
export function validateMutationOrigin(request: Request): MutationOriginDecision {
if (SAFE_METHODS.has(request.method.toUpperCase())) {
return { allowed: true, reason: "safe-method" };
}
const fetchSite = request.headers.get("sec-fetch-site")?.trim().toLowerCase();
if (fetchSite === "cross-site") {
return { allowed: false, reason: "cross-site" };
}
const requestOrigin = new URL(request.url).origin;
const origin = normalizeOrigin(request.headers.get("origin"));
if (origin !== null) {
return origin === requestOrigin
? { allowed: true, reason: "same-origin" }
: { allowed: false, reason: "origin-mismatch" };
}
const referer = normalizeOrigin(request.headers.get("referer"));
if (referer !== null) {
return referer === requestOrigin
? { allowed: true, reason: "same-origin" }
: { allowed: false, reason: "origin-mismatch" };
}
if (fetchSite === "same-origin") {
return { allowed: true, reason: "same-origin" };
}
if (fetchSite !== undefined) {
return { allowed: false, reason: "browser-origin-missing" };
}
// Non-browser integrations authenticate with their own signature/token and
// do not emit Fetch Metadata headers. Their route-level authentication still
// applies; this gate only rejects browser-originated CSRF.
return { allowed: true, reason: "non-browser" };
}
function normalizeOrigin(rawValue: string | null): string | null {
if (!rawValue || rawValue === "null") return null;
try {
return new URL(rawValue).origin;
} catch {
return "invalid";
}
}