Files
2026-09-29 07:35:42 +02:00

12 KiB

phase, plan, quick_id, subsystem, tags, status, requires, provides, affects, key-files, decisions, duration, completed, commits, plan_head_before, plan_head_after, actuals
phase plan quick_id subsystem tags status requires provides affects key-files decisions duration completed commits plan_head_before plan_head_after actuals
quick-260929-9wc 01 260929-9wc api, web, prisma
custom-modules
sidebar
iframe
rls
admin
complete
Tabelle CustomModule mit Zeilenschutz (Migration 20260929120000)
API /custom-modules (GET fuer jeden Angemeldeten, POST/PATCH/DELETE nur Admin)
Seitenleisten-Eintraege und Rahmen-Seite /modules/custom/[id]
Verwaltungsseite Verwaltung > Eigene Module
MODULE_CATEGORIES in @tessera/shared
sidebar
admin-sidebar
docs/mandantentrennung-zugriffsklassifikation.md
created modified
apps/api/prisma/migrations/20260929120000_custom_module/migration.sql
apps/api/src/custom-modules/ (Controller, Dienst, Modul, DTO, je mit Spec)
apps/web/src/lib/custom-modules-api.ts
apps/web/src/components/modules/custom-module-view.tsx
apps/web/src/app/(portal)/modules/custom/[id]/page.tsx
apps/web/src/app/(portal)/admin/custom-modules/ (page, FormModal, DeleteDialog, Test)
apps/web/src/messages/module-categories.spec.ts
apps/api/prisma/schema.prisma
apps/api/src/app.module.ts
packages/shared/src/index.ts
apps/web/src/components/layout/sidebar.tsx (+ Test)
apps/web/src/components/admin/admin-sidebar.tsx
apps/web/src/messages/de.json, en.json
apps/web/src/app/(portal)/modules/module-layouts.test.tsx
docs/mandantentrennung-zugriffsklassifikation.md
CHANGELOG.md
Gruppen-Einschraenkung (D-02) zurueckgestellt, siehe Bewusst offen
Eigene Module haengen an keiner Modul-Aktivierung (kein @UseModule, kein ModuleAccessGate)
Seitenleiste vereinheitlicht Module und eigene Module in einem internen Eintragstyp; eingebaute Module stehen je Kategorie vor eigenen
https-Regel im Web bleibt EINE (isHttpsUrl aus xframe-config), Sandbox-Wert wird importiert, nicht kopiert
ca. 10 Minuten reine Ausfuehrung 2026-09-29 3 643b1a2caa e48c0de238
tokens tasks commits
31000 3 3

Phase quick-260929-9wc Plan 01: Eigene Module Summary

Administratoren binden externe https-Seiten als Seitenleisten-Eintraege ein (Name, Adresse, Kategorie); alle Benutzer sehen sie unter der Kategorie, ein Klick zeigt die Seite in einem Rahmen mit dem XFrame-Sandbox-Wert und immer sichtbarem Knopf „In neuem Tab öffnen“. Daten liegen mandantengetrennt mit Zeilenschutz in der neuen Tabelle CustomModule.

Was gebaut wurde

Aufgabe 1 (Tracer), Commit b9d87be

  • MODULE_CATEGORIES (fuenf Kennungen) + Typ ModuleCategory in packages/shared; Gleichlauf-Spec gegen moduleCategories in de.json/en.json.
  • Prisma-Modell CustomModule und handgeschriebene Migration 20260929120000_custom_module (ENABLE/FORCE RLS, tenant_isolation_policy ohne Benutzerdimension, bewusst keine system_read_policy).
  • API /custom-modules: DTO (Name 1 bis 100, Adresse nur https ohne Zugangsdaten, max 2048, Kategorie per @IsIn), Dienst (je Methode const tenantPrisma = forTenant(this.prisma, tenantId), row.tenantId-Pruefung, 404 bei fremd/unbekannt), Controller (list vor getOne, schreibende Routen @Roles(ADMIN, SUPER_ADMIN)), Modul in app.module.ts.
  • Zugriffsklassifikation nachgemessen fortgeschrieben (siehe Zahlen).
  • Web: custom-modules-api.ts (inkl. checkCustomModuleUrl), Seitenleiste mit vereinigter Eintragsliste (Gruppierung, Suche, eingeklappte Kacheln, Auswahlmarke, Kopfzeilen-Titel ueber useNavStore mit slug = id), CustomModuleView + Seite /modules/custom/[id], Texte customModules de/en.
  • Lokal migriert (Container-IP, prisma migrate deploy), API neu gebaut; curl-Durchstich bestanden.

Aufgabe 2, Commit e7fc4de

  • Verwaltungsseite admin/custom-modules (Liste, Leer-Zustand, Anlegen/Bearbeiten-Dialog mit Adresspruefung vor dem Senden, Loeschen mit Rueckfrage), Aufruf von bumpSidebarRefresh nach jedem erfolgreichen Speichern/Loeschen, Admin-Leisten-Eintrag hinter „Module“, Texte admin.customModules und header.admin.customModules de/en.

