Files
proxdeploy/src/app/anmelden/page.tsx
T
florian.wildeandClaude Opus 5.5 6d6b0ff121 feat(auth): Anmeldung per OIDC mit Rollen aus Gruppen
Authorization Code mit PKCE, State und Nonce über openid-client 6.8.8; die Rolle kommt bei jeder Anmeldung aus dem Gruppen-Claim. Lokale Konten werden nie über den Namen übernommen, verknüpfen geht nur angemeldet. Getestet gegen einen Mock-IdP inklusive gefälschtem State, falscher Nonce und fremder Audience.

Closes #016

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:57:50 +02:00

72 lines
2.9 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 type { Metadata } from "next";
import { redirect } from "next/navigation";
import { Status } from "@/components/status";
import { Symbol, Wortmarke } from "@/components/wortmarke";
import { oidcEinstellungen } from "@/server/auth/oidc";
import { aktuellerBenutzer } from "@/server/auth/zugang";
import { Anmeldeformular } from "./anmeldeformular";
export const metadata: Metadata = { title: "Anmelden" };
/** Nur relative Ziele innerhalb der Anwendung – sonst wäre `?ziel=` eine offene Weiterleitung. */
function sicheresZiel(ziel: string | string[] | undefined): string {
return typeof ziel === "string" && ziel.startsWith("/") && !ziel.startsWith("//") ? ziel : "/";
}
const SSO_MELDUNGEN: Record<string, string> = {
abgelaufen: "Die SSO-Anmeldung hat zu lange gedauert. Bitte noch einmal.",
idp: "Der Identity Provider hat die Anmeldung nicht bestätigt.",
keine_gruppe: "Dein SSO-Konto ist für ProxDeploy nicht freigegeben – es fehlt die Gruppe.",
gesperrt: "Dein Konto ist gesperrt.",
name_vergeben: "Für dein SSO-Konto ließ sich kein Benutzername vergeben.",
aus: "Die Anmeldung per SSO ist nicht eingerichtet.",
};
export default async function Anmelden({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[]>>;
}) {
const parameter = await searchParams;
const ziel = sicheresZiel(parameter["ziel"]);
const sso = oidcEinstellungen();
const ssoFehler =
typeof parameter["sso"] === "string" ? (SSO_MELDUNGEN[parameter["sso"]] ?? SSO_MELDUNGEN["idp"]) : null;
if (await aktuellerBenutzer()) redirect(ziel);
return (
<main className="flex min-h-dvh items-center justify-center px-4 py-10">
<div className="w-full max-w-sm">
<div className="mb-8 flex flex-col items-center gap-3">
<Symbol className="h-14 w-14" />
<h1 className="text-2xl">
<Wortmarke blinkt />
</h1>
<p className="text-muted text-center text-sm">VMs und LXC-Container auf Proxmox VE ausrollen</p>
</div>
{ssoFehler ? (
<Status art="fehler" className="mb-4 text-sm">
{ssoFehler}
</Status>
) : null}
<Anmeldeformular ziel={ziel} />
{sso ? (
<div className="mt-6 flex flex-col gap-3">
<div className="text-muted flex items-center gap-3 text-xs" aria-hidden="true">
<span className="bg-linie h-px flex-1" />
oder
<span className="bg-linie h-px flex-1" />
</div>
<a
href={`/api/v1/auth/oidc/start?ziel=${encodeURIComponent(ziel)}`}
className="border-linie bg-panel-hoch text-text hover:border-muted inline-flex h-11 items-center justify-center rounded-lg border px-4 text-sm font-semibold"
>
Mit {sso.anzeigename} anmelden
</a>
</div>
) : null}
</div>
</main>
);
}