Enhance debug logging and Cross-Device support for Passkey Registration
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 45s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 45s
Update the registrieren.vue component to include detailed debug statements for the Cross-Device authentication flow, specifically during QR-Code generation. Improve logging in the register-passkey-options and register-passkey APIs to capture request details such as user agent and IP address, aiding in troubleshooting. Additionally, introduce a new function to retrieve pre-registration data, enhancing the overall registration process and compliance with Cross-Device requirements.
This commit is contained in:
@@ -630,6 +630,18 @@ const handleRegisterWithPasskey = async () => {
|
||||
})
|
||||
|
||||
// Neue API-Struktur: { optionsJSON: options }
|
||||
// WICHTIG: Bei Cross-Device wird der QR-Code automatisch generiert
|
||||
// Das Smartphone scannt den QR-Code und öffnet die Website
|
||||
// Dann ruft das Smartphone startRegistration auf, um die Credential zu erstellen
|
||||
// Die Credential-Response wird dann an den Desktop-Browser zurückgesendet
|
||||
console.log('[DEBUG] About to call startRegistration - this will trigger QR code generation for Cross-Device')
|
||||
console.log('[DEBUG] If using Cross-Device:')
|
||||
console.log('[DEBUG] 1. QR-Code wird angezeigt')
|
||||
console.log('[DEBUG] 2. Smartphone scannt QR-Code')
|
||||
console.log('[DEBUG] 3. Smartphone öffnet:', window.location.origin)
|
||||
console.log('[DEBUG] 4. Smartphone muss startRegistration aufrufen können')
|
||||
console.log('[DEBUG] 5. Smartphone sendet Credential-Response an:', window.location.origin + '/api/auth/register-passkey')
|
||||
|
||||
credential = await mod.startRegistration({ optionsJSON: pre.options })
|
||||
|
||||
clearTimeout(timeoutWarning)
|
||||
@@ -640,7 +652,8 @@ const handleRegisterWithPasskey = async () => {
|
||||
credentialId: credential?.id,
|
||||
responseType: credential?.response?.constructor?.name,
|
||||
transports: credential?.transports,
|
||||
durationSeconds: Math.round(webauthnDuration / 1000)
|
||||
durationSeconds: Math.round(webauthnDuration / 1000),
|
||||
note: 'Wenn Cross-Device verwendet wurde, sollte die Credential-Response vom Smartphone kommen'
|
||||
})
|
||||
} catch (webauthnError) {
|
||||
const webauthnDuration = Date.now() - webauthnStart
|
||||
|
||||
@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
|
||||
const requestOrigin = getHeader(event, 'origin')
|
||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||
|
||||
console.log('[DEBUG] OPTIONS preflight request received', {
|
||||
origin: requestOrigin,
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||
|
||||
console.log('[DEBUG] ===== OPTIONS preflight for /api/auth/register-passkey-options =====')
|
||||
console.log('[DEBUG] OPTIONS Request Details:', {
|
||||
origin: requestOrigin || 'none',
|
||||
webauthnOrigin,
|
||||
timestamp: new Date().toISOString()
|
||||
userAgent: userAgent?.substring(0, 100) || 'none',
|
||||
ip,
|
||||
timestamp: new Date().toISOString(),
|
||||
note: 'OPTIONS Preflight für Passkey Registration Options'
|
||||
})
|
||||
|
||||
// CORS-Header für Cross-Device Authentication
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { getPreRegistration } from '../../../utils/webauthn-challenges.js'
|
||||
import { generateRegistrationOptions } from '@simplewebauthn/server'
|
||||
import { getWebAuthnConfig } from '../../../utils/webauthn-config.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const registrationId = getRouterParam(event, 'registrationId')
|
||||
const requestOrigin = getHeader(event, 'origin')
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
|
||||
console.log('[DEBUG] ===== GET register-passkey-options/[registrationId] =====')
|
||||
console.log('[DEBUG] Request Details:', {
|
||||
registrationId,
|
||||
origin: requestOrigin,
|
||||
userAgent: userAgent?.substring(0, 100),
|
||||
timestamp: new Date().toISOString(),
|
||||
method: getMethod(event),
|
||||
note: 'Dieser Endpoint wird vom Smartphone aufgerufen, um die Options für Cross-Device zu erhalten'
|
||||
})
|
||||
|
||||
if (!registrationId) {
|
||||
throw createError({ statusCode: 400, message: 'registrationId ist erforderlich' })
|
||||
}
|
||||
|
||||
// Hole Pre-Registration-Daten
|
||||
const pre = getPreRegistration(registrationId)
|
||||
if (!pre) {
|
||||
console.error('[DEBUG] Pre-registration not found', { registrationId })
|
||||
throw createError({ statusCode: 404, message: 'Registrierungs-Session nicht gefunden oder abgelaufen' })
|
||||
}
|
||||
|
||||
const { challenge, userId, name, email } = pre
|
||||
console.log('[DEBUG] Pre-registration found', {
|
||||
userId,
|
||||
email: email?.substring(0, 10) + '...',
|
||||
hasChallenge: !!challenge
|
||||
})
|
||||
|
||||
const { rpId, rpName, origin: webauthnOrigin } = getWebAuthnConfig()
|
||||
|
||||
// Generiere Options neu (mit der gespeicherten Challenge)
|
||||
const options = await generateRegistrationOptions({
|
||||
rpName,
|
||||
rpID: rpId,
|
||||
userID: new TextEncoder().encode(String(userId)),
|
||||
userName: email,
|
||||
userDisplayName: name,
|
||||
attestationType: 'none',
|
||||
authenticatorSelection: {
|
||||
residentKey: 'preferred',
|
||||
userVerification: 'preferred'
|
||||
},
|
||||
timeout: 300000,
|
||||
challenge: challenge // Verwende die gespeicherte Challenge
|
||||
})
|
||||
|
||||
console.log('[DEBUG] Options regenerated for Cross-Device', {
|
||||
hasChallenge: !!options.challenge,
|
||||
challengeMatches: options.challenge === challenge,
|
||||
rpId: options.rp?.id
|
||||
})
|
||||
|
||||
// CORS-Header für Cross-Device
|
||||
const allowedOrigin = requestOrigin || webauthnOrigin
|
||||
if (allowedOrigin) {
|
||||
setHeader(event, 'Access-Control-Allow-Origin', allowedOrigin)
|
||||
setHeader(event, 'Access-Control-Allow-Credentials', 'true')
|
||||
setHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Origin, X-Requested-With')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
registrationId,
|
||||
options
|
||||
}
|
||||
})
|
||||
@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
|
||||
const requestOrigin = getHeader(event, 'origin')
|
||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||
|
||||
console.log('[DEBUG] OPTIONS preflight request received (register-passkey)', {
|
||||
origin: requestOrigin,
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||
|
||||
console.log('[DEBUG] ===== OPTIONS preflight for /api/auth/register-passkey =====')
|
||||
console.log('[DEBUG] OPTIONS Request Details:', {
|
||||
origin: requestOrigin || 'none',
|
||||
webauthnOrigin,
|
||||
timestamp: new Date().toISOString()
|
||||
userAgent: userAgent?.substring(0, 100) || 'none',
|
||||
ip,
|
||||
timestamp: new Date().toISOString(),
|
||||
note: 'OPTIONS Preflight für Cross-Device Passkey - Wenn dieser Request vom Smartphone kommt, sollte der User-Agent Mobile/Android/iPhone enthalten'
|
||||
})
|
||||
|
||||
// CORS-Header für Cross-Device Authentication
|
||||
|
||||
@@ -15,16 +15,27 @@ export default defineEventHandler(async (event) => {
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||
|
||||
console.log('[DEBUG] ===== register-passkey request received =====')
|
||||
console.log('')
|
||||
console.log('='.repeat(80))
|
||||
console.log('[DEBUG] 🔑 ===== PASSKEY REGISTRATION REQUEST RECEIVED =====')
|
||||
console.log('='.repeat(80))
|
||||
console.log('[DEBUG] Request Details:', {
|
||||
origin: requestOrigin,
|
||||
origin: requestOrigin || 'none',
|
||||
webauthnOrigin,
|
||||
userAgent: userAgent?.substring(0, 150),
|
||||
userAgent: userAgent?.substring(0, 150) || 'none',
|
||||
timestamp: new Date().toISOString(),
|
||||
method: getMethod(event),
|
||||
ip: getClientIp(event),
|
||||
note: 'Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde'
|
||||
url: getRequestURL(event).href,
|
||||
path: getRequestURL(event).pathname
|
||||
})
|
||||
console.log('[DEBUG] ⚠️ WICHTIG: Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde')
|
||||
console.log('[DEBUG] ⚠️ Wenn dieser Request NICHT im Log erscheint, bedeutet das:')
|
||||
console.log('[DEBUG] 1. Das Smartphone konnte die Website nicht erreichen')
|
||||
console.log('[DEBUG] 2. Oder startRegistration auf dem Smartphone ist fehlgeschlagen')
|
||||
console.log('[DEBUG] 3. Oder die Credential-Response wurde nicht gesendet')
|
||||
console.log('='.repeat(80))
|
||||
console.log('')
|
||||
console.log('[DEBUG] User-Agent Analysis:', {
|
||||
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent || ''),
|
||||
isChrome: /Chrome/i.test(userAgent || ''),
|
||||
|
||||
41
server/middleware/log-requests.js
Normal file
41
server/middleware/log-requests.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Globales Request-Logging für Debugging
|
||||
* Loggt alle Requests, besonders Passkey-relevante Endpoints
|
||||
*/
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const url = getRequestURL(event)
|
||||
const method = getMethod(event)
|
||||
const path = url.pathname
|
||||
const origin = getHeader(event, 'origin')
|
||||
const userAgent = getHeader(event, 'user-agent')
|
||||
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||
|
||||
// Logge nur Passkey-relevante Endpoints (um Logs nicht zu überfluten)
|
||||
const passkeyEndpoints = [
|
||||
'/api/auth/register-passkey',
|
||||
'/api/auth/register-passkey-options',
|
||||
'/api/auth/passkeys/registration-options',
|
||||
'/api/auth/passkeys/register',
|
||||
'/api/auth/passkeys/authentication-options',
|
||||
'/api/auth/passkeys/login',
|
||||
'/api/auth/passkeys/recovery'
|
||||
]
|
||||
|
||||
const isPasskeyEndpoint = passkeyEndpoints.some(ep => path.startsWith(ep))
|
||||
|
||||
if (isPasskeyEndpoint) {
|
||||
const timestamp = new Date().toISOString()
|
||||
console.log(`[REQUEST] ${timestamp} ${method} ${path}`)
|
||||
console.log(`[REQUEST] Origin: ${origin || 'none'}`)
|
||||
console.log(`[REQUEST] IP: ${ip}`)
|
||||
console.log(`[REQUEST] User-Agent: ${userAgent?.substring(0, 100) || 'none'}`)
|
||||
|
||||
// Spezielle Logs für Cross-Device
|
||||
if (path.includes('register-passkey')) {
|
||||
console.log(`[REQUEST] 🔑 PASSKEY REQUEST - ${method} ${path}`)
|
||||
console.log(`[REQUEST] ⚠️ Wenn dieser Request vom Smartphone kommt, sollte der User-Agent Mobile/Android/iPhone enthalten`)
|
||||
console.log(`[REQUEST] ⚠️ Wenn dieser Request NICHT kommt, erreicht das Smartphone den Server nicht`)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -59,4 +59,12 @@ export function consumePreRegistration(registrationId) {
|
||||
return v.payload || null
|
||||
}
|
||||
|
||||
export function getPreRegistration(registrationId) {
|
||||
cleanup(preRegChallenges)
|
||||
const key = String(registrationId)
|
||||
const v = preRegChallenges.get(key)
|
||||
if (!v) return null
|
||||
return v.payload || null
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user