- /setup fuehrt beim ersten Aufruf durch das Anlegen des Administratorkontos und die Wahl der Betriebsart. Der Endpunkt funktioniert nur, solange es keinen Benutzer gibt; danach kann sich niemand mehr nachtraeglich ein Adminkonto erschleichen. Vorhandene Projekte ohne Besitzer werden dabei uebernommen und im Formular vorher angekuendigt. - /login mit Passwort oder Passkey. Der Passkey-Knopf entfaellt, wenn APP_URL keinen sicheren Kontext ergibt - mit Hinweis, woran es liegt. Das Ziel nach der Anmeldung wird auf relative Pfade begrenzt, sonst waere der weiter-Parameter ein offener Redirect. - /register nur im Mehrbenutzerbetrieb und nur, wenn ein Administrator die Selbstregistrierung freigegeben hat. - Kopfzeile mit Benutzermenue: eigenes Konto, Admin-Einstellungen, Abmelden. Ohne Anmeldung bleibt sie auf das Logo reduziert. - Layout liest Titel und Namen ueber generateMetadata, damit APP_NAME zur Laufzeit greift statt beim Build eingefroren zu werden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { requireUserApi } from "@/lib/auth/guard";
|
|
import { WebAuthnUnavailableError, buildRegistrationOptions } from "@/lib/auth/webauthn";
|
|
import { LIMITS, rateLimit } from "@/lib/security/rateLimit";
|
|
import { clientIp, jsonError, tooManyRequests } from "@/lib/security/request";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/** Liefert die Optionen fuer navigator.credentials.create(). */
|
|
export async function POST(request: Request) {
|
|
const auth = await requireUserApi();
|
|
if (!auth.ok) return auth.response;
|
|
|
|
const limited = rateLimit(
|
|
`webauthn:reg:${clientIp(request)}`,
|
|
LIMITS.webauthn.limit,
|
|
LIMITS.webauthn.windowMs,
|
|
);
|
|
if (!limited.ok) return tooManyRequests(limited.retryAfter);
|
|
|
|
try {
|
|
const options = await buildRegistrationOptions({
|
|
id: auth.user.id,
|
|
username: auth.user.username,
|
|
displayName: auth.user.displayName,
|
|
});
|
|
return NextResponse.json({ options });
|
|
} catch (error) {
|
|
if (error instanceof WebAuthnUnavailableError) return jsonError(error.message, 501);
|
|
return jsonError(error instanceof Error ? error.message : String(error), 500);
|
|
}
|
|
}
|