M5: Hinweise auf tenant-scoped Notices umziehen, Mitgliederzugang auf Rollen umstellen

Hinweise:
- Neue Tabelle notices (tenant-scoped, Soft-Delete via deleted_at) loest
  die global unscoped kl_hinweise als aktive Datenquelle ab; Migration
  uebernimmt einmalig aktuell gueltige kl_hinweise-Eintraege fuer den
  Default-Mandanten. kl_hinweise bleibt als Golden-Master-Referenz stehen.
- hinweise.php und der Banner in header.php sind tenant-scoped umgestellt.

Mitgliederverwaltung:
- mitarbeiterverwalten.php verwaltet jetzt participants (tenant-scoped)
  statt der global unscoped kl_Mitarbeiter-Tabelle als primaere Quelle.
  Das behebt nebenbei ein Mandanten-Datenleck: jeder SaaS-Mandant mit
  Owner/Admin-Rolle haette zuvor die komplette Default-Mandanten-
  Mitgliederliste sehen und bearbeiten koennen.
- Fuer den Default-Mandanten bleibt Dual-Write nach kl_Mitarbeiter
  bestehen, damit stricheintragen.php/einzahlung.php weiter funktionieren;
  andere Mandanten werden rein participant-nativ verwaltet.
- Die Legacy-Administrator-Checkbox ist raus. Stattdessen kann ein Admin
  je Mitglied unabhaengig von Name/E-Mail einen Login-Zugang mit Rolle
  (member/treasurer/admin) gewaehren oder entziehen
  (saas_grant_participant_access / saas_revoke_participant_access).
  Einladung laeuft ueber den bestehenden Passwort-Reset-Mechanismus,
  Entzug setzt die Mitgliedschaft auf revoked statt sie zu loeschen.
- Kompletter Flow live getestet: anlegen, Zugang gewaehren, Einladungsmail,
  Passwort setzen, Login, Rollenschutz, Zugang entziehen, Login-Sperre.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:18:55 +02:00
