Iteration 1: Auth (lokale Accounts), RBAC, RLS-Policies, i18n
- 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>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user