diff --git a/surfsense_web/hooks/use-session.ts b/surfsense_web/hooks/use-session.ts index 0012e8763..207475eae 100644 --- a/surfsense_web/hooks/use-session.ts +++ b/surfsense_web/hooks/use-session.ts @@ -1,7 +1,7 @@ "use client"; import { useCallback, useEffect, useState } from "react"; -import { refreshSession } from "@/lib/auth-utils"; +import { authenticatedFetch } from "@/lib/auth-fetch"; import { buildBackendUrl } from "@/lib/env-config"; type SessionState = @@ -9,22 +9,6 @@ type SessionState = | { status: "authenticated"; authenticated: true; accessExpiresAt: number | null } | { status: "unauthenticated"; authenticated: false; accessExpiresAt: null }; -async function getSessionHeaders(): Promise { - if (typeof window === "undefined" || !window.electronAPI?.getAccessToken) { - return {}; - } - - const token = await window.electronAPI.getAccessToken(); - return token ? { Authorization: `Bearer ${token}` } : {}; -} - -async function fetchSession(): Promise { - return fetch(buildBackendUrl("/auth/session"), { - credentials: "include", - headers: await getSessionHeaders(), - }); -} - export function useSession() { const [state, setState] = useState({ status: "loading", @@ -34,13 +18,9 @@ export function useSession() { const refresh = useCallback(async () => { try { - let response = await fetchSession(); - if (response.status === 401) { - const refreshed = await refreshSession(); - if (refreshed) { - response = await fetchSession(); - } - } + const response = await authenticatedFetch(buildBackendUrl("/auth/session"), { + skipAuthRedirect: true, + }); if (!response.ok) { setState({ status: "unauthenticated", diff --git a/surfsense_web/lib/auth-fetch.ts b/surfsense_web/lib/auth-fetch.ts index 4f512c2ca..a79777825 100644 --- a/surfsense_web/lib/auth-fetch.ts +++ b/surfsense_web/lib/auth-fetch.ts @@ -7,6 +7,12 @@ type DesktopAccessTokenOptions = { forceRefresh?: boolean; }; +type AuthenticatedFetchOptions = RequestInit & { + skipAuthRedirect?: boolean; + skipRefresh?: boolean; + forceDesktopTokenRefresh?: boolean; +}; + function subscribeToDesktopAuth(): void { if (didSubscribeToDesktopAuth || typeof window === "undefined" || !window.electronAPI) { return; @@ -40,42 +46,61 @@ export function getAuthHeaders(additionalHeaders?: Record): Reco }; } +async function fetchWithAuth( + url: string, + options: RequestInit, + { forceDesktopTokenRefresh = false }: { forceDesktopTokenRefresh?: boolean } = {} +): Promise { + const headers = new Headers(options.headers); + const token = await getDesktopAccessToken({ forceRefresh: forceDesktopTokenRefresh }); + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + return fetch(url, { + ...options, + headers, + credentials: options.credentials ?? "include", + }); +} + export async function authenticatedFetch( url: string, - options?: RequestInit & { skipAuthRedirect?: boolean; skipRefresh?: boolean } + options: AuthenticatedFetchOptions = {} ): Promise { - const { skipAuthRedirect = false, skipRefresh = false, ...fetchOptions } = options || {}; - const token = await getDesktopAccessToken(); - const headers = { - ...(fetchOptions.headers as Record), - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }; + const { + skipAuthRedirect = false, + skipRefresh = false, + forceDesktopTokenRefresh = false, + ...fetchOptions + } = options; - const response = await fetch(url, { - ...fetchOptions, - headers, - credentials: "include", + const response = await fetchWithAuth(url, fetchOptions, { + forceDesktopTokenRefresh, }); - if (response.status === 401 && !skipAuthRedirect) { - if (!skipRefresh) { - const refreshed = await refreshSession(); - if (refreshed) { - const newToken = await getDesktopAccessToken({ forceRefresh: true }); - return fetch(url, { - ...fetchOptions, - headers: { - ...(fetchOptions.headers as Record), - ...(newToken ? { Authorization: `Bearer ${newToken}` } : {}), - }, - credentials: "include", - }); - } - } + if (response.status !== 401) { + return response; + } + let unauthorizedResponse = response; + if (!skipRefresh) { + const refreshed = await refreshSession(); + if (refreshed) { + const retryResponse = await fetchWithAuth(url, fetchOptions, { + forceDesktopTokenRefresh: true, + }); + if (retryResponse.status !== 401) { + return retryResponse; + } + unauthorizedResponse = retryResponse; + } + } + + if (!skipAuthRedirect) { handleUnauthorized(); throw new Error("Unauthorized: Redirecting to login page"); } - return response; + return unauthorizedResponse; }