fix(change-password): return updated user via record outcome w/ flush
CI / Build Native (push) Successful in 7m49s

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.
This commit is contained in:
2026-08-15 13:30:26 -04:00
parent 7b76bde15b
commit 0ef2991bcd
2 changed files with 17 additions and 13 deletions
@@ -91,16 +91,14 @@ public class AuthResource {
Optional<User> user = authService.getEntityFromToken(jwt.get()); Optional<User> user = authService.getEntityFromToken(jwt.get());
if (user.isEmpty()) return Response.status(401).build(); if (user.isEmpty()) return Response.status(401).build();
User current = user.get(); User current = user.get();
AuthService.ChangePasswordResult result = authService.changePassword( AuthService.ChangePasswordOutcome outcome = authService.changePassword(
current.id, body.currentPassword(), body.newPassword()); current.id, body.currentPassword(), body.newPassword());
switch (result) { switch (outcome.result()) {
case OK: case OK:
// Refetch fresh desde DB porque la version cacheada en el // outcome.updatedUser() viene de la propia tx con flush() hecho,
// persistence context tenia mustChangePassword=true (estado // asi que mustChangePassword=false y todas las columnas reflejan
// previo al cambio). Mejor un read fresco. // el estado actual de la DB.
User refreshed = User.findById(current.id); User refreshed = outcome.updatedUser();
if (refreshed == null) return Response.status(401).build();
User.getEntityManager().refresh(refreshed);
String newToken = authService.buildToken(refreshed.id); String newToken = authService.buildToken(refreshed.id);
return Response.ok(AuthService.toAuthMe(refreshed)) return Response.ok(AuthService.toAuthMe(refreshed))
.cookie(buildAuthCookie(newToken)) .cookie(buildAuthCookie(newToken))
@@ -95,19 +95,22 @@ public class AuthService {
} }
@Transactional @Transactional
public ChangePasswordResult changePassword(UUID userId, String currentPassword, String newPassword) { public ChangePasswordOutcome changePassword(UUID userId, String currentPassword, String newPassword) {
User user = User.findById(userId); 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)) { 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) { 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.passwordHash = BcryptUtil.bcryptHash(newPassword);
user.mustChangePassword = false; user.mustChangePassword = false;
user.persist(); 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 @Transactional
@@ -141,4 +144,7 @@ public class AuthService {
public static AuthMeResponse toAuthMe(User user) { public static AuthMeResponse toAuthMe(User user) {
return new AuthMeResponse(user.id, user.username, user.createdAt, user.mustChangePassword, user.isAdmin); return new AuthMeResponse(user.id, user.username, user.createdAt, user.mustChangePassword, user.isAdmin);
} }
public record ChangePasswordOutcome(ChangePasswordResult result, User updatedUser) {
}
} }