Files
harheimertc/server/api/profile.put.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

125 lines
3.9 KiB
JavaScript

import { verifyToken, readUsers, writeUsers, verifyPassword, hashPassword, migrateUserRoles, revokeRefreshSessionsForUser } from '../utils/auth.js'
import { assertPasswordNotPwned } from '../utils/hibp.js'
export default defineEventHandler(async (event) => {
try {
const token = getCookie(event, 'auth_token')
if (!token) {
throw createError({
statusCode: 401,
message: 'Nicht authentifiziert.'
})
}
const decoded = verifyToken(token)
if (!decoded) {
throw createError({
statusCode: 401,
message: 'Ungültiges Token.'
})
}
const body = await readBody(event)
const { name, email, phone, geburtsdatum, currentPassword, newPassword } = body
if (!name || !email) {
throw createError({
statusCode: 400,
message: 'Name und E-Mail sind erforderlich.'
})
}
const users = await readUsers()
const userIndex = users.findIndex(u => u.id === decoded.id)
if (userIndex === -1) {
throw createError({
statusCode: 404,
message: 'Benutzer nicht gefunden.'
})
}
const user = users[userIndex]
let passwordChanged = false
// Check if email is already taken by another user
if (email !== user.email) {
const emailExists = users.some(u => u.email === email && u.id !== user.id)
if (emailExists) {
throw createError({
statusCode: 409,
message: 'Diese E-Mail-Adresse wird bereits verwendet.'
})
}
}
// Update basic info
user.name = name
user.email = email
user.phone = phone || ''
user.geburtsdatum = geburtsdatum || ''
// Optional visibility preferences (what to show to other logged-in members)
// Expected shape: { showEmail: boolean, showPhone: boolean, showAddress: boolean, showBirthday: boolean }
const visibility = body.visibility || body.visibilityPreferences || null
if (visibility && typeof visibility === 'object') {
user.visibility = user.visibility || {}
// Coerce values to booleans to be robust against string values from clients
if (visibility.showEmail !== undefined) user.visibility.showEmail = Boolean(visibility.showEmail)
if (visibility.showPhone !== undefined) user.visibility.showPhone = Boolean(visibility.showPhone)
if (visibility.showAddress !== undefined) user.visibility.showAddress = Boolean(visibility.showAddress)
if (visibility.showBirthday !== undefined) user.visibility.showBirthday = Boolean(visibility.showBirthday)
}
// Handle password change
if (currentPassword && newPassword) {
const isValid = await verifyPassword(currentPassword, user.password)
if (!isValid) {
throw createError({
statusCode: 401,
message: 'Aktuelles Passwort ist falsch.'
})
}
if (newPassword.length < 6) {
throw createError({
statusCode: 400,
message: 'Das neue Passwort muss mindestens 6 Zeichen lang sein.'
})
}
await assertPasswordNotPwned(newPassword)
user.password = await hashPassword(newPassword)
passwordChanged = true
}
await writeUsers(users)
if (passwordChanged) {
await revokeRefreshSessionsForUser(user.id, 'password_changed')
}
const migratedUser = migrateUserRoles({ ...user })
const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied'])
return {
success: true,
message: 'Profil erfolgreich aktualisiert.',
user: {
id: user.id,
email: user.email,
name: user.name,
phone: user.phone,
geburtsdatum: user.geburtsdatum || '',
visibility: user.visibility || {},
roles: roles,
role: roles[0] || 'mitglied' // Rückwärtskompatibilität
}
}
} catch (error) {
console.error('Profil-Update-Fehler:', error)
throw error
}
})