mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
Integrated release/v3.7.2 changes — refreshed web client user agents, env docs, Gemini OAuth fix
89 lines
2.6 KiB
TypeScript
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;
|
|
}
|