Skip to content

Apply patches#36

Merged
kathari00 merged 1 commit intomainfrom
34-apply-patches-front-channel-logoutftl-and-registerftl
Aug 9, 2025
Merged

Apply patches#36
kathari00 merged 1 commit intomainfrom
34-apply-patches-front-channel-logoutftl-and-registerftl

Conversation

@kathari00
Copy link
Collaborator

@kathari00 kathari00 commented Aug 8, 2025

Summary by CodeRabbit

  • New Features

    • Logout now waits for all logout client processes to complete before redirecting, improving reliability of the logout flow.
    • Registration now uses a global callback for reCAPTCHA submission, ensuring consistent form handling.
  • Bug Fixes

    • Improved detection for logout redirect link rendering to only show when explicitly defined.
    • Fixed reCAPTCHA callback configuration on the registration page for better integration.
  • Refactor

    • Streamlined internal logic for logout and registration components for improved maintainability.

@kathari00 kathari00 requested a review from garronej August 8, 2025 16:49
@coderabbitai
Copy link

coderabbitai bot commented Aug 8, 2025

Walkthrough

The changes update the frontchannel logout and registration components. The frontchannel logout flow now waits for all logout client iframes to load before redirecting, using a reactive counter and effect. The registration component switches the reCAPTCHA callback from a dynamic Angular binding to a global function managed via Angular lifecycle hooks.

Changes

Cohort / File(s) Change Summary
Frontchannel Logout Logic Update
src/login/pages/frontchannel-logout/frontchannel-logout.component.ts, src/login/pages/frontchannel-logout/frontchannel-logout.component.html
Replaces immediate redirect on init with a mechanism that waits for all logout client iframes to load before redirecting. Removes Angular OnInit usage, adds a signal to track loaded iframes, and updates template event bindings.
Register Component reCAPTCHA Callback Refactor
src/login/pages/register/register.component.ts, src/login/pages/register/register.component.html
Changes reCAPTCHA callback from a dynamic Angular binding to a global function. Implements OnInit and OnDestroy for lifecycle management of the global callback. Removes unused imports and updates class declaration accordingly.

Sequence Diagram(s)

Frontchannel Logout Flow (After Changes)

sequenceDiagram
    participant User
    participant FrontchannelLogoutComponent
    participant LogoutClientIframe
    participant Window

    User->>FrontchannelLogoutComponent: Loads component
    FrontchannelLogoutComponent->>LogoutClientIframe: Renders iframe(s) for each logout client
    LogoutClientIframe-->>FrontchannelLogoutComponent: load event (onIframeLoaded)
    FrontchannelLogoutComponent->>FrontchannelLogoutComponent: Increment iframeLoadCount
    FrontchannelLogoutComponent->>FrontchannelLogoutComponent: Effect checks if all iframes loaded
    alt All iframes loaded and logoutRedirectUri defined
        FrontchannelLogoutComponent->>Window: Redirect to logoutRedirectUri
    end
Loading

Register Component reCAPTCHA Callback (After Changes)

sequenceDiagram
    participant User
    participant RegisterComponent
    participant Window
    participant reCAPTCHA

    RegisterComponent->>Window: Defines window.onSubmitRecaptcha in ngOnInit
    User->>reCAPTCHA: Completes challenge
    reCAPTCHA->>Window: Calls window.onSubmitRecaptcha
    Window->>RegisterComponent: window.onSubmitRecaptcha submits form
    RegisterComponent->>Window: Removes window.onSubmitRecaptcha in ngOnDestroy
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Poem

Oh, what a hop in the logout flow,
Now we wait for iframes before we go!
Register’s callback, now global and neat,
Lifecycle hooks make cleanup complete.
With signals and hooks, our code’s in tune—
This bunny’s work will ship real soon! 🐇✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 34-apply-patches-front-channel-logoutftl-and-registerftl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (1)
src/login/pages/register/register.component.html (1)

91-98: ReCAPTCHA callback hookup looks correct; add disabled state parity with the non-recaptcha submit.

The static data-callback="onSubmitRecaptcha" aligns with the global defined in the component. However, unlike the non-recaptcha submit (Lines 107–112), this button lacks the disabled binding, allowing clicks when the form shouldn’t be submittable.

Apply:

                 <button
                     class="g-recaptcha"
                     type="submit"
                     [kcClass]="['kcButtonClass', 'kcButtonPrimaryClass', 'kcButtonBlockClass', 'kcButtonLargeClass']"
                     [attr.data-sitekey]="recaptchaSiteKey"
