Files
harheimertc/server/api/auth/register-passkey-options.post.js
Torsten Schulz (local) 8fa8c8b5d9
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 55s
Enhance deployment script and registration components with improved error handling and debug logging
Update deploy-production.sh to include comprehensive build validation checks, ensuring critical files and directories are present before proceeding. Enhance the registrieren.vue component to conditionally display the window origin and improve debug logging for the registration process. Additionally, add debug information in the register-passkey-options API to capture request details, including environment and process ID, for better troubleshooting capabilities.
2026-01-08 08:59:57 +01:00

162 lines
5.3 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
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 }
})