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>
72 lines
2.9 KiB
TypeScript
72 lines
2.9 KiB
TypeScript
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>
|
||
);
|
||
}
|