Skip to content

refactor: rework frontend use effect calls#630

Merged
steveiliop56 merged 3 commits into
mainfrom
refactor/frontend-effects
Feb 7, 2026
Merged

refactor: rework frontend use effect calls#630
steveiliop56 merged 3 commits into
mainfrom
refactor/frontend-effects

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Feb 6, 2026

Summary by CodeRabbit

  • New Features

    • Centralized redirect URI parsing/validation for safer redirects
    • Improved OAuth auto-redirect flow with timed prompt and manual redirect option
  • Bug Fixes

    • Prevented redirect loops with one-time redirect tracking
    • Better detection and handling of HTTPS-to-HTTP downgrades
    • Unified redirect parameter behavior across login, continue, logout and TOTP flows
    • Use navigation replace to avoid extra history entries and tightened redirect timer cleanup
  • Chores

    • Removed legacy URL validity helper in favor of the new centralized logic

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Adds a new frontend hook to parse and validate redirect URIs (domain trust, protocol, HTTPS downgrade), removes a small URL utility, and updates auth pages to use the hook, unify redirect URL construction, introduce one-time redirect guards, and move timer cleanup into effect-scoped handlers. (50 words)

Changes

Cohort / File(s) Summary
Redirect URI Hook
frontend/src/lib/hooks/redirect-uri.ts
New useRedirectUri(redirect_uri, cookieDomain) hook: parses redirect_uri into URL, returns { url?, valid, trusted, allowedProto, httpsDowngrade } based on cookieDomain and protocol checks.
Utilities Cleanup
frontend/src/lib/utils.ts
Removed exported isValidUrl helper; URL validation logic moved into the new hook.
Continue Page
frontend/src/pages/continue-page.tsx
Replaced ad-hoc URL checks with useRedirectUri; added hasRedirected ref guard, useCallback handler for redirects, adjusted loading state -> isLoading, and updated auto/manual redirect flows and button states.
Login Page
frontend/src/pages/login-page.tsx
Refactored OAuth/login mutation naming and pending flags, added one-time auto-redirect state/ref and 5s auto-redirect timer, append redirect_uri only when present, updated cleanup and effect dependencies.
Logout & TOTP Pages
frontend/src/pages/logout-page.tsx, frontend/src/pages/totp-page.tsx
Changed logout navigation to location.replace("/login"); unified continue redirect behavior to /continue optionally with redirect_uri; moved timer cleanup into effects tied to timer refs.
Timers & Cleanup (cross-pages)
frontend/src/pages/...
Timer cleanup moved from unmount-only to effect returns tied to timer refs to ensure proper clear on change/unmount across login/continue/totp/logout flows.

Sequence Diagram(s)

