import { verifyRegistrationResponse, type RegistrationResponseJSON } from '@simplewebauthn/server' import { requireAuth } from '../../../../utils/auth' import { createUserPasskey, getUserById, listUserPasskeys } from '../../../../utils/user-repository' import { buildPasskeyLabel, consumeRegistrationChallenge, getWebAuthnConfig } from '../../../../utils/webauthn' export default defineEventHandler(async (event) => { const auth = await requireAuth(event) const body = await readBody<{ response?: RegistrationResponseJSON }>(event) if (!body.response) { throw createError({ statusCode: 400, statusMessage: 'Passkey registration response is required' }) } const expectedChallenge = await consumeRegistrationChallenge(auth.user.id) if (!expectedChallenge) { throw createError({ statusCode: 400, statusMessage: 'Passkey registration challenge expired. Try again.' }) } const config = getWebAuthnConfig(event) const verification = await verifyRegistrationResponse({ response: body.response, expectedChallenge, expectedOrigin: config.origin, expectedRPID: config.rpID }) if (!verification.verified || !verification.registrationInfo) { throw createError({ statusCode: 400, statusMessage: 'Passkey registration could not be verified' }) } try { await createUserPasskey({ userId: auth.user.id, credentialId: verification.registrationInfo.credential.id, publicKey: verification.registrationInfo.credential.publicKey, counter: verification.registrationInfo.credential.counter, deviceType: verification.registrationInfo.credentialDeviceType, backedUp: verification.registrationInfo.credentialBackedUp, transports: body.response.response.transports || [], label: buildPasskeyLabel() }) } catch (error: any) { if (error?.code === '23505') { throw createError({ statusCode: 409, statusMessage: 'This passkey is already registered' }) } throw error } const updatedUser = await getUserById(auth.user.id) const passkeys = await listUserPasskeys(auth.user.id) return { ok: true, user: updatedUser, passkeys } })