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>
127 lines
5.4 KiB
TypeScript
127 lines
5.4 KiB
TypeScript
import prisma from '../lib/prisma.js';
|
||
import { SYSTEMROLLEN } from '../config/rechte-katalog.js';
|
||
|
||
/**
|
||
* Prueft beim Start, ob die gesetzlich gebundenen Rechte ueberhaupt jemand
|
||
* ausueben kann (Pentest R189-01).
|
||
*
|
||
* Hintergrund: `gdpr:export`, `gdpr:delete` und `gdpr:admin` haengen an den
|
||
* Rollen DSGVO und Developer – nicht an der Admin-Rolle. Nach einem frischen
|
||
* Seed war keine der beiden einem Konto zugewiesen. Ergebnis: Eine Auskunft
|
||
* nach Art. 15 oder eine Loeschung nach Art. 17 konnte NIEMAND ausfuehren,
|
||
* obwohl mehrere „Admin"-Konten existierten. Ein Ausfall mit Fristwirkung,
|
||
* ausgeloest durch nichts weiter als eine Neuinstallation.
|
||
*
|
||
* Der Seed weist das Recht jetzt zu – aber nur bei NEUINSTALLATION. Auf einer
|
||
* laufenden Datenbank aendert er nichts, und niemand merkt es, bis es darauf
|
||
* ankommt. Deshalb diese Wache: Sie erkennt die ABWESENHEIT einer Faehigkeit,
|
||
* so wie der Heartbeat das Ausbleiben eines Dienstkontos erkennt.
|
||
*
|
||
* Bewusst nur eine Meldung, keine automatische Vergabe: Rechte zu verteilen,
|
||
* ohne dass ein Mensch es veranlasst hat, waere der groessere Fehler.
|
||
*/
|
||
|
||
/** Rechte, deren Fehlen ein rechtliches und kein technisches Problem ist. */
|
||
const PFLICHTRECHTE: Array<{ resource: string; action: string; wofuer: string }> = [
|
||
{ resource: 'gdpr', action: 'export', wofuer: 'Auskunft nach Art. 15 DSGVO' },
|
||
{ resource: 'gdpr', action: 'delete', wofuer: 'Löschung nach Art. 17 DSGVO' },
|
||
{ resource: 'audit', action: 'read', wofuer: 'Prüfung des Audit-Protokolls' },
|
||
// Ohne dieses Recht laesst sich keine Rolle mehr anlegen oder aendern - die
|
||
// Rechtevergabe waere eingefroren. Kein Rechtsproblem wie die beiden
|
||
// darueber, aber dieselbe Bauart: eine Faehigkeit, deren Fehlen erst
|
||
// auffaellt, wenn man sie braucht.
|
||
{ resource: 'roles', action: 'manage', wofuer: 'Pflege der Rollen und Rechte' },
|
||
];
|
||
|
||
export async function pruefePflichtrechte(): Promise<void> {
|
||
try {
|
||
const fehlend: string[] = [];
|
||
|
||
for (const recht of PFLICHTRECHTE) {
|
||
const traeger = await prisma.user.count({
|
||
where: {
|
||
isActive: true,
|
||
roles: {
|
||
some: {
|
||
role: {
|
||
permissions: {
|
||
some: {
|
||
permission: { resource: recht.resource, action: recht.action },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
if (traeger === 0) {
|
||
fehlend.push(`${recht.resource}:${recht.action} (${recht.wofuer})`);
|
||
}
|
||
}
|
||
|
||
// Zweite Wache: Stimmen die Systemrollen noch?
|
||
//
|
||
// Die versteckten Rollen werden ueber ihren NAMEN gefunden
|
||
// (`findFirst({ where: { name: 'DSGVO' } })`). Fehlt eine, oder ist ihr
|
||
// isSystem-Flag von Hand entfernt worden, laeuft der Notfallpfad ins
|
||
// Leere - lautlos. Das hier ist die Meldung, die es dann geben soll.
|
||
const rollenHinweise: string[] = [];
|
||
for (const spec of SYSTEMROLLEN) {
|
||
const rolle = await prisma.role.findUnique({ where: { name: spec.name } });
|
||
if (!rolle) {
|
||
rollenHinweise.push(`Systemrolle „${spec.name}" fehlt.`);
|
||
} else if (!rolle.isSystem) {
|
||
rollenHinweise.push(
|
||
`Systemrolle „${spec.name}" ist nicht als Systemrolle gekennzeichnet – ` +
|
||
'sie ist damit über die Rollenverwaltung änderbar.',
|
||
);
|
||
}
|
||
}
|
||
|
||
if (rollenHinweise.length > 0) {
|
||
console.warn(
|
||
'\n' +
|
||
'========================================================================\n' +
|
||
' ACHTUNG: Die Systemrollen stimmen nicht mit dem Katalog überein:\n' +
|
||
rollenHinweise.map((h) => ` – ${h}\n`).join('') +
|
||
'\n' +
|
||
' Beheben: npx tsx prisma/sync-roles.ts\n' +
|
||
'========================================================================\n',
|
||
);
|
||
}
|
||
|
||
if (fehlend.length === 0) return;
|
||
|
||
console.warn(
|
||
'\n' +
|
||
'========================================================================\n' +
|
||
' ACHTUNG: Für folgende Rechte gibt es KEIN aktives Konto:\n' +
|
||
fehlend.map((f) => ` – ${f}\n`).join('') +
|
||
'\n' +
|
||
' Das ist kein Fehler der Anwendung, sondern eine Lücke in der\n' +
|
||
' Rechtevergabe – und sie fällt erst auf, wenn eine Frist läuft.\n' +
|
||
'\n' +
|
||
' Beheben: In der Benutzerverwaltung bei einem verantwortlichen Konto\n' +
|
||
' den Haken „DSGVO-Zugriff" setzen (Audit-Protokoll lesen und\n' +
|
||
' Datenschutz-Verwaltung). Für Eingriffe am Protokoll – versiegeln,\n' +
|
||
' aufräumen, Aufbewahrung ändern – zusätzlich „Audit-Betrieb".\n' +
|
||
'\n' +
|
||
' Geht das nicht, weil niemand mehr die nötigen Rechte hat: Rechte\n' +
|
||
' lassen sich über die Oberfläche nur weitergeben, nicht erschaffen.\n' +
|
||
' Für die Erstvergabe gibt es den Weg über die Kommandozeile:\n' +
|
||
' docker compose exec backend \\\n' +
|
||
' npx tsx prisma/rolle-zuweisen.ts <e-mail> DSGVO\n' +
|
||
' („--liste" zeigt die vorhandenen Rollen.)\n' +
|
||
'========================================================================\n',
|
||
);
|
||
} catch (err) {
|
||
// Eine Wache darf den Start nicht verhindern. Aber schweigen darf sie
|
||
// auch nicht: „nicht geprüft" ist nicht dasselbe wie „nichts gefunden".
|
||
console.warn(
|
||
'[Pflichtrechte] Prüfung nicht möglich – der Zustand der Rechtevergabe ist ' +
|
||
'damit UNBEKANNT, nicht in Ordnung:',
|
||
err instanceof Error ? err.message : err,
|
||
);
|
||
}
|
||
}
|