Die Zugriffspruefung lief bisher in 19 Dateien auf einen Fallback gegen die
Legacy-Tabelle kl_Mitarbeiter (checkKaffeelisteAdmin/-Access). Der Zugang
haengt jetzt ausschliesslich an der SaaS-Anmeldung.
- Fallback-Bloecke in allen 19 Dateien entfernt, ebenso getUserName/getUserId
- functions.php besteht nur noch aus der SaaS-Anmeldung; die per String
zusammengebaute Abfrage "WHERE Email like '<eingabe>'" ist damit weg
- config.php baut keine sqlsrv-Verbindung mehr auf, der Kompatibilitaetslayer
lib/sqlsrv_mysql_compat.php ist verwaist und entfaellt
- Auto-Login per DEV_AUTH_EMAIL gibt es nicht mehr: er meldete jeden Besucher
an und machte damit den Login-Schutz unpruefbar. Angemeldet wird auch lokal
ueber login.php; die Variable dient nur noch scripts/init-mysql-dev.php
http-smoke war an die Golden-Master-Daten des Default-Mandanten gebunden und
hatte deshalb sechs dauerhaft rote Pruefungen. Der Test legt sich jetzt einen
eigenen Mandanten mit bekannten Salden an, meldet sich per HTTP an und raeumt
danach auf. Geschuetzte Seiten werden nicht mehr ueber das Wort "Login" im
Text geprueft, sondern ueber die tatsaechliche 302-Umleitung.
http-smoke 27 PASS / 6 FAIL -> 33 PASS / 0 FAIL
role-matrix 55 PASS / 0 FAIL -> unveraendert
tenant-isolation 9 PASS / 1 FAIL -> 11 PASS / 1 FAIL (Altfehler)
Ausserdem "keine Zugang" -> "keinen Zugang" in zwei Fehlermeldungen; der
Rollentest erkannte die Ablehnung an genau diesem Tippfehler und wurde
mitgezogen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bugfixes aus dem Code-Review:
- XSS: unescaptes $_SERVER['PHP_SELF'] in csvupload.php und
letzteneintraege.php durch feste Seitennamen ersetzt.
- Stripe-Webhook: Event-Deduplizierung (neue Tabelle stripe_webhook_events)
gegen doppelte Verarbeitung/Dolibarr-Rechnungen bei Retry-Zustellung.
- Stripe-Webhook: Tarifwechsel aus dem Kundenportal wird lokal nachgezogen
(plan_code aus dem Preis-lookup_key bei subscription.updated).
- Post-Redirect-Get fuer jahresauswertung, mailversenden und die
Selbst-Stricheintragung - verhindert Doppelbuchung/Doppelversand per
Browser-Refresh.
- Jahresbonus: Mails erst nach erfolgreichem Commit; Restcent-Ausgleich
beim letzten Empfaenger, damit die Summe exakt stimmt.
- Verschachtelte HTML-Dokumente in csvupload/einzahlung/stricheintragen
entfernt (Layout kommt aus header.php).
- Rate-Limit fuer den Versand von E-Mail-Verifizierungslinks.
Neue Funktionen:
- Mitglieder koennen ihren zuletzt selbst eingetragenen Strich wieder
stornieren (nur eigene Web-Eintraege, Kassenwart-Eintraege bleiben).
- Monatsuebersicht des eigenen Verbrauchs im Mitglieder-Dashboard.
- Automatische Zahlungserinnerung: opt-in pro Mandant ab der
Warnschwelle, mit Intervall; Cron-Skript scripts/send-payment-reminders.php.
- CSV-Import fuer Mitgliederlisten inkl. herunterladbarer Vorlage
(mitglieder-vorlage.php), Semikolon-/Komma- und BOM-Erkennung.
- Logo als PDF-Wasserzeichen pro Mandant (Upload in den Mandant-
Einstellungen, geschuetzt unter var/tenant_logos/, ersetzt den
Text-Wasserzeichen im Ausdruck).
Robusterer Teilnehmer-Lookup im Dashboard ueber user_id (Fallback E-Mail).
Erste drei Bausteine der Haertung:
- Security-Headers (X-Content-Type-Options, X-Frame-Options, Referrer-
Policy, Permissions-Policy, HSTS bei HTTPS) laufen automatisch ueber
app_send_security_headers() am Ende von app/bootstrap.php fuer jede
dynamische Seite; landing.php war als einzige Seite ganz ohne PHP und
bekam einen minimalen Bootstrap-Aufruf. Bewusst kein CSP, da die
bestehenden Templates durchgaengig auf Inline-style-Attribute setzen.
- DB-gestuetzte Rate-Limits (neue Tabelle rate_limit_attempts) fuer
Login (10/15min je E-Mail, 20/15min je IP), Registrierung (5/h je IP)
und Passwort-Reset-Anfrage (5/h je E-Mail, 10/h je IP); bei
ausgereiztem Reset-Limit erscheint dieselbe generische Meldung wie im
Erfolgsfall, um kein Konto-Enumeration-Signal zu geben.
- Zentrales Audit-Log (neue Tabelle audit_log) fuer Mitgliederverwaltung,
Zugangsvergabe/-entzug, Storno, Mandant-Einstellungen, Hinweise,
CSV-Import, Jahresbonus-Verteilung und Live-Mailversand; sichtbar fuer
Owner/Admin auf mandant-einstellungen.php.
Live getestet: Rate-Limit greift nach 10 Fehlversuchen, Audit-Log-Eintrag
mit korrekten Metadaten und Nutzernamen ueber einen isolierten Test-
Mandanten geprueft. Alle Regressionstests weiterhin gruen (26/26 Smoke,
104 Golden-Master-Assertions).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
csvupload.php verarbeitete CSV-Zeilen bisher sofort beim Upload, ohne
Vorschau, ohne Zugriffskontrolle (nur CSRF) und schrieb ausschliesslich in
die global unscoped kl_Einzahlungen-Tabelle.
- Neue Tabellen payment_import_batches/payment_import_rows protokollieren
jeden Import: Datei, Pruefsumme, jede Zeile mit Rohwerten, erkanntem
Mitglied, Status und erzeugter Ledger-Zeile.
- Zweistufiger Ablauf: Hochladen zeigt nur eine Vorschau (matched/
duplicate/unmatched/invalid), erst "Import bestaetigen" bucht.
- Zuordnung per paypal_name oder display_name (tenant-scoped), Dubletten-
pruefung gegen bestehende nicht-stornierte Ledger-Zahlungen.
- Buchung folgt dem etablierten Zweig-Muster: Default-Mandant per
Dual-Write nach kl_Einzahlungen plus Spiegelung, alle anderen Mandanten
direkt ueber ledger_record_payment().
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- Live getestet: Treffer, unbekannter Name, ungueltiger Betrag korrekt
klassifiziert; Import bestaetigt mit korrektem Dual-Write; erneuter
Upload derselben Datei erkennt die Zeile korrekt als Dublette.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Upload-Formular mit CSRF-Token absichern
- CSV-Dateien temporär unter var/uploads speichern und nach Import löschen
- Dateityp, Dateigröße und Dateiendung prüfen
- CSV-Ausgabe escapen und PayPal-Name-Lookup korrigieren
- M2- und Sicherheitsdokumentation aktualisieren