Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 55s
Update deploy-production.sh to include comprehensive build validation checks, ensuring critical files and directories are present before proceeding. Enhance the registrieren.vue component to conditionally display the window origin and improve debug logging for the registration process. Additionally, add debug information in the register-passkey-options API to capture request details, including environment and process ID, for better troubleshooting capabilities.
619 lines
23 KiB
Vue
619 lines
23 KiB
Vue
<template>
|
|
<div class="min-h-full flex items-center justify-center py-16 px-4 sm:px-6 lg:px-8 bg-gray-50">
|
|
<div class="max-w-md w-full space-y-8">
|
|
<div class="text-center">
|
|
<h2 class="text-3xl font-display font-bold text-gray-900">
|
|
Registrierung
|
|
</h2>
|
|
<p class="mt-2 text-sm text-gray-600">
|
|
Beantragen Sie Zugang zum Mitgliederbereich
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-lg p-8">
|
|
<!-- Debug: Sichtbarer Test -->
|
|
<div class="mb-4 p-3 bg-yellow-200 border-2 border-yellow-500 rounded-lg text-sm font-mono">
|
|
<div class="font-bold text-yellow-900">🔍 DEBUG MODE AKTIV</div>
|
|
<div class="mt-1 text-yellow-800">Komponente: registrieren.vue geladen</div>
|
|
<div class="mt-1 text-yellow-800">Passkey-Support: {{ isPasskeySupported ? 'JA' : 'NEIN' }}</div>
|
|
<div class="mt-1 text-yellow-800">Use Passkey: {{ usePasskey ? 'JA' : 'NEIN' }}</div>
|
|
</div>
|
|
|
|
<form
|
|
class="space-y-6"
|
|
@submit.prevent="handleFormSubmit"
|
|
>
|
|
<!-- Registration Mode -->
|
|
<div class="flex items-center justify-between bg-gray-50 border border-gray-200 rounded-lg p-3">
|
|
<div class="text-sm text-gray-700">
|
|
<div class="font-medium">Registrierungsmethode</div>
|
|
<div class="text-xs text-gray-600">Passkey = Anmeldung ohne Passwort (z.B. FaceID/TouchID/Windows Hello)</div>
|
|
<div v-if="!isPasskeySupported" class="text-xs text-red-700 mt-1">
|
|
Passkeys aktuell nicht verfügbar: {{ passkeySupportReason || 'Bitte HTTPS/aktuellen Browser verwenden.' }}
|
|
</div>
|
|
</div>
|
|
<label class="flex items-center gap-2 text-sm font-medium text-gray-800">
|
|
<input v-model="usePasskey" type="checkbox" class="h-4 w-4" :disabled="isLoading || !isPasskeySupported">
|
|
Mit Passkey
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Name -->
|
|
<div>
|
|
<label
|
|
for="name"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
Vollständiger Name
|
|
</label>
|
|
<input
|
|
id="name"
|
|
v-model="formData.name"
|
|
type="text"
|
|
required
|
|
autocomplete="name"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
|
placeholder="Max Mustermann"
|
|
>
|
|
</div>
|
|
|
|
<!-- Email -->
|
|
<div>
|
|
<label
|
|
for="email"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
E-Mail-Adresse
|
|
</label>
|
|
<input
|
|
id="email"
|
|
v-model="formData.email"
|
|
type="email"
|
|
required
|
|
autocomplete="email"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
|
placeholder="ihre-email@example.com"
|
|
>
|
|
</div>
|
|
|
|
<!-- Phone -->
|
|
<div>
|
|
<label
|
|
for="phone"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
Telefonnummer (optional)
|
|
</label>
|
|
<input
|
|
id="phone"
|
|
v-model="formData.phone"
|
|
type="tel"
|
|
autocomplete="tel"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
|
placeholder="069-12345678"
|
|
>
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<div v-if="!usePasskey || setPasswordForPasskey">
|
|
<label
|
|
for="password"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
Passwort <span v-if="usePasskey" class="text-xs text-gray-500">(Fallback, optional)</span>
|
|
</label>
|
|
<input
|
|
id="password"
|
|
v-model="formData.password"
|
|
type="password"
|
|
:required="!usePasskey"
|
|
autocomplete="new-password"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
|
placeholder="••••••••"
|
|
>
|
|
<p class="mt-1 text-xs text-gray-500">
|
|
Mindestens 8 Zeichen
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Confirm Password -->
|
|
<div v-if="!usePasskey || setPasswordForPasskey">
|
|
<label
|
|
for="confirmPassword"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
Passwort bestätigen <span v-if="usePasskey" class="text-xs text-gray-500">(Fallback)</span>
|
|
</label>
|
|
<input
|
|
id="confirmPassword"
|
|
v-model="formData.confirmPassword"
|
|
type="password"
|
|
:required="!usePasskey"
|
|
autocomplete="new-password"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
|
placeholder="••••••••"
|
|
>
|
|
</div>
|
|
|
|
<!-- Optional password toggle for passkey users -->
|
|
<div v-if="usePasskey" class="flex items-center gap-2 text-sm text-gray-700">
|
|
<input
|
|
v-model="setPasswordForPasskey"
|
|
type="checkbox"
|
|
class="h-4 w-4"
|
|
:disabled="isLoading"
|
|
>
|
|
<span>Zusätzlich ein Passwort als Fallback setzen (z.B. für Firefox/Linux)</span>
|
|
</div>
|
|
|
|
<!-- Error Message -->
|
|
<div
|
|
v-if="errorMessage"
|
|
class="bg-red-50 border border-red-200 rounded-lg p-4"
|
|
>
|
|
<p class="text-sm text-red-800 flex items-center">
|
|
<AlertCircle
|
|
:size="18"
|
|
class="mr-2"
|
|
/>
|
|
{{ errorMessage }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Success Message -->
|
|
<div
|
|
v-if="successMessage"
|
|
class="bg-green-50 border border-green-200 rounded-lg p-4"
|
|
>
|
|
<p class="text-sm text-green-800 flex items-center">
|
|
<Check
|
|
:size="18"
|
|
class="mr-2"
|
|
/>
|
|
{{ successMessage }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Debug Info (nur bei Passkey-Registrierung) -->
|
|
<div
|
|
v-if="usePasskey && showDebugInfo && debugChallenge"
|
|
class="bg-blue-50 border border-blue-200 rounded-lg p-4 text-xs"
|
|
>
|
|
<div class="font-semibold text-blue-900 mb-2">🔍 Debug-Informationen (QR-Code):</div>
|
|
<div class="space-y-1 text-blue-800">
|
|
<div><strong>Challenge:</strong> <code class="bg-blue-100 px-1 rounded">{{ debugChallenge.substring(0, 40) }}...</code></div>
|
|
<div><strong>RP-ID:</strong> <code class="bg-blue-100 px-1 rounded">{{ debugRpId }}</code></div>
|
|
<div><strong>Origin:</strong> <code class="bg-blue-100 px-1 rounded">{{ typeof window !== 'undefined' ? window.location.origin : 'N/A (SSR)' }}</code></div>
|
|
<div class="mt-2 text-blue-700">
|
|
<strong>Hinweis:</strong> Der QR-Code wird vom Browser generiert.
|
|
Prüfe in der Browser-Konsole (F12) für vollständige Debug-Ausgaben.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<button
|
|
type="submit"
|
|
:disabled="isLoading"
|
|
class="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
|
>
|
|
<Loader2
|
|
v-if="isLoading"
|
|
:size="20"
|
|
class="mr-2 animate-spin"
|
|
/>
|
|
<span>{{ isLoading ? 'Wird gesendet...' : (usePasskey ? 'Mit Passkey registrieren' : 'Registrierung beantragen') }}</span>
|
|
</button>
|
|
|
|
<!-- Back to Login -->
|
|
<div class="text-center">
|
|
<NuxtLink
|
|
to="/login"
|
|
class="text-sm text-primary-600 hover:text-primary-700 font-medium"
|
|
>
|
|
Bereits registriert? Zum Login
|
|
</NuxtLink>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Info Box -->
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
<p class="text-sm text-yellow-800">
|
|
<Info
|
|
:size="16"
|
|
class="inline mr-1"
|
|
/>
|
|
<strong>Hinweis:</strong> Ihre Registrierung muss vom Vorstand freigegeben werden.
|
|
Sie erhalten eine E-Mail, sobald Ihr Zugang aktiviert wurde.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, ref } from 'vue'
|
|
import { AlertCircle, Check, Loader2, Info } from 'lucide-vue-next'
|
|
|
|
// Debug: Test-Ausgabe beim Laden der Komponente
|
|
console.log('[DEBUG] ===== registrieren.vue component loaded =====')
|
|
console.log('[DEBUG] Component setup started')
|
|
|
|
const formData = ref({
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
password: '',
|
|
confirmPassword: ''
|
|
})
|
|
|
|
const isLoading = ref(false)
|
|
const errorMessage = ref('')
|
|
const successMessage = ref('')
|
|
const usePasskey = ref(false)
|
|
const isPasskeySupported = ref(false)
|
|
const passkeySupportReason = ref('')
|
|
const setPasswordForPasskey = ref(true)
|
|
|
|
console.log('[DEBUG] Component refs initialized')
|
|
|
|
// Debug: Log beim Form-Submit
|
|
const handleFormSubmit = (event) => {
|
|
console.log('[DEBUG] ===== FORM SUBMIT EVENT =====')
|
|
console.log('[DEBUG] Form submitted', {
|
|
usePasskey: usePasskey.value,
|
|
name: formData.value.name,
|
|
email: formData.value.email
|
|
})
|
|
|
|
if (usePasskey.value) {
|
|
console.log('[DEBUG] Calling handleRegisterWithPasskey...')
|
|
handleRegisterWithPasskey()
|
|
} else {
|
|
console.log('[DEBUG] Calling handleRegister...')
|
|
handleRegister()
|
|
}
|
|
}
|
|
const showDebugInfo = ref(false)
|
|
const debugChallenge = ref('')
|
|
const debugRpId = ref('')
|
|
|
|
onMounted(() => {
|
|
try {
|
|
const hasPKC = typeof window !== 'undefined' && typeof window.PublicKeyCredential !== 'undefined'
|
|
const secure = typeof window !== 'undefined' && !!window.isSecureContext
|
|
isPasskeySupported.value = !!(hasPKC && secure)
|
|
if (!secure) passkeySupportReason.value = 'Kein Secure Context (HTTPS oder localhost erforderlich).'
|
|
else if (!hasPKC) passkeySupportReason.value = 'Browser unterstützt WebAuthn/Passkeys nicht.'
|
|
else passkeySupportReason.value = ''
|
|
} catch {
|
|
isPasskeySupported.value = false
|
|
passkeySupportReason.value = 'Passkey-Check fehlgeschlagen.'
|
|
}
|
|
})
|
|
|
|
const handleRegister = async () => {
|
|
errorMessage.value = ''
|
|
successMessage.value = ''
|
|
|
|
// Validate
|
|
if (formData.value.password.length < 8) {
|
|
errorMessage.value = 'Das Passwort muss mindestens 8 Zeichen lang sein.'
|
|
return
|
|
}
|
|
|
|
if (formData.value.password !== formData.value.confirmPassword) {
|
|
errorMessage.value = 'Die Passwörter stimmen nicht überein.'
|
|
return
|
|
}
|
|
|
|
isLoading.value = true
|
|
|
|
try {
|
|
const response = await $fetch('/api/auth/register', {
|
|
method: 'POST',
|
|
body: {
|
|
name: formData.value.name,
|
|
email: formData.value.email,
|
|
phone: formData.value.phone,
|
|
password: formData.value.password
|
|
}
|
|
})
|
|
|
|
if (response.success) {
|
|
successMessage.value = 'Registrierung erfolgreich! Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.'
|
|
|
|
// Reset form
|
|
formData.value = {
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
password: '',
|
|
confirmPassword: ''
|
|
}
|
|
|
|
// Redirect after 3 seconds
|
|
setTimeout(() => {
|
|
navigateTo('/login')
|
|
}, 3000)
|
|
}
|
|
} catch (error) {
|
|
errorMessage.value = error.data?.message || 'Registrierung fehlgeschlagen. Bitte versuchen Sie es später erneut.'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleRegisterWithPasskey = async () => {
|
|
console.log('[DEBUG] ===== handleRegisterWithPasskey CALLED =====')
|
|
console.log('[DEBUG] Function entry point reached')
|
|
|
|
errorMessage.value = ''
|
|
successMessage.value = ''
|
|
|
|
console.log('[DEBUG] Checking passkey support:', {
|
|
isPasskeySupported: isPasskeySupported.value,
|
|
passkeySupportReason: passkeySupportReason.value
|
|
})
|
|
|
|
if (!isPasskeySupported.value) {
|
|
console.warn('[DEBUG] Passkey not supported, returning early')
|
|
errorMessage.value = passkeySupportReason.value || 'Passkeys sind in diesem Browser/unter dieser URL nicht verfügbar (HTTPS erforderlich).'
|
|
return
|
|
}
|
|
|
|
if (!formData.value.name || !formData.value.email) {
|
|
errorMessage.value = 'Bitte Name und E-Mail ausfüllen.'
|
|
return
|
|
}
|
|
|
|
// Passwort-Fallback optional validieren
|
|
if (setPasswordForPasskey.value) {
|
|
if (formData.value.password.length < 8) {
|
|
errorMessage.value = 'Das Passwort muss mindestens 8 Zeichen lang sein.'
|
|
return
|
|
}
|
|
if (formData.value.password !== formData.value.confirmPassword) {
|
|
errorMessage.value = 'Die Passwörter stimmen nicht überein.'
|
|
return
|
|
}
|
|
} else {
|
|
// Nicht mitschicken
|
|
formData.value.password = ''
|
|
formData.value.confirmPassword = ''
|
|
}
|
|
|
|
isLoading.value = true
|
|
|
|
console.log('[DEBUG] Passkey-Registrierung gestartet', {
|
|
name: formData.value.name,
|
|
email: formData.value.email,
|
|
hasPassword: !!formData.value.password,
|
|
isPasskeySupported: isPasskeySupported.value,
|
|
userAgent: navigator.userAgent,
|
|
origin: window.location.origin,
|
|
isSecureContext: window.isSecureContext
|
|
})
|
|
|
|
try {
|
|
console.log('[DEBUG] Requesting registration options from server...')
|
|
const requestStart = Date.now()
|
|
|
|
const pre = await $fetch('/api/auth/register-passkey-options', {
|
|
method: 'POST',
|
|
body: {
|
|
name: formData.value.name,
|
|
email: formData.value.email,
|
|
phone: formData.value.phone
|
|
}
|
|
})
|
|
|
|
const requestDuration = Date.now() - requestStart
|
|
console.log(`[DEBUG] Options received (${requestDuration}ms)`, {
|
|
success: pre.success,
|
|
hasOptions: !!pre.options,
|
|
hasRegistrationId: !!pre.registrationId,
|
|
registrationId: pre.registrationId
|
|
})
|
|
|
|
if (!pre.success || !pre.options) {
|
|
console.error('[DEBUG] Invalid server response:', pre)
|
|
throw new Error('Ungültige Antwort vom Server')
|
|
}
|
|
|
|
// Debug: Prüfe Options-Struktur
|
|
console.log('[DEBUG] Options structure:', {
|
|
hasChallenge: !!pre.options?.challenge,
|
|
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',
|
|
userName: pre.options?.user?.name,
|
|
timeout: pre.options?.timeout,
|
|
pubKeyCredParams: pre.options?.pubKeyCredParams?.length,
|
|
authenticatorSelection: pre.options?.authenticatorSelection,
|
|
excludeCredentials: pre.options?.excludeCredentials?.length || 0
|
|
})
|
|
|
|
if (!pre.options || !pre.options.challenge) {
|
|
console.error('[DEBUG] Options missing challenge:', JSON.stringify(pre.options, null, 2))
|
|
throw new Error('Ungültige WebAuthn-Options vom Server')
|
|
}
|
|
|
|
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))
|
|
console.log('[DEBUG] Options summary for startRegistration:', {
|
|
challenge: pre.options?.challenge ? 'present' : 'missing',
|
|
challengeValue: pre.options?.challenge ? pre.options.challenge.substring(0, 20) + '...' : 'missing',
|
|
rp: pre.options?.rp,
|
|
rpId: pre.options?.rp?.id,
|
|
rpName: pre.options?.rp?.name,
|
|
user: pre.options?.user,
|
|
userName: pre.options?.user?.name,
|
|
userDisplayName: pre.options?.user?.displayName,
|
|
timeout: pre.options?.timeout,
|
|
timeoutSeconds: pre.options?.timeout ? Math.round(pre.options.timeout / 1000) : 'default',
|
|
pubKeyCredParams: pre.options?.pubKeyCredParams?.length,
|
|
authenticatorSelection: pre.options?.authenticatorSelection,
|
|
excludeCredentials: pre.options?.excludeCredentials?.length || 0
|
|
})
|
|
|
|
// Vollständige Options für QR-Code-Debug (wird im QR-Code kodiert)
|
|
console.log('[DEBUG] Full options object (encoded in QR code for Cross-Device):', JSON.stringify(pre.options, null, 2))
|
|
|
|
// Prüfe, ob Cross-Device-Authentifizierung verwendet wird
|
|
console.log('[DEBUG] Cross-Device Info (QR-Code sollte zu dieser URL führen):', {
|
|
isSecureContext: window.isSecureContext,
|
|
origin: window.location.origin,
|
|
protocol: window.location.protocol,
|
|
hostname: window.location.hostname,
|
|
port: window.location.port || 'default (443 for HTTPS)',
|
|
fullUrl: window.location.href,
|
|
// Der QR-Code sollte zur aktuellen Origin führen
|
|
qrCodeShouldPointTo: window.location.origin,
|
|
// Prüfe, ob die Options die richtige Origin enthalten
|
|
optionsRpId: pre.options?.rp?.id,
|
|
optionsMatchesOrigin: pre.options?.rp?.id === window.location.hostname
|
|
})
|
|
|
|
// QR-Code-Debug: Die Challenge ist Teil der WebAuthn-Request
|
|
// Der Browser generiert automatisch einen QR-Code für Cross-Device
|
|
debugChallenge.value = pre.options?.challenge || ''
|
|
debugRpId.value = pre.options?.rp?.id || ''
|
|
showDebugInfo.value = true
|
|
|
|
console.log('[DEBUG] QR-Code Info (for Cross-Device):', {
|
|
challenge: pre.options?.challenge,
|
|
challengeLength: pre.options?.challenge?.length,
|
|
rpId: pre.options?.rp?.id,
|
|
expectedOrigin: window.location.origin,
|
|
currentURL: window.location.href,
|
|
isHTTPS: window.location.protocol === 'https:',
|
|
note: 'Der QR-Code wird vom Browser generiert und enthält die Challenge + Server-Info'
|
|
})
|
|
|
|
// Prüfe, ob die Origin korrekt ist
|
|
if (window.location.origin.includes(':3100')) {
|
|
console.error('[DEBUG] WARNING: Current origin contains port 3100!', window.location.origin)
|
|
console.error('[DEBUG] This might cause Cross-Device authentication to fail.')
|
|
}
|
|
|
|
const webauthnStart = Date.now()
|
|
|
|
const mod = await import('@simplewebauthn/browser')
|
|
// startRegistration erwartet die Options direkt
|
|
// @simplewebauthn/browser v13+ erwartet die Options direkt
|
|
let credential
|
|
try {
|
|
// Timeout-Warnung nach 2 Minuten
|
|
const timeoutWarning = setTimeout(() => {
|
|
console.warn('[DEBUG] startRegistration still waiting after 2 minutes. This might be a Cross-Device timeout.')
|
|
console.warn('[DEBUG] Make sure your smartphone can reach the server and CORS is configured correctly.')
|
|
console.warn('[DEBUG] Current origin:', typeof window !== 'undefined' ? window.location.origin : 'N/A')
|
|
console.warn('[DEBUG] Challenge:', pre.options?.challenge)
|
|
}, 120000)
|
|
|
|
// Stelle sicher, dass die Options korrekt formatiert sind
|
|
// @simplewebauthn/browser v13+ erwartet die Options direkt als Objekt
|
|
const registrationOptions = {
|
|
challenge: pre.options.challenge,
|
|
rp: pre.options.rp,
|
|
user: pre.options.user,
|
|
pubKeyCredParams: pre.options.pubKeyCredParams,
|
|
timeout: pre.options.timeout,
|
|
attestation: pre.options.attestation || 'none',
|
|
excludeCredentials: pre.options.excludeCredentials || [],
|
|
authenticatorSelection: pre.options.authenticatorSelection,
|
|
extensions: pre.options.extensions || {}
|
|
}
|
|
|
|
console.log('[DEBUG] startRegistration called - QR-Code should appear now (if Cross-Device)')
|
|
console.log('[DEBUG] Registration options structure:', {
|
|
hasChallenge: !!registrationOptions.challenge,
|
|
hasRp: !!registrationOptions.rp,
|
|
hasUser: !!registrationOptions.user,
|
|
timeout: registrationOptions.timeout
|
|
})
|
|
|
|
credential = await mod.startRegistration(registrationOptions)
|
|
|
|
clearTimeout(timeoutWarning)
|
|
|
|
const webauthnDuration = Date.now() - webauthnStart
|
|
console.log(`[DEBUG] startRegistration completed (${webauthnDuration}ms)`, {
|
|
hasCredential: !!credential,
|
|
credentialId: credential?.id,
|
|
responseType: credential?.response?.constructor?.name,
|
|
transports: credential?.transports,
|
|
durationSeconds: Math.round(webauthnDuration / 1000)
|
|
})
|
|
} catch (webauthnError) {
|
|
const webauthnDuration = Date.now() - webauthnStart
|
|
console.error(`[DEBUG] WebAuthn startRegistration failed (${webauthnDuration}ms / ${Math.round(webauthnDuration / 1000)}s):`, {
|
|
error: webauthnError,
|
|
message: webauthnError?.message,
|
|
name: webauthnError?.name,
|
|
code: webauthnError?.code,
|
|
stack: webauthnError?.stack
|
|
})
|
|
|
|
// Spezifische Fehlermeldungen für häufige Probleme
|
|
if (webauthnError?.message?.includes('timeout') || webauthnDuration > 290000) {
|
|
throw new Error('Passkey-Registrierung: Timeout. Bitte stellen Sie sicher, dass Ihr Smartphone eine Internetverbindung hat und die Website über HTTPS erreichbar ist.')
|
|
} else if (webauthnError?.message?.includes('NotAllowedError') || webauthnError?.name === 'NotAllowedError') {
|
|
throw new Error('Passkey-Registrierung abgebrochen oder nicht erlaubt.')
|
|
} else {
|
|
throw new Error('Passkey-Registrierung fehlgeschlagen: ' + (webauthnError?.message || 'Unbekannter Fehler'))
|
|
}
|
|
}
|
|
|
|
console.log('[DEBUG] Sending credential to server...')
|
|
const verifyStart = Date.now()
|
|
|
|
const response = await $fetch('/api/auth/register-passkey', {
|
|
method: 'POST',
|
|
body: {
|
|
registrationId: pre.registrationId,
|
|
credential,
|
|
password: setPasswordForPasskey.value ? formData.value.password : undefined
|
|
}
|
|
})
|
|
|
|
const verifyDuration = Date.now() - verifyStart
|
|
console.log(`[DEBUG] Server verification completed (${verifyDuration}ms)`, {
|
|
success: response.success,
|
|
message: response.message
|
|
})
|
|
|
|
if (response.success) {
|
|
console.log('[DEBUG] Registration successful!')
|
|
successMessage.value = 'Registrierung erfolgreich! Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.'
|
|
formData.value = { name: '', email: '', phone: '', password: '', confirmPassword: '' }
|
|
setTimeout(() => navigateTo('/login'), 3000)
|
|
} else {
|
|
console.warn('[DEBUG] Registration response indicates failure:', response)
|
|
}
|
|
} catch (error) {
|
|
console.error('[DEBUG] Registration error:', {
|
|
error,
|
|
message: error?.message,
|
|
data: error?.data,
|
|
statusCode: error?.statusCode,
|
|
statusMessage: error?.statusMessage
|
|
})
|
|
errorMessage.value = error.data?.message || error?.message || 'Registrierung mit Passkey fehlgeschlagen.'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
useHead({
|
|
title: 'Registrierung - Harheimer TC',
|
|
})
|
|
</script>
|
|
|