diff --git a/src/contexts/User.ctx.tsx b/src/contexts/User.ctx.tsx index 4b1ad75..9d99e59 100644 --- a/src/contexts/User.ctx.tsx +++ b/src/contexts/User.ctx.tsx @@ -29,6 +29,17 @@ interface UserContextType { const UserContext = createContext(undefined); +// Global logout callback for use in non-React contexts (e.g., API interceptors) +let globalLogoutCallback: (() => void) | null = null; + +export const registerLogoutCallback = (callback: () => void) => { + globalLogoutCallback = callback; +}; + +export const getLogoutCallback = (): (() => void) | null => { + return globalLogoutCallback; +}; + interface UserProviderProps { children: ReactNode; } @@ -47,6 +58,14 @@ export const UserProvider: React.FC = ({ children }) => { Cookies.remove(COOKIE_KEYS.user); window.location.href = "/auth/sign-in"; }, [router]); + + // Register logout callback for use in API interceptors + useEffect(() => { + registerLogoutCallback(logout); + return () => { + globalLogoutCallback = null; + }; + }, [logout]); useEffect(() => { try { const tokenFromCookie = Cookies.get(COOKIE_KEYS.access_token); diff --git a/src/middleware/response-interceptor.ts b/src/middleware/response-interceptor.ts index 34868fe..9d7f84f 100644 --- a/src/middleware/response-interceptor.ts +++ b/src/middleware/response-interceptor.ts @@ -1,6 +1,7 @@ import { AxiosError, AxiosResponse } from "axios"; import Cookies from "js-cookie"; import { toast } from "react-toastify"; +import { getLogoutCallback } from "../contexts/User.ctx"; import { API_ENDPOINTS, api } from "../lib/api"; import { COOKIE_KEYS } from "../lib/shared/cookies"; @@ -31,6 +32,27 @@ export const responseErrorHandler = async (error: AxiosError) => { API_ENDPOINTS.USER.LOGIN, ); + const isRefreshTokenRequest = originalRequest?.url?.includes( + API_ENDPOINTS.USER.REFRESH_TOKEN, + ); + + // If we get a 401 on refresh token endpoint, logout immediately (token refresh failed) + if (error.response?.status === 401 && isRefreshTokenRequest) { + const logout = getLogoutCallback(); + if (logout) { + logout(); + } else { + // Fallback if logout callback is not registered + Cookies.remove(COOKIE_KEYS.access_token); + Cookies.remove(COOKIE_KEYS.refresh_token); + Cookies.remove(COOKIE_KEYS.user); + if (typeof window !== "undefined") { + window.location.href = "/auth/sign-in"; + } + } + return Promise.reject(error); + } + if ( error.response?.status === 401 && !originalRequest._retry && @@ -55,7 +77,20 @@ export const responseErrorHandler = async (error: AxiosError) => { try { const refreshToken = Cookies.get(COOKIE_KEYS.refresh_token); if (!refreshToken) { - throw new Error("Refresh token not found."); + // No refresh token available, logout immediately + const logout = getLogoutCallback(); + if (logout) { + logout(); + } else { + // Fallback if logout callback is not registered + Cookies.remove(COOKIE_KEYS.access_token); + Cookies.remove(COOKIE_KEYS.refresh_token); + Cookies.remove(COOKIE_KEYS.user); + if (typeof window !== "undefined") { + window.location.href = "/auth/sign-in"; + } + } + return Promise.reject(new Error("Refresh token not found.")); } const response = await api.post(API_ENDPOINTS.USER.REFRESH_TOKEN, { @@ -81,14 +116,21 @@ export const responseErrorHandler = async (error: AxiosError) => { return api(originalRequest); } catch (refreshError) { processQueue(refreshError, null); - Cookies.remove(COOKIE_KEYS.access_token); - Cookies.remove(COOKIE_KEYS.refresh_token); - Cookies.remove(COOKIE_KEYS.user); - - if (typeof window !== "undefined") { - localStorage.removeItem("auth-token"); - localStorage.removeItem("user-data"); - window.location.href = "/login"; + + // Use logout callback from User context + const logout = getLogoutCallback(); + if (logout) { + logout(); + } else { + // Fallback if logout callback is not registered + Cookies.remove(COOKIE_KEYS.access_token); + Cookies.remove(COOKIE_KEYS.refresh_token); + Cookies.remove(COOKIE_KEYS.user); + if (typeof window !== "undefined") { + localStorage.removeItem("auth-token"); + localStorage.removeItem("user-data"); + window.location.href = "/auth/sign-in"; + } } const errorMessage =