(Skipped — changes are a frontend refactor and per-page control-flow adjustments that don't introduce a new multi-component sequential flow requiring visualization.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: invalid domain warning #332: Related modifications touching redirect/continue flow and frontend redirect validation UI that overlap continue-page and domain/redirect handling.

Poem

🐰 I sniffed a URL under moonlit code,
Matched cookie roots where trusted domains grow.
I warned when HTTPS slid to plain road,
I hopped once, then paused — no double-redirect show. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: rework frontend use effect calls' is partially related to the changeset. While the PR does refactor useEffect calls across multiple pages, the primary changes involve introducing a new redirect URI validation hook, removing URL validation logic, and refactoring redirect handling throughout the frontend. The title captures one aspect (useEffect rework) but misses the core architectural change of centralizing redirect URI validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/frontend-effects

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

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 16.16%. Comparing base (ce25f95) to head (089f926).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #630   +/-   ##
=======================================
  Coverage   16.16%   16.16%           
=======================================
  Files          45       45           
  Lines        3427     3427           
=======================================
  Hits          554      554           
  Misses       2816     2816           
  Partials       57       57           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

Copy link
Copy Markdown
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: 4

🤖 Fix all issues with AI agents
In `@frontend/src/pages/continue-page.tsx`:
- Around line 83-90: The redirect URL construction uses the raw search param
variable redirectUri (from searchParams.get) which can be null and yields
"/loginnull"; update the conditional to explicitly check redirectUri for
null/empty before appending the query string (e.g. use a ternary like
redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : "") so the
Navigate target is `/login` when redirectUri is absent; change the code around
the Navigate JSX in continue-page.tsx that references redirectUri to use that
explicit check.

In `@frontend/src/pages/login-page.tsx`:
- Around line 159-167: The effect watching redirectTimer and redirectButtonTimer
never returns a cleanup so timers set later can leak; update the useEffect in
login-page.tsx (the one referencing redirectTimer and redirectButtonTimer) to
use an empty dependency array and return a cleanup function that checks
redirectTimer.current and redirectButtonTimer.current and calls clearTimeout on
each (and optionally sets .current = null) so both timeouts are reliably cleared
on unmount.

In `@frontend/src/pages/logout-page.tsx`:
- Around line 42-44: The useEffect that currently checks redirectTimer.current
but doesn't return a cleanup should be changed to return a cleanup function that
clears the pending timeout on unmount (i.e., call
clearTimeout(redirectTimer.current) if set); update the effect for the stable
ref by using an empty dependency array and ensure the cleanup clears
redirectTimer.current after clearing so window.location.replace won't fire after
unmount — locate the effect using useEffect and the redirectTimer ref to
implement this.

In `@frontend/src/pages/totp-page.tsx`:
- Around line 62-64: The current useEffect in totp-page.tsx that tries to clear
redirectTimer is a no-op because redirectTimer ref identity is stable; change
the effect to run on totpPending (not the ref) and return a cleanup that clears
the timeout held in redirectTimer.current so the pending redirect is cancelled
if totpPending flips to false (reference redirectTimer and totpPending in your
effect, and ensure you call clearTimeout(redirectTimer.current) in the returned
cleanup). This ensures the timeout set elsewhere (where you call setTimeout for
Navigate / window.location.replace) is properly cleared when totpPending
changes.
🧹 Nitpick comments (3)
frontend/src/lib/hooks/redirect-uri.ts (1)

42-55: Prefer strict equality (===) over loose equality (==).

Lines 43, 49, and 53 use ==. While both operands are strings so the result is identical, === is the idiomatic TypeScript convention and avoids any ambiguity.

Proposed diff
   if (
-    url.hostname == cookieDomain ||
+    url.hostname === cookieDomain ||
     url.hostname.endsWith(`.${cookieDomain}`)
   ) {
     isTrusted = true;
   }
 
-  if (url.protocol == "http:" || url.protocol == "https:") {
+  if (url.protocol === "http:" || url.protocol === "https:") {
     isAllowedProto = true;
   }
 
-  if (window.location.protocol == "https:" && url.protocol == "http:") {
+  if (window.location.protocol === "https:" && url.protocol === "http:") {
     isHttpsDowngrade = true;
   }
frontend/src/pages/login-page.tsx (1)

58-61: State is inferred as string | false instead of boolean.

... !== undefined && props.redirect_uri evaluates to the redirect URI string (truthy) or false. This works at runtime in boolean contexts, but the inferred type is string | false, which muddies the intent. A !! coercion keeps it cleanly boolean.

Proposed fix
  const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
    providers.find((provider) => provider.id === oauthAutoRedirect) !==
-      undefined && props.redirect_uri,
+      undefined && !!props.redirect_uri,
  );
frontend/src/pages/continue-page.tsx (1)

35-39: Non-null assertion on url is safe today but fragile.

url! relies on rendering guards (!valid || !allowedProto → Navigate to /logout) ensuring url is defined before handleRedirect can be called. If the rendering order changes, this would throw. A guard clause would be more defensive:

Optional defensive check
  const handleRedirect = useCallback(() => {
+   if (!url) return;
    hasRedirected.current = true;
    setIsLoading(true);
-   window.location.assign(url!);
+   window.location.assign(url);
  }, [url]);

Comment thread frontend/src/pages/continue-page.tsx
Comment thread frontend/src/pages/login-page.tsx
Comment thread frontend/src/pages/logout-page.tsx
Comment thread frontend/src/pages/totp-page.tsx
Copy link
Copy Markdown
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: 0

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/pages/login-page.tsx (1)

199-207: ⚠️ Potential issue | 🟡 Minor

oauthData?.data.url can be undefined, causing navigation to literal "undefined".

window.location.replace(undefined) coerces to window.location.replace("undefined"). While showRedirectButton is only set inside onSuccess, the optional chaining on oauthData?.data.url signals the value isn't guaranteed. If for any reason oauthData is cleared (e.g. a re-render resets query state), the user would navigate to "undefined".

Proposed fix — guard the click handler
  <Button
    onClick={() => {
-     window.location.replace(oauthData?.data.url);
+     if (oauthData?.data.url) {
+       window.location.replace(oauthData.data.url);
+     }
    }}
  >
🧹 Nitpick comments (3)
frontend/src/pages/continue-page.tsx (2)

35-39: url! non-null assertion is fragile — guard against undefined instead.

url is undefined when valid is false (the hook only sets it on successful parse). Although the current effect guards and early-return JSX paths should prevent handleRedirect from being called with an undefined url, the non-null assertion silently masks the invariant. A small future refactor (e.g., changing effect conditions or adding a new call site) could trigger a runtime crash.

Proposed fix — add a runtime guard
  const handleRedirect = useCallback(() => {
+   if (!url) return;
    hasRedirected.current = true;
    setIsLoading(true);
-   window.location.assign(url!);
+   window.location.assign(url);
  }, [url]);

41-81: Effect dependency array includes stable refs and state setters — harmless but noisy.

hasRedirected (a useRef), setIsLoading, and setShowRedirectButton (state setters) are stable across renders and never trigger re-runs. Including them in the dep array is unnecessary noise. Modern eslint-plugin-react-hooks won't require them either. Consider trimming to the values that actually change.

frontend/src/pages/login-page.tsx (1)

58-61: isOauthAutoRedirect is typed string | false rather than boolean.

The && expression yields the right-hand string operand when the left is truthy. This works at runtime due to JS truthiness, but a string-typed state can be confusing (e.g., setIsOauthAutoRedirect(false) on Line 98 mixes types). Wrapping in !! keeps it a clean boolean.

- const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
-   providers.find((provider) => provider.id === oauthAutoRedirect) !==
-     undefined && props.redirect_uri,
- );
+ const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
+   providers.find((provider) => provider.id === oauthAutoRedirect) !==
+     undefined && !!props.redirect_uri,
+ );

@steveiliop56 steveiliop56 merged commit f08d859 into main Feb 7, 2026
8 checks passed
@Rycochet Rycochet deleted the refactor/frontend-effects branch April 1, 2026 16:09
@coderabbitai coderabbitai Bot mentioned this pull request May 19, 2026
1 task
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.

1 participant