Files
sshkeyvault/src/app/anmelden/totp/page.tsx
T
florian.wildeandClaude Opus 5.5 d1ba822a4a feat(auth): Anmeldeseiten, TOTP-Einrichtung und Bestaetigung (#004)
Anmeldung, Einrichtung des zweiten Faktors fuer den Notfall-Admin,
erneute Bestaetigung vor kritischen Aktionen und die Seite fuer
fehlende Rechte. Die Huelle zeigt jetzt den echten Benutzer, und jede
Seite prueft die Sitzung selbst; ein struktureller Test haelt das fest.

Die Ablaeufe sind im gebauten Stand gegen den Mock-OIDC geprueft
(pnpm test:e2e): Admin, Konto ohne Gruppe, Notfall-Admin mit TOTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:28:51 +02:00

46 lines
1.6 KiB
TypeScript

import type { Metadata } from "next";
import { redirect } from "next/navigation";
import QRCode from "qrcode";
import { AnmeldeRahmen } from "@/components/anmeldung/AnmeldeRahmen";
import { TotpFormular } from "@/components/anmeldung/TotpFormular";
import { MonoKopieren } from "@/components/ui";
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.
*/
export default async function TotpSeite() {
const einrichtung = await totpEinrichtungLesen();
if (!einrichtung) redirect("/anmelden");
const adresse = totpAdresse(einrichtung.geheimnis, "Notfall-Admin");
// Als SVG im Server gerendert: kein Bild-Endpunkt, keine Bibliothek im Browser.
const svg = await QRCode.toString(adresse, {
type: "svg",
margin: 1,
color: { dark: "#0D1013", light: "#E8ECEF" },
});
return (
<AnmeldeRahmen
titel="Zweiten Faktor einrichten"
text="Code mit einer Authenticator-App scannen und den angezeigten Code eingeben."
>
<div className="flex flex-col gap-5">
<div
className="mx-auto w-[200px] overflow-hidden rounded-[10px]"
aria-label="QR-Code für die Authenticator-App"
role="img"
dangerouslySetInnerHTML={{ __html: svg }}
/>
<MonoKopieren wert={einrichtung.geheimnis} beschriftung="Geheimnis" umbrechen />
<TotpFormular />
</div>
</AnmeldeRahmen>
);
}