- Auth.js v5 mit Credentials-Provider: Argon2id-Verifikation, JWT-Session mit Mandanten-Kontext, Rollen und Permissions; Login-/Logout-Flow (deutsch) - RBAC-Katalog (28 Permissions, 5 Rollen-Blueprints) mit serverseitigem requirePermission; Seed legt Demo-Mandant und 4 Demo-Nutzer an - Route-Gate über Next-16-proxy.ts (UX-Ebene), autoritative Prüfung serverseitig via requireSession/requirePermission - Prisma-Migrationen: init + Row-Level-Security-Policies (zweite Verteidigungslinie, Scharfschaltung in Härtungs-Iteration dokumentiert) - i18n-Gerüst mit next-intl (de aktiv, en vorbereitet), Audit-Log-Helper - Login/Logout end-to-end im Browser verifiziert; Build/Lint/Typecheck grün Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
153 lines
3.3 KiB
TypeScript
153 lines
3.3 KiB
TypeScript
/**
|
|
* RBAC catalog: granular permissions bundled into per-tenant roles.
|
|
* See docs/SPEC.md §3. Permissions are global rows (no tenantId),
|
|
* roles are created per tenant from ROLE_DEFS at tenant provisioning.
|
|
*/
|
|
|
|
export const PERMISSIONS = [
|
|
// Administration
|
|
"tenant:manage",
|
|
"user:read",
|
|
"user:manage",
|
|
// Assets & BIA (one module)
|
|
"asset:read",
|
|
"asset:write",
|
|
"bia:read",
|
|
"bia:write",
|
|
// Risk
|
|
"risk:read",
|
|
"risk:write",
|
|
"risk:accept",
|
|
// Compliance
|
|
"soa:read",
|
|
"soa:write",
|
|
// Measures / tasks
|
|
"measure:read",
|
|
"measure:write",
|
|
// Policies
|
|
"policy:read",
|
|
"policy:write",
|
|
"policy:approve",
|
|
// Incidents
|
|
"incident:report",
|
|
"incident:manage",
|
|
// Audits
|
|
"audit:read",
|
|
"audit:conduct",
|
|
// Suppliers
|
|
"supplier:read",
|
|
"supplier:write",
|
|
// Evidence / documents
|
|
"evidence:read",
|
|
"evidence:write",
|
|
// Reporting / management review
|
|
"report:read",
|
|
"review:manage",
|
|
// Chat
|
|
"chat:use",
|
|
] as const;
|
|
|
|
export type Permission = (typeof PERMISSIONS)[number];
|
|
|
|
/** Role blueprints instantiated for every tenant (key → name + permissions). */
|
|
export const ROLE_DEFS: Record<
|
|
string,
|
|
{ name: string; permissions: readonly Permission[] }
|
|
> = {
|
|
"tenant-admin": {
|
|
name: "Mandanten-Admin",
|
|
permissions: ["tenant:manage", "user:read", "user:manage", "report:read", "chat:use"],
|
|
},
|
|
isb: {
|
|
name: "ISB / CISO",
|
|
permissions: [
|
|
"user:read",
|
|
"asset:read",
|
|
"asset:write",
|
|
"bia:read",
|
|
"bia:write",
|
|
"risk:read",
|
|
"risk:write",
|
|
"risk:accept",
|
|
"soa:read",
|
|
"soa:write",
|
|
"measure:read",
|
|
"measure:write",
|
|
"policy:read",
|
|
"policy:write",
|
|
"policy:approve",
|
|
"incident:report",
|
|
"incident:manage",
|
|
"audit:read",
|
|
"supplier:read",
|
|
"supplier:write",
|
|
"evidence:read",
|
|
"evidence:write",
|
|
"report:read",
|
|
"review:manage",
|
|
"chat:use",
|
|
],
|
|
},
|
|
auditor: {
|
|
name: "Auditor",
|
|
permissions: [
|
|
"asset:read",
|
|
"bia:read",
|
|
"risk:read",
|
|
"soa:read",
|
|
"measure:read",
|
|
"policy:read",
|
|
"audit:read",
|
|
"audit:conduct",
|
|
"supplier:read",
|
|
"evidence:read",
|
|
"report:read",
|
|
],
|
|
},
|
|
owner: {
|
|
name: "Asset-/Risk-Owner",
|
|
permissions: [
|
|
"asset:read",
|
|
"asset:write",
|
|
"bia:read",
|
|
"risk:read",
|
|
"risk:write",
|
|
"measure:read",
|
|
"measure:write",
|
|
"policy:read",
|
|
"incident:report",
|
|
"evidence:read",
|
|
"evidence:write",
|
|
"chat:use",
|
|
],
|
|
},
|
|
user: {
|
|
name: "Mitarbeiter",
|
|
permissions: ["policy:read", "incident:report", "measure:read", "chat:use"],
|
|
},
|
|
};
|
|
|
|
export class ForbiddenError extends Error {
|
|
constructor(permission: Permission) {
|
|
super(`Fehlende Berechtigung: ${permission}`);
|
|
this.name = "ForbiddenError";
|
|
}
|
|
}
|
|
|
|
/** Server-side permission check — UI hiding is convenience, this is security. */
|
|
export function requirePermission(
|
|
session: { user?: { permissions?: string[] } } | null,
|
|
permission: Permission
|
|
): void {
|
|
if (!session?.user?.permissions?.includes(permission)) {
|
|
throw new ForbiddenError(permission);
|
|
}
|
|
}
|
|
|
|
export function hasPermission(
|
|
session: { user?: { permissions?: string[] } } | null,
|
|
permission: Permission
|
|
): boolean {
|
|
return session?.user?.permissions?.includes(permission) ?? false;
|
|
}
|