From 9dcb4865fa0eec62e97eeb806065f928c1c1a75f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 12 Mar 2026 18:16:20 -0600 Subject: [PATCH] Invalidate auth tokens --- .../backend/controller/AuthController.java | 48 ++++++++----------- .../backend/controller/ChatController.java | 11 +++-- .../petshop/backend/service/UserService.java | 9 ++++ .../backend/util/AuthenticationHelper.java | 26 +++++++++- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 0aca7d24..717ca7fa 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -14,6 +14,7 @@ import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.UserBusinessLinkageService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -22,8 +23,6 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @@ -137,11 +136,7 @@ public class AuthController { @GetMapping("/me") public ResponseEntity getCurrentUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); EmployeeStore employeeStore = resolveEmployeeStore(user); @@ -159,11 +154,8 @@ public class AuthController { @PutMapping("/me") public ResponseEntity updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); + boolean invalidateToken = false; if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) { if (userRepository.findByUsername(request.getUsername()).isPresent()) { @@ -172,6 +164,7 @@ public class AuthController { return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } user.setUsername(request.getUsername()); + invalidateToken = true; } if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { @@ -189,6 +182,11 @@ public class AuthController { if (request.getPassword() != null && !request.getPassword().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); + invalidateToken = true; + } + + if (invalidateToken) { + user.setTokenVersion(user.getTokenVersion() + 1); } User updatedUser = userRepository.save(user); @@ -219,11 +217,7 @@ public class AuthController { @PostMapping("/me/avatar") public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (file.isEmpty()) { Map error = new HashMap<>(); @@ -275,11 +269,7 @@ public class AuthController { @GetMapping("/me/avatar") public ResponseEntity getAvatar() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) { Map error = new HashMap<>(); @@ -294,11 +284,7 @@ public class AuthController { @DeleteMapping("/me/avatar") public ResponseEntity deleteAvatar() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) { try { @@ -322,4 +308,12 @@ public class AuthController { response.put("note", "Token remains valid until expiration. Clear token from client storage."); return ResponseEntity.ok(response); } + + private User getAuthenticatedUser() { + try { + return AuthenticationHelper.getAuthenticatedUser(userRepository); + } catch (RuntimeException ex) { + throw new UsernameNotFoundException(ex.getMessage(), ex); + } + } } diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index 4392b25d..8cfb5df3 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -9,12 +9,11 @@ import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; @@ -37,9 +36,11 @@ public class ChatController { } private User getCurrentUser() { - UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return userRepository.findByUsername(userDetails.getUsername()) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + try { + return AuthenticationHelper.getAuthenticatedUser(userRepository); + } catch (RuntimeException ex) { + throw new UsernameNotFoundException(ex.getMessage(), ex); + } } @PostMapping("/conversations") diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index 3e8d36a1..ee705a8a 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -68,14 +68,23 @@ public class UserService { User user = userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); + boolean invalidateToken = + !user.getUsername().equals(request.getUsername()) + || user.getRole() != request.getRole() + || !user.getActive().equals(request.getActive() != null ? request.getActive() : true); + user.setUsername(request.getUsername()); if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); + invalidateToken = true; } user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); user.setRole(request.getRole()); user.setActive(request.getActive() != null ? request.getActive() : true); + if (invalidateToken) { + user.setTokenVersion(user.getTokenVersion() + 1); + } user = userRepository.save(user); return mapToResponse(user); diff --git a/src/main/java/com/petshop/backend/util/AuthenticationHelper.java b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java index 26468202..b1ab33a1 100644 --- a/src/main/java/com/petshop/backend/util/AuthenticationHelper.java +++ b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -6,6 +6,7 @@ import com.petshop.backend.entity.User; import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.AppPrincipal; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -13,11 +14,34 @@ import org.springframework.stereotype.Component; @Component public class AuthenticationHelper { - public static User getAuthenticatedUser(UserRepository userRepository) { + public static Authentication getAuthentication() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { throw new RuntimeException("No authenticated user found"); } + return authentication; + } + + public static AppPrincipal getAuthenticatedPrincipal() { + Object principal = getAuthentication().getPrincipal(); + if (principal instanceof AppPrincipal appPrincipal) { + return appPrincipal; + } + throw new RuntimeException("Authenticated principal is not supported"); + } + + public static Long getAuthenticatedUserId() { + return getAuthenticatedPrincipal().getUserId(); + } + + public static User getAuthenticatedUser(UserRepository userRepository) { + Authentication authentication = getAuthentication(); + Object principal = authentication.getPrincipal(); + + if (principal instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) + .orElseThrow(() -> new RuntimeException("User not found: " + appPrincipal.getUserId())); + } String username = authentication.getName(); return userRepository.findByUsername(username)