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
+7 -2
View File
@@ -5,11 +5,13 @@ import { revalidatePath } from "next/cache";
import { DutyAssignmentStatus, UserRole } from "@prisma/client";
import { endOfWeek, format, startOfDay, startOfWeek } from "date-fns";
import { de } from "date-fns/locale";
import { dateOnlySchema } from "@/lib/date-schema";
import { z } from "zod";
import { DutyReminderEmail } from "@/emails/DutyReminderEmail";
import { requireRole } from "@/lib/auth-utils";
import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail";
import { rateLimit } from "@/lib/rate-limit";
import { prisma } from "@/lib/prisma";
const dutyTypeSchema = z.object({
@@ -20,8 +22,8 @@ const dutyTypeSchema = z.object({
const assignmentSchema = z.object({
dutyTypeId: z.string().min(1),
familyId: z.string().nullable(),
startDate: z.string().min(1),
endDate: z.string().min(1),
startDate: dateOnlySchema,
endDate: dateOnlySchema,
});
async function requireDutyAdmin() {
@@ -160,6 +162,7 @@ export async function upsertDutyAssignment(rawPayload: unknown) {
export async function checkAndSendDutyReminders() {
const { kitaId } = await requireDutyAdmin();
if (!(await rateLimit(`duty-reminders:${kitaId}`, 3, 3600)).allowed) return { error: "Bitte versuche es später erneut." };
const configError = getAppEmailConfigError();
if (configError) {
@@ -195,6 +198,8 @@ export async function checkAndSendDutyReminders() {
users: {
where: {
emailVerifiedAt: { not: null },
disabledAt: null,
privacyPolicyAcceptedAt: { not: null },
},
select: {
email: true,
+1 -1
View File
@@ -93,7 +93,7 @@ export function NewsForm() {
/>
<p className="mt-2 flex items-center gap-1 text-xs text-[var(--ink-muted)]">
<UploadCloud className="h-3.5 w-3.5 [stroke-width:1.7]" />
PDF, JPG und PNG bis 10 MB pro Datei.
PDF, JPG und PNG: maximal 5 Dateien, je 10 MB und insgesamt 25 MB.
</p>
</div>
</div>