fix(change-password): return updated user via record outcome w/ flush
CI / Build Native (push) Successful in 7m49s
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:
@@ -91,16 +91,14 @@ public class AuthResource {
|
||||
Optional<User> 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))
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user