From: Michael Tremer Date: Fri, 4 Jul 2025 13:15:52 +0000 (+0000) Subject: frontend: Rebase the API calls on axios X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b2ce6a2083bfb497ec74e4f4eba2bd3fbf9f4dcc;p=pbs.git frontend: Rebase the API calls on axios This will transparently handle authentication. Signed-off-by: Michael Tremer --- diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 2c56d1c8..0b59190d 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -1,5 +1,5 @@ +import api from "@/api" import type { User } from "@/types/User" -import { fetchWithAuth } from "@/utils/fetchWithAuth" interface AuthResponse { access_token: string; @@ -10,7 +10,7 @@ interface AuthResponse { Access Token */ -function getAccessToken(): string | null { +export function getAccessToken(): string | null { return localStorage.getItem("token"); } @@ -27,41 +27,38 @@ export function deleteAccessToken(): void { */ export async function authenticateUser(username: string, password: string): Promise { - const response = await fetch("/api/v1/auth/user", { - method : "POST", - - // Headers - headers : { - "Content-Type" : "application/x-www-form-urlencoded", - }, - - // Body - body : new URLSearchParams({ + try { + const params = new URLSearchParams({ username : username, password : password, - }), - }); - if (!response.ok) { - throw new Error("Invalid username or password"); - } + }) + + const response = await api.post("/v1/auth/user", params, { + headers: { + "Content-Type" : "application/x-www-form-urlencoded", + }, + }) - // Parse the response - const data: AuthResponse = await response.json(); + const data: AuthResponse = response.data; - // Store the access token - setAccessToken(data.access_token); + // Store the access token from response + setAccessToken(data.access_token) - // Fetch the logged in user - return await fetchCurrentUser(); + // Fetch and return the logged in user + return await fetchCurrentUser() + } catch (error) { + throw new Error("Invalid username or password") + } } /* Returns the currently logged in user */ export async function fetchCurrentUser(): Promise { - const res = await fetchWithAuth("/api/v1/auth/whoami"); - if (!res.ok) - throw new Error("Failed to load user"); - - return res.json(); + try { + const response = await api.get("/v1/auth/whoami") + return response.data + } catch (error) { + throw new Error("Failed to load user") + } } diff --git a/frontend/src/api/builders.ts b/frontend/src/api/builders.ts index 617db273..b05dd3ea 100644 --- a/frontend/src/api/builders.ts +++ b/frontend/src/api/builders.ts @@ -1,10 +1,12 @@ +import api from "@/api" import type { Builder } from "@/types/Builder"; +// Fetch all builders export async function fetchBuilders(): Promise { - // Fetch all builders - const response = await fetch("/api/v1/builders"); - if (!response.ok) - throw new Error("Failed to fetch builders"); - - return response.json(); + try { + const response = await api.get("/v1/builders"); + return response.data; + } catch (error) { + throw new Error("Failed to load builders"); + } } diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 00000000..ddb3d822 --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,44 @@ +import axios from "axios" + +// Fetch authentication +import { useAuthStore } from "@/stores/auth" + +// API Authentication +import { getAccessToken } from "@/api/auth" + +// Create a new API client +const api = axios.create({ + baseURL: "/api", +}) + +// Authenticate if we are logged in +api.interceptors.request.use((config) => { + // Fetch the access token + const access_token: string | null = getAccessToken() + + // Add the Authorization header if a token is available + if (access_token) { + config.headers.Authorization = `Bearer ${access_token}` + } + + return config +}) + +// Log out the user if a request has been unauthenticated +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + const auth = useAuthStore() + auth.logout() + + // XXX Should we go back to the log in page? + // router.push("/login") + } + + return Promise.reject(error) + } +) + +// Export the API +export default api diff --git a/frontend/src/api/mirrors.ts b/frontend/src/api/mirrors.ts index 82b92f08..a082f335 100644 --- a/frontend/src/api/mirrors.ts +++ b/frontend/src/api/mirrors.ts @@ -1,10 +1,12 @@ +import api from "@/api" import type { Mirror } from "@/types/Mirror"; +// Fetch all mirrors export async function fetchMirrors(): Promise { - // Fetch all mirrors - const response = await fetch("/api/v1/mirrors"); - if (!response.ok) - throw new Error("Failed to fetch mirrors"); - - return response.json(); + try { + const response = await api.get("/v1/mirrors"); + return response.data; + } catch (error) { + throw new Error("Failed to load mirrors"); + } } diff --git a/frontend/src/utils/fetchWithAuth.ts b/frontend/src/utils/fetchWithAuth.ts deleted file mode 100644 index 0824bb6b..00000000 --- a/frontend/src/utils/fetchWithAuth.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { isTokenExpired } from './jwt' - -interface RefreshResponse { - access_token: string; - refresh_token?: string; -} - -async function refreshToken(): Promise { - try { - const response = await fetch("/api/v1/auth/refresh", { - method: "POST", - credentials: "include", - }); - if (!response.ok) - return false; - - // Parse the response - const data: RefreshResponse = await response.json(); - - // Store the new access token - localStorage.setItem("token", data.access_token); - - return true; - - } catch { - return false; - } -} - -export async function fetchWithAuth(input: RequestInfo, init?: RequestInit): Promise { - let token = localStorage.getItem("token"); - - if (!token || isTokenExpired(token)) { - const refreshed = await refreshToken(); - if (!refreshed) { - // no valid token and refresh failed — maybe logout user here or throw - throw new Error('Not authenticated'); - } - - // Check if we have received a token - token = localStorage.getItem("token"); - if (!token) - throw new Error('No access token after refresh'); - } - - const authHeaders = { - ...(init?.headers || {}), - Authorization: `Bearer ${token}`, - }; - - const fetchInit = { - ...init, - headers: authHeaders, - }; - - return fetch(input, fetchInit); -}