diff --git a/surfsense_web/app/auth/callback/page.tsx b/surfsense_web/app/auth/callback/page.tsx deleted file mode 100644 index da1755835..000000000 --- a/surfsense_web/app/auth/callback/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { Suspense } from "react"; -import TokenHandler from "@/components/TokenHandler"; - -export default function AuthCallbackPage() { - // Suspense fallback returns null - the GlobalLoadingProvider handles the loading UI - // TokenHandler uses useGlobalLoadingEffect to show the loading screen - return ( - - - - ); -} diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx index 46182f40e..30aedcce5 100644 --- a/surfsense_web/app/layout.tsx +++ b/surfsense_web/app/layout.tsx @@ -5,6 +5,7 @@ import { Roboto } from "next/font/google"; import Script from "next/script"; import { AnnouncementToastProvider } from "@/components/announcements/AnnouncementToastProvider"; import { DesktopUpdateToast } from "@/components/desktop/desktop-update-toast"; +import { AuthCutoverPurge } from "@/components/providers/AuthCutoverPurge"; import { GlobalLoadingProvider } from "@/components/providers/GlobalLoadingProvider"; import { I18nProvider } from "@/components/providers/I18nProvider"; import { PostHogProvider } from "@/components/providers/PostHogProvider"; @@ -164,6 +165,7 @@ export default function RootLayout({ + {children} diff --git a/surfsense_web/components/TokenHandler.tsx b/surfsense_web/components/TokenHandler.tsx deleted file mode 100644 index 64e99487e..000000000 --- a/surfsense_web/components/TokenHandler.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useGlobalLoadingEffect } from "@/hooks/use-global-loading"; -import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; -import { getAndClearRedirectPath } from "@/lib/auth-utils"; -import { buildBackendUrl } from "@/lib/env-config"; -import { trackLoginSuccess } from "@/lib/posthog/events"; - -interface TokenHandlerProps { - redirectPath?: string; // Default path to redirect after storing token (if no saved path) - tokenParamName?: string; // Deprecated: tokens are no longer read from URLs -} - -/** - * Client component that finalizes a cookie session after OAuth/local login. - * After confirming the session, it redirects the user back to the page they were on before - * being redirected to login (if available), or to the default redirectPath. - * - * @param redirectPath - Default path to redirect after storing token (default: '/dashboard') - * @param tokenParamName - Name of the URL parameter containing the token (default: 'token') - */ -const TokenHandler = ({ - redirectPath = "/dashboard", - tokenParamName: _tokenParamName = "token", -}: TokenHandlerProps) => { - // Always show loading for this component - spinner animation won't reset - useGlobalLoadingEffect(true); - - useEffect(() => { - if (typeof window === "undefined") return; - - const run = async () => { - try { - const sessionResponse = await fetch(buildBackendUrl("/auth/session"), { - credentials: "include", - }); - if (!sessionResponse.ok) { - window.location.href = "/login"; - return; - } - - const alreadyTracked = sessionStorage.getItem("login_success_tracked"); - if (!alreadyTracked) { - trackLoginSuccess("google"); - } - sessionStorage.removeItem("login_success_tracked"); - - // Auto-set active search space in desktop if not already set - if (window.electronAPI?.getActiveSearchSpace) { - try { - const stored = await window.electronAPI.getActiveSearchSpace(); - if (!stored) { - const spaces = await searchSpacesApiService.getSearchSpaces(); - if (spaces?.length) { - await window.electronAPI.setActiveSearchSpace?.(String(spaces[0].id)); - } - } - } catch { - // non-critical - } - } - - const savedRedirectPath = getAndClearRedirectPath(); - const finalRedirectPath = savedRedirectPath || redirectPath; - window.location.href = finalRedirectPath; - } catch (error) { - console.error("Error finalizing session:", error); - window.location.href = redirectPath; - } - }; - - run(); - }, [redirectPath]); - - // Return null - the global provider handles the loading UI - return null; -}; - -export default TokenHandler; diff --git a/surfsense_web/components/providers/AuthCutoverPurge.tsx b/surfsense_web/components/providers/AuthCutoverPurge.tsx new file mode 100644 index 000000000..db028cb39 --- /dev/null +++ b/surfsense_web/components/providers/AuthCutoverPurge.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useEffect } from "react"; + +const CUTOVER_FLAG_KEY = "surfsense_auth_cutover_v1_complete"; +const LEGACY_BEARER_TOKEN_KEY = "surfsense_bearer_token"; +const LEGACY_REFRESH_TOKEN_KEY = "surfsense_refresh_token"; + +export function AuthCutoverPurge() { + useEffect(() => { + try { + if (localStorage.getItem(CUTOVER_FLAG_KEY) === "true") return; + localStorage.removeItem(LEGACY_BEARER_TOKEN_KEY); + localStorage.removeItem(LEGACY_REFRESH_TOKEN_KEY); + localStorage.setItem(CUTOVER_FLAG_KEY, "true"); + } catch { + // Storage can be unavailable in private mode; cookie auth still works. + } + }, []); + + return null; +}