diff --git a/frontend/src/services/auth.ts b/frontend/src/services/auth.ts new file mode 100644 index 0000000..a61a6d3 --- /dev/null +++ b/frontend/src/services/auth.ts @@ -0,0 +1,107 @@ +import axios from "axios"; + +const API_URL = "/api"; + +const apiInstance = axios.create({ + baseURL: API_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +// Helper function to refresh tokens +const refreshAccessToken = async () => { + try { + const refreshToken = localStorage.getItem("refresh_token"); + if (!refreshToken) throw new Error("No refresh token available"); + + const response = await axios.post(`${API_URL}/auth/token/refresh/`, { + refresh: refreshToken, + }); + + const newAccessToken = response.data.access; + const newRefreshToken = response.data.refresh || refreshToken; // Update if rotated + + localStorage.setItem("access_token", newAccessToken); + localStorage.setItem("refresh_token", newRefreshToken); + + return newAccessToken; + } catch (error) { + console.error("Failed to refresh token:", error); + logout(); // Log out if refresh fails + return null; + } +}; + +// Request Interceptor - Attaches access token +apiInstance.interceptors.request.use( + async (config) => { + const token = localStorage.getItem("access_token"); + if (token) { + config.headers["Authorization"] = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) +); + +// Response Interceptor - Refreshes token if needed +apiInstance.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + if (error.response) { + // Check for 401 Unauthorized + if (error.response.status === 401) { + console.log("401 Unauthorized detected."); + } + + // Check for 403 with token invalid message + if (error.response.status === 403 && error.response.data?.code === "token_not_valid") { + console.log("403 Forbidden detected with 'token_not_valid'."); + } + + // Handle both cases + if ((error.response.status === 401 || error.response.status === 403) && !originalRequest._retry) { + console.log("Attempting token refresh..."); + + originalRequest._retry = true; + + const newAccessToken = await refreshAccessToken(); + if (newAccessToken) { + originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`; + return apiInstance(originalRequest); + } else { + console.log("Token refresh failed, logging out user."); + } + } + } + + return Promise.reject(error); + } +); + +// Login function +export const login = async (username: string, password: string) => { + const response = await apiInstance.post("/auth/token/", { username, password }); + localStorage.setItem("access_token", response.data.access); + localStorage.setItem("refresh_token", response.data.refresh); + return response.data; +}; + +// Logout function +export const logout = () => { + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + window.location.href = "/user/login"; // Redirect to login page +}; + +// Fetch posts example +export const fetchPosts = async () => { + const response = await apiInstance.get("/posts/"); + return response.data; +}; + +export default apiInstance; +