import { getSessionUser } from '$lib/server/auth/session'; import { consumeChallenge } from '$lib/server/auth/passkeys'; import { getDatabase } from '$lib/server/database'; import { verifyRegistrationResponse, type RegistrationResponseJSON, } from '@simplewebauthn/server'; import { error, json } from '@sveltejs/kit'; import { Buffer } from 'node:buffer'; import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ cookies, request }) => { const user = await getSessionUser(cookies); if (!user) error(401, 'Sign in before registering a passkey.'); const body = (await request.json()) as { challengeId?: string; name?: string; response?: RegistrationResponseJSON; }; if (!body.challengeId || !body.response) error(400, 'Incomplete passkey registration.'); const database = await getDatabase(); const challenge = await consumeChallenge( database, body.challengeId, 'registration', user.id, ); if (!challenge) error(400, 'The passkey registration expired. Try again.'); const verification = await verifyRegistrationResponse({ response: body.response, expectedChallenge: String(challenge.challenge), expectedOrigin: String(challenge.origin), expectedRPID: String(challenge.rp_id), requireUserVerification: true, }); if (!verification.verified || !verification.registrationInfo) error(400, 'Passkey registration could not be verified.'); const { credential, credentialDeviceType, credentialBackedUp } = verification.registrationInfo; const name = String(body.name || 'My passkey') .trim() .slice(0, 60) || 'My passkey'; await ( await database.prepare(`INSERT INTO passkeys(id,user_id,webauthn_user_id,public_key,counter,device_type,backed_up,transports,name,created_at) VALUES(?,?,?,?,?,?,?,?,?,?)`) ).run( credential.id, user.id, Buffer.from(user.id).toString('base64url'), credential.publicKey, credential.counter, credentialDeviceType, credentialBackedUp ? 1 : 0, credential.transports ? JSON.stringify(credential.transports) : null, name, new Date().toISOString(), ); return json({ verified: true }); };