From 005cef4fcbe56e8dd2991fa17ec9971f4d7f04ce Mon Sep 17 00:00:00 2001 From: Rathan Naik <30756840+Rathan-Naik@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:56:22 +0530 Subject: [PATCH] Fix: Remove VERIFY_EMAIL action and publish event in ExecuteActionsActionTokenHandler When processing execute-actions token, the email was marked as verified but the VERIFY_EMAIL required action was not removed and no event was published. This led to inconsistent state. Now properly: - Removes VERIFY_EMAIL from user and auth session - Publishes VERIFY_EMAIL event for audit trail - Only performs these actions if email was not already verified Closes #42875 Signed-off-by: Rathan Naik <30756840+Rathan-Naik@users.noreply.github.com> --- .../ExecuteActionsActionTokenHandler.java | 11 +++++- .../RequiredActionEmailVerificationTest.java | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java index fc7cad0efc4..56ecb729d6f 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java @@ -31,6 +31,7 @@ import org.keycloak.authentication.actiontoken.AbstractActionTokenHandler; import org.keycloak.authentication.actiontoken.ActionTokenContext; import org.keycloak.authentication.actiontoken.TokenUtils; import org.keycloak.authentication.requiredactions.util.RequiredActionsValidator; +import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.forms.login.LoginFormsProvider; @@ -119,7 +120,15 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHandler UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser(); // verify user email as we know it is valid as this entry point would never have gotten here. - user.setEmailVerified(true); + if (!user.isEmailVerified()) { + user.setEmailVerified(true); + user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL); + authSession.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL); + tokenContext.getEvent().clone() + .event(EventType.VERIFY_EMAIL) + .detail(Details.EMAIL, user.getEmail()) + .success(); + } String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getRequest(), tokenContext.getEvent()); return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java index 0aa766470b8..860bf7b9d8c 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java @@ -1216,4 +1216,40 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo assertTrue(userRep.isEmailVerified()); assertThat(userRep.getRequiredActions(), Matchers.empty()); } + + @Test + public void executeActionsEmailVerifiesEmailAndRemovesRequiredAction() throws IOException { + try (Closeable u = new UserAttributeUpdater(testRealm().users().get(testUserId)) + .setEmailVerified(false) + .setRequiredActions(RequiredAction.VERIFY_EMAIL) + .update()) { + + UserRepresentation userBefore = testRealm().users().get(testUserId).toRepresentation(); + assertFalse(userBefore.isEmailVerified()); + assertThat(userBefore.getRequiredActions(), Matchers.contains(RequiredAction.VERIFY_EMAIL.name())); + + testRealm().users().get(testUserId).executeActionsEmail(List.of(RequiredAction.UPDATE_PASSWORD.name())); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getLastReceivedMessage(); + String verificationUrl = getEmailLink(message); + + driver.manage().deleteAllCookies(); + driver.navigate().to(verificationUrl.trim()); + + proceedPage.assertCurrent(); + proceedPage.clickProceedLink(); + + events.expectRequiredAction(EventType.VERIFY_EMAIL) + .user(testUserId) + .client("account") + .detail(Details.EMAIL, "test-user@localhost") + .detail(Details.REDIRECT_URI, Matchers.any(String.class)) + .assertEvent(); + + UserRepresentation userAfter = testRealm().users().get(testUserId).toRepresentation(); + assertTrue(userAfter.isEmailVerified()); + assertThat(userAfter.getRequiredActions(), Matchers.not(Matchers.contains(RequiredAction.VERIFY_EMAIL.name()))); + } + } }