Harden authentication, tenant isolation and security boundaries
This commit is contained in:
+17
-23
@@ -1,3 +1,4 @@
|
||||
import { cache } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import type { Session } from "next-auth";
|
||||
import { UserRole } from "@prisma/client";
|
||||
@@ -28,12 +29,14 @@ export type KitaSession = AuthenticatedSession & {
|
||||
user: AuthenticatedSession["user"] & { kitaId: string };
|
||||
};
|
||||
|
||||
const readRequestSession = cache(() => auth());
|
||||
|
||||
export async function getSession(): Promise<Session | null> {
|
||||
return auth();
|
||||
return readRequestSession();
|
||||
}
|
||||
|
||||
export async function requireSession(): Promise<AuthenticatedSession> {
|
||||
const session = await auth();
|
||||
const session = await getSession();
|
||||
if (!session?.user?.id) {
|
||||
redirect("/login");
|
||||
}
|
||||
@@ -46,17 +49,10 @@ export async function requireSession(): Promise<AuthenticatedSession> {
|
||||
* für alle Tenant-gebundenen Server Actions / Page Components.
|
||||
*/
|
||||
export async function requireKitaSession(): Promise<KitaSession> {
|
||||
if (process.env.TEST_MODE === "true") {
|
||||
return {
|
||||
user: {
|
||||
id: process.env.TEST_USER_ID || "test-user-id",
|
||||
role: (process.env.TEST_USER_ROLE as UserRole) || UserRole.ELTERN,
|
||||
kitaId: process.env.TEST_KITA_ID || "test-kita-id",
|
||||
familyId: process.env.TEST_FAMILY_ID || "test-family-id",
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
const session = await requireSession();
|
||||
return enforceKitaSession(await requireSession());
|
||||
}
|
||||
|
||||
async function enforceKitaSession(session: AuthenticatedSession): Promise<KitaSession> {
|
||||
|
||||
if (!session.user.kitaId) {
|
||||
// Superadmins haben keine Kita → eigene Oberfläche.
|
||||
@@ -80,20 +76,11 @@ export async function requireKitaSession(): Promise<KitaSession> {
|
||||
export async function requireRole(
|
||||
allowed: UserRole[],
|
||||
): Promise<AuthenticatedSession> {
|
||||
if (process.env.TEST_MODE === "true") {
|
||||
return {
|
||||
user: {
|
||||
id: process.env.TEST_USER_ID || "test-user-id",
|
||||
role: (process.env.TEST_USER_ROLE as UserRole) || UserRole.ADMIN,
|
||||
kitaId: process.env.TEST_KITA_ID || "test-kita-id",
|
||||
familyId: process.env.TEST_FAMILY_ID || "test-family-id",
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
const session = await requireSession();
|
||||
if (!allowed.includes(session.user.role)) {
|
||||
redirect("/forbidden");
|
||||
}
|
||||
if (session.user.role !== UserRole.SUPERADMIN) return enforceKitaSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -113,3 +100,10 @@ async function consentCheck(userId: string | undefined): Promise<boolean> {
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user