opencrm/backend/todo.md

16 KiB
Raw Blame History

📋 OpenCRM Todo-Liste


🔜 Offen

Manuelle Tests (vor Release durchklicken)

Checklisten für Security + Email-Log-System stehen in docs/TESTING.md. Einmal komplett durchlaufen vor v1.0.0-Release.

🚀 SaaS-Ausbau: Instance-per-Customer + Admin-Portal + GoCardless

Vision: OpenCRM als SaaS anbieten. Jeder Kunde bekommt seine eigene isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung über ein zentrales Admin-Portal.

Architektur-Entscheidung: Weg C (Instance-per-Customer)

  • Pro Kunde eine eigene Docker-Instanz mit eigener DB
  • Keine tenantId im CRM-Code → keine Security-Risiken durch vergessene Filter
  • Komplette Datenisolation (DSGVO-freundlich)
  • Updates können gestaffelt ausgerollt werden (erst 10% testen)
  • Bei Kündigung: Docker-Image + DB-Export als "Mitnehm-Paket"

Bewusst NICHT dabei: eigener Mailserver. Stattdessen Plesk-Integration (die wir schon haben) Kunde bekommt Mail-Zugang über unseren Plesk bei Bedarf.


Admin-Portal (separate App, neben den CRM-Instanzen):

  • Kundenverwaltung: wer hat welchen Plan, Status (Trial/Active/Suspended/Cancelled)
  • "Neuen Kunden anlegen" → Provisioning-Script
    • DB anlegen (Master-DB kennt die Mapping)
    • Docker-Container starten
    • Subdomain konfigurieren (kundenname.deincrm.de via Caddy/Traefik)
    • Initial-Admin-Account erstellen + Einladungs-Email senden
    • Optional: Factory-Defaults für Stammdaten einspielen
  • GoCardless-Integration (Webhook + Dashboard)
  • Instanz-Management: Pause/Resume bei Zahlungsproblemen
  • Logs & Metriken pro Instanz (optional)
  • Support-Bereich (Tickets? oder einfach E-Mail)

Abrechnung mit GoCardless (gocardless.com):

  • Zahlungsmethoden: SEPA-Lastschrift (Hauptfokus) + Kreditkarte (über GoCardless Embedded/Success)
  • 30 Tage kostenlose Testphase ohne Zahlungsmittel
  • Nach Trial: Mandats-Erfassung → regelmäßige Abbuchung
  • Mehrere Pläne (z.B. Basic / Pro / Enterprise) mit unterschiedlichen Features
  • Webhook-Endpoint im Admin-Portal:
    • payment_confirmed → Instanz aktiv lassen
    • payment_failed → Banner im CRM, nach X Tagen pausieren
    • mandate_cancelled → Kündigungs-Flow
  • Rechnungsstellung: GoCardless liefert Zahlungsbelege, aber echte Rechnungen (mit USt-ID, Rechnungsnummer etc.) müssen wir selbst generieren (evtl. über das existierende PDF-Template-System aus dem CRM nutzen)

Provisioning-Flow (grober Entwurf):

  1. Kunde registriert sich auf Landing Page (Name, Firma, E-Mail, Wunsch-Subdomain)
  2. Admin-Portal: Trial-Instanz starten
    • DB erstellen, Docker-Container hochfahren, Caddy-Config für Subdomain
    • Einladungs-Email mit Admin-Login + Passwort-Reset-Link
  3. Tag 25: Erinnerungs-Email "Deine Trial läuft bald ab"
  4. Tag 30: Banner im CRM "Jetzt bezahlen oder pausieren"
  5. Kunde erfasst GoCardless-Mandat im Admin-Portal-Login
  6. Bei erfolgreicher Zahlung: Instanz bleibt aktiv
  7. Bei fehlender Zahlung nach 7 Tagen: Instanz pausiert (DB bleibt, UI zeigt Hinweis)

