feat(auth): revoke other sessions on password change; add force-logout endpoint
changePassword now calls authService.revokeOtherSessions() after the
password is updated and emits a LOGOUT audit with reason=password_change.
POST /api/users/{id}/force-logout (ADMIN_USER permission) revokes all
sessions for the target user and emits ADMIN_FORCE_LOGOUT audit. Returns
{"revokedCount": N} with 200.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.validation.Valid;
|
||||
import org.raddatz.familienarchiv.audit.AuditKind;
|
||||
import org.raddatz.familienarchiv.audit.AuditService;
|
||||
import org.raddatz.familienarchiv.auth.AuthService;
|
||||
import org.raddatz.familienarchiv.user.AdminUpdateUserRequest;
|
||||
import org.raddatz.familienarchiv.user.ChangePasswordDTO;
|
||||
import org.raddatz.familienarchiv.user.CreateUserRequest;
|
||||
@@ -33,6 +37,8 @@ import lombok.AllArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
public class UserController {
|
||||
private UserService userService;
|
||||
private AuthService authService;
|
||||
private AuditService auditService;
|
||||
|
||||
@GetMapping("users/me")
|
||||
public ResponseEntity<AppUser> getCurrentUser(Authentication authentication) {
|
||||
@@ -56,9 +62,14 @@ public class UserController {
|
||||
@PostMapping("users/me/password")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void changePassword(Authentication authentication,
|
||||
HttpSession session,
|
||||
@RequestBody ChangePasswordDTO dto) {
|
||||
AppUser current = userService.findByEmail(authentication.getName());
|
||||
userService.changePassword(current.getId(), dto);
|
||||
int revoked = authService.revokeOtherSessions(session.getId(), authentication.getName());
|
||||
auditService.log(AuditKind.LOGOUT, current.getId(), null, Map.of(
|
||||
"reason", "password_change",
|
||||
"revokedCount", revoked));
|
||||
}
|
||||
|
||||
@GetMapping("users/{id}")
|
||||
@@ -101,6 +112,18 @@ public class UserController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/users/{id}/force-logout")
|
||||
@RequirePermission(Permission.ADMIN_USER)
|
||||
public ResponseEntity<Map<String, Object>> forceLogout(Authentication authentication,
|
||||
@PathVariable UUID id) {
|
||||
AppUser target = userService.getById(id);
|
||||
int revoked = authService.revokeAllSessions(target.getEmail());
|
||||
auditService.log(AuditKind.ADMIN_FORCE_LOGOUT, actorId(authentication), null, Map.of(
|
||||
"targetUserId", target.getId().toString(),
|
||||
"revokedCount", revoked));
|
||||
return ResponseEntity.ok(Map.of("revokedCount", revoked));
|
||||
}
|
||||
|
||||
private UUID actorId(Authentication auth) {
|
||||
return userService.findByEmail(auth.getName()).getId();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user