Skip to content

Implement recovery key support for user storage providers#9

Open
ShashankFC wants to merge 1 commit into
feature-recovery-keys-foundationfrom
feature-recovery-keys-implementation
Open

Implement recovery key support for user storage providers#9
ShashankFC wants to merge 1 commit into
feature-recovery-keys-foundationfrom
feature-recovery-keys-implementation

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

Test 9

Summary by CodeRabbit

New Features

  • Recovery Codes Authentication: Added support for recovery codes as an authentication credential type, enabling users to set up and use recovery codes during login as an additional authentication method.
  • Recovery Code Verification: Implemented recovery code validation and verification within the authentication flow.
  • Backwards Compatibility: Extended legacy storage systems to support recovery codes credentials alongside existing authentication methods.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#9

closes #38445

Signed-off-by: rtufisi <rtufisi@phasetwo.io>
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces support for Recovery Codes credentials in Keycloak. It adds centralized helper methods for creating and retrieving recovery code credentials, refactors existing authentication and action components to use these helpers, extends the backwards-compatibility storage layer to support the new credential type, and provides integration test coverage for the complete flow.

Changes

Cohort / File(s) Summary
Helper Methods & Utilities
server-spi-private/src/main/java/org/keycloak/utils/CredentialHelper.java, server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java
Added createRecoveryCodesCredential() static method to centralize recovery codes credential creation with JSON serialization and storage support. Added getCredential() utility method that retrieves recovery codes from federated or stored credentials, reducing duplication across components.
Refactored Components
services/src/main/java/org/keycloak/authentication/authenticators/browser/RecoveryAuthnCodesFormAuthenticator.java, services/src/main/java/org/keycloak/authentication/requiredactions/RecoveryAuthnCodesAction.java, services/src/main/java/org/keycloak/forms/login/freemarker/model/RecoveryAuthnCodeInputLoginBean.java
Updated to use new RecoveryAuthnCodesUtils.getCredential() and CredentialHelper.createRecoveryCodesCredential() methods. Eliminates direct credential provider lookups and centralizes credential retrieval logic.
Storage Backend Extensions
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java, testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorageFactory.java
Extended backwards-compatibility storage to support RecoveryAuthnCodesCredentialModel including credential storage, retrieval, validation, and serialization. Added hasRecoveryCodes() check method and internal recoveryCodes field to MyUser class.
Integration Tests
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java
Added end-to-end test coverage: recovery codes browser flow configuration, setup via required action, credential storage verification, and login validation. Includes new page objects and helper methods for recovery codes UI interactions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A hop and a skip through recovery's grace,
New codes are stored in their rightful place,
Helper methods gather, refactoring neat,
Storage extends—the auth flow's complete! 🔐

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete and off-topic. It contains only 'Test 9' and a replication note, with no actual implementation details or context. Add a proper description explaining the changes: what recovery key support was implemented, why it was needed, how it affects user storage providers, and key technical details.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement recovery key support for user storage providers' clearly summarizes the main change: adding recovery key functionality to user storage providers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@services/src/main/java/org/keycloak/forms/login/freemarker/model/RecoveryAuthnCodeInputLoginBean.java:
- Around line 17-21: The code calls RecoveryAuthnCodesUtils.getCredential(user)
and immediately dereferences the Optional with credentialModelOpt.get(), which
can throw NoSuchElementException; update RecoveryAuthnCodeInputLoginBean to
first check credentialModelOpt.isPresent() (or use
credentialModelOpt.orElseThrow(...) with a clear IllegalStateException) before
passing to RecoveryAuthnCodesCredentialModel.createFromCredentialModel, and
handle the absent case by either setting codeNumber to a safe default or
throwing a descriptive exception so the class does not crash at runtime.

In
@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java:
- Around line 123-143: Typo in the config alias: in
configureBrowserFlowWithRecoveryAuthnCodes update the mistaken string
"delayed-suthenticator-config" to "delayed-authenticator-config" where
config.setAlias(...) is called so the delayed authenticator configuration alias
matches the authenticator name.
🧹 Nitpick comments (4)
server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java (1)

