Files
EvoScientist-WebUI/src/lib/server/actor.ts
T
2026-08-11 10:08:45 +08:00

52 lines
1.5 KiB
TypeScript

import "server-only";
import type { NextRequest } from "next/server";
import { AUTH_COOKIE_NAME } from "@/lib/auth";
import { decodeSessionPayload, roleForUsername } from "./auth";
import { effectiveSecurity } from "./systemConfig";
import { ensureBootstrapAdmin, type UserRole } from "./userStore";
export interface Actor {
sub: string;
role: UserRole;
}
export class ActorError extends Error {
constructor(
message: string,
readonly status: 401 | 403 = 401
) {
super(message);
}
}
/**
* Resolve the end-user actor for a BFF request. When WebUI authentication is
* disabled (single-user local deployments), every request acts as the
* built-in local admin — the loopback-only posture is enforced by the
* callers that need it.
*/
export function requireActor(request: NextRequest): Actor {
if (!effectiveSecurity().authEnabled) {
return { sub: "local-admin", role: "admin" };
}
const session = decodeSessionPayload(
request.cookies.get(AUTH_COOKIE_NAME)?.value ?? ""
);
if (!session || session.expiresAt <= Math.floor(Date.now() / 1000)) {
throw new ActorError("Authentication required.", 401);
}
ensureBootstrapAdmin();
const role = roleForUsername(session.username);
if (!role) {
throw new ActorError("Authentication required.", 401);
}
return { sub: session.username, role };
}
export function requireAdmin(actor: Actor): void {
if (actor.role !== "admin") {
throw new ActorError("Model configuration requires an admin account.", 403);
}
}