-                    data-callback="onSubmitRecaptcha"
+                    data-callback="onSubmitRecaptcha"
+                    [disabled]="!isFormSubmittable() || (termsAcceptanceRequired && !areTermsAccepted())"
                     [attr.data-action]="recaptchaAction"
                 >

Note: If you later rename the global callback, remember to update the string here too.

🧹 Nitpick comments (3)
src/login/pages/register/register.component.ts (1)

72-75: Clean up defensively in OnDestroy.

Minor: Guard against undefined window in SSR environments.

   ngOnDestroy(): void {
-        // eslint-disable-next-line
-        delete (window as any)['onSubmitRecaptcha'];
+        if (typeof window !== 'undefined') {
+            delete window.onSubmitRecaptcha;
+        }
   }
src/login/pages/frontchannel-logout/frontchannel-logout.component.ts (1)

42-42: Scope the signal to the class (nit).

The signal isn't referenced in the template; you can narrow visibility for clarity.

-readonly iframeLoadCount = signal(0);
+private readonly iframeLoadCount = signal(0);
src/login/pages/frontchannel-logout/frontchannel-logout.component.html (1)

8-8: Loop changes look good; validate trackBy uniqueness.

Using track client.name is fine if names are unique. If not guaranteed, prefer a stable unique key (e.g., clientId or URL).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a134f8 and a3fe427.

📒 Files selected for processing (4)
  • src/login/pages/frontchannel-logout/frontchannel-logout.component.html (1 hunks)
  • src/login/pages/frontchannel-logout/frontchannel-logout.component.ts (3 hunks)
  • src/login/pages/register/register.component.html (1 hunks)
  • src/login/pages/register/register.component.ts (3 hunks)
🧰 Additional context used
🪛 ESLint
src/login/pages/register/register.component.ts

[error] 68-68: Include a description after the "@ts-expect-error" directive to explain why the @ts-expect-error is necessary. The description must be 3 characters or longer.

(@typescript-eslint/ban-ts-comment)

🔇 Additional comments (6)
src/login/pages/register/register.component.ts (3)

1-14: Imports update LGTM.

OnInit/OnDestroy are appropriate for wiring/unwiring the global callback.


34-34: Component imports LGTM.

AsyncPipe/NgClass removal seems safe; not used in the template.


42-42: Class implements OnInit/OnDestroy — LGTM.

Matches the lifecycle usage below.

src/login/pages/frontchannel-logout/frontchannel-logout.component.ts (1)

1-1: Good adoption of Angular signals/effect.

Imports look correct for using signals/effect with OnPush and the new viewChild function.

src/login/pages/frontchannel-logout/frontchannel-logout.component.html (2)

14-14: LGTM: (load) binding correctly integrates with the component counter.

The event binding pairs well with the new onIframeLoaded() handler.


19-19: Unify redirect-URI presence check between template and TS

The template (frontchannel-logout.component.html:19) uses

@if (logout.logoutRedirectUri !== undefined)

but the TS effect (frontchannel-logout.component.ts:48) does

if (!logout.logoutRedirectUri) { return; }

This mismatch can show a “Continue” link with an empty href while the auto-redirect never fires.

Two options to align behavior:

• Preferred: use a strict undefined check in TS

