Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
13 KiB
phase, plan, quick_id, subsystem, tags, status, requires, provides, key-files, decisions, completed, commits, plan_head_before, plan_head_after, actuals
| phase | plan | quick_id | subsystem | tags | status | requires | provides | key-files | decisions | completed | commits | plan_head_before | plan_head_after | actuals | |||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260929-dzu | 01 | 260929-dzu | api, web, prisma |
|
complete |
|
|
|
|
2026-09-29 | 3 | 76a923450f |
8f41bd26bd |
|
Phase quick-260929-dzu Plan 01: Eigene Module fuer jeden Benutzer Summary
Jeder angemeldete Benutzer legt unter Einstellungen > Eigene Module persoenliche Seitenleisten-Eintraege an, die nur er sieht; Administratoren pflegen weiter gemeinsame Eintraege unter Verwaltung > Eigene Module (Senden von shared: true). Niemand kann etwas in die Seitenleiste eines anderen Benutzers legen.
Was gebaut wurde
Aufgabe 1, Commit c703d87 (Modell + API, Tests zuerst angepasst)
- Schema:
ownerUserId String?mit Relation zuUser(onDelete: Cascade), Index[tenantId, ownerUserId]; GegenfeldcustomModulesamUser. - Migration
20260929130000_custom_module_owner(von Hand, mit Kopfkommentar): Spalte, Index, Fremdschluessel, alte Regel ersetzt durch vier Regeln. - Dienst/Controller:
GET /custom-modulesliefert gemeinsame plus eigene Zeilen mitpersonal: boolean(ownerUserId wird nicht ausgeliefert);GET :id404 bei fremdem persoenlichem Eintrag (auch fuer Administratoren);POSTfuer jeden Angemeldeten,shared: truenur fuer ADMIN/SUPER_ADMIN (sonst 403), Standard persoenlich;PATCH/DELETE: persoenlich nur Besitzer (fremd: 404), gemeinsam nur Administrator (sonst 403).shared/ownerUserIdsind per PATCH nicht aenderbar (OmitTypeim DTO pluswhitelist). - Routen:
liststeht weiter vorgetOne; kein@Rolesmehr an den Schreibrouten, die Rollenpruefung sitzt im Dienst. - Specs: Dienst (22 Faelle: A sieht/aendert/loescht B nicht, Nicht-Admin nicht shared, Admin persoenlich vs. gemeinsam, RLS-Bindung mit/ohne Benutzer), Controller, DTO-Pipe-Faelle.
- Zugriffsklassifikation nachgemessen (siehe unten).
Aufgabe 2, Commit ee97b4e (Web)
- Neue Seite
/settings/custom-modulesund Nav-Eintrag „Eigene Module“ unter „Allgemein“. - Gemeinsame Komponenten unter
components/custom-modules/:CustomModuleFormModalundDeleteCustomModuleDialog(verschoben, Parametershared) plus neuCustomModuleManager(Liste, Anlegen/Bearbeiten/Loeschen,bumpSidebarRefresh), aufgerufen mitscope="shared"(Verwaltung) oderscope="personal"(Einstellungen). Filter ueberpersonalim Web. - Verwaltung sendet beim Anlegen
shared: true, zeigt nur gemeinsame Eintraege, Einleitung nennt „alle Benutzer“; Einstellungen senden keinshared, Einleitung: „Diese Einträge sehen nur Sie.“ - Texte de/en (Namensraeume
customModules.form,customModules.manage,settings.customModules), Umlaut-Waechter gruen. - Tests: neuer Settings-Test (7), Admin-Test angepasst (
shared: true, Filter; 14).
Aufgabe 3, Commit 8f41bd2 (Doku) + Neubau
- CHANGELOG-Punkt „Eigene Module“ umgeschrieben (Einstellungen fuer jeden, Verwaltung zusaetzlich fuer alle),
docs/anleitung-anwender.md(Abschnitt „Allgemein > Eigene Module“) unddocs/anleitung-administration.md(Unterabschnitt bei 5.). docker compose up -d --build web api: web :3000/login 200, api /health ok,GET /custom-modulesanonym 401,/settings/custom-modulesohne Anmeldung 307 (Umleitung auf Login).
RLS-Muster und Begruendung
Gefolgt bin ich dem Muster SearchProvider aus 20260911120000_rls_user_dimension_personal_tables: nullable Besitzerspalte, vier nach Befehl getrennte Regeln.
- SELECT: Mandant UND (kein Benutzer gesetzt ODER
ownerUserId IS NULLODERownerUserId = current_user_id()). - INSERT/UPDATE/DELETE: Mandant UND (kein Benutzer gesetzt ODER
ownerUserId = current_user_id()).
Warum nicht das einfachere Muster DashboardImage/Favorite (Pflicht-userId, eine Regel): eigene Module haben gemeinsame Zeilen (NULL), die jeder lesen, aber nur ein Administrator schreiben darf. Eine einzelne Regel, die die gemeinsame Zeile zum Lesen freigibt, wuerde sie auch zum Aendern/Loeschen freigeben (Praezedenz 260910-jab (3)), deshalb getrennte Befehle. Folge: ein Benutzerkontext kann gemeinsame Zeilen nicht schreiben; der Dienst bindet Schreibzugriffe auf gemeinsame Eintraege deshalb bewusst OHNE Benutzer (forTenant(prisma, tenantId)), nachdem er die Administrator-Rolle geprueft hat. Persoenliche Zugriffe binden mit Benutzer. Wie bei allen RLS-Regeln wirkt der Schutz erst mit dem Datenbankrollen-Schalter (heute AUS); bis dahin tragen die Anwendungspruefungen (row.tenantId, ownerUserId) den Schutz.
Curl-Pruefung (lokal, echte API :3001)
Benutzer: admin (SUPER_ADMIN), testuser (USER), curltmp (USER, nur fuer die Pruefung angelegt und danach geloescht).
| Fall | Ergebnis |
|---|---|
| USER legt Eintrag ohne shared an | 200, personal: true |
USER shared: true |
403 „Gemeinsame Einträge dürfen nur Administratoren anlegen“ |
Admin shared: true |
200, personal: false |
| Admin ohne shared | 200, personal: true |
| Liste USER | gemeinsam + eigener |
| Liste zweiter USER | nur gemeinsam |
| Liste Admin | nur gemeinsam (persoenliche Eintraege anderer nicht) |
| zweiter USER: GET / PATCH / DELETE auf fremden persoenlichen Eintrag | 404 / 404 / 404 |
| Admin: GET / DELETE auf persoenlichen Eintrag eines Benutzers | 404 / 404 |
| USER GET gemeinsam | 200 |
| USER PATCH / DELETE gemeinsam | 403 / 403 |
Admin PATCH gemeinsam (mit eingeschmuggeltem shared:false) |
200, bleibt gemeinsam |
USER PATCH eigenen mit shared:true, ownerUserId:null |
200, bleibt persoenlich |
| http-Adresse | 400 |
| anonym | 401 |
| Benutzer loeschen -> seine persoenlichen Eintraege | Cascade, 0 Zeilen |
Alle Testeintraege sind geloescht, CustomModule ist leer.
Tore (gemessen)
| Tor | Ergebnis |
|---|---|
| API-Tests vollstaendig | 88 Dateien, 1511 Tests gruen |
| Web-Tests vollstaendig | 103 Dateien, 1003 Tests gruen |
pnpm turbo run type-check lint --force |
9/9 erfolgreich |
| Biome-Warnungen Web / API | 55 (Grundlinie 55) / 82 (Grundlinie 82) |
| rls-coverage / rls-access-inventory | gruen |
prisma migrate deploy lokal (Container-IP 172.19.0.2) |
Migration angewendet, migrate diff danach leer |
| Zugriffsklassifikation | Gate-Schleife 61/223/6 (vorher 61/224/6); custom-modules 0/6/0 |
Testbenutzer fuer die Browser-Pruefung des Orchestrators
Es gab lokal schon die Nicht-Admin-Konten nutzer1 und nutzer2, deren Passwoerter aber nicht bekannt sind. Deshalb habe ich per Admin-API angelegt: Login testuser, Passwort Test1234!test (Rolle USER, mustChangePassword auf false gesetzt, damit die Anmeldung nicht auf die Passwort-Seite umleitet). Der Administrator ist wie gehabt admin / admin123.
Vorschlag fuer die Browser-Pruefung (dunkel): als testuser unter Einstellungen > Allgemein > Eigene Module einen Eintrag anlegen (Seitenleiste zieht ohne Neuladen nach), als admin unter Verwaltung > Eigene Module einen gemeinsamen Eintrag anlegen (testuser sieht ihn in der Seitenleiste, kann ihn unter Einstellungen aber nicht bearbeiten), als admin pruefen, dass der persoenliche Eintrag von testuser weder in Seitenleiste noch Verwaltung erscheint. Danach die Testeintraege loeschen.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Festplatte voll (0 Byte frei) mitten in der Arbeit
- Found during: Aufgabe 2 (Biome meldete „No space left on device“)
- Issue: die Docker-Build-Cache-Ablagen der Neubauten fuellten die Platte.
- Fix:
docker builder prune -f(nur Build-Cache, 17,97 GB, keine Images, Container oder Volumes); danach type-check/lint/Tests frisch und vollstaendig wiederholt, alle gruen. - Commit: kein Code betroffen.
2. [Rule 1 - Bug] Detektor-Vorgaben fuer forTenant
- Found during: Aufgabe 1 (rls-access-inventory schlug zweimal fehl)
- Issue: eine Ternary-Bindung (
shared ? forTenant(..) : forTenant(..)) und einclient.customModule.createin einer Hilfsfunktion werden vom Detektor nicht als Zuweisungsform/Modellaufruf erkannt. - Fix: je Zweig
const tenantPrisma = forTenant(...)mit direktem Modellaufruf; Ausnahmeliste unveraendert leer. - Files modified:
apps/api/src/custom-modules/custom-modules.service.ts - Commit:
c703d87
3. Plan-Feinheit: Kein API-Parameter scope; die Verwaltung filtert im Web ueber personal (Plan liess beides zu, „das Einfachste“). Nebenwirkung: die Verwaltungsseite laedt auch die eigenen persoenlichen Eintraege des Administrators und blendet sie aus.
4. Plan-Feinheit: Formular-/Dialog-Texte aus admin.customModules in den neuen Namensraum customModules.form umgezogen (beide Aufrufer teilen sie); Admin-Test entsprechend angepasst. Die Anleitung des Anwenders hatte den Punkt „Eigene Module“ vorher nicht, er ist jetzt neu beschrieben (der Plan sprach von „aktualisieren“).
Hinweise
- Zwischen
c703d87undee97b4eliegt ein fremder Commitbc4c011(fix(web) Widgets nicht mehr zur Mitte versetzen), nicht von diesem Plan; er beruehrt CHANGELOG.md unddocs/anleitung-anwender.mdan anderen Stellen. Die 3 Commits dieses Plans sindc703d87,ee97b4e,8f41bd2(git rev-listab dem Vorgaenger vonc703d87zaehlt 4 inklusive des fremden). Der Ledger nach Protokoll 0c wurde nicht vor dem ersten Commit angelegt,plan_head_beforeist deshalb der Vorgaenger vonc703d87. - Die Verwaltungs-Nav zeigt weiterhin „Eigene Module“; sie fuehrt jetzt auf die gemeinsamen Eintraege, die Einleitung nennt das.
- Nichts gepusht.
Known Stubs
Keine.
Threat Flags
Keine neue Angriffsflaeche ausserhalb des bestehenden Modells: ownerUserId kommt nie aus dem Body (Whitelist, im Test belegt), shared ist per PATCH nicht setzbar, fremde persoenliche Eintraege sind ununterscheidbar 404.
Self-Check: PASSED
- Dateien vorhanden: Migration
20260929130000_custom_module_owner,custom-module-manager.tsx,settings/custom-modules/page.tsx, Settings-Test. - Commits vorhanden:
c703d87,ee97b4e,8f41bd2(git log); nichts gepusht (git branch -r --contains HEADleer).
Browser-Pruefung (Orchestrator, 29.09.)
- testuser: Einstellungen > Eigene Module vorhanden; „Meine Seite“ angelegt -> sofort in eigener Seitenleiste (Infrastruktur).
- admin: sieht „Meine Seite“ weder in Seitenleiste noch Verwaltung; Direktlink zeigt „Dieses Modul gibt es nicht mehr.“; Verwaltung heisst „Gemeinsamen Eintrag anlegen“.
- admin legt „Firmenseite“ (Sicherheit) an -> testuser sieht sie in der Seitenleiste, nicht in seinen Einstellungen; DELETE als testuser -> 403.
- Nebenbei: Widgets nicht mehr zentriert (
bc4c011) — alle linken Kanten am Raster (272 px bei Rasterbeginn 260 + 12 Rand). - Testeintraege geloescht, CustomModule leer.