Aufgabe 3, Commit e48c0de

  • CHANGELOG-Eintrag unter „Unveröffentlicht“ > „Neu“, alle Tore, Stack neu gebaut.

Tore (gemessen)

Tor Ergebnis
Web-Tests vollstaendig 102 Dateien, 992 Tests, alle gruen
API-Tests vollstaendig 88 Dateien, 1495 Tests, alle gruen
pnpm turbo run type-check lint 9/9 Aufgaben erfolgreich
Biome-Warnungen Web 55 (Grundlinie 55)
Biome-Warnungen API 82 (Grundlinie 82)
rls-coverage.spec / rls-access-inventory.spec gruen (30 Zusicherungen im Inventar)
prisma migrate deploy (zweiter Lauf) „No pending migrations to apply.“
prisma migrate diff enthaelt „CustomModule“ nicht
curl-Durchstich Anlegen, Liste, Einzelabruf ok; http 400; anonym 401; Loeschen; danach 404
Stack web :3000/login 200, api /health ok, GET /custom-modules anonym 401
Nicht gepusht git branch -r --contains HEAD leer

Zugriffsklassifikation nachgemessen (Gate-Schleife, nur .ts ohne spec):

  • Summe vorher gemessen 61/217/6 (Dokument nannte 61/216/6); Drift in user: gemessen 18 gebunden statt 17 (aus quick-260928-ujj), korrigiert.
  • custom-modules: 0/7/0 (list 1, getOne 1, create 1, update 2, remove 2).
  • Neue Summe: 61/224/6.
  • Klassen-Verteilung: Ueberschrift/Tabelle nannten 77 Paare/40 muss, Bestandsaufnahme hatte schon 78/41 (grep -cE '^\| apps/api/src/'); nach neuem Eintrag 79 Paare, davon 42 muss, 21 keine-mandantengebundene-tabelle, 14 beides, 2 bewusst-uebergreifend. Nachtrag-Absatz „quick-260929-9wc“ ergaenzt.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Endlosschleife beim Laden der Verwaltungsseite

  • Found during: Aufgabe 2 (Test zaehlte 5 statt 2 Listenabrufe)
  • Issue: fetchModules hing per useCallback an t (Uebersetzungsfunktion); ein Mock liefert je Render eine neue Funktion, der Effekt lief erneut. Auch mit echtem next-intl fragil.
  • Fix: Fehler als Boolean loadFailed gefuehrt, Text erst im JSX uebersetzt; fetchModules ohne Abhaengigkeit.
  • Files modified: apps/web/src/app/(portal)/admin/custom-modules/page.tsx
  • Commit: e7fc4de

2. [Rule 3 - Blocking] Layout-Waechter der Modulordner

  • Found during: Aufgabe 3 (voller Web-Testlauf)
  • Issue: module-layouts.test.tsx (T-e8k-04) verlangt in jedem nicht-dynamischen Ordner unter modules/ eine layout.tsx mit ModuleAccessGate; der neue Ordner custom/ ist bewusst fuer alle sichtbar (D-01) und hat keine Schranke.
  • Fix: Explizite, begruendete Ausnahmeliste DIRS_WITHOUT_GATE = ['custom'] im Test, statt eine wirkungslose Durchreich-Layout-Datei anzulegen.
  • Files modified: apps/web/src/app/(portal)/modules/module-layouts.test.tsx
  • Commit: e48c0de

3. Plan-Feinheit (kein Regelfall): Die Seitenleisten-Fehlerbehandlung fuer listCustomModules laesst bei Fehler den bisherigen Stand stehen (leer beim ersten Laden), wie der Modulabruf, statt aktiv zu leeren; Ergebnis beim ersten Laden identisch mit „leere Liste“.

Bewusst offen

  • Gruppen-Einschraenkung fuer eigene Module (D-02) zurueckgestellt. ModuleGrant.moduleId ist ein Pflicht-Fremdschluessel auf Module (onDelete: Cascade); eigene Module sind keine Module-Zeilen. Eine Einschraenkung braeuchte eine neue Freigabetabelle oder einen Umbau von ModuleGrant samt module-access.service.ts und der Admin-Freigabeoberflaeche, also nicht „sehr wenig Aufwand“. Im Code nichts dafuer gebaut.

Browser-Pruefung offen (Orchestrator)

