# Phase 15: Modul-Berechtigungen: Gruppen & User-Grants - Context **Gathered:** 2026-08-04 **Status:** Ready for planning ## Phase Boundary Modulzugriff wird zweistufig. Die bestehende Mandanten-Aktivierung (`TenantModuleActivation`) bleibt Voraussetzung; darüber entscheiden neue Freigaben pro Gruppe und pro einzelnem Benutzer, wer ein Modul sieht und dessen API nutzen darf. Dazu kommen Tessera-eigene Gruppen mit optionaler AD-Bindung, eine Admin-Oberfläche für Gruppen und Freigaben, und eine Migration, die den Bestand ohne Zugriffsverlust überführt. Nicht in dieser Phase: Rechtestufen (read/write/admin) innerhalb eines Moduls, eigene Rollendefinitionen jenseits von SUPER_ADMIN/ADMIN/USER, Self-Service-Workflows zum Anfordern von Zugriff. ## Implementation Decisions ### Zugriffsmodell - **D-01:** Zugriff auf ein Modul = Mandanten-Aktivierung UND (Rolle ADMIN/SUPER_ADMIN ODER Direkt-Grant für den Benutzer ODER Grant über eine seiner Gruppen). Eine einzige Auflösungsfunktion bedient sowohl `ModuleGuard` als auch `GET /modules/active`, damit Sidebar, Modulseiten und API dieselbe Wahrheit sehen — **Reversibility:** one-way — die Auflösung wird zur Zugriffsgrundlage jedes Modul-Endpoints; ein späterer Wechsel des Modells müsste jeden Guard-Aufrufpfad und die bereits vergebenen Grants migrieren. - **D-02:** Default ist geschlossen: ohne Grant kein Zugriff. Die Mandanten-Aktivierung allein genügt nicht mehr — **Reversibility:** costly — ein Rückbau auf „offen, sofern nicht eingeschränkt" verlangt eine erneute Datenmigration und kehrt die Bedeutung aller bestehenden Grant-Datensätze um. - **D-03:** ADMIN und SUPER_ADMIN umgehen Grants innerhalb ihres Mandanten und sehen alle dort aktiven Module. Verhindert Aussperren beim Konfigurieren. - **D-04:** Nur Zugriff an/aus. Keine Rechtestufen innerhalb der Module — Module werten keine Grant-Attribute aus. - **D-05:** Gruppen sind Tessera-eigene Objekte pro Mandant mit optionaler AD-Bindung über einen Gruppen-DN. Mitgliedschaften tragen ihre Herkunft (`MANUAL` oder `LDAP`), damit der Sync nur seine eigenen Einträge aufräumt — **Reversibility:** one-way — Group/GroupMembership/ModuleGrant sind neue Tabellen mit Fremdschlüsseln auf User, Tenant und Module; ein Umbau nach Vergabe echter Freigaben ist eine Datenmigration. - **D-06:** Die Migration legt pro Mandant eine Gruppe „Alle Benutzer" an, nimmt alle bestehenden Benutzer auf und erzeugt Grants für alle zum Migrationszeitpunkt aktiven Module. Kein bestehender Benutzer verliert beim Deploy Zugriff — **Reversibility:** one-way — die Migration schreibt Bestandsdaten; ein Rückbau erfordert ein eigenes Rückabwicklungsskript. ### Sperr-Verhalten im Portal - **D-07:** Ruft ein Benutzer die URL eines nicht freigegebenen Moduls direkt auf, erscheint eine 403-Seite mit dem Hinweis „Kein Zugriff auf dieses Modul — wende dich an deinen Administrator". Kein stiller Redirect, kein 404. Die Prüfung erfolgt serverseitig in der Modulseiten-Route — das Ausblenden in der Sidebar ist ausdrücklich keine Zugriffskontrolle. - **D-08:** Der Marketplace zeigt auch nicht freigegebene Module weiter, gekennzeichnet mit einem Badge „Kein Zugriff"; öffnen lassen sie sich nicht. Der Katalog bleibt Schaufenster, `GET /modules` bleibt für authentifizierte Benutzer offen. - **D-09:** Ein Freigabe-Entzug wirkt in der API sofort, weil der Guard jede Anfrage prüft. Die Sidebar zieht beim nächsten Seitenaufruf nach. Kein Polling, keine Push-Verbindung. ### Standard-Zuweisungen - **D-10:** Aktiviert ein Admin ein Modul für seinen Mandanten, fragt ein Dialog, ob es sofort für die Standardgruppe freigegeben oder erst konfiguriert werden soll. Der bisherige Aktivieren-Toggle bekommt damit eine Rückfrage. - **D-11:** Manuell angelegte Benutzer werden automatisch Mitglied der Standardgruppe. - **D-12:** Per LDAP importierte Benutzer ebenso — eine Regel für beide Herkünfte. AD-gebundene Gruppen steuern die Feinverteilung zusätzlich. - **D-13:** Welche Gruppe die Standardgruppe ist, entscheidet eine Markierung an der Gruppe, nicht ihr Name. Genau eine Gruppe pro Mandant trägt sie; die Migration setzt sie auf „Alle Benutzer". Der Admin kann die Gruppe umbenennen, die Markierung auf eine andere Gruppe umhängen oder ganz abschalten — ohne Markierung wandert kein neuer Benutzer automatisch irgendwohin. Die Gruppe selbst ist eine normale Gruppe: bearbeitbar, umbenennbar, löschbar. ### Admin-Oberfläche - **D-14:** Gruppenverwaltung als eigener Navigationspunkt `/admin/groups`, sechster Eintrag neben LDAP, Benutzern, Modulen, Mandanten und SMTP. Dort: Gruppen anlegen, umbenennen, löschen, Mitglieder manuell zuweisen und entfernen. - **D-15:** Die Modulfreigaben für Gruppen werden auf einer eigenen Matrix-Seite gepflegt: Module × Gruppen mit Häkchen, gesamter Berechtigungsstand auf einen Blick. - **D-16:** Freigaben für einzelne Benutzer werden im Benutzer-Detail in `/admin/users` gepflegt, zusammen mit der Anzeige dessen, was der Benutzer bereits über seine Gruppen erbt. - **D-17:** Beim Löschen einer Gruppe mit Mitgliedern oder Freigaben erscheint ein Warndialog mit beiden Anzahlen und dem Hinweis, dass betroffene Benutzer den Zugriff verlieren, sofern sie ihn nicht anderweitig haben. Nach Bestätigung werden Mitgliedschaften und Grants mitgelöscht. ### AD-Bindung und Sync - **D-18:** Die AD-Gruppe wird aus einer Liste gewählt, nicht als DN abgetippt. Die vorhandene Gruppen-/OU-Suche aus dem LDAP-Service (genutzt für den selektiven Import-Filter) wird dafür wiederverwendet. - **D-19:** Verschwindet ein Benutzer aus der gebundenen AD-Gruppe, wird seine `LDAP`-Mitgliedschaft entfernt. Manuell gesetzte Mitgliedschaften bleiben unberührt. Für gebundene Gruppen ist das AD die Wahrheit. - **D-20:** Mischbetrieb ist erlaubt: einer AD-gebundenen Gruppe dürfen zusätzlich Benutzer von Hand hinzugefügt werden — für Externe oder Testkonten ohne AD-Mitgliedschaft. - **D-21:** Die Gruppen-Mitgliedschaften werden im bestehenden Benutzer-Sync mitgeführt: der Sync liest `memberOf` je Benutzer zusätzlich aus und aktualisiert die Mitgliedschaften im selben Durchlauf, manuell per Button wie über das eingestellte Intervall. Kein separater Gruppen-Sync-Job und kein zweiter Button. ### Widgets und Nachvollziehbarkeit - **D-22:** Dashboard-Widgets bekommen einen optionalen Modul-Bezug, und Widgets eines für den Benutzer gesperrten Moduls verschwinden vom Dashboard. Heute trägt `WidgetInstance.widgetType` einen freien String ohne Modulverweis — die Zuordnung Widget-Typ → Modul wird in dieser Phase mitgebaut. Bewusste Erweiterung gegenüber den ursprünglichen Success Criteria der Roadmap — **Reversibility:** costly — betrifft Schema, Dashboard-Auslieferung und die Widget-Registrierung; ein Rückbau berührt alle drei. - **D-23:** Änderungen an Freigaben werden nur ins Server-Log geschrieben. Keine Audit-Tabelle in der Datenbank, keine Ansicht im Admin-UI. ### Claude's Discretion - Konkrete Schema-Details der neuen Modelle: Feldnamen, Indizes, Kaskadenregeln, und wie die Entweder-oder-Beziehung von `ModuleGrant` auf Gruppe bzw. Benutzer erzwungen wird - Aufbau und Ort der Zugriffsauflösung im NestJS-Code sowie ob und wie ihr Ergebnis pro Request zwischengespeichert wird - Technik der Migration: Prisma-Migration mit Datenschritt oder separates Seed-Skript - Darstellung der Matrix bei vielen Modulen und Gruppen (Scrollverhalten, Gruppierung, Suche) - Namensraum und Schnitt der i18n-Keys für die neuen Oberflächen - Mechanik der Widget-Typ-→-Modul-Zuordnung: statische Registrierung im Code oder Feld in der Datenbank ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Projekt-Kontext - `.planning/ROADMAP.md` — Abschnitt „### Phase 15" mit Goal, sechs Success Criteria, den vier Grundsatzentscheidungen und den vorgesehenen neuen Modellen - `.planning/PROJECT.md` — Core Value, Mandantenfähigkeit als Architekturkonstante - `.planning/REQUIREMENTS.md` — Requirement-IDs (für Phase 15 noch nicht vergeben) ### Vorherige Entscheidungen - `.planning/phases/02-authentication-multi-tenancy/02-CONTEXT.md` — D-08 bis D-13 (Tenant-Zuordnung pro Benutzer, RLS, drei Rollen), D-14 bis D-18 (LDAP-Sync-Verhalten, Feld-Mapping, LDAP-Konfig pro Mandant) ### Bestehender Code, der das Verhalten definiert - `apps/api/prisma/schema.prisma` — `Module` (Z. 93), `TenantModuleActivation` (Z. 107), `WidgetInstance` (Z. 130), `Role`-Enum (Z. 21), `User.ldapDn` - `apps/api/src/module-registry/module-registry.service.ts` — `findActiveForTenant`, `isModuleActive`: die beiden Stellen, an denen die zweite Stufe ansetzt - `apps/api/src/module-registry/module.guard.ts` — `ModuleGuard` prüft heute ausschließlich die Mandanten-Aktivierung - `apps/api/src/module-registry/module-registry.controller.ts` — `GET /modules`, `GET /modules/active`, Aktivieren/Deaktivieren mit `RolesGuard` - `apps/api/src/ldap/ldap.service.ts` — Gruppen-/OU-Suche für den Import-Filter (wiederverwendbar für D-18), `memberOf`-Behandlung als Suchfilter, Attributliste aus dem Feld-Mapping - `apps/web/src/components/layout/sidebar.tsx` — lädt `GET /modules/active` beim Mount und baut daraus die Modulnavigation ## Existing Code Insights ### Reusable Assets - Gruppen-/OU-Suche im LDAP-Service: liefert Gruppen samt DN und bedient bereits das Admin-UI für den selektiven Import — deckt die Auswahl-Oberfläche aus D-18 ohne neue LDAP-Logik ab - `RolesGuard` + `@Roles(...)`-Dekorator: das etablierte Muster für rollengeschützte Admin-Endpoints, direkt übertragbar auf die neuen Gruppen- und Grant-Routen - Admin-Bereich mit fünf bestehenden Unterseiten (`ldap`, `users`, `modules`, `tenants`, `smtp`): Layout, Navigation und Formularmuster für `/admin/groups` sind vorhanden - i18n über `apps/web/src/messages/de.json` und `en.json` mit `useTranslations`, wie zuletzt in Phase 14 durchgezogen ### Established Patterns - Zugriffskontrolle sitzt in NestJS-Guards, nicht in Controllern — die neue Auflösung gehört dorthin, wo `ModuleGuard` heute schon greift - `tenantId` kommt aus dem JWT über die Tenant-Middleware, nie aus Benutzereingaben (Bedrohungsmodell T-03-04 aus Phase 3) — für Gruppen und Grants gilt dasselbe - Deaktivierungen sind Soft-Deletes mit erhaltenem Datensatz (`TenantModuleActivation.isActive`), Benutzer werden bei LDAP-Löschung deaktiviert statt entfernt ### Integration Points - `ModuleGuard.canActivate` — hier kommt die Benutzer-Dimension hinzu - `GET /modules/active` — muss vom Mandanten- auf den Benutzer-Blick wechseln, sonst zeigt die Sidebar mehr als die API erlaubt - Modulseiten-Route im Frontend (`(portal)/modules/[category]`) — braucht die serverseitige Prüfung aus D-07 - Marketplace-Katalog — braucht die Kennzeichnung aus D-08 - LDAP-Sync-Durchlauf — braucht `memberOf` in der Attributliste und den Abgleich aus D-19/D-21 - Dashboard-Auslieferung der Widget-Instanzen — braucht den Modul-Bezug aus D-22 ## Specific Ideas - Die 403-Seite soll benennen, was zu tun ist („wende dich an deinen Administrator"), statt nur zu sperren - Im Benutzer-Detail soll sichtbar sein, was der Benutzer über Gruppen erbt und was ihm zusätzlich direkt gegeben wurde — sonst ist nicht erkennbar, warum jemand Zugriff hat - Der Löschdialog einer Gruppe nennt konkrete Zahlen (Mitglieder, Freigaben), keine allgemeine Warnung ## Deferred Ideas - Rechtestufen innerhalb eines Moduls (read / write / admin pro Grant) — eigene Phase, verlangt Anpassung jedes bestehenden Moduls - Self-Service „Zugriff anfragen" aus dem Marketplace heraus, inklusive Genehmigungsfluss — neue Fähigkeit - Audit-Trail für Berechtigungsänderungen in der Datenbank mit Ansicht im Admin-UI — bewusst gegen die Datenbankvariante entschieden (D-23), für ein verkauftes Produkt später plausibel nachzurüsten - Mehrfach-Mandantenzugehörigkeit eines Benutzers — steht seit Phase 2 (D-09) aus und würde die Gruppenzuordnung erneut berühren --- *Phase: 15-modul-berechtigungen-gruppen-user-grants* *Context gathered: 2026-08-04*