Paket B — Mandanten-Admin: Benutzer- & Rollenverwaltung

Eigener Bereich /settings/users (verlinkt aus /settings), sichtbar nur mit
user:manage bzw. role:manage. Strikt mandantenintern (dbForTenant über die eigene
Session) — kein Cross-Tenant-Zugriff. Audit-Scope: tenant.

- actions/tenant-users.ts:
  - Benutzer (user:manage): createUser (Initial-/Einmal-Passwort, mustChangePassword),
    resetUserPassword, setUserStatus (deaktivieren/reaktivieren), setUserRoles.
  - Rollen (role:manage): createRole (+ Permissions), updateRolePermissions
    (Standardrollen schreibgeschützt), cloneRole (Standard klonbar), deleteRole
    (nur eigene, nur ohne zugewiesene Nutzer — sonst Umzug-Hinweis).
  - Lockout-Schutz: letzter aktiver Mandanten-Admin kann nicht deaktiviert werden
    und ihm kann die Admin-Rolle nicht entzogen werden.
- Komponenten tenant-users-manager.tsx (Benutzer) und role-manager.tsx (Rollen +
  gruppierter Permission-Katalog); Bestands-UI/Dark-Theme wiederverwendet.

Browser-verifiziert: Mandanten-Admin sieht Benutzer + Rollen des eigenen Mandanten,
legt eine eigene Rolle „Externer Prüfer" mit ausgewählten Rechten an (asset:read,
audit:read, policy:read → korrekt gespeichert); Standardrollen als schreibgeschützt
markiert. tsc + lint + build + Guard-Check grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 09:44:28 +02:00
co-authored by Claude Opus 4.8
parent 3db820ea90
commit d0821e2e10
6 changed files with 542 additions and 0 deletions
+11
View File
@@ -1,3 +1,4 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { requireSession } from "@/server/auth";
import { dbForTenant } from "@/server/db";
@@ -28,6 +29,16 @@ export default async function SettingsPage() {
<main className="flex-1 p-6">
<PageHead crumb="Organisation" title="Einstellungen" sub="Unternehmensdaten, Branding, Sicherheits-Policy — Stammdaten speisen die ISMS-Variablen" />
{(hasPermission(session, "user:manage") || hasPermission(session, "role:manage")) && (
<div className="shadow-card mt-4 flex items-center justify-between rounded-xl border bg-card p-5">
<div>
<p className="font-heading text-sm font-semibold">Benutzer &amp; Rollen</p>
<p className="text-[12px] text-muted-foreground">Benutzer anlegen/verwalten, Rollen und Berechtigungen pflegen (mandantenintern).</p>
</div>
<Button nativeButton={false} render={<Link href="/settings/users" />}>Öffnen</Button>
</div>
)}
<form action={updateTenantSettings} className="mt-4 grid gap-5 lg:grid-cols-[1fr_320px]">
<div className="space-y-5">
{/* Unternehmensdaten */}