Skip to content

Conversation

@yash-rajpal
Copy link
Member

@yash-rajpal yash-rajpal commented Sep 22, 2025

Proposed changes (including videos or screenshots)

The iframe auth API is being triggered hundreds of times by the client due to a dependency loop and a race condition.

Issue(s)

Steps to test or reproduce

  • Setup iframe authentication
  • Have the auth API return invalid token
  • Open login page, client calls api many times

Further comments

CORE-1390

Summary by CodeRabbit

  • New Features

    • Automatic iframe-based login now triggers when enabled, initiating login flow automatically.
  • Bug Fixes

    • Improved handling of failed responses with clearer fallback to standard login.
    • More consistent desktop (Electron) behavior and increased reliability for token-based logins.
  • Refactor

    • Reorganized iframe login flow for greater stability and clearer event-driven invocation.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 22, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 22, 2025

⚠️ No Changeset found

Latest commit: 9d457f1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Refactors the iframe login hook to replace a useEffect-driven auto-run with a useEffectEvent-based, externally-invoked tryLogin; URL construction (including Electron flag) and fetch/login flow moved inside tryLogin with revised error handling. LoginPage now consumes tryLogin and enabled and auto-invokes tryLogin when enabled.

Changes

Cohort / File(s) Summary
Iframe login hook refactor
apps/meteor/client/hooks/iframe/useIframe.ts
Replaced auto-running useEffect login with a useEffectEvent-based tryLogin; moved URL assembly (append client=electron when applicable) earlier; reorganized fetch and token login under tryLogin with updated try/catch and error paths; preserved exported surface (tryLogin, loginWithToken, iframeLoginUrl, enabled) though tryLogin is now event-driven.
Login page integration
apps/meteor/client/views/root/MainLayout/LoginPage.tsx
Updated hook destructuring to { iframeLoginUrl, tryLogin, enabled: iframeEnabled }; added an effect to call tryLogin() when iframeEnabled is true; iframe rendering unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant LP as LoginPage
  participant UI as useIframe Hook
  participant S as Auth Server
  participant IF as Iframe

  U->>LP: Open Login Page
  LP->>UI: call useIframe() -> { iframeLoginUrl, enabled, tryLogin }
  alt enabled
    LP->>UI: invoke tryLogin()
    rect rgb(220,240,255)
      UI->>UI: build login URL (append client=electron if needed)
      UI->>S: fetch(login URL, headers)
      alt success (2xx)
        S-->>UI: token payload
        UI->>S: loginWithToken(token)
        S-->>UI: login result
        UI-->>LP: callback(success, body)
      else non-ok / error
        S-->>UI: error/redirect info or network error
        UI->>UI: setIframeLoginUrl(url)
        UI-->>LP: callback(error)
      end
    end
  else not enabled
    LP->>LP: no iframe attempt
  end

  note over LP,IF: If `iframeLoginUrl` is set, LP renders <iframe src=...>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • MartinSchoeler

Poem

A rabbit nibbles code at dawn, 🐇
I stitched a flag where URLs are drawn.
I wait, I call, tryLogin leaps—
If token falls, the iframe keeps.
Hop, retry, the login’s bright.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "regression: iframe auth api request loop" accurately and concisely describes the primary issue the changes target — repeated iframe auth API calls — and maps directly to the changeset which reworks useIframe.tryLogin and LoginPage usage to break the dependency loop. The title is specific, clear, and useful for teammates scanning history.
Linked Issues Check ✅ Passed The changes address CORE-1390 by converting tryLogin to an event-driven function (useEffectEvent), removing its automatic mount-driven invocation, and reorganizing URL/token handling so the login flow no longer appears to trigger dependency loops or race conditions; LoginPage now invokes tryLogin only when iframe auth is enabled. These code edits align with the issue's coding goals to stop excessive/repeated API calls, but the PR does not include automated tests that reproduce or assert the fix. Based on the provided summaries the implementation meets the linked issue's coding objectives, though runtime/QA verification is still needed.
Out of Scope Changes Check ✅ Passed All modified files in the provided summary are limited to apps/meteor/client/hooks/iframe/useIframe.ts and apps/meteor/client/views/root/MainLayout/LoginPage.tsx and relate directly to the iframe authentication flow, so no unrelated or out-of-scope code changes are evident.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch reg/iframe-auth

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

