import NextAuth from "next-auth"; import { NextResponse } from "next/server"; import { authConfig } from "@/lib/auth/config"; // Next.js 16 renamed Middleware to Proxy (same mechanism, new filename and // export). Das hier ist die vorderste Tür: wer keine Sitzung hat, landet auf // /login statt in der Anwendung. // // Gebaut wird dafür eine *zweite*, absichtlich unvollständige Auth.js-Instanz // — nur aus lib/auth/config.ts, ohne die Rückrufe aus auth.ts. Der Grund ist // handfest: auth.ts spricht mit der Datenbank, und der Proxy läuft je nach // Betriebsart in einer Umgebung ohne Node-Module. Die Instanz hier liest das // Sitzungscookie und sonst nichts. // // Deshalb prüft der Proxy auch nur, *ob* jemand angemeldet ist — nicht mehr, // ob die Person HR-Rechte hat. Das ist keine Lücke, sondern eine Verschiebung // an die Stellen, die es wahrheitsgemäss beantworten können: // // • app/(app)/layout.tsx fragt profiles bei jedem Aufbau frisch ab und // leitet auf /login?error=no_hr_access um, // • die Route Handler unter /api/export/* tun dasselbe über requireHrUser(), // • und darunter, unabhängig von allem Anwendungscode, entscheiden die // RLS-Policies über is_hr_user(). // // Die Alternative — role und is_active ins Sitzungstoken schreiben — hätte // den Proxy schneller gemacht und dafür eine Behauptung eingefroren: eine // entzogene Freischaltung wirkte erst mit dem nächsten Token. Bei einer // Personalanwendung ist das die falsche Richtung. // Als Funktion übergeben, nicht als Objekt: siehe authConfig(). const { auth } = NextAuth(() => authConfig()); const gate = auth((request) => { const { pathname } = request.nextUrl; // Auth.js' eigene Endpunkte müssen durch, bevor es eine Sitzung gibt — // dort entsteht sie ja erst. Ohne diese Ausnahme leitet der Proxy den // Rückweg aus Entra nach /login um und die Anmeldung kommt nie zustande. if (pathname.startsWith("/api/auth")) return NextResponse.next(); const isLoginRoute = pathname.startsWith("/login"); const isSignedIn = Boolean(request.auth?.user?.id); if (!isSignedIn) { if (isLoginRoute) return NextResponse.next(); const url = request.nextUrl.clone(); url.pathname = "/login"; url.search = ""; return NextResponse.redirect(url); } if (isLoginRoute) { // Angemeldet und trotzdem auf /login: nur weiterschicken, wenn keine // Meldung ansteht. Sonst geriete jemand ohne HR-Freischaltung in eine // Schleife — das Layout leitet nach /login?error=no_hr_access, der Proxy // zurück auf /, das Layout wieder … und der Grund wäre nie zu lesen. if (request.nextUrl.searchParams.has("error")) return NextResponse.next(); const url = request.nextUrl.clone(); url.pathname = "/"; url.search = ""; return NextResponse.redirect(url); } return NextResponse.next(); }); // Zwei Eigenheiten auf einmal, beide erst beim Ausprobieren aufgefallen: // // 1. Next.js sucht hier eine *Funktionsdeklaration* namens `proxy` (oder // einen Default-Export) und erkennt `export const proxy = auth(…)` nicht. // Jede Anfrage lief in einen 404 — und `next build` meldete Erfolg und // listete den Proxy sogar auf. // // 2. In der Funktionsform liefert `auth(handler)` den Handler erst als // Zusage. Ohne `await` steht hier ein Promise, und der Aufruf scheitert // mit „gate is not a function". Auf einem gewöhnlichen Funktionswert ist // `await` wirkungslos, das `await` ist also in beiden Fällen richtig. export async function proxy(...args: Parameters>) { return (await gate)(...args); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], };