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