import { z } from "@/lib/validierung"; import { ANLAESSE, webhookUrlPruefen, type AnlassArt } from "./regeln"; /** * Form der Benachrichtigungs-Einstellungen (#028). Gespeichert als JSON in `Einstellung` * unter EINSTELLUNG_BENACHRICHTIGUNG; Passwort und Webhook-Geheimnis liegen dort nur * verschluesselt (`verschluesseln()` mit SSHKV_APP_SECRET). */ export const EINSTELLUNG_BENACHRICHTIGUNG = "benachrichtigungen"; /** STARTTLS ist der Weg von Microsoft 365 (Port 587); KEINE nur fuer interne Relays. */ export const SMTP_SICHERHEIT = ["STARTTLS", "TLS", "KEINE"] as const; export type SmtpSicherheit = (typeof SMTP_SICHERHEIT)[number]; export type GespeicherteBenachrichtigung = { email: { aktiv: boolean; server: string; port: number; sicherheit: SmtpSicherheit; absender: string; benutzer: string; /** Chiffrat aus `verschluesseln()` oder null. */ passwort: string | null; empfaenger: string[]; }; webhook: { aktiv: boolean; url: string; httpErlaubt: boolean; /** Chiffrat aus `verschluesseln()` oder null. */ geheimnis: string | null; }; anlaesse: Record; }; /** Vorbelegung fuer Microsoft 365 – alles aus, bis ein Admin es einschaltet. */ export const STANDARD_BENACHRICHTIGUNG: GespeicherteBenachrichtigung = { email: { aktiv: false, server: "smtp.office365.com", port: 587, sicherheit: "STARTTLS", absender: "", benutzer: "", passwort: null, empfaenger: [], }, webhook: { aktiv: false, url: "", httpErlaubt: false, geheimnis: null }, anlaesse: { drift: true, ablauf: true, abgelaufen: true, unerreichbar: true, hostkey: true }, }; /** * Was die Oberflaeche sehen darf: statt der Geheimnisse nur, ob sie gesetzt sind. Auch das * Chiffrat geht nicht in Client-Props – es waere mit SSHKV_APP_SECRET lesbar. */ export type BenachrichtigungAnsicht = { email: Omit & { passwortGesetzt: boolean }; webhook: Omit & { geheimnisGesetzt: boolean }; anlaesse: Record; }; export function alsAnsicht(gespeichert: GespeicherteBenachrichtigung): BenachrichtigungAnsicht { const { passwort, ...email } = gespeichert.email; const { geheimnis, ...webhook } = gespeichert.webhook; return { email: { ...email, passwortGesetzt: passwort !== null }, webhook: { ...webhook, geheimnisGesetzt: geheimnis !== null }, anlaesse: { ...gespeichert.anlaesse }, }; } /** Liest gespeichertes JSON tolerant: fehlende Felder bekommen die Vorbelegung. */ export function gespeichertLesen(wert: unknown): GespeicherteBenachrichtigung { const roh = ( typeof wert === "object" && wert !== null ? wert : {} ) as Partial; const s = STANDARD_BENACHRICHTIGUNG; return { email: { ...s.email, ...(roh.email ?? {}) }, webhook: { ...s.webhook, ...(roh.webhook ?? {}) }, anlaesse: { ...s.anlaesse, ...(roh.anlaesse ?? {}) }, }; } // ---------------------------------------------------------------- Formular /** Empfaengerliste: Komma, Semikolon, Leerzeichen oder Zeilenumbruch trennen. */ export function empfaengerAus(text: string): string[] { return [ ...new Set( text .split(/[\s,;]+/) .map((e) => e.trim()) .filter(Boolean), ), ]; } const email = z.email({ error: "Keine gültige E-Mail-Adresse." }); export const benachrichtigungSchema = z .object({ emailAktiv: z.boolean(), emailServer: z.string().trim().max(253), emailPort: z.coerce.number().int().min(1, "Port 1–65535.").max(65535, "Port 1–65535."), emailSicherheit: z.enum(SMTP_SICHERHEIT), emailAbsender: z.string().trim().max(254), emailBenutzer: z.string().trim().max(254), /** Leer = unveraendert lassen. */ emailPasswort: z.string().max(1024).optional(), emailPasswortEntfernen: z.boolean(), emailEmpfaenger: z.array(z.string()).max(50, "Höchstens 50 Empfänger."), webhookAktiv: z.boolean(), webhookUrl: z.string().trim().max(2048), webhookHttpErlaubt: z.boolean(), webhookGeheimnis: z.string().max(1024).optional(), webhookGeheimnisEntfernen: z.boolean(), anlaesse: z.array(z.enum(ANLAESSE)), }) .superRefine((e, ctx) => { const fehler = (pfad: string, message: string) => ctx.addIssue({ code: "custom", path: [pfad], message }); if (e.emailAktiv && !e.emailServer) fehler("emailServer", "Server angeben."); if (e.emailServer && !/^[A-Za-z0-9.-]+$/.test(e.emailServer)) fehler("emailServer", "Nur Hostname oder IP."); if (e.emailAktiv && !e.emailAbsender) fehler("emailAbsender", "Absender angeben."); if (e.emailAbsender && !email.safeParse(e.emailAbsender).success) fehler("emailAbsender", "Keine gültige E-Mail-Adresse."); if (e.emailAktiv && e.emailEmpfaenger.length === 0) fehler("emailEmpfaenger", "Mindestens ein Empfänger."); const falsch = e.emailEmpfaenger.find((a) => !email.safeParse(a).success); if (falsch) fehler("emailEmpfaenger", `„${falsch.slice(0, 80)}“ ist keine gültige E-Mail-Adresse.`); if (e.webhookAktiv && !e.webhookUrl) fehler("webhookUrl", "Adresse angeben."); if (e.webhookUrl) { const meldung = webhookUrlPruefen(e.webhookUrl, e.webhookHttpErlaubt); if (meldung) fehler("webhookUrl", meldung); } }); export type BenachrichtigungEingabe = z.input; export type BenachrichtigungDaten = z.output;