Files
harheimertc/server/api/auth/register-passkey-options.post.js
Torsten Schulz (local) bb985ddc8f
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 44s
Improve passkey registration error handling and options serialization
Enhance the registration process by adding error handling for the WebAuthn startRegistration method and ensuring the presence of required options. Include debug logging for received options and serialize the options correctly before returning them in the API response, improving robustness and clarity in the registration flow.
2026-01-07 21:30:13 +01:00

98 lines
3.2 KiB
JavaScript

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 }
})