import type { APIRoute } from "astro"; import { verifyRegistrationResponse } from "@simplewebauthn/server"; import { db } from "../../../../../db"; import { passkeys, passkeyChallenges } from "../../../../../db/schema"; import { eq, and, gt } from "drizzle-orm"; export const POST: APIRoute = async ({ request, locals }) => { const user = locals.user; if (!user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, }); } const body = await request.json(); const clientDataJSON = Buffer.from( body.response.clientDataJSON, "base64url", ).toString("utf-8"); const clientData = JSON.parse(clientDataJSON); const challenge = clientData.challenge; const dbChallenge = await db.query.passkeyChallenges.findFirst({ where: and( eq(passkeyChallenges.challenge, challenge), eq(passkeyChallenges.userId, user.id), gt(passkeyChallenges.expiresAt, new Date()), ), }); if (!dbChallenge) { return new Response( JSON.stringify({ error: "Invalid or expired challenge" }), { status: 400, }, ); } let verification; try { verification = await verifyRegistrationResponse({ response: body, expectedChallenge: challenge, expectedOrigin: new URL(request.url).origin, expectedRPID: new URL(request.url).hostname, }); } catch (error) { console.error("Passkey registration verification failed:", error); return new Response(JSON.stringify({ error: "Verification failed" }), { status: 400, }); } if (verification.verified && verification.registrationInfo) { const { registrationInfo } = verification; const { credential, credentialDeviceType, credentialBackedUp } = registrationInfo; await db.insert(passkeys).values({ id: credential.id, userId: user.id, publicKey: Buffer.from(credential.publicKey).toString("base64"), counter: credential.counter, deviceType: credentialDeviceType, backedUp: credentialBackedUp, transports: body.response.transports ? JSON.stringify(body.response.transports) : undefined, }); await db .delete(passkeyChallenges) .where(eq(passkeyChallenges.challenge, challenge)); return new Response(JSON.stringify({ verified: true })); } return new Response(JSON.stringify({ verified: false }), { status: 400 }); };