56-62: Consider adding null-check for the user parameter.

If user is null, calling user.credentialManager() will throw a NullPointerException. Consider adding a null check for consistency with other methods in this class (e.g., hashRawCode uses Objects.requireNonNull).

♻️ Suggested improvement
 public static Optional<CredentialModel> getCredential(UserModel user) {
+    Objects.requireNonNull(user, "user cannot be null");
     return user.credentialManager()
             .getFederatedCredentialsStream()
             .filter(c -> RecoveryAuthnCodesCredentialModel.TYPE.equals(c.getType()))
             .findFirst()
             .or(() -> user.credentialManager().getStoredCredentialsByTypeStream(RecoveryAuthnCodesCredentialModel.TYPE).findFirst());
 }
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java (1)

230-252: Use parameterized type for JSON deserialization.

Line 238 uses raw List.class which loses type information and causes unchecked conversion warnings. While this is test code, using a TypeReference would be more type-safe.

♻️ Suggested improvement
-                model = RecoveryAuthnCodesCredentialModel.createFromValues(
-                        JsonSerialization.readValue(myUser.recoveryCodes.getCredentialData(), List.class),
+                @SuppressWarnings("unchecked")
+                List<String> codes = JsonSerialization.readValue(myUser.recoveryCodes.getCredentialData(), List.class);
+                model = RecoveryAuthnCodesCredentialModel.createFromValues(
+                        codes,
                         myUser.recoveryCodes.getCreatedDate(),
                         myUser.recoveryCodes.getUserLabel()
                 );

Or alternatively, use Jackson's TypeReference:

List<String> codes = JsonSerialization.readValue(
    myUser.recoveryCodes.getCredentialData(), 
    new TypeReference<List<String>>() {}
);
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java (2)

240-269: Consider using a distinct username for this test.

The test uses "otp1" as the username (line 245, 258), which is also used in OTP-related tests. Consider using a more descriptive username like "recovery1" to improve test readability and avoid potential confusion when debugging test failures.

The test structure with try-finally for flow cleanup is good practice.


410-436: Hardcoded username reduces reusability.

Line 420 uses hardcoded "otp1" instead of deriving the username from the userId parameter. This limits the method's reusability for different users.

♻️ Suggested improvement

Consider fetching the username from the user resource or passing it as a parameter:

-    private List<String> setupRecoveryKeysForUserWithRequiredAction(String userId, boolean logoutOtherSessions) throws URISyntaxException, IOException {
+    private List<String> setupRecoveryKeysForUserWithRequiredAction(String userId, String username, boolean logoutOtherSessions) throws URISyntaxException, IOException {
         // Add required action to the user to reset RecoveryKeys
         UserResource user = testRealm().users().get(userId);
         UserRepresentation userRep = user.toRepresentation();
         userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name()));
         user.update(userRep);

         TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);

         // Login as the user and setup RecoveryKeys
-        testAppHelper.startLogin("otp1", "pass");
+        testAppHelper.startLogin(username, "pass");

Note: The same issue exists in setupOTPForUserWithRequiredAction (line 392).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0852ea and 134437a.

📒 Files selected for processing (8)
  • server-spi-private/src/main/java/org/keycloak/utils/CredentialHelper.java
  • server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/RecoveryAuthnCodesFormAuthenticator.java
  • services/src/main/java/org/keycloak/authentication/requiredactions/RecoveryAuthnCodesAction.java
  • services/src/main/java/org/keycloak/forms/login/freemarker/model/RecoveryAuthnCodeInputLoginBean.java
  • testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java
  • testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorageFactory.java
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java
🧰 Additional context used
🧬 Code graph analysis (6)
server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java (1)
server-spi/src/main/java/org/keycloak/credential/CredentialModel.java (1)
  • CredentialModel (36-355)
