From 48f8b46e57fa32df022bba0dca345ab503b66b37 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 15 May 2026 13:30:15 +0200 Subject: [PATCH] =?UTF-8?q?Erweitere=20die=20Passkey-Registrierung=20um=20?= =?UTF-8?q?Unterst=C3=BCtzung=20f=C3=BCr=20bevorzugte=20Authentifikatortyp?= =?UTF-8?q?en=20und=20verbessere=20die=20Fehlerbehandlung.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/mitgliederbereich/profil.vue | 56 +++++++++++++++++-- .../passkeys/registration-options.post.js | 6 ++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/pages/mitgliederbereich/profil.vue b/pages/mitgliederbereich/profil.vue index d4f419a..4f8b233 100644 --- a/pages/mitgliederbereich/profil.vue +++ b/pages/mitgliederbereich/profil.vue @@ -398,10 +398,53 @@ const addPasskey = async () => { passkeyLoading.value = true try { const name = window.prompt('Name für den Passkey (z.B. "iPhone", "Laptop"):', 'Passkey') || 'Passkey' - const res = await $fetch('/api/auth/passkeys/registration-options', { method: 'POST' }) const mod = await import('@simplewebauthn/browser') - // @simplewebauthn/browser v13+ erwartet { optionsJSON: options } - const credential = await mod.startRegistration({ optionsJSON: res.options }) + + const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent || '' : '' + const isFirefox = /Firefox\//i.test(userAgent) + + let platformAvailable = true + if (typeof mod.platformAuthenticatorIsAvailable === 'function') { + try { + platformAvailable = await mod.platformAuthenticatorIsAvailable() + } catch { + platformAvailable = true + } + } + + const preferenceChain = [] + if (isFirefox) { + // Firefox kann unter Linux Passkeys browserseitig verwalten, auch wenn kein + // klassischer Platform-Authenticator erkannt wird. + preferenceChain.push('localDevice', 'remoteDevice') + } else if (!platformAvailable) { + preferenceChain.push('remoteDevice', 'localDevice') + } else { + preferenceChain.push('localDevice') + } + + let credential = null + let lastError = null + + for (const preferredAuthenticatorType of preferenceChain) { + try { + const res = await $fetch('/api/auth/passkeys/registration-options', { + method: 'POST', + body: { preferredAuthenticatorType } + }) + + // @simplewebauthn/browser v13+ erwartet { optionsJSON: options } + credential = await mod.startRegistration({ optionsJSON: res.options }) + break + } catch (err) { + lastError = err + } + } + + if (!credential) { + throw lastError || new Error('Passkey konnte nicht erstellt werden.') + } + await $fetch('/api/auth/passkeys/register', { method: 'POST', body: { credential, name } @@ -409,7 +452,12 @@ const addPasskey = async () => { await loadPasskeys() successMessage.value = 'Passkey hinzugefügt.' } catch (e) { - passkeyError.value = e?.data?.message || e?.message || 'Passkey konnte nicht hinzugefügt werden.' + const rawMessage = e?.data?.message || e?.message || '' + if (String(rawMessage).includes('NotAllowedError')) { + passkeyError.value = 'Passkey-Erstellung abgebrochen oder nicht erlaubt. Unter Firefox/Linux ggf. Smartphone-Passkey oder Sicherheitsschlüssel verwenden.' + } else { + passkeyError.value = rawMessage || 'Passkey konnte nicht hinzugefügt werden.' + } } finally { passkeyLoading.value = false } diff --git a/server/api/auth/passkeys/registration-options.post.js b/server/api/auth/passkeys/registration-options.post.js index 284dce8..0a32121 100644 --- a/server/api/auth/passkeys/registration-options.post.js +++ b/server/api/auth/passkeys/registration-options.post.js @@ -18,6 +18,11 @@ export default defineEventHandler(async (event) => { return { success: true } } + const body = await readBody(event) + const preferredAuthenticatorType = ['securityKey', 'localDevice', 'remoteDevice'].includes(body?.preferredAuthenticatorType) + ? body.preferredAuthenticatorType + : undefined + const token = getCookie(event, 'auth_token') const user = token ? await getUserFromToken(token) : null @@ -64,6 +69,7 @@ export default defineEventHandler(async (event) => { // authenticatorAttachment weglassen = beide Typen erlauben (platform + cross-platform) }, excludeCredentials, + preferredAuthenticatorType, // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) timeout: 300000 })