import { verifyRegistrationResponse } from '@simplewebauthn/server' import crypto from 'crypto' import nodemailer from 'nodemailer' import { hashPassword, readUsers, writeUsers } from '../../utils/auth.js' import { getWebAuthnConfig } from '../../utils/webauthn-config.js' import { consumePreRegistration } from '../../utils/webauthn-challenges.js' import { toBase64Url } from '../../utils/webauthn-encoding.js' import { writeAuditLog } from '../../utils/audit-log.js' import { assertPasswordNotPwned } from '../../utils/hibp.js' export default defineEventHandler(async (event) => { const body = await readBody(event) const registrationId = String(body?.registrationId || '') const response = body?.credential const password = body?.password ? String(body.password) : '' if (!registrationId || !response) { throw createError({ statusCode: 400, statusMessage: 'Ungültige Anfrage' }) } const pre = consumePreRegistration(registrationId) if (!pre) { throw createError({ statusCode: 400, statusMessage: 'Registrierungs-Session abgelaufen. Bitte erneut versuchen.' }) } const { challenge, userId, name, email, phone } = pre const users = await readUsers() if (users.some(u => String(u.email || '').toLowerCase() === String(email).toLowerCase())) { throw createError({ statusCode: 409, message: 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits' }) } const { origin, rpId, requireUV } = getWebAuthnConfig() const verification = await verifyRegistrationResponse({ response, expectedChallenge: challenge, expectedOrigin: origin, expectedRPID: rpId, requireUserVerification: requireUV }) const { verified, registrationInfo } = verification if (!verified || !registrationInfo) { await writeAuditLog('auth.passkey.prereg.failed', { email }) throw createError({ statusCode: 400, statusMessage: 'Passkey-Registrierung fehlgeschlagen' }) } const { credentialID, credentialPublicKey, counter, credentialDeviceType, credentialBackedUp } = registrationInfo const credentialId = toBase64Url(credentialID) const publicKey = toBase64Url(credentialPublicKey) // Optional: Passwort als Fallback (z.B. Firefox/Linux) erlauben let hashedPassword if (password && password.trim().length > 0) { if (password.length < 8) { throw createError({ statusCode: 400, message: 'Das Passwort muss mindestens 8 Zeichen lang sein' }) } await assertPasswordNotPwned(password) hashedPassword = await hashPassword(password) } else { // Kein Passwort gesetzt: random Hash, damit bestehende Code-Pfade (verifyPassword) konsistent bleiben. hashedPassword = await hashPassword(crypto.randomBytes(32).toString('hex')) } const newUser = { id: String(userId), email: String(email).toLowerCase(), password: hashedPassword, name, phone: phone || '', role: 'mitglied', active: false, created: new Date().toISOString(), lastLogin: null, passkeys: [ { 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: 'Passkey' } ] } users.push(newUser) await writeUsers(users) await writeAuditLog('auth.passkey.prereg.success', { email, userId: newUser.id }) // Send notification emails (same behavior as password registration) try { const smtpUser = process.env.SMTP_USER const smtpPass = process.env.SMTP_PASS if (smtpUser && smtpPass) { const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST || 'smtp.gmail.com', port: process.env.SMTP_PORT || 587, secure: false, auth: { user: smtpUser, pass: smtpPass } }) await transporter.sendMail({ from: process.env.SMTP_FROM || 'noreply@harheimertc.de', to: process.env.SMTP_ADMIN || 'j.dichmann@gmx.de', subject: 'Neue Registrierung (Passkey) - Harheimer TC', html: `

Neue Registrierung (Passkey)

Ein neuer Benutzer hat sich registriert und wartet auf Freigabe:

Bitte prüfen Sie die Registrierung im CMS.

` }) await transporter.sendMail({ from: process.env.SMTP_FROM || 'noreply@harheimertc.de', to: email, subject: 'Registrierung erhalten - Harheimer TC', html: `

Registrierung erhalten

Hallo ${name},

vielen Dank für Ihre Registrierung beim Harheimer TC!

Ihre Anfrage wird vom Vorstand geprüft. Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.


Mit sportlichen Grüßen,
Ihr Harheimer TC

` }) } } catch (emailError) { console.error('E-Mail-Versand fehlgeschlagen:', emailError) } return { success: true, message: 'Registrierung erfolgreich. Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.' } })