Harden authentication, tenant isolation and security boundaries

This commit is contained in:
t.indorf
2026-10-09 22:49:55 +02:00
parent ec3f4f933d
commit 656ada80bd
71 changed files with 3107 additions and 1200 deletions
+11 -10
View File
@@ -4,7 +4,7 @@ import { redirect } from "next/navigation";
import { Prisma, UserRole } from "@prisma/client";
import { z } from "zod";
import { auth } from "@/auth";
import { requireSession } from "@/lib/auth-utils";
import { prisma } from "@/lib/prisma";
// =====================================================================
@@ -23,9 +23,9 @@ const onboardingSchema = z
.trim()
.min(2, "Bitte mindestens 2 Zeichen angeben.")
.max(120, "Maximal 120 Zeichen."),
notdienstModuleEnabled: z.preprocess(checkboxToBool, z.boolean()),
terminModuleEnabled: z.preprocess(checkboxToBool, z.boolean()),
adressbuchModuleEnabled: z.preprocess(checkboxToBool, z.boolean()),
notdienstModuleEnabled: z.preprocess(checkboxToBool, z.boolean()).default(false),
terminModuleEnabled: z.preprocess(checkboxToBool, z.boolean()).default(false),
adressbuchModuleEnabled: z.preprocess(checkboxToBool, z.boolean()).default(false),
notdienstMinPerChildPerMonth: z.coerce
.number()
.int("Bitte eine ganze Zahl angeben.")
@@ -87,10 +87,10 @@ export async function completeOnboardingAction(
formData: FormData,
): Promise<OnboardingState> {
const attempt = prev.attempt + 1;
const session = await auth();
if (!session?.user) {
redirect("/login");
}
const session = await requireSession();
if (session.user.role !== UserRole.ADMIN) redirect("/forbidden");
const founder = await prisma.user.findUnique({ where: { id: session.user.id } });
if (!founder?.emailVerifiedAt || !founder.privacyPolicyAcceptedAt) redirect("/register");
if (session.user.kitaId) {
// Onboarding bereits abgeschlossen — verhindert doppeltes Einrichten
// (z.B. durch zurück-navigieren oder Replay des Formulars).
@@ -124,14 +124,15 @@ export async function completeOnboardingAction(
// tatsächlich noch keine Kita hat (verhindert Race-Conditions bei
// parallelen Onboarding-Submits).
const updated = await tx.user.updateMany({
where: { id: session.user.id, kitaId: null },
where: { id: session.user.id, kitaId: null, role: UserRole.ADMIN, disabledAt: null,
sessionVersion: session.user.sessionVersion, emailVerifiedAt: { not: null }, privacyPolicyAcceptedAt: { not: null } },
data: {
kitaId: kita.id,
role: UserRole.ADMIN,
},
});
if (updated.count === 0) {
if (updated.count !== 1) {
throw new Error("USER_ALREADY_HAS_KITA");
}
});