148 lines
12 KiB
Markdown
148 lines
12 KiB
Markdown
---
|
||
phase: quick-261002-icv
|
||
plan: 01
|
||
quick_id: 261002-icv
|
||
subsystem: module-grants
|
||
tags: [berechtigungen, freigabestufe, module-guard, prisma, nestjs, nextjs]
|
||
status: complete
|
||
completed: 2026-10-02
|
||
commits: 3
|
||
plan_head_before: b94d267584398be7ccf714954dbd71ce577ef301
|
||
plan_head_after: eaf2c4574afb41f0787eb17874b709bd0faf1a01
|
||
actuals:
|
||
tasks: 3
|
||
commits: 3
|
||
requires: []
|
||
provides:
|
||
- "ModuleGrant.level (USE/MANAGE), Bestand = USE"
|
||
- "ModuleAccessService.getModuleAccessLevels als einzige Auflösung für Zugriff und Stufe"
|
||
- "@ModuleManage(slug) am ModuleGuard"
|
||
- "GET /modules/active liefert canManage je Modul"
|
||
- "Web-Hook useCanManageModule(slug)"
|
||
affects: [kantine-datev, handelsware-datev, proxmox, dkv-fleet, admin-freigaben]
|
||
key-files:
|
||
created:
|
||
- apps/api/prisma/migrations/20261002140000_module_grant_level/migration.sql
|
||
- apps/api/src/groups/dto/create-module-grant.dto.spec.ts
|
||
- apps/api/src/module-registry/module-manage-handlers.spec.ts
|
||
- apps/web/src/lib/use-module-capability.ts
|
||
modified:
|
||
- apps/api/prisma/schema.prisma
|
||
- apps/api/src/module-registry/module-access.service.ts
|
||
- apps/api/src/module-registry/module.guard.ts
|
||
- apps/api/src/groups/module-grants.service.ts
|
||
- apps/api/src/groups/dto/create-module-grant.dto.ts
|
||
- apps/api/src/kantine-datev/kantine-datev.controller.ts
|
||
- apps/api/src/handelsware-datev/handelsware-datev.controller.ts
|
||
- apps/api/src/proxmox/proxmox.controller.ts
|
||
- apps/api/src/dkv/dkv.controller.ts
|
||
- apps/web/src/lib/module-access-actions.ts
|
||
- apps/web/src/components/modules/module-access-gate.tsx
|
||
- "apps/web/src/app/(portal)/admin/modules/grants/page.tsx"
|
||
- "apps/web/src/app/(portal)/admin/users/components/UserAccessModal.tsx"
|
||
decisions:
|
||
- "Stufe wird ausschließlich serverseitig aus ModuleGrant-Zeilen aufgelöst; MANAGE gewinnt bei mehreren Wegen."
|
||
- "Wiederholter Klick auf eine Matrix-Zelle (POST ohne level) ändert die Stufe nie."
|
||
- "DKV-Fleet wird als Ganzes Verwalten-Stufe, Benutzen-Stufe bekommt keinen API-Zugriff (wie bisher)."
|
||
---
|
||
|
||
# Quick 261002-icv: Modul-Freigabe mit Stufe Benutzen und Verwalten
|
||
|
||
Jede Modul-Freigabe (Gruppe oder einzelner Benutzer) hat jetzt eine Stufe: **Benutzen** (USE, Standard und Bestand) oder **Verwalten** (MANAGE, zusätzlich die eigenen Einstellungen dieses einen Moduls ändern). Die Stufe wird im Backend über den neuen Dekorator `@ModuleManage('<slug>')` am `ModuleGuard` erzwungen. Die Weboberfläche erfährt die wirksame Stufe aus `GET /modules/active` (`canManage`) und zeigt Einstellungen nur Administratoren und Verwaltern. Administratoren vergeben die Stufe in der Freigaben-Matrix (je Gruppe) und im Benutzer-Detaildialog (je Benutzer).
|
||
|
||
## Was gebaut wurde
|
||
|
||
**Datenbank (Task 1).** Migration `20261002140000_module_grant_level` (von Hand geschrieben): `CREATE TYPE "ModuleGrantLevel"` und `ADD COLUMN "level" ... NOT NULL DEFAULT 'USE'`. Lokal angewendet über die Container-IP; `prisma migrate status` aktuell, Drift-Prüfung (`migrate diff --exit-code`) Rückgabewert 0. Die vorhandenen vier Freigaben stehen lokal alle auf USE. Keine neue Tabelle, daher keine Änderung an RLS-Regeln; `rls-coverage` und `rls-access-inventory` grün.
|
||
|
||
**Zugriffsauflösung und Wächter (Task 1).**
|
||
- `ModuleAccessService.getModuleAccessLevels` ist die einzige Auflösung: ADMIN/SUPER_ADMIN bekommen auf jedem aktiven Modul MANAGE ohne Grant-Abfragen; andere Rollen Direkt- plus Gruppen-Grants, MANAGE gewinnt, geschnitten mit aktiven Aktivierungen (MANAGE auf deaktiviertem Modul zählt nicht). Alles über denselben `forTenant`-Klienten. `getAccessibleModuleIds` ist nur noch die Schlüsselmenge (Signatur unverändert), `findAccessibleModules` liefert `canManage`.
|
||
- `ModuleGuard` liest `MODULE_MANAGE_KEY`, nutzt pro Request `request.moduleAccessLevels` (Klassen-`@UseModule` plus Handler-`@ModuleManage` fragen den Dienst nur einmal) und wirft bei Stufe USE `Module '<slug>' requires manage permission`. MANAGE gilt nur für das Modul der Route.
|
||
- `CreateModuleGrantDto.level` (optional, `@IsEnum`). `ModuleGrantsService.grant`: ohne Stufe USE; vorhandene Freigabe wird nur bei ausdrücklich anderer Stufe geändert (Logzeile `Grant-Stufe geändert … level=…`), ein Wiederholungsklick stuft nie herab; P2002-Wettlauf wendet dieselbe Regel an.
|
||
|
||
**Umgestellte Handler (Tasks 1 und 2).**
|
||
|
||
| Controller | Auf `@ModuleManage` umgestellt |
|
||
|---|---|
|
||
| `KantineDatevController` | `saveSettings` (`kantine-datev`) |
|
||
| `HandelswareDatevController` | `saveSettings` (`handelsware-datev`) |
|
||
| `ProxmoxController` | `create`, `update`, `remove`, `poll`, `test`, `testDraft` (`proxmox`); `list` bleibt Benutzen |
|
||
| `DkvController` | ganze Klasse (`dkv-fleet`), alle 11 früheren `@Roles` entfernt |
|
||
|
||
In den vier Controllern steht kein dekoratorseitiges `@Roles` mehr.
|
||
|
||
**Web (Tasks 1 bis 3).** `useCanManageModule(slug)` (Admins sofort `true` ohne Abfrage, sonst eine Abfrage von `/modules/active`, Fehler = `false`). Kantinenabrechnung, Handelsware, Proxmox-Seite, Proxmox-Einstellungen, `ServerCard`/`ServerForm` (Props `isAdmin` zu `canManage`) und das Proxmox-Dashboard-Widget (Einstellungs-Link) folgen `canManage`. Neue Server-Funktion `getModuleAccessLevel`; `checkModuleAccess` delegiert. `ModuleAccessGate` kennt `MANAGE_ONLY_MODULE_SLUGS = {'dkv-fleet'}`. Matrix und Benutzerdialog haben ein Stufen-Auswahlfeld (optimistisch, Rücksprung bei Fehler); Gruppen, die Verwalten gewähren, sind im Dialog mit „Verwalten“ markiert. Die API liefert dafür `level` je Matrix-Eintrag sowie `directLevel` und `manageViaGroups` je Modulzeile.
|
||
|
||
**Texte, Doku, Changelog.** Neue Schlüssel in `de.json`/`en.json` (formales „Sie“, echte Umlaute, kein „Mandant“/Tenant in den neuen Formulierungen; per Skript geprüft). `docs/anleitung-administration.md` (Kapitel 1, 2, 5 mit neuem Abschnitt „Freigabestufen: Benutzen und Verwalten“, Fehlersuche), `docs/anleitung-anwender.md`, `CHANGELOG.md` (Unveröffentlicht, Neu).
|
||
|
||
## Bewusst nur für Administratoren
|
||
|
||
Diese Handler blieben unverändert auf `@Roles(ADMIN, SUPER_ADMIN)`; die Metadaten-Spec `module-manage-handlers.spec.ts` beweist das:
|
||
|
||
| Handler | Grund |
|
||
|---|---|
|
||
| `TendersController.getSourceConfig` | plattformweiter Singleton der Abrufeinstellungen für die ganze Installation, keine Konfiguration eines einzelnen Moduls je Firma |
|
||
| `TendersController.saveSourceConfig` | wie oben; ändert Abrufintervall und Quelle für alle |
|
||
| `TendersController.pollNow` | stößt den plattformweiten Abruf beim Datenanbieter an (Lastschalter, T-lvg-01) |
|
||
| `TendersController.createRssFeed` (Bereich „platform“), `removeRssFeed` (plattformweiter Zweig) | prüfen die Rolle inline; plattformweite RSS-Feeds sehen alle Benutzer der Installation. Nicht angefasst |
|
||
| `ModuleRegistryController.activate` / `deactivate` | Modul-Aktivierung ist Sache der Administratoren (L-01) |
|
||
| `ModuleGrantsController.matrix` / `userAccess` / `create` / `remove` | Freigaben vergeben und Stufe wählen bleibt Administratoren vorbehalten (L-01); sonst könnte sich ein Verwalter selbst Rechte geben (T-icv-01) |
|
||
| Benutzer-, Gruppen-, LDAP-, SMTP-, Willkommensmail-, Mandanten-Controller | globale Administration, nicht modulgebunden |
|
||
| Gemeinsame eigene Module (`custom-modules`) | kein Registry-Modul und kein `@UseModule`; Seitenleisten-Einträge für alle, Rollenprüfung im Dienst |
|
||
|
||
Geprüft mit `grep -rn "Roles(" apps/api/src`: `cert-manager`, `domaincheck` und `reminders` haben keinen Administrator-Handler, also nichts umzustellen.
|
||
|
||
## DKV-Verhalten (Änderung)
|
||
|
||
Vorher trug jeder der 11 DKV-Handler `@Roles(ADMIN, SUPER_ADMIN)`, das Modul war faktisch nur für Administratoren benutzbar, und der Controller hatte kein `@UseModule`. Jetzt trägt die ganze Klasse `@ModuleManage('dkv-fleet')`:
|
||
- Zugriff haben Administratoren und Benutzer mit Stufe Verwalten.
|
||
- Benutzer mit nur Benutzen bekommen weiterhin 403 von der API (nichts wurde aufgeweitet).
|
||
- Neu ist, dass der Wächter zusätzlich die Aktivierung von `dkv-fleet` für den Mandanten verlangt (die Webseite verlangte sie schon). Ein Administrator ohne Aktivierung wird jetzt von der API ebenfalls abgewiesen.
|
||
- `ModuleAccessGate` zeigt Benutzern mit nur Benutzen eine erklärende Zugriffsseite („Dieses Modul steht nur Benutzern zur Verfügung, die es verwalten dürfen …“), sonst den Standardtext.
|
||
|
||
## Tests und Prüfungen (ehrlich)
|
||
|
||
- **API:** `pnpm --filter @tessera/api test`: 111 Dateien, 1911 Tests, alle grün (vorher in den Teilläufen u. a. `rls-coverage`, `rls-access-inventory`, `migration-sql`).
|
||
- **Web:** `pnpm --filter @tessera/web test`: 115 Dateien, 1234 Tests, alle grün (Vollauf vor einer reinen Umbenennung unbenutzter Testparameter; danach die betroffenen `admin`-Tests erneut grün, 7 Dateien, 96 Tests).
|
||
- **tsc:** `tsc --noEmit` in api und web ohne Fehler.
|
||
- **Biome:** `biome lint` auf allen berührten TS/TSX-Dateien ohne neue Meldungen. Zwei bereits vorhandene Warnungen bleiben bestehen und stammen nicht aus diesem Plan (`noAssignInExpressions` in `migration-sql.spec.ts`, `noArrayIndexKey` in `grants/page.tsx`).
|
||
- **Lokaler Stand:** `docker compose up -d --build api web` erfolgreich; api, web und db laufen; Log meldet `Nest application successfully started`, kein Migrations- oder Prisma-Fehler. Browserprüfung macht der Orchestrator.
|
||
|
||
## Abweichungen vom Plan
|
||
|
||
**1. [Rule 2 - Konsistenz] Proxmox-Dashboard-Widget auf `canManage` umgestellt**
|
||
- **Gefunden bei:** Task 2
|
||
- **Problem:** `proxmox-widget.tsx` blendete den Link „Zu den Einstellungen“ nur für Administratoren ein, obwohl Verwalter die Einstellungsseite jetzt nutzen dürfen. Die Datei stand nicht in der Dateiliste des Plans.
|
||
- **Fix:** `useCanManageModule('proxmox')` statt Rollenprüfung; bestehender Widget-Test blieb grün.
|
||
- **Commit:** c2ebc8d
|
||
|
||
**2. [Rule 1 - Typfehler] `applyToExisting` in `ModuleGrantsService.grant`** brauchte den Typ `ModuleGrant`, damit `tsc` die Spec akzeptiert. Behoben vor dem Commit von Task 1 (a222711).
|
||
|
||
Sonst: Plan wie geschrieben ausgeführt. Die Metadaten-Spec liegt wie geplant als eine Datei vor. Beim Handelsware-Test wurde eine Textprüfung (`/Ein Administrator muss zuerst/`) an den neuen Wortlaut angepasst, ebenso bei der Kantinenabrechnung.
|
||
|
||
## Bekannte Stubs
|
||
|
||
Keine.
|
||
|
||
## Threat Flags
|
||
|
||
Keine neuen Angriffsflächen außerhalb des Bedrohungsmodells des Plans. Hinweis zu T-icv-09: Wer für Proxmox „Verwalten“ erhält, darf Serveradressen eintragen (private Ziele per Design); das steht im Kommentar von `proxmox-client.service.ts` und im Administrationshandbuch.
|
||
|
||
## Commits (nicht gepusht)
|
||
|
||
- `a222711` feat(module-grants): Freigabestufe Verwalten – Datenbank, Zugriffsprüfung und Kantinen-Einstellungen
|
||
- `c2ebc8d` feat(module-grants): Proxmox, Handelsware und DKV mit Freigabestufe Verwalten
|
||
- `eaf2c45` feat(module-grants): Stufe Verwalten in Freigaben-Matrix und Benutzerdetails, Doku und Changelog
|
||
|
||
## Self-Check: PASSED
|
||
|
||
- Migration, `module.guard.ts`, `use-module-capability.ts`, `module-manage-handlers.spec.ts`, `create-module-grant.dto.spec.ts` vorhanden.
|
||
- Alle drei Commit-Hashes existieren auf `main` (`git rev-list --count` über das Ledger: 3).
|
||
- Keine unbeabsichtigten Löschungen in den Commits.
|
||
|
||
## Browser-Prüfung (Orchestrator, 02.10., lokal, dunkel)
|
||
|
||
- Testgruppe „Buchhaltung Test“ mit Testbenutzer (USER) per API angelegt; in der Freigaben-Matrix Kantinenabrechnung = Verwalten, Handelsware = Benutzen gesetzt, bleibt nach Neuladen erhalten.
|
||
- Als Testbenutzer: Kantine zeigt Reiter Einstellungen, Speichern klappt; Handelsware ohne Einstellungs-Reiter; API: Handelsware-Einstellungen PUT → 403, DKV ohne Freigabe → 403.
|
||
- Korrektur: Matrix zeigte Kategorie-Kennungen („accounting“) → jetzt Anzeigenamen (Finanzbuchhaltung, Fuhrpark, …).
|
||
- Testbenutzer und Testgruppe wieder gelöscht.
|