import { verifyRegistrationResponse } from '@simplewebauthn/server' import { getUserFromToken, readUsers, writeUsers } from '../../../utils/auth.js' import { getWebAuthnConfig } from '../../../utils/webauthn-config.js' import { clearRegistrationChallenge, getRegistrationChallenge } from '../../../utils/webauthn-challenges.js' import { toBase64Url } from '../../../utils/webauthn-encoding.js' import { writeAuditLog } from '../../../utils/audit-log.js' export default defineEventHandler(async (event) => { // CORS-Header für Cross-Device Authentication const requestOrigin = getHeader(event, 'origin') if (requestOrigin) { setHeader(event, 'Access-Control-Allow-Origin', requestOrigin) setHeader(event, 'Access-Control-Allow-Credentials', 'true') setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS') setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization') } if (getMethod(event) === 'OPTIONS') { return { success: true } } const token = getCookie(event, 'auth_token') const user = token ? await getUserFromToken(token) : null if (!user) { throw createError({ statusCode: 401, statusMessage: 'Nicht authentifiziert' }) } const body = await readBody(event) const response = body?.credential if (!response) { throw createError({ statusCode: 400, statusMessage: 'Credential fehlt' }) } const expectedChallenge = getRegistrationChallenge(user.id) if (!expectedChallenge) { throw createError({ statusCode: 400, statusMessage: 'Registrierungs-Session abgelaufen. Bitte erneut versuchen.' }) } const { origin, rpId, requireUV } = getWebAuthnConfig() let verification try { verification = await verifyRegistrationResponse({ response, expectedChallenge, expectedOrigin: origin, expectedRPID: rpId, requireUserVerification: requireUV }) } finally { clearRegistrationChallenge(user.id) } const { verified, registrationInfo } = verification if (!verified || !registrationInfo) { await writeAuditLog('auth.passkey.registration.failed', { userId: user.id }) throw createError({ statusCode: 400, statusMessage: 'Passkey-Registrierung fehlgeschlagen' }) } const { credentialID, credentialPublicKey, counter, credentialDeviceType, credentialBackedUp } = registrationInfo const credentialId = toBase64Url(credentialID) const publicKey = toBase64Url(credentialPublicKey) const users = await readUsers() const idx = users.findIndex(u => u.id === user.id) if (idx === -1) { throw createError({ statusCode: 404, statusMessage: 'Benutzer nicht gefunden' }) } const u = users[idx] if (!Array.isArray(u.passkeys)) u.passkeys = [] // Duplikate verhindern if (u.passkeys.some(pk => pk.credentialId === credentialId)) { return { success: true, message: 'Passkey ist bereits registriert.' } } u.passkeys.push({ id: `${Date.now()}`, credentialId, publicKey, counter: Number(counter) || 0, transports: Array.isArray(response.transports) ? response.transports : undefined, deviceType: credentialDeviceType, backedUp: !!credentialBackedUp, createdAt: new Date().toISOString(), lastUsedAt: null, name: body?.name ? String(body.name).slice(0, 80) : 'Passkey' }) users[idx] = u await writeUsers(users) await writeAuditLog('auth.passkey.registered', { userId: user.id }) return { success: true, message: 'Passkey hinzugefügt.' } })