Drei echte Luecken gefunden und geschlossen: 1. Die 27 Server Actions verliessen sich allein auf proxy.ts. Jede prueft die Sitzung jetzt selbst und schlaegt gegen die Datenbank nach, ob das Konto noch gilt - damit lassen sich Sitzungen ueberhaupt erst widerrufen. 2. Der Anmelde-Endpunkt prueft die Herkunft des Formulars; vorher war Login-CSRF moeglich. 3. Content-Security-Policy mit Nonce je Anfrage und strict-dynamic. Dazu: npm audit ist ohne Befund (mysql2 und deepmerge-ts per overrides angehoben statt Prisma zurueckzustufen), eine Benutzerverwaltung fuer die Kommandozeile und docs/sicherheit.md mit allem, was bewusst offen bleibt - keine Rollen, keine Zwei-Faktor-Anmeldung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { prisma } from "@/lib/db";
|
|
import { protokolliere } from "@/server/audit";
|
|
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
|
|
import type { AktionsErgebnis } from "./kunden";
|
|
|
|
/** Speichert Notizen und Tags eines Hosts. */
|
|
export async function hostNotizenSpeichern(
|
|
_vorher: AktionsErgebnis,
|
|
formular: FormData,
|
|
): Promise<AktionsErgebnis> {
|
|
await sitzungErforderlich();
|
|
const hostId = formular.get("hostId")?.toString();
|
|
if (!hostId) return { fehler: "Host nicht gefunden." };
|
|
|
|
const notizen = formular.get("notizen")?.toString().trim() || null;
|
|
const tags = (formular.get("tags")?.toString() ?? "")
|
|
.split(",")
|
|
.map((tag) => tag.trim().toLowerCase())
|
|
.filter(Boolean)
|
|
.slice(0, 12);
|
|
|
|
await prisma.host.update({
|
|
where: { id: hostId },
|
|
data: {
|
|
notizen,
|
|
tags: {
|
|
set: [],
|
|
connectOrCreate: tags.map((name) => ({ where: { name }, create: { name } })),
|
|
},
|
|
},
|
|
});
|
|
|
|
await protokolliere("host.notizen_geaendert", "Host", hostId, { notizen, tags });
|
|
revalidatePath(`/hosts/${hostId}`);
|
|
|
|
return { erfolg: "Gespeichert." };
|
|
}
|
|
|
|
/** Ordnet einen einzelnen Host einem Kunden zu (aus der Detailansicht heraus). */
|
|
export async function hostKundeSetzen(formular: FormData) {
|
|
await sitzungErforderlich();
|
|
const hostId = formular.get("hostId")?.toString();
|
|
const kundeId = formular.get("kundeId")?.toString() || null;
|
|
const standortId = formular.get("standortId")?.toString() || null;
|
|
if (!hostId) return;
|
|
|
|
await prisma.host.update({ where: { id: hostId }, data: { kundeId, standortId } });
|
|
await protokolliere("host.zugewiesen", "Host", hostId, { kundeId, standortId });
|
|
|
|
if (kundeId) {
|
|
await prisma.alert.updateMany({
|
|
where: { regel: "host-ohne-kunde", hostId, erledigtAm: null },
|
|
data: { erledigtAm: new Date() },
|
|
});
|
|
}
|
|
|
|
revalidatePath(`/hosts/${hostId}`);
|
|
revalidatePath("/hosts");
|
|
revalidatePath("/hosts/nicht-zugeordnet");
|
|
revalidatePath("/");
|
|
}
|