import { prisma } from "./db"; export type AppUser = { id: string; authentikUid: string; username: string; displayName: string; email: string; avatarUrl: string; role: "admin" | "reviewer" | "viewer"; }; type CacheEntry = { user: AppUser; ts: number }; const userCache = new Map(); const CACHE_TTL_MS = 60_000; function mapGroupsToRole(groups: string[]): "admin" | "reviewer" | "viewer" { const lower = groups.map((g) => g.toLowerCase()); if (lower.some((g) => g.includes("admin"))) return "admin"; if (lower.some((g) => g.includes("reviewer") || g.includes("review"))) return "reviewer"; return "viewer"; } export async function getOrCreateUser(headers: Headers): Promise { const uid = headers.get("x-authentik-uid") ?? ""; const username = headers.get("x-authentik-username") ?? ""; const email = headers.get("x-authentik-email") ?? ""; if (!uid && !username && !email) return null; const cacheKey = uid || username || email; const cached = userCache.get(cacheKey); if (cached && Date.now() - cached.ts < CACHE_TTL_MS) { return cached.user; } const name = headers.get("x-authentik-name") ?? ""; const groupsRaw = headers.get("x-authentik-groups") ?? ""; const groups = groupsRaw ? groupsRaw.split("|") : []; const role = mapGroupsToRole(groups); const dbUser = await prisma.user.upsert({ where: { authentikUid: uid || `fallback-${username || email}` }, update: { username: username || undefined, displayName: name || username || undefined, email: email || undefined, role, }, create: { authentikUid: uid || `fallback-${username || email}`, username: username || email, displayName: name || username || email, email, role, }, }); const user: AppUser = { id: dbUser.id, authentikUid: dbUser.authentikUid, username: dbUser.username, displayName: dbUser.displayName, email: dbUser.email, avatarUrl: dbUser.avatarUrl, role: dbUser.role as AppUser["role"], }; userCache.set(cacheKey, { user, ts: Date.now() }); return user; } export function requireRole(user: AppUser | null, ...roles: AppUser["role"][]): void { if (!user || !roles.includes(user.role)) { throw new RoleError("Insufficient permissions"); } } export class RoleError extends Error { constructor(message: string) { super(message); this.name = "RoleError"; } }