]> git.ipfire.org Git - pbs.git/commitdiff
frontend: Rebase the API calls on axios
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 4 Jul 2025 13:15:52 +0000 (13:15 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 4 Jul 2025 13:15:52 +0000 (13:15 +0000)
This will transparently handle authentication.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
frontend/src/api/auth.ts
frontend/src/api/builders.ts
frontend/src/api/index.ts [new file with mode: 0644]
frontend/src/api/mirrors.ts
frontend/src/utils/fetchWithAuth.ts [deleted file]

index 2c56d1c8bbc102a46173b5b1619121bf7854efaa..0b59190d0c0bf5cdf9452a81f86a85f8d7450da0 100644 (file)
@@ -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<User> {
-       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<User> {
-       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")
+       }
 }
index 617db273f0628f4e5bb043e563f6f154f4a750f8..b05dd3ea53730ad4f28aac93ded5f44591d9cdd1 100644 (file)
@@ -1,10 +1,12 @@
+import api from "@/api"
 import type { Builder } from "@/types/Builder";
 
+// Fetch all builders
 export async function fetchBuilders(): Promise<Builder[]> {
-       // 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 (file)
index 0000000..ddb3d82
--- /dev/null
@@ -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
index 82b92f08d4900d87f3b4a11fdaf69516cf222135..a082f3351ed3551444dfca57c6024a1f9685ebb0 100644 (file)
@@ -1,10 +1,12 @@
+import api from "@/api"
 import type { Mirror } from "@/types/Mirror";
 
+// Fetch all mirrors
 export async function fetchMirrors(): Promise<Mirror[]> {
-       // 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 (file)
index 0824bb6..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import { isTokenExpired } from './jwt'
-
-interface RefreshResponse {
-  access_token: string;
-  refresh_token?: string;
-}
-
-async function refreshToken(): Promise<boolean> {
-       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<Response> {
-       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);
-}