Files
ProxLedger/src/app/(dashboard)/einstellungen/benutzer/page.tsx
T
florian.wildeandClaude Opus 5 2f45e33e20 feat(auth): Rollen, Benutzerverwaltung und Passkeys (#027, #028)
Drei Rollen: Administrator, Techniker, Kunde. Jede der 28 Aktionen nennt das
Recht, das sie verlangt; die Rechte stehen an einer Stelle. Kundenkonten
haengen an genau einem Kunden und sehen nur dessen Hosts und Rechnungen -
ohne Einkaufspreise und Marge. Die Einschraenkung sitzt in den Abfragen
selbst, damit keine Seite vergessen kann, sie weiterzugeben; sechs Tests
pruefen das.

Dabei gefunden: Neu ueber OIDC angelegte Konten haetten die Vorgabe des
Datenmodells geerbt - ADMIN. Jetzt gibt es dafuer eine Einstellung mit der
Vorgabe Techniker.

Passkeys (#028) als gleichwertiger, passwortloser Anmeldeweg neben Passwort
und OIDC. Mehrere je Konto, Aufgabe in einem kurzlebigen Cookie, rpID aus der
oeffentlichen Adresse. Ohne HTTPS sagt die Seite es, statt still zu scheitern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 00:46:29 +02:00

56 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { BenutzerVerwaltung } from "@/components/einstellungen/BenutzerVerwaltung";
import { ContentHeader } from "@/components/layout/ContentHeader";
import { Callout } from "@/components/ui/Callout";
import { prisma } from "@/lib/db";
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
export const metadata = { title: "Benutzer" };
export const dynamic = "force-dynamic";
export default async function BenutzerSeite() {
const ich = await sitzungErforderlich("verwaltung");
const [benutzer, kunden] = await Promise.all([
prisma.benutzer.findMany({
orderBy: [{ rolle: "asc" }, { name: "asc" }],
include: { kunde: { select: { name: true } }, _count: { select: { passkeys: true } } },
}),
prisma.kunde.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } }),
]);
const zeilen = benutzer.map((eintrag) => ({
id: eintrag.id,
email: eintrag.email,
name: eintrag.name,
rolle: eintrag.rolle,
kundeId: eintrag.kundeId,
kundeName: eintrag.kunde?.name ?? null,
aktiv: eintrag.aktiv,
hatPasswort: eintrag.passwortHash !== null,
hatOidc: eintrag.oidcSubject !== null,
passkeys: eintrag._count.passkeys,
letzterLoginAm: eintrag.letzterLoginAm,
}));
return (
<>
<ContentHeader
titel="Benutzer"
brotkrumen={[{ titel: "Einstellungen" }, { titel: "Benutzer" }]}
/>
<div className="app-content">
<div className="container-fluid">
<Callout farbe="info" titel="Rollen">
<strong>Administrator</strong> darf alles, auch Benutzer und Systemeinstellungen.{" "}
<strong>Techniker</strong> darf alles Fachliche, aber keine Benutzer anlegen.{" "}
<strong>Kunde</strong> sieht nur die Hosts und Rechnungen des eigenen Kunden – ohne
Einkaufspreise – und darf Notizen schreiben sowie Warnungen quittieren.
</Callout>
<BenutzerVerwaltung benutzer={zeilen} kunden={kunden} ichId={ich.benutzerId} />
</div>
</div>
</>
);
}