Technische Bausteine für später:

  • Master-DB mit Tenant-Tabelle (Name, Subdomain, DB-Name, Plan, Status, GoCardlessIDs)
  • Caddy oder Traefik als Reverse-Proxy mit Auto-SSL (Let's Encrypt)
  • Docker-Orchestrierung: einzelne docker-compose.yml pro Kunde oder Docker-Swarm/K8s
  • Backup-Strategie: pro Tenant separate Backups + zentrale Master-DB-Backups
  • Monitoring: ein Fail macht nicht alle down, aber wir müssen es mitbekommen
  • Logs zentral: z.B. Loki + Grafana für aggregierte Logs aller Instanzen

Grobe Zeitschätzung:

  • Admin-Portal (MVP): ~1 Woche
  • GoCardless-Integration + Webhooks: ~3-5 Tage
  • Provisioning-Automatisierung (Docker + Caddy): ~1 Woche
  • Landing Page + Checkout: ~3-5 Tage
  • Tests + Polishing: ~1 Woche
  • Gesamt: ~3-4 Wochen

Vorbereitung JETZT (einfach, macht später Arbeit leichter):

  • Factory-Defaults System (schon erledigt, hilft beim Provisioning)
  • Domain/Label dynamisch per Provider (schon erledigt)
  • Docker-Compose aufräumen, Env-Variablen dokumentieren (klein, ein Tag)
  • Backup-Script robust + wiederherstellbar (haben wir schon weitgehend)

Erledigt

  • 🔄 Automatische Vertrags-Status-Übergänge

    • Nightly-Cron (02:00 + Catch-up 60s nach Start): alle Verträge mit status=ACTIVE und endDate < heuteEXPIRED (mit Audit-Log).
    • Beim Upload der Kündigungsbestätigung (cancellationConfirmationPath): wenn Vertrag aktuell ACTIVE → auf CANCELLED setzen (Audit-Log). Frontend fragt per Modal das Bestätigungs-Datum ab (Default: heute), wird direkt als cancellationConfirmationDate gespeichert. Der "Optionen"-Upload löst den Status-Wechsel bewusst NICHT aus, da er für Vertragsänderungen (nicht echte Kündigungen) gedacht ist, setzt aber cancellationConfirmationOptionsDate analog.
    • Beim Upload einer Lieferbestätigung (ContractDocument via direkt-Upload oder Email-Anhang-Import): wenn Vertrag aktuell DRAFT → auf ACTIVE setzen + startDate auf das erfasste Lieferdatum (falls leer). Frontend zeigt Datums-Input conditional, wenn Typ "Lieferbestätigung" ausgewählt ist.
    • Keine neuen Status eingeführt: cancellationSentDate vs. cancellationConfirmationDate genügen, um "gesendet vs. bestätigt" abzubilden. ACTIVE bleibt bis zur Bestätigung.
  • 🛡️ Security-Review + Hardening vor Production-Deployment (3 Runden)

    • Vollständiger Review aller kritischen Bereiche, dokumentiert in docs/SECURITY-REVIEW.md

    • Runde 1 6 kritische + 2 wichtige Findings gefixt:

      • CORS offen → CORS_ORIGINS explizit
      • Helmet + Security-Headers
      • JWT-Fallback-Secret entfernt (Fail-Fast beim Start)
      • IDOR bei 7 Contract-Endpoints
      • XSS via Email-Body (DOMPurify)
      • Customer-API Data Exposure (Passwort-Hashes)
      • Portal-JWT-Invalidation nach Passwort-Reset
      • Body-Size-Limit 5 MB
    • Runde 2 Deep-Dive mit parallelen Audit-Agents, 5 weitere kritische + 2 wichtige:

      • Zip-Slip im Backup-Upload (Arbitrary File Write!)
      • Mass Assignment bei Customer/User (Privilege Escalation via roleIds!)
      • 13 weitere IDOR-Stellen (Meter-Readings, Email-Anhänge, StressfreiEmail-Credentials …)
      • Path-Traversal bei Backup-Name und GDPR-Proof-Download
    • Runde 3 Tiefer Dive (8 weitere Hardenings):

      • JWT algorithm confusion: jwt.verify auf algorithms: ['HS256'] festgenagelt
      • trust proxy = 1 für Rate-Limiter hinter Reverse-Proxy (sonst unwirksam)
      • IDOR Invoice (alte /api/energy-details/:ecdId/invoices): jetzt canAccessEnergyContractDetails → Contract → customerId
      • IDOR PDF-Template-Generator (:id/generate/:contractId): jetzt canAccessContract
      • Email-Anhang-Download: Content-Type-Safelist (HTML/SVG nie inline) + X-Content-Type-Options: nosniff + Filename-CRLF-Sanitizing
      • Provider/Tariff-GETs: requirePermission('providers:read') (Portal-Kunden sehen Provider-Liste nicht mehr)
      • SMTP-Header-Injection: zentrale CRLF-Validierung in smtpService.sendEmail (schützt alle Caller)
      • bcrypt cost 10 → 12 (OWASP 2026)
    • Runde 4 Live-Tests gegen Dev-Server deckten 9 weitere IDORs auf:

      • getCustomer + getAddresses/getBankCards/getDocuments/getMeters/getRepresentatives/getPortalSettings hatten NUR Daten-Sanitizer aber KEINEN canAccessCustomer-Check
      • gdpr.getCustomerConsents + getAuthorizations + checkConsentStatus ebenso ungeschützt
      • Portal-Kunde konnte live per GET /api/customers/<fremde-id> kompletten Fremdkunden-Datensatz auslesen → jetzt 403
      • Error-Handler: err.status wird jetzt respektiert (413/400 statt pauschalem 500)

      Live-verifiziert als Portal-Kunde gegen fremden Test-Kunden #4:

      Endpoint Vorher Nachher
      GET /api/customers/4 🚨 200 mit Daten 403
      GET /api/customers/4/addresses 🚨 200 403
      GET /api/customers/4/bank-cards 🚨 200 403
      GET /api/customers/4/documents 🚨 200 403
      GET /api/customers/4/meters 🚨 200 403
      GET /api/customers/4/representatives 🚨 200 403
      GET /api/gdpr/customer/4/consents 🚨 200 mit Consent-Daten 403
      GET /api/gdpr/customer/4/authorizations 🚨 200 403
      GET /api/gdpr/customer/4/consent-status 🚨 200 403
      Eigene Daten /api/customers/1 200 200 (unverändert)
      12 MB Body 500 „Interner Serverfehler" 413 „Anfrage zu groß"
      Malformed JSON 500 „Interner Serverfehler" 400 „Ungültiges JSON"
    • Deployment-Checkliste komplett

  • 🎉 Version 1.0.0 Feinschliff: Passwort-Reset + Rate-Limiting + Auto-Geburtstagsgrüße

    • Passwort vergessen-Flow (Login → "Passwort vergessen?" Link)
      • Email-Reset-Token mit 2h Gültigkeit (kryptografisch sicher: 32 Byte Random)
      • Funktioniert für Mitarbeiter UND Portal-Kunden (Typ-Auswahl)
      • User-Enumeration-Schutz: immer 200 OK, egal ob Email existiert
      • Reset-Link per Email mit schönem HTML-Template
      • Nach Reset: alle bestehenden Sessions werden gekickt
    • Rate-Limiting gegen Brute-Force
      • Login: 10 Versuche pro 15 Min pro IP (erfolgreiche zählen nicht)
      • Passwort-Reset-Anfrage: 5 Versuche pro Stunde pro IP
    • Cron-Job für automatische Geburtstagsgrüße
      • Täglich 08:00 Uhr: alle Kunden mit heutigem Geburtstag + autoBirthdayGreeting=true
      • Email-Versand über System-E-Mail, Du/Sie-abhängiger Text
      • Catch-up 30s nach Server-Start (falls Server am Geburtstag kurz down war)
      • Marker lastBirthdayGreetingYear verhindert Doppel-Versand
  • Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider

    • Neues Feld customerEmailLabel am EmailProviderConfig (z.B. "Stressfrei-Wechseln", "Meine-Firma")
    • Wenn leer, wird das Label automatisch aus der Domain abgeleitet ("stressfrei-wechseln.de" → "Stressfrei-Wechseln")
    • Neuer Frontend-Hook useProviderSettings() liefert Domain + Label
    • Alle hardcoded "Stressfrei-Wechseln" und @stressfrei-wechseln.de Strings durch dynamische Werte ersetzt (CustomerDetail, ContractForm, ContractDetail, EmailClientTab, Settings)
    • Modal-Eingabefeld "Bezeichnung für Kunden-E-Mails" in Provider-Einstellungen
    • Notwendig für Multi-Mandanten-Betrieb wenn das CRM an Dritte vermietet wird
  • Factory-Defaults: Export + Import von Stammdaten-Katalogen

    • Enthält: Anbieter, Tarife, Kündigungsfristen, Laufzeiten, Vertragskategorien, PDF-Auftragsvorlagen (+ PDF-Dateien)
    • Enthält NICHT: Kundendaten, Verträge, Dokumente, Emails, Einstellungen (dafür gibt es den Datenbank-Backup)
    • Neue Einstellungsseite „Factory-Defaults" mit Übersicht (Anzahl pro Kategorie) und Export-Button
    • Export: ZIP mit manifest.json + Kategorie-JSONs + PDF-Dateien, Download über Browser
    • Import-Script: npm run seed:defaults liest backend/factory-defaults/, merged mehrere JSONs pro Kategorie, upsertet idempotent + kopiert PDFs in uploads/
    • Ordner backend/factory-defaults/ gitignoriert (außer .gitkeep + README), damit firmen-spezifische Kataloge nicht ins Repo kommen
  • Email-Anhänge → Vertragsdokumente + Rechnungen für alle Vertragstypen

    • Im SaveAttachmentModal (bei einem per Email zugeordneten Vertrag) gibt es jetzt drei Modi:
      1. Als Dokument (in feste Slots wie Kündigungsschreiben) wie bisher
      2. Als Vertragsdokument neu, mit Typ-Dropdown (Auftragsformular, Lieferbestätigung, Vertragsunterlagen, Vollmacht, Widerrufsbelehrung, Preisblatt, Sonstiges) + Notizen
      3. Als Rechnung jetzt für alle Vertragstypen (vorher nur Strom/Gas)
    • Gleiches gilt für das Speichern der gesamten Email als PDF-Rechnung
    • Neuer Backend-Endpoint saveAttachmentAsContractDocument für die flexible ContractDocument-Tabelle
  • Geburtstag-Management-Modal in Kundenstammdaten

    • Neuer Button (Cake-Icon) neben Geburtsdatum öffnet Modal
    • Gruß zurücksetzen: setzt lastBirthdayGreetingYear auf null zurück (fürs Debugging + Fallback)
    • Gruß jetzt senden: per Email (direkt), WhatsApp/Telegram/Signal (öffnet vorbefülltes Fenster)
    • Beide Aktionen mit Ja/Nein-Bestätigungsdialog (kein versehentliches Klicken)
    • Text respektiert Du/Sie-Einstellung des Kunden
    • Checkbox "Automatisch senden" mit Kanal-Dropdown (neue Felder am Customer)
    • Audit-Log für Reset + Send
  • Anrede-Verhältnis Du/Sie pro Kunde

    • Neues Feld useInformalAddress in Stammdaten (auch bei Firmenkunden)
    • Default: Sie (formell)
    • Geburtstagsgruß im Portal nutzt die Anrede: "Du"-Kunden bekommen "Herzlichen Glückwunsch, Max!", "Sie"-Kunden "Herzlichen Glückwunsch, Herr Müller!"
    • Komplett konsistent auch bei nachträglichen Glückwünschen ("hattest" vs "hatten")
  • Geburtsdatum + Geburtsort auch bei Firmenkunden

    • Felder werden jetzt unabhängig vom Kundentyp angezeigt
    • Ermöglicht z.B. Geburtstage für Ansprechpartner bei Firmen
  • Geburtstagskalender + Geburtstagsgruß-Modal

    • Admin: Section im Vertrags-Cockpit mit Kunden, die in den nächsten 30 Tagen oder letzten 7 Tagen Geburtstag haben
    • Portal: Modal mit Gruß am Geburtstag (inkl. nachträglichem Glückwunsch bis 7 Tage danach)
    • Wird pro Jahr nur einmal angezeigt
  • Typspezifische Zusatzinfos in Vertragslisten

    • Strom/Gas → "Lieferadresse: ..."
    • DSL/Glasfaser/Kabel → "Anschlussadresse: ..."
    • Mobilfunk → "Rufnummer: ..."
    • KFZ → "Kennzeichen: ..."
    • Sichtbar in Admin-Liste, Portal-Liste und Kunden-Tab
  • Datenschutzerklärung PDF ↔ Online-Einwilligungen synchronisieren

    • PDF hochgeladen → alle 4 Consents auf GRANTED
    • Haken entfernt im Portal → PDF löschen + Tabs sperren
    • Entsperrung nur durch alle Haken oder neues PDF
  • Zweitarif-Zähler (HT/NT) bei Strom + Verbrauchsberechnung

  • Datumsformate vereinheitlichen (01.01.2026 statt 1.1.2026)

  • Audit-Log aussagekräftig (Vorher/Nachher bei allen Änderungen)

  • Impressum + Website-Datenschutzerklärung im Kundenportal

    • Editor in Einstellungen
    • Vorschlagstexte
  • Consent-Bestätigungs-Flow per Email

    • Alle Hebel müssen gesetzt sein
    • Bestätigungsbutton + Bestätigungsemail
  • Vertragsdokumente-Upload (Auftragsformular, Lieferbestätigung, Vertragsunterlagen als PDF/PNG)

  • Bug: Stressfrei-Email im Auftragsgenerator (funktioniert jetzt im Vertrag)

  • PDF-Auftragsvorlagen-System

    • Template-Editor in Einstellungen
    • PDF hochladen, Formularfelder automatisch auslesen
    • CRM-Felder zuordnen (visuell mit Vorschau)
    • Seitenweise Sortierung der Felder
    • Dynamische Rufnummern-Felder mit Vorwahl-Extraktion
    • Nicht zugeordnete Felder bleiben editierbar
    • Auftrag generieren aus Vertragsdaten (Button im Vertrags-Detail)
  • Eigentümer-Verwaltung

    • An Adresse gehängt (Firma, Vorname, Nachname, Anschrift, Kontakt)
    • Fallback auf Kundendaten wenn leer
    • Nur bei Liefer-/Meldeadressen (nicht Rechnung)
    • Namens-Kombinationen (Firma + Vorname + Nachname etc.)
  • Gruppenauswahl Liefer-/Rechnungs-/Eigentümer-Adresse im Auftragsgenerator

  • Objekttyp + Lage + Lage des Anschlusses bei Festnetz-Verträgen (DSL/Glasfaser/Kabel)

  • Bankverbindung-Fallback im PDF-Generator (neueste aktive Bankverbindung des Kunden)