110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import { cache } from "react";
|
|
import { redirect } from "next/navigation";
|
|
import type { Session } from "next-auth";
|
|
import { UserRole } from "@prisma/client";
|
|
|
|
import { auth } from "@/auth";
|
|
|
|
// =====================================================================
|
|
// Auth-Utils für Server Components, Server Actions, Route Handlers
|
|
// ---------------------------------------------------------------------
|
|
// Wir kapseln die Session-Auflösung an einer Stelle, damit jede
|
|
// geschützte Route denselben Sicherheits-Pfad geht:
|
|
//
|
|
// 1. `getSession()` → optional, gibt `null` zurück
|
|
// 2. `requireSession()` → eingeloggt, sonst Redirect /login
|
|
// 3. `requireKitaSession()` → eingeloggt + kitaId vorhanden + Privacy-Consent
|
|
// 4. `requireRole()` → zusätzlich Rollen-Whitelist
|
|
//
|
|
// Mandanten-Isolation: NIEMALS `session.user.kitaId` ungeprüft an Prisma
|
|
// reichen. Über `requireKitaSession` ist garantiert, dass der Wert
|
|
// nicht-null ist und der Consent-Zeitstempel gesetzt wurde.
|
|
// =====================================================================
|
|
|
|
export type AuthenticatedSession = Session & {
|
|
user: NonNullable<Session["user"]>;
|
|
};
|
|
|
|
export type KitaSession = AuthenticatedSession & {
|
|
user: AuthenticatedSession["user"] & { kitaId: string };
|
|
};
|
|
|
|
const readRequestSession = cache(() => auth());
|
|
|
|
export async function getSession(): Promise<Session | null> {
|
|
return readRequestSession();
|
|
}
|
|
|
|
export async function requireSession(): Promise<AuthenticatedSession> {
|
|
const session = await getSession();
|
|
if (!session?.user?.id) {
|
|
redirect("/login");
|
|
}
|
|
return session as AuthenticatedSession;
|
|
}
|
|
|
|
/**
|
|
* Garantiert: eingeloggter User MIT Mandantenzuordnung UND akzeptierter
|
|
* Datenschutzerklärung. Genau diese Funktion ist der Single-Point-of-Truth
|
|
* für alle Tenant-gebundenen Server Actions / Page Components.
|
|
*/
|
|
export async function requireKitaSession(): Promise<KitaSession> {
|
|
return enforceKitaSession(await requireSession());
|
|
}
|
|
|
|
async function enforceKitaSession(session: AuthenticatedSession): Promise<KitaSession> {
|
|
|
|
if (!session.user.kitaId) {
|
|
// Superadmins haben keine Kita → eigene Oberfläche.
|
|
if (session.user.role === UserRole.SUPERADMIN) {
|
|
redirect("/admin");
|
|
}
|
|
// Frisch registrierte Gründer → in den Onboarding-Wizard.
|
|
redirect("/onboarding");
|
|
}
|
|
|
|
// DSGVO-Gate: Ohne Privacy-Consent → erst Consent einholen.
|
|
// Wir prüfen das hier zentral statt in jeder Route einzeln.
|
|
const consent = await consentCheck(session.user.id);
|
|
if (!consent) {
|
|
redirect("/onboarding/consent");
|
|
}
|
|
|
|
return session as KitaSession;
|
|
}
|
|
|
|
export async function requireRole(
|
|
allowed: UserRole[],
|
|
): Promise<AuthenticatedSession> {
|
|
const session = await requireSession();
|
|
if (!allowed.includes(session.user.role)) {
|
|
redirect("/forbidden");
|
|
}
|
|
if (session.user.role !== UserRole.SUPERADMIN) return enforceKitaSession(session);
|
|
return session;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// interne Helfer
|
|
// ---------------------------------------------------------------------
|
|
|
|
async function consentCheck(userId: string | undefined): Promise<boolean> {
|
|
if (!userId) return false;
|
|
|
|
// Lazy-Import, damit `auth-utils` selbst Edge-kompatibel bleibt
|
|
// (Prisma läuft nur in Node-Runtime).
|
|
const { prisma } = await import("@/lib/prisma");
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { privacyPolicyAcceptedAt: true },
|
|
});
|
|
return !!user?.privacyPolicyAcceptedAt;
|
|
}
|
|
|
|
// Route handlers use this non-redirecting counterpart of the same consent gate.
|
|
export async function getKitaSession(): Promise<KitaSession | null> {
|
|
const session = await getSession();
|
|
if (!session?.user?.id || !session.user.kitaId || !(await consentCheck(session.user.id))) return null;
|
|
return session as KitaSession;
|
|
}
|