From 19428b4e270713f70c39581207880ba65e3b28bc Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:36:59 +0200 Subject: [PATCH] feat: add password verify and role management methods to LogtoManagementClient Adds verifyUserPassword (for current-password check before password change) and four global role methods (listRoleUsers, getRoleByName, assignGlobalRole, revokeGlobalRole) needed by the upcoming AccountService and VendorAdminService. Co-Authored-By: Claude Sonnet 4.6 --- .../saas/identity/LogtoManagementClient.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/main/java/net/siegeln/cameleer/saas/identity/LogtoManagementClient.java b/src/main/java/net/siegeln/cameleer/saas/identity/LogtoManagementClient.java index 26a91f4..50386eb 100644 --- a/src/main/java/net/siegeln/cameleer/saas/identity/LogtoManagementClient.java +++ b/src/main/java/net/siegeln/cameleer/saas/identity/LogtoManagementClient.java @@ -526,6 +526,26 @@ public class LogtoManagementClient { .toBodilessEntity(); } + /** Verify a user's current password. Returns true if correct, false if wrong. */ + public boolean verifyUserPassword(String userId, String password) { + try { + var token = getAccessToken(); + restClient.post() + .uri(config.getLogtoEndpoint() + "/api/users/" + userId + "/password/verify") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of("password", password)) + .retrieve() + .toBodilessEntity(); + return true; + } catch (org.springframework.web.client.HttpClientErrorException e) { + if (e.getStatusCode().value() == 422 || e.getStatusCode().value() == 400) { + return false; + } + throw e; + } + } + // --- MFA Verification Management --- /** List all MFA verifications for a user. Returns a list of MFA factor objects. */ @@ -673,6 +693,60 @@ public class LogtoManagementClient { } } + // --- Global Role Management --- + + /** List all users assigned to a global role. */ + @SuppressWarnings("unchecked") + public List> listRoleUsers(String roleId) { + var token = getAccessToken(); + var response = restClient.get() + .uri(config.getLogtoEndpoint() + "/api/roles/" + roleId + "/users?page=1&page_size=200") + .header("Authorization", "Bearer " + token) + .retrieve() + .body(List.class); + return response != null ? response : List.of(); + } + + /** Find a global role by exact name. Returns null if not found. */ + @SuppressWarnings("unchecked") + public Map getRoleByName(String roleName) { + var token = getAccessToken(); + var response = restClient.get() + .uri(config.getLogtoEndpoint() + "/api/roles?search=" + + java.net.URLEncoder.encode(roleName, java.nio.charset.StandardCharsets.UTF_8) + + "&page=1&page_size=20") + .header("Authorization", "Bearer " + token) + .retrieve() + .body(List.class); + if (response == null) return null; + return ((List>) response).stream() + .filter(r -> roleName.equals(r.get("name"))) + .findFirst() + .orElse(null); + } + + /** Assign a global role to a user. */ + public void assignGlobalRole(String userId, String roleId) { + var token = getAccessToken(); + restClient.post() + .uri(config.getLogtoEndpoint() + "/api/roles/" + roleId + "/users") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of("userIds", List.of(userId))) + .retrieve() + .toBodilessEntity(); + } + + /** Revoke a global role from a user. */ + public void revokeGlobalRole(String userId, String roleId) { + var token = getAccessToken(); + restClient.delete() + .uri(config.getLogtoEndpoint() + "/api/roles/" + roleId + "/users/" + userId) + .header("Authorization", "Bearer " + token) + .retrieve() + .toBodilessEntity(); + } + private static final String MGMT_API_RESOURCE = "https://default.logto.app/api"; private synchronized String getAccessToken() {