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>
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import { aufruferAdresse, istHttps, oeffentlicheAdresse } from "@/lib/adressen";
|
||
import { logger } from "@/lib/logger";
|
||
import { SITZUNG_COOKIE, SITZUNG_LAUFZEIT_STUNDEN, sitzungErzeugen } from "@/lib/sitzung";
|
||
import {
|
||
OIDC_COOKIE,
|
||
oidcAbschliessen,
|
||
oidcEinstellungen,
|
||
OidcFehler,
|
||
RUECKRUF_PFAD,
|
||
} from "@/server/auth/oidc";
|
||
|
||
function cookieLesen(kopf: string | null, name: string): string | undefined {
|
||
for (const teil of (kopf ?? "").split(";")) {
|
||
const [schluessel, ...wert] = teil.trim().split("=");
|
||
if (schluessel === name) return decodeURIComponent(wert.join("="));
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/**
|
||
* Öffentlich: Rückkehr vom Identity Provider. Erfolg → Sitzungscookie und Weiterleitung zum Ziel;
|
||
* Fehler → Anmeldeseite mit Code (`?sso=keine_gruppe` …), nie eine Fehlerseite mit Interna.
|
||
*/
|
||
export async function GET(anfrage: Request): Promise<Response> {
|
||
const herkunft = new URL(anfrage.url).origin;
|
||
const e = oidcEinstellungen();
|
||
const zurAnmeldung = (code: string) =>
|
||
NextResponse.redirect(oeffentlicheAdresse(`/anmelden?sso=${code}`, herkunft), 302);
|
||
if (!e) return zurAnmeldung("aus");
|
||
|
||
// Die öffentliche Adresse: genau die, die als redirect_uri beim IdP registriert ist.
|
||
const aktuell = oeffentlicheAdresse(RUECKRUF_PFAD + new URL(anfrage.url).search, herkunft);
|
||
let antwort: NextResponse;
|
||
try {
|
||
const { benutzer, ziel } = await oidcAbschliessen(
|
||
e,
|
||
aktuell,
|
||
cookieLesen(anfrage.headers.get("cookie"), OIDC_COOKIE),
|
||
aufruferAdresse(anfrage.headers),
|
||
);
|
||
antwort = NextResponse.redirect(oeffentlicheAdresse(ziel, herkunft), 302);
|
||
antwort.cookies.set(
|
||
SITZUNG_COOKIE,
|
||
await sitzungErzeugen({ benutzerId: benutzer.id, herkunft: "oidc" }),
|
||
{
|
||
httpOnly: true,
|
||
sameSite: "lax",
|
||
secure: istHttps(anfrage.headers, anfrage.url),
|
||
path: "/",
|
||
maxAge: SITZUNG_LAUFZEIT_STUNDEN * 3600,
|
||
},
|
||
);
|
||
} catch (fehler) {
|
||
if (!(fehler instanceof OidcFehler)) logger.error({ err: fehler }, "OIDC-Rückruf fehlgeschlagen");
|
||
const code = fehler instanceof OidcFehler ? fehler.code : "idp";
|
||
// Beim Verknüpfen ist man schon angemeldet – die Meldung gehört dann in die Einstellungen.
|
||
antwort =
|
||
code === "schon_verknuepft"
|
||
? NextResponse.redirect(oeffentlicheAdresse(`/einstellungen?sso=${code}`, herkunft), 302)
|
||
: zurAnmeldung(code);
|
||
}
|
||
antwort.cookies.set(OIDC_COOKIE, "", { path: "/api/v1/auth/oidc", maxAge: 0 });
|
||
return antwort;
|
||
}
|