import "server-only"; import { NextResponse } from "next/server"; import { sql, withUser } from "@/lib/db"; import { currentUserId } from "./session"; // Route Handlers under /api/export/* and /api/import are outside the App // Router layout tree, so app/(app)/layout.tsx's HR gate never runs for them — // each one has to re-establish that the caller is an active HR user itself. // RLS is still the real boundary (an unauthorized session simply reads // nothing); this exists so those routes answer 401/403 instead of handing back // an empty workbook. // // Gefragt wird is_hr_user() und nicht mehr profiles.role/is_active von Hand. // Der Unterschied ist nicht kosmetisch: is_hr_user() ist dieselbe Funktion, die // alle 25 RLS-Policies aufrufen. Was immer sie künftig zusätzlich prüft, gilt // hier automatisch mit — beim ausstehenden Passwortwechsel ist genau das schon // passiert (Migration 20260908120000). Die Handfassung hätte davon nichts // gewusst und einen Export ausgeliefert, den die Policies darunter leer // gelassen hätten. export type HrGate = { denied: NextResponse } | { userId: string }; export async function requireHrUser(): Promise { const userId = await currentUserId(); if (!userId) return { denied: NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }) }; const erlaubt = await withUser(userId, async (tx) => { const ergebnis = await sql<{ ok: boolean }>`select is_hr_user() as ok`.execute(tx); return ergebnis.rows[0]?.ok === true; }); if (!erlaubt) return { denied: NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 }) }; return { userId }; }