Files
harheimertc/server/api/auth/passkeys/register.post.js
Torsten Schulz (local) 58fd7fa5c6
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 5m7s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
feat(auth): implement Android refresh token handling and session management
- Added support for generating Android access tokens and managing refresh sessions in the auth endpoints.
- Implemented new tests for login, logout, and refresh functionalities specific to Android clients.
- Enhanced password reset logging with normalization and masking of email addresses.
- Created a new diagnostics endpoint for password reset attempts, including filtering and summarizing logs.
- Introduced a new utility for managing password reset logs with retention policies.
- Added tests for password reset log utilities to ensure proper functionality and privacy compliance.
- Updated WebAuthn configuration tests to validate origin handling for production and allowed origins.
2026-05-27 19:34:53 +02:00

109 lines
3.6 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 requestOrigin = getHeader(event, 'origin')
if (requestOrigin) {
setHeader(event, 'Access-Control-Allow-Origin', requestOrigin)
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 { origins, rpId, requireUV } = getWebAuthnConfig()
let verification
try {
verification = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin: origins,
expectedRPID: rpId,
requireUserVerification: requireUV
})
} catch {
await writeAuditLog('auth.passkey.registration.failed', { userId: user.id, reason: 'verification_error' })
throw createError({ statusCode: 400, statusMessage: 'Passkey-Registrierung fehlgeschlagen' })
} 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.' }
})