services/src/main/java/org/keycloak/forms/login/freemarker/model/RecoveryAuthnCodeInputLoginBean.java (1)
server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java (1)
  • RecoveryAuthnCodesUtils (20-63)
services/src/main/java/org/keycloak/authentication/authenticators/browser/RecoveryAuthnCodesFormAuthenticator.java (1)
server-spi/src/main/java/org/keycloak/models/utils/RecoveryAuthnCodesUtils.java (1)
  • RecoveryAuthnCodesUtils (20-63)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java (2)
server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java (1)
  • KeycloakModelUtils (103-1230)
core/src/main/java/org/keycloak/util/JsonSerialization.java (1)
  • JsonSerialization (40-126)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java (4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java (1)
  • BrowserFlowTest (68-1298)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/EnterRecoveryAuthnCodePage.java (1)
  • EnterRecoveryAuthnCodePage (15-64)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/SetupRecoveryAuthnCodesPage.java (1)
  • SetupRecoveryAuthnCodesPage (14-78)
common/src/main/java/org/keycloak/common/Profile.java (1)
  • Profile (45-483)
server-spi-private/src/main/java/org/keycloak/utils/CredentialHelper.java (1)
core/src/main/java/org/keycloak/util/JsonSerialization.java (1)
  • JsonSerialization (40-126)
🔇 Additional comments (14)
services/src/main/java/org/keycloak/authentication/requiredactions/RecoveryAuthnCodesAction.java (1)

116-116: Clean refactor to centralized credential creation.

The change correctly delegates recovery codes credential creation to CredentialHelper.createRecoveryCodesCredential, improving code maintainability and consistency across the codebase.

testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorageFactory.java (1)

53-57: LGTM!

The new hasRecoveryCodes method follows the same pattern as hasUserOTP, maintaining consistency in the test utility. The null checks are correctly implemented.

services/src/main/java/org/keycloak/authentication/authenticators/browser/RecoveryAuthnCodesFormAuthenticator.java (2)

80-80: Good use of centralized utility.

The refactor to use RecoveryAuthnCodesUtils.getCredential() correctly handles the Optional with proper isPresent() check before accessing the value, unlike the similar change in RecoveryAuthnCodeInputLoginBean.


80-89: Use separate removal logic for federated vs. stored recovery code credentials.

RecoveryAuthnCodesUtils.getCredential() checks federated credentials first, then stored credentials. However, calling removeStoredCredentialById() on lines 86-87 only removes stored credentials. If the credential is federated, it will not be removed correctly. Check if federated recovery code credentials are possible and handle removal using removeFederatedIdentity() if needed.

server-spi-private/src/main/java/org/keycloak/utils/CredentialHelper.java (2)

76-85: LGTM!

The refactored method maintains the same logic with improved readability through explicit null checks.


112-131: Return type mismatch: consider whether callers need feedback on credential creation success.

The createRecoveryCodesCredential method returns void, while the similar createOTPCredential method returns boolean indicating whether credential creation/validation succeeded. If callers need to know whether recovery codes were successfully created, this method should return a status value. If recovery codes don't require validation like OTP does, the current void return is acceptable—clarify the design intent with a comment.

Likely an incorrect or invalid review comment.

testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java (5)

111-120: LGTM!

The supportsCredentialType method correctly extends support to include RecoveryAuthnCodesCredentialModel.TYPE.


193-201: LGTM!

The recovery codes credential creation follows the established pattern used for OTP credentials, correctly setting the ID, type, credential data, and creation date.


275-276: LGTM!

The isConfiguredFor method correctly extends to check for recovery codes credentials using the same pattern as OTP credentials.


326-341: LGTM with minor note.

The validation logic correctly checks if the provided challenge response matches any of the stored recovery keys. The use of raw List type (line 332) is consistent with the rest of the file, though parameterized typing would be preferable.


427-439: LGTM!

The recoveryCodes field and its getter follow the established pattern used for other credentials in the MyUser class.

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java (3)

82-86: LGTM!

The @EnableFeature annotation correctly enables the RECOVERY_CODES feature for this test class with skipRestart = true to avoid unnecessary server restarts.


456-463: LGTM!

The assertion method follows the same pattern as assertUserHasOTPCredentialInUserStorage. Note that the hardcoded "otp1" username (line 460) is consistent with other methods but limits flexibility.


476-483: Remove this suggestion—the setDriver call is necessary and follows the established test pattern.

The setDriver(driver) call on line 478 is not redundant. While enterRecoveryAuthnCodePage is injected via @Page, the page object receives a WebDriver parameter that may differ from the injected instance. This pattern is consistently used throughout the test suite (see RecoveryAuthnCodesAuthenticatorTest.java lines 275, 283, 293, 301 for identical usage), ensuring the page object operates with the correct driver instance when passed as a parameter. The call is intentional and necessary.

Comment on lines +17 to 21
Optional<CredentialModel> credentialModelOpt = RecoveryAuthnCodesUtils.getCredential(user);

RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModel);
RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModelOpt.get());

