R190-01: Rollentausch zerstoerte, wo er ablehnte

Befund der Pentesterin. updateUser machte deleteMany + createMany ohne
Transaktion und ohne skipDuplicates. Eine doppelte roleId ([4,4]) oder
eine erfundene ([99999]) kam durch die Teilmengenregel - sie bringt keine
Rechte mit, faellt dort also nicht auf - und schlug erst beim Schreiben
fehl, nach dem Loeschen. Antwort HTTP 400, Konto danach ohne jede Rolle.

Die Antwort log ueber sich selbst: "400" liest sich als abgelehnt, nichts
passiert. Zerstoert wurde trotzdem.

Damit war die Letzter-Admin-Sperre umgangen, ohne sie anzugreifen: Sie
prueft die Absicht (die Admin-Rolle steht im Body, also "bleibt Admin"),
der Schreibvorgang scheiterte danach. Aussperrung, Rueckweg nur per CLI.
Auch versehentlich ausloesbar durch doppelte IDs aus dem Frontend.

Es war meine eigene Haertung, die ich nicht mitgenommen habe: updateRole
hatte Dedup und skipDuplicates seit Etappe 1, das Geschwister
updateUser/createUser nicht. Genau das Muster, das in derselben Runde
dreimal aufgeraeumt wurde - eine Absicherung, die nur in einer von zwei
Kopien ankommt.

normalisiereRollenIds() prueft jetzt vor jeder Entscheidung und jedem
Schreibvorgang gegen die Datenbank, dedupliziert, und der Tausch laeuft
in einer Transaktion. Die Letzter-Admin-Sperre arbeitet damit auf einer
Liste, die auch einloesbar ist.

Nebenbefund beim Nachstellen: Die rohe Prisma-Fehlermeldung ging
wortwoertlich an den Client, inklusive Dateipfad des Servers. Wird jetzt
protokolliert statt ausgeliefert.

Nachgestellt: [22,22] -> 200 mit erhaltener Rolle; [99999] und
[22,99999] -> 400 mit Rollen unveraendert; [-1] -> 400; [22,23] -> 200
korrekt gesetzt. createUser ebenso, abgelehnte Anlagen hinterlassen kein
Konto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 01:14:53 +02:00
co-authored by Claude Opus 5
parent 9f879759d8
commit b2963be482
4 changed files with 135 additions and 12 deletions
+36
View File
@@ -98,6 +98,42 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🧨 R190-01: Rollentausch zerstörte, wo er ablehnte** (2026-09-09)
- Befund der Pentesterin: `updateUser` machte `deleteMany` + `createMany`
**ohne Transaktion und ohne `skipDuplicates`**. Eine doppelte roleId
(`[4,4]`) oder eine erfundene (`[99999]`) kam durch die Teilmengenregel
sie bringt ja keine Rechte mit und schlug erst beim Schreiben fehl,
**nach** dem Löschen. Antwort: HTTP 400. Konto danach: **keine Rolle**.
- Die Antwort log über sich selbst: „400" liest sich als *abgelehnt, nichts
passiert*. Zerstört wurde trotzdem.
- **Die Letzter-Admin-Sperre wurde damit umgangen**, ohne sie anzugreifen:
Sie prüft die *Absicht* (die Admin-Rolle steht im Body → „bleibt Admin"),
der Schreibvorgang scheiterte danach. Aussperrung, Rückweg nur per CLI.
Auch versehentlich auslösbar durch doppelte IDs aus dem Frontend.
- **Es war meine Härtung, die ich nicht mitgenommen habe.** `updateRole`
hatte Dedup und `skipDuplicates` seit Etappe 1 das Geschwister
`updateUser`/`createUser` nicht. Genau das Muster, das ich in derselben
Runde dreimal angeprangert habe: eine Absicherung, die nur in einer von
zwei Kopien ankommt.
- Fix: `normalisiereRollenIds()` prüft **vor** jeder Entscheidung und jedem
Schreibvorgang gegen die Datenbank (unbekannte ID → 400 mit Klartext),
dedupliziert, und der Tausch läuft in `$transaction` mit
`skipDuplicates`. Die Letzter-Admin-Sperre arbeitet damit auf einer Liste,
die auch einlösbar ist.
- **Nebenbefund beim Nachstellen**: Die rohe Prisma-Fehlermeldung ging
wortwörtlich an den Client inklusive Dateipfad des Servers. Wird jetzt
protokolliert statt ausgeliefert.
- Nachgestellt und gegengeprüft: `[22,22]` → 200, Rolle erhalten;
`[99999]` und `[22,99999]` → 400, Rollen **unverändert**; `[-1]` → 400;
`[22,23]` → 200 korrekt gesetzt. `createUser` ebenso, abgelehnte Anlagen
hinterlassen kein Konto.
- Bestätigt dicht gemeldet: Restore/Factory-Reset ohne `roles:manage` → 403,
roleIds+Haken gleichzeitig → 403 ohne Teil-Write, alle vier Vergabewege →
403, isSystem-Sperre inkl. `" aDmIn "` → 403.
- Dateien: `src/services/rechte.service.ts`, `src/services/user.service.ts`,
`src/controllers/user.controller.ts`
- [x] **🔑 Rechtemodell Etappe 1: Katalog begradigt, Selbst-Erhöhung geschlossen** (2026-09-04)
- Vorarbeit für die Rollen-Oberfläche (Etappe 2). Eine Checkbox-Liste über
einem Katalog, der nicht stimmt, wäre schlimmer als gar keine.