Vorarbeit fuer die Rollen-Oberflaeche. Eine Checkbox-Liste ueber einem Katalog, der nicht stimmt, waere schlimmer als gar keine. 18 von 50 Rechten bewachten nichts: tariffs:*, cancellation-periods:*, contract-durations:* und email-providers:* standen im Katalog und waren anhakbar - die Routen prueften in Wahrheit providers:*, platforms:* und settings:*. Die Routen gaten jetzt granular; eine rein additive Migration vererbt jedes neue Recht an jede Rolle, die bisher das Sammelrecht hatte. Nachgerechnet: keine Rolle hat etwas verloren. Der Katalog stand dreifach und divergent - seed.ts, sync-roles.ts und, abweichend, factoryReset. Die dritte Kopie kannte weder audit:* noch gdpr:* und legte DSGVO, Audit-Betrieb und Gegenbuch gar nicht an: Nach einem Werksreset konnte niemand mehr eine Auskunft nach Art. 15 ausfuehren, und aufgefallen waere es erst, wenn eine Frist laeuft. Jetzt eine Quelle: src/config/rechte-katalog.ts. Im selben Code: factoryReset setzte das Admin-Kennwort fest auf "admin" mit bcrypt-Cost 10 - genau das, was seed.ts seit Pentest Runde 12 verbietet. Dieselbe Haertung war nur in einer der beiden Kopien angekommen. Jetzt zufaellig, Cost 12, einmalig im Log. Selbst-Erhoehung geschlossen: neues Recht roles:manage, getrennt von users:*, dazu die Teilmengenregel in rechte.service.ts - niemand kann ein Recht weitergeben, das er selbst nicht haelt. Sie greift auf allen vier Wegen: Rolle anlegen, Rolle aendern, Rollen zuweisen und die drei Haken. Der Haken-Weg war der wichtigste: Er brauchte nur users:create, ein zweites Konto mit "Entwicklerzugriff" anlegen und sich damit anmelden - die Developer-Rolle traegt alle Rechte. Geprueft wird der Zuwachs, nicht der Endzustand, damit Entziehen erlaubt bleibt; und die Rechte des Handelnden kommen frisch aus der Datenbank, nicht aus dem bis zu 15 Minuten alten Token. Systemrollen gesperrt (Role.isSystem): Admin liess sich bisher umbenennen oder leeren - und die versteckten Rollen haengen an ihrem Namen. Role.isHidden ersetzt die im Frontend hartkodierte Namensliste, in der "Gegenbuch" fehlte. Rechteaenderung wirkt sofort: updateRole/deleteRole melden alle Traeger ab. Werksreset und Backup-Restore verlangen zusaetzlich roles:manage - beide loeschen alle Rollen und legen ein frisches admin@admin.com an. Rollenpflege war der einzige Eingriff in die Rechtevergabe ohne SecurityEvent, obwohl sie viele Konten auf einmal trifft. Jetzt PERMISSION_CHANGED - ebenso fuer die bisher stummen Haken DSGVO und Entwicklerzugriff. Aussperr-Ausweg: prisma/rolle-zuweisen.ts. Umgeht die Regel bewusst - wer Shell-Zugang hat, hat ohnehin die Datenbank; ein gestohlener Web-Zugang hat ihn nicht. Schreibt ueber die Hash-Kette, nicht roh. Die Startwache nennt den Befehl im Klartext. Geprueft gegen Dev-DB und frische Wegwerf-DB: 7 Eskalationswege alle 403, 2 Gegenproben erlaubt; 6 Sperrtests auf Systemrollen alle 403, eigene Rollen weiter aenderbar; Stammdaten-Lesen unveraendert 200; Rechteaenderung sofort 401; Migration und sync-roles dreimal identisch; db:seed auf bestehender DB ohne Wirkung auf die Rollenmatrix; frische Installation mit allen 8 Systemrollen und ohne Hinweise. Beim Deploy: Die Migration beendet alle Sitzungen einmalig. Noetig, weil die Rechte im Token stehen - sonst liefen bis zu 15 Minuten 403er. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
105 lines
5.4 KiB
SQL
105 lines
5.4 KiB
SQL
-- Rechtekatalog begradigen (Rechtemodell, Etappe 1).
|
|
--
|
|
-- 18 der 50 Rechte bewachten nichts: `tariffs:*`, `cancellation-periods:*`,
|
|
-- `contract-durations:*` und `email-providers:*` standen im Katalog und waren
|
|
-- in der Rollenverwaltung anhakbar - die Routen prueften in Wahrheit
|
|
-- `providers:*`, `platforms:*` und `settings:*`. Ein Haken, der nichts tut,
|
|
-- ist schlimmer als ein fehlender: Er behauptet eine Trennung, die es nicht
|
|
-- gibt, und wer sich darauf verlaesst, vergibt zu viel oder zu wenig, ohne
|
|
-- es zu merken.
|
|
--
|
|
-- Diese Migration ist REIN ADDITIV. Sie entzieht keiner Rolle irgendetwas.
|
|
-- Sie muss vor dem Code laufen, der die Routen umstellt - sonst verlieren
|
|
-- bestehende Rollen den Zugriff auf Tarife, Kuendigungsfristen, Laufzeiten
|
|
-- und E-Mail-Provider.
|
|
--
|
|
-- Das Aufraeumen der jetzt ueberfluessigen Sammelrechte ist ausdruecklich
|
|
-- NICHT Aufgabe dieser Migration, sondern eine bewusste Entscheidung des
|
|
-- Betreibers in der Rollenoberflaeche.
|
|
|
|
-- 1) Fehlende Rechte in den Katalog.
|
|
-- UNIQUE(resource, action) traegt die Idempotenz.
|
|
INSERT IGNORE INTO `Permission` (`resource`,`action`) VALUES
|
|
('roles','manage'),
|
|
('tariffs','create'),('tariffs','read'),('tariffs','update'),('tariffs','delete'),
|
|
('cancellation-periods','create'),('cancellation-periods','read'),
|
|
('cancellation-periods','update'),('cancellation-periods','delete'),
|
|
('contract-durations','create'),('contract-durations','read'),
|
|
('contract-durations','update'),('contract-durations','delete'),
|
|
('contract-categories','create'),('contract-categories','read'),
|
|
('contract-categories','update'),('contract-categories','delete'),
|
|
('email-providers','create'),('email-providers','read'),
|
|
('email-providers','update'),('email-providers','delete'),
|
|
('platforms','read');
|
|
|
|
-- 2) Vererbung alt -> neu: Jede Rolle, die bisher das Sammelrecht hielt,
|
|
-- bekommt das neue Recht dazu. INSERT IGNORE gegen den Primaerschluessel
|
|
-- (roleId, permissionId) macht den Block beliebig wiederholbar.
|
|
--
|
|
-- `users:delete` -> `roles:manage` bewusst nur ueber `users:delete`, nicht
|
|
-- ueber `users:create`/`users:update`: `users:delete` ist im Code bereits
|
|
-- die Admin-Heuristik (user.service.ts). Wuerde man `roles:manage` an
|
|
-- jeden mit `users:update` vererben, waere die Trennung, die dieses Recht
|
|
-- herstellen soll, im selben Zug wieder eingerissen.
|
|
INSERT IGNORE INTO `RolePermission` (`roleId`,`permissionId`)
|
|
SELECT rp.`roleId`, neu.`id`
|
|
FROM `RolePermission` rp
|
|
JOIN `Permission` alt ON alt.`id` = rp.`permissionId`
|
|
JOIN (
|
|
SELECT 'providers' AS aRes,'read' AS aAct,'tariffs' AS nRes,'read' AS nAct
|
|
UNION ALL SELECT 'providers','create','tariffs','create'
|
|
UNION ALL SELECT 'providers','update','tariffs','update'
|
|
UNION ALL SELECT 'providers','delete','tariffs','delete'
|
|
UNION ALL SELECT 'platforms','create','cancellation-periods','create'
|
|
UNION ALL SELECT 'platforms','update','cancellation-periods','update'
|
|
UNION ALL SELECT 'platforms','delete','cancellation-periods','delete'
|
|
UNION ALL SELECT 'platforms','create','contract-durations','create'
|
|
UNION ALL SELECT 'platforms','update','contract-durations','update'
|
|
UNION ALL SELECT 'platforms','delete','contract-durations','delete'
|
|
UNION ALL SELECT 'settings','read', 'email-providers','read'
|
|
UNION ALL SELECT 'settings','update','email-providers','create'
|
|
UNION ALL SELECT 'settings','update','email-providers','update'
|
|
UNION ALL SELECT 'settings','update','email-providers','delete'
|
|
UNION ALL SELECT 'users','delete','roles','manage'
|
|
) m ON m.aRes = alt.`resource` AND m.aAct = alt.`action`
|
|
JOIN `Permission` neu ON neu.`resource` = m.nRes AND neu.`action` = m.nAct;
|
|
|
|
-- 3) Die bisher ungegateten Lese-Endpunkte (Plattformen, Vertragstypen,
|
|
-- Kuendigungsfristen, Laufzeiten) bekommen ab jetzt ein Recht. Damit
|
|
-- niemand seine Listen verliert, erhalten es alle Rollen, die die
|
|
-- Anwendung ueberhaupt benutzen.
|
|
--
|
|
-- Bewusst NICHT pauschal an jede Rolle: "Gegenbuch" haelt nur `audit:read`
|
|
-- und soll so wenig wert bleiben wie moeglich - sein Kennwort liegt auf
|
|
-- der Notar-Maschine im Klartext (R185-01). Dasselbe gilt fuer
|
|
-- "Audit-Betrieb".
|
|
INSERT IGNORE INTO `RolePermission` (`roleId`,`permissionId`)
|
|
SELECT r.`id`, p.`id`
|
|
FROM `Role` r
|
|
JOIN `Permission` p
|
|
ON (p.`resource`,p.`action`) IN
|
|
(('cancellation-periods','read'),('contract-durations','read'),
|
|
('contract-categories','read'),('platforms','read'),('tariffs','read'))
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM `RolePermission` rp2
|
|
JOIN `Permission` p2 ON p2.`id` = rp2.`permissionId`
|
|
WHERE rp2.`roleId` = r.`id`
|
|
AND (p2.`resource`,p2.`action`) IN
|
|
(('customers','read'),('contracts','read'),('providers','read'),
|
|
('platforms','create'),('settings','read'))
|
|
);
|
|
|
|
-- 4) Alle Sitzungen einmalig beenden.
|
|
--
|
|
-- Die Rechte stehen im Zugangstoken. Nach der Routenumstellung verlangt
|
|
-- z.B. PUT /tariffs/:id das Recht `tariffs:update`; jedes bereits
|
|
-- ausgestellte Token traegt aber noch den alten Anspruch und liefe bis zu
|
|
-- 15 Minuten lang in ein 403. Die Datenbank waere dann laengst richtig -
|
|
-- nur das Token alt.
|
|
--
|
|
-- Einmal neu anmelden ist ehrlicher als ein Uebergangs-Doppelgate, das in
|
|
-- sechs Monaten jemand fuer Absicht haelt. Die Meldung dafuer gibt es
|
|
-- bereits im Klartext ("Ihre Berechtigungen wurden geändert."), und das
|
|
-- Gegenbuch-Dienstkonto meldet sich stuendlich ohnehin neu an.
|
|
UPDATE `User` SET `tokenInvalidatedAt` = NOW();
|