@codecov
Copy link

codecov bot commented Sep 22, 2025

Codecov Report

❌ Patch coverage is 74.07407% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.31%. Comparing base (411f130) to head (9d457f1).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37021      +/-   ##
===========================================
- Coverage    67.34%   67.31%   -0.03%     
===========================================
  Files         3339     3339              
  Lines       113178   113181       +3     
  Branches     20535    20532       -3     
===========================================
- Hits         76217    76189      -28     
- Misses       34355    34386      +31     
  Partials      2606     2606              
Flag Coverage Δ
e2e 56.92% <74.07%> (-0.02%) ⬇️
unit 71.21% <ø> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yash-rajpal yash-rajpal added this to the 7.11.0 milestone Sep 22, 2025
@yash-rajpal yash-rajpal marked this pull request as ready for review September 22, 2025 19:10
@yash-rajpal yash-rajpal requested a review from a team as a code owner September 22, 2025 19:10
@yash-rajpal yash-rajpal added the stat: QA assured Means it has been tested and approved by a company insider label Sep 22, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 22, 2025
Copy link
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/client/hooks/iframe/useIframe.ts (1)

18-33: Potential double login and double-callback if both loginToken and token exist.
Both branches run independently; if payload contains both fields, we issue two logins and call the callback twice. Also, when neither field exists, we silently do nothing.

Apply this diff:

 const loginWithToken = useCallback(
   (tokenData: string | { loginToken: string } | { token: string }, callback?: (error: Error | null | undefined) => void) => {
     if (typeof tokenData === 'string') {
       tokenData = {
         token: tokenData,
       };
     }
-    if ('loginToken' in tokenData) {
-      tokenLogin(tokenData.loginToken, callback);
-    }
-    if ('token' in tokenData) {
-      iframeLogin(tokenData.token, callback);
-    }
+    if ('loginToken' in tokenData) {
+      tokenLogin(tokenData.loginToken, callback);
+    } else if ('token' in tokenData) {
+      iframeLogin(tokenData.token, callback);
+    } else {
+      callback?.(new Error('Missing token data'), undefined);
+    }
   },
   [iframeLogin, tokenLogin],
 );
🧹 Nitpick comments (6)
apps/meteor/client/views/root/MainLayout/LoginPage.tsx (1)

16-23: Guard effect dependencies to avoid accidental re-invocation loops.
If tryLogin’s identity is not stable (or changes across HMR/StrictMode), this effect will re‑fire and may cause repeated API hits. Since tryLogin comes from useEffectEvent and should be stable, consider depending only on iframeEnabled to guarantee a single attempt per enablement.

Apply this diff:

-useEffect(() => {
-  if (!iframeEnabled) {
-    return;
-  }
-
-  tryLogin();
-}, [tryLogin, iframeEnabled]);
+useEffect(() => {
+  if (!iframeEnabled) {
+    return;
+  }
+  // tryLogin is stable (useEffectEvent); only react to enablement changes.
+  tryLogin();
+}, [iframeEnabled]);

Please confirm useEffectEvent guarantees a stable function identity in our hooks package. If not, we should keep tryLogin in deps and add an in‑flight guard in the hook.

apps/meteor/client/hooks/iframe/useIframe.ts (5)

57-61: Tighten status check and return a meaningful error.
Using both !result.ok and status !== 200 is redundant and hides context in the error.

Apply this diff:

-      if (!result.ok || result.status !== 200) {
-        setIframeLoginUrl(url);
-        callback?.(new Error(), null);
-        return;
-      }
+      if (!result.ok) {
+        setIframeLoginUrl(url);
+        callback?.(new Error(`Iframe auth API failed: ${result.status} ${result.statusText}`), null);
+        return;
+      }

50-56: Remove no-op headers assignment and consider AbortController.
headers: undefined is redundant. For resilience, optionally pass an AbortSignal from the caller or keep one here to abort stale attempts.

