Anmeldung ueber Authentik und andere OIDC-Anbieter
Anmeldefluss mit PKCE (S256), State und Nonce. Das ID-Token wird gegen den JWKS des Anbieters geprueft — Signatur, Aussteller, Empfaenger, Ablauf und Nonce; alles Noetige kommt aus dem Discovery-Dokument, damit die Anbindung nicht auf Authentik festgelegt ist. Wiedererkannt wird jemand an der Kennung des Anbieters (sub), nicht an der E-Mail-Adresse — Adressen aendern sich. Ein bestehendes Konto mit derselben Adresse wird verknuepft statt verdoppelt, aber nur bei bestaetigter Adresse: sonst koennte sich beim Anbieter jemand eine fremde Adresse eintragen und damit ein hiesiges Konto uebernehmen. Uebernommen wird bewusst nur, wer jemand ist — plus die Instanz-Administration, wenn OIDC_ADMIN_GROUP gesetzt ist. Gruppen und Rollen bleiben Sache dieser Anwendung: die Gruppen im Verzeichnis bedeuten etwas anderes als die hier. Konten aus dem Verzeichnis haben kein Passwort mehr (passwordHash ist jetzt optional); Konto-Bereich und Passwort-Anmeldung sagen das statt an einer leeren Pruefung zu scheitern. Anleitung mit allen Feldern in Authentik: docs/authentik.md
This commit is contained in:
@@ -5,12 +5,15 @@ import { LoginForm, SetupForm } from "@/components/auth-forms";
|
||||
import { Icon } from "@/components/icons";
|
||||
import { Notice } from "@/components/ui";
|
||||
import { getActor, isAuthEnabled, isSetupOpen } from "@/lib/auth/actor";
|
||||
import { getOidcConfig, isOidcEnabled } from "@/lib/auth/oidc";
|
||||
import { MIN_PASSWORD_LENGTH } from "@/lib/auth/password";
|
||||
|
||||
export const metadata: Metadata = { title: "Anmelden" };
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function SignInPage() {
|
||||
type PageProps = { searchParams: Promise<{ fehler?: string }> };
|
||||
|
||||
export default async function SignInPage({ searchParams }: PageProps) {
|
||||
// Ohne Anmeldung gibt es hier nichts zu tun.
|
||||
if (!isAuthEnabled()) redirect("/");
|
||||
|
||||
@@ -18,30 +21,85 @@ export default async function SignInPage() {
|
||||
if (actor.kind === "user") redirect("/");
|
||||
|
||||
const setup = await isSetupOpen();
|
||||
const { fehler } = await searchParams;
|
||||
|
||||
// Eine unvollständige OIDC-Konfiguration soll die Anmeldeseite nicht
|
||||
// unbenutzbar machen — dann bleibt wenigstens das Passwortfeld.
|
||||
let oidc: { label: string; nurOidc: boolean } | null = null;
|
||||
let oidcFehler: string | null = null;
|
||||
if (isOidcEnabled()) {
|
||||
try {
|
||||
const config = getOidcConfig();
|
||||
oidc = { label: config.buttonLabel, nurOidc: !config.allowLocalLogin };
|
||||
} catch (error) {
|
||||
oidcFehler =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Die Anmeldung über den externen Anbieter ist nicht eingerichtet.";
|
||||
}
|
||||
}
|
||||
|
||||
// Bei reiner Anbieter-Anmeldung gibt es keine Ersteinrichtung mit Passwort:
|
||||
// Der erste, der sich über den Anbieter anmeldet, wird Administrator.
|
||||
const zeigeFormular = !oidc?.nurOidc;
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-md flex-col gap-6 px-4 py-14 sm:px-6">
|
||||
<div className="text-center">
|
||||
<span className="mx-auto flex size-12 items-center justify-center rounded-xl bg-indigo-600 text-white">
|
||||
<Icon name={setup ? "lock" : "qr"} className="size-6" />
|
||||
<Icon name={setup && zeigeFormular ? "lock" : "qr"} className="size-6" />
|
||||
</span>
|
||||
<h1 className="mt-4 text-2xl font-bold tracking-tight text-slate-900">
|
||||
{setup ? "Ersteinrichtung" : "Anmelden"}
|
||||
{setup && zeigeFormular ? "Ersteinrichtung" : "Anmelden"}
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
{setup
|
||||
{setup && zeigeFormular
|
||||
? "Es gibt noch keinen Benutzer. Lege jetzt den ersten Administrator an — danach ist diese Seite geschlossen."
|
||||
: "Bitte melde dich an, um QR-Codes zu erstellen und zu verwalten."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{setup ? <SetupForm minLength={MIN_PASSWORD_LENGTH} /> : <LoginForm />}
|
||||
{fehler ? <Notice tone="warning">{fehler}</Notice> : null}
|
||||
{oidcFehler ? <Notice tone="warning">{oidcFehler}</Notice> : null}
|
||||
|
||||
{setup ? (
|
||||
{oidc ? (
|
||||
<a
|
||||
href="/api/auth/oidc/start"
|
||||
className="flex items-center justify-center gap-2 rounded-lg bg-indigo-600 px-4 py-3 font-medium text-white transition-colors hover:bg-indigo-700"
|
||||
>
|
||||
<Icon name="lock" className="size-4" />
|
||||
{oidc.label}
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
{oidc && zeigeFormular ? (
|
||||
<div className="flex items-center gap-3 text-xs text-slate-400 uppercase">
|
||||
<span className="h-px flex-1 bg-slate-200" />
|
||||
oder mit Passwort
|
||||
<span className="h-px flex-1 bg-slate-200" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{zeigeFormular ? (
|
||||
setup ? (
|
||||
<SetupForm minLength={MIN_PASSWORD_LENGTH} />
|
||||
) : (
|
||||
<LoginForm />
|
||||
)
|
||||
) : null}
|
||||
|
||||
{setup && zeigeFormular ? (
|
||||
<Notice tone="info">
|
||||
Der erste Benutzer wird Administrator der Installation und darf
|
||||
Gruppen, Benutzer und die QR-Typen verwalten.
|
||||
</Notice>
|
||||
) : oidc?.nurOidc ? (
|
||||
<p className="text-center text-sm text-slate-500">
|
||||
Die Anmeldung mit Passwort ist abgeschaltet (<code>OIDC_ONLY=true</code>).
|
||||
{setup
|
||||
? " Wer sich zuerst anmeldet, wird Administrator dieser Installation."
|
||||
: ""}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-center text-sm text-slate-500">
|
||||
Passwort vergessen? Ein Administrator kann es unter Verwaltung →
|
||||
|
||||
Reference in New Issue
Block a user