co-authored by Claude Sonnet 5
parent dcc8dc3421
commit aeb687f42e
10 changed files with 790 additions and 273 deletions
+92 -3
View File
@@ -167,13 +167,98 @@ Bewusst nicht umgesetzt:
Admin-Rollen für neue SaaS-Nutzer laufen bis dahin weiter über
`scripts/backfill-default-tenant.php` oder die Registrierung.
## Fortsetzung: Hinweise und rollenbasierter Zugang
Umgesetzte Dateien:
```text
database/migrations/0007_saas_notices.sql
app/notices.php
hinweise.php
header.php
mitarbeiterverwalten.php (grundlegend neu)
app/ledger.php (Participant-CRUD)
app/saas-auth.php (Zugangsvergabe/-entzug)
app/saas-mail.php (Einladungsmail)
```
### Hinweise auf Notices umgezogen
- Neue Tabelle `notices` (tenant-scoped, mit `deleted_at` für Soft-Delete)
ersetzt `kl_hinweise` als aktive Datenquelle. `kl_hinweise` bleibt
unangetastet als Golden-Master-Referenz.
- Die Migration übernimmt einmalig alle zum Migrationszeitpunkt noch
gültigen `kl_hinweise`-Einträge in `notices` des Default-Mandanten, damit
keine sichtbaren Banner verloren gehen.
- `hinweise.php` liest/schreibt jetzt ausschließlich `notices`, tenant-scoped
mit dem gleichen Rollen-/Legacy-Fallback-Muster wie andere Admin-Seiten.
Löschen ist ein Soft-Delete (`deleted_at`), kein Hard-Delete.
- `header.php` (Banner-Anzeige auf allen App-Seiten) löst den Mandanten
jetzt selbst auf (SaaS-Session oder Default-Tenant-Fallback) und zeigt den
aktuell gültigen Hinweis dieses Mandanten statt eines global-legacy
Hinweises.
### Mitgliederverwaltung: participant-nativ mit Rollen-Zugang
`mitarbeiterverwalten.php` wurde grundlegend umgebaut, nicht mehr additiv
gepatcht:
- Datenquelle ist jetzt `participants` (tenant-scoped), nicht mehr die
global unscoped `kl_Mitarbeiter`-Tabelle. Das behebt nebenbei ein
Datenleck: Da `kl_Mitarbeiter` keine `tenant_id` hat, hätte jeder
SaaS-Mandant mit Owner/Admin-Rolle über die vorherige Version dieser
Seite die komplette Mitgliederliste des Default-Mandanten sehen und
bearbeiten können.
- Für den Default-Mandanten wird weiterhin dual-write nach
`kl_Mitarbeiter` betrieben (`ledger_create_participant()`,
`ledger_update_participant()`, `ledger_set_participant_active()`), damit
`stricheintragen.php` und `einzahlung.php` (deren Mitarbeiter-Picker
weiterhin direkt aus `kl_Mitarbeiter` liest) neue Mitglieder sofort
anzeigen. Andere Mandanten haben keine Legacy-Schattentabelle und werden
rein participant-nativ verwaltet.
- Die Legacy-„Administrator"-Checkbox ist aus dem Anlegen-/Bearbeiten-
Formular entfernt. Stattdessen gibt es je Mitglied eine eigene
„Zugang"-Spalte: Rolle wählen (`member`, `treasurer`, `admin`) und
„Zugang gewähren", oder bei bestehendem Zugang Rolle ändern beziehungsweise
„Zugang entziehen". `owner` ist über diese UI nicht vergebbar oder
entziehbar (nur bei Registrierung gesetzt).
- Name und E-Mail eines Mitglieds (`participants.display_name`/`email`)
bleiben unabhängig vom Login: Ein Mitglied kann ohne jeden Zugang
existieren (nur für Kaffeeliste/Benachrichtigung), und ein Zugang kann
jederzeit gewährt oder entzogen werden, ohne den Mitgliedsdatensatz zu
berühren.
- Zugangsvergabe (`saas_grant_participant_access()`) legt bei Bedarf einen
`users`-Datensatz ohne Passwort an, setzt/aktualisiert die
`tenant_memberships`-Rolle und verknüpft `participants.user_id`.
Anschließend wird ein Einladungslink über den bestehenden
Passwort-Reset-Mechanismus verschickt (`saas_send_invite_mail()`, gleicher
Token-Typ `password_reset`, gleiche Zielseite
`passwort-zuruecksetzen.php` wie beim regulären Passwort-Reset).
- Zugangsentzug (`saas_revoke_participant_access()`) setzt die
`tenant_memberships`-Zeile auf `status = 'revoked'`, statt sie zu löschen
oder den `user_id`-Verweis zu entfernen. Der Zugang kann später erneut
gewährt werden, ohne den Account neu anzulegen. Der Login prüft bereits
überall auf `tm.status = 'active'`, wodurch ein entzogener Zugang sofort
wirkt.
- Live gegen die Dev-Datenbank getestet: Mitglied anlegen (inklusive
Dual-Write-Check), Zugang mit Rolle `treasurer` gewähren, Einladungsmail
geprüft, Passwort über den Einladungslink gesetzt, erfolgreicher Login,
Rollenschutz geprüft (kein Zugriff auf `mitarbeiterverwalten.php`, Zugriff
auf `kaffeeliste.php`), Zugang entzogen, anschließender Login-Versuch
korrekt mit „kein aktiver Mandant" abgelehnt.
## Noch offen
- Eigene PayPal-/Zahlungsbereich als eigenständiger App-Screen (aktuell nur im
Dashboard integriert).
- Einladungs-Flow, um bestehenden Teilnehmern nachträglich einen
Login-Account mit `tenant_memberships`-Rolle zuzuweisen.
- Hinweise als tenant-spezifische Notices umsetzen.
- `stricheintragen.php` und `einzahlung.php` lesen ihre Mitarbeiter-Picker
weiterhin aus der global unscoped `kl_Mitarbeiter`-Tabelle. Für den
Default-Mandanten funktioniert das unverändert; für jeden anderen Mandanten
ist die Liste faktisch leer beziehungsweise zeigt (nur lesend, Schreiben
schlägt dank Tenant-Scope in `ledger_mirror_legacy_*` sicher fehl) die
Namen der Default-Mandanten-Mitglieder an. Das ist ein bestehendes,
eigenständiges Scope-Thema für eine spätere Iteration, keine Regression
dieser Session.
- Export, Mail und Jahresprozesse bleiben M6-Themen.
## Aktueller Prüfstatus
@@ -187,3 +272,7 @@ Bewusst nicht umgesetzt:
- Live-Test der Mitgliederverwaltung gegen die Dev-DB: Anlegen (inklusive
XSS-Payload-Check), Deaktivieren, participants-Spiegelung erfolgreich
geprüft.
- Live-Test Hinweise/Notices: Carry-over-Migration, Anlegen mit HTML-Payload
(korrekt escaped), Soft-Delete, Banner-Anzeige auf Mandant geprüft.
- Live-Test Zugangsvergabe/-entzug: kompletter Flow von Einladung bis
Login-Sperre nach Entzug erfolgreich geprüft (siehe oben).
+17 -7
View File
@@ -305,7 +305,7 @@ Nicht tun:
| M2 | Technisches Fundament | Abgeschlossen: Migrationen, Bootstrap, Session, CSRF-Helper und Legacy-Schreibseitenschutz stehen |
| M3 | SaaS-Basis | Abgeschlossen: Tenants, User, Registrierung, Login, Rollen, Mail-Links und zentrale Mandantenauswahl funktionieren |
| M4 | Datenmigration | Gestartet: Ledger-Tabelle, Legacy-Backfill, Paritätscheck, Ledger-Service und Preview sind umgesetzt |
| M5 | App-Kern | Weit fortgeschritten: Kernseiten lesen und schreiben tenant-sicher gegen das Ledger; offen sind Hinweise und ein eigener Zahlungs-Screen |
| M5 | App-Kern | Weit fortgeschritten: Kernseiten lesen und schreiben tenant-sicher gegen das Ledger, inklusive Hinweise und rollenbasiertem Zugang; offen ist ein eigener Zahlungs-Screen |
| M6 | Betriebsflows | Import, Export, Mail und Jahresprozesse sind auditierbar |
| M7 | Landingpage | Public-Seite und Auth-Seiten sind im gemeinsamen Stil nutzbar; spätere Ausbaustufen folgen |
| M8 | Härtung | Betrieb, Datenschutz, Monitoring und Isolation sind geprüft |
@@ -515,9 +515,15 @@ Schritte:
`index.php`. Ein eigener `/app/einzahlungen`-Screen wie ursprünglich in der
Zielarchitektur skizziert existiert nicht separat; die Funktion ist bewusst
im Dashboard gebündelt statt als eigene Route.
- Mitgliederverwaltung tenant- und rollenbasiert umsetzen: erledigt in
`mitarbeiterverwalten.php`, inklusive Spiegelung nach `participants` und
Behebung einer gespeicherten XSS-Lücke bei Name/E-Mail.
- Mitgliederverwaltung tenant- und rollenbasiert umsetzen: erledigt.
`mitarbeiterverwalten.php` verwaltet jetzt `participants` als primäre,
tenant-scoped Quelle (nicht mehr die global unscoped `kl_Mitarbeiter`-
Tabelle) und erlaubt Admins, Mitgliedern unabhängig von Name/E-Mail einen
Login-Zugang mit Rolle zu gewähren oder zu entziehen (Einladung per Mail
über den bestehenden Passwort-Reset-Mechanismus, Entzug als Statuswechsel
statt Delete). Nebenbei behoben: eine gespeicherte XSS-Lücke bei Name/
E-Mail und ein Mandanten-Datenleck, weil die alte Version jedem
SaaS-Mandanten die komplette Default-Mandanten-Mitgliederliste zeigte.
- Gesamtübersicht umsetzen: erster read-only Stand in `kaffeeliste.php`
erledigt.
- Teilnehmerauswertung umsetzen: read-only Stand in `teilnehmerauswertung.php`
@@ -529,8 +535,10 @@ Schritte:
- Sammelerfassung (`stricheintragen.php`, `einzahlung.php`) tenant-sicher
absichern und ans Ledger anbinden: erledigt. Beide Seiten hatten zuvor
keine Zugriffskontrolle außer CSRF; das ist behoben.
- Hinweise als tenant-spezifische Notices umsetzen: offen, `hinweise.php`
ist noch vollständig Legacy ohne Tenant-Bezug.
- Hinweise als tenant-spezifische Notices umsetzen: erledigt. Neue Tabelle
`notices` mit Soft-Delete, `hinweise.php` und die Banner-Anzeige in
`header.php` sind tenant-scoped umgestellt; `kl_hinweise` bleibt nur noch
als Golden-Master-Referenz bestehen.
Ergebnis:
@@ -717,7 +725,9 @@ UX-Prüfungen:
werden?
- Wie sollen Kunden-Tenants adressiert werden: Subdomain, Pfad oder Auswahl nach
Login?
- Welche Rolle soll `treasurer` haben: eigene Rolle oder Teil von `admin`?
- ~~Welche Rolle soll `treasurer` haben: eigene Rolle oder Teil von `admin`?~~
Entschieden mit der M5-Zugangsvergabe: `treasurer` ist eine eigene,
über `mitarbeiterverwalten.php` vergebbare Rolle, getrennt von `admin`.
- Wird das Umfrage-Modul Teil des SaaS-Produkts oder nur archiviert?
- Braucht der MVP schon Tarife/Billing oder erstmal nur Registrierung?
- Sollen bestehende Kunden per Einladung oder per Self-Service onboarden?