From 1fd932afe1ccdfdcc5cba77dba6a06cf42d2f3fe Mon Sep 17 00:00:00 2001 From: Aaron Zhong Date: Wed, 1 Oct 2025 14:36:02 +1300 Subject: [PATCH 1/3] Add queryUsers and queryUserActions --- .../java/com/authsignal/AuthsignalClient.java | 58 +++++++++++++++++++ .../model/QueryUserActionsRequest.java | 9 +++ .../model/QueryUserActionsResponseItem.java | 13 +++++ .../authsignal/model/QueryUsersRequest.java | 11 ++++ .../authsignal/model/QueryUsersResponse.java | 8 +++ .../model/QueryUsersResponseUser.java | 11 ++++ 6 files changed, 110 insertions(+) create mode 100644 src/main/java/com/authsignal/model/QueryUserActionsRequest.java create mode 100644 src/main/java/com/authsignal/model/QueryUserActionsResponseItem.java create mode 100644 src/main/java/com/authsignal/model/QueryUsersRequest.java create mode 100644 src/main/java/com/authsignal/model/QueryUsersResponse.java create mode 100644 src/main/java/com/authsignal/model/QueryUsersResponseUser.java diff --git a/src/main/java/com/authsignal/AuthsignalClient.java b/src/main/java/com/authsignal/AuthsignalClient.java index 684bd07..603680e 100644 --- a/src/main/java/com/authsignal/AuthsignalClient.java +++ b/src/main/java/com/authsignal/AuthsignalClient.java @@ -58,6 +58,41 @@ public CompletableFuture getUser(GetUserRequest request) { response -> new Gson().fromJson(response.body(), GetUserResponse.class)); } + public CompletableFuture queryUsers(QueryUsersRequest request) { + Map params = new HashMap<>(); + + if (request.username != null) { + params.put("username", request.username); + } + + if (request.email != null) { + params.put("email", request.email); + } + + if (request.phoneNumber != null) { + params.put("phoneNumber", request.phoneNumber); + } + + if (request.token != null) { + params.put("token", request.token); + } + + if (request.limit != null) { + params.put("limit", request.limit.toString()); + } + + if (request.lastEvaluatedUserId != null) { + params.put("lastEvaluatedUserId", request.lastEvaluatedUserId); + } + + String query = buildQueryString(params); + + String path = "/users" + (query.isEmpty() ? "" : "?" + query); + + return getRequest(path) + .thenApply(response -> new Gson().fromJson(response.body(), QueryUsersResponse.class)); + } + public CompletableFuture updateUser(UpdateUserRequest request) { String path = String.format("/users/%s", request.userId); @@ -112,6 +147,29 @@ public CompletableFuture getAction(GetActionRequest request) return getRequest(path).thenApply(response -> new Gson().fromJson(response.body(), GetActionResponse.class)); } + public CompletableFuture queryUserActions(QueryUserActionsRequest request) { + Map params = new HashMap<>(); + + if (request.fromDate != null) { + params.put("fromDate", request.fromDate); + } + + if (request.actionCodes != null && request.actionCodes.length > 0) { + params.put("codes", String.join(",", request.actionCodes)); + } + + if (request.state != null) { + params.put("state", request.state.toString()); + } + + String query = buildQueryString(params); + + String path = String.format("/users/%s/actions", request.userId) + (query.isEmpty() ? "" : "?" + query); + + return getRequest(path) + .thenApply(response -> new Gson().fromJson(response.body(), QueryUserActionsResponseItem[].class)); + } + public CompletableFuture updateAction(UpdateActionRequest request) { String path = String.format("/users/%s/actions/%s/%s", request.userId, request.action, request.idempotencyKey); diff --git a/src/main/java/com/authsignal/model/QueryUserActionsRequest.java b/src/main/java/com/authsignal/model/QueryUserActionsRequest.java new file mode 100644 index 0000000..e0f5db1 --- /dev/null +++ b/src/main/java/com/authsignal/model/QueryUserActionsRequest.java @@ -0,0 +1,9 @@ +package com.authsignal.model; + +public class QueryUserActionsRequest extends ApiModel { + public String userId; + public String fromDate; + public String[] actionCodes; + public UserActionState state; +} + diff --git a/src/main/java/com/authsignal/model/QueryUserActionsResponseItem.java b/src/main/java/com/authsignal/model/QueryUserActionsResponseItem.java new file mode 100644 index 0000000..b667b38 --- /dev/null +++ b/src/main/java/com/authsignal/model/QueryUserActionsResponseItem.java @@ -0,0 +1,13 @@ +package com.authsignal.model; + +public class QueryUserActionsResponseItem extends ApiModel { + public String actionCode; + public String idempotencyKey; + public String createdAt; + public String updatedAt; + public String state; + public String stateUpdatedAt; + public VerificationMethodType verificationMethod; + public String verifiedByAuthenticatorId; +} + diff --git a/src/main/java/com/authsignal/model/QueryUsersRequest.java b/src/main/java/com/authsignal/model/QueryUsersRequest.java new file mode 100644 index 0000000..df105a4 --- /dev/null +++ b/src/main/java/com/authsignal/model/QueryUsersRequest.java @@ -0,0 +1,11 @@ +package com.authsignal.model; + +public class QueryUsersRequest extends ApiModel { + public String username; + public String email; + public String phoneNumber; + public String token; + public Integer limit; + public String lastEvaluatedUserId; +} + diff --git a/src/main/java/com/authsignal/model/QueryUsersResponse.java b/src/main/java/com/authsignal/model/QueryUsersResponse.java new file mode 100644 index 0000000..0e98d41 --- /dev/null +++ b/src/main/java/com/authsignal/model/QueryUsersResponse.java @@ -0,0 +1,8 @@ +package com.authsignal.model; + +public class QueryUsersResponse extends ApiModel { + public QueryUsersResponseUser[] users; + public String lastEvaluatedUserId; + public Object tokenPayload; +} + diff --git a/src/main/java/com/authsignal/model/QueryUsersResponseUser.java b/src/main/java/com/authsignal/model/QueryUsersResponseUser.java new file mode 100644 index 0000000..9c8c6f0 --- /dev/null +++ b/src/main/java/com/authsignal/model/QueryUsersResponseUser.java @@ -0,0 +1,11 @@ +package com.authsignal.model; + +public class QueryUsersResponseUser extends ApiModel { + public String userId; + public String email; + public Boolean emailVerified; + public String phoneNumber; + public Boolean phoneNumberVerified; + public String username; +} + From b693e23c27f87a75bb4955ee4f744daebd14dad5 Mon Sep 17 00:00:00 2001 From: Aaron Zhong Date: Wed, 1 Oct 2025 14:40:25 +1300 Subject: [PATCH 2/3] Bump version to 2.9.0 --- README.md | 4 ++-- gradle.properties | 2 +- src/main/java/com/authsignal/AuthsignalClient.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1e321f1..4352257 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Check out our [official Java SDK documentation](https://docs.authsignal.com/sdks Add this dependency to your project's build file: ```groovy -implementation 'com.authsignal:authsignal-java:2.7.0' +implementation 'com.authsignal:authsignal-java:2.9.0' ``` ### Maven users @@ -26,7 +26,7 @@ Add this dependency to your project's POM: com.authsignal authsignal-java - 2.7.0 + 2.9.0 ``` diff --git a/gradle.properties b/gradle.properties index a3315df..28b6517 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.8.0 \ No newline at end of file +version=2.9.0 \ No newline at end of file diff --git a/src/main/java/com/authsignal/AuthsignalClient.java b/src/main/java/com/authsignal/AuthsignalClient.java index 603680e..72aab6d 100644 --- a/src/main/java/com/authsignal/AuthsignalClient.java +++ b/src/main/java/com/authsignal/AuthsignalClient.java @@ -27,7 +27,7 @@ public class AuthsignalClient { private static final String DEFAULT_API_URL = "https://api.authsignal.com/v1"; private static final int DEFAULT_RETRIES = 2; - private static final String VERSION = "2.6.0"; + private static final String VERSION = "2.9.0"; public Webhook webhook; From b05f11d3bed69e30f09be2aa145bd3b37c4bc4a4 Mon Sep 17 00:00:00 2001 From: Aaron Zhong Date: Wed, 1 Oct 2025 14:54:04 +1300 Subject: [PATCH 3/3] Add tests --- .../com/authsignal/AuthsignalClientTests.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/test/java/com/authsignal/AuthsignalClientTests.java b/src/test/java/com/authsignal/AuthsignalClientTests.java index 6cb2dfb..bfd04b3 100644 --- a/src/test/java/com/authsignal/AuthsignalClientTests.java +++ b/src/test/java/com/authsignal/AuthsignalClientTests.java @@ -272,4 +272,131 @@ public void testPasskeyAuthenticator() { fail("should not throw any exception"); } } + + @Test + public void testQueryUsers() { + String userId = UUID.randomUUID().toString(); + String testEmail = "test-" + UUID.randomUUID().toString() + "@example.com"; + + try { + // First create a user with a unique email + UpdateUserRequest updateUserRequest = new UpdateUserRequest(); + updateUserRequest.userId = userId; + updateUserRequest.attributes = new UserAttributes(); + updateUserRequest.attributes.email = testEmail; + + client.updateUser(updateUserRequest).get(); + + // Query by email + QueryUsersRequest queryRequest = new QueryUsersRequest(); + queryRequest.email = testEmail; + + QueryUsersResponse queryResponse = client.queryUsers(queryRequest).get(); + + assertNotNull("queryResponse should exist", queryResponse); + assertNotNull("users array should exist", queryResponse.users); + assertTrue("users array should not be empty", queryResponse.users.length > 0); + + QueryUsersResponseUser user = queryResponse.users[0]; + assertEquals("userId should match", userId, user.userId); + assertEquals("email should match", testEmail, user.email); + + // Test pagination with limit + QueryUsersRequest limitRequest = new QueryUsersRequest(); + limitRequest.email = testEmail; + limitRequest.limit = 1; + + QueryUsersResponse limitResponse = client.queryUsers(limitRequest).get(); + + assertNotNull("limitResponse should exist", limitResponse); + assertTrue("users array should respect limit", limitResponse.users.length <= 1); + + // Clean up + DeleteUserRequest deleteRequest = new DeleteUserRequest(); + deleteRequest.userId = userId; + client.deleteUser(deleteRequest).get(); + } catch (Exception e) { + System.out.println(e.getMessage()); + + fail("should not throw any exception"); + } + } + + @Test + public void testQueryUserActions() { + String userId = UUID.randomUUID().toString(); + + EnrollVerifiedAuthenticatorRequest enrollRequest = new EnrollVerifiedAuthenticatorRequest(); + enrollRequest.userId = userId; + enrollRequest.attributes = new EnrollVerifiedAuthenticatorAttributes(); + enrollRequest.attributes.verificationMethod = VerificationMethodType.SMS; + enrollRequest.attributes.phoneNumber = "+6427000000"; + + try { + EnrollVerifiedAuthenticatorResponse enrollResponse = client.enrollVerifiedAuthenticator(enrollRequest) + .get(); + + assertNotNull("enrollResponse should exist", enrollResponse); + + // Track an action + TrackRequest trackRequest = new TrackRequest(); + trackRequest.userId = userId; + trackRequest.action = "signIn"; + + TrackResponse trackResponse = client.track(trackRequest).get(); + + assertNotNull("trackResponse should exist", trackResponse); + + // Query user actions + QueryUserActionsRequest queryRequest = new QueryUserActionsRequest(); + queryRequest.userId = userId; + + QueryUserActionsResponseItem[] actions = client.queryUserActions(queryRequest).get(); + + assertNotNull("actions should exist", actions); + assertTrue("actions should not be empty", actions.length > 0); + + QueryUserActionsResponseItem action = actions[0]; + assertEquals("action code should match", "signIn", action.actionCode); + assertNotNull("idempotencyKey should exist", action.idempotencyKey); + assertNotNull("createdAt should exist", action.createdAt); + assertNotNull("state should exist", action.state); + + // Query with filter by action code + QueryUserActionsRequest filteredRequest = new QueryUserActionsRequest(); + filteredRequest.userId = userId; + filteredRequest.actionCodes = new String[] { "signIn" }; + + QueryUserActionsResponseItem[] filteredActions = client.queryUserActions(filteredRequest).get(); + + assertNotNull("filtered actions should exist", filteredActions); + assertTrue("filtered actions should not be empty", filteredActions.length > 0); + + for (QueryUserActionsResponseItem filteredAction : filteredActions) { + assertEquals("all actions should have signIn code", "signIn", filteredAction.actionCode); + } + + // Query with state filter + QueryUserActionsRequest stateRequest = new QueryUserActionsRequest(); + stateRequest.userId = userId; + stateRequest.state = UserActionState.CHALLENGE_REQUIRED; + + QueryUserActionsResponseItem[] stateActions = client.queryUserActions(stateRequest).get(); + + assertNotNull("state filtered actions should exist", stateActions); + // Actions exist if any match the state filter + if (stateActions.length > 0) { + assertEquals("action state should match filter", "CHALLENGE_REQUIRED", stateActions[0].state); + } + + // Clean up + DeleteUserRequest deleteRequest = new DeleteUserRequest(); + deleteRequest.userId = userId; + client.deleteUser(deleteRequest).get(); + } catch (Exception e) { + System.out.println(e.getMessage()); + + fail("should not throw any exception"); + } + } }