import { Middleware } from "@simple-api/core"; import { useAuthStore } from "./auth-store"; /** * Middleware to inject the authentication token into requests. * Skips login, register, and refresh endpoints. */ export const authMiddleware: Middleware = async ({ config, options }, next) => { const { token } = useAuthStore.getState(); // Don't send Authorization header for sensitive auth-related endpoints, // EXCEPT for logout which needs to identify the session. const isAuthPath = config.path === "auth/login" || config.path === "auth/register" || config.path === "auth/refresh"; if (token && !isAuthPath) { options.headers = { ...options.headers, Authorization: `Bearer ${token}`, }; } return await next(options); }; /** * Middleware to handle token refreshment on 401 Unauthorized errors. */ export const refreshMiddleware: Middleware = async ( { config, options }, next, ) => { try { return await next(options); } catch (error: any) { const status = error.status || error.statusCode; const { refreshToken, setAuth, logout } = useAuthStore.getState(); // Skip refresh logic for the login/refresh endpoints themselves const isAuthPath = config.path?.includes("auth/login") || config.path?.includes("auth/refresh"); if (status === 401 && refreshToken && !isAuthPath) { console.log( `[API Refresh] 401 detected for ${config.path}. Attempting refresh...`, ); try { // We call the refresh endpoint manually here to avoid circular dependencies with the 'api' object const refreshUrl = `${config.baseUrl}auth/refresh`; const response = await fetch(refreshUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ refreshToken, refresh_token: refreshToken, }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const refreshErr = new Error( errorData.message || `Refresh failed with status ${response.status}`, ) as any; refreshErr.status = response.status; throw refreshErr; } const data = await response.json(); // Backend might return snake_case (access_token) or camelCase (accessToken) // We handle both to be safe when using raw fetch const accessToken = data.accessToken || data.access_token; const newRefreshToken = data.refreshToken || data.refresh_token; const user = data.user; if (!accessToken) { throw new Error("No access token returned from refresh"); } setAuth(user, accessToken, newRefreshToken); console.log("[API Refresh] Success. Retrying original request..."); // Update headers and retry options.headers = { ...options.headers, Authorization: `Bearer ${accessToken}`, }; return await next(options); } catch (refreshError: any) { // Only logout if the refresh token itself is invalid (400, 401, 403) // If it's a network error, we should NOT logout the user. const refreshStatus = refreshError.status || refreshError.statusCode; const isAuthError = refreshStatus === 401; if (isAuthError) { console.error("[API Refresh] Invalid refresh token. Logging out."); logout(); } else { console.error( "[API Refresh] Network error or server issues during refresh. Staying logged in.", ); } throw refreshError; } } throw error; } };