import { getUserByUsername, getUserByEmail, deleteOtherUserSessions } from '~/server/utils/database' import { getAuthUser, getAuthCookie } from '~/server/utils/auth' import bcrypt from 'bcrypt' export default defineEventHandler(async (event) => { const authUser = await getAuthUser(event) if (!authUser) { throw createError({ statusCode: 401, message: 'Unauthorized', }) } const body = await readBody(event) const { firstName, lastName, email, currentPassword, newPassword, confirmPassword } = body if (!firstName || !lastName || !email) { throw createError({ statusCode: 400, message: 'First name, last name, and email are required', }) } // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailRegex.test(email)) { throw createError({ statusCode: 400, message: 'Invalid email format', }) } const db = (await import('~/server/utils/database')).getDatabase() // Get current user data const currentUser = getUserByUsername(authUser.username) if (!currentUser) { throw createError({ statusCode: 404, message: 'User not found', }) } // Check if email is already taken by another user if (email.toLowerCase() !== currentUser.email?.toLowerCase()) { const existingEmail = getUserByEmail(email) if (existingEmail && existingEmail.id !== currentUser.id) { throw createError({ statusCode: 409, message: 'Email already exists', }) } } // If changing password, validate it if (newPassword) { if (!currentPassword) { throw createError({ statusCode: 400, message: 'Current password is required to change password', }) } // Verify current password const isValidPassword = bcrypt.compareSync(currentPassword, currentUser.password) if (!isValidPassword) { throw createError({ statusCode: 400, message: 'Current password is incorrect', }) } if (newPassword !== confirmPassword) { throw createError({ statusCode: 400, message: 'New passwords do not match', }) } // Validate new password requirements if (newPassword.length < 8) { throw createError({ statusCode: 400, message: 'Password must be at least 8 characters long', }) } const hasUpperCase = /[A-Z]/.test(newPassword) const hasLowerCase = /[a-z]/.test(newPassword) const hasNumberOrSymbol = /[0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(newPassword) if (!hasUpperCase || !hasLowerCase || !hasNumberOrSymbol) { throw createError({ statusCode: 400, message: 'Password must contain uppercase, lowercase, and number/symbol', }) } // Update with new password const saltRounds = 10 const hashedPassword = bcrypt.hashSync(newPassword, saltRounds) db.prepare('UPDATE users SET first_name = ?, last_name = ?, email = ?, password = ? WHERE id = ?') .run(firstName, lastName, email.toLowerCase(), hashedPassword, currentUser.id) // SECURITY: Invalidate all other sessions when password changes // This prevents session fixation and forces re-login on all other devices const currentSessionToken = getAuthCookie(event) if (currentSessionToken) { // Keep current session active, but logout all other sessions deleteOtherUserSessions(authUser.username, currentSessionToken) } return { success: true, message: 'Password updated successfully. Other sessions have been logged out for security.', passwordChanged: true } } else { // Update without changing password db.prepare('UPDATE users SET first_name = ?, last_name = ?, email = ? WHERE id = ?') .run(firstName, lastName, email.toLowerCase(), currentUser.id) return { success: true, message: 'Profile updated successfully' } } })