53 lines
1.8 KiB
JavaScript
53 lines
1.8 KiB
JavaScript
import { generateRegistrationOptions } from '@simplewebauthn/server'
|
|
import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js'
|
|
import { getWebAuthnConfig } from '../../../utils/webauthn-config.js'
|
|
import { setRegistrationChallenge } from '../../../utils/webauthn-challenges.js'
|
|
import { writeAuditLog } from '../../../utils/audit-log.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const token = getCookie(event, 'auth_token')
|
|
const user = token ? await getUserFromToken(token) : null
|
|
|
|
if (!user) {
|
|
throw createError({ statusCode: 401, statusMessage: 'Nicht authentifiziert' })
|
|
}
|
|
|
|
// Mindestens für Admin/Vorstand anbieten (und auch für Mitglieder ok)
|
|
if (!hasAnyRole(user, 'admin', 'vorstand', 'mitglied', 'newsletter')) {
|
|
throw createError({ statusCode: 403, statusMessage: 'Keine Berechtigung' })
|
|
}
|
|
|
|
const { rpId, rpName } = getWebAuthnConfig()
|
|
|
|
const existing = Array.isArray(user.passkeys) ? user.passkeys : []
|
|
const excludeCredentials = existing
|
|
.filter(pk => pk && pk.credentialId)
|
|
.map(pk => ({
|
|
id: pk.credentialId,
|
|
type: 'public-key',
|
|
transports: pk.transports || undefined
|
|
}))
|
|
|
|
const options = await generateRegistrationOptions({
|
|
rpName,
|
|
rpID: rpId,
|
|
// @simplewebauthn/server erwartet inzwischen Uint8Array/Buffer statt String
|
|
userID: new TextEncoder().encode(String(user.id)),
|
|
userName: user.email,
|
|
// Keine Attestation-Daten speichern
|
|
attestationType: 'none',
|
|
authenticatorSelection: {
|
|
residentKey: 'preferred',
|
|
userVerification: 'preferred'
|
|
},
|
|
excludeCredentials
|
|
})
|
|
|
|
setRegistrationChallenge(user.id, options.challenge)
|
|
await writeAuditLog('auth.passkey.registration.options', { userId: user.id })
|
|
|
|
return { success: true, options }
|
|
})
|
|
|
|
|