Passwort-Anmeldung: Provider, Formulare, drei Zustaende der Shell
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
import "server-only";
|
||||
import { NextResponse } from "next/server";
|
||||
import { withUser } from "@/lib/db";
|
||||
import { sql, withUser } from "@/lib/db";
|
||||
import { currentUserId } from "./session";
|
||||
|
||||
// Route Handlers under /api/export/* 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.
|
||||
// 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 };
|
||||
|
||||
@@ -15,12 +24,11 @@ export async function requireHrUser(): Promise<HrGate> {
|
||||
const userId = await currentUserId();
|
||||
if (!userId) return { denied: NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }) };
|
||||
|
||||
const profile = await withUser(userId, (tx) =>
|
||||
tx.selectFrom("profiles").select(["role", "is_active"]).where("id", "=", userId).executeTakeFirst()
|
||||
);
|
||||
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 (profile?.role !== "hr" || profile.is_active !== true) {
|
||||
return { denied: NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 }) };
|
||||
}
|
||||
if (!erlaubt) return { denied: NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 }) };
|
||||
return { userId };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user