Zwei vom Nacht-Agenten gefundene Lücken, gegen den echten Code
verifiziert und behoben:
1. Das Ergebnis von GET_LOCK() wurde nie geprüft. Bei Timeout (10s)
oder Fehler lief audit() trotzdem ungesperrt weiter – zwei
gleichzeitige Aufrufe hätten dieselbe "letzte Zeile" lesen und
beide anhängen können, was die Kettengarantie bricht. Wirft jetzt
einen Fehler, wenn die Sperre nicht erlangt wurde.
2. connector und ip wurden zwar in audit_events gespeichert, aber nie
mitgehasht – beide Felder ließen sich im Nachhinein unbemerkt
ändern, ohne die Kette zu brechen. Neue Einträge (hash_version = 2,
Migration 030) hashen sie jetzt mit. Bestehende Einträge (Version 1)
bleiben mit ihrer ursprünglichen Formel gültig; verifyAuditChain()
berücksichtigt die Version pro Zeile.
Gegen die echte Datenbank verifiziert: Kette bleibt über alle 183
bestehenden (v1) Einträge sauber, ein neu eingefügter (v2) Eintrag mit
connector/ip ebenfalls – 184/184 ohne Bruch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nutzerfrage: warum gibt es neben der Kontoverknüpfung noch eine
separate "Erlaubte Nutzer-IDs"-Liste, und woher weiß der Bot, ob
jemand Kunde oder Personal ist? Antwort: das waren zwei parallele,
verwirrende Mechanismen. /kc-status und /kc-kunde prüften bisher eine
manuell gepflegte Discord-ID-Liste (aus der Zeit vor der OAuth-
Verknüpfung), während /zuweisen und /schliessen bereits das verknüpfte
Personal-Konto (users.discord_user_id, kind='staff') prüften.
Jetzt einheitlich: alle vier Personal-Befehle verlangen ein verknüpftes
Personal-Konto, genau wie /zuweisen und /schliessen. Die Liste entfällt
vollständig (Migration 029, Spalte staff_user_ids entfernt) – wer ein
Personal-Konto im Kundencenter hat und es unter "Mein Konto" mit
Discord verknüpft, darf automatisch alle vier Befehle nutzen, ohne
zusätzliche manuelle Pflege einer ID-Liste.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Mailvorlage "welcome" (Migration 028), von Personal gezielt über
einen Button auf der Kundenseite ausgelöst ("Begrüßungsmail senden"),
statt automatisch bei der Kundenanlage – sinnvoll ist sie erst, sobald
Produkte zugeordnet sind.
Enthält: Begrüßung, Kundennummer, eine Klartext-Aufstellung der
aktuellen aktiven Produkte mit Preisen (Privatkunden brutto,
Geschäftskunden netto+brutto, wie im Rest der Oberfläche), eine kurze
Anleitung (Ressourcen/Tickets/Rechnungen) und einen Hinweis zur
Discord-Verknüpfung unter Mein Konto. Zugangslink ist je nach Status
entweder ein frischer Einladungslink (noch nicht aktiviert) oder der
normale Login-Link (bereits aktiv).
Außerdem: renderTemplateHtml verlinkt jetzt auch weitere nackte URLs
im Text automatisch (nicht nur die Haupt-Link-Variable), damit z. B.
der Discord-Hinweis in der HTML-Ansicht klickbar ist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Erster Teil des größeren Discord-Ausbaus (Konto verknüpfen, Tickets
über Discord sehen/beantworten). Dieser Schritt: Kunden können unter
"Mein Konto" ihr Discord-Konto per OAuth verknüpfen/trennen.
- Migration 027: discord_settings um client_id/client_secret_enc/
ticket_channel_id erweitert; users um discord_user_id/
discord_username (unique); discord_oauth_states (kurzlebiger
CSRF-Schutz für den Redirect-Flow, 10 Min. gültig); neue Tabelle
ticket_discord_threads für den nächsten Schritt (Ticket-Threads).
- Neue Routen: GET/POST /discord/link, /discord/unlink,
GET /discord/oauth/start (leitet zu Discord weiter),
GET /discord/oauth/callback (tauscht Code gegen Discord-Nutzer-ID,
verknüpft das Konto, verhindert doppelte Verknüpfung).
- Einstellungen > Discord: neue Felder für Client ID/Secret, Anzeige
der einzutragenden Redirect-URI, aktualisierte Anleitung inkl.
OAuth-Einrichtung und Hinweis auf die Message Content Intent (für
den nächsten Schritt: Ticket-Threads).
Noch offen (nächster Schritt): Ticket-Threads je Ticket, bidirektionale
Synchronisierung der Nachrichten.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bisher kam die Discord-Konfiguration (Token, Server-ID, Kanal-ID,
erlaubte Nutzer-IDs) ausschließlich aus Umgebungsvariablen. Jetzt
unter Einstellungen > Discord einstellbar, inkl. Schritt-für-Schritt-
Anleitung zum Einrichten einer Discord-Anwendung/eines Bots (Token
erzeugen, einladen, IDs ermitteln).
- Migration 026: discord_settings (Token verschlüsselt, wie SMTP/IMAP).
- apps/worker/src/discord.ts liest die Einstellungen jetzt aus der DB
statt aus process.env; syncDiscord() läuft periodisch (60s) und
verbindet automatisch neu, wenn sich Token/Server/Aktivierung
geändert haben (Trennung von Verbindungs- und Laufzeitdaten: Kanal-ID
und erlaubte Nutzer werden bei jeder Nutzung frisch aus der DB
gelesen, ohne Neuverbindung).
- Neues API-Modul apps/api/src/modules/discord: Einstellungen lesen/
schreiben, Token- und Testnachricht-Prüfung direkt über die Discord-
REST-API (unabhängig vom laufenden Bot-Prozess im Worker).
- Rechte discord.read (Admin+) / discord.write (Superadmin), wie beim
E-Mail-Modul.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mails wurden bisher nur als Klartext verschickt. sendMail/sendTemplateMail
verschicken jetzt zusätzlich eine einfache, mandantenfarbige HTML-Version
(renderTemplateHtml in @kc/platform/mail): ein Platzhalter, dessen Name auf
"Link" endet, wird darin zu einer Schaltfläche statt einer nackten URL; der
Klartext-Teil behält den Link weiterhin sichtbar (Fallback für Mailprogramme
ohne HTML-Ansicht).
ticket_created/ticket_message bekommen dafür eine neue Variable
{{ticketLink}}, invoice_issued {{invoiceLink}} (Migration 025 – nur
Vorlagen mit noch unverändertem Text werden angepasst, damit von Personal
bereits bearbeitete Texte nicht überschrieben werden).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neues Postfach lässt sich unter Einstellungen > E-Mail > IMAP-Posteingang
einrichten (Host/Port/Verschlüsselung/Zugangsdaten/Ordner, An/Aus,
Verbindungstest). Der Worker ruft es alle 2 Minuten ab (mailbox.ts) und
ordnet eingehende Mails zu:
1. Betreff enthält eine Ticketnummer (T-123) UND Absender gehört
nachweislich zum Kunden dieses Tickets -> wird als Kundennachricht
angehängt (Status pending_staff, Discord-Benachrichtigung).
2. Sonst, wenn der Absender ein bekannter aktiver Kunde ist -> neues
Ticket wird automatisch unter seinem Kundenkonto angelegt.
3. Sonst (unbekannter/nicht verifizierbarer Absender) -> landet im
neuen Posteingang (/admin/inbox, UI unter /admin/posteingang) zur
manuellen Zuordnung: Personal wählt einen Kunden (legt Ticket an),
hängt die Mail an ein bestehendes Ticket, oder ignoriert sie.
Beim ersten Aktivieren wird kein Mailbox-Bestand verarbeitet, nur ab
dann neu Eingehendes (last_uid wird auf den aktuellen Stand gesetzt).
Migration 024: imap_settings (Verbindung, verschlüsseltes Passwort,
last_uid/last_error), inbox_messages (Warteschlange). Rechte:
email.read/write für die IMAP-Verbindung (wie SMTP), tickets.read/write
für den Posteingang (Zuordnung ist eine Ticket-Aktion, daher im
Tickets-Modul statt eines eigenen Moduls, um keine Modulgrenzen zu
verletzen).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bisher gab es nur einen globalen SMTP-Absender für alle Mailvorlagen.
Jetzt kann je Bereich ein eigener Absendername/-adresse hinterlegt
werden (z. B. support@… für Tickets, rechnung@… für Rechnungen);
leer gelassene Felder fallen weiter auf den globalen SMTP-Absender
zurück. Jede Vorlage ist fest einem Bereich zugeordnet (Migration
023: mail_templates.category, neue Tabelle mail_sender_profiles mit
den drei Zeilen kundencenter/stoerungsticket/rechnungen).
Außerdem: der Testmail-Versand (SMTP-Verbindung wie auch je Vorlage)
zeigt jetzt den konkreten Fehler aus dem Mail-Protokoll an (z. B.
"535 5.7.8 authentication failed") statt nur "Fehlgeschlagen".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bisher gab es nur zwei fest im Code verdrahtete E-Mails (Einladung,
Passwort-Reset) und die SMTP-Verbindung kam ausschließlich aus
Umgebungsvariablen. Jetzt:
- SMTP-Verbindung unter Einstellungen > E-Mail konfigurierbar (Host,
Port, Verschlüsselung, Zugangsdaten verschlüsselt gespeichert,
Absender), mit Testmail-Versand. Die Mail-Logik wandert dafür nach
@kc/platform/mail, damit API und Worker sie gemeinsam nutzen.
- Mailvorlagen-Verwaltung: jedes Ereignis hat eine feste "Definition"
(Name, Auslöser, verfügbare Platzhalter/Daten), Betreff/Text sind
editierbar, je Vorlage einzeln aktivierbar, mit Testversand anhand
von Beispieldaten. Unbekannte Platzhalter werden beim Speichern
abgelehnt.
- Neue Ereignisse verdrahtet: Ticket erstellt (Bestätigung an Kunde),
Personal antwortet auf Ticket (Benachrichtigung an Kunde), Rechnung
ausgestellt, sowie die zuvor zurückgestellte Funktion "Panel-
Zugangsdaten per E-Mail senden" nach einem Passwort-Reset. Diese
laufen über die Jobqueue (mail.template), analog zu discord.notify.
- Versandprotokoll (letzte 100 Versuche, ohne Inhalte) einsehbar unter
Einstellungen > E-Mail.
- Migration 022, Rechte email.read (Admin+) / email.write (Superadmin).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>