From a763c959efb39440393f7fe5b2a3dc5b5958c578 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 8 Jan 2026 12:12:15 +0100 Subject: [PATCH] Enhance debug logging and validation for Passkey Registration Add comprehensive debug statements in the registrieren.vue component to validate the options structure and ensure the challenge format is correct before initiating registration. Update the register-passkey API to log additional request details, including client IP and user-agent analysis, to improve troubleshooting and provide better insights during the registration process. --- pages/registrieren.vue | 20 ++++++++++++++++++++ server/api/auth/register-passkey.post.js | 14 ++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/pages/registrieren.vue b/pages/registrieren.vue index a9f7e24..ed8a7b9 100644 --- a/pages/registrieren.vue +++ b/pages/registrieren.vue @@ -537,7 +537,27 @@ const handleRegisterWithPasskey = async () => { console.log('[DEBUG] 4. Smartphone sendet Credential-Response an:', window.location.origin + '/api/auth/register-passkey') console.log('[DEBUG] 5. Server verifiziert die Response') + // Prüfe Options-Struktur vor dem Aufruf + console.log('[DEBUG] Options validation before startRegistration:', { + hasChallenge: !!pre.options.challenge, + challengeType: typeof pre.options.challenge, + hasRp: !!pre.options.rp, + hasRpId: !!pre.options.rp?.id, + hasUser: !!pre.options.user, + hasUserID: !!pre.options.user?.id, + timeout: pre.options.timeout, + timeoutType: typeof pre.options.timeout, + allKeys: Object.keys(pre.options) + }) + + // Stelle sicher, dass challenge ein String ist (nicht Base64URL-encoded) + if (typeof pre.options.challenge !== 'string') { + console.error('[DEBUG] ERROR: Challenge is not a string!', pre.options.challenge) + throw new Error('Invalid challenge format') + } + // Direkt die Options übergeben (wie in profil.vue und passkey-wiederherstellen.vue) + // @simplewebauthn/browser v13+ erwartet die Options direkt credential = await mod.startRegistration(pre.options) clearTimeout(timeoutWarning) diff --git a/server/api/auth/register-passkey.post.js b/server/api/auth/register-passkey.post.js index b8d6c38..5212230 100644 --- a/server/api/auth/register-passkey.post.js +++ b/server/api/auth/register-passkey.post.js @@ -7,6 +7,7 @@ import { consumePreRegistration } from '../../utils/webauthn-challenges.js' import { toBase64Url } from '../../utils/webauthn-encoding.js' import { writeAuditLog } from '../../utils/audit-log.js' import { assertPasswordNotPwned } from '../../utils/hibp.js' +import { getClientIp } from '../../utils/rate-limit.js' export default defineEventHandler(async (event) => { const requestStart = Date.now() @@ -14,14 +15,23 @@ export default defineEventHandler(async (event) => { const userAgent = getHeader(event, 'user-agent') const { origin: webauthnOrigin } = getWebAuthnConfig() - console.log('[DEBUG] register-passkey request received', { + console.log('[DEBUG] ===== register-passkey request received =====') + console.log('[DEBUG] Request Details:', { origin: requestOrigin, webauthnOrigin, - userAgent: userAgent?.substring(0, 100), + userAgent: userAgent?.substring(0, 150), timestamp: new Date().toISOString(), method: getMethod(event), + ip: getClientIp(event), note: 'Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde' }) + console.log('[DEBUG] User-Agent Analysis:', { + isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent || ''), + isChrome: /Chrome/i.test(userAgent || ''), + isSafari: /Safari/i.test(userAgent || '') && !/Chrome/i.test(userAgent || ''), + isFirefox: /Firefox/i.test(userAgent || ''), + fullUserAgent: userAgent + }) // CORS-Header für Cross-Device Authentication // OPTIONS-Requests werden von .options.js behandelt