this.codeNumber = recoveryCodeCredentialModel.getNextRecoveryAuthnCode().get().getNumber();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing Optional.isPresent() check will cause NoSuchElementException.

RecoveryAuthnCodesUtils.getCredential(user) returns an Optional that may be empty if no recovery codes credential exists. Calling .get() on line 19 without checking isPresent() will throw NoSuchElementException at runtime.

🐛 Proposed fix
 public RecoveryAuthnCodeInputLoginBean(KeycloakSession session, RealmModel realm, UserModel user) {
     Optional<CredentialModel> credentialModelOpt = RecoveryAuthnCodesUtils.getCredential(user);
 
-    RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModelOpt.get());
-
-    this.codeNumber = recoveryCodeCredentialModel.getNextRecoveryAuthnCode().get().getNumber();
+    if (credentialModelOpt.isEmpty()) {
+        throw new IllegalStateException("Recovery authentication codes credential not found for user");
+    }
+    RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModelOpt.get());
+    this.codeNumber = recoveryCodeCredentialModel.getNextRecoveryAuthnCode()
+            .orElseThrow(() -> new IllegalStateException("No recovery authentication code available"))
+            .getNumber();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Optional<CredentialModel> credentialModelOpt = RecoveryAuthnCodesUtils.getCredential(user);
RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModel);
RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModelOpt.get());
this.codeNumber = recoveryCodeCredentialModel.getNextRecoveryAuthnCode().get().getNumber();
Optional<CredentialModel> credentialModelOpt = RecoveryAuthnCodesUtils.getCredential(user);
if (credentialModelOpt.isEmpty()) {
throw new IllegalStateException("Recovery authentication codes credential not found for user");
}
RecoveryAuthnCodesCredentialModel recoveryCodeCredentialModel = RecoveryAuthnCodesCredentialModel.createFromCredentialModel(credentialModelOpt.get());
this.codeNumber = recoveryCodeCredentialModel.getNextRecoveryAuthnCode()
.orElseThrow(() -> new IllegalStateException("No recovery authentication code available"))
.getNumber();
🤖 Prompt for AI Agents
In
@services/src/main/java/org/keycloak/forms/login/freemarker/model/RecoveryAuthnCodeInputLoginBean.java
around lines 17 - 21, The code calls RecoveryAuthnCodesUtils.getCredential(user)
and immediately dereferences the Optional with credentialModelOpt.get(), which
can throw NoSuchElementException; update RecoveryAuthnCodeInputLoginBean to
first check credentialModelOpt.isPresent() (or use
credentialModelOpt.orElseThrow(...) with a clear IllegalStateException) before
passing to RecoveryAuthnCodesCredentialModel.createFromCredentialModel, and
handle the absent case by either setting codeNumber to a safe default or
throwing a descriptive exception so the class does not crash at runtime.

