fix(admin): Kategorie und Slug beim Produkt-Anlegen serverseitig pruefen

Die Kategorie kam bisher nur per Typ-Cast aus dem Formular; ein
manipulierter Wert endete als unbehandelte Prisma-Ausnahme. Ein eigener
Slug wurde ungeprueft zur oeffentlichen Adresse, inklusive Schraegstrichen
und Sonderzeichen. Neues adminProductIdentitySchema: Kategorie per Enum,
Slug auf a-z, 0-9 und Bindestrich (Umlaute umschrieben, max. 80 Zeichen),
leere Adresse wird mit Meldung abgelehnt.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 20:43:54 +02:00
1 parent c125e91941
commit 95b4b5953d
3 files changed
+68 -4

No files matched your search

+10 -3
View File
@@ -4,7 +4,7 @@ import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { logAdminAction } from "@/lib/audit-log";
import { notifyStockAvailable } from "@/lib/stock-notifications";
import { adminProductNumbersSchema } from "@/lib/validation";
import { adminProductIdentitySchema, adminProductNumbersSchema } from "@/lib/validation";
import { isValidUploadPath } from "@/lib/uploads";
import { requireAdmin, fail, str, strOrNull, strArray } from "./_shared";
import { succeed } from "@/lib/flash";
@@ -29,7 +29,15 @@ async function assertPricedIfActive(redirectPath: string, isActive: boolean, pri
export async function createProductAction(formData: FormData): Promise<void> {
const admin = await requireAdmin();
const slug = str(formData, "slug") || str(formData, "name").toLowerCase().replace(/[^a-z0-9]+/g, "-");
const parsedIdentity = adminProductIdentitySchema.safeParse({
name: str(formData, "name"),
slug: str(formData, "slug"),
category: str(formData, "category") || "HONIG",
});
if (!parsedIdentity.success) {
return fail("/admin/produkte/neu", parsedIdentity.error.issues[0]?.message ?? "Ungültige Eingabe.");
}
const { slug, category } = parsedIdentity.data;
const parsedNumbers = adminProductNumbersSchema.safeParse({
intensityMild: str(formData, "intensityMild"),
@@ -49,7 +57,6 @@ export async function createProductAction(formData: FormData): Promise<void> {
const isActive = formData.get("isActive") === "on";
await assertPricedIfActive("/admin/produkte/neu", isActive, numbers.priceCents);
const category = (str(formData, "category") || "HONIG") as "HONIG" | "KERZEN" | "PROPOLIS";
// Nur Honig ist einer Ernte/Charge zugeordnet — Kerzen, Propolis & Co. sind
// davon unabhaengig. Eingaben aus dem Formular werden fuer diese Kategorien
// ignoriert statt sich auf eine korrekt ausgefuellte Auswahl zu verlassen.