Files
OmniRoute/src/server/authz/assertAuth.ts
backryun a16e47c593 fix(providers): refresh web client user agents (#1699)
Integrated release/v3.7.2 changes — refreshed web client user agents, env docs, Gemini OAuth fix
2026-04-28 02:41:19 -03:00

89 lines
2.6 KiB
TypeScript

import {
AUTHZ_HEADER_AUTH_ID,
AUTHZ_HEADER_AUTH_KIND,
AUTHZ_HEADER_AUTH_LABEL,
AUTHZ_HEADER_AUTH_SCOPES,
AUTHZ_HEADER_ROUTE_CLASS,
} from "./headers";
import type { AuthSubject, RouteClass } from "./types";
export class AuthzAssertionError extends Error {
readonly status: number;
readonly code: string;
constructor(code: string, message: string, status = 500) {
super(message);
this.name = "AuthzAssertionError";
this.code = code;
this.status = status;
}
}
type HeaderSource = Headers | { get(name: string): string | null };
function readHeader(source: HeaderSource, name: string): string | null {
return source.get(name) ?? null;
}
function isHeaderSource(value: unknown): value is HeaderSource {
if (value instanceof Headers) return true;
if (typeof value !== "object" || value === null) return false;
return typeof (value as { get?: unknown }).get === "function";
}
export function readSubjectFromHeaders(headers: HeaderSource): AuthSubject {
const kind = (readHeader(headers, AUTHZ_HEADER_AUTH_KIND) ?? "anonymous") as AuthSubject["kind"];
const id = readHeader(headers, AUTHZ_HEADER_AUTH_ID) ?? "anonymous";
const label = readHeader(headers, AUTHZ_HEADER_AUTH_LABEL) ?? undefined;
const rawScopes = readHeader(headers, AUTHZ_HEADER_AUTH_SCOPES);
const scopes = rawScopes
? rawScopes
.split(",")
.map((s) => s.trim())
.filter(Boolean)
: [];
return { kind, id, label, scopes };
}
export function readRouteClassFromHeaders(headers: HeaderSource): RouteClass | null {
const raw = readHeader(headers, AUTHZ_HEADER_ROUTE_CLASS);
if (raw === "PUBLIC" || raw === "CLIENT_API" || raw === "MANAGEMENT") return raw;
return null;
}
export function assertAuth(
request: Request | { headers: HeaderSource },
expected: RouteClass
): AuthSubject {
const headers = request.headers;
if (!isHeaderSource(headers)) {
throw new AuthzAssertionError("AUTHZ_INVALID_REQUEST", "Request headers are unavailable", 500);
}
const actualClass = readRouteClassFromHeaders(headers);
if (!actualClass) {
throw new AuthzAssertionError(
"AUTHZ_NOT_INITIALIZED",
"Request did not pass through the authz middleware",
500
);
}
if (actualClass !== expected) {
throw new AuthzAssertionError(
"AUTHZ_ROUTE_CLASS_MISMATCH",
`Expected ${expected} but got ${actualClass}`,
500
);
}
const subject = readSubjectFromHeaders(headers);
if (expected !== "PUBLIC" && subject.kind === "anonymous") {
throw new AuthzAssertionError("AUTHZ_UNAUTHENTICATED", "Authentication required", 401);
}
return subject;
}