feat(auth): Anmeldung mit Passwort und OIDC, Schutz aller Seiten (#013)
Signierte Sitzungs-Cookies mit jose, OIDC ueber openid-client (authentik), Schutz in proxy.ts, Anmeldeseite, Audit-Log-Ansicht, Sicherheits-Kopfzeilen und Rate-Limit. Kein Auth.js, weil Version 5 Beta ist. 147 Tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { cookieOptionen, SITZUNGS_COOKIE, sitzungErzeugen } from "@/lib/sitzung";
|
||||
import { mitOidcAnmelden } from "@/server/auth/benutzer";
|
||||
import { anmeldungAbschliessen, oidcEinstellungen } from "@/server/auth/oidc";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/** Nimmt die Rückkehr vom Anmeldedienst entgegen und legt die Sitzung an. */
|
||||
export async function GET(request: Request) {
|
||||
const einstellungen = oidcEinstellungen();
|
||||
const speicher = await cookies();
|
||||
const pruefwert = speicher.get("pl_oidc_pruefwert")?.value;
|
||||
const zustand = speicher.get("pl_oidc_zustand")?.value;
|
||||
const ziel = speicher.get("pl_oidc_ziel")?.value ?? "/";
|
||||
|
||||
for (const name of ["pl_oidc_pruefwert", "pl_oidc_zustand", "pl_oidc_ziel"]) {
|
||||
speicher.delete(name);
|
||||
}
|
||||
|
||||
if (!einstellungen || !pruefwert || !zustand) {
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
}
|
||||
|
||||
try {
|
||||
const angaben = await anmeldungAbschliessen(einstellungen, new URL(request.url), pruefwert, zustand);
|
||||
const benutzer = await mitOidcAnmelden(angaben.subject, angaben.email, angaben.name);
|
||||
|
||||
const token = await sitzungErzeugen({
|
||||
benutzerId: benutzer.id,
|
||||
email: benutzer.email,
|
||||
name: benutzer.name,
|
||||
herkunft: "oidc",
|
||||
});
|
||||
speicher.set(SITZUNGS_COOKIE, token, cookieOptionen());
|
||||
|
||||
return Response.redirect(new URL(ziel.startsWith("/") ? ziel : "/", request.url), 302);
|
||||
} catch (fehler) {
|
||||
console.error("OIDC-Rückkehr fehlgeschlagen:", fehler);
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { anmeldungStarten, oidcEinstellungen } from "@/server/auth/oidc";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/** Schickt den Browser zum Anmeldedienst und merkt sich PKCE-Prüfwert und Zustand im Cookie. */
|
||||
export async function GET(request: Request) {
|
||||
const einstellungen = oidcEinstellungen();
|
||||
if (!einstellungen) {
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
}
|
||||
|
||||
const ziel = new URL(request.url).searchParams.get("ziel") ?? "/";
|
||||
|
||||
try {
|
||||
const start = await anmeldungStarten(einstellungen);
|
||||
const speicher = await cookies();
|
||||
const optionen = {
|
||||
httpOnly: true,
|
||||
sameSite: "lax" as const,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
path: "/",
|
||||
maxAge: 600,
|
||||
};
|
||||
|
||||
speicher.set("pl_oidc_pruefwert", start.pruefwert, optionen);
|
||||
speicher.set("pl_oidc_zustand", start.zustand, optionen);
|
||||
speicher.set("pl_oidc_ziel", ziel.startsWith("/") ? ziel : "/", optionen);
|
||||
|
||||
return Response.redirect(start.url, 302);
|
||||
} catch (fehler) {
|
||||
console.error("OIDC-Start fehlgeschlagen:", fehler);
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user