From 0ef2991bcd5051202c67ecbc6dc8c4b653a0b84f Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Sat, 15 Aug 2026 13:30:26 -0400 Subject: [PATCH] fix(change-password): return updated user via record outcome w/ flush User.getEntityManager().refresh() requiere una tx activa, y el caller (AuthResource.changePassword) no es @Transactional -> ese approach tiraba TransactionRequiredException. En lugar de refrescar en el caller, cambio AuthService.changePassword para devolver la User ya actualizada en un record ChangePasswordOutcome, despues de un flush() explicito para que los cambios lleguen a DB antes de cerrar la tx. Asi el response del endpoint refleja el estado real de la fila. --- .../java/com/l2/shots/auth/AuthResource.java | 14 ++++++-------- src/main/java/com/l2/shots/auth/AuthService.java | 16 +++++++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/l2/shots/auth/AuthResource.java b/src/main/java/com/l2/shots/auth/AuthResource.java index efe5ad4..cd6c435 100644 --- a/src/main/java/com/l2/shots/auth/AuthResource.java +++ b/src/main/java/com/l2/shots/auth/AuthResource.java @@ -91,16 +91,14 @@ public class AuthResource { Optional user = authService.getEntityFromToken(jwt.get()); if (user.isEmpty()) return Response.status(401).build(); User current = user.get(); - AuthService.ChangePasswordResult result = authService.changePassword( + AuthService.ChangePasswordOutcome outcome = authService.changePassword( current.id, body.currentPassword(), body.newPassword()); - switch (result) { + switch (outcome.result()) { case OK: - // Refetch fresh desde DB porque la version cacheada en el - // persistence context tenia mustChangePassword=true (estado - // previo al cambio). Mejor un read fresco. - User refreshed = User.findById(current.id); - if (refreshed == null) return Response.status(401).build(); - User.getEntityManager().refresh(refreshed); + // outcome.updatedUser() viene de la propia tx con flush() hecho, + // asi que mustChangePassword=false y todas las columnas reflejan + // el estado actual de la DB. + User refreshed = outcome.updatedUser(); String newToken = authService.buildToken(refreshed.id); return Response.ok(AuthService.toAuthMe(refreshed)) .cookie(buildAuthCookie(newToken)) diff --git a/src/main/java/com/l2/shots/auth/AuthService.java b/src/main/java/com/l2/shots/auth/AuthService.java index 395d94e..3b40e94 100644 --- a/src/main/java/com/l2/shots/auth/AuthService.java +++ b/src/main/java/com/l2/shots/auth/AuthService.java @@ -95,19 +95,22 @@ public class AuthService { } @Transactional - public ChangePasswordResult changePassword(UUID userId, String currentPassword, String newPassword) { + public ChangePasswordOutcome changePassword(UUID userId, String currentPassword, String newPassword) { User user = User.findById(userId); - if (user == null) return ChangePasswordResult.NOT_FOUND; + if (user == null) return new ChangePasswordOutcome(ChangePasswordResult.NOT_FOUND, null); if (currentPassword == null || !BcryptUtil.matches(currentPassword, user.passwordHash)) { - return ChangePasswordResult.WRONG_CURRENT_PASSWORD; + return new ChangePasswordOutcome(ChangePasswordResult.WRONG_CURRENT_PASSWORD, null); } if (newPassword == null || newPassword.length() < MIN_PASSWORD_LENGTH) { - return ChangePasswordResult.WEAK_NEW_PASSWORD; + return new ChangePasswordOutcome(ChangePasswordResult.WEAK_NEW_PASSWORD, null); } user.passwordHash = BcryptUtil.bcryptHash(newPassword); user.mustChangePassword = false; user.persist(); - return ChangePasswordResult.OK; + // Forzar flush para que los cambios lleguen a DB antes de salir de la tx. + // Asi el caller (que NO esta en una tx) puede leer el estado actualizado. + User.getEntityManager().flush(); + return new ChangePasswordOutcome(ChangePasswordResult.OK, user); } @Transactional @@ -141,4 +144,7 @@ public class AuthService { public static AuthMeResponse toAuthMe(User user) { return new AuthMeResponse(user.id, user.username, user.createdAt, user.mustChangePassword, user.isAdmin); } + + public record ChangePasswordOutcome(ChangePasswordResult result, User updatedUser) { + } }