From 4219701b2904a27ab9a56dded0b39729e0e4adf9 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 12 Oct 2026 22:36:25 +0200 Subject: [PATCH] feat(anmeldung): lokale Benutzerkonten mit Pflicht-TOTP (#035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ohne authentik gab es nur den Notfall-Admin – ein geteiltes Konto ohne Rollen und ohne erkennbare Akteure im Audit-Log. Admins legen jetzt lokale Konten an; sshkv erzeugt ein Einmal-Passwort, das bei der ersten Anmeldung ersetzt wird, danach ist TOTP Pflicht. Rollen, Sperren (beendet Sitzungen sofort) und Zurücksetzen unter Einstellungen → Benutzer, nur für Admins mit frischer Bestätigung. SSHKV_LOKALE_KONTEN=0 schaltet lokale Konten ab; der Notfall-Admin bleibt. Co-Authored-By: Claude Opus 5.5 --- .env.example | 6 + CHANGELOG.md | 1 + docker-compose.yml | 2 + docs/anleitung/README.md | 20 +- docs/anleitung/bilder/benutzer-desktop.png | Bin 0 -> 131429 bytes docs/betrieb/abnahme.md | 13 ++ docs/betrieb/installation.md | 14 ++ docs/betrieb/sicherheit.md | 6 +- docs/issues/035-lokale-konten.md | 32 +++ .../migration.sql | 2 + prisma/schema.prisma | 40 ++-- src/app/(app)/einstellungen/benutzer/page.tsx | 101 +++++++++ src/app/(app)/einstellungen/page.tsx | 12 ++ src/app/(app)/huellen-benutzer.ts | 6 +- src/app/anmelden/page.tsx | 12 +- src/app/anmelden/passwort/page.tsx | 24 +++ src/app/anmelden/totp/page.tsx | 16 +- src/app/bestaetigen/page.tsx | 2 +- .../anmeldung/BestaetigenFormular.tsx | 4 +- ...{NotfallFormular.tsx => KontoFormular.tsx} | 13 +- .../anmeldung/PasswortSetzenFormular.tsx | 32 +++ src/components/benutzer/EinmalPasswort.tsx | 18 ++ src/components/benutzer/KontoAktionen.tsx | 111 ++++++++++ src/components/benutzer/KontoAnlegen.tsx | 47 +++++ src/lib/lokale-konten.ts | 55 +++++ src/lib/protokoll-aktionen.ts | 12 ++ src/lib/sitzung.ts | 4 +- src/server/actions/anmeldung.ts | 100 ++++++--- src/server/actions/benutzer.ts | 82 ++++++++ src/server/auth/rechte.ts | 7 +- src/server/auth/sitzungspflicht.ts | 11 +- src/server/auth/totp-einrichtung.ts | 14 ++ src/server/queries/benutzer.ts | 35 ++++ src/server/services/anmeldung.ts | 104 +++++++--- src/server/services/benutzer.ts | 152 ++++++++++++++ tests/anmeldung.test.ts | 1 + tests/e2e/anmeldung.spec.ts | 4 +- tests/e2e/lokale-konten.spec.ts | 76 +++++++ tests/lokale-konten.test.ts | 192 ++++++++++++++++++ tests/notfall-anmeldung.test.ts | 20 +- tests/zugang.test.ts | 1 + 41 files changed, 1295 insertions(+), 109 deletions(-) create mode 100644 docs/anleitung/bilder/benutzer-desktop.png create mode 100644 docs/issues/035-lokale-konten.md create mode 100644 prisma/migrations/20261012203000_lokale_konten/migration.sql create mode 100644 src/app/(app)/einstellungen/benutzer/page.tsx create mode 100644 src/app/anmelden/passwort/page.tsx rename src/components/anmeldung/{NotfallFormular.tsx => KontoFormular.tsx} (69%) create mode 100644 src/components/anmeldung/PasswortSetzenFormular.tsx create mode 100644 src/components/benutzer/EinmalPasswort.tsx create mode 100644 src/components/benutzer/KontoAktionen.tsx create mode 100644 src/components/benutzer/KontoAnlegen.tsx create mode 100644 src/lib/lokale-konten.ts create mode 100644 src/server/actions/benutzer.ts create mode 100644 src/server/queries/benutzer.ts create mode 100644 src/server/services/benutzer.ts create mode 100644 tests/e2e/lokale-konten.spec.ts create mode 100644 tests/lokale-konten.test.ts diff --git a/.env.example b/.env.example index 35f4fb4..836a2fb 100644 --- a/.env.example +++ b/.env.example @@ -51,6 +51,12 @@ SSHKV_OIDC_GROUP_READER=sshkv-readers # Nur Entwicklung: OIDC ueber http erlauben (Mock-Anbieter). Im Betrieb nie setzen. # SSHKV_OIDC_HTTP_ERLAUBT=1 +# --- Lokale Konten (#035) ------------------------------------------------------- +# Admins legen unter /einstellungen/benutzer Konten mit Passwort und Pflicht-TOTP an – neben +# oder statt authentik. 0 schaltet sie ab (vorhandene lokale Konten kommen dann nicht mehr +# hinein); der Notfall-Admin bleibt immer. +SSHKV_LOKALE_KONTEN=1 + # --- Notfall-Admin ------------------------------------------------------------- # Wird beim ersten Seed angelegt. TOTP wird bei der ersten Anmeldung eingerichtet. SSHKV_BREAKGLASS_USER=notfall diff --git a/CHANGELOG.md b/CHANGELOG.md index 6edb93c..d134b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). - Laufende Schlüsselrotation abbrechen – der Nachfolger wird per Dry-Run wieder entfernt (#032) - „Zum Dry-Run“ in der Zuweisungsmatrix springt direkt zur Dry-Run-Karte (#032) - Nach einer abgebrochenen Rotation lässt sich derselbe Schlüssel erneut rotieren (#033) +- Lokale Konten mit Passwort und Pflicht-TOTP neben oder statt authentik: Admins legen sie unter Einstellungen → Benutzer an (Einmal-Passwort, Rollen, Sperren, Zurücksetzen); abschaltbar mit `SSHKV_LOKALE_KONTEN=0` (#035) ### Geändert - Protokoll-IDs werden lückenlos vergeben (#032) diff --git a/docker-compose.yml b/docker-compose.yml index f6e381b..146875f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,6 +64,8 @@ services: SSHKV_OIDC_GROUP_ADMIN: ${SSHKV_OIDC_GROUP_ADMIN:-sshkv-admins} SSHKV_OIDC_GROUP_OPERATOR: ${SSHKV_OIDC_GROUP_OPERATOR:-sshkv-operators} SSHKV_OIDC_GROUP_READER: ${SSHKV_OIDC_GROUP_READER:-sshkv-readers} + # Lokale Konten mit Passwort und TOTP (#035); 0 schaltet sie ab, der Notfall-Admin bleibt. + SSHKV_LOKALE_KONTEN: ${SSHKV_LOKALE_KONTEN:-1} # Syslog-Kopie des Protokolls (#027) – optional, leer heisst aus. SSHKV_SYSLOG_HOST: ${SSHKV_SYSLOG_HOST:-} SSHKV_SYSLOG_PORT: ${SSHKV_SYSLOG_PORT:-514} diff --git a/docs/anleitung/README.md b/docs/anleitung/README.md index f7d2729..e51f2bf 100644 --- a/docs/anleitung/README.md +++ b/docs/anleitung/README.md @@ -40,9 +40,13 @@ in die Verwaltung. - **Mit authentik anmelden** – der Normalfall. Ihre Rolle (Leser, Operator, Admin) kommt aus den authentik-Gruppen und wird bei jeder Anmeldung neu bestimmt. -- **Notfallzugang** – nur, wenn authentik ausfällt. Anmeldename, Passwort und ein Code aus einer - Authenticator-App. Bei der ersten Anmeldung richtet der Notfall-Admin den zweiten Faktor ein - (QR-Code scannen, Code bestätigen). +- **Lokales Konto** – ohne authentik oder zusätzlich dazu. Ein Admin legt das Konto an und gibt + Ihnen ein **Einmal-Passwort**. Bei der ersten Anmeldung (Abschnitt „Lokales Konto oder + Notfallzugang“) setzen Sie ein eigenes Passwort (mindestens 12 Zeichen) und richten den zweiten + Faktor ein: QR-Code mit einer Authenticator-App scannen, Code bestätigen. Danach immer + Anmeldename, Passwort und Code. +- **Notfallzugang** – nur, wenn authentik ausfällt. Gleiches Formular; der Notfall-Admin richtet + bei der ersten Anmeldung ebenfalls den zweiten Faktor ein. Kritische Aktionen (Private Key anzeigen, Benutzer mit sudo anlegen, Massenwiderruf, Host-Key ersetzen) verlangen eine **erneute Bestätigung**; sie gilt fünf Minuten. @@ -225,6 +229,16 @@ weder ändern noch löschen; jeder Eintrag ist per Hash mit seinem Vorgänger ve ## 12. Einstellungen und Benachrichtigungen +**Benutzer** (Einstellungen → Instanz → „Konten verwalten“, nur Admin): lokale Konten anlegen – +sshkv erzeugt das Einmal-Passwort und zeigt es **genau einmal** –, Rollen ändern, Konten sperren +(beendet laufende Sitzungen sofort) und zurücksetzen (neues Einmal-Passwort, TOTP neu einrichten, +etwa nach verlorenem Telefon). Konten aus authentik erscheinen nach ihrer ersten Anmeldung; ihre +Rolle kommt aus den Gruppen, sie lassen sich hier nur sperren. Der Notfall-Admin ist +unveränderlich. + +![Benutzer](bilder/benutzer-desktop.png) + + Unter **Einstellungen** (ändern nur Admins) richten Sie Benachrichtigungen ein: - **E-Mail** über SMTP – für Microsoft 365 vorbelegt (`smtp.office365.com:587`, STARTTLS). diff --git a/docs/anleitung/bilder/benutzer-desktop.png b/docs/anleitung/bilder/benutzer-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..9d208eef8bfad53e725a7dd66f14dc263dd9420b GIT binary patch literal 131429 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 = { ADMIN: "Admin", OPERATOR: "Operator", LESER: "Leser" }; +const HERKUNFT: Record = { + notfall: "Notfall-Admin", + lokal: "lokal", + oidc: process.env["SSHKV_OIDC_NAME"] ?? "OIDC", +}; + +/** + * Benutzerverwaltung (#035): lokale Konten anlegen, Rollen vergeben, sperren, zuruecksetzen. + * OIDC-Konten erscheinen nach ihrer ersten Anmeldung; ihre Rolle kommt aus dem Anbieter. + */ +export default async function BenutzerSeite() { + const { benutzer } = await sitzungErforderlich("benutzerVerwalten", "/einstellungen/benutzer"); + const konten = await benutzerListe(); + const lokal = lokaleKontenAktiv(); + + return ( +
+ + } + titel="Benutzer" + untertitel="Lokale Konten mit Passwort und TOTP – neben oder statt des Identitätsanbieters." + /> + + {!bestaetigungFrisch(benutzer.bestaetigtAm) && ( +

+ Änderungen an Konten verlangen eine frische Bestätigung Ihrer Anmeldung.{" "} + + Jetzt bestätigen + +

+ )} + + {lokal ? ( + + + + ) : ( +

+ Lokale Konten sind abgeschaltet (SSHKV_LOKALE_KONTEN=0). + Vorhandene lokale Konten können sich nicht anmelden. +

+ )} + + + + {konten.map((k) => ( + + + {k.name} + + {k.anmeldename ?? k.email ?? "–"} + + + + + {HERKUNFT[k.herkunft]} + {!k.aktiv && gesperrt} + {k.aktiv && k.einrichtungOffen && Einrichtung offen} + + + {ROLLE[k.rolle]} + + {k.letzteAnmeldungAm ? datumZeit(k.letzteAnmeldungAm) : "noch nie"} + + + + + + ))} + + +
+ ); +} diff --git a/src/app/(app)/einstellungen/page.tsx b/src/app/(app)/einstellungen/page.tsx index d5cb40e..7ec2588 100644 --- a/src/app/(app)/einstellungen/page.tsx +++ b/src/app/(app)/einstellungen/page.tsx @@ -188,6 +188,18 @@ export default async function Einstellungen() { nicht eingerichtet )} + + {darf(benutzer.rolle, "benutzerVerwalten") ? ( + + Konten verwalten + + ) : ( + nur für Admins + )} + {!instanz.notfall.vorhanden ? ( nicht angelegt diff --git a/src/app/(app)/huellen-benutzer.ts b/src/app/(app)/huellen-benutzer.ts index 0b06218..f7b90f7 100644 --- a/src/app/(app)/huellen-benutzer.ts +++ b/src/app/(app)/huellen-benutzer.ts @@ -11,6 +11,10 @@ export async function huellenBenutzerErmitteln(): Promise { name: benutzer.name, rolle: benutzer.rolle, herkunft: - sitzung.herkunft === "notfall" ? "Notfallzugang" : (process.env["SSHKV_OIDC_NAME"] ?? "authentik"), + sitzung.herkunft === "notfall" + ? "Notfallzugang" + : sitzung.herkunft === "lokal" + ? "lokales Konto" + : (process.env["SSHKV_OIDC_NAME"] ?? "authentik"), }; } diff --git a/src/app/anmelden/page.tsx b/src/app/anmelden/page.tsx index 5754610..f751a22 100644 --- a/src/app/anmelden/page.tsx +++ b/src/app/anmelden/page.tsx @@ -1,8 +1,9 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; import { AnmeldeRahmen } from "@/components/anmeldung/AnmeldeRahmen"; -import { NotfallFormular } from "@/components/anmeldung/NotfallFormular"; +import { KontoFormular } from "@/components/anmeldung/KontoFormular"; import { buttonKlassen, Formularfehler } from "@/components/ui"; +import { lokaleKontenAktiv } from "@/lib/lokale-konten"; import { sicheresZiel } from "@/server/auth/anmeldung"; import { oidcAktiv, oidcName } from "@/server/auth/oidc"; import { angemeldet } from "@/server/auth/sitzungspflicht"; @@ -33,6 +34,7 @@ export default async function AnmeldenSeite({ if (await angemeldet()) redirect(ziel); const oidc = oidcAktiv(); + const lokal = lokaleKontenAktiv(); const fehler = parameter.fehler ? (FEHLER[parameter.fehler] ?? FEHLER["allgemein"]) : undefined; return ( @@ -53,15 +55,17 @@ export default async function AnmeldenSeite({ ) : (

- Kein Identitätsanbieter eingerichtet – nur der Notfallzugang ist aktiv. + {lokal + ? "Kein Identitätsanbieter eingerichtet – Anmeldung mit lokalem Konto." + : "Kein Identitätsanbieter eingerichtet – nur der Notfallzugang ist aktiv."}

)}
- Notfallzugang + {lokal ? "Lokales Konto oder Notfallzugang" : "Notfallzugang"}
- +
diff --git a/src/app/anmelden/passwort/page.tsx b/src/app/anmelden/passwort/page.tsx new file mode 100644 index 0000000..ad67c05 --- /dev/null +++ b/src/app/anmelden/passwort/page.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import { redirect } from "next/navigation"; +import { AnmeldeRahmen } from "@/components/anmeldung/AnmeldeRahmen"; +import { PasswortSetzenFormular } from "@/components/anmeldung/PasswortSetzenFormular"; +import { KONTO_PASSWORT_MINDESTLAENGE } from "@/lib/lokale-konten"; +import { passwortSetzenLesen } from "@/server/auth/totp-einrichtung"; + +export const metadata: Metadata = { title: "Eigenes Passwort setzen" }; + +/** + * Erste Anmeldung eines lokalen Kontos (#035): das Einmal-Passwort vom Admin wird durch ein + * eigenes ersetzt. Nur mit dem Cookie erreichbar, das ein richtiges Einmal-Passwort ausstellt. + */ +export default async function PasswortSetzenSeite() { + if (!(await passwortSetzenLesen())) redirect("/anmelden"); + return ( + + + + ); +} diff --git a/src/app/anmelden/totp/page.tsx b/src/app/anmelden/totp/page.tsx index a191ccc..9b0ebb1 100644 --- a/src/app/anmelden/totp/page.tsx +++ b/src/app/anmelden/totp/page.tsx @@ -4,20 +4,30 @@ import QRCode from "qrcode"; import { AnmeldeRahmen } from "@/components/anmeldung/AnmeldeRahmen"; import { TotpFormular } from "@/components/anmeldung/TotpFormular"; import { MonoKopieren } from "@/components/ui"; +import { prisma } from "@/lib/db"; import { totpAdresse } from "@/lib/totp"; import { totpEinrichtungLesen } from "@/server/auth/totp-einrichtung"; export const metadata: Metadata = { title: "TOTP einrichten" }; /** - * Erste Anmeldung des Notfall-Admins: TOTP ist Pflicht (Freigabe Frage 5). Die Seite ist - * nur mit dem Einrichtungs-Cookie erreichbar, das ein richtiges Passwort ausgestellt hat. + * Erste Anmeldung eines Passwort-Kontos (Notfall-Admin, lokale Konten #035): TOTP ist Pflicht + * (Freigabe Frage 5). Die Seite ist nur mit dem Einrichtungs-Cookie erreichbar, das ein + * richtiges Passwort ausgestellt hat. */ export default async function TotpSeite() { const einrichtung = await totpEinrichtungLesen(); if (!einrichtung) redirect("/anmelden"); - const adresse = totpAdresse(einrichtung.geheimnis, "Notfall-Admin"); + const konto = await prisma.benutzer.findUnique({ + where: { id: einrichtung.benutzerId }, + select: { anmeldename: true, istNotfall: true }, + }); + // In der Authenticator-App soll stehen, welches Konto das ist. + const adresse = totpAdresse( + einrichtung.geheimnis, + konto?.istNotfall ? "Notfall-Admin" : (konto?.anmeldename ?? "Konto"), + ); // Als SVG im Server gerendert: kein Bild-Endpunkt, keine Bibliothek im Browser. const svg = await QRCode.toString(adresse, { type: "svg", diff --git a/src/app/bestaetigen/page.tsx b/src/app/bestaetigen/page.tsx index 2e32282..0ed5183 100644 --- a/src/app/bestaetigen/page.tsx +++ b/src/app/bestaetigen/page.tsx @@ -22,7 +22,7 @@ export default async function BestaetigenSeite({ titel="Anmeldung bestätigen" text="Diese Aktion ist kritisch. Bitte bestätigen Sie Ihre Anmeldung – die Bestätigung gilt fünf Minuten." > - {benutzer.istNotfall ? ( + {benutzer.passwortHash && !benutzer.oidcSub ? ( ) : ( (notfallBestaetigenAktion, {}); + const [zustand, aktion, laeuft] = useActionState(kontoBestaetigenAktion, {}); return (
diff --git a/src/components/anmeldung/NotfallFormular.tsx b/src/components/anmeldung/KontoFormular.tsx similarity index 69% rename from src/components/anmeldung/NotfallFormular.tsx rename to src/components/anmeldung/KontoFormular.tsx index a195ce3..ed7b40e 100644 --- a/src/components/anmeldung/NotfallFormular.tsx +++ b/src/components/anmeldung/KontoFormular.tsx @@ -2,11 +2,14 @@ import { useActionState } from "react"; import { Button, Feld, Formularfehler } from "@/components/ui"; -import { notfallAnmeldenAktion, type FormularZustand } from "@/server/actions/anmeldung"; +import { kontoAnmeldenAktion, type FormularZustand } from "@/server/actions/anmeldung"; -/** Notfallzugang: Anmeldename, Passwort und – sobald eingerichtet – TOTP-Code (#004). */ -export function NotfallFormular({ ziel }: { ziel: string }) { - const [zustand, aktion, laeuft] = useActionState(notfallAnmeldenAktion, {}); +/** + * Lokale Konten und Notfallzugang: Anmeldename, Passwort und – sobald eingerichtet – TOTP-Code + * (#004, #035). + */ +export function KontoFormular({ ziel, lokal }: { ziel: string; lokal: boolean }) { + const [zustand, aktion, laeuft] = useActionState(kontoAnmeldenAktion, {}); return ( @@ -29,7 +32,7 @@ export function NotfallFormular({ ziel }: { ziel: string }) { hinweis="Bei der ersten Anmeldung leer lassen – dann wird TOTP eingerichtet." /> ); diff --git a/src/components/anmeldung/PasswortSetzenFormular.tsx b/src/components/anmeldung/PasswortSetzenFormular.tsx new file mode 100644 index 0000000..90404de --- /dev/null +++ b/src/components/anmeldung/PasswortSetzenFormular.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { useActionState } from "react"; +import { Button, Feld, Formularfehler } from "@/components/ui"; +import { passwortSetzenAktion, type FormularZustand } from "@/server/actions/anmeldung"; + +export function PasswortSetzenFormular() { + const [zustand, aktion, laeuft] = useActionState(passwortSetzenAktion, {}); + return ( +
+ + + + + + ); +} diff --git a/src/components/benutzer/EinmalPasswort.tsx b/src/components/benutzer/EinmalPasswort.tsx new file mode 100644 index 0000000..04d3c10 --- /dev/null +++ b/src/components/benutzer/EinmalPasswort.tsx @@ -0,0 +1,18 @@ +import { MonoKopieren } from "@/components/ui"; + +/** Einmal-Passwort fuer ein neues oder zurueckgesetztes Konto (#035) – wird nur jetzt gezeigt. */ +export function EinmalPasswort({ anmeldename, passwort }: { anmeldename: string; passwort: string }) { + return ( +
+

+ Einmal-Passwort für {anmeldename} –{" "} + nur jetzt sichtbar. Bitte auf sicherem Weg weitergeben; bei der ersten Anmeldung wird + ein eigenes Passwort und TOTP eingerichtet. +

+ +
+ ); +} diff --git a/src/components/benutzer/KontoAktionen.tsx b/src/components/benutzer/KontoAktionen.tsx new file mode 100644 index 0000000..8df827e --- /dev/null +++ b/src/components/benutzer/KontoAktionen.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState, useTransition } from "react"; +import { EinmalPasswort } from "@/components/benutzer/EinmalPasswort"; +import { Button } from "@/components/ui"; +import type { KontoHerkunft } from "@/server/queries/benutzer"; +import { aktivSetzenAktion, rolleAendernAktion, zuruecksetzenAktion } from "@/server/actions/benutzer"; + +const ROLLEN = [ + { wert: "LESER", text: "Leser" }, + { wert: "OPERATOR", text: "Operator" }, + { wert: "ADMIN", text: "Admin" }, +] as const; + +/** + * Aktionen je Konto (#035): Rolle (nur lokal), sperren/entsperren, zuruecksetzen (nur lokal). + * Der Notfall-Admin und das eigene Konto bekommen keine Knoepfe – der Server lehnt es ohnehin ab. + */ +export function KontoAktionen({ + konto, + selbst, +}: { + konto: { + id: string; + name: string; + anmeldename: string | null; + rolle: string; + aktiv: boolean; + herkunft: KontoHerkunft; + }; + selbst: boolean; +}) { + const router = useRouter(); + const [fehler, setFehler] = useState(); + const [einmal, setEinmal] = useState(); + const [rueckfrage, setRueckfrage] = useState(false); + const [laeuft, starten] = useTransition(); + + if (konto.herkunft === "notfall" || selbst) { + return {selbst ? "eigenes Konto" : "unveränderlich"}; + } + + const ausfuehren = (aktion: () => Promise<{ fehler?: string; einmalPasswort?: string }>) => + starten(async () => { + setFehler(undefined); + const antwort = await aktion(); + if (antwort.fehler) setFehler(antwort.fehler); + if (antwort.einmalPasswort) setEinmal(antwort.einmalPasswort); + router.refresh(); + }); + + return ( +
+
+ {konto.herkunft === "lokal" && ( + + )} + {konto.herkunft === "lokal" && ( + + )} + + {konto.herkunft === "lokal" && + (rueckfrage ? ( + + ) : ( + + ))} +
+ {fehler && ( +

+ {fehler} +

+ )} + {einmal && konto.anmeldename && } +
+ ); +} diff --git a/src/components/benutzer/KontoAnlegen.tsx b/src/components/benutzer/KontoAnlegen.tsx new file mode 100644 index 0000000..583ebe8 --- /dev/null +++ b/src/components/benutzer/KontoAnlegen.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { UserPlus } from "lucide-react"; +import { useActionState } from "react"; +import { EinmalPasswort } from "@/components/benutzer/EinmalPasswort"; +import { Auswahlliste, Button, Feld, Formularfehler } from "@/components/ui"; +import { kontoAnlegenAktion, type KontoZustand } from "@/server/actions/benutzer"; + +/** Lokales Konto anlegen (#035). Das Passwort waehlt nicht der Admin, sshkv erzeugt es. */ +export function KontoAnlegen() { + const [zustand, aktion, laeuft] = useActionState(kontoAnlegenAktion, {}); + return ( +
+ {zustand.einmalPasswort && zustand.anmeldename && ( + + )} +
+
+ +
+ + + + + + + + + +
+ ); +} diff --git a/src/lib/lokale-konten.ts b/src/lib/lokale-konten.ts new file mode 100644 index 0000000..b6c39ec --- /dev/null +++ b/src/lib/lokale-konten.ts @@ -0,0 +1,55 @@ +import { randomInt } from "node:crypto"; +import { z } from "@/lib/validierung"; + +/** + * Regeln fuer lokale Konten (#035): Anmeldename, Passwortlaenge, Einmal-Passwort. Rein, damit + * testbar. Lokale Konten melden sich wie der Notfall-Admin mit Passwort und Pflicht-TOTP an. + */ + +/** Mit Pflicht-TOTP genuegen 12 Zeichen; der Notfall-Admin behaelt seine 16 (#004). */ +export const KONTO_PASSWORT_MINDESTLAENGE = 12; +export const KONTO_PASSWORT_HOECHSTLAENGE = 256; + +export const anmeldenameSchema = z + .string() + .trim() + .toLowerCase() + .regex(/^[a-z][a-z0-9._-]{1,63}$/, { + message: + "2–64 Zeichen: Kleinbuchstaben, Ziffern, Punkt, Binde- und Unterstrich; mit einem Buchstaben beginnen.", + }); + +export const kontoNameSchema = z.string().trim().min(1).max(120); + +/** Welche Rollen ein lokales Konto haben kann – dieselben wie ueber OIDC. */ +export const kontoRolleSchema = z.enum(["ADMIN", "OPERATOR", "LESER"]); + +export function passwortFehler(passwort: string, anmeldename?: string | null): string | null { + if (passwort.length < KONTO_PASSWORT_MINDESTLAENGE) + return `Das Passwort braucht mindestens ${KONTO_PASSWORT_MINDESTLAENGE} Zeichen.`; + if (passwort.length > KONTO_PASSWORT_HOECHSTLAENGE) return "Das Passwort ist zu lang."; + if (anmeldename && passwort.toLowerCase().includes(anmeldename.toLowerCase())) + return "Das Passwort darf den Anmeldenamen nicht enthalten."; + return null; +} + +/** Ohne leicht verwechselbare Zeichen (0/O, 1/l/I) – das Passwort wird abgetippt. */ +const ZEICHEN = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + +/** + * Einmal-Passwort fuer ein neues oder zurueckgesetztes Konto: 4 × 5 Zeichen mit Bindestrichen, + * gut 115 Bit. Es wird genau einmal angezeigt und muss bei der ersten Anmeldung ersetzt werden. + */ +export function einmalPasswortErzeugen(): string { + return Array.from({ length: 4 }, () => + Array.from({ length: 5 }, () => ZEICHEN[randomInt(ZEICHEN.length)]).join(""), + ).join("-"); +} + +/** Lokale Konten lassen sich per `SSHKV_LOKALE_KONTEN=0` abschalten; der Notfall-Admin bleibt. */ +export function lokaleKontenAktiv( + umgebung: Readonly> = process.env, +): boolean { + const wert = umgebung["SSHKV_LOKALE_KONTEN"]?.trim().toLowerCase(); + return !(wert === "0" || wert === "false" || wert === "nein" || wert === "aus"); +} diff --git a/src/lib/protokoll-aktionen.ts b/src/lib/protokoll-aktionen.ts index 59cbfc7..b57185e 100644 --- a/src/lib/protokoll-aktionen.ts +++ b/src/lib/protokoll-aktionen.ts @@ -13,6 +13,7 @@ export const AKTIONSGRUPPEN = { anmeldung: "Anmeldung", + benutzer: "Benutzer", hostkey: "Host-Key", ausrollen: "Ausrollen", schluessel: "Schlüssel", @@ -44,6 +45,17 @@ export const AKTIONEN: Readonly> = { "anmeldung.oidc.erstmals": a("Erste Anmeldung (OIDC)", "anmeldung"), "anmeldung.verweigert": a("Anmeldung verweigert", "anmeldung", false), "anmeldung.notfall": a("Anmeldung Notfall-Admin", "anmeldung", false), + "anmeldung.lokal": a("Anmeldung (lokales Konto)", "anmeldung", false), + "anmeldung.lokal.fehlgeschlagen": a("Anmeldung (lokal) fehlgeschlagen", "anmeldung", false), + "anmeldung.lokal.gesperrt": a("Anmeldung (lokal) gesperrt – zu viele Fehlversuche", "anmeldung", false), + "anmeldung.lokal.totp_falsch": a("Anmeldung (lokal): falscher TOTP-Code", "anmeldung", false), + "konto.passwort_gesetzt": a("Eigenes Passwort gesetzt", "benutzer"), + "konto.totp_eingerichtet": a("TOTP eingerichtet", "anmeldung"), + "benutzer.angelegt": a("Lokales Konto angelegt", "benutzer"), + "benutzer.rolle_geaendert": a("Rolle geändert", "benutzer"), + "benutzer.gesperrt": a("Konto gesperrt", "benutzer"), + "benutzer.entsperrt": a("Konto entsperrt", "benutzer"), + "benutzer.zurueckgesetzt": a("Konto zurückgesetzt (Passwort und TOTP)", "benutzer"), "anmeldung.notfall.fehlgeschlagen": a("Notfall-Anmeldung fehlgeschlagen", "anmeldung", false), "anmeldung.notfall.gesperrt": a("Notfall-Anmeldung gesperrt", "anmeldung", false), "anmeldung.notfall.totp_falsch": a("Notfall-Anmeldung: TOTP falsch", "anmeldung", false), diff --git a/src/lib/sitzung.ts b/src/lib/sitzung.ts index 5070adc..6c47b67 100644 --- a/src/lib/sitzung.ts +++ b/src/lib/sitzung.ts @@ -21,7 +21,7 @@ export const SITZUNG_COOKIE = "sshkv_sitzung"; */ export const SITZUNG_LAUFZEIT_STUNDEN = 8; -export type SitzungsHerkunft = "notfall" | "oidc"; +export type SitzungsHerkunft = "notfall" | "lokal" | "oidc"; export type Sitzung = { benutzerId: string; @@ -45,7 +45,7 @@ const ALGORITHMUS = "HS256"; const MINDESTLAENGE = 32; -const ERLAUBTE_HERKUNFT: readonly SitzungsHerkunft[] = ["notfall", "oidc"]; +const ERLAUBTE_HERKUNFT: readonly SitzungsHerkunft[] = ["notfall", "lokal", "oidc"]; export class SitzungsFehler extends Error { constructor(nachricht: string, optionen?: ErrorOptions) { diff --git a/src/server/actions/anmeldung.ts b/src/server/actions/anmeldung.ts index b3db3d5..3de883b 100644 --- a/src/server/actions/anmeldung.ts +++ b/src/server/actions/anmeldung.ts @@ -7,16 +7,21 @@ import { totpGeheimnisErzeugen } from "@/lib/totp"; import { z } from "@/lib/validierung"; import { sicheresZiel, sitzungSetzen, ueberHttps } from "@/server/auth/anmeldung"; import { angemeldet } from "@/server/auth/sitzungspflicht"; +import type { Benutzer } from "@/generated/prisma/client"; import { EINRICHTUNG_SEKUNDEN, EINRICHTUNG_ZWECK, + PASSWORT_SETZEN_COOKIE, + PASSWORT_SETZEN_ZWECK, TOTP_EINRICHTUNG_COOKIE, + passwortSetzenLesen, totpEinrichtungLesen, } from "@/server/auth/totp-einrichtung"; import { AnmeldeFehler, - notfallAnmelden, - notfallBestaetigen, + eigenesPasswortSetzen, + kontoAnmelden, + kontoBestaetigen, totpEinrichtungAbschliessen, } from "@/server/services/anmeldung"; @@ -27,48 +32,97 @@ import { */ export type FormularZustand = { readonly fehler?: string }; -const notfallSchema = z.object({ +const kontoSchema = z.object({ anmeldename: z.string().trim().min(1).max(64), passwort: z.string().min(1).max(256), code: z.string().trim().max(10).default(""), ziel: z.string().max(512).optional(), }); -export async function notfallAnmeldenAktion( +const herkunft = (benutzer: Benutzer) => (benutzer.istNotfall ? "notfall" : "lokal"); + +/** Kurzlebige httpOnly-Cookies fuer die Einrichtungsschritte – nur auf /anmelden gueltig. */ +async function einrichtungsCookie(name: string, token: string) { + (await cookies()).set(name, token, { + httpOnly: true, + sameSite: "strict", + secure: ueberHttps(), + path: "/anmelden", + maxAge: EINRICHTUNG_SEKUNDEN, + }); +} + +async function totpEinrichtungStarten(benutzer: Benutzer, ziel: string): Promise { + // Das neue Geheimnis lebt bis zur Bestaetigung nur in einem signierten, kurzlebigen + // httpOnly-Cookie dieses Browsers – gespeichert wird es erst, wenn ein Code passt. + const token = await kurzzeitTokenErzeugen( + EINRICHTUNG_ZWECK, + { benutzerId: benutzer.id, geheimnis: totpGeheimnisErzeugen(), ziel }, + EINRICHTUNG_SEKUNDEN, + ); + await einrichtungsCookie(TOTP_EINRICHTUNG_COOKIE, token); + redirect("/anmelden/totp"); +} + +export async function kontoAnmeldenAktion( _vorher: FormularZustand, formular: FormData, ): Promise { - const eingabe = notfallSchema.safeParse(Object.fromEntries(formular)); + const eingabe = kontoSchema.safeParse(Object.fromEntries(formular)); if (!eingabe.success) return { fehler: "Bitte Anmeldename und Passwort angeben." }; const ziel = sicheresZiel(eingabe.data.ziel); let ergebnis; try { - ergebnis = await notfallAnmelden(eingabe.data.anmeldename, eingabe.data.passwort, eingabe.data.code); + ergebnis = await kontoAnmelden(eingabe.data.anmeldename, eingabe.data.passwort, eingabe.data.code); } catch (fehler) { if (fehler instanceof AnmeldeFehler) return { fehler: fehler.message }; throw fehler; } - if (ergebnis.art === "totpEinrichten") { - // Das neue Geheimnis lebt bis zur Bestaetigung nur in einem signierten, kurzlebigen - // httpOnly-Cookie dieses Browsers – gespeichert wird es erst, wenn ein Code passt. + if (ergebnis.art === "passwortSetzen") { const token = await kurzzeitTokenErzeugen( - EINRICHTUNG_ZWECK, - { benutzerId: ergebnis.benutzer.id, geheimnis: totpGeheimnisErzeugen(), ziel }, + PASSWORT_SETZEN_ZWECK, + { benutzerId: ergebnis.benutzer.id, ziel }, EINRICHTUNG_SEKUNDEN, ); - (await cookies()).set(TOTP_EINRICHTUNG_COOKIE, token, { - httpOnly: true, - sameSite: "strict", - secure: ueberHttps(), - path: "/anmelden", - maxAge: EINRICHTUNG_SEKUNDEN, - }); - redirect("/anmelden/totp"); + await einrichtungsCookie(PASSWORT_SETZEN_COOKIE, token); + redirect("/anmelden/passwort"); } + if (ergebnis.art === "totpEinrichten") await totpEinrichtungStarten(ergebnis.benutzer, ziel); - await sitzungSetzen(ergebnis.benutzer, "notfall"); + await sitzungSetzen(ergebnis.benutzer, herkunft(ergebnis.benutzer)); + redirect(ziel); +} + +const passwortSchema = z.object({ + passwort: z.string().max(256), + wiederholung: z.string().max(256), +}); + +/** Einmal-Passwort ersetzen (#035); danach geht es zur TOTP-Einrichtung. */ +export async function passwortSetzenAktion( + _vorher: FormularZustand, + formular: FormData, +): Promise { + const ausstehend = await passwortSetzenLesen(); + if (!ausstehend) + return { fehler: "Die Sitzung zum Passwortwechsel ist abgelaufen. Bitte erneut anmelden." }; + const eingabe = passwortSchema.safeParse(Object.fromEntries(formular)); + if (!eingabe.success) return { fehler: "Bitte das neue Passwort zweimal eingeben." }; + if (eingabe.data.passwort !== eingabe.data.wiederholung) + return { fehler: "Die beiden Eingaben stimmen nicht überein." }; + let benutzer; + try { + benutzer = await eigenesPasswortSetzen(ausstehend.benutzerId, eingabe.data.passwort); + } catch (fehler) { + if (fehler instanceof AnmeldeFehler) return { fehler: fehler.message }; + throw fehler; + } + (await cookies()).delete({ name: PASSWORT_SETZEN_COOKIE, path: "/anmelden" }); + const ziel = sicheresZiel(ausstehend.ziel); + if (!benutzer.totpEingerichtetAm) await totpEinrichtungStarten(benutzer, ziel); + await sitzungSetzen(benutzer, herkunft(benutzer)); redirect(ziel); } @@ -88,7 +142,7 @@ export async function totpEinrichtenAktion( } const laden = await cookies(); laden.delete({ name: TOTP_EINRICHTUNG_COOKIE, path: "/anmelden" }); - await sitzungSetzen(benutzer, "notfall"); + await sitzungSetzen(benutzer, herkunft(benutzer)); redirect(sicheresZiel(einrichtung.ziel)); } @@ -98,7 +152,7 @@ const bestaetigenSchema = z.object({ ziel: z.string().max(512).optional(), }); -export async function notfallBestaetigenAktion( +export async function kontoBestaetigenAktion( _vorher: FormularZustand, formular: FormData, ): Promise { @@ -107,7 +161,7 @@ export async function notfallBestaetigenAktion( const eingabe = bestaetigenSchema.safeParse(Object.fromEntries(formular)); if (!eingabe.success) return { fehler: "Bitte Passwort und Code angeben." }; try { - await notfallBestaetigen(vorhanden.benutzer, eingabe.data.passwort, eingabe.data.code); + await kontoBestaetigen(vorhanden.benutzer, eingabe.data.passwort, eingabe.data.code); } catch (fehler) { if (fehler instanceof AnmeldeFehler) return { fehler: fehler.message }; throw fehler; diff --git a/src/server/actions/benutzer.ts b/src/server/actions/benutzer.ts new file mode 100644 index 0000000..6038455 --- /dev/null +++ b/src/server/actions/benutzer.ts @@ -0,0 +1,82 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { anmeldenameSchema, kontoNameSchema, kontoRolleSchema } from "@/lib/lokale-konten"; +import { z } from "@/lib/validierung"; +import { akteurVon, fehlerZustand, textfeld, type FormularZustand } from "@/server/actions/formular"; +import { rechtErforderlich } from "@/server/auth/sitzungspflicht"; +import { + kontoAktivSetzen, + kontoZuruecksetzen, + lokalesKontoAnlegen, + rolleAendern, +} from "@/server/services/benutzer"; + +/** Benutzerverwaltung (#035). Duenn: Recht mit Re-Auth, Eingabe, Service. */ + +const PFAD = "/einstellungen/benutzer"; +const id = z.string().min(1).max(64); + +/** Das Einmal-Passwort geht genau einmal an den Browser des Admins – nie ins Protokoll. */ +export type KontoZustand = FormularZustand & { + readonly einmalPasswort?: string; + readonly anmeldename?: string; +}; + +export async function kontoAnlegenAktion(_vorher: KontoZustand, formular: FormData): Promise { + try { + const { benutzer } = await rechtErforderlich("benutzerVerwalten"); + const eingabe = z + .object({ anmeldename: anmeldenameSchema, name: kontoNameSchema, rolle: kontoRolleSchema }) + .safeParse({ + anmeldename: textfeld(formular, "anmeldename") ?? "", + name: textfeld(formular, "name") ?? "", + rolle: textfeld(formular, "rolle") ?? "LESER", + }); + if (!eingabe.success) { + const feldfehler: Record = {}; + for (const fehler of eingabe.error.issues) feldfehler[String(fehler.path[0])] ??= fehler.message; + return { feldfehler }; + } + const { einmalPasswort } = await lokalesKontoAnlegen(eingabe.data, akteurVon(benutzer)); + revalidatePath(PFAD); + return { einmalPasswort, anmeldename: eingabe.data.anmeldename }; + } catch (fehler) { + return fehlerZustand(fehler); + } +} + +export async function rolleAendernAktion(kontoId: string, rolle: string): Promise<{ fehler?: string }> { + try { + const { benutzer } = await rechtErforderlich("benutzerVerwalten"); + await rolleAendern(id.parse(kontoId), kontoRolleSchema.parse(rolle), akteurVon(benutzer)); + revalidatePath(PFAD); + return {}; + } catch (fehler) { + return fehlerZustand(fehler); + } +} + +export async function aktivSetzenAktion(kontoId: string, aktiv: boolean): Promise<{ fehler?: string }> { + try { + const { benutzer } = await rechtErforderlich("benutzerVerwalten"); + await kontoAktivSetzen(id.parse(kontoId), z.boolean().parse(aktiv), akteurVon(benutzer)); + revalidatePath(PFAD); + return {}; + } catch (fehler) { + return fehlerZustand(fehler); + } +} + +export async function zuruecksetzenAktion( + kontoId: string, +): Promise<{ fehler?: string; einmalPasswort?: string }> { + try { + const { benutzer } = await rechtErforderlich("benutzerVerwalten"); + const { einmalPasswort } = await kontoZuruecksetzen(id.parse(kontoId), akteurVon(benutzer)); + revalidatePath(PFAD); + return { einmalPasswort }; + } catch (fehler) { + return fehlerZustand(fehler); + } +} diff --git a/src/server/auth/rechte.ts b/src/server/auth/rechte.ts index 4e618b9..4f2a9ce 100644 --- a/src/server/auth/rechte.ts +++ b/src/server/auth/rechte.ts @@ -20,7 +20,8 @@ export type Recht = | "deploymentKeyVerwalten" | "massenwiderruf" | "zielkontenVerwalten" - | "einstellungenVerwalten"; + | "einstellungenVerwalten" + | "benutzerVerwalten"; export const RECHTE: Readonly> = { lesen: ["ADMIN", "OPERATOR", "LESER"], @@ -40,6 +41,9 @@ export const RECHTE: Readonly> = { massenwiderruf: ["ADMIN"], zielkontenVerwalten: ["ADMIN"], einstellungenVerwalten: ["ADMIN"], + // Lokale Konten anlegen, Rollen vergeben, zuruecksetzen (#035) – wer das darf, kann sich jede + // Rolle verschaffen; deshalb nur Admins und nur mit Re-Auth. + benutzerVerwalten: ["ADMIN"], }; /** Rechte, die zusaetzlich eine frische Bestaetigung (Re-Auth) verlangen. */ @@ -49,6 +53,7 @@ export const BESTAETIGUNG_NOETIG: ReadonlySet = new Set([ "deploymentKeyVerwalten", "massenwiderruf", "zielkontenVerwalten", + "benutzerVerwalten", ]); export function darf(rolle: Rolle, recht: Recht): boolean { diff --git a/src/server/auth/sitzungspflicht.ts b/src/server/auth/sitzungspflicht.ts index 3e00520..47e52b3 100644 --- a/src/server/auth/sitzungspflicht.ts +++ b/src/server/auth/sitzungspflicht.ts @@ -5,6 +5,7 @@ import type { Benutzer } from "@/generated/prisma/client"; import { prisma } from "@/lib/db"; import { SITZUNG_COOKIE, sitzungLesen, type Sitzung } from "@/lib/sitzung"; import { BESTAETIGUNG_NOETIG, darf, type Recht } from "@/server/auth/rechte"; +import { passwortKonto } from "@/server/services/anmeldung"; /** * Wer ist angemeldet und was darf er (#004, Muster aus Laufzettel). @@ -74,9 +75,13 @@ export const angemeldet = cache(async (): Promise => { const benutzer = await prisma.benutzer.findUnique({ where: { id: sitzung.benutzerId } }); const urteil = sitzungBewerten(sitzung, benutzer); if (!urteil.gueltig || !benutzer) return undefined; - // Ein Notfall-Admin ohne eingerichtetes TOTP ist nur halb angemeldet und kommt nicht - // ueber die Einrichtung hinaus – siehe /anmelden/totp. - if (benutzer.istNotfall && !benutzer.totpEingerichtetAm) return undefined; + // Ein Passwort-Konto ohne eigenes Passwort oder ohne TOTP ist nur halb angemeldet und kommt + // nicht ueber die Einrichtung hinaus (/anmelden/passwort, /anmelden/totp). Abgeschaltete lokale + // Konten verlieren ihre Sitzung sofort (#035). + if (benutzer.passwortHash && !benutzer.oidcSub) { + if (!passwortKonto(benutzer) || benutzer.passwortWechselNoetig || !benutzer.totpEingerichtetAm) + return undefined; + } return { benutzer, sitzung }; }); diff --git a/src/server/auth/totp-einrichtung.ts b/src/server/auth/totp-einrichtung.ts index f2067f1..aa214cf 100644 --- a/src/server/auth/totp-einrichtung.ts +++ b/src/server/auth/totp-einrichtung.ts @@ -18,3 +18,17 @@ export async function totpEinrichtungLesen(): Promise< if (!werte?.["benutzerId"] || !werte["geheimnis"]) return undefined; return { benutzerId: werte["benutzerId"], geheimnis: werte["geheimnis"], ziel: werte["ziel"] ?? "/" }; } + +/** + * Ausstehender Passwortwechsel eines lokalen Kontos (#035): ausgestellt nur nach einem + * richtigen Einmal-Passwort, gilt so lange wie die TOTP-Einrichtung. + */ +export const PASSWORT_SETZEN_COOKIE = "sshkv_passwort_setzen"; +export const PASSWORT_SETZEN_ZWECK = "passwort-setzen"; + +export async function passwortSetzenLesen(): Promise<{ benutzerId: string; ziel: string } | undefined> { + const token = (await cookies()).get(PASSWORT_SETZEN_COOKIE)?.value; + const werte = await kurzzeitTokenLesen(PASSWORT_SETZEN_ZWECK, token); + if (!werte?.["benutzerId"]) return undefined; + return { benutzerId: werte["benutzerId"], ziel: werte["ziel"] ?? "/" }; +} diff --git a/src/server/queries/benutzer.ts b/src/server/queries/benutzer.ts new file mode 100644 index 0000000..43f7ab0 --- /dev/null +++ b/src/server/queries/benutzer.ts @@ -0,0 +1,35 @@ +import { prisma } from "@/lib/db"; + +/** Lesemodell der Benutzerverwaltung (#035). Nie Hashes oder TOTP-Geheimnisse – nur Zustaende. */ +export type KontoHerkunft = "notfall" | "lokal" | "oidc"; + +export async function benutzerListe() { + const konten = await prisma.benutzer.findMany({ + orderBy: [{ aktiv: "desc" }, { name: "asc" }], + select: { + id: true, + name: true, + anmeldename: true, + email: true, + rolle: true, + aktiv: true, + istNotfall: true, + oidcSub: true, + passwortWechselNoetig: true, + totpEingerichtetAm: true, + letzteAnmeldungAm: true, + angelegtAm: true, + }, + }); + return konten.map(({ oidcSub, istNotfall, totpEingerichtetAm, passwortWechselNoetig, ...k }) => { + const herkunft: KontoHerkunft = istNotfall ? "notfall" : oidcSub ? "oidc" : "lokal"; + return { + ...k, + herkunft, + // Nur fuer Passwort-Konten von Bedeutung: noch nicht fertig eingerichtet. + einrichtungOffen: herkunft !== "oidc" && (passwortWechselNoetig || !totpEingerichtetAm), + }; + }); +} + +export type BenutzerZeile = Awaited>[number]; diff --git a/src/server/services/anmeldung.ts b/src/server/services/anmeldung.ts index 3b2a768..a78844b 100644 --- a/src/server/services/anmeldung.ts +++ b/src/server/services/anmeldung.ts @@ -1,7 +1,8 @@ import type { Benutzer } from "@/generated/prisma/client"; import { prisma } from "@/lib/db"; import { entschluesseln, verschluesseln } from "@/lib/geheimnisse"; -import { passwortPruefen } from "@/lib/passwort-hash"; +import { lokaleKontenAktiv, passwortFehler } from "@/lib/lokale-konten"; +import { passwortHashen, passwortPruefen } from "@/lib/passwort-hash"; import { totpPruefen } from "@/lib/totp"; import type { OidcAnspruecke } from "@/server/auth/oidc"; import { rolleAusGruppen } from "@/server/auth/oidc"; @@ -9,7 +10,7 @@ import { protokollieren } from "@/server/services/protokoll"; import { versuchZaehlen, zaehlerLoeschen } from "@/server/services/ratenbegrenzung"; /** - * Anmeldelogik ohne Next (#004): OIDC-Konto uebernehmen, Notfall-Admin pruefen, TOTP + * Anmeldelogik ohne Next (#004, #035): OIDC-Konto uebernehmen, Passwort-Konten pruefen, TOTP * einrichten, erneute Bestaetigung. Die Next-Schicht setzt danach nur noch das Cookie. */ @@ -27,8 +28,8 @@ export class AnmeldeFehler extends Error { } /** Fuenf Fehlversuche je Anmeldename in 15 Minuten. */ -const NOTFALL_VERSUCHE = 5; -const NOTFALL_FENSTER_SEKUNDEN = 15 * 60; +const KONTO_VERSUCHE = 5; +const KONTO_FENSTER_SEKUNDEN = 15 * 60; /** Allgemeine Meldung – verraet nicht, ob der Name, das Passwort oder der Code falsch war. */ const FALSCHE_ANGABEN = "Anmeldung fehlgeschlagen. Bitte Angaben prüfen."; @@ -115,53 +116,67 @@ export async function oidcBestaetigungVermerken( }); } -// ---------------------------------------------------------------- Notfall-Admin +// ---------------------------------------------------------------- Lokale Konten -export type NotfallErgebnis = +/** + * Passwort-Konten: der Notfall-Admin (#004) und von Admins angelegte lokale Konten (#035). + * Beide melden sich mit Anmeldename, Passwort und Pflicht-TOTP an. + */ +export type KontoErgebnis = | { readonly art: "angemeldet"; readonly benutzer: Benutzer } + | { readonly art: "passwortSetzen"; readonly benutzer: Benutzer } | { readonly art: "totpEinrichten"; readonly benutzer: Benutzer }; +/** Darf sich dieses Konto mit Passwort anmelden? OIDC-Konten nie, lokale nur wenn eingeschaltet. */ +export function passwortKonto(benutzer: Pick): boolean { + return Boolean(benutzer.passwortHash) && !benutzer.oidcSub && (benutzer.istNotfall || lokaleKontenAktiv()); +} + +/** Protokoll-Praefix: der Notfall-Admin bleibt im Audit-Log als solcher erkennbar. */ +const praefix = (benutzer: Pick | null) => + benutzer?.istNotfall ? "anmeldung.notfall" : "anmeldung.lokal"; + /** * Prueft Anmeldename, Passwort und – sobald eingerichtet – den TOTP-Code. * - * Ohne eingerichtetes TOTP fuehrt ein richtiges Passwort nur zur Einrichtung, nie direkt - * in die Anwendung (Freigabe Frage 5: TOTP ist Pflicht). + * Ein Einmal-Passwort fuehrt nur zum Setzen eines eigenen, ein Konto ohne TOTP nur zur + * Einrichtung – nie direkt in die Anwendung (Freigabe Frage 5: TOTP ist Pflicht). */ -export async function notfallAnmelden( +export async function kontoAnmelden( anmeldename: string, passwort: string, code: string, jetzt = new Date(), -): Promise { - const zaehler = `notfall:${anmeldename.toLowerCase()}`; - const versuch = await versuchZaehlen(zaehler, NOTFALL_VERSUCHE, NOTFALL_FENSTER_SEKUNDEN); +): Promise { + const name = anmeldename.trim().toLowerCase(); + const zaehler = `konto:${name}`; + const versuch = await versuchZaehlen(zaehler, KONTO_VERSUCHE, KONTO_FENSTER_SEKUNDEN); + const benutzer = await prisma.benutzer.findUnique({ where: { anmeldename: name } }); if (!versuch.erlaubt) { await protokollieren({ akteur: "System", - aktion: "anmeldung.notfall.gesperrt", + aktion: `${praefix(benutzer)}.gesperrt`, ergebnis: "VERWEIGERT", - details: { anmeldename }, + details: { anmeldename: name }, }); throw new AnmeldeFehler( `Zu viele Fehlversuche. Bitte in ${Math.ceil(versuch.wiederInSekunden / 60)} Minuten erneut versuchen.`, ); } - const benutzer = await prisma.benutzer.findUnique({ where: { anmeldename } }); const passwortStimmt = - benutzer?.istNotfall && benutzer.passwortHash - ? await passwortPruefen(benutzer.passwortHash, passwort) - : false; + benutzer && passwortKonto(benutzer) ? await passwortPruefen(benutzer.passwortHash!, passwort) : false; if (!benutzer || !passwortStimmt || !benutzer.aktiv) { await protokollieren({ akteur: "System", - aktion: "anmeldung.notfall.fehlgeschlagen", + aktion: `${praefix(benutzer)}.fehlgeschlagen`, ergebnis: "VERWEIGERT", - details: { anmeldename }, + details: { anmeldename: name }, }); throw new AnmeldeFehler(FALSCHE_ANGABEN); } + if (benutzer.passwortWechselNoetig) return { art: "passwortSetzen", benutzer }; if (!benutzer.totpGeheimnis || !benutzer.totpEingerichtetAm) { return { art: "totpEinrichten", benutzer }; } @@ -175,7 +190,7 @@ export async function notfallAnmelden( if (schritt === undefined) { await protokollieren({ akteur: { id: benutzer.id, name: benutzer.name }, - aktion: "anmeldung.notfall.totp_falsch", + aktion: `${praefix(benutzer)}.totp_falsch`, ergebnis: "VERWEIGERT", }); throw new AnmeldeFehler(FALSCHE_ANGABEN); @@ -188,16 +203,42 @@ export async function notfallAnmelden( }); await protokollieren({ akteur: { id: benutzer.id, name: benutzer.name }, - aktion: "anmeldung.notfall", + aktion: praefix(benutzer), ergebnis: "ERFOLG", }); return { art: "angemeldet", benutzer: aktualisiert }; } +/** + * Ersetzt das Einmal-Passwort durch ein eigenes (#035). Nur moeglich, solange der Wechsel + * aussteht – das Kurzzeit-Token dafuer stellt allein ein richtiges Einmal-Passwort aus. + */ +export async function eigenesPasswortSetzen(benutzerId: string, neu: string): Promise { + const benutzer = await prisma.benutzer.findUnique({ where: { id: benutzerId } }); + if (!benutzer || !passwortKonto(benutzer) || !benutzer.passwortWechselNoetig || !benutzer.aktiv) { + throw new AnmeldeFehler("Für dieses Konto steht kein Passwortwechsel aus."); + } + const fehler = passwortFehler(neu, benutzer.anmeldename); + if (fehler) throw new AnmeldeFehler(fehler); + if (await passwortPruefen(benutzer.passwortHash!, neu)) { + throw new AnmeldeFehler("Bitte ein anderes Passwort als das Einmal-Passwort wählen."); + } + const aktualisiert = await prisma.benutzer.update({ + where: { id: benutzerId }, + data: { passwortHash: await passwortHashen(neu), passwortWechselNoetig: false }, + }); + await protokollieren({ + akteur: { id: benutzer.id, name: benutzer.name }, + aktion: "konto.passwort_gesetzt", + ergebnis: "ERFOLG", + }); + return aktualisiert; +} + /** * Schliesst die TOTP-Einrichtung ab. Das Geheimnis kommt aus dem kurzlebigen * Einrichtungs-Token und wird erst gespeichert, wenn ein Code dazu passt – sonst saesse - * der Notfall-Admin mit einem Geheimnis fest, das in keiner App steht. + * das Konto mit einem Geheimnis fest, das in keiner App steht. */ export async function totpEinrichtungAbschliessen( benutzerId: string, @@ -206,7 +247,12 @@ export async function totpEinrichtungAbschliessen( jetzt = new Date(), ): Promise { const benutzer = await prisma.benutzer.findUnique({ where: { id: benutzerId } }); - if (!benutzer?.istNotfall || benutzer.totpEingerichtetAm) { + if ( + !benutzer || + !passwortKonto(benutzer) || + benutzer.totpEingerichtetAm || + benutzer.passwortWechselNoetig + ) { throw new AnmeldeFehler("TOTP ist für dieses Konto bereits eingerichtet."); } const schritt = totpPruefen(geheimnisBase32, code, jetzt.getTime()); @@ -227,18 +273,18 @@ export async function totpEinrichtungAbschliessen( }); await protokollieren({ akteur: { id: benutzer.id, name: benutzer.name }, - aktion: "notfall.totp_eingerichtet", + aktion: benutzer.istNotfall ? "notfall.totp_eingerichtet" : "konto.totp_eingerichtet", ergebnis: "ERFOLG", }); return aktualisiert; } -/** Erneute Bestaetigung des Notfall-Admins: Passwort und TOTP noch einmal. */ -export async function notfallBestaetigen(benutzer: Benutzer, passwort: string, code: string): Promise { - if (!benutzer.istNotfall || !benutzer.anmeldename) { +/** Erneute Bestaetigung eines Passwort-Kontos: Passwort und TOTP noch einmal. */ +export async function kontoBestaetigen(benutzer: Benutzer, passwort: string, code: string): Promise { + if (!passwortKonto(benutzer) || !benutzer.anmeldename) { throw new AnmeldeFehler("Dieses Konto bestätigt sich über den Identitätsanbieter."); } - const ergebnis = await notfallAnmelden(benutzer.anmeldename, passwort, code); + const ergebnis = await kontoAnmelden(benutzer.anmeldename, passwort, code); if (ergebnis.art !== "angemeldet" || ergebnis.benutzer.id !== benutzer.id) { throw new AnmeldeFehler(FALSCHE_ANGABEN); } diff --git a/src/server/services/benutzer.ts b/src/server/services/benutzer.ts new file mode 100644 index 0000000..50ea881 --- /dev/null +++ b/src/server/services/benutzer.ts @@ -0,0 +1,152 @@ +import type { Rolle } from "@/generated/prisma/enums"; +import { prisma } from "@/lib/db"; +import { einmalPasswortErzeugen, lokaleKontenAktiv } from "@/lib/lokale-konten"; +import { passwortHashen } from "@/lib/passwort-hash"; +import { FachFehler } from "@/server/services/fachfehler"; +import { protokollieren } from "@/server/services/protokoll"; + +/** + * Benutzerverwaltung (#035). Lokale Konten legt ein Admin an; sshkv erzeugt ein Einmal-Passwort, + * das genau einmal angezeigt wird – Admins waehlen also nie Passwoerter fuer andere. Bei der + * ersten Anmeldung setzt der Benutzer ein eigenes Passwort und richtet TOTP ein. + * + * OIDC-Konten bekommen ihre Rolle aus den Gruppen des Anbieters; hier lassen sie sich nur + * sperren und entsperren. Der Notfall-Admin ist unantastbar – er ist der Weg zurueck, wenn + * alles andere versagt. + */ +type Akteur = { readonly id: string; readonly name: string }; + +const KONTO = { + id: true, + name: true, + anmeldename: true, + rolle: true, + aktiv: true, + istNotfall: true, + oidcSub: true, + passwortHash: true, +} as const; + +async function kontoLaden(id: string) { + const konto = await prisma.benutzer.findUnique({ where: { id }, select: KONTO }); + if (!konto) throw new FachFehler("Dieses Konto gibt es nicht."); + return konto; +} + +function lokal(konto: { istNotfall: boolean; oidcSub: string | null; passwortHash: string | null }) { + return !konto.istNotfall && !konto.oidcSub && Boolean(konto.passwortHash); +} + +function nichtSelbst(konto: { id: string }, akteur: Akteur, was: string) { + if (konto.id === akteur.id) throw new FachFehler(`Das eigene Konto lässt sich nicht ${was}.`); +} + +function kontenErlaubt() { + if (!lokaleKontenAktiv()) { + throw new FachFehler("Lokale Konten sind abgeschaltet (SSHKV_LOKALE_KONTEN=0)."); + } +} + +export async function lokalesKontoAnlegen( + eingabe: { anmeldename: string; name: string; rolle: Rolle }, + akteur: Akteur, +): Promise<{ id: string; einmalPasswort: string }> { + kontenErlaubt(); + if ( + await prisma.benutzer.findUnique({ where: { anmeldename: eingabe.anmeldename }, select: { id: true } }) + ) { + throw new FachFehler("Diesen Anmeldenamen gibt es schon.", { anmeldename: "Bereits vergeben." }); + } + const einmalPasswort = einmalPasswortErzeugen(); + const konto = await prisma.benutzer.create({ + data: { + anmeldename: eingabe.anmeldename, + name: eingabe.name, + rolle: eingabe.rolle, + passwortHash: await passwortHashen(einmalPasswort), + passwortWechselNoetig: true, + }, + select: { id: true }, + }); + await protokollieren({ + akteur, + aktion: "benutzer.angelegt", + ergebnis: "ERFOLG", + details: { anmeldename: eingabe.anmeldename, name: eingabe.name, rolle: eingabe.rolle }, + }); + return { id: konto.id, einmalPasswort }; +} + +/** Rolle nur fuer lokale Konten – bei OIDC entscheidet die Gruppe im Anbieter. */ +export async function rolleAendern(id: string, rolle: Rolle, akteur: Akteur): Promise { + const konto = await kontoLaden(id); + if (!lokal(konto)) { + throw new FachFehler( + konto.istNotfall + ? "Der Notfall-Admin bleibt Admin." + : "Die Rolle dieses Kontos kommt aus den Gruppen des Identitätsanbieters.", + ); + } + nichtSelbst(konto, akteur, "herabstufen"); + if (konto.rolle === rolle) return; + await prisma.benutzer.update({ + where: { id }, + // Neue Rolle gilt sofort; alte Sitzungen sollen nicht mit gespeicherter Bestaetigung weiterlaufen. + data: { rolle, bestaetigtAm: null }, + }); + await protokollieren({ + akteur, + aktion: "benutzer.rolle_geaendert", + ergebnis: "ERFOLG", + details: { anmeldename: konto.anmeldename, name: konto.name, vorher: konto.rolle, nachher: rolle }, + }); +} + +/** Sperren beendet alle Sitzungen sofort; gilt auch fuer OIDC-Konten. */ +export async function kontoAktivSetzen(id: string, aktiv: boolean, akteur: Akteur): Promise { + const konto = await kontoLaden(id); + if (konto.istNotfall) throw new FachFehler("Der Notfall-Admin lässt sich nicht sperren."); + nichtSelbst(konto, akteur, "sperren"); + if (konto.aktiv === aktiv) return; + await prisma.benutzer.update({ + where: { id }, + data: aktiv ? { aktiv } : { aktiv, sitzungenGueltigAb: new Date(), bestaetigtAm: null }, + }); + await protokollieren({ + akteur, + aktion: aktiv ? "benutzer.entsperrt" : "benutzer.gesperrt", + ergebnis: "ERFOLG", + details: { anmeldename: konto.anmeldename, name: konto.name }, + }); +} + +/** + * Passwort und TOTP eines lokalen Kontos zuruecksetzen – etwa nach verlorenem Telefon. Es gibt + * ein neues Einmal-Passwort; alle Sitzungen enden. + */ +export async function kontoZuruecksetzen(id: string, akteur: Akteur): Promise<{ einmalPasswort: string }> { + kontenErlaubt(); + const konto = await kontoLaden(id); + if (!lokal(konto)) throw new FachFehler("Nur lokale Konten lassen sich zurücksetzen."); + nichtSelbst(konto, akteur, "zurücksetzen"); + const einmalPasswort = einmalPasswortErzeugen(); + await prisma.benutzer.update({ + where: { id }, + data: { + passwortHash: await passwortHashen(einmalPasswort), + passwortWechselNoetig: true, + totpGeheimnis: null, + totpEingerichtetAm: null, + totpLetzterSchritt: null, + sitzungenGueltigAb: new Date(), + bestaetigtAm: null, + }, + }); + await protokollieren({ + akteur, + aktion: "benutzer.zurueckgesetzt", + ergebnis: "ERFOLG", + details: { anmeldename: konto.anmeldename, name: konto.name }, + }); + return { einmalPasswort }; +} diff --git a/tests/anmeldung.test.ts b/tests/anmeldung.test.ts index b51624b..ef54353 100644 --- a/tests/anmeldung.test.ts +++ b/tests/anmeldung.test.ts @@ -74,6 +74,7 @@ describe("Rechte", () => { it("verlangt fuer kritische Aktionen eine Bestaetigung, und die nur fuer Admin-Rechte", () => { expect([...BESTAETIGUNG_NOETIG].sort()).toEqual( [ + "benutzerVerwalten", "deploymentKeyVerwalten", "massenwiderruf", "privateKeyAnzeigen", diff --git a/tests/e2e/anmeldung.spec.ts b/tests/e2e/anmeldung.spec.ts index f85fe15..25ba6ac 100644 --- a/tests/e2e/anmeldung.spec.ts +++ b/tests/e2e/anmeldung.spec.ts @@ -66,10 +66,10 @@ test("OIDC ohne sshkv-Gruppe wird abgewiesen", async ({ page }) => { test("Notfall-Admin richtet TOTP ein und bestätigt sich erneut", async ({ page }) => { test.skip(NOTFALL_PASSWORT.length < 16, "SSHKV_BREAKGLASS_PASSWORD fehlt in .env"); await page.goto("/anmelden"); - await page.getByText("Notfallzugang", { exact: true }).click(); + await page.getByText("Lokales Konto oder Notfallzugang", { exact: true }).click(); await page.getByLabel("Anmeldename").fill("notfall"); await page.getByLabel("Passwort").fill(NOTFALL_PASSWORT); - await page.getByRole("button", { name: "Mit Notfallzugang anmelden" }).click(); + await page.getByRole("button", { name: "Mit Benutzername anmelden" }).click(); await expect(page).toHaveURL(/\/anmelden\/totp/); await expect(page.getByRole("img", { name: /QR-Code/ })).toBeVisible(); diff --git a/tests/e2e/lokale-konten.spec.ts b/tests/e2e/lokale-konten.spec.ts new file mode 100644 index 0000000..3c3dece --- /dev/null +++ b/tests/e2e/lokale-konten.spec.ts @@ -0,0 +1,76 @@ +import { expect, test } from "@playwright/test"; +import { base32Dekodieren, totpCode, totpSchritt } from "../../src/lib/totp"; +import { anmelden, oidcBestaetigen } from "./hilfen"; + +/** + * Lokale Konten (#035) im gebauten Stand: Admin legt ein Konto an, die Person meldet sich zum + * ersten Mal an (eigenes Passwort, TOTP), der Admin sperrt sie – die Sitzung endet sofort. + */ +test.setTimeout(120_000); + +test("lokales Konto anlegen, erste Anmeldung, sperren", async ({ page, browser }) => { + const anmeldename = `zz-e2e-${Date.now().toString(36)}`; + const passwort = "ein-eigenes-e2e-passwort"; + + await anmelden(page); + await page.goto("/einstellungen"); + await page.getByRole("link", { name: "Konten verwalten" }).click(); + await expect(page.getByRole("heading", { name: "Benutzer", exact: true })).toBeVisible(); + + await page.getByLabel("Anmeldename").fill(anmeldename); + await page.getByLabel("Name", { exact: true }).fill("E2E Lokal"); + await page.getByLabel("Rolle", { exact: true }).selectOption("OPERATOR"); + await page.getByRole("button", { name: "Konto anlegen" }).click(); + // Ohne frische Bestaetigung lehnt der Server ab; danach klappt es. + await expect(page.getByText(/erneute Bestätigung/)).toBeVisible(); + await page.goto("/bestaetigen?ziel=%2Feinstellungen%2Fbenutzer"); + await oidcBestaetigen(page); + await expect(page).toHaveURL(/\/einstellungen\/benutzer$/); + await page.getByLabel("Anmeldename").fill(anmeldename); + await page.getByLabel("Name", { exact: true }).fill("E2E Lokal"); + await page.getByLabel("Rolle", { exact: true }).selectOption("OPERATOR"); + await page.getByRole("button", { name: "Konto anlegen" }).click(); + const anzeige = page.getByRole("region", { name: `Einmal-Passwort für ${anmeldename}` }); + const einmal = ((await anzeige.locator("code").textContent()) ?? "").trim(); + expect(einmal).toMatch(/^[a-zA-Z2-9]{5}(-[a-zA-Z2-9]{5}){3}$/); + await page.screenshot({ path: "docs/anleitung/bilder/benutzer-desktop.png", fullPage: true }); + + // Zweiter Browser: die Person meldet sich zum ersten Mal an. + const kontext = await browser.newContext(); + const person = await kontext.newPage(); + await person.goto("/anmelden"); + await person.getByText("Lokales Konto oder Notfallzugang", { exact: true }).click(); + await person.getByLabel("Anmeldename").fill(anmeldename); + await person.getByLabel("Passwort").fill(einmal); + await person.getByRole("button", { name: "Mit Benutzername anmelden" }).click(); + await expect(person).toHaveURL(/\/anmelden\/passwort/); + await person.getByLabel("Neues Passwort", { exact: true }).fill("zu-kurz"); + await person.getByLabel("Neues Passwort wiederholen").fill("zu-kurz"); + await person.getByRole("button", { name: "Passwort setzen" }).click(); + await expect(person.getByText(/mindestens 12 Zeichen/).last()).toBeVisible(); + await person.getByLabel("Neues Passwort", { exact: true }).fill(passwort); + await person.getByLabel("Neues Passwort wiederholen").fill(passwort); + await person.getByRole("button", { name: "Passwort setzen" }).click(); + + await expect(person).toHaveURL(/\/anmelden\/totp/); + const geheimnis = ((await person.locator("code").first().textContent()) ?? "").trim(); + await person + .getByLabel("Code zur Bestätigung") + .fill(totpCode(base32Dekodieren(geheimnis), totpSchritt(Date.now()))); + await person.getByRole("button", { name: /TOTP einrichten/ }).click(); + await expect(person.getByRole("heading", { name: "Übersicht" })).toBeVisible(); + await expect(person.getByText("Operator · über lokales Konto")).toBeVisible(); + // Ein Operator verwaltet keine Benutzer. + await person.goto("/einstellungen/benutzer"); + await expect(person).toHaveURL(/\/kein-zugriff/); + + // Admin sperrt das Konto – die Sitzung der Person endet sofort. + await page.reload(); + const zeile = page.getByRole("row").filter({ hasText: anmeldename }); + await expect(zeile.getByText("Einrichtung offen")).toHaveCount(0); + await zeile.getByRole("button", { name: /Sperren/ }).click(); + await expect(zeile.getByText("gesperrt", { exact: true })).toBeVisible(); + await person.goto("/"); + await expect(person).toHaveURL(/\/anmelden/); + await kontext.close(); +}); diff --git a/tests/lokale-konten.test.ts b/tests/lokale-konten.test.ts new file mode 100644 index 0000000..db30989 --- /dev/null +++ b/tests/lokale-konten.test.ts @@ -0,0 +1,192 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { prisma } from "@/lib/db"; +import { + anmeldenameSchema, + einmalPasswortErzeugen, + lokaleKontenAktiv, + passwortFehler, +} from "@/lib/lokale-konten"; +import { passwortHashen } from "@/lib/passwort-hash"; +import { base32Dekodieren, totpCode, totpGeheimnisErzeugen, totpSchritt } from "@/lib/totp"; +import { + AnmeldeFehler, + eigenesPasswortSetzen, + kontoAnmelden, + totpEinrichtungAbschliessen, +} from "@/server/services/anmeldung"; +import { + kontoAktivSetzen, + kontoZuruecksetzen, + lokalesKontoAnlegen, + rolleAendern, +} from "@/server/services/benutzer"; +import { FachFehler } from "@/server/services/fachfehler"; +import { datenbankLeeren } from "./hilfen/db"; + +/** Lokale Konten (#035): Regeln, erste Anmeldung, Verwaltung, Abschalten. */ + +const EIGENES = "ein-eigenes-langes-passwort"; +let admin: { id: string; name: string }; + +beforeEach(async () => { + await datenbankLeeren(); + delete process.env["SSHKV_LOKALE_KONTEN"]; + admin = await prisma.benutzer.create({ + data: { name: "Ada Admin", rolle: "ADMIN", oidcSub: "ada" }, + select: { id: true, name: true }, + }); +}); +afterEach(() => { + delete process.env["SSHKV_LOKALE_KONTEN"]; +}); + +const code = (geheimnis: string, zeit: Date) => + totpCode(base32Dekodieren(geheimnis), totpSchritt(zeit.getTime())); + +/** Anlegen, Einmal-Passwort ersetzen, TOTP einrichten – wie bei der ersten Anmeldung. */ +async function eingerichtetesKonto(anmeldename = "anna") { + const { id, einmalPasswort } = await lokalesKontoAnlegen( + { anmeldename, name: "Anna", rolle: "OPERATOR" }, + admin, + ); + expect((await kontoAnmelden(anmeldename, einmalPasswort, "")).art).toBe("passwortSetzen"); + await eigenesPasswortSetzen(id, EIGENES); + expect((await kontoAnmelden(anmeldename, EIGENES, "")).art).toBe("totpEinrichten"); + const geheimnis = totpGeheimnisErzeugen(); + const t0 = new Date("2026-10-12T10:00:00Z"); + await totpEinrichtungAbschliessen(id, geheimnis, code(geheimnis, t0), t0); + return { id, geheimnis, einmalPasswort }; +} + +describe("Regeln", () => { + it("erzeugt gut abtippbare, verschiedene Einmal-Passwoerter", () => { + const pw = einmalPasswortErzeugen(); + expect(pw).toMatch(/^[a-zA-Z2-9]{5}(-[a-zA-Z2-9]{5}){3}$/); + expect(pw).not.toMatch(/[01OlI]/); + expect(new Set(Array.from({ length: 50 }, einmalPasswortErzeugen)).size).toBe(50); + }); + + it("prueft Anmeldename und Passwort", () => { + expect(anmeldenameSchema.parse(" Anna.Muster ")).toBe("anna.muster"); + expect(anmeldenameSchema.safeParse("1anna").success).toBe(false); + expect(anmeldenameSchema.safeParse("a").success).toBe(false); + expect(anmeldenameSchema.safeParse("anna muster").success).toBe(false); + expect(passwortFehler("kurz")).toMatch(/12 Zeichen/); + expect(passwortFehler("xx-anna-xx-geheim", "Anna")).toMatch(/Anmeldenamen/); + expect(passwortFehler(EIGENES, "anna")).toBeNull(); + }); + + it("schaltet lokale Konten nur mit ausdruecklichem Aus ab", () => { + expect(lokaleKontenAktiv({})).toBe(true); + expect(lokaleKontenAktiv({ SSHKV_LOKALE_KONTEN: "1" })).toBe(true); + for (const aus of ["0", "false", "nein", "AUS"]) + expect(lokaleKontenAktiv({ SSHKV_LOKALE_KONTEN: aus })).toBe(false); + }); +}); + +describe("Erste Anmeldung", () => { + it("verlangt erst ein eigenes Passwort, dann TOTP, dann geht es hinein", async () => { + const { id, geheimnis, einmalPasswort } = await eingerichtetesKonto(); + const t1 = new Date("2026-10-12T10:01:00Z"); + // Das Einmal-Passwort gilt nicht mehr, Gross-/Kleinschreibung des Namens egal. + await expect(kontoAnmelden("anna", einmalPasswort, code(geheimnis, t1), t1)).rejects.toThrow( + AnmeldeFehler, + ); + const ergebnis = await kontoAnmelden("Anna", EIGENES, code(geheimnis, t1), t1); + expect(ergebnis).toMatchObject({ art: "angemeldet", benutzer: { id, rolle: "OPERATOR" } }); + expect(await prisma.protokoll.count({ where: { aktion: "anmeldung.lokal" } })).toBe(1); + }); + + it("lehnt schwache und unveraenderte Passwoerter ab und laesst nichts im Protokoll", async () => { + const { id, einmalPasswort } = await lokalesKontoAnlegen( + { anmeldename: "bert", name: "Bert", rolle: "LESER" }, + admin, + ); + await expect(eigenesPasswortSetzen(id, "zu-kurz")).rejects.toThrow(/12 Zeichen/); + await expect(eigenesPasswortSetzen(id, einmalPasswort)).rejects.toThrow(/anderes Passwort/); + await eigenesPasswortSetzen(id, EIGENES); + // Kein zweites Mal ohne Zuruecksetzen. + await expect(eigenesPasswortSetzen(id, `${EIGENES}-2`)).rejects.toThrow(/kein Passwortwechsel/); + const protokoll = JSON.stringify(await prisma.protokoll.findMany(), (_k, v: unknown) => + typeof v === "bigint" ? v.toString() : v, + ); + expect(protokoll).not.toContain(einmalPasswort); + expect(protokoll).not.toContain(EIGENES); + }); + + it("kennt keinen doppelten Anmeldenamen – auch nicht den des Notfall-Admins", async () => { + await prisma.benutzer.create({ + data: { anmeldename: "notfall", name: "Notfall", rolle: "ADMIN", istNotfall: true, passwortHash: "x" }, + }); + await lokalesKontoAnlegen({ anmeldename: "anna", name: "Anna", rolle: "LESER" }, admin); + for (const name of ["anna", "notfall"]) { + await expect( + lokalesKontoAnlegen({ anmeldename: name, name: "X", rolle: "LESER" }, admin), + ).rejects.toThrow(FachFehler); + } + }); +}); + +describe("Verwaltung", () => { + it("aendert Rollen nur bei lokalen Konten und nie am eigenen", async () => { + const { id } = await eingerichtetesKonto(); + await rolleAendern(id, "ADMIN", admin); + expect((await prisma.benutzer.findUniqueOrThrow({ where: { id } })).rolle).toBe("ADMIN"); + // OIDC-Konto: Rolle kommt aus den Gruppen. + await expect(rolleAendern(admin.id, "LESER", { id: id, name: "Anna" })).rejects.toThrow(/Gruppen/); + await expect(rolleAendern(id, "LESER", { id, name: "Anna" })).rejects.toThrow(/eigene Konto/); + const notfall = await prisma.benutzer.create({ + data: { anmeldename: "notfall", name: "Notfall", rolle: "ADMIN", istNotfall: true, passwortHash: "x" }, + }); + await expect(rolleAendern(notfall.id, "LESER", admin)).rejects.toThrow(/Notfall-Admin/); + await expect(kontoAktivSetzen(notfall.id, false, admin)).rejects.toThrow(/Notfall-Admin/); + }); + + it("sperrt sofort: Sitzungen enden, Anmeldung scheitert", async () => { + const { id, geheimnis } = await eingerichtetesKonto(); + const vorher = (await prisma.benutzer.findUniqueOrThrow({ where: { id } })).sitzungenGueltigAb; + await kontoAktivSetzen(id, false, admin); + const gesperrt = await prisma.benutzer.findUniqueOrThrow({ where: { id } }); + expect(gesperrt.aktiv).toBe(false); + expect(gesperrt.sitzungenGueltigAb.getTime()).toBeGreaterThan(vorher.getTime()); + const t = new Date("2026-10-12T10:02:00Z"); + await expect(kontoAnmelden("anna", EIGENES, code(geheimnis, t), t)).rejects.toThrow(AnmeldeFehler); + await kontoAktivSetzen(id, true, admin); + expect((await kontoAnmelden("anna", EIGENES, code(geheimnis, t), t)).art).toBe("angemeldet"); + }); + + it("setzt Passwort und TOTP zurueck", async () => { + const { id } = await eingerichtetesKonto(); + const { einmalPasswort } = await kontoZuruecksetzen(id, admin); + const konto = await prisma.benutzer.findUniqueOrThrow({ where: { id } }); + expect(konto).toMatchObject({ + passwortWechselNoetig: true, + totpGeheimnis: null, + totpEingerichtetAm: null, + }); + expect((await kontoAnmelden("anna", einmalPasswort, "")).art).toBe("passwortSetzen"); + await expect(kontoZuruecksetzen(admin.id, { id, name: "Anna" })).rejects.toThrow(/lokale Konten/); + }); +}); + +describe("Abgeschaltet (SSHKV_LOKALE_KONTEN=0)", () => { + it("laesst lokale Konten nicht mehr hinein, den Notfall-Admin schon", async () => { + const { geheimnis } = await eingerichtetesKonto(); + await prisma.benutzer.create({ + data: { + anmeldename: "notfall", + name: "Notfall", + rolle: "ADMIN", + istNotfall: true, + passwortHash: await passwortHashen("notfall-test-passwort-123"), + }, + }); + process.env["SSHKV_LOKALE_KONTEN"] = "0"; + const t = new Date("2026-10-12T10:03:00Z"); + await expect(kontoAnmelden("anna", EIGENES, code(geheimnis, t), t)).rejects.toThrow(AnmeldeFehler); + expect((await kontoAnmelden("notfall", "notfall-test-passwort-123", "")).art).toBe("totpEinrichten"); + await expect( + lokalesKontoAnlegen({ anmeldename: "carl", name: "Carl", rolle: "LESER" }, admin), + ).rejects.toThrow(/abgeschaltet/); + }); +}); diff --git a/tests/notfall-anmeldung.test.ts b/tests/notfall-anmeldung.test.ts index 48f2dcc..decf9c2 100644 --- a/tests/notfall-anmeldung.test.ts +++ b/tests/notfall-anmeldung.test.ts @@ -4,7 +4,7 @@ import { passwortHashen } from "@/lib/passwort-hash"; import { base32Dekodieren, totpCode, totpGeheimnisErzeugen, totpSchritt } from "@/lib/totp"; import { AnmeldeFehler, - notfallAnmelden, + kontoAnmelden, oidcBenutzerUebernehmen, totpEinrichtungAbschliessen, } from "@/server/services/anmeldung"; @@ -37,7 +37,7 @@ beforeEach(async () => { describe("Notfall-Admin", () => { it("fuehrt beim ersten Mal zur TOTP-Einrichtung, nicht in die Anwendung", async () => { await notfallAnlegen(); - const ergebnis = await notfallAnmelden("notfall", PASSWORT, ""); + const ergebnis = await kontoAnmelden("notfall", PASSWORT, ""); expect(ergebnis.art).toBe("totpEinrichten"); }); @@ -56,33 +56,33 @@ describe("Notfall-Admin", () => { expect(Buffer.from(gespeichert.totpGeheimnis ?? []).toString("utf8")).not.toContain(geheimnis); const t1 = new Date(t0.getTime() + 60_000); - await expect(notfallAnmelden("notfall", PASSWORT, "123456", t1)).rejects.toThrow(AnmeldeFehler); - const ergebnis = await notfallAnmelden("notfall", PASSWORT, codeZu(geheimnis, t1), t1); + await expect(kontoAnmelden("notfall", PASSWORT, "123456", t1)).rejects.toThrow(AnmeldeFehler); + const ergebnis = await kontoAnmelden("notfall", PASSWORT, codeZu(geheimnis, t1), t1); expect(ergebnis.art).toBe("angemeldet"); // Derselbe Code ein zweites Mal: abgelehnt. - await expect(notfallAnmelden("notfall", PASSWORT, codeZu(geheimnis, t1), t1)).rejects.toThrow( + await expect(kontoAnmelden("notfall", PASSWORT, codeZu(geheimnis, t1), t1)).rejects.toThrow( AnmeldeFehler, ); }); it("verraet nicht, ob Name oder Passwort falsch war", async () => { await notfallAnlegen(); - const a = await notfallAnmelden("gibtsnicht", PASSWORT, "").catch((f: Error) => f.message); - const b = await notfallAnmelden("notfall", "falsch-falsch-falsch", "").catch((f: Error) => f.message); + const a = await kontoAnmelden("gibtsnicht", PASSWORT, "").catch((f: Error) => f.message); + const b = await kontoAnmelden("notfall", "falsch-falsch-falsch", "").catch((f: Error) => f.message); expect(a).toBe(b); }); it("sperrt nach fuenf Fehlversuchen, auch fuer das richtige Passwort", async () => { await notfallAnlegen(); for (let i = 0; i < 5; i += 1) { - await expect(notfallAnmelden("notfall", "falsch-falsch-falsch", "")).rejects.toThrow(); + await expect(kontoAnmelden("notfall", "falsch-falsch-falsch", "")).rejects.toThrow(); } - await expect(notfallAnmelden("notfall", PASSWORT, "")).rejects.toThrow(/Zu viele Fehlversuche/); + await expect(kontoAnmelden("notfall", PASSWORT, "")).rejects.toThrow(/Zu viele Fehlversuche/); }); it("protokolliert Fehlversuche ohne Passwort", async () => { await notfallAnlegen(); - await notfallAnmelden("notfall", "geheimes-falsches-pw", "").catch(() => undefined); + await kontoAnmelden("notfall", "geheimes-falsches-pw", "").catch(() => undefined); const eintraege = await prisma.protokoll.findMany(); expect(eintraege.map((e) => e.aktion)).toContain("anmeldung.notfall.fehlgeschlagen"); expect( diff --git a/tests/zugang.test.ts b/tests/zugang.test.ts index 2ab1688..485a6bd 100644 --- a/tests/zugang.test.ts +++ b/tests/zugang.test.ts @@ -10,6 +10,7 @@ import { describe, expect, it } from "vitest"; const AUSNAHMEN: Record = { "src/app/anmelden/page.tsx": "Anmeldeseite selbst", "src/app/anmelden/totp/page.tsx": "nur mit Einrichtungs-Cookie aus richtigem Passwort erreichbar", + "src/app/anmelden/passwort/page.tsx": "nur mit Cookie aus richtigem Einmal-Passwort erreichbar (#035)", "src/app/api/auth/abmelden/route.ts": "Abmelden braucht keine Pruefung", "src/app/api/auth/oidc/start/route.ts": "Beginn der Anmeldung", "src/app/api/auth/oidc/rueckruf/route.ts": "Ende der Anmeldung; prueft state, nonce und PKCE",