import crypto from 'crypto' import { generateRegistrationOptions } from '@simplewebauthn/server' import { readUsers } from '../../utils/auth.js' import { getWebAuthnConfig } from '../../utils/webauthn-config.js' import { setPreRegistration } from '../../utils/webauthn-challenges.js' import { writeAuditLog } from '../../utils/audit-log.js' function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || '')) } export default defineEventHandler(async (event) => { const body = await readBody(event) const name = String(body?.name || '').trim() const email = String(body?.email || '').trim().toLowerCase() const phone = String(body?.phone || '').trim() if (!name || !email) { throw createError({ statusCode: 400, message: 'Name und E-Mail sind erforderlich' }) } if (!isValidEmail(email)) { throw createError({ statusCode: 400, message: 'Ungültige E-Mail-Adresse' }) } const users = await readUsers() if (users.some(u => String(u.email || '').toLowerCase() === email)) { throw createError({ statusCode: 409, message: 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits' }) } const { rpId, rpName } = getWebAuthnConfig() const userId = crypto.randomUUID() const registrationId = crypto.randomBytes(16).toString('hex') const options = await generateRegistrationOptions({ rpName, rpID: rpId, userID: new TextEncoder().encode(String(userId)), userName: email, userDisplayName: name, attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' }, // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) timeout: 300000 }) setPreRegistration(registrationId, { challenge: options.challenge, userId, name, email, phone }) await writeAuditLog('auth.passkey.prereg.options', { email }) // 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 } } // Debug: Log Options-Struktur console.log('[WebAuthn Pre-Registration Options]', { hasChallenge: !!options.challenge, rpId: options.rp?.id, userId: options.user?.id ? 'present' : 'missing', timeout: options.timeout, challengeType: typeof options.challenge }) // Stelle sicher, dass die Options korrekt serialisiert werden // @simplewebauthn/server gibt ein Objekt zurück, das direkt JSON-serialisierbar ist // Aber wir müssen sicherstellen, dass alle Properties vorhanden sind const serializedOptions = { ...options, challenge: options.challenge, rp: options.rp, user: options.user, pubKeyCredParams: options.pubKeyCredParams, authenticatorSelection: options.authenticatorSelection, timeout: options.timeout || 300000 } return { success: true, registrationId, options: serializedOptions } })