Remove deprecated Passkey-related documentation and test files
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 47s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 47s
This commit deletes several files related to Passkey functionality, including CORS_TEST_ANLEITUNG.md, CROSS_DEVICE_DEBUG.md, CROSS_DEVICE_PROBLEM_ZUSAMMENFASSUNG.md, SMARTPHONE_TEST_ANLEITUNG.md, test-cors.html, test-smartphone.html, and Vue components for Passkey registration and recovery. These removals are part of a broader effort to streamline the codebase and focus on core authentication methods while Passkey support is under review.
This commit is contained in:
@@ -1,214 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CORS Test für Passkey Cross-Device</title>
|
||||
<style>
|
||||
body { font-family: monospace; padding: 20px; }
|
||||
.test { margin: 20px 0; padding: 10px; border: 1px solid #ccc; }
|
||||
.success { background: #d4edda; }
|
||||
.error { background: #f8d7da; }
|
||||
.info { background: #d1ecf1; }
|
||||
button { padding: 10px 20px; margin: 5px; }
|
||||
pre { background: #f5f5f5; padding: 10px; overflow-x: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CORS Test für Passkey Cross-Device Authentication</h1>
|
||||
|
||||
<div class="test info">
|
||||
<h3>1. Origin-Info</h3>
|
||||
<p><strong>Current Origin:</strong> <span id="currentOrigin"></span></p>
|
||||
<p><strong>Is Secure Context:</strong> <span id="isSecure"></span></p>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>2. OPTIONS Preflight Test</h3>
|
||||
<button onclick="testOptions()">Test OPTIONS Request</button>
|
||||
<pre id="optionsResult"></pre>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>3. POST Request Test (mit Origin-Header)</h3>
|
||||
<button onclick="testPost()">Test POST Request</button>
|
||||
<pre id="postResult"></pre>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>4. Registration Options Test</h3>
|
||||
<button onclick="testRegistrationOptions()">Test /api/auth/register-passkey-options</button>
|
||||
<pre id="registrationResult"></pre>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>5. CORS Headers Check (Network Tab)</h3>
|
||||
<p>Öffne die Browser-Entwicklertools (F12) → Network Tab</p>
|
||||
<p>Führe die Tests oben aus und prüfe:</p>
|
||||
<ul>
|
||||
<li><strong>OPTIONS Request:</strong> Sollte Status 204 haben</li>
|
||||
<li><strong>Response Headers:</strong> Sollten enthalten:
|
||||
<ul>
|
||||
<li>Access-Control-Allow-Origin: <span id="currentOrigin2"></span></li>
|
||||
<li>Access-Control-Allow-Credentials: true</li>
|
||||
<li>Access-Control-Allow-Methods: GET, POST, OPTIONS</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const origin = window.location.origin;
|
||||
document.getElementById('currentOrigin').textContent = origin;
|
||||
document.getElementById('currentOrigin2').textContent = origin;
|
||||
document.getElementById('isSecure').textContent = window.isSecureContext ? 'JA ✓' : 'NEIN ✗';
|
||||
|
||||
async function testOptions() {
|
||||
const resultEl = document.getElementById('optionsResult');
|
||||
resultEl.textContent = 'Testing OPTIONS request...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/register-passkey-options', {
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
'Origin': origin,
|
||||
'Access-Control-Request-Method': 'POST',
|
||||
'Access-Control-Request-Headers': 'Content-Type'
|
||||
}
|
||||
});
|
||||
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
resultEl.innerHTML = `
|
||||
Status: ${response.status} ${response.status === 204 ? '✓' : '✗'}
|
||||
Status Text: ${response.statusText}
|
||||
|
||||
Response Headers:
|
||||
${JSON.stringify(headers, null, 2)}
|
||||
|
||||
CORS Headers Check:
|
||||
- Access-Control-Allow-Origin: ${headers['access-control-allow-origin'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Credentials: ${headers['access-control-allow-credentials'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Methods: ${headers['access-control-allow-methods'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Headers: ${headers['access-control-allow-headers'] || 'FEHLT ✗'}
|
||||
`;
|
||||
|
||||
if (response.status === 204 && headers['access-control-allow-origin']) {
|
||||
resultEl.parentElement.className = 'test success';
|
||||
} else {
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
} catch (error) {
|
||||
resultEl.textContent = `ERROR: ${error.message}\n${error.stack}`;
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
}
|
||||
|
||||
async function testPost() {
|
||||
const resultEl = document.getElementById('postResult');
|
||||
resultEl.textContent = 'Testing POST request...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/register-passkey-options', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': origin
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Test User',
|
||||
email: 'test@example.com'
|
||||
})
|
||||
});
|
||||
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({ error: 'Could not parse JSON' }));
|
||||
|
||||
resultEl.innerHTML = `
|
||||
Status: ${response.status}
|
||||
Status Text: ${response.statusText}
|
||||
|
||||
Response Headers:
|
||||
${JSON.stringify(headers, null, 2)}
|
||||
|
||||
Response Body:
|
||||
${JSON.stringify(data, null, 2)}
|
||||
|
||||
CORS Headers Check:
|
||||
- Access-Control-Allow-Origin: ${headers['access-control-allow-origin'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Credentials: ${headers['access-control-allow-credentials'] || 'FEHLT ✗'}
|
||||
`;
|
||||
|
||||
if (headers['access-control-allow-origin']) {
|
||||
resultEl.parentElement.className = 'test success';
|
||||
} else {
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
} catch (error) {
|
||||
resultEl.textContent = `ERROR: ${error.message}\n${error.stack}`;
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
}
|
||||
|
||||
async function testRegistrationOptions() {
|
||||
const resultEl = document.getElementById('registrationResult');
|
||||
resultEl.textContent = 'Testing registration options endpoint...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/register-passkey-options', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phone: ''
|
||||
})
|
||||
});
|
||||
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
resultEl.innerHTML = `
|
||||
Status: ${response.status}
|
||||
Status Text: ${response.statusText}
|
||||
|
||||
Response Headers (CORS):
|
||||
- Access-Control-Allow-Origin: ${headers['access-control-allow-origin'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Credentials: ${headers['access-control-allow-credentials'] || 'FEHLT ✗'}
|
||||
- Access-Control-Allow-Methods: ${headers['access-control-allow-methods'] || 'FEHLT ✗'}
|
||||
|
||||
Response Body:
|
||||
${JSON.stringify(data, null, 2)}
|
||||
|
||||
Options Structure:
|
||||
- hasChallenge: ${data.options?.challenge ? 'JA ✓' : 'NEIN ✗'}
|
||||
- hasRp: ${data.options?.rp ? 'JA ✓' : 'NEIN ✗'}
|
||||
- hasUser: ${data.options?.user ? 'JA ✓' : 'NEIN ✗'}
|
||||
- rpId: ${data.options?.rp?.id || 'FEHLT'}
|
||||
- timeout: ${data.options?.timeout || 'FEHLT'}
|
||||
`;
|
||||
|
||||
if (data.success && data.options && headers['access-control-allow-origin']) {
|
||||
resultEl.parentElement.className = 'test success';
|
||||
} else {
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
} catch (error) {
|
||||
resultEl.textContent = `ERROR: ${error.message}\n${error.stack}`;
|
||||
resultEl.parentElement.className = 'test error';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,438 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Smartphone Passkey Test</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.test-section {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.test-section h2 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
}
|
||||
button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.result {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
.error {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
.loading {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.status.ok { background: #28a745; color: white; }
|
||||
.status.fail { background: #dc3545; color: white; }
|
||||
.status.warn { background: #ffc107; color: black; }
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔍 Smartphone Passkey Test</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. Basis-Informationen</h2>
|
||||
<div id="basicInfo" class="result info"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. OPTIONS Preflight Test</h2>
|
||||
<button onclick="testOptions()">Test OPTIONS Request</button>
|
||||
<div id="optionsResult" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. Registration Options Test</h2>
|
||||
<button onclick="testRegistrationOptions()">Test /api/auth/register-passkey-options</button>
|
||||
<div id="registrationResult" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>4. WebAuthn API Test</h2>
|
||||
<button onclick="testWebAuthn()">Test WebAuthn startRegistration</button>
|
||||
<div id="webauthnResult" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>5. Network Monitor</h2>
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
Öffne die Browser-Entwicklertools (falls verfügbar) oder prüfe die Netzwerk-Requests unten.
|
||||
</p>
|
||||
<div id="networkLog" class="result info" style="max-height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const origin = window.location.origin;
|
||||
const apiBase = origin;
|
||||
|
||||
// Basis-Informationen
|
||||
function showBasicInfo() {
|
||||
const info = {
|
||||
'Current URL': window.location.href,
|
||||
'Origin': origin,
|
||||
'Protocol': window.location.protocol,
|
||||
'Hostname': window.location.hostname,
|
||||
'Port': window.location.port || 'default (443 for HTTPS)',
|
||||
'Is Secure Context': window.isSecureContext ? 'JA ✓' : 'NEIN ✗',
|
||||
'User Agent': navigator.userAgent,
|
||||
'WebAuthn Support': window.PublicKeyCredential ? 'JA ✓' : 'NEIN ✗',
|
||||
'Platform': navigator.platform,
|
||||
'Screen': `${screen.width}x${screen.height}`
|
||||
};
|
||||
|
||||
document.getElementById('basicInfo').textContent =
|
||||
Object.entries(info).map(([key, value]) => `${key}: ${value}`).join('\n');
|
||||
}
|
||||
|
||||
function logNetwork(url, method, status, headers) {
|
||||
const log = document.getElementById('networkLog');
|
||||
const time = new Date().toLocaleTimeString();
|
||||
const entry = `[${time}] ${method} ${url}\n Status: ${status}\n Origin: ${origin}\n`;
|
||||
log.textContent = entry + '\n' + log.textContent;
|
||||
}
|
||||
|
||||
async function testOptions() {
|
||||
const resultEl = document.getElementById('optionsResult');
|
||||
resultEl.className = 'result loading';
|
||||
resultEl.textContent = 'Testing OPTIONS request...';
|
||||
|
||||
const url = `${apiBase}/api/auth/register-passkey-options`;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await fetch(url, {
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
'Origin': origin,
|
||||
'Access-Control-Request-Method': 'POST',
|
||||
'Access-Control-Request-Headers': 'Content-Type'
|
||||
}
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
logNetwork(url, 'OPTIONS', response.status, headers);
|
||||
|
||||
const result = {
|
||||
'Status': `${response.status} ${response.status === 204 ? '✓' : '✗'}`,
|
||||
'Status Text': response.statusText,
|
||||
'Duration': `${duration}ms`,
|
||||
'CORS Headers': {
|
||||
'Access-Control-Allow-Origin': headers['access-control-allow-origin'] || 'FEHLT ✗',
|
||||
'Access-Control-Allow-Credentials': headers['access-control-allow-credentials'] || 'FEHLT ✗',
|
||||
'Access-Control-Allow-Methods': headers['access-control-allow-methods'] || 'FEHLT ✗',
|
||||
'Access-Control-Allow-Headers': headers['access-control-allow-headers'] || 'FEHLT ✗'
|
||||
},
|
||||
'All Headers': headers
|
||||
};
|
||||
|
||||
resultEl.textContent = JSON.stringify(result, null, 2);
|
||||
|
||||
if (response.status === 204 && headers['access-control-allow-origin']) {
|
||||
resultEl.className = 'result success';
|
||||
} else {
|
||||
resultEl.className = 'result error';
|
||||
}
|
||||
} catch (error) {
|
||||
logNetwork(url, 'OPTIONS', 'ERROR', {});
|
||||
resultEl.className = 'result error';
|
||||
resultEl.textContent = `ERROR: ${error.message}\n${error.stack}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testRegistrationOptions() {
|
||||
const resultEl = document.getElementById('registrationResult');
|
||||
resultEl.className = 'result loading';
|
||||
resultEl.textContent = 'Testing registration options...';
|
||||
|
||||
const url = `${apiBase}/api/auth/register-passkey-options`;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': origin
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phone: ''
|
||||
})
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
logNetwork(url, 'POST', response.status, headers);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const result = {
|
||||
'Status': `${response.status} ${response.status === 200 ? '✓' : '✗'}`,
|
||||
'Duration': `${duration}ms`,
|
||||
'CORS Headers': {
|
||||
'Access-Control-Allow-Origin': headers['access-control-allow-origin'] || 'FEHLT ✗',
|
||||
'Access-Control-Allow-Credentials': headers['access-control-allow-credentials'] || 'FEHLT ✗'
|
||||
},
|
||||
'Response': {
|
||||
'Success': data.success ? 'JA ✓' : 'NEIN ✗',
|
||||
'Has Options': !!data.options ? 'JA ✓' : 'NEIN ✗',
|
||||
'Has Challenge': !!data.options?.challenge ? 'JA ✓' : 'NEIN ✗',
|
||||
'RP ID': data.options?.rp?.id || 'FEHLT',
|
||||
'Timeout': data.options?.timeout || 'FEHLT',
|
||||
'Registration ID': data.registrationId || 'FEHLT'
|
||||
},
|
||||
'Full Response': data
|
||||
};
|
||||
|
||||
resultEl.textContent = JSON.stringify(result, null, 2);
|
||||
|
||||
if (data.success && data.options && headers['access-control-allow-origin']) {
|
||||
resultEl.className = 'result success';
|
||||
} else {
|
||||
resultEl.className = 'result error';
|
||||
}
|
||||
} catch (error) {
|
||||
logNetwork(url, 'POST', 'ERROR', {});
|
||||
resultEl.className = 'result error';
|
||||
resultEl.textContent = `ERROR: ${error.message}\n${error.stack}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function testWebAuthn() {
|
||||
const resultEl = document.getElementById('webauthnResult');
|
||||
resultEl.className = 'result loading';
|
||||
resultEl.textContent = 'Testing WebAuthn API...';
|
||||
|
||||
if (!window.PublicKeyCredential) {
|
||||
resultEl.className = 'result error';
|
||||
resultEl.textContent = 'ERROR: WebAuthn wird nicht unterstützt in diesem Browser/Kontext.\n\nMögliche Gründe:\n- Nicht HTTPS\n- Nicht in Secure Context\n- Browser unterstützt WebAuthn nicht';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Zuerst Options holen
|
||||
const optionsResponse = await fetch(`${apiBase}/api/auth/register-passkey-options`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': origin
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phone: ''
|
||||
})
|
||||
});
|
||||
|
||||
if (!optionsResponse.ok) {
|
||||
throw new Error(`Options request failed: ${optionsResponse.status}`);
|
||||
}
|
||||
|
||||
const optionsData = await optionsResponse.json();
|
||||
|
||||
if (!optionsData.success || !optionsData.options) {
|
||||
throw new Error('Invalid options response');
|
||||
}
|
||||
|
||||
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\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';
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
// 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
|
||||
});
|
||||
dd
|
||||
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`,
|
||||
'Credential': {
|
||||
'ID': credential.id,
|
||||
'Type': credential.type,
|
||||
'Raw ID': credential.rawId ? 'present' : 'missing',
|
||||
'Response': credential.response ? 'present' : 'missing',
|
||||
'Transports': credentialData.transports || []
|
||||
},
|
||||
'Note': 'Dies ist die native WebAuthn-API. Die eigentliche App verwendet @simplewebauthn/browser.',
|
||||
'Credential Data': credentialData
|
||||
};
|
||||
|
||||
resultEl.className = 'result success';
|
||||
resultEl.textContent = JSON.stringify(result, null, 2);
|
||||
|
||||
logNetwork(`${apiBase}/api/auth/register-passkey-options`, 'WebAuthn', 'SUCCESS', {});
|
||||
|
||||
} catch (error) {
|
||||
resultEl.className = 'result error';
|
||||
const errorInfo = {
|
||||
'Error': error.name,
|
||||
'Message': error.message,
|
||||
'Stack': error.stack,
|
||||
'Note': 'Dies könnte ein Cross-Device-Problem sein, wenn kein lokaler Authenticator verfügbar ist.'
|
||||
};
|
||||
resultEl.textContent = JSON.stringify(errorInfo, null, 2);
|
||||
logNetwork(`${apiBase}/api/auth/register-passkey-options`, 'WebAuthn', 'ERROR', {});
|
||||
}
|
||||
}
|
||||
|
||||
// Beim Laden Basis-Info anzeigen
|
||||
showBasicInfo();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user