diff --git a/public/test-smartphone.html b/public/test-smartphone.html index 426bda2..46e67f2 100644 --- a/public/test-smartphone.html +++ b/public/test-smartphone.html @@ -328,34 +328,77 @@ throw new Error('Invalid options response'); } - resultEl.textContent = 'Options erhalten. Starte WebAuthn startRegistration...\n\n'; + resultEl.textContent = 'Options erhalten. Starte WebAuthn Registrierung...\n\n'; resultEl.textContent += `Challenge: ${optionsData.options.challenge?.substring(0, 20)}...\n`; resultEl.textContent += `RP ID: ${optionsData.options.rp?.id}\n`; resultEl.textContent += `Timeout: ${optionsData.options.timeout}ms\n\n`; resultEl.textContent += 'Warte auf Passkey-Authentifizierung...\n'; - resultEl.textContent += '(Scanne QR-Code oder verwende lokalen Authenticator)\n'; + resultEl.textContent += '(Scanne QR-Code oder verwende lokalen Authenticator)\n\n'; + resultEl.textContent += 'HINWEIS: Diese Test-Seite verwendet die native WebAuthn-API.\n'; + resultEl.textContent += 'Die eigentliche App verwendet @simplewebauthn/browser (ist im Build enthalten).\n\n'; - // WebAuthn startRegistration - // Versuche, das Modul zu laden (kann fehlschlagen auf Smartphone) - let startRegistration; - try { - const mod = await import('https://unpkg.com/@simplewebauthn/browser@13.2.2/dist/bundle/index.umd.min.js'); - startRegistration = mod.startRegistration; - } catch (importError) { - // Fallback: Versuche, es lokal zu laden (wenn auf der Seite verfügbar) - resultEl.textContent += '\n\nWARNUNG: Konnte @simplewebauthn/browser nicht laden.\n'; - resultEl.textContent += 'Dies ist normal, wenn die Bibliothek nicht verfügbar ist.\n'; - resultEl.textContent += 'Die Options wurden aber erfolgreich vom Server erhalten.\n\n'; - resultEl.textContent += 'Options-Struktur:\n'; - resultEl.textContent += JSON.stringify(optionsData.options, null, 2); - resultEl.className = 'result warn'; - return; + // Verwende native WebAuthn-API (navigator.credentials.create) + // Dies ist die Basis-API, die @simplewebauthn/browser verwendet + const startTime = Date.now(); + + // Konvertiere user.id von Base64URL zu Uint8Array (falls nötig) + let userId; + if (typeof optionsData.options.user?.id === 'string') { + // Base64URL decode + const base64Url = optionsData.options.user.id; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const binaryString = atob(base64); + userId = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + userId[i] = binaryString.charCodeAt(i); + } + } else if (optionsData.options.user?.id instanceof Uint8Array) { + userId = optionsData.options.user.id; + } else { + throw new Error('Invalid user.id format'); } - const startTime = Date.now(); - const credential = await startRegistration(optionsData.options); + // Erstelle PublicKeyCredentialCreationOptions + const publicKeyCredentialCreationOptions = { + challenge: Uint8Array.from(atob(optionsData.options.challenge.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)), + rp: optionsData.options.rp, + user: { + id: userId, + name: optionsData.options.user.name, + displayName: optionsData.options.user.displayName + }, + pubKeyCredParams: optionsData.options.pubKeyCredParams, + timeout: optionsData.options.timeout, + attestation: optionsData.options.attestation, + excludeCredentials: optionsData.options.excludeCredentials?.map(cred => ({ + id: Uint8Array.from(atob(cred.id.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)), + type: cred.type, + transports: cred.transports + })) || [], + authenticatorSelection: optionsData.options.authenticatorSelection, + extensions: optionsData.options.extensions + }; + + console.log('[DEBUG] Calling navigator.credentials.create with options:', publicKeyCredentialCreationOptions); + + const credential = await navigator.credentials.create({ + publicKey: publicKeyCredentialCreationOptions + }); + const duration = Date.now() - startTime; + // Konvertiere Credential zu einem serialisierbaren Format + const credentialData = { + id: credential.id, + rawId: Array.from(new Uint8Array(credential.rawId)), + type: credential.type, + response: { + clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)), + attestationObject: Array.from(new Uint8Array(credential.response.attestationObject)) + }, + transports: credential.response.getTransports?.() || [] + }; + const result = { 'Status': 'SUCCESS ✓', 'Duration': `${duration}ms`, @@ -364,9 +407,10 @@ 'Type': credential.type, 'Raw ID': credential.rawId ? 'present' : 'missing', 'Response': credential.response ? 'present' : 'missing', - 'Transports': credential.transports || [] + 'Transports': credentialData.transports || [] }, - 'Full Credential': credential + 'Note': 'Dies ist die native WebAuthn-API. Die eigentliche App verwendet @simplewebauthn/browser.', + 'Credential Data': credentialData }; resultEl.className = 'result success';