feat: add user profile editing with email re-verification

Backend changes:
- Add PATCH /api/users/me endpoint for profile updates (firstName, lastName, email)
- Add PATCH /api/users/me/password endpoint for password change
- Email change triggers re-verification flow (emailVerified=false, new verification token/code)
- Send verification email automatically on email change
- Return new JWT token when email changes (to update emailVerified status)
- Add validation for profile update and password change
- Create user controller with updateProfile and changePassword functions

Frontend changes:
- Add ProfilePage with tabbed interface (Profile & Password tabs)
- Profile tab: Edit firstName, lastName, email
- Password tab: Change password (requires current password)
- Add Profile link to navigation bar
- Add authAPI.updateProfile() and authAPI.changePassword() functions
- Update AuthContext user data when profile is updated
- Display success/error messages for profile and password updates

Security:
- Username cannot be changed (permanent identifier)
- Email uniqueness validation
- Password change requires current password
- Email change forces re-verification to prevent hijacking

User flow:
1. User edits profile and changes email
2. Backend sets emailVerified=false and generates new verification tokens
3. Verification email sent to new address
4. User must verify new email to access all features
5. Banner appears until email is verified
This commit is contained in:
Radosław Gierwiało
2025-11-13 20:26:49 +01:00
parent 9d8fc9f6d6
commit 7c2ed687c1
7 changed files with 586 additions and 0 deletions

View File

@@ -113,10 +113,42 @@ const passwordResetValidation = [
handleValidationErrors,
];
// Update profile validation
const updateProfileValidation = [
body('firstName')
.optional()
.trim()
.isLength({ min: 1, max: 50 })
.withMessage('First name must be between 1 and 50 characters'),
body('lastName')
.optional()
.trim()
.isLength({ min: 1, max: 50 })
.withMessage('Last name must be between 1 and 50 characters'),
body('email')
.optional()
.trim()
.isEmail()
.withMessage('Must be a valid email address')
.normalizeEmail(),
handleValidationErrors,
];
// Change password validation
const changePasswordValidation = [
body('currentPassword')
.notEmpty()
.withMessage('Current password is required'),
buildPasswordValidation('newPassword'),
handleValidationErrors,
];
module.exports = {
registerValidation,
loginValidation,
verifyCodeValidation,
passwordResetValidation,
updateProfileValidation,
changePasswordValidation,
handleValidationErrors,
};