Quellcode durchsuchen

improve user update feature

Daniel Bohry vor 3 Wochen
Ursprung
Commit
f392d607d0

+ 9 - 8
src/main/java/com/danielbohry/authservice/api/UserController.java

@@ -3,6 +3,7 @@ package com.danielbohry.authservice.api;
 import com.danielbohry.authservice.api.dto.AuthenticationResponse;
 import com.danielbohry.authservice.api.dto.PasswordChangeRequest;
 import com.danielbohry.authservice.api.dto.PasswordResetRequest;
+import com.danielbohry.authservice.api.dto.ProfileUpdateRequest;
 import com.danielbohry.authservice.api.dto.UserResponse;
 import com.danielbohry.authservice.domain.ApplicationUser;
 import com.danielbohry.authservice.service.auth.AuthService;
@@ -39,28 +40,28 @@ public class UserController {
         return ResponseEntity.status(FORBIDDEN).build();
     }
 
-    @PostMapping("change-password")
-    public ResponseEntity<AuthenticationResponse> changePassword(@RequestBody PasswordChangeRequest request) {
+    @PostMapping("reset-password")
+    public ResponseEntity<AuthenticationResponse> resetPassword(@RequestBody PasswordResetRequest request) {
         SecurityContext context = SecurityContextHolder.getContext();
         Object principal = context.getAuthentication().getPrincipal();
 
         if (principal instanceof ApplicationUser user) {
-            log.info("Changing password for user [{}]", user.getUsername());
-            var response = service.changePassword(user.getId(), request.getCurrentPassword(), request.getNewPassword());
+            log.info("Resetting password for user [{}]", user.getUsername());
+            var response = service.resetPassword(user.getId(), request.getNewPassword());
             return ResponseEntity.ok(response);
         }
 
         return ResponseEntity.status(UNAUTHORIZED).build();
     }
 
-    @PostMapping("reset-password")
-    public ResponseEntity<AuthenticationResponse> resetPassword(@RequestBody PasswordResetRequest request) {
+    @PostMapping("update-profile")
+    public ResponseEntity<AuthenticationResponse> updateProfile(@RequestBody ProfileUpdateRequest request) {
         SecurityContext context = SecurityContextHolder.getContext();
         Object principal = context.getAuthentication().getPrincipal();
 
         if (principal instanceof ApplicationUser user) {
-            log.info("Resetting password for user [{}]", user.getUsername());
-            var response = service.resetPassword(user.getId(), request.getNewPassword());
+            log.info("Updating profile for user [{}]", user.getUsername());
+            var response = service.updateProfile(user.getId(), request.getCurrentPassword(), request.getNewPassword(), request.getEmail());
             return ResponseEntity.ok(response);
         }
 

+ 1 - 0
src/main/java/com/danielbohry/authservice/api/dto/AuthenticationRequest.java

@@ -10,6 +10,7 @@ import lombok.NoArgsConstructor;
 public class AuthenticationRequest {
 
     private String username;
+    private String email;
     private String password;
 
 }

+ 1 - 0
src/main/java/com/danielbohry/authservice/api/dto/AuthenticationResponse.java

@@ -12,6 +12,7 @@ public class AuthenticationResponse {
 
     private final String id;
     private final String username;
+    private final String email;
     private final String token;
     private final Instant expirationDate;
     private final List<String> roles;

+ 16 - 0
src/main/java/com/danielbohry/authservice/api/dto/ProfileUpdateRequest.java

@@ -0,0 +1,16 @@
+package com.danielbohry.authservice.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ProfileUpdateRequest {
+
+    private String currentPassword;
+    private String newPassword;
+    private String email;
+
+}

+ 14 - 7
src/main/java/com/danielbohry/authservice/service/auth/AuthService.java

@@ -43,9 +43,9 @@ public class AuthService implements UserDetailsService {
 
     public AuthenticationResponse register(AuthenticationRequest request) {
         UserDetails user = buildUserDetails(request);
-        ApplicationUser saved = service.create(convert(user));
+        ApplicationUser saved = service.create(convert(user, request.getEmail()));
         Authentication authentication = jwtService.generateToken(saved);
-        return buildResponse(saved.getId(), authentication);
+        return buildResponse(saved, authentication);
     }
 
     public AuthenticationResponse authenticate(AuthenticationRequest request) {
@@ -54,19 +54,25 @@ public class AuthService implements UserDetailsService {
         );
         ApplicationUser user = service.findByUsername(request.getUsername());
         Authentication authentication = jwtService.generateToken(user);
-        return buildResponse(user.getId(), authentication);
+        return buildResponse(user, authentication);
     }
 
     public AuthenticationResponse changePassword(String userId, String currentPassword, String newPassword) {
         ApplicationUser user = service.changePassword(userId, currentPassword, newPassword, passwordEncoder);
         Authentication authentication = jwtService.generateToken(user);
-        return buildResponse(user.getId(), authentication);
+        return buildResponse(user, authentication);
     }
 
     public AuthenticationResponse resetPassword(String userId, String newPassword) {
         ApplicationUser user = service.resetPassword(userId, newPassword, passwordEncoder);
         Authentication authentication = jwtService.generateToken(user);
-        return buildResponse(user.getId(), authentication);
+        return buildResponse(user, authentication);
+    }
+
+    public AuthenticationResponse updateProfile(String userId, String currentPassword, String newPassword, String email) {
+        ApplicationUser user = service.updateProfile(userId, currentPassword, newPassword, email, passwordEncoder);
+        Authentication authentication = jwtService.generateToken(user);
+        return buildResponse(user, authentication);
     }
 
     public void forgotPassword(String username) {
@@ -88,10 +94,11 @@ public class AuthService implements UserDetailsService {
                 .build();
     }
 
-    private static AuthenticationResponse buildResponse(String id, Authentication authentication) {
+    private static AuthenticationResponse buildResponse(ApplicationUser user, Authentication authentication) {
         return AuthenticationResponse.builder()
-                .id(id)
+                .id(user.getId())
                 .username(authentication.username())
+                .email(user.getEmail())
                 .token(authentication.token())
                 .expirationDate(authentication.expirationDate())
                 .roles(authentication.authorities())

+ 2 - 1
src/main/java/com/danielbohry/authservice/service/auth/UserConverter.java

@@ -7,10 +7,11 @@ import org.springframework.security.core.userdetails.UserDetails;
 @UtilityClass
 public class UserConverter {
 
-    public static ApplicationUser convert(UserDetails user) {
+    public static ApplicationUser convert(UserDetails user, String email) {
         return ApplicationUser.builder()
                 .username(user.getUsername())
                 .password(user.getPassword())
+                .email(email)
                 .build();
     }
 

+ 29 - 0
src/main/java/com/danielbohry/authservice/service/user/UserService.java

@@ -66,6 +66,35 @@ public class UserService {
         return repository.save(user);
     }
 
+    public ApplicationUser updateProfile(String userId, String currentPassword, String newPassword, String email, PasswordEncoder passwordEncoder) {
+        ApplicationUser user = repository.findById(userId)
+                .orElseThrow(() -> new NotFoundException("User not found"));
+
+        if (!passwordEncoder.matches(currentPassword, user.getPassword())) {
+            throw new BadRequestException("Current password is incorrect");
+        }
+
+        boolean hasChanges = false;
+
+        // Update password if provided
+        if (newPassword != null && !newPassword.trim().isEmpty()) {
+            user.setPassword(passwordEncoder.encode(newPassword));
+            hasChanges = true;
+        }
+
+        // Update email if provided and different from current
+        if (email != null && !email.equals(user.getEmail())) {
+            user.setEmail(email);
+            hasChanges = true;
+        }
+
+        if (!hasChanges) {
+            throw new BadRequestException("No changes detected in the profile update request");
+        }
+
+        return repository.save(user);
+    }
+
     private void validateUsername(ApplicationUser applicationUser) {
         boolean exists = repository.existsByUsername(applicationUser.getUsername());
 

+ 140 - 62
src/main/resources/static/index.html

@@ -256,6 +256,21 @@
         @keyframes spin {
             to { transform: rotate(360deg); }
         }
+
+        .profile-section, .security-section {
+            margin-bottom: 25px;
+            padding: 15px;
+            background: #f8f9fa;
+            border-radius: 8px;
+            border-left: 3px solid #667eea;
+        }
+
+        .profile-section h4, .security-section h4 {
+            margin: 0 0 15px 0;
+            color: #333;
+            font-size: 1rem;
+            font-weight: 600;
+        }
     </style>
 </head>
 <body>
@@ -289,6 +304,10 @@
                         <label for="registerUsername">Username</label>
                         <input type="text" id="registerUsername" name="username" required>
                     </div>
+                    <div class="form-group">
+                        <label for="registerEmail">Email (optional)</label>
+                        <input type="text" id="registerEmail" name="email">
+                    </div>
                     <div class="form-group">
                         <label for="registerPassword">Password</label>
                         <input type="password" id="registerPassword" name="password" required>
@@ -324,34 +343,53 @@
         <div id="userSection" class="user-info">
             <div class="user-details">
                 <p><strong>Username:</strong> <span id="currentUsername"></span></p>
+                <p><strong>Email:</strong> <span id="currentEmail"></span></p>
                 <p><strong>User ID:</strong> <span id="currentUserId"></span></p>
                 <p><strong>Roles:</strong> <span id="currentRoles"></span></p>
                 <p><strong>Token Expires:</strong> <span id="tokenExpiration"></span></p>
             </div>
 
             <div class="action-buttons">
-                <button class="action-btn" id="showChangePasswordBtn" onclick="toggleChangePasswordForm()">Change Password</button>
+                <button class="action-btn" id="showEditProfileBtn" onclick="toggleEditProfileForm()">Edit Profile</button>
             </div>
 
-            <div class="change-password-section" id="changePasswordSection" style="display: none;">
-                <h3>Change Password</h3>
-                <form id="changePasswordForm">
-                    <div class="form-group">
-                        <label for="currentPassword">Current Password</label>
-                        <input type="password" id="currentPassword" name="currentPassword" required>
+            <div class="change-password-section" id="editProfileSection" style="display: none;">
+                <h3>Edit Profile</h3>
+<!--                <div style="background: #e8f4f8; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 0.9rem; color: #2c5282;">-->
+<!--                    <strong>How it works:</strong> You can update your email address, change your password, or both. Your current password is required for security verification.-->
+<!--                </div>-->
+                <form id="editProfileForm">
+                    <div class="profile-section">
+                        <h4 style="margin-bottom: 15px; color: #333; font-size: 1rem;">Email Address</h4>
+                        <div class="form-group">
+                            <label for="editEmail">Email</label>
+                            <input type="email" id="editEmail" name="email" placeholder="Enter your email address">
+                        </div>
                     </div>
-                    <div class="form-group">
-                        <label for="newPassword">New Password</label>
-                        <input type="password" id="newPassword" name="newPassword" required>
+
+                    <div class="profile-section">
+                        <h4 style="margin-bottom: 15px; color: #333; font-size: 1rem;">Change Password</h4>
+                        <div class="form-group">
+                            <label for="newPassword">New Password</label>
+                            <input type="password" id="newPassword" name="newPassword" placeholder="Leave empty to keep current password">
+                        </div>
+                        <div class="form-group">
+                            <label for="confirmPassword">Confirm New Password</label>
+                            <input type="password" id="confirmPassword" name="confirmPassword" placeholder="Confirm your new password">
+                        </div>
                     </div>
-                    <div class="form-group">
-                        <label for="confirmPassword">Confirm New Password</label>
-                        <input type="password" id="confirmPassword" name="confirmPassword" required>
+
+                    <div class="security-section">
+                        <h4 style="margin-bottom: 15px; color: #333; font-size: 1rem;">Security Verification</h4>
+                        <div class="form-group">
+                            <label for="currentPassword">Current Password</label>
+                            <input type="password" id="currentPassword" name="currentPassword" required placeholder="Enter current password to verify changes">
+                        </div>
                     </div>
                     <div class="form-buttons">
-                        <button type="submit" class="submit-btn" id="changePasswordBtn">Change Password</button>
+                        <button type="submit" class="submit-btn" id="editProfileBtn">Update Profile</button>
                     </div>
-                    <div id="changePasswordMessage" class="message" style="display: none;"></div>
+                    <div id="editProfileMessage" class="message" style="display: none;"></div>
                 </form>
             </div>
 
@@ -433,8 +471,8 @@
                     button.innerHTML = 'Sign In';
                 } else if (buttonId.includes('register')) {
                     button.innerHTML = 'Create Account';
-                } else if (buttonId.includes('changePassword')) {
-                    button.innerHTML = 'Change Password';
+                } else if (buttonId.includes('editProfile')) {
+                    button.innerHTML = 'Update Profile';
                 } else if (buttonId.includes('resetPassword')) {
                     button.innerHTML = 'Reset Password';
                 }
@@ -486,6 +524,7 @@
 
             const username = document.getElementById('registerUsername').value;
             const password = document.getElementById('registerPassword').value;
+            const email = document.getElementById('registerEmail').value;
 
             setButtonLoading('registerBtn', true);
             clearMessages();
@@ -496,7 +535,7 @@
                     headers: {
                         'Content-Type': 'application/json'
                     },
-                    body: JSON.stringify({ username, password })
+                    body: JSON.stringify({ username, email, password })
                 });
 
                 if (response.ok) {
@@ -526,15 +565,13 @@
             const newPassword = document.getElementById('resetNewPassword').value;
             const confirmPassword = document.getElementById('resetConfirmPassword').value;
 
-            // Validate password confirmation
             if (newPassword !== confirmPassword) {
                 showMessage('resetPasswordMessage', 'New passwords do not match.', 'error');
                 return;
             }
 
-            // Validate password length
             if (newPassword.length < 4) {
-                showMessage('resetPasswordMessage', 'New password must be at least 6 characters long.', 'error');
+                showMessage('resetPasswordMessage', 'New password must be at least 4 characters long.', 'error');
                 return;
             }
 
@@ -572,60 +609,95 @@
             }
         });
 
-        document.getElementById('changePasswordForm').addEventListener('submit', async function(e) {
+        document.getElementById('editProfileForm').addEventListener('submit', async function(e) {
             e.preventDefault();
 
             const currentPassword = document.getElementById('currentPassword').value;
             const newPassword = document.getElementById('newPassword').value;
             const confirmPassword = document.getElementById('confirmPassword').value;
+            const email = document.getElementById('editEmail').value;
 
-            // Validate password confirmation
-            if (newPassword !== confirmPassword) {
-                showMessage('changePasswordMessage', 'New passwords do not match.', 'error');
+            // Validate that at least one field is being updated
+            const isPasswordChange = newPassword.trim() !== '';
+            const currentEmail = currentUser.email || '';
+            const newEmail = email.trim();
+            const isEmailChange = newEmail !== currentEmail;
+
+            if (!isPasswordChange && !isEmailChange) {
+                showMessage('editProfileMessage', 'Please make at least one change: update your email address or change your password.', 'error');
                 return;
             }
 
-            // Validate password length
-            if (newPassword.length < 6) {
-                showMessage('changePasswordMessage', 'New password must be at least 6 characters long.', 'error');
+            if (isPasswordChange && newPassword !== confirmPassword) {
+                showMessage('editProfileMessage', 'New passwords do not match.', 'error');
                 return;
             }
 
-            setButtonLoading('changePasswordBtn', true);
-            clearPasswordChangeMessages();
+            if (isPasswordChange && newPassword.length < 4) {
+                showMessage('editProfileMessage', 'New password must be at least 4 characters long.', 'error');
+                return;
+            }
+
+            setButtonLoading('editProfileBtn', true);
+            clearEditProfileMessages();
 
             try {
-                const response = await fetch(`${API_BASE_URL}/users/change-password`, {
+                const requestBody = { currentPassword };
+
+                if (isPasswordChange) {
+                    requestBody.newPassword = newPassword;
+                }
+
+                if (isEmailChange) {
+                    requestBody.email = newEmail;
+                }
+
+                const response = await fetch(`${API_BASE_URL}/users/update-profile`, {
                     method: 'POST',
                     headers: {
                         'Content-Type': 'application/json',
                         'Authorization': `Bearer ${localStorage.getItem('authToken')}`
                     },
-                    body: JSON.stringify({ currentPassword, newPassword })
+                    body: JSON.stringify(requestBody)
                 });
 
                 if (response.ok) {
                     const data = await response.json();
 
-                    showMessage('changePasswordMessage', 'Password changed successfully! Please log in with your new password.', 'success');
-
-                    // Clear the form and force logout after a brief delay
-                    document.getElementById('changePasswordForm').reset();
-                    logout();
+                    let message = '';
+                    if (isPasswordChange && isEmailChange) {
+                        message = 'Email and password updated successfully! Please log in with your new password.';
+                        document.getElementById('editProfileForm').reset();
+                        setTimeout(() => logout(), 1500);
+                    } else if (isPasswordChange) {
+                        message = 'Password updated successfully! Please log in with your new password.';
+                        document.getElementById('editProfileForm').reset();
+                        setTimeout(() => logout(), 1500);
+                    } else if (isEmailChange) {
+                        message = 'Email updated successfully!';
+                        // Update the stored user data with new email
+                        currentUser.email = newEmail;
+                        localStorage.setItem('userData', JSON.stringify(currentUser));
+                        updateUserDisplay(); // Refresh the displayed email
+                        document.getElementById('editProfileForm').reset();
+                        setTimeout(() => hideEditProfileForm(), 1000);
+                    }
+
+                    showMessage('editProfileMessage', message, 'success');
                 } else {
                     const errorText = await response.text();
-                    showMessage('changePasswordMessage', errorText || 'Failed to change password. Please try again.', 'error');
+                    showMessage('editProfileMessage', errorText || 'Failed to update profile. Please try again.', 'error');
                 }
             } catch (error) {
-                console.error('Change password error:', error);
-                showMessage('changePasswordMessage', 'Network error. Please check if the auth service is running.', 'error');
+                console.error('Update profile error:', error);
+                showMessage('editProfileMessage', 'Network error. Please check if the auth service is running.', 'error');
             } finally {
-                setButtonLoading('changePasswordBtn', false);
+                setButtonLoading('editProfileBtn', false);
             }
         });
 
-        function clearPasswordChangeMessages() {
-            const messageElement = document.getElementById('changePasswordMessage');
+        function clearEditProfileMessages() {
+            const messageElement = document.getElementById('editProfileMessage');
             messageElement.style.display = 'none';
             messageElement.textContent = '';
         }
@@ -638,6 +710,7 @@
 
         function updateUserDisplay() {
             document.getElementById('currentUsername').textContent = currentUser.username;
+            document.getElementById('currentEmail').textContent = currentUser.email || 'Not set';
             document.getElementById('currentUserId').textContent = maskUserId(currentUser.id);
             document.getElementById('currentRoles').textContent = currentUser.roles.join(', ');
 
@@ -676,49 +749,54 @@
             document.getElementById('loginForm').reset();
             document.getElementById('registerForm').reset();
 
-            // Reset password change form and hide it
-            hideChangePasswordForm();
+            // Reset edit profile form and hide it
+            hideEditProfileForm();
 
             clearMessages();
 
             switchTab('login');
         }
 
-        function toggleChangePasswordForm() {
-            const formSection = document.getElementById('changePasswordSection');
+        function toggleEditProfileForm() {
+            const formSection = document.getElementById('editProfileSection');
             const isVisible = formSection.style.display === 'block';
 
             if (isVisible) {
-                hideChangePasswordForm();
+                hideEditProfileForm();
             } else {
-                showChangePasswordForm();
+                showEditProfileForm();
             }
         }
 
-        function showChangePasswordForm() {
-            const formSection = document.getElementById('changePasswordSection');
-            const actionBtn = document.getElementById('showChangePasswordBtn');
+        function showEditProfileForm() {
+            const formSection = document.getElementById('editProfileSection');
+            const actionBtn = document.getElementById('showEditProfileBtn');
 
             formSection.style.display = 'block';
-            actionBtn.textContent = 'Hide Change Password';
+            actionBtn.textContent = 'Hide Edit Profile';
 
             // Clear any previous messages
-            clearPasswordChangeMessages();
+            clearEditProfileMessages();
+
+            // Pre-populate email field with current user's email
+            if (currentUser) {
+                document.getElementById('editEmail').value = currentUser.email || '';
+            }
 
-            // Focus on first input
-            document.getElementById('currentPassword').focus();
+            // Focus on email input first since that's the most common change
+            setTimeout(() => document.getElementById('editEmail').focus(), 100);
         }
 
-        function hideChangePasswordForm() {
-            const formSection = document.getElementById('changePasswordSection');
-            const actionBtn = document.getElementById('showChangePasswordBtn');
+        function hideEditProfileForm() {
+            const formSection = document.getElementById('editProfileSection');
+            const actionBtn = document.getElementById('showEditProfileBtn');
 
             formSection.style.display = 'none';
-            actionBtn.textContent = 'Change Password';
+            actionBtn.textContent = 'Edit Profile';
 
             // Clear form and messages
-            document.getElementById('changePasswordForm').reset();
-            clearPasswordChangeMessages();
+            document.getElementById('editProfileForm').reset();
+            clearEditProfileMessages();
         }
     </script>
 </body>