Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 44s
Add detailed debug statements in the registrieren.vue component to validate the complete options structure during Passkey registration. Update the register-passkey API to simplify the options return process, eliminating unnecessary serialization while maintaining comprehensive logging of the options details for improved troubleshooting.
163 lines
5.6 KiB
JavaScript
163 lines
5.6 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 requestStart = Date.now()
|
|
const requestOrigin = getHeader(event, 'origin')
|
|
const userAgent = getHeader(event, 'user-agent')
|
|
const nodeEnv = process.env.NODE_ENV || 'development'
|
|
|
|
// Debug-Ausgaben immer ausgeben (nicht nur in dev)
|
|
console.log('[DEBUG] register-passkey-options request received', {
|
|
origin: requestOrigin,
|
|
userAgent: userAgent?.substring(0, 100),
|
|
method: getMethod(event),
|
|
timestamp: new Date().toISOString(),
|
|
nodeEnv: nodeEnv,
|
|
pid: process.pid
|
|
})
|
|
|
|
const body = await readBody(event)
|
|
const name = String(body?.name || '').trim()
|
|
const email = String(body?.email || '').trim().toLowerCase()
|
|
const phone = String(body?.phone || '').trim()
|
|
|
|
console.log('[DEBUG] Request body parsed', {
|
|
hasName: !!name,
|
|
hasEmail: !!email,
|
|
hasPhone: !!phone,
|
|
email: email.substring(0, 10) + '...'
|
|
})
|
|
|
|
if (!name || !email) {
|
|
console.error('[DEBUG] Validation failed: missing name or email')
|
|
throw createError({ statusCode: 400, message: 'Name und E-Mail sind erforderlich' })
|
|
}
|
|
if (!isValidEmail(email)) {
|
|
console.error('[DEBUG] Validation failed: invalid email format')
|
|
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, origin: webauthnOrigin } = getWebAuthnConfig()
|
|
|
|
console.log('[DEBUG] WebAuthn config', {
|
|
rpId,
|
|
rpName,
|
|
webauthnOrigin,
|
|
requestOrigin,
|
|
webauthnOriginEnv: process.env.WEBAUTHN_ORIGIN,
|
|
baseUrlEnv: process.env.NUXT_PUBLIC_BASE_URL
|
|
})
|
|
|
|
// WICHTIG: Sicherstellen, dass die Origin KEINEN Port hat
|
|
if (webauthnOrigin.includes(':3100')) {
|
|
console.error('[DEBUG] ERROR: webauthnOrigin contains port 3100! This will cause verification to fail.')
|
|
console.error('[DEBUG] Fix: Set WEBAUTHN_ORIGIN=https://harheimertc.tsschulz.de (without port) in .env')
|
|
}
|
|
|
|
const userId = crypto.randomUUID()
|
|
const registrationId = crypto.randomBytes(16).toString('hex')
|
|
|
|
console.log('[DEBUG] Generated IDs', {
|
|
userId,
|
|
registrationId
|
|
})
|
|
|
|
console.log('[DEBUG] Generating registration options...')
|
|
const optionsStart = Date.now()
|
|
|
|
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
|
|
})
|
|
|
|
const optionsDuration = Date.now() - optionsStart
|
|
console.log(`[DEBUG] Registration options generated (${optionsDuration}ms)`, {
|
|
hasChallenge: !!options.challenge,
|
|
challengeLength: options.challenge?.length,
|
|
rpId: options.rp?.id,
|
|
rpName: options.rp?.name,
|
|
userId: options.user?.id ? 'present' : 'missing',
|
|
userName: options.user?.name,
|
|
userDisplayName: options.user?.displayName,
|
|
timeout: options.timeout,
|
|
pubKeyCredParamsCount: options.pubKeyCredParams?.length,
|
|
authenticatorSelection: options.authenticatorSelection,
|
|
excludeCredentialsCount: options.excludeCredentials?.length || 0
|
|
})
|
|
|
|
setPreRegistration(registrationId, {
|
|
challenge: options.challenge,
|
|
userId,
|
|
name,
|
|
email,
|
|
phone
|
|
})
|
|
|
|
console.log('[DEBUG] Pre-registration stored', {
|
|
registrationId,
|
|
challengeStored: !!options.challenge
|
|
})
|
|
|
|
await writeAuditLog('auth.passkey.prereg.options', { email })
|
|
|
|
// CORS-Header für Cross-Device Authentication
|
|
// WICHTIG: Für Cross-Device muss CORS korrekt konfiguriert sein
|
|
// OPTIONS-Requests werden von .options.js behandelt
|
|
const allowedOrigin = requestOrigin || webauthnOrigin
|
|
|
|
if (allowedOrigin) {
|
|
setHeader(event, 'Access-Control-Allow-Origin', allowedOrigin)
|
|
setHeader(event, 'Access-Control-Allow-Credentials', 'true')
|
|
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
|
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Origin, X-Requested-With')
|
|
setHeader(event, 'Access-Control-Max-Age', '86400') // 24 Stunden Cache für Preflight
|
|
console.log('[DEBUG] CORS headers set for POST', {
|
|
origin: allowedOrigin,
|
|
requestOrigin,
|
|
webauthnOrigin
|
|
})
|
|
}
|
|
|
|
// Options direkt zurückgeben (wie in passkeys/registration-options.post.js)
|
|
// @simplewebauthn/server gibt bereits korrekt formatierte Options zurück
|
|
const totalDuration = Date.now() - requestStart
|
|
console.log(`[DEBUG] Returning options (total: ${totalDuration}ms)`, {
|
|
registrationId,
|
|
optionsKeys: Object.keys(options),
|
|
challengeLength: options.challenge?.length,
|
|
challengeType: typeof options.challenge,
|
|
rpId: options.rp?.id,
|
|
userIdType: typeof options.user?.id,
|
|
timeout: options.timeout
|
|
})
|
|
|
|
// WICHTIG: Options direkt zurückgeben, keine manuelle Serialisierung
|
|
// Die Options von @simplewebauthn/server sind bereits korrekt formatiert
|
|
return { success: true, registrationId, options }
|
|
})
|
|
|