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,47 @@
|
||||
"use server";
|
||||
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookieOptionen, SITZUNGS_COOKIE, sitzungErzeugen } from "@/lib/sitzung";
|
||||
import { clientIp } from "@/lib/ratelimit";
|
||||
import { protokolliere } from "@/server/audit";
|
||||
import { AnmeldeFehler, mitPasswortAnmelden } from "@/server/auth/benutzer";
|
||||
import type { AktionsErgebnis } from "./kunden";
|
||||
|
||||
/** Anmeldung mit E-Mail und Passwort (Rückfallweg, wenn OIDC nicht erreichbar ist). */
|
||||
export async function anmelden(_vorher: AktionsErgebnis, formular: FormData): Promise<AktionsErgebnis> {
|
||||
const email = formular.get("email")?.toString().trim() ?? "";
|
||||
const passwort = formular.get("passwort")?.toString() ?? "";
|
||||
const ziel = formular.get("ziel")?.toString() || "/";
|
||||
|
||||
if (!email || !passwort) {
|
||||
return { fehler: "Bitte E-Mail-Adresse und Passwort eingeben." };
|
||||
}
|
||||
|
||||
try {
|
||||
const kopfzeilen = await headers();
|
||||
const benutzer = await mitPasswortAnmelden(email, passwort, clientIp(kopfzeilen));
|
||||
|
||||
const token = await sitzungErzeugen({
|
||||
benutzerId: benutzer.id,
|
||||
email: benutzer.email,
|
||||
name: benutzer.name,
|
||||
herkunft: "passwort",
|
||||
});
|
||||
|
||||
(await cookies()).set(SITZUNGS_COOKIE, token, cookieOptionen());
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof AnmeldeFehler) return { fehler: fehler.message };
|
||||
throw fehler;
|
||||
}
|
||||
|
||||
// Nur eigene Pfade als Ziel zulassen, damit die Anmeldung nicht auf fremde Seiten führt.
|
||||
redirect(ziel.startsWith("/") && !ziel.startsWith("//") ? ziel : "/");
|
||||
}
|
||||
|
||||
export async function abmelden() {
|
||||
const speicher = await cookies();
|
||||
speicher.delete(SITZUNGS_COOKIE);
|
||||
await protokolliere("abmeldung", "Benutzer", null, {});
|
||||
redirect("/login");
|
||||
}
|
||||
Reference in New Issue
Block a user