Playwright MCP steht in diesem Ausfuehrungskontext nicht zur Verfuegung. Der Orchestrator fuehrt die Pruefung durch: web :3000, Anmeldung admin / admin123, echte Navigation (browser_navigate), nie per fetch() aus der Seite messen, zuerst ueber den Theme-Knopf der Kopfzeile auf dunkel schalten. Stack ist neu gebaut und laeuft.

  1. Verwaltung > „Eigene Module“ (neuer Eintrag in der Admin-Leiste hinter „Module“, „Module“ dabei nicht markiert): Leer-Zustand mit Knopf „Eigenes Modul anlegen“.
  2. Anlegen mit Name „Beispielseite“, Adresse http://example.com -> Meldung, nichts gespeichert; dann https://user:pw@example.com -> Meldung; dann https://example.com, Kategorie „Infrastruktur“ -> gespeichert, Tabelle zeigt den Eintrag, die Seitenleiste zeigt „Beispielseite“ unter „Infrastruktur“ OHNE Neuladen.
  3. Zweiter Eintrag „GitHub“, https://github.com, Kategorie „Sicherheit“ -> erscheint unter „Sicherheit“.
  4. Klick auf „Beispielseite“: /modules/custom/<id>, Kopfzeilen-Titel „Beispielseite“, Auswahlmarke am Eintrag, Rahmen fuellt den Inhaltsbereich ohne doppelten Rollbalken, „In neuem Tab öffnen“ sichtbar; iframe traegt den Sandbox-Wert allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox und referrerpolicy="no-referrer"; der Link oeffnet einen neuen Tab mit example.com.
  5. Klick auf „GitHub“: Rahmen zeigt die Einbettungssperre des Browsers, „In neuem Tab öffnen“ ist trotzdem sichtbar und funktioniert.
  6. Seitenleiste eingeklappt: beide Eintraege als Kachel mit Namen im Tooltip; Suche „Beisp“ findet den Eintrag.
  7. Bearbeiten: „Beispielseite“ in „Beispiel“ umbenennen -> Seitenleiste zieht sofort nach. Loeschen mit Rueckfrage -> Eintrag verschwindet aus Tabelle und Seitenleiste; alte Adresse /modules/custom/<id> zeigt „Dieses Modul gibt es nicht mehr.“
  8. Sprache auf Englisch: keine rohen Uebersetzungsschluessel auf Verwaltungsseite und Rahmen-Seite.
  9. Screenshots (dunkel) von Verwaltungsseite, Seitenleiste mit Eintraegen und Rahmen-Seite ablegen; danach die Testeintraege loeschen, damit die lokale Datenbank sauber bleibt.

Hinweis: Der curl-Durchstich hat seinen Testeintrag bereits geloescht; die lokale Datenbank enthaelt keine eigenen Module.

Known Stubs

Keine.

Threat Flags

Keine neue Angriffsflaeche ausserhalb des Plan-Bedrohungsmodells (T-9WC-01 bis 07 umgesetzt: Rollen-Metadaten per Spec geprueft, tenantId nur aus req.tenantId, https-Regel in API und Web, exakter Sandbox-Wert, Referrer/rel, MaxLength, whitelist: true).

Nichts gepusht

Drei lokale Commits (b9d87be, e7fc4de, e48c0de), kein git push; die vorgemerkten Loeschungen von .planning/.continue-here.md und .planning/HANDOFF.json blieben unangetastet im Index.

Self-Check: PASSED

  • Dateien vorhanden: Migration, custom-modules.service.ts/controller.ts/module.ts/dto, custom-modules-api.ts, custom-module-view.tsx, modules/custom/[id]/page.tsx, admin/custom-modules/page.tsx mit Komponenten (alle im Commit-Stat sichtbar).
  • Commits vorhanden: b9d87be, e7fc4de, e48c0de (git log), commits: 3 gemessen ueber rev-list vom Ledger.

Browser-Pruefung (Orchestrator, 29.09., dunkel)

Durchgefuehrt per Playwright MCP auf :3000, Theme per Kopfzeilen-Knopf auf „Dunkel“:

  1. Verwaltung > „Eigene Module“: Eintrag in der Admin-Leiste, Leer-Zustand korrekt.
  2. http://example.com -> „Bitte geben Sie eine Adresse ein, die mit https:// beginnt.“; https://user:pw@example.com -> „Die Adresse darf keinen Benutzernamen und kein Kennwort enthalten.“; https://example.com / Infrastruktur -> gespeichert, Seitenleiste zeigt „Beispielseite“ ohne Neuladen.
  3. „GitHub“ / Sicherheit erscheint unter „Sicherheit“.
  4. Rahmen-Seite: Kopfzeilen-Titel, Auswahlmarke, kein doppelter Rollbalken; sandbox = allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox, referrerpolicy = no-referrer; Link target=_blank rel="noopener noreferrer".
  5. GitHub: Einbettung per frame-ancestors blockiert, „In neuem Tab öffnen“ oeffnet github.com im neuen Tab.
  6. Eingeklappt: Eintraege als Symbole; Suche „Beisp“ findet den Eintrag.
  7. Umbenennen zieht Seitenleiste sofort nach; Loeschen mit Rueckfrage; alte Adresse zeigt „Dieses Modul gibt es nicht mehr.“
  8. Englisch: nicht per Oberflaeche umgeschaltet; stattdessen Schluessel-Paritaet de/en geprueft (keine fehlenden Schluessel, alle Texte ueber t()).
  9. Testeintraege geloescht, lokale DB ohne eigene Module.