Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 48s
Add comprehensive debug logging throughout the passkey registration flow, including request handling, option generation, and verification steps. Implement validation for incoming requests and responses to ensure required fields are present, improving error handling and clarity. This update aims to facilitate troubleshooting and enhance the overall robustness of the registration process.
150 lines
4.8 KiB
JavaScript
150 lines
4.8 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')
|
|
|
|
console.log('[DEBUG] register-passkey-options request received', {
|
|
origin: requestOrigin,
|
|
userAgent: userAgent?.substring(0, 100),
|
|
method: getMethod(event),
|
|
timestamp: new Date().toISOString()
|
|
})
|
|
|
|
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
|
|
})
|
|
|
|
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
|
|
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')
|
|
console.log('[DEBUG] CORS headers set', { origin: requestOrigin })
|
|
}
|
|
|
|
if (getMethod(event) === 'OPTIONS') {
|
|
console.log('[DEBUG] OPTIONS request, returning early')
|
|
return { success: true }
|
|
}
|
|
|
|
// Stelle sicher, dass die Options korrekt serialisiert werden
|
|
const serializedOptions = {
|
|
...options,
|
|
challenge: options.challenge,
|
|
rp: options.rp,
|
|
user: options.user,
|
|
pubKeyCredParams: options.pubKeyCredParams,
|
|
authenticatorSelection: options.authenticatorSelection,
|
|
timeout: options.timeout || 300000
|
|
}
|
|
|
|
const totalDuration = Date.now() - requestStart
|
|
console.log(`[DEBUG] Returning options (total: ${totalDuration}ms)`, {
|
|
registrationId,
|
|
optionsKeys: Object.keys(serializedOptions),
|
|
serializedChallengeLength: serializedOptions.challenge?.length
|
|
})
|
|
|
|
return { success: true, registrationId, options: serializedOptions }
|
|
})
|
|
|