import { verifyRegistrationResponse, type RegistrationResponseJSON } from '@simplewebauthn/server' import { requireAuth } from '../../../../utils/auth' import { assertBadRequest, httpError, mapDatabaseError } from '../../../../utils/http' import { createUserPasskey, listUserPasskeys } from '../../../../utils/user-repository' import { requireExistingUser } from '../../../../utils/users' import { buildPasskeyLabel, consumeRegistrationChallenge, getWebAuthnConfig } from '../../../../utils/webauthn' export default defineEventHandler(async (event) => { const auth = await requireAuth(event) const body = await readBody<{ response?: RegistrationResponseJSON }>(event) assertBadRequest(body.response, 'Passkey registration response is required') const expectedChallenge = await consumeRegistrationChallenge(auth.user.id) if (!expectedChallenge) { httpError(400, '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) { httpError(400, '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) { mapDatabaseError(error, { '23505': { statusCode: 409, statusMessage: 'This passkey is already registered' } }) } const updatedUser = await requireExistingUser(auth.user.id, 'Unable to load updated user') const passkeys = await listUserPasskeys(auth.user.id) return { ok: true, user: updatedUser, passkeys } })