From 81cb98aaa741c7a8981a0a73fe5d88c211b40a80 Mon Sep 17 00:00:00 2001 From: pablonyx Date: Thu, 13 Mar 2025 16:09:53 -0700 Subject: [PATCH] k --- backend/onyx/auth/routes.py | 6 -- backend/onyx/main.py | 3 +- web/src/components/user/UserProvider.tsx | 79 ++------------------- web/src/hooks/useTokenRefresh.ts | 89 ++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 82 deletions(-) delete mode 100644 backend/onyx/auth/routes.py create mode 100644 web/src/hooks/useTokenRefresh.ts diff --git a/backend/onyx/auth/routes.py b/backend/onyx/auth/routes.py deleted file mode 100644 index 0dcd149984..0000000000 --- a/backend/onyx/auth/routes.py +++ /dev/null @@ -1,6 +0,0 @@ -from fastapi import APIRouter - - -router = APIRouter() - -# Add production endpoints here diff --git a/backend/onyx/main.py b/backend/onyx/main.py index fc181a98b6..812ac558be 100644 --- a/backend/onyx/main.py +++ b/backend/onyx/main.py @@ -423,8 +423,7 @@ def get_application() -> FastAPI: # NOTE: needs to be outside of the `if __name__ == "__main__"` block so that the # app is exportable set_is_ee_based_on_env_variable() -# Call get_application() to get the actual application instance -app = fetch_versioned_implementation(module="onyx.main", attribute="get_application")() +app = fetch_versioned_implementation(module="onyx.main", attribute="get_application") if __name__ == "__main__": diff --git a/web/src/components/user/UserProvider.tsx b/web/src/components/user/UserProvider.tsx index 00f90fa124..c5e8a58e78 100644 --- a/web/src/components/user/UserProvider.tsx +++ b/web/src/components/user/UserProvider.tsx @@ -12,6 +12,7 @@ import { getCurrentUser } from "@/lib/user"; import { usePostHog } from "posthog-js/react"; import { CombinedSettings } from "@/app/admin/settings/interfaces"; import { SettingsContext } from "../settings/SettingsProvider"; +import { useTokenRefresh } from "@/hooks/useTokenRefresh"; interface UserContextType { user: User | null; @@ -71,12 +72,6 @@ export function UserProvider({ mergeUserPreferences(user, settings) ); - // Track last refresh time to avoid unnecessary calls - const [lastTokenRefresh, setLastTokenRefresh] = useState(Date.now()); - - // Use a ref to track first load - const isFirstLoad = useRef(true); - useEffect(() => { setUpToDateUser(mergeUserPreferences(user, updatedSettings)); }, [user, updatedSettings]); @@ -97,74 +92,6 @@ export function UserProvider({ } }, [posthog, user]); - // Implement token refresh mechanism - useEffect(() => { - if (!upToDateUser) return; - - // Refresh token every 10 minutes (600000ms) - // This is shorter than the session expiry time to ensure tokens stay valid - const REFRESH_INTERVAL = 600000; - - const refreshTokenPeriodically = async () => { - try { - // Skip time check if this is first load - we always refresh on first load - const isTimeToRefresh = - isFirstLoad.current || - Date.now() - lastTokenRefresh > REFRESH_INTERVAL - 60000; - - if (!isTimeToRefresh) { - return; - } - - // Reset first load flag - if (isFirstLoad.current) { - isFirstLoad.current = false; - } - - const response = await fetch("/api/auth/refresh", { - method: "POST", - credentials: "include", - }); - - if (response.ok) { - // Update last refresh time on success - setLastTokenRefresh(Date.now()); - console.debug("Auth token refreshed successfully"); - } else { - console.warn("Failed to refresh auth token:", response.status); - // If token refresh fails, try to get current user info - await fetchUser(); - } - } catch (error) { - console.error("Error refreshing auth token:", error); - } - }; - - // Always attempt to refresh on first component mount - // This helps ensure tokens are fresh, especially after browser refresh - refreshTokenPeriodically(); - - // Set up interval for periodic refreshes - const intervalId = setInterval(refreshTokenPeriodically, REFRESH_INTERVAL); - - // Also refresh token on window focus, but no more than once per minute - const handleVisibilityChange = () => { - if ( - document.visibilityState === "visible" && - Date.now() - lastTokenRefresh > 60000 - ) { - refreshTokenPeriodically(); - } - }; - - document.addEventListener("visibilitychange", handleVisibilityChange); - - return () => { - clearInterval(intervalId); - document.removeEventListener("visibilitychange", handleVisibilityChange); - }; - }, [upToDateUser, lastTokenRefresh]); - const fetchUser = async () => { try { const currentUser = await getCurrentUser(); @@ -173,6 +100,10 @@ export function UserProvider({ console.error("Error fetching current user:", error); } }; + + // Use the custom token refresh hook + useTokenRefresh(upToDateUser, fetchUser); + const updateUserTemperatureOverrideEnabled = async (enabled: boolean) => { try { setUpToDateUser((prevUser) => { diff --git a/web/src/hooks/useTokenRefresh.ts b/web/src/hooks/useTokenRefresh.ts new file mode 100644 index 0000000000..28e8c38c6c --- /dev/null +++ b/web/src/hooks/useTokenRefresh.ts @@ -0,0 +1,89 @@ +import { useState, useEffect, useRef } from "react"; +import { User } from "@/lib/types"; + +/** + * Custom hook for handling JWT token refresh + * + * @param user The current user or null if not logged in + * @param onRefreshFail Callback function to execute if token refresh fails + * @returns Object containing the last token refresh timestamp + */ +export function useTokenRefresh( + user: User | null, + onRefreshFail: () => Promise +) { + // Track last refresh time to avoid unnecessary calls + const [lastTokenRefresh, setLastTokenRefresh] = useState(Date.now()); + + // Use a ref to track first load + const isFirstLoad = useRef(true); + + useEffect(() => { + if (!user) return; + + // Refresh token every 10 minutes (600000ms) + // This is shorter than the session expiry time to ensure tokens stay valid + const REFRESH_INTERVAL = 600000; + + const refreshTokenPeriodically = async () => { + try { + // Skip time check if this is first load - we always refresh on first load + const isTimeToRefresh = + isFirstLoad.current || + Date.now() - lastTokenRefresh > REFRESH_INTERVAL - 60000; + + if (!isTimeToRefresh) { + return; + } + + // Reset first load flag + if (isFirstLoad.current) { + isFirstLoad.current = false; + } + + const response = await fetch("/api/auth/refresh", { + method: "POST", + credentials: "include", + }); + + if (response.ok) { + // Update last refresh time on success + setLastTokenRefresh(Date.now()); + console.debug("Auth token refreshed successfully"); + } else { + console.warn("Failed to refresh auth token:", response.status); + // If token refresh fails, try to get current user info + await onRefreshFail(); + } + } catch (error) { + console.error("Error refreshing auth token:", error); + } + }; + + // Always attempt to refresh on first component mount + // This helps ensure tokens are fresh, especially after browser refresh + refreshTokenPeriodically(); + + // Set up interval for periodic refreshes + const intervalId = setInterval(refreshTokenPeriodically, REFRESH_INTERVAL); + + // Also refresh token on window focus, but no more than once per minute + const handleVisibilityChange = () => { + if ( + document.visibilityState === "visible" && + Date.now() - lastTokenRefresh > 60000 + ) { + refreshTokenPeriodically(); + } + }; + + document.addEventListener("visibilitychange", handleVisibilityChange); + + return () => { + clearInterval(intervalId); + document.removeEventListener("visibilitychange", handleVisibilityChange); + }; + }, [user, lastTokenRefresh, onRefreshFail]); + + return { lastTokenRefresh }; +}