Files
sshkeyvault/src/server/actions/einstellungen.ts
T
florian.wildeandClaude Opus 5.5 8f5b31c0cb feat(benachrichtigung): E-Mail und Webhook bei Drift und Ablauf (#028)
Drift, ablaufende und abgelaufene Schluessel, unerreichbare Hosts und
geaenderte Host-Keys sollen auffallen, ohne dass jemand taeglich in die
Oberflaeche schaut. Versand per SMTP (Microsoft 365, STARTTLS) und per
Webhook mit HMAC-Signatur, eingestellt unter /einstellungen.

Die Anlaesse liest der Worker aus der Datenbank, statt sie im Abgleich
auszuloesen: der Abgleich-Ablauf bleibt unberuehrt, und ein verpasster
Lauf holt sich der naechste. Ein Versandprotokoll (Zustellung) mit
Anlass-Fingerabdruck verhindert Doppelmeldungen und haelt Fehlschlaege
fuer begrenzte Wiederholungen fest. Taeglich eine Zusammenfassung,
geaenderte Host-Keys im Fuenf-Minuten-Takt sofort.

Versand und Testnachricht laufen im Worker, damit der Webprozess keinen
Weg zu Mailserver oder Webhook-Ziel braucht. Passwort und Geheimnis
liegen nur verschluesselt in der Datenbank; die Seite zeigt "gesetzt".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:43 +02:00

67 lines
2.7 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { empfaengerAus, type BenachrichtigungEingabe } from "@/lib/benachrichtigung/einstellungen";
import { ANLAESSE, type AnlassArt } from "@/lib/benachrichtigung/regeln";
import {
akteurVon,
fehlerZustand,
listenfeld,
textfeld,
type FormularZustand,
} from "@/server/actions/formular";
import { RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
import { benachrichtigungSpeichern, testAnfordern } from "@/server/services/benachrichtigung";
import { FachFehler } from "@/server/services/fachfehler";
/** Einstellungen speichern und Testnachrichten anstossen (#028). Nur `einstellungenVerwalten`. */
function felder(formular: FormData): BenachrichtigungEingabe {
const ja = (name: string) => formular.get(name) === "ja";
return {
emailAktiv: ja("emailAktiv"),
emailServer: textfeld(formular, "emailServer") ?? "",
emailPort: textfeld(formular, "emailPort") ?? "",
emailSicherheit: (textfeld(formular, "emailSicherheit") ??
"STARTTLS") as BenachrichtigungEingabe["emailSicherheit"],
emailAbsender: textfeld(formular, "emailAbsender") ?? "",
emailBenutzer: textfeld(formular, "emailBenutzer") ?? "",
emailPasswort: textfeld(formular, "emailPasswort"),
emailPasswortEntfernen: ja("emailPasswortEntfernen"),
emailEmpfaenger: empfaengerAus(textfeld(formular, "emailEmpfaenger") ?? ""),
webhookAktiv: ja("webhookAktiv"),
webhookUrl: textfeld(formular, "webhookUrl") ?? "",
webhookHttpErlaubt: ja("webhookHttpErlaubt"),
webhookGeheimnis: textfeld(formular, "webhookGeheimnis"),
webhookGeheimnisEntfernen: ja("webhookGeheimnisEntfernen"),
anlaesse: listenfeld(formular, "anlaesse").filter((a): a is AnlassArt =>
(ANLAESSE as readonly string[]).includes(a),
),
};
}
export async function benachrichtigungSpeichernAktion(
_vorher: FormularZustand,
formular: FormData,
): Promise<FormularZustand & { gespeichert?: boolean }> {
try {
const { benutzer } = await rechtErforderlich("einstellungenVerwalten");
await benachrichtigungSpeichern(felder(formular), akteurVon(benutzer));
} catch (fehler) {
return fehlerZustand(fehler);
}
revalidatePath("/einstellungen");
return { gespeichert: true };
}
export async function testSendenAktion(kanal: string): Promise<{ vorgangId?: string; fehler?: string }> {
try {
const { benutzer } = await rechtErforderlich("einstellungenVerwalten");
if (kanal !== "EMAIL" && kanal !== "WEBHOOK") return { fehler: "Unbekannter Kanal." };
return { vorgangId: await testAnfordern(kanal, benutzer.id) };
} catch (fehler) {
if (fehler instanceof RechteFehler || fehler instanceof FachFehler) return { fehler: fehler.message };
throw fehler;
}
}