import { NextResponse } from "next/server"; import { verifyAuthenticationResponse } from "@simplewebauthn/server"; import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, } from "@simplewebauthn/server"; import { query, queryOne } from "@/lib/db"; import { createSession } from "@/lib/auth/session"; import { origin, rpID } from "@/lib/auth/webauthn-config"; import { consumeChallenge } from "@/lib/auth/webauthn-challenges"; import { isSameOrigin } from "@/lib/origin"; import type { Role } from "@/lib/types"; export async function POST(request: Request) { if (!isSameOrigin(request)) { return NextResponse.json({ error: "Nicht erlaubt." }, { status: 403 }); } const body = (await request.json()) as { response: AuthenticationResponseJSON }; const challenge = extractChallengeFromClientData(body.response); const pending = await consumeChallenge(challenge, "authentication"); if (!pending) { return NextResponse.json({ error: "challenge_expired" }, { status: 400 }); } // Passkey-Login ist unabhängig von password_auth_enabled – dieses Flag // betrifft ausschließlich den Passwort-Login. const stored = await queryOne<{ user_id: string; public_key: Buffer; counter: string; transports: AuthenticatorTransportFuture[]; role: Role; }>( `select pc.user_id, pc.public_key, pc.counter, pc.transports, u.role from passkey_credentials pc join users u on u.id = pc.user_id where pc.credential_id = $1`, [body.response.id] ); if (!stored) { return NextResponse.json({ error: "unknown_credential" }, { status: 400 }); } const verification = await verifyAuthenticationResponse({ response: body.response, expectedChallenge: challenge, expectedOrigin: origin(), expectedRPID: rpID(), credential: { id: body.response.id, publicKey: new Uint8Array(stored.public_key), counter: Number(stored.counter), transports: stored.transports, }, }); if (!verification.verified) { return NextResponse.json({ error: "verification_failed" }, { status: 400 }); } await query( "update passkey_credentials set counter = $1, last_used_at = now() where credential_id = $2", [verification.authenticationInfo.newCounter, body.response.id] ); await createSession(stored.user_id); return NextResponse.json({ role: stored.role }); } function extractChallengeFromClientData(response: AuthenticationResponseJSON): string { const json = JSON.parse( Buffer.from(response.response.clientDataJSON, "base64url").toString("utf8") ) as { challenge: string }; return json.challenge; }