Files
Jyotisha/frontend/src/modules/identity/host.ts
T
Jesse_Chen 99e2abe9bc
Staging Backend Quality Gate / validate (push) Successful in 19m41s
Staging Backend Quality Gate / publish (push) Successful in 28m13s
fix(admin): secure proxied model mutations
2026-08-08 01:38:50 +08:00

72 lines
2.0 KiB
TypeScript

import type { SelfHostedIdentityConfig } from "./config.ts";
export type IdentityRequestHandler = (
request: Request,
) => Response | Promise<Response>;
export interface IdentityAuthHandlers {
GET: IdentityRequestHandler;
POST: IdentityRequestHandler;
}
export function normalizeIdentityHost(value: string | null): string | null {
if (!value || value !== value.trim() || /[\s,@/\\]/.test(value)) return null;
try {
const url = new URL(`https://${value}`);
if (
url.username ||
url.password ||
url.pathname !== "/" ||
url.search ||
url.hash
) {
return null;
}
return url.host.toLowerCase();
} catch {
return null;
}
}
export function resolveIdentitySurface(
hostHeader: string | null,
config: SelfHostedIdentityConfig,
): "user" | "admin" | null {
const host = normalizeIdentityHost(hostHeader);
if (!host) return null;
if (host === new URL(config.userOrigin).host.toLowerCase()) return "user";
if (host === new URL(config.adminOrigin).host.toLowerCase()) return "admin";
return null;
}
function isAdminEndpoint(request: Request): boolean {
try {
const path = decodeURIComponent(new URL(request.url).pathname);
return /^\/api\/auth\/+admin(?:\/|$)/i.test(path);
} catch {
return true;
}
}
export function createHostIsolatedAuthHandlers(
config: SelfHostedIdentityConfig,
handlers: { user: IdentityAuthHandlers },
): IdentityAuthHandlers {
const dispatch =
(method: keyof IdentityAuthHandlers): IdentityRequestHandler =>
async (request) => {
const surface = resolveIdentitySurface(request.headers.get("host"), config);
if (!surface) {
return new Response("Unrecognized identity host", { status: 421 });
}
if (surface === "user" && isAdminEndpoint(request)) {
return new Response("Not found", { status: 404 });
}
return handlers.user[method](request);
};
return { GET: dispatch("GET"), POST: dispatch("POST") };
}