← back to Govarbitrage
src/lib/auth.ts
49 lines
import { NextRequest, NextResponse } from "next/server";
import { SESSION_COOKIE, verifySession } from "@/lib/session";
export type Role = "ADMIN" | "ANALYST" | "VIEWER";
const RANK: Record<Role, number> = { VIEWER: 0, ANALYST: 1, ADMIN: 2 };
/** Resolve the caller's role from the session cookie, or null if unauthenticated. */
async function sessionRole(req: NextRequest): Promise<Role | null> {
const token = req.cookies.get(SESSION_COOKIE)?.value;
const s = token ? await verifySession(token) : null;
if (!s) return null;
return ((s.role as Role) ?? "VIEWER");
}
function deny(role: Role | null): NextResponse {
// Distinguish unauthenticated (401) from authenticated-but-insufficient (403).
const status = role ? 403 : 401;
return NextResponse.json({ error: status === 403 ? "Forbidden" : "Unauthorized" }, { status });
}
/**
* Write-guard for import + operational endpoints (defense-in-depth behind the
* middleware). Allows the request when EITHER:
* • a logged-in user whose role meets `minRole` (default ANALYST — a VIEWER
* session, including one minted from the fleet SSO cookie, CANNOT write), OR
* • the machine `x-import-token` header matches IMPORT_TOKEN (extension / CSV).
* Returns a 401/403 NextResponse when denied, or null when allowed.
*/
export async function requireWrite(req: NextRequest, minRole: Role = "ANALYST"): Promise<NextResponse | null> {
const role = await sessionRole(req);
if (role && RANK[role] >= RANK[minRole]) return null;
const importToken = process.env.IMPORT_TOKEN;
if (importToken && req.headers.get("x-import-token") === importToken) return null;
return deny(role);
}
/**
* Admin-only guard for tier-0 surfaces (stored provider credentials/secrets).
* NO machine-token bypass and NO fleet-SSO VIEWER access — a full ADMIN session
* is required, which on this app means the local username/password login.
*/
export async function requireAdmin(req: NextRequest): Promise<NextResponse | null> {
const role = await sessionRole(req);
if (role === "ADMIN") return null;
return deny(role);
}