From 9ab53e4dfb233b0e17f4716531dd8f52a519171e Mon Sep 17 00:00:00 2001 From: Bennett Date: Wed, 23 Sep 2026 21:35:20 +0300 Subject: [PATCH 1/2] release: Fix error in auth with PATs --- .../PersonalAccessTokenRepository.java | 11 ++++++- .../modules/auth/services/UserService.java | 14 +++++---- .../auth/services/UserServiceTest.java | 30 ++++++++++--------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/flexcodelabs/flextuma/core/repositories/PersonalAccessTokenRepository.java b/src/main/java/com/flexcodelabs/flextuma/core/repositories/PersonalAccessTokenRepository.java index df995ff..c83f405 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/repositories/PersonalAccessTokenRepository.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/repositories/PersonalAccessTokenRepository.java @@ -1,6 +1,8 @@ package com.flexcodelabs.flextuma.core.repositories; import com.flexcodelabs.flextuma.core.entities.auth.PersonalAccessToken; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -9,5 +11,12 @@ @Repository public interface PersonalAccessTokenRepository extends BaseRepository, org.springframework.data.jpa.repository.JpaSpecificationExecutor { - Optional findByToken(String token); + + // Mirrors UserRepository#findByUsername's eager fetch: PatAuthenticationFilter walks + // pat.getUser().getRoles()...getPrivileges() outside any transaction, so roles/privileges + // must be loaded here, before this call's own short-lived session closes + // (spring.jpa.open-in-view=false) -- otherwise that walk throws + // "could not initialize proxy - no session". + @Query("SELECT p FROM PersonalAccessToken p LEFT JOIN FETCH p.user u LEFT JOIN FETCH u.roles LEFT JOIN FETCH u.roles.privileges WHERE p.token = :token") + Optional findByToken(@Param("token") String token); } diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/auth/services/UserService.java b/src/main/java/com/flexcodelabs/flextuma/modules/auth/services/UserService.java index 211ff14..8d09a91 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/auth/services/UserService.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/auth/services/UserService.java @@ -255,13 +255,15 @@ public User updateProfile(String currentUsername, ProfileUpdateDto request) { } public User changePassword(User user, String newPassword) { - User managedUser = repository.findById(user.getId()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); + // Mutate the caller's already-loaded user rather than re-fetching via plain findById: + // that eager-loads roles/privileges (see UserRepository#findByUsername), while a plain + // findById would return a fresh instance with roles as an uninitialized lazy proxy -- + // AuthController#changePassword serializes it via UserResponseDto.fromUser() right after + // this returns, outside any session (spring.jpa.open-in-view=false), which would 500. + user.setPassword(passwordEncoder.encode(newPassword)); + user.setChangePassword(false); - managedUser.setPassword(passwordEncoder.encode(newPassword)); - managedUser.setChangePassword(false); - - return repository.save(managedUser); + return repository.save(user); } } diff --git a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/UserServiceTest.java b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/UserServiceTest.java index b7c5ce0..218ba4c 100644 --- a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/UserServiceTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/UserServiceTest.java @@ -220,25 +220,27 @@ void delete_shouldThrowException_whenUserIsSystem() { } @Test - void changePassword_shouldEncodeAndSaveManagedUser() { + void changePassword_shouldEncodeAndSaveTheGivenUser_withoutRefetching() { + // The caller (AuthController) already loaded this user with roles/privileges eager- + // fetched (UserRepository#findByUsername) and serializes it right after this call + // returns, outside any transaction (spring.jpa.open-in-view=false). Re-fetching via a + // plain findById here would hand back a fresh instance with roles as an uninitialized + // lazy proxy, so this must mutate and save the given instance directly. UUID id = UUID.randomUUID(); - User detachedUser = new User(); - detachedUser.setId(id); - - User managedUser = new User(); - managedUser.setId(id); - managedUser.setChangePassword(true); + User user = new User(); + user.setId(id); + user.setChangePassword(true); - when(repository.findById(id)).thenReturn(Optional.of(managedUser)); when(passwordEncoder.encode("new-password")).thenReturn("encoded-password"); - when(repository.save(managedUser)).thenReturn(managedUser); + when(repository.save(user)).thenReturn(user); - User result = service.changePassword(detachedUser, "new-password"); + User result = service.changePassword(user, "new-password"); - assertSame(managedUser, result); - assertEquals("encoded-password", managedUser.getPassword()); - assertFalse(Boolean.TRUE.equals(managedUser.getChangePassword())); - verify(repository).save(managedUser); + assertSame(user, result); + assertEquals("encoded-password", user.getPassword()); + assertFalse(Boolean.TRUE.equals(user.getChangePassword())); + verify(repository, never()).findById(any()); + verify(repository).save(user); } private void mockPermissions(Set permissions) { From 5702a32cd74e8d2f22d71ecb764c8fee4d902bbb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 23 Sep 2026 18:36:00 +0000 Subject: [PATCH 2/2] Release v0.0.78 [skip ci] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b5b785f..f77593f 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.77' +version = '0.0.78' description = 'Flextuma App' java {