import { generateRegistrationOptions } from '@simplewebauthn/server' import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js' import { getWebAuthnConfig } from '../../../utils/webauthn-config.js' import { setRegistrationChallenge } from '../../../utils/webauthn-challenges.js' import { writeAuditLog } from '../../../utils/audit-log.js' export default defineEventHandler(async (event) => { // CORS-Header für Cross-Device Authentication const origin = getHeader(event, 'origin') if (origin) { setHeader(event, 'Access-Control-Allow-Origin', origin) 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' }) } // Mindestens für Admin/Vorstand anbieten (und auch für Mitglieder ok) if (!hasAnyRole(user, 'admin', 'vorstand', 'mitglied', 'newsletter')) { throw createError({ statusCode: 403, statusMessage: 'Keine Berechtigung' }) } const { rpId, rpName, origin: webauthnOrigin } = getWebAuthnConfig() // Debug: Log für Cross-Device Troubleshooting const requestOrigin = getHeader(event, 'origin') console.log('[WebAuthn Registration]', { rpId, webauthnOrigin, requestOrigin, userAgent: getHeader(event, 'user-agent') }) const existing = Array.isArray(user.passkeys) ? user.passkeys : [] const excludeCredentials = existing .filter(pk => pk && pk.credentialId) .map(pk => ({ id: pk.credentialId, type: 'public-key', transports: pk.transports || undefined })) const options = await generateRegistrationOptions({ rpName, rpID: rpId, // @simplewebauthn/server erwartet inzwischen Uint8Array/Buffer statt String userID: new TextEncoder().encode(String(user.id)), userName: user.email, // Keine Attestation-Daten speichern attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' // authenticatorAttachment weglassen = beide Typen erlauben (platform + cross-platform) }, excludeCredentials, // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) timeout: 300000 }) setRegistrationChallenge(user.id, options.challenge) await writeAuditLog('auth.passkey.registration.options', { userId: user.id }) return { success: true, options } })