5e0e408f0f
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
244 lines
14 KiB
Markdown
244 lines
14 KiB
Markdown
---
|
|
phase: quick-260914-ebg
|
|
plan: 01
|
|
subsystem: auth
|
|
tags: [nestjs, prisma, rbac, vitest, biome]
|
|
|
|
requires:
|
|
- phase: quick-260911-fh9
|
|
provides: "Zielrollen-Riegel-Vorlage in AuthService.adminResetPassword (T-FH9-04) — dieselbe Bedingung (user.role === SUPER_ADMIN && callerRole !== SUPER_ADMIN) wird hier in UserController.update()/remove() uebernommen"
|
|
provides:
|
|
- "Zielrollen-Riegel in UserController.update() und UserController.remove(): ein ADMIN kann den SUPER_ADMIN seines eigenen Mandanten weder aendern noch loeschen"
|
|
- "acht neue Tests (Test 9-16) in user.controller.spec.ts, Spec-Gesamtzahl 8 -> 16"
|
|
- "WINDOWS #29 geschlossen (fixed); zwei neue Ledger-Eintraege #35 (Biome-Konfiguration defekt) und #36 (Frontend verschluckt 403 still)"
|
|
affects: [user-management, auth, windows-ledger]
|
|
|
|
actuals:
|
|
tokens: 6669
|
|
tasks: 3
|
|
commits: 3
|
|
plan_head_before: e76c0f8a3371d6ac210bce5e1d2ce0fe1d372e8c
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Zielrollen-Riegel-Muster: Mandantengrenze IMMER vor Zielrollen-Pruefung, damit die Fehlermeldung nichts ueber die Rolle eines fremdmandantigen Benutzers verraet (jetzt an zwei Stellen: AuthService.adminResetPassword, UserController.update/remove)"
|
|
|
|
key-files:
|
|
created: []
|
|
modified:
|
|
- apps/api/src/user/user.controller.ts
|
|
- apps/api/src/user/user.controller.spec.ts
|
|
- apps/api/src/auth/auth.service.ts
|
|
- .planning/WINDOWS.md
|
|
|
|
key-decisions:
|
|
- "Sechs ueberfluessige `as any`-Umschreibungen in den neuen Tests entfernt (Rule 1) — UpdateUserDto ist vollstaendig optional, die Objektliteral-Zuweisung ist ohne Umschreibung typkorrekt, und die Umschreibungen trieben die Biome-Warnungen der Spec-Datei ueber die Baseline (25)"
|
|
|
|
requirements-completed: [WINDOWS-29, T-FH9-05]
|
|
|
|
coverage:
|
|
- id: D1
|
|
description: "ADMIN kann SUPER_ADMIN des eigenen Mandanten weder per PATCH aendern (Kennwort, isActive, Rolle) noch per DELETE loeschen — ForbiddenException, Dienst nicht aufgerufen"
|
|
requirement: "WINDOWS-29"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/user/user.controller.spec.ts#Test 9: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten weder übernehmen..."
|
|
status: pass
|
|
- kind: unit
|
|
ref: "apps/api/src/user/user.controller.spec.ts#Test 13: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten nicht löschen..."
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D2
|
|
description: "Regressionsschutz: SUPER_ADMIN gegen SUPER_ADMIN und ADMIN gegen USER bleiben auf beiden Wegen erlaubt"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/user/user.controller.spec.ts#Test 10, Test 11, Test 14, Test 15"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D3
|
|
description: "Reihenfolge: Mandantengrenze VOR Zielrollen-Pruefung in beiden Handlern — die Mandanten-Meldung verraet nichts ueber die Rolle eines fremdmandantigen Benutzers"
|
|
requirement: "T-EBG-04"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/user/user.controller.spec.ts#Test 12, Test 16"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D4
|
|
description: "Falsifizierung: Rueckbau des Riegels macht genau Test 9 und Test 13 rot, byte-identisch wiederhergestellt"
|
|
verification:
|
|
- kind: other
|
|
ref: "git apply -R Rueckbau-Lauf, siehe Abschnitt Nachweis WINDOWS #29 — Rueckbau unten"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D5
|
|
description: "Kopfkommentar AuthService.adminResetPassword nennt T-FH9-05 nicht mehr als offen"
|
|
requirement: "T-FH9-05"
|
|
verification:
|
|
- kind: other
|
|
ref: "grep -c T-FH9-05 apps/api/src/auth/auth.service.ts -> 0"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D6
|
|
description: "WINDOWS-Ledger: #29 fixed, #35 und #36 als eigene Nebenbefunde eingetragen, nicht mitgeschlossen"
|
|
verification:
|
|
- kind: other
|
|
ref: "node gsd-tools.cjs windows fixed 29; windows append (2x); Frontmatter-Gegenprobe"
|
|
status: pass
|
|
human_judgment: false
|
|
|
|
duration: 6min
|
|
completed: 2026-09-14
|
|
status: complete
|
|
---
|
|
|
|
# Quick 260914-ebg: Zielrollen-Riegel in UserController.update/remove Summary
|
|
|
|
**Zielrollen-Riegel (Vorlage aus AuthService.adminResetPassword, T-FH9-04) in UserController.update() und remove() eingezogen — ein ADMIN kann den SUPER_ADMIN seines Mandanten nicht mehr uebernehmen, aussperren, herabstufen oder loeschen; acht neue Tests, Falsifizierung durch Rueckbau bestanden, WINDOWS #29 geschlossen.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** 6 min
|
|
- **Started:** 2026-09-14T10:33:00+02:00 (Baseline-Lauf vor Task 1)
|
|
- **Completed:** 2026-09-14T10:38:29+02:00 (Push)
|
|
- **Tasks:** 3
|
|
- **Files modified:** 4
|
|
|
|
## Accomplishments
|
|
- Zielrollen-Riegel in `UserController.update()` (nach der Mandantengrenze, vor der `dto.role`-Pruefung) und `UserController.remove()` (nach der Mandantengrenze, vor `userService.delete`), jeweils mit `ForbiddenException` und Kommentar, der auf WINDOWS #29 und die Vorlage T-FH9-04 verweist
|
|
- Acht neue Tests (Test 9-16) in `user.controller.spec.ts`: drei Angriffsformen gegen den SUPER_ADMIN (Kennwort, isActive, Rolle), zwei Regressionstests (SUPER_ADMIN gegen SUPER_ADMIN, ADMIN gegen USER) je Handler, zwei Ordnungstests (Mandantengrenze vor Zielrolle)
|
|
- Falsifizierung: Rueckbau des Task-1-Commits macht genau Test 9 und Test 13 rot, danach byte-identisch wiederhergestellt
|
|
- Kopfkommentar `AuthService.adminResetPassword` fortgeschrieben — die alte Ledger-Kennung T-FH9-05 kommt in der Datei nicht mehr vor
|
|
- WINDOWS #29 auf `fixed` gesetzt; zwei neue, eigenstaendige Nebenbefunde #35 (Biome-Konfiguration defekt) und #36 (Frontend verschluckt 403 still) eingetragen, nicht mit #29 mitgeschlossen
|
|
|
|
## Task Commits
|
|
|
|
Alle drei Aufgaben wurden einzeln committet:
|
|
|
|
1. **Task 1: Zielrollen-Riegel in update() und remove() — Tests zuerst (RED), dann Riegel (GREEN)** - `759ea3b` (fix)
|
|
2. **Task 2: Falsifizierung durch Rueckbau, Kopfkommentar der Vorlage nachziehen, Gesamt-Gates** - `63f9df0` (docs)
|
|
3. **Task 3: Ledger — #29 schliessen, zwei Nebenbefunde eintragen, pushen** - `70d007b` (docs)
|
|
|
|
_Task 1 ist TDD: Tests wurden vor dem Riegel geschrieben (RED), dann der Riegel eingezogen (GREEN) — beides im selben Commit, da RED und GREEN Teil derselben Aufgabe und desselben Nachweises sind._
|
|
|
|
## Nachweis WINDOWS #29 — Rueckbau
|
|
|
|
**RED-Lauf (Task 1, Schritt A — vor dem Riegel, Tests bereits vorhanden):**
|
|
|
|
```
|
|
Test Files 1 failed (1)
|
|
Tests 2 failed | 14 passed (16)
|
|
```
|
|
|
|
Rot: `Test 9: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten weder übernehmen (Kennwort setzen), noch aussperren (isActive=false), noch herabstufen (role=USER) — alle drei Angriffsformen werden mit der Zielrollen-Ausnahme abgelehnt, und der Dienst wird in keinem der drei Fälle aufgerufen` (Fehler: `Cannot destructure property 'passwordHash' of 'updated' as it is undefined` statt der erwarteten `Cannot modify a SUPER_ADMIN user`) und `Test 13: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten nicht löschen — die Zielrollen-Ausnahme greift, und der Dienst wird nicht aufgerufen` (Promise loeste mit `{ message: 'User deleted' }` auf statt abzulehnen). Alle anderen 14 Tests gruen.
|
|
|
|
Nach dem Riegel (GREEN): `Tests 16 passed (16)`.
|
|
|
|
**Falsifizierungs-Rueckbau (Task 2, Schritt A — gegen den committeten Stand):**
|
|
|
|
```bash
|
|
git show HEAD -- apps/api/src/user/user.controller.ts > $SCR/riegel.patch && git apply -R $SCR/riegel.patch
|
|
pnpm -C apps/api exec vitest run src/user/user.controller.spec.ts
|
|
```
|
|
|
|
Ergebnis, woertlich:
|
|
|
|
```
|
|
Test Files 1 failed (1)
|
|
Tests 2 failed | 14 passed (16)
|
|
```
|
|
|
|
Rot exakt dieselben zwei: `Test 9` (`AssertionError: expected [Function] to throw error including 'Cannot modify a SUPER_ADMIN user' but got 'Cannot destructure property \'passwor…'`) und `Test 13` (`AssertionError: promise resolved "{ message: 'User deleted' }" instead of rejecting`). Die anderen 14 Tests blieben gruen — der Riegel ist damit als notwendig fuer genau diese zwei Verhaltensnachweise belegt.
|
|
|
|
**Wiederherstellung:**
|
|
|
|
```bash
|
|
git checkout -- apps/api/src/user/user.controller.ts
|
|
git status --porcelain apps/api/src/user/user.controller.ts
|
|
```
|
|
|
|
Ausgabe der zweiten Zeile: leer (byte-identisch wiederhergestellt). Spec danach erneut `Tests 16 passed (16)`.
|
|
|
|
## Gates
|
|
|
|
1. `pnpm -C apps/api exec vitest run` -> `Test Files 62 passed (62)` / `Tests 1028 passed (1028)` (Baseline 1020/62 plus 8 neue Tests)
|
|
2. `pnpm -C apps/api exec tsc --noEmit; echo EXIT=$?` -> `EXIT=0`
|
|
3. `D=$(git diff --stat 37a2f73 -- . ':!.planning'); echo GIT_EXIT=$?; tail -n1 <<< "$D"` -> `GIT_EXIT=0`, `3 files changed, 136 insertions(+), 2 deletions(-)`
|
|
4. `grep -cE "^\| 29 \|.*\| fixed \|" .planning/WINDOWS.md` -> `1`
|
|
|
|
**Biome-Zahlentripel (Ersatzkonfiguration im Scratchpad, relativ zur Baseline — `biome.json` im Repo unangetastet):**
|
|
|
|
| Datei | Fehler | Warnungen | Infos | Baseline-Warnungen |
|
|
|-------|--------|-----------|-------|---------------------|
|
|
| `apps/api/src/user/user.controller.ts` | 0 | 22 | 2 | 22 |
|
|
| `apps/api/src/user/user.controller.spec.ts` | 0 | 25 | 0 | 25 |
|
|
| `apps/api/src/auth/auth.service.ts` | 0 | 20 | 1 | 20 |
|
|
|
|
Alle drei Dateien treffen die Baseline exakt (nach dem Rule-1-Nebenfund unten). Kein neues `any`, kein neuer Import.
|
|
|
|
**git status --porcelain nach Task 3 (Arbeitsbaum sauber):**
|
|
|
|
```
|
|
(leer)
|
|
```
|
|
|
|
**git log e76c0f8..HEAD:**
|
|
|
|
```
|
|
70d007b docs(quick-260914-ebg): Ledger — WINDOWS #29 fixed, Nebenbefunde Biome-Konfiguration und stilles 403 im Frontend eingetragen
|
|
63f9df0 docs(quick-260914-ebg): Kopfkommentar adminResetPassword — Schwesterwege PATCH/DELETE /users/:id geschlossen (WINDOWS #29)
|
|
759ea3b fix(quick-260914-ebg): Zielrollen-Riegel in UserController.update/remove — ADMIN kann SUPER_ADMIN nicht mehr aendern oder loeschen (WINDOWS #29)
|
|
```
|
|
|
|
## Files Created/Modified
|
|
- `apps/api/src/user/user.controller.ts` - Zielrollen-Riegel in `update()` und `remove()`, je ein Kommentarblock mit Verweis auf WINDOWS #29 und T-FH9-04
|
|
- `apps/api/src/user/user.controller.spec.ts` - neuer describe-Block mit acht Tests (Test 9-16), Spec-Gesamtzahl 16
|
|
- `apps/api/src/auth/auth.service.ts` - Kopfkommentar `adminResetPassword` fortgeschrieben (T-FH9-05 nicht mehr offen)
|
|
- `.planning/WINDOWS.md` - #29 `fixed`, #35 und #36 neu (`quick-260914-ebg`, `deviation`)
|
|
|
|
## Decisions Made
|
|
- Zielrollen-Riegel als eigenstaendige `if`-Pruefung nach der Mandantengrenze eingezogen, nicht als Erweiterung der bestehenden `dto.role`-Pruefung — die bestehende Pruefung sichert die NEUE Rollenzuweisung, der neue Riegel sichert das bereits vorhandene ZIEL; beide bleiben unabhaengig lesbar
|
|
- Mandantengrenze bewusst VOR der Zielrollen-Pruefung belassen (nicht umgestellt), damit ein Ordnungsfehler durch die Ordnungstests (Test 12, Test 16) sofort rot wird
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 1 - Bug] Sechs ueberfluessige `as any`-Umschreibungen in den neuen Tests entfernt**
|
|
- **Found during:** Task 2, Schritt C.3 (Biome-Gate)
|
|
- **Issue:** Die acht neuen Tests in `user.controller.spec.ts` trugen sechs `as any`-Umschreibungen bei den `UpdateUserDto`-Objektliteralen (`{ password: '...' } as any` usw.). Der Plan hatte in `planning_measurements` bereits festgehalten, dass `UpdateUserDto` vollstaendig optional ist und die Objektliterale ohne Umschreibung zuweisbar sind — die Umschreibungen waren unnoetig und trieben die Biome-Warnungen von `user.controller.spec.ts` von der Baseline 25 auf 31 (relative Ersatzkonfiguration, `noExplicitAny`-Familie).
|
|
- **Fix:** Alle sechs `as any` an den betroffenen Aufrufstellen entfernt (Test 9, Test 10, Test 11, Test 12).
|
|
- **Files modified:** `apps/api/src/user/user.controller.spec.ts`
|
|
- **Verification:** `pnpm -C apps/api exec vitest run src/user/user.controller.spec.ts` weiterhin `Tests 16 passed (16)`; Biome-Gate danach `Found 25 warnings.` (Baseline exakt getroffen)
|
|
- **Committed in:** `63f9df0` (Task 2 Commit, zusammen mit dem Kopfkommentar in `auth.service.ts`, da beide Aenderungen aus demselben Gate-Durchlauf stammen)
|
|
|
|
---
|
|
|
|
**Total deviations:** 1 auto-fixed (Rule 1)
|
|
**Impact on plan:** Reine Aufraeumarbeit an eigenem, in Task 1 neu geschriebenem Testcode — kein Scope-Creep, keine Verhaltensaenderung, Biome-Schwelle nicht angehoben, sondern die Baseline exakt wiederhergestellt.
|
|
|
|
## Nebenbefunde
|
|
|
|
**#35 (Biome-Konfiguration defekt, `biome.json`):** Biome ist im Bestand nicht lauffaehig — `biome.json` traegt den in Biome 2.5.0 unbekannten Schluessel `organizeImports` (gehoert unter `assist`), und es fehlt `javascript.parser.unsafeParameterDecoratorsEnabled`, ohne den jeder NestJS-Parameter-Dekorator ein Parse-Fehler ist. Der CI-Schritt „Lint" ruft `pnpm lint` = `turbo lint`, doch keine App hat ein `lint`-Skript — der Schritt ist ein Leerlauf, der gruen meldet. Als eigener Ledger-Eintrag festgehalten (nicht in #29 mitgeschlossen), `biome.json` liegt ausserhalb der Erlaubnisliste dieses Plans.
|
|
|
|
**#36 (Frontend verschluckt 403 still, `apps/web/.../admin/users/page.tsx`):** `handleSubmit` und `handleDelete` pruefen nur `res.ok` ohne `else`-Zweig und fangen mit leerem `catch` — ein 403 fuehrt zu keiner sichtbaren Reaktion. Bestehendes Verhalten fuer alle 403-Wege, aber seit diesem Plan (WINDOWS #29) fuer einen ADMIN im Alltag erstmals erreichbar, weil die SUPER_ADMIN-Zeile in der eigenen Benutzerliste steht und Aendern/Loeschen darauf jetzt 403 liefert. Als eigener Ledger-Eintrag festgehalten, Frontend von diesem Plan nicht geaendert (ausserhalb der Erlaubnisliste).
|
|
|
|
## Issues Encountered
|
|
None - die Umsetzung folgte dem Plan, bis auf den in „Deviations from Plan" dokumentierten Rule-1-Nebenfund.
|
|
|
|
## User Setup Required
|
|
None - keine externe Konfiguration erforderlich.
|
|
|
|
## Next Phase Readiness
|
|
- WINDOWS #29 geschlossen, Rechteausweitung ADMIN gegen SUPER_ADMIN in beiden Schwesterwegen (`adminResetPassword`, `UserController.update/remove`) geschlossen
|
|
- Zwei Nebenbefunde (#35 Biome, #36 Frontend-403) offen und im Ledger sichtbar — kein Blocker fuer diesen Plan, aber vor dem naechsten Milestone-Abschluss zu pruefen
|
|
- Kein laufender Milestone begonnen; v1.2 bleibt abgeschlossen (siehe STATE.md)
|
|
|
|
---
|
|
*Phase: quick-260914-ebg*
|
|
*Completed: 2026-09-14*
|
|
|
|
## Self-Check: PASSED
|
|
|
|
Alle vier geaenderten Dateien und die SUMMARY-Datei selbst gefunden; alle drei Task-Commits (`759ea3b`, `63f9df0`, `70d007b`) in der Historie gefunden.
|