Files
harheimertc/server/api/auth/register-passkey-options.post.js
Torsten Schulz (local) 0deddeca51
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 49s
Enhance deployment script with PM2 process checks and error handling
Update deploy-production.sh to include checks for PM2 installation and process existence before restarting. Implement error messages for failed starts and restarts, improving robustness and user guidance during deployment. Additionally, add useful commands for managing the PM2 process post-deployment.
2026-01-08 11:24:38 +01:00

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