Apply this diff:

-      const result = await fetch(apiUrl, {
-        method: apiMethod,
-        headers: undefined,
-        credentials: 'include',
-      });
+      const result = await fetch(apiUrl, {
+        method: apiMethod,
+        credentials: 'include',
+      });

46-48: Harden Electron detection against SSR and future UA changes.
Avoid accessing navigator on the server and prefer includes for clarity.

Apply this diff:

-    if (navigator.userAgent.indexOf('Electron') > -1) {
+    if (typeof navigator !== 'undefined' && navigator.userAgent.includes('Electron')) {
       url += `${separator}client=electron`;
     }

35-75: Add an in-flight guard to make tryLogin idempotent under rapid re-invocations.
Prevents overlapping fetches if multiple components call tryLogin or settings toggle quickly.

Apply this diff (plus one new import):

-import { useCallback, useState } from 'react';
+import { useCallback, useRef, useState } from 'react';
@@
-const tryLogin = useEffectEvent(async (callback?: (error: Error | null | undefined, result: unknown) => void) => {
+const inFlightRef = useRef(false);
+const tryLogin = useEffectEvent(async (callback?: (error: Error | null | undefined, result: unknown) => void) => {
   if (!enabled) {
     return;
   }
+  if (inFlightRef.current) {
+    return;
+  }
+  inFlightRef.current = true;
@@
-  } catch (error) {
+  } catch (error) {
     setIframeLoginUrl(url);
     callback?.(error instanceof Error ? error : undefined, null);
-  }
+  } finally {
+    inFlightRef.current = false;
+  }
 });

16-17: Enablement predicate looks good. Consider stricter string checks if whitespace is possible.
Minor: trim strings if settings may contain accidental whitespace.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a503b4 and 2bb1afe.

📒 Files selected for processing (2)
  • apps/meteor/client/hooks/iframe/useIframe.ts (2 hunks)
  • apps/meteor/client/views/root/MainLayout/LoginPage.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/meteor/client/views/root/MainLayout/LoginPage.tsx (1)
apps/meteor/client/hooks/iframe/useIframe.ts (1)
  • useIframe (5-83)
🔇 Additional comments (5)
apps/meteor/client/views/root/MainLayout/LoginPage.tsx (2)

5-5: Importing useEffect is appropriate for driving the event-based login.


14-15: Good: consume new tryLogin/enabled API and alias enabled → iframeEnabled.
This keeps call‑site semantics clear and avoids naming collisions.

apps/meteor/client/hooks/iframe/useIframe.ts (3)

1-1: Adopting useEffectEvent for tryLogin is the right direction — verified.
Scanned repo: apps/meteor/client/views/root/MainLayout/LoginPage.tsx and apps/meteor/client/hooks/iframe/useIframeLoginListener.ts both explicitly invoke tryLogin (useEffect, message handlers, and passed to unstoreLoginToken); no consumer relies on automatic invocation.


8-12: Settings keys validated — casing matches repository settings.
useSetting keys in useIframe.ts (Accounts_iframe_enabled, Accounts_iframe_url, Accounts_Iframe_api_url, Accounts_Iframe_api_method) match the repository i18n/settings entries. (fossies.org)


63-70: Ignore type import suggestion Meteor.Error and Meteor.TypedError come from the ambient Meteor definitions and are globally available—no import or widening is needed.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/client/hooks/iframe/useIframe.ts (1)

18-33: Ensure callback is always invoked; avoid param mutation.

If the response lacks both token shapes, loginWithToken currently does nothing and never calls the callback, leaving callers hanging and potentially re-triggering flows.

Apply:

-	const loginWithToken = useCallback(
-		(tokenData: string | { loginToken: string } | { token: string }, callback?: (error: Error | null | undefined) => void) => {
-			if (typeof tokenData === 'string') {
-				tokenData = {
-					token: tokenData,
-				};
-			}
-			if ('loginToken' in tokenData) {
-				tokenLogin(tokenData.loginToken, callback);
-			}
-			if ('token' in tokenData) {
-				iframeLogin(tokenData.token, callback);
-			}
-		},
-		[iframeLogin, tokenLogin],
-	);
+	const loginWithToken = useCallback(
+		(tokenData: string | { loginToken: string } | { token: string }, callback?: (error: Error | null | undefined) => void) => {
+			const payload = typeof tokenData === 'string' ? { token: tokenData } : tokenData;
+			if ('loginToken' in payload) {
+				tokenLogin(payload.loginToken, callback);
+				return;
+			}
+			if ('token' in payload) {
+				iframeLogin(payload.token, callback);
+				return;
+			}
+			callback?.(new Error('Invalid token payload'));
+		},
+		[iframeLogin, tokenLogin],
+	);
🧹 Nitpick comments (4)
apps/meteor/client/hooks/iframe/useIframe.ts (4)

40-48: Prefer URL API for query assembly.

Manual “? / &” logic is brittle. The URL API is safer for existing queries and hashes.

Example:

-		let url = accountIframeUrl;
-		let separator = '?';
-		if (url.indexOf('?') > -1) {
-			separator = '&';
-		}
-		if (navigator.userAgent.indexOf('Electron') > -1) {
-			url += `${separator}client=electron`;
-		}
+		const u = new URL(accountIframeUrl, window.location.origin);
+		if (navigator.userAgent.indexOf('Electron') > -1) {
+			u.searchParams.set('client', 'electron');
+		}
+		const url = u.toString();

51-55: Tighten fetch options.

  • Remove headers: undefined.
  • Send Accept: application/json.
  • Ensure credentials: 'include' is intentional for cross-origin.

57-61: Simplify error check and surface status in the error.

!result.ok suffices; include status/statusText for diagnostics.

Apply (also included in a larger diff above):

-			if (!result.ok || result.status !== 200) {
-				setIframeLoginUrl(url);
-				callback?.(new Error(), null);
-				return;
-			}
+			if (!result.ok) {
+				const err = new Error(`Iframe auth API ${result.status} ${result.statusText || ''}`.trim());
+				setIframeLoginUrl(url);
+				callback?.(err, null);
+				return;
+			}

64-71: Drop unnecessary async on callback.

The inner callback doesn’t await; remove async to avoid creating an unused promise.

-			loginWithToken(body, async (error: Meteor.Error | Meteor.TypedError | Error | null | undefined) => {
+			loginWithToken(body, (error: Meteor.Error | Meteor.TypedError | Error | null | undefined) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2bb1afe and 4492fba.

📒 Files selected for processing (1)
  • apps/meteor/client/hooks/iframe/useIframe.ts (2 hunks)
🔇 Additional comments (2)
apps/meteor/client/hooks/iframe/useIframe.ts (2)

63-71: Good fix: parse body once; no double-consume.

The prior “body used already” bug is resolved by storing const body = await result.json() and reusing it.


1-1: Confirm useEffectEvent provides fresh-state semantics.

yarn.lock shows @rocket.chat/fuselage-hooks@^0.37.0 and many files import useEffectEvent, but the automated search didn’t locate the package export — verify in node_modules/@rocket.chat/fuselage-hooks that useEffectEvent is implemented to read fresh state (does not capture stale closures); if not, upgrade or pin to a version that does.

@yash-rajpal yash-rajpal added stat: ready to merge PR tested and approved waiting for merge and removed stat: ready to merge PR tested and approved waiting for merge labels Sep 23, 2025
@kodiakhq kodiakhq bot merged commit 652ff73 into develop Sep 23, 2025
70 of 72 checks passed
@kodiakhq kodiakhq bot deleted the reg/iframe-auth branch September 23, 2025 18:16
@coderabbitai coderabbitai bot mentioned this pull request Sep 23, 2025
@yash-rajpal
Copy link
Member Author

/backport 7.10.3

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Oct 24, 2025

Sorry, I couldn't do that backport because of conflicts. Could you please solve them?

you can do so by running the following commands:

git fetch
git checkout backport-7.10.3-37021
git cherry-pick 652ff7313a1231bc47bb3c3fd1342a93765cf144
// solve the conflict
git push

after that just run /backport 7.10.3 again

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

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants