Skip to content

Commit 1320395

Browse files
authored
feat(cognito): implement AdminRespondToAuthChallenge operation (#666)
Adds the admin variant of RespondToAuthChallenge, enabling server-side challenge response handling with explicit UserPoolId validation. Extracts shared challenge logic into processChallenge to avoid duplication.
1 parent 832cdf9 commit 1320395

6 files changed

Lines changed: 157 additions & 1 deletion

File tree

compatibility-tests/sdk-test-java/src/test/java/com/floci/test/CognitoFeaturesTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,46 @@ void describeUserPoolReturnsAllTwentyStandardAttributes() {
400400
assertThat(sub.mutable()).isFalse();
401401
}
402402

403+
// ── AdminRespondToAuthChallenge ─────────────────────────────────────────
404+
405+
@Test
406+
@Order(60)
407+
void adminRespondToAuthChallengeNewPasswordRequired() {
408+
String tempUser = "admin-challenge-user-" + java.util.UUID.randomUUID();
409+
String tempPassword = "TempPass1!";
410+
String newPassword = "Permanent99!";
411+
412+
cognito.adminCreateUser(b -> b
413+
.userPoolId(poolId)
414+
.username(tempUser)
415+
.temporaryPassword(tempPassword)
416+
.userAttributes(AttributeType.builder().name("email").value(tempUser + "@example.com").build())
417+
.messageAction(MessageActionType.SUPPRESS));
418+
419+
AdminInitiateAuthResponse initResp = cognito.adminInitiateAuth(b -> b
420+
.userPoolId(poolId)
421+
.clientId(clientId)
422+
.authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
423+
.authParameters(Map.of("USERNAME", tempUser, "PASSWORD", tempPassword)));
424+
425+
assertThat(initResp.challengeNameAsString()).isEqualTo("NEW_PASSWORD_REQUIRED");
426+
assertThat(initResp.session()).isNotBlank();
427+
428+
AdminRespondToAuthChallengeResponse challengeResp = cognito.adminRespondToAuthChallenge(b -> b
429+
.userPoolId(poolId)
430+
.clientId(clientId)
431+
.challengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED)
432+
.session(initResp.session())
433+
.challengeResponses(Map.of("USERNAME", tempUser, "NEW_PASSWORD", newPassword)));
434+
435+
assertThat(challengeResp.authenticationResult()).isNotNull();
436+
assertThat(challengeResp.authenticationResult().accessToken()).isNotBlank();
437+
assertThat(challengeResp.authenticationResult().idToken()).isNotBlank();
438+
assertThat(challengeResp.authenticationResult().refreshToken()).isNotBlank();
439+
440+
cognito.adminDeleteUser(b -> b.userPoolId(poolId).username(tempUser));
441+
}
442+
403443
// ── Issue #234 note ───────────────────────────────────────────────────────
404444
// GetTokensFromRefreshToken is tested in sdk-test-node/tests/cognito-features.test.ts
405445
// because GetTokensFromRefreshTokenCommand is not available in Java SDK 2.31.8.

src/main/java/io/github/hectorvent/floci/core/common/AwsQueryController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ private Response handleCognitoQuery(String action, MultivaluedMap<String, String
287287
private static final Set<String> COGNITO_ACTIONS = Set.of(
288288
"AdminCreateUser", "AdminGetUser", "AdminDeleteUser", "AdminSetUserPassword",
289289
"AdminUpdateUserAttributes", "AdminUserGlobalSignOut", "ListUsers",
290-
"InitiateAuth", "AdminInitiateAuth", "RespondToAuthChallenge",
290+
"InitiateAuth", "AdminInitiateAuth", "RespondToAuthChallenge", "AdminRespondToAuthChallenge",
291291
"SignUp", "ConfirmSignUp", "ChangePassword", "ForgotPassword",
292292
"ConfirmForgotPassword", "GetUser", "UpdateUserAttributes",
293293
"CreateUserPool", "DescribeUserPool", "ListUserPools", "UpdateUserPool", "DeleteUserPool",

src/main/java/io/github/hectorvent/floci/services/cognito/CognitoAuthFlowHandler.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,20 @@ Map<String, Object> respondToAuthChallenge(String clientId, String challengeName
137137
Map<String, String> responses, Map<String, String> clientMetadata) {
138138
UserPoolClient client = service.findClientById(clientId);
139139
UserPool pool = service.describeUserPool(client.getUserPoolId());
140+
return processChallenge(pool, client, challengeName, session, responses, clientMetadata);
141+
}
142+
143+
Map<String, Object> adminRespondToAuthChallenge(String userPoolId, String clientId, String challengeName,
144+
String session, Map<String, String> responses,
145+
Map<String, String> clientMetadata) {
146+
UserPoolClient client = service.describeUserPoolClient(userPoolId, clientId);
147+
UserPool pool = service.describeUserPool(userPoolId);
148+
return processChallenge(pool, client, challengeName, session, responses, clientMetadata);
149+
}
140150

151+
private Map<String, Object> processChallenge(UserPool pool, UserPoolClient client, String challengeName,
152+
String session, Map<String, String> responses,
153+
Map<String, String> clientMetadata) {
141154
if ("PASSWORD_VERIFIER".equals(challengeName)) {
142155
return handlePasswordVerifierChallenge(pool, client, session, responses, clientMetadata);
143156
}

src/main/java/io/github/hectorvent/floci/services/cognito/CognitoJsonHandler.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public Response handle(String action, JsonNode request, String region) {
6666
case "InitiateAuth" -> handleInitiateAuth(request);
6767
case "AdminInitiateAuth" -> handleAdminInitiateAuth(request);
6868
case "RespondToAuthChallenge" -> handleRespondToAuthChallenge(request);
69+
case "AdminRespondToAuthChallenge" -> handleAdminRespondToAuthChallenge(request);
6970
case "SignUp" -> handleSignUp(request);
7071
case "ConfirmSignUp" -> handleConfirmSignUp(request);
7172
case "ChangePassword" -> handleChangePassword(request);
@@ -407,6 +408,23 @@ private Response handleRespondToAuthChallenge(JsonNode request) {
407408
return Response.ok(objectMapper.valueToTree(result)).build();
408409
}
409410

411+
private Response handleAdminRespondToAuthChallenge(JsonNode request) {
412+
Map<String, String> responses = new HashMap<>();
413+
request.path("ChallengeResponses").fields().forEachRemaining(e -> responses.put(e.getKey(), e.getValue().asText()));
414+
Map<String, String> clientMetadata = new HashMap<>();
415+
request.path("ClientMetadata").fields().forEachRemaining(e -> clientMetadata.put(e.getKey(), e.getValue().asText()));
416+
417+
Map<String, Object> result = service.adminRespondToAuthChallenge(
418+
request.path("UserPoolId").asText(),
419+
request.path("ClientId").asText(),
420+
request.path("ChallengeName").asText(),
421+
request.path("Session").asText(null),
422+
responses,
423+
clientMetadata
424+
);
425+
return Response.ok(objectMapper.valueToTree(result)).build();
426+
}
427+
410428
private Response handleSignUp(JsonNode request) {
411429
Map<String, String> attrs = new HashMap<>();
412430
request.path("UserAttributes").forEach(a -> attrs.put(a.path("Name").asText(), a.path("Value").asText()));

src/main/java/io/github/hectorvent/floci/services/cognito/CognitoService.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,19 @@ public Map<String, Object> respondToAuthChallenge(String clientId, String challe
770770
return authFlowHandler.respondToAuthChallenge(clientId, challengeName, session, responses, clientMetadata);
771771
}
772772

773+
public Map<String, Object> adminRespondToAuthChallenge(String userPoolId, String clientId,
774+
String challengeName, String session,
775+
Map<String, String> responses) {
776+
return authFlowHandler.adminRespondToAuthChallenge(userPoolId, clientId, challengeName, session, responses, Map.of());
777+
}
778+
779+
public Map<String, Object> adminRespondToAuthChallenge(String userPoolId, String clientId,
780+
String challengeName, String session,
781+
Map<String, String> responses,
782+
Map<String, String> clientMetadata) {
783+
return authFlowHandler.adminRespondToAuthChallenge(userPoolId, clientId, challengeName, session, responses, clientMetadata);
784+
}
785+
773786
public void changePassword(String accessToken, String previousPassword, String proposedPassword) {
774787
String username = extractUsernameFromToken(accessToken);
775788
String poolId = extractPoolIdFromToken(accessToken);

src/test/java/io/github/hectorvent/floci/services/cognito/CognitoServiceTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,4 +1196,76 @@ void initiateAuthAcceptsCorrectSecretHash() throws Exception {
11961196
assertNotNull(auth);
11971197
assertNotNull(auth.get("AccessToken"));
11981198
}
1199+
1200+
// =========================================================================
1201+
// AdminRespondToAuthChallenge
1202+
// =========================================================================
1203+
1204+
@Test
1205+
@SuppressWarnings("unchecked")
1206+
void adminRespondToAuthChallengeNewPasswordRequired() {
1207+
UserPool pool = service.createUserPool(Map.of("PoolName", "TestPool"), "us-east-1");
1208+
service.adminCreateUser(pool.getId(), "bob", Map.of("email", "[email protected]"), "TempPass1!");
1209+
UserPoolClient client = service.createUserPoolClient(
1210+
pool.getId(), "c", false, false, List.of(), List.of());
1211+
1212+
Map<String, Object> challengeResp = service.adminInitiateAuth(
1213+
pool.getId(), client.getClientId(), "ADMIN_USER_PASSWORD_AUTH",
1214+
Map.of("USERNAME", "bob", "PASSWORD", "TempPass1!"), Map.of());
1215+
assertEquals("NEW_PASSWORD_REQUIRED", challengeResp.get("ChallengeName"));
1216+
String session = (String) challengeResp.get("Session");
1217+
1218+
Map<String, Object> result = service.adminRespondToAuthChallenge(
1219+
pool.getId(), client.getClientId(), "NEW_PASSWORD_REQUIRED", session,
1220+
Map.of("USERNAME", "bob", "NEW_PASSWORD", "Permanent99!"));
1221+
Map<String, Object> auth = (Map<String, Object>) result.get("AuthenticationResult");
1222+
assertNotNull(auth, "AuthenticationResult should be present");
1223+
assertNotNull(auth.get("AccessToken"));
1224+
assertNotNull(auth.get("IdToken"));
1225+
assertNotNull(auth.get("RefreshToken"));
1226+
1227+
CognitoUser user = service.adminGetUser(pool.getId(), "bob");
1228+
assertEquals("CONFIRMED", user.getUserStatus());
1229+
}
1230+
1231+
@Test
1232+
void adminRespondToAuthChallengeInvalidPool() {
1233+
UserPool pool1 = service.createUserPool(Map.of("PoolName", "Pool1"), "us-east-1");
1234+
UserPool pool2 = service.createUserPool(Map.of("PoolName", "Pool2"), "us-east-1");
1235+
service.adminCreateUser(pool1.getId(), "alice", Map.of("email", "[email protected]"), "TempPass1!");
1236+
UserPoolClient client = service.createUserPoolClient(
1237+
pool1.getId(), "c", false, false, List.of(), List.of());
1238+
1239+
AwsException ex = assertThrows(AwsException.class, () ->
1240+
service.adminRespondToAuthChallenge(
1241+
pool2.getId(), client.getClientId(), "NEW_PASSWORD_REQUIRED", null,
1242+
Map.of("USERNAME", "alice", "NEW_PASSWORD", "NewPass1!")));
1243+
assertEquals("ResourceNotFoundException", ex.getErrorCode());
1244+
}
1245+
1246+
@Test
1247+
@SuppressWarnings("unchecked")
1248+
void adminRespondToAuthChallengeWithUserAttributes() {
1249+
UserPool pool = service.createUserPool(Map.of("PoolName", "TestPool"), "us-east-1");
1250+
service.adminCreateUser(pool.getId(), "carol", Map.of("email", "[email protected]"), "TempPass1!");
1251+
UserPoolClient client = service.createUserPoolClient(
1252+
pool.getId(), "c", false, false, List.of(), List.of());
1253+
1254+
Map<String, Object> challengeResp = service.adminInitiateAuth(
1255+
pool.getId(), client.getClientId(), "ADMIN_USER_PASSWORD_AUTH",
1256+
Map.of("USERNAME", "carol", "PASSWORD", "TempPass1!"), Map.of());
1257+
String session = (String) challengeResp.get("Session");
1258+
1259+
Map<String, String> responses = new HashMap<>();
1260+
responses.put("USERNAME", "carol");
1261+
responses.put("NEW_PASSWORD", "Permanent99!");
1262+
responses.put("userAttributes.given_name", "Carolyn");
1263+
1264+
Map<String, Object> result = service.adminRespondToAuthChallenge(
1265+
pool.getId(), client.getClientId(), "NEW_PASSWORD_REQUIRED", session, responses);
1266+
assertNotNull(((Map<String, Object>) result.get("AuthenticationResult")).get("AccessToken"));
1267+
1268+
CognitoUser user = service.adminGetUser(pool.getId(), "carol");
1269+
assertEquals("Carolyn", user.getAttributes().get("given_name"));
1270+
}
11991271
}

0 commit comments

Comments
 (0)