Comment on lines +123 to +143
void configureBrowserFlowWithRecoveryAuthnCodes(KeycloakTestingClient testingClient, long delay) {
final String newFlowAlias = BROWSER_FLOW_WITH_RECOVERY_AUTHN_CODES;
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
.selectFlow(newFlowAlias)
.inForms(forms -> forms
.clear()
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, UsernamePasswordFormFactory.PROVIDER_ID)
.addSubFlowExecution(AuthenticationExecutionModel.Requirement.REQUIRED, reqSubFlow -> reqSubFlow
.addSubFlowExecution("Recovery-Authn-Codes subflow", AuthenticationFlow.BASIC_FLOW, AuthenticationExecutionModel.Requirement.ALTERNATIVE, altSubFlow -> altSubFlow
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RecoveryAuthnCodesFormAuthenticatorFactory.PROVIDER_ID)
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, "delayed-authenticator", config -> {
config.setAlias("delayed-suthenticator-config");
config.setConfig(Map.of("delay", Long.toString(delay)));
})
)
)
)
.defineAsBrowserFlow()
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in config alias.

Line 135 has a typo: "delayed-suthenticator-config" should be "delayed-authenticator-config".

🔤 Suggested fix
                                         .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, "delayed-authenticator", config -> {
-                                            config.setAlias("delayed-suthenticator-config");
+                                            config.setAlias("delayed-authenticator-config");
                                             config.setConfig(Map.of("delay", Long.toString(delay)));
                                         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void configureBrowserFlowWithRecoveryAuthnCodes(KeycloakTestingClient testingClient, long delay) {
final String newFlowAlias = BROWSER_FLOW_WITH_RECOVERY_AUTHN_CODES;
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
.selectFlow(newFlowAlias)
.inForms(forms -> forms
.clear()
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, UsernamePasswordFormFactory.PROVIDER_ID)
.addSubFlowExecution(AuthenticationExecutionModel.Requirement.REQUIRED, reqSubFlow -> reqSubFlow
.addSubFlowExecution("Recovery-Authn-Codes subflow", AuthenticationFlow.BASIC_FLOW, AuthenticationExecutionModel.Requirement.ALTERNATIVE, altSubFlow -> altSubFlow
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RecoveryAuthnCodesFormAuthenticatorFactory.PROVIDER_ID)
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, "delayed-authenticator", config -> {
config.setAlias("delayed-suthenticator-config");
config.setConfig(Map.of("delay", Long.toString(delay)));
})
)
)
)
.defineAsBrowserFlow()
);
}
void configureBrowserFlowWithRecoveryAuthnCodes(KeycloakTestingClient testingClient, long delay) {
final String newFlowAlias = BROWSER_FLOW_WITH_RECOVERY_AUTHN_CODES;
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
.selectFlow(newFlowAlias)
.inForms(forms -> forms
.clear()
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, UsernamePasswordFormFactory.PROVIDER_ID)
.addSubFlowExecution(AuthenticationExecutionModel.Requirement.REQUIRED, reqSubFlow -> reqSubFlow
.addSubFlowExecution("Recovery-Authn-Codes subflow", AuthenticationFlow.BASIC_FLOW, AuthenticationExecutionModel.Requirement.ALTERNATIVE, altSubFlow -> altSubFlow
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RecoveryAuthnCodesFormAuthenticatorFactory.PROVIDER_ID)
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, "delayed-authenticator", config -> {
config.setAlias("delayed-authenticator-config");
config.setConfig(Map.of("delay", Long.toString(delay)));
})
)
)
)
.defineAsBrowserFlow()
);
}
🤖 Prompt for AI Agents
In
@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java
around lines 123 - 143, Typo in the config alias: in
configureBrowserFlowWithRecoveryAuthnCodes update the mistaken string
"delayed-suthenticator-config" to "delayed-authenticator-config" where
config.setAlias(...) is called so the delayed authenticator configuration alias
matches the authenticator name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants