Enhance debug logging for Cross-Device Passkey Registration
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 50s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 50s
Add detailed debug statements in the registrieren.vue component to provide insights into the QR-Code generation process and the Cross-Device authentication flow. Additionally, update the register-passkey API to log request details, including user agent and method, to improve troubleshooting capabilities and ensure clarity during the registration process.
This commit is contained in:
152
CROSS_DEVICE_DEBUG.md
Normal file
152
CROSS_DEVICE_DEBUG.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Cross-Device Passkey Debugging
|
||||
|
||||
## Problem: Smartphone kann keine Verbindung aufbauen
|
||||
|
||||
CORS funktioniert jetzt korrekt (OPTIONS gibt 204 zurück), aber das Smartphone kann immer noch keine Verbindung aufbauen.
|
||||
|
||||
## Debugging-Schritte
|
||||
|
||||
### 1. Prüfe Server-Logs
|
||||
|
||||
Auf dem Server, während du versuchst, den QR-Code zu scannen:
|
||||
|
||||
```bash
|
||||
pm2 logs harheimertc --lines 100 | grep -i "register-passkey\|OPTIONS\|CORS\|origin"
|
||||
```
|
||||
|
||||
**Was zu prüfen ist:**
|
||||
- Kommt ein Request vom Smartphone an? (Sollte `[DEBUG] register-passkey request received` zeigen)
|
||||
- Welche Origin hat der Request? (Sollte `https://harheimertc.tsschulz.de` sein)
|
||||
- Welcher User-Agent? (Sollte das Smartphone-Browser sein)
|
||||
|
||||
### 2. Prüfe Browser-Konsole (Desktop)
|
||||
|
||||
Öffne F12 → Console auf dem Desktop-Browser und prüfe:
|
||||
- Wird der QR-Code angezeigt?
|
||||
- Gibt es Fehlermeldungen?
|
||||
- Was steht in den Debug-Logs?
|
||||
|
||||
### 3. Prüfe Smartphone-Browser
|
||||
|
||||
**Wichtig:** Wenn du den QR-Code scannst, öffnet das Smartphone die Website. Prüfe dort:
|
||||
|
||||
1. **Öffne die Website direkt auf dem Smartphone:**
|
||||
```
|
||||
https://harheimertc.tsschulz.de/registrieren
|
||||
```
|
||||
|
||||
2. **Prüfe, ob die Website lädt** (sollte funktionieren, da du sagst, du kannst die Website vom Smartphone aus erreichen)
|
||||
|
||||
3. **Versuche, den QR-Code zu scannen** und prüfe:
|
||||
- Öffnet sich die Website auf dem Smartphone?
|
||||
- Gibt es Fehlermeldungen?
|
||||
- Wird ein Request an den Server gesendet?
|
||||
|
||||
### 4. Netzwerk-Test
|
||||
|
||||
**Auf dem Smartphone:**
|
||||
- Öffne die Website direkt: `https://harheimertc.tsschulz.de`
|
||||
- Prüfe, ob sie lädt
|
||||
- Prüfe, ob HTTPS korrekt funktioniert (keine Zertifikat-Fehler)
|
||||
|
||||
**Mit curl (vom Server aus):**
|
||||
```bash
|
||||
# Simuliere einen Request vom Smartphone
|
||||
curl -X POST \
|
||||
-H "Origin: https://harheimertc.tsschulz.de" \
|
||||
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"registrationId":"test","credential":{}}' \
|
||||
-v \
|
||||
https://harheimertc.tsschulz.de/api/auth/register-passkey
|
||||
```
|
||||
|
||||
### 5. Häufige Probleme
|
||||
|
||||
#### Problem 1: QR-Code wird nicht angezeigt
|
||||
**Symptom:** Kein QR-Code erscheint im Browser
|
||||
**Lösung:**
|
||||
- Prüfe Browser-Konsole auf Fehler
|
||||
- Prüfe, ob `startRegistration` aufgerufen wird
|
||||
- Prüfe, ob die Options korrekt sind
|
||||
|
||||
#### Problem 2: QR-Code wird gescannt, aber Website öffnet sich nicht
|
||||
**Symptom:** QR-Code wird gescannt, aber nichts passiert
|
||||
**Lösung:**
|
||||
- Prüfe, ob der QR-Code die richtige URL enthält
|
||||
- Prüfe, ob das Smartphone die Website erreichen kann
|
||||
- Prüfe, ob es Firewall-Blockierungen gibt
|
||||
|
||||
#### Problem 3: Website öffnet sich, aber Request kommt nicht an
|
||||
**Symptom:** Website öffnet sich auf dem Smartphone, aber Server-Logs zeigen keinen Request
|
||||
**Lösung:**
|
||||
- Prüfe CORS-Header (sollte jetzt funktionieren)
|
||||
- Prüfe, ob der Request wirklich gesendet wird (Browser-Entwicklertools auf dem Smartphone)
|
||||
- Prüfe, ob Apache den Request weiterleitet
|
||||
|
||||
#### Problem 4: Request kommt an, aber Fehler
|
||||
**Symptom:** Request kommt im Server-Log an, aber es gibt einen Fehler
|
||||
**Lösung:**
|
||||
- Prüfe die Fehlermeldung im Server-Log
|
||||
- Prüfe, ob die Challenge korrekt ist
|
||||
- Prüfe, ob die Origin übereinstimmt
|
||||
|
||||
### 6. Manueller Test
|
||||
|
||||
**Test 1: Direkte Website-Zugriff vom Smartphone**
|
||||
```
|
||||
https://harheimertc.tsschulz.de/registrieren
|
||||
```
|
||||
Sollte funktionieren.
|
||||
|
||||
**Test 2: API-Endpoint vom Smartphone**
|
||||
Öffne auf dem Smartphone:
|
||||
```
|
||||
https://harheimertc.tsschulz.de/api/auth/register-passkey-options
|
||||
```
|
||||
Sollte einen JSON-Fehler zurückgeben (weil POST erwartet wird), aber kein 404.
|
||||
|
||||
**Test 3: OPTIONS-Request vom Smartphone**
|
||||
Mit einem Tool wie "HTTP Request" auf dem Smartphone:
|
||||
```
|
||||
OPTIONS https://harheimertc.tsschulz.de/api/auth/register-passkey-options
|
||||
Origin: https://harheimertc.tsschulz.de
|
||||
```
|
||||
Sollte 204 mit CORS-Headern zurückgeben.
|
||||
|
||||
### 7. Browser-spezifische Probleme
|
||||
|
||||
**Chrome/Edge:**
|
||||
- Sollte Cross-Device unterstützen
|
||||
- QR-Code sollte automatisch erscheinen
|
||||
|
||||
**Firefox:**
|
||||
- Unterstützt Cross-Device möglicherweise nicht vollständig
|
||||
- Prüfe Browser-Konsole auf Warnungen
|
||||
|
||||
**Safari (iOS):**
|
||||
- Unterstützt Cross-Device
|
||||
- QR-Code sollte automatisch erscheinen
|
||||
|
||||
### 8. Was die Logs zeigen sollten
|
||||
|
||||
**Wenn alles funktioniert:**
|
||||
1. Desktop-Browser: `[DEBUG] startRegistration called - QR-Code should appear now`
|
||||
2. Smartphone scannt QR-Code
|
||||
3. Smartphone sendet Request: `[DEBUG] register-passkey request received` mit Smartphone User-Agent
|
||||
4. Server verifiziert: `[DEBUG] Verification successful`
|
||||
|
||||
**Wenn es nicht funktioniert:**
|
||||
- Kein Request vom Smartphone → Problem mit QR-Code oder Netzwerk
|
||||
- Request kommt an, aber Fehler → Problem mit Challenge oder Origin
|
||||
- Timeout → Problem mit Verbindung oder CORS
|
||||
|
||||
### 9. Nächste Schritte
|
||||
|
||||
1. **Prüfe Server-Logs** während des QR-Code-Scans
|
||||
2. **Prüfe Browser-Konsole** auf dem Desktop
|
||||
3. **Prüfe, ob der QR-Code angezeigt wird**
|
||||
4. **Prüfe, ob das Smartphone die Website öffnet**
|
||||
5. **Prüfe, ob ein Request ankommt**
|
||||
|
||||
Bitte teile die Ergebnisse dieser Tests, dann können wir das Problem weiter eingrenzen.
|
||||
98
SMARTPHONE_TEST_ANLEITUNG.md
Normal file
98
SMARTPHONE_TEST_ANLEITUNG.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Smartphone Passkey Test - Anleitung
|
||||
|
||||
## Test-Seite öffnen
|
||||
|
||||
**Auf dem Smartphone öffnen:**
|
||||
```
|
||||
https://harheimertc.tsschulz.de/test-smartphone.html
|
||||
```
|
||||
|
||||
## Was die Test-Seite zeigt
|
||||
|
||||
### 1. Basis-Informationen
|
||||
- Aktuelle URL und Origin
|
||||
- Ob HTTPS/Secure Context aktiv ist
|
||||
- Ob WebAuthn unterstützt wird
|
||||
- Browser und Plattform-Info
|
||||
|
||||
### 2. OPTIONS Preflight Test
|
||||
- Testet, ob OPTIONS-Requests funktionieren
|
||||
- Zeigt CORS-Header
|
||||
- Sollte Status 204 zurückgeben
|
||||
|
||||
### 3. Registration Options Test
|
||||
- Testet den `/api/auth/register-passkey-options` Endpoint
|
||||
- Zeigt, ob Options korrekt zurückgegeben werden
|
||||
- Zeigt CORS-Header
|
||||
|
||||
### 4. WebAuthn API Test
|
||||
- Testet die komplette WebAuthn-Registrierung
|
||||
- Zeigt, ob `startRegistration` funktioniert
|
||||
- Zeigt, ob ein QR-Code erscheint (für Cross-Device)
|
||||
|
||||
### 5. Network Monitor
|
||||
- Zeigt alle Requests, die gesendet werden
|
||||
- Zeigt Status-Codes und URLs
|
||||
|
||||
## Was zu prüfen ist
|
||||
|
||||
### ✅ Erfolgreich, wenn:
|
||||
- **Basis-Info:** "Is Secure Context: JA ✓" und "WebAuthn Support: JA ✓"
|
||||
- **OPTIONS Test:** Status 204 und alle CORS-Header vorhanden
|
||||
- **Registration Options:** Status 200, `success: true`, `hasChallenge: JA ✓`
|
||||
- **WebAuthn Test:** Zeigt "SUCCESS ✓" oder QR-Code erscheint
|
||||
|
||||
### ❌ Probleme, wenn:
|
||||
- **OPTIONS Test:** Status 404 oder CORS-Header fehlen
|
||||
- **Registration Options:** Status 500 oder `success: false`
|
||||
- **WebAuthn Test:** Fehler wie "NotSupportedError" oder Timeout
|
||||
|
||||
## Häufige Probleme
|
||||
|
||||
### Problem 1: "WebAuthn Support: NEIN ✗"
|
||||
**Ursache:** Browser unterstützt WebAuthn nicht oder nicht in Secure Context
|
||||
**Lösung:**
|
||||
- Stelle sicher, dass HTTPS verwendet wird
|
||||
- Prüfe, ob der Browser WebAuthn unterstützt (Chrome, Safari, Edge sollten funktionieren)
|
||||
|
||||
### Problem 2: OPTIONS gibt 404
|
||||
**Ursache:** Endpoint behandelt OPTIONS nicht korrekt
|
||||
**Lösung:** Sollte jetzt behoben sein mit `.options.js` Dateien
|
||||
|
||||
### Problem 3: CORS-Header fehlen
|
||||
**Ursache:** Server setzt CORS-Header nicht
|
||||
**Lösung:** Prüfe Server-Logs, ob CORS-Header gesetzt werden
|
||||
|
||||
### Problem 4: WebAuthn Test schlägt fehl
|
||||
**Ursache:**
|
||||
- Kein lokaler Authenticator verfügbar
|
||||
- Cross-Device-Verbindung funktioniert nicht
|
||||
- QR-Code wird nicht angezeigt
|
||||
|
||||
**Lösung:**
|
||||
- Prüfe, ob ein QR-Code erscheint
|
||||
- Prüfe, ob das Smartphone die Website erreichen kann
|
||||
- Prüfe Server-Logs, ob Requests ankommen
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. **Öffne die Test-Seite auf dem Smartphone**
|
||||
2. **Führe alle Tests aus**
|
||||
3. **Screenshot oder kopiere die Ergebnisse**
|
||||
4. **Prüfe Server-Logs während der Tests:**
|
||||
```bash
|
||||
pm2 logs harheimertc --lines 50
|
||||
```
|
||||
5. **Teile die Ergebnisse**, dann können wir das Problem weiter eingrenzen
|
||||
|
||||
## Zusätzliche Debug-Info
|
||||
|
||||
**Auf dem Smartphone:**
|
||||
- Öffne die Browser-Entwicklertools (falls verfügbar)
|
||||
- Prüfe den Network-Tab
|
||||
- Prüfe die Console auf Fehler
|
||||
|
||||
**Auf dem Server:**
|
||||
- Prüfe PM2-Logs während der Tests
|
||||
- Prüfe Apache-Logs für Requests
|
||||
- Prüfe, ob Requests ankommen
|
||||
@@ -518,6 +518,24 @@ const handleRegisterWithPasskey = async () => {
|
||||
|
||||
console.log('[DEBUG] startRegistration called - QR-Code should appear now (if Cross-Device)')
|
||||
console.log('[DEBUG] Passing options directly to startRegistration (same as in profil.vue)')
|
||||
console.log('[DEBUG] Options for QR-Code:', {
|
||||
rpId: pre.options.rp?.id,
|
||||
rpName: pre.options.rp?.name,
|
||||
challenge: pre.options.challenge?.substring(0, 20) + '...',
|
||||
timeout: pre.options.timeout,
|
||||
timeoutSeconds: Math.round(pre.options.timeout / 1000),
|
||||
note: 'Der Browser generiert automatisch einen QR-Code. Das Smartphone muss diese Origin erreichen können: ' + window.location.origin
|
||||
})
|
||||
|
||||
// WICHTIG: Für Cross-Device muss das Smartphone die gleiche Origin erreichen können
|
||||
// Der QR-Code enthält die Challenge und die Server-Info
|
||||
// Das Smartphone öffnet dann die Website und sendet die Credential-Response zurück
|
||||
console.log('[DEBUG] Cross-Device Flow:')
|
||||
console.log('[DEBUG] 1. Browser generiert QR-Code mit Challenge:', pre.options.challenge?.substring(0, 20) + '...')
|
||||
console.log('[DEBUG] 2. Smartphone scannt QR-Code')
|
||||
console.log('[DEBUG] 3. Smartphone muss diese URL erreichen können:', window.location.origin)
|
||||
console.log('[DEBUG] 4. Smartphone sendet Credential-Response an:', window.location.origin + '/api/auth/register-passkey')
|
||||
console.log('[DEBUG] 5. Server verifiziert die Response')
|
||||
|
||||
// Direkt die Options übergeben (wie in profil.vue und passkey-wiederherstellen.vue)
|
||||
credential = await mod.startRegistration(pre.options)
|
||||
|
||||
394
public/test-smartphone.html
Normal file
394
public/test-smartphone.html
Normal file
@@ -0,0 +1,394 @@
|
||||
<!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 startRegistration...\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';
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const credential = await startRegistration(optionsData.options);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
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': credential.transports || []
|
||||
},
|
||||
'Full Credential': credential
|
||||
};
|
||||
|
||||
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>
|
||||
@@ -11,12 +11,16 @@ import { assertPasswordNotPwned } from '../../utils/hibp.js'
|
||||
export default defineEventHandler(async (event) => {
|
||||
const requestStart = Date.now()
|
||||
const requestOrigin = getHeader(event, 'origin')
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||
|
||||
console.log('[DEBUG] register-passkey request received', {
|
||||
origin: requestOrigin,
|
||||
webauthnOrigin,
|
||||
timestamp: new Date().toISOString()
|
||||
userAgent: userAgent?.substring(0, 100),
|
||||
timestamp: new Date().toISOString(),
|
||||
method: getMethod(event),
|
||||
note: 'Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde'
|
||||
})
|
||||
|
||||
// CORS-Header für Cross-Device Authentication
|
||||
|
||||
Reference in New Issue
Block a user