fix: proper token refreshing BEFORE sending req

This commit is contained in:
2025-05-29 12:34:52 +02:00
parent 03d6730151
commit 3dd91cf238

View File

@ -4,65 +4,100 @@ import { useAuth } from "@/store/auth";
import Axios, { type AxiosResponse } from "axios"; import Axios, { type AxiosResponse } from "axios";
import { refreshTokenApi } from "./refresh"; import { refreshTokenApi } from "./refresh";
import { isExpired } from "react-jwt";
export const axios = Axios.create({ export const axios = Axios.create({
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); });
axios.interceptors.request.use( let isRefreshing = false;
(request) => { let refreshQueue: ((token: string | null) => void)[] = [];
const account = useAuth.getState().activeAccount;
if (account?.access) {
request.headers["Authorization"] = `Bearer ${account.access}`;
}
return request; const waitForTokenRefresh = () => {
}, return new Promise<string | null>((resolve) => {
(error) => { refreshQueue.push((token: string | null) => resolve(token));
return Promise.reject(error); });
} };
);
axios.interceptors.response.use( const processRefreshQueue = async (token: string | null) => {
(response) => response, refreshQueue.forEach((resolve) => resolve(token));
async (error) => { refreshQueue = [];
const originalRequest = error.config; };
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const account = useAuth.getState().activeAccount;
if (!account?.refresh) {
return Promise.reject(new Error("Unauthorized. No refresh token"));
}
const refreshToken = async (
accountId: string,
refreshToken: string
): Promise<{ access: string; refresh: string }> => {
const db = useDbStore.getState().db; const db = useDbStore.getState().db;
const loadAccounts = useAuth.getState().loadAccounts;
const requireSignIn = useAuth.getState().requireSignIn;
if (!db) { if (!db) {
return Promise.reject(new Error("No database connection")); console.log("No database connection available.");
return Promise.reject("No database connection available.");
} }
try { try {
const response = await refreshTokenApi(account.refresh); const response = await refreshTokenApi(refreshToken);
updateAccountTokens(db, { await updateAccountTokens(db, {
accountId: account.accountId, accountId: accountId,
access: response.access, access: response.access,
refresh: response.refresh, refresh: response.refresh,
}); });
processRefreshQueue(response.access);
return { access: response.access, refresh: response.refresh };
} catch (err) { } catch (err) {
console.error("token refresh failed:", err); console.error("Token refresh failed:", err);
await deleteAccount(db, account.accountId); await deleteAccount(db, accountId);
const loadAccounts = useAuth.getState().loadAccounts;
loadAccounts?.();
const requireSignIn = useAuth.getState().requireSignIn;
requireSignIn?.(); requireSignIn?.();
return Promise.reject(err); processRefreshQueue(null);
throw err;
} finally {
localStorage.removeItem("refreshing");
loadAccounts?.();
isRefreshing = false;
} }
};
axios.interceptors.request.use(
async (request) => {
const account = useAuth.getState().activeAccount;
let token: string | null = account?.access ?? null;
if (!token || !isExpired(token)) {
request.headers["Authorization"] = `Bearer ${token}`;
return request;
} }
return Promise.reject(error); if (!isRefreshing) {
isRefreshing = true;
try {
const { access } = await refreshToken(
account!.accountId,
account!.refresh
);
token = access;
} catch (err) {
console.error("Token refresh failed:", err);
throw err;
} }
} else {
token = await waitForTokenRefresh();
}
if (!token) {
throw new Error("No token available");
}
request.headers["Authorization"] = `Bearer ${token}`;
return request;
},
(error) => Promise.reject(error)
); );
export const handleApiError = async (response: AxiosResponse) => { export const handleApiError = async (response: AxiosResponse) => {