diff --git a/pages/mitgliederbereich/profil.vue b/pages/mitgliederbereich/profil.vue index 8d17e16..e834b0b 100644 --- a/pages/mitgliederbereich/profil.vue +++ b/pages/mitgliederbereich/profil.vue @@ -319,7 +319,8 @@ const addPasskey = async () => { const name = window.prompt('Name für den Passkey (z.B. "iPhone", "Laptop"):', 'Passkey') || 'Passkey' const res = await $fetch('/api/auth/passkeys/registration-options', { method: 'POST' }) const mod = await import('@simplewebauthn/browser') - const credential = await mod.startRegistration(res.options) + // @simplewebauthn/browser v13+ erwartet { optionsJSON: options } + const credential = await mod.startRegistration({ optionsJSON: res.options }) await $fetch('/api/auth/passkeys/register', { method: 'POST', body: { credential, name } diff --git a/pages/passkey-wiederherstellen.vue b/pages/passkey-wiederherstellen.vue index ef3ae09..e1472ab 100644 --- a/pages/passkey-wiederherstellen.vue +++ b/pages/passkey-wiederherstellen.vue @@ -167,7 +167,8 @@ const addPasskeyViaToken = async () => { }) const mod = await import('@simplewebauthn/browser') - const credential = await mod.startRegistration(opts.options) + // @simplewebauthn/browser v13+ erwartet { optionsJSON: options } + const credential = await mod.startRegistration({ optionsJSON: opts.options }) const res = await $fetch('/api/auth/passkeys/recovery/complete', { method: 'POST', diff --git a/pages/registrieren.vue b/pages/registrieren.vue index 67b3785..d651bcf 100644 --- a/pages/registrieren.vue +++ b/pages/registrieren.vue @@ -445,26 +445,44 @@ const handleRegisterWithPasskey = async () => { } // Debug: Prüfe die vollständige Options-Struktur + // WICHTIG: @simplewebauthn/browser erwartet: + // - challenge: Base64URL-String + // - user.id: Base64URL-String (wird automatisch zu Uint8Array konvertiert) + // - excludeCredentials[].id: Base64URL-String (wird automatisch zu Uint8Array konvertiert) console.log('[DEBUG] Full options structure check:', { hasChallenge: !!pre.options.challenge, challengeValue: pre.options.challenge?.substring(0, 20) + '...', challengeType: typeof pre.options.challenge, + challengeLength: pre.options.challenge?.length, hasRp: !!pre.options.rp, rpId: pre.options.rp?.id, rpName: pre.options.rp?.name, hasUser: !!pre.options.user, userId: pre.options.user?.id ? 'present' : 'missing', userIdType: typeof pre.options.user?.id, + userIdValue: typeof pre.options.user?.id === 'string' ? pre.options.user.id.substring(0, 20) + '...' : 'not a string', userName: pre.options.user?.name, userDisplayName: pre.options.user?.displayName, hasPubKeyCredParams: !!pre.options.pubKeyCredParams, pubKeyCredParamsCount: pre.options.pubKeyCredParams?.length, hasAuthenticatorSelection: !!pre.options.authenticatorSelection, + authenticatorSelection: pre.options.authenticatorSelection, timeout: pre.options.timeout, timeoutType: typeof pre.options.timeout, + excludeCredentialsCount: pre.options.excludeCredentials?.length || 0, + hasExtensions: !!pre.options.extensions, + hasHints: !!pre.options.hints, allKeys: Object.keys(pre.options), - optionsStringified: JSON.stringify(pre.options).substring(0, 200) + '...' + userKeys: pre.options.user ? Object.keys(pre.options.user) : [] }) + + // Prüfe, ob user.id ein String ist (Base64URL) + // @simplewebauthn/browser erwartet user.id als Base64URL-String + if (pre.options.user?.id && typeof pre.options.user.id !== 'string') { + console.error('[DEBUG] ERROR: user.id is not a string!', typeof pre.options.user.id, pre.options.user.id) + console.error('[DEBUG] @simplewebauthn/browser erwartet user.id als Base64URL-String') + throw new Error('Invalid user.id format - must be Base64URL string') + } console.log('[DEBUG] Calling startRegistration...') console.log('[DEBUG] Full options object (will be encoded in QR code for Cross-Device):', JSON.stringify(pre.options, null, 2)) @@ -526,8 +544,20 @@ const handleRegisterWithPasskey = async () => { const webauthnStart = Date.now() + // Importiere @simplewebauthn/browser const mod = await import('@simplewebauthn/browser') - // startRegistration erwartet die Options direkt (wie in anderen Dateien auch) + + // Prüfe, ob startRegistration verfügbar ist + if (!mod.startRegistration) { + console.error('[DEBUG] ERROR: mod.startRegistration is not available!') + console.error('[DEBUG] Available exports:', Object.keys(mod)) + throw new Error('startRegistration ist nicht verfügbar. Bitte prüfen Sie, ob @simplewebauthn/browser korrekt installiert ist.') + } + + console.log('[DEBUG] @simplewebauthn/browser imported successfully') + console.log('[DEBUG] Available exports:', Object.keys(mod)) + console.log('[DEBUG] startRegistration type:', typeof mod.startRegistration) + let credential try { // Timeout-Warnung nach 2 Minuten @@ -563,24 +593,44 @@ const handleRegisterWithPasskey = async () => { console.log('[DEBUG] Options validation before startRegistration:', { hasChallenge: !!pre.options.challenge, challengeType: typeof pre.options.challenge, + challengeValue: pre.options.challenge?.substring(0, 20) + '...', hasRp: !!pre.options.rp, hasRpId: !!pre.options.rp?.id, hasUser: !!pre.options.user, hasUserID: !!pre.options.user?.id, + userIdType: typeof pre.options.user?.id, + userIdValue: typeof pre.options.user?.id === 'string' ? pre.options.user.id.substring(0, 20) + '...' : 'not a string', timeout: pre.options.timeout, timeoutType: typeof pre.options.timeout, - allKeys: Object.keys(pre.options) + allKeys: Object.keys(pre.options), + userKeys: pre.options.user ? Object.keys(pre.options.user) : [] }) - // Stelle sicher, dass challenge ein String ist (nicht Base64URL-encoded) + // Stelle sicher, dass challenge ein String ist (Base64URL) if (typeof pre.options.challenge !== 'string') { - console.error('[DEBUG] ERROR: Challenge is not a string!', pre.options.challenge) + console.error('[DEBUG] ERROR: Challenge is not a string!', typeof pre.options.challenge, 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) + // Prüfe user.id - sollte ein String (Base64URL) sein + if (pre.options.user?.id && typeof pre.options.user.id !== 'string') { + console.warn('[DEBUG] WARNING: user.id is not a string!', typeof pre.options.user.id, pre.options.user.id) + console.warn('[DEBUG] @simplewebauthn/browser erwartet user.id als Base64URL-String') + } + + // @simplewebauthn/browser v13+ erwartet { optionsJSON: options } + // Die Bibliothek unterstützt auch die alte API (direkt options), gibt aber eine Warnung + // Wir verwenden die neue API-Struktur, um die Warnung zu vermeiden + console.log('[DEBUG] Calling mod.startRegistration with new API structure...') + console.log('[DEBUG] Options structure:', { + challenge: pre.options.challenge?.substring(0, 20) + '...', + rpId: pre.options.rp?.id, + userId: typeof pre.options.user?.id === 'string' ? pre.options.user.id.substring(0, 20) + '...' : pre.options.user?.id, + timeout: pre.options.timeout + }) + + // Neue API-Struktur: { optionsJSON: options } + credential = await mod.startRegistration({ optionsJSON: pre.options }) clearTimeout(timeoutWarning) diff --git a/public/test-smartphone.html b/public/test-smartphone.html index 46e67f2..7e50b16 100644 --- a/public/test-smartphone.html +++ b/public/test-smartphone.html @@ -384,7 +384,7 @@ const credential = await navigator.credentials.create({ publicKey: publicKeyCredentialCreationOptions }); - + dd const duration = Date.now() - startTime; // Konvertiere Credential zu einem serialisierbaren Format