--- src/login/pages/frontchannel-logout/frontchannel-logout.component.ts
@@ effect(() => {
-    if (!logout.logoutRedirectUri) {
+    if (logout.logoutRedirectUri === undefined) {
         return;
     }

• Or make the template use a truthy check to match TS

--- src/login/pages/frontchannel-logout/frontchannel-logout.component.html
@@
-@if (logout.logoutRedirectUri !== undefined) {
+@if (logout.logoutRedirectUri) {

Please confirm whether logout.logoutRedirectUri can ever be an empty string—if so, the strict === undefined check is safer.

Comment on lines +44 to +56
constructor() {
super();
effect(() => {
const logout = this.kcContext.logout;
if (!logout.logoutRedirectUri) {
return;
}
if (this.iframeLoadCount() !== logout.clients.length) {
return;
}
window.location.replace(logout.logoutRedirectUri);
});
}
Copy link

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Redirect gating has two pitfalls: mismatched redirectUri check and equals-only count.

  • The effect uses a falsy check (!logout.logoutRedirectUri), while the template renders the "Continue" link when the URI is not undefined. This can render an anchor with an empty href but never redirect.
  • Using strict equality (===) means a duplicate load event can overshoot the count and block redirect forever. Use >=.

Apply this minimal diff to harden the logic and align with the template:

   constructor() {
     super();
     effect(() => {
       const logout = this.kcContext.logout;
-      if (!logout.logoutRedirectUri) {
+      const redirectUri = logout.logoutRedirectUri;
+      if (redirectUri === undefined) {
         return;
       }
-      if (this.iframeLoadCount() !== logout.clients.length) {
+      const expected = logout.clients.length;
+      if (this.iframeLoadCount() < expected) {
         return;
       }
-      window.location.replace(logout.logoutRedirectUri);
+      if (typeof window !== 'undefined') {
+        window.location.replace(redirectUri);
+      }
     });
   }

Optional but recommended for resilience (no diff provided to avoid noise):

  • Add a fallback timeout (e.g., 5–10s) to redirect even if some iframes never load.
  • If some clients can lack a frontChannelLogoutUrl, consider counting only rendered iframes or guarding template rendering for those without URLs so the counts match exactly.
🤖 Prompt for AI Agents
In src/login/pages/frontchannel-logout/frontchannel-logout.component.ts around
lines 44 to 56, replace the falsy check on logoutRedirectUri with an explicit
undefined check to match the template logic and avoid rendering an empty href.
Also, change the iframe load count comparison from strict equality to greater
than or equal to (>=) to prevent blocking the redirect if duplicate load events
occur. Optionally, consider adding a fallback timeout to trigger the redirect
after a delay and ensure the iframe count matches only those with valid
frontChannelLogoutUrl to keep counts consistent.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The effect uses a falsy check (!logout.logoutRedirectUri), while the template renders the "Continue" link when the URI is not undefined. This can render an anchor with an empty href but never redirect.

Good rabit.

Using strict equality (===) means a duplicate load event can overshoot the count and block redirect forever. Use >=.

No, it can't possibly overshoot. Bad rabbit.

Add a fallback timeout (e.g., 5–10s) to redirect even if some iframes never load.

No

If some clients can lack a frontChannelLogoutUrl, consider counting only rendered iframes or guarding template rendering for those without URLs so the counts match exactly.

No, we won't start hazardously handling cases that aren't handled in keycloak

Copy link

Choose a reason for hiding this comment

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

@garronej, you're absolutely right about the overshoot - each iframe in the @for loop can only trigger its load event once, so the count cannot exceed the number of clients. My apologies for the confusion.

Here's the focused fix for just the redirectUri check inconsistency:

constructor() {
  super();
  effect(() => {
    const logout = this.kcContext.logout;
    if (logout.logoutRedirectUri === undefined) {
      return;
    }
    if (this.iframeLoadCount() !== logout.clients.length) {
      return;
    }
    window.location.replace(logout.logoutRedirectUri);
  });
}

This aligns the effect's check with the template's @if (logout.logoutRedirectUri !== undefined) condition, ensuring consistent behavior between automatic redirect and manual "Continue" link rendering.

Comment on lines +58 to 60
onIframeLoaded() {
this.iframeLoadCount.update(count => count + 1);
}
Copy link

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against duplicate iframe load events to avoid missing the redirect.

If an iframe fires load more than once, the count can overshoot, and with an equals-only check the redirect never triggers. Even after adopting >=, clamping keeps the counter bounded.

 onIframeLoaded() {
-  this.iframeLoadCount.update(count => count + 1);
+  const expected = this.kcContext.logout.clients.length;
+  this.iframeLoadCount.update(count => Math.min(count + 1, expected));
 }
📝 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
onIframeLoaded() {
this.iframeLoadCount.update(count => count + 1);
}
onIframeLoaded() {
const expected = this.kcContext.logout.clients.length;
this.iframeLoadCount.update(count => Math.min(count + 1, expected));
}
🤖 Prompt for AI Agents
In src/login/pages/frontchannel-logout/frontchannel-logout.component.ts around
lines 58 to 60, the onIframeLoaded method increments iframeLoadCount without
guarding against multiple load events, which can cause the count to overshoot
and miss the redirect trigger. Modify the method to clamp the iframeLoadCount so
it does not exceed the expected maximum, ensuring that the redirect logic
triggers correctly even if the iframe fires load multiple times.

Copy link
Collaborator

@garronej garronej Aug 9, 2025

Choose a reason for hiding this comment

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

He's potentially right here, I don't think in practice that the iframed page will trigger a navigation so the load event will only be loaded once.
However this is a valid concern, it should be refactored but not in the way sugessted, it introduce another bug. (What if on iframe fire load twice before all iframe have fired load?)

The sugested fix is worst than the current code. Bad rabbit.

Copy link

Choose a reason for hiding this comment

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

@garronej You're absolutely right! My suggestion was flawed. If one iframe fires load twice before others have loaded, clamping would allow the count to reach the target prematurely, triggering the redirect before all iframes have actually loaded. That's indeed worse than the current implementation.

A better approach would be to track which specific iframes have loaded using a Set to ensure each is counted only once:

readonly loadedIframes = signal(new Set<number>());

constructor() {
    super();
    effect(() => {
        const logout = this.kcContext.logout;
        if (!logout.logoutRedirectUri) {
            return;
        }
        if (this.loadedIframes().size !== logout.clients.length) {
            return;
        }
        window.location.replace(logout.logoutRedirectUri);
    });
}

onIframeLoaded(index: number) {
    this.loadedIframes.update(set => new Set(set).add(index));
}

And in the template, pass the index:

@for (client of logout.clients; track client; let idx = $index) {
    <iframe src="{{ client }}" (load)="onIframeLoaded(idx)"></iframe>
}

This ensures each iframe is counted exactly once, regardless of how many times its load event fires.

🐰💭

Comment on lines +65 to +71
ngOnInit(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any)['onSubmitRecaptcha'] = () => {
// @ts-expect-error
document.getElementById('kc-register-form').requestSubmit();
};
}
Copy link

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Remove any/ts-expect-error; add null-safety and cross-browser fallback.

Current code triggers ESLint’s ban-ts-comment (missing description) and risks a null deref on getElementById. Also, requestSubmit may be missing on older browsers.

Apply:

   ngOnInit(): void {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (window as any)['onSubmitRecaptcha'] = () => {
-            // @ts-expect-error
-            document.getElementById('kc-register-form').requestSubmit();
-        };
+        window.onSubmitRecaptcha = (_token?: string) => {
+            const form = document.getElementById('kc-register-form') as HTMLFormElement | null;
+            if (!form) {
+                return;
+            }
+            type FormWithRequestSubmit = HTMLFormElement & { requestSubmit?: () => void };
+            const f = form as FormWithRequestSubmit;
+            if (typeof f.requestSubmit === 'function') {
+                f.requestSubmit();
+            } else {
+                form.submit();
+            }
+        };
   }

Add this type augmentation (outside the above range) to avoid window casts:

declare global {
  interface Window {
    onSubmitRecaptcha: (token?: string) => void;
  }
}

If SSR is in play, consider guarding the assignment with if (typeof window === 'undefined') return;.

🧰 Tools
🪛 ESLint

[error] 68-68: Include a description after the "@ts-expect-error" directive to explain why the @ts-expect-error is necessary. The description must be 3 characters or longer.

(@typescript-eslint/ban-ts-comment)

🤖 Prompt for AI Agents
In src/login/pages/register/register.component.ts around lines 65 to 71, remove
the use of 'any' and '@ts-expect-error' by adding a proper type declaration for
'window.onSubmitRecaptcha' outside the ngOnInit method. Add null checks to
safely access the element returned by getElementById and provide a fallback for
browsers that do not support requestSubmit, such as calling the form's submit
method. Also, if server-side rendering is used, guard the assignment with a
check for 'typeof window === "undefined"' before assigning the function to avoid
runtime errors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, this is a very very bad rabbit. We won't start to check for things that we know won't happen just because typescript has no way to statically understand the code.
And the global augmentation is leaky and totally unessesary. We use that function only here.

Bad rabbit

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@garronej
Copy link
Collaborator

garronej commented Aug 9, 2025

Hello @kathari00,

Thank you for implementing this.
I can't review the actual code. I put some comment on the rabbit for fun but I trust that you have correctly ported what's done in react. As far as I can tell it looks good to me but if it was wrong I wouldn't be able to tell.

I let you merge, realease, and bump the version of @keycloakify/angular in the starters.

Best,

@kathari00 kathari00 merged commit 4905d19 into main Aug 9, 2025
6 checks passed
@kathari00 kathari00 deleted the 34-apply-patches-front-channel-logoutftl-and-registerftl branch August 9, 2025 07:22
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