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>
mailversenden.php hatte keine Zugriffskontrolle, versendete bei jedem
GET-Request sofort echte Mails und nutzte PHPMailer, dessen Quelldateien
im Repo gar nicht vorhanden waren (nur composer.json/Lizenz) - der Aufruf
waere also ohnehin mit Fatal Error abgebrochen. Zusaetzlich waren SMTP-
Host, Absender, PayPal-Link und FAQ-URL fest auf einen Alt-Kunden (AOK)
codiert.
- Ersetzt PHPMailer durch die bestehende saas_send_mail()-Abstraktion aus
M3 (Transport log/mail je nach APP_MAIL_TRANSPORT) statt eine fehlende
Abhaengigkeit nachzuvendoren.
- Neue Tabelle outbound_emails protokolliert jeden Versandversuch:
Mandant, Mitglied, Vorlage, Betreff, Status, Fehler - das Versandlog.
- Formular hat eine standardmaessig aktive Dry-Run-Checkbox; im Dry-Run
wird nur geloggt, saas_send_mail() nicht aufgerufen.
- Mailtext ist jetzt tenant-generisch (Saldo, optionaler PayPal-Link nur
wenn der Mandant PayPal aktiviert hat, eigener Dashboard-Link) statt
hartcodierter Alt-Kunden-Inhalte.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: mailversenden.php ist jetzt reguel</EOF>