Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 45s
Enhance authentication options in the server API by adding CORS headers to support cross-device authentication. Implement handling for preflight OPTIONS requests and increase timeout for registration and authentication processes to 5 minutes, improving user experience and compatibility across devices.
107 lines
3.4 KiB
JavaScript
107 lines
3.4 KiB
JavaScript
import { verifyRegistrationResponse } from '@simplewebauthn/server'
|
|
import { getUserFromToken, readUsers, writeUsers } from '../../../utils/auth.js'
|
|
import { getWebAuthnConfig } from '../../../utils/webauthn-config.js'
|
|
import { clearRegistrationChallenge, getRegistrationChallenge } from '../../../utils/webauthn-challenges.js'
|
|
import { toBase64Url } from '../../../utils/webauthn-encoding.js'
|
|
import { writeAuditLog } from '../../../utils/audit-log.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
// CORS-Header für Cross-Device Authentication
|
|
const origin = getHeader(event, 'origin')
|
|
if (origin) {
|
|
setHeader(event, 'Access-Control-Allow-Origin', origin)
|
|
setHeader(event, 'Access-Control-Allow-Credentials', 'true')
|
|
setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS')
|
|
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
}
|
|
|
|
if (getMethod(event) === 'OPTIONS') {
|
|
return { success: true }
|
|
}
|
|
|
|
const token = getCookie(event, 'auth_token')
|
|
const user = token ? await getUserFromToken(token) : null
|
|
|
|
if (!user) {
|
|
throw createError({ statusCode: 401, statusMessage: 'Nicht authentifiziert' })
|
|
}
|
|
|
|
const body = await readBody(event)
|
|
const response = body?.credential
|
|
if (!response) {
|
|
throw createError({ statusCode: 400, statusMessage: 'Credential fehlt' })
|
|
}
|
|
|
|
const expectedChallenge = getRegistrationChallenge(user.id)
|
|
if (!expectedChallenge) {
|
|
throw createError({ statusCode: 400, statusMessage: 'Registrierungs-Session abgelaufen. Bitte erneut versuchen.' })
|
|
}
|
|
|
|
const { origin, rpId, requireUV } = getWebAuthnConfig()
|
|
|
|
let verification
|
|
try {
|
|
verification = await verifyRegistrationResponse({
|
|
response,
|
|
expectedChallenge,
|
|
expectedOrigin: origin,
|
|
expectedRPID: rpId,
|
|
requireUserVerification: requireUV
|
|
})
|
|
} finally {
|
|
clearRegistrationChallenge(user.id)
|
|
}
|
|
|
|
const { verified, registrationInfo } = verification
|
|
if (!verified || !registrationInfo) {
|
|
await writeAuditLog('auth.passkey.registration.failed', { userId: user.id })
|
|
throw createError({ statusCode: 400, statusMessage: 'Passkey-Registrierung fehlgeschlagen' })
|
|
}
|
|
|
|
const {
|
|
credentialID,
|
|
credentialPublicKey,
|
|
counter,
|
|
credentialDeviceType,
|
|
credentialBackedUp
|
|
} = registrationInfo
|
|
|
|
const credentialId = toBase64Url(credentialID)
|
|
const publicKey = toBase64Url(credentialPublicKey)
|
|
|
|
const users = await readUsers()
|
|
const idx = users.findIndex(u => u.id === user.id)
|
|
if (idx === -1) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Benutzer nicht gefunden' })
|
|
}
|
|
|
|
const u = users[idx]
|
|
if (!Array.isArray(u.passkeys)) u.passkeys = []
|
|
|
|
// Duplikate verhindern
|
|
if (u.passkeys.some(pk => pk.credentialId === credentialId)) {
|
|
return { success: true, message: 'Passkey ist bereits registriert.' }
|
|
}
|
|
|
|
u.passkeys.push({
|
|
id: `${Date.now()}`,
|
|
credentialId,
|
|
publicKey,
|
|
counter: Number(counter) || 0,
|
|
transports: Array.isArray(response.transports) ? response.transports : undefined,
|
|
deviceType: credentialDeviceType,
|
|
backedUp: !!credentialBackedUp,
|
|
createdAt: new Date().toISOString(),
|
|
lastUsedAt: null,
|
|
name: body?.name ? String(body.name).slice(0, 80) : 'Passkey'
|
|
})
|
|
|
|
users[idx] = u
|
|
await writeUsers(users)
|
|
|
|
await writeAuditLog('auth.passkey.registered', { userId: user.id })
|
|
return { success: true, message: 'Passkey hinzugefügt.' }
|
|
})
|
|
|
|
|