refactor(auth): send login token through apiFetch
Build, Test & Push Frontend / quality-check (push) Successful in 13s
Build, Test & Push Frontend / docker (push) Successful in 23s

All authenticated calls now use apiFetch, and only utils/auth.js
touches the stored token. The cookie login can then change these two
files instead of every page.
This commit is contained in:
lobo committed 2026-09-25 11:29:14 +02:00
1 parent 085fcf15a4
commit 5ff8d48c53
18 files changed
+127 -70

No files matched your search

+12 -13
View File
@@ -25,6 +25,8 @@ import SteamAvatar from '../components/SteamAvatar.jsx';
import useSteamProfile from '../hooks/useSteamProfile.js';
import {cn} from '@/lib/utils';
import {usePreferences} from '../context/PreferencesContext.jsx';
import {apiFetch} from '../utils/api.js';
import {getToken} from '../utils/auth.js';
const TOP_GAMES = 5;
// The games endpoint accepts only the page sizes 20, 40 and 80.
@@ -32,10 +34,7 @@ const TOP_GAMES_URL = '/api/library/games?page=0&size=20&sort=playtimeMinutes%2C
const PLAYING_GAMES = 4;
const PLAYING_URL = '/api/library/games?page=0&size=20&sort=playtimeMinutes%2Cdesc&status=PLAYING';
const authHeaders = () => ({
'Authorization': `Bearer ${localStorage.getItem('tbp_jwt')}`,
'Content-Type': 'application/json',
});
const JSON_HEADERS = {'Content-Type': 'application/json'};
const Dashboard = () => {
const {t} = useTranslation();
@@ -55,7 +54,7 @@ const Dashboard = () => {
const fetchSteamCount = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/games-count`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/games-count`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return;
@@ -70,7 +69,7 @@ const Dashboard = () => {
const fetchTopGames = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}${TOP_GAMES_URL}`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}${TOP_GAMES_URL}`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return;
@@ -86,7 +85,7 @@ const Dashboard = () => {
const fetchPlaying = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}${PLAYING_URL}`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}${PLAYING_URL}`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return;
@@ -107,7 +106,7 @@ const Dashboard = () => {
const fetchSummary = useCallback(async (excludeAppid) => {
const query = excludeAppid ? `?exclude=${excludeAppid}` : '';
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/summary${query}`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/summary${query}`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return true;
@@ -126,7 +125,7 @@ const Dashboard = () => {
const fetchActivity = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/activity`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/activity`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return;
@@ -141,7 +140,7 @@ const Dashboard = () => {
const fetchAchievements = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/achievements/summary`, {headers: authHeaders()});
const r = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/achievements/summary`, {headers: JSON_HEADERS});
if (r.status === 401) {
expireSession();
return;
@@ -173,15 +172,15 @@ const Dashboard = () => {
useEffect(() => {
const fetchUserData = async () => {
if (!localStorage.getItem('tbp_jwt')) {
if (!getToken()) {
navigate('/login');
return;
}
try {
const profileResponse = await fetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
const profileResponse = await apiFetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
method: 'GET',
headers: authHeaders(),
headers: JSON_HEADERS,
});
if (profileResponse.ok) {
+4 -4
View File
@@ -19,6 +19,8 @@ import useSessionExpired from '../hooks/useSessionExpired.js';
import useSaveAppearance from '../hooks/useSaveAppearance.js';
import {usePreferences} from '../context/PreferencesContext.jsx';
import PageContainer from '../components/PageContainer.jsx';
import {apiFetch} from '../utils/api.js';
import {getToken} from '../utils/auth.js';
const formatUnlockedAt = (instant, locale) => {
if (!instant) return '';
@@ -43,8 +45,7 @@ export default function GameDetail() {
useEffect(() => {
let cancelled = false;
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return;
}
@@ -53,9 +54,8 @@ export default function GameDetail() {
(async () => {
try {
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games/${appid}`;
const response = await fetch(url, {
const response = await apiFetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
+4 -4
View File
@@ -19,6 +19,8 @@ import GameCover from '../components/GameCover.jsx';
import LibraryTabs from '../components/LibraryTabs.jsx';
import GameStatusBadge from '../components/GameStatusBadge.jsx';
import PageContainer from '../components/PageContainer.jsx';
import {apiFetch} from '../utils/api.js';
import {getToken} from '../utils/auth.js';
const PAGE_SIZES = [20, 40, 80];
const DEFAULT_SIZE = 20;
@@ -58,8 +60,7 @@ export default function GameLibrary() {
const [error, setError] = useState(null);
const load = useCallback(async () => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return;
}
@@ -67,9 +68,8 @@ export default function GameLibrary() {
setError(null);
try {
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?${libraryQuery({page, size, sort, status, q, maxMinutes: shortOnly ? SHORT_GAME_MINUTES : null})}`;
const response = await fetch(url, {
const response = await apiFetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
+2 -2
View File
@@ -8,7 +8,7 @@ import {Input} from '@/components/ui/input';
import {Label} from '@/components/ui/label';
import AuthLayout from '../components/AuthLayout.jsx';
import FeedbackAlert from '../components/FeedbackAlert.jsx';
import {getValidToken, safeReturnPath} from '../utils/auth.js';
import {getValidToken, safeReturnPath, setToken} from '../utils/auth.js';
const LOGIN_ERROR_KEY = {
INVALID_CREDENTIALS: 'LOGIN.ERROR_INVALID_CREDENTIALS',
@@ -51,7 +51,7 @@ export default function LoginPage() {
const data = await response.json().catch(() => ({}));
if (response.ok) {
localStorage.setItem('tbp_jwt', data.token);
setToken(data.token);
navigate(target, {replace: true});
} else {
const minutes = Math.ceil(Number(response.headers.get('Retry-After') ?? 900) / 60);
+5 -5
View File
@@ -13,6 +13,8 @@ import PageContainer from '../components/PageContainer.jsx';
import useSessionExpired from '../hooks/useSessionExpired.js';
import {saveGameStatus} from '../utils/backlog.js';
import {formatPlaytime} from '../utils/games.js';
import {apiFetch} from '../utils/api.js';
import {getToken} from '../utils/auth.js';
// maxMinutes is IGDB's normal time to beat; a limit leaves out games without IGDB data.
const BUDGETS = [
@@ -45,8 +47,7 @@ export default function PlayNextPage() {
const latestRequest = useRef(0);
const load = useCallback(async (maxMinutes, exclude) => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return;
}
@@ -54,9 +55,8 @@ export default function PlayNextPage() {
setLoading(true);
setError(null);
try {
const response = await fetch(
`${import.meta.env.VITE_CORE_API_URL}/api/library/picker?${pickerQuery(maxMinutes, exclude)}`,
{headers: {'Authorization': `Bearer ${token}`}});
const response = await apiFetch(
`${import.meta.env.VITE_CORE_API_URL}/api/library/picker?${pickerQuery(maxMinutes, exclude)}`);
if (request !== latestRequest.current) return;
if (response.status === 401) {
expireSession();
+7 -9
View File
@@ -22,6 +22,8 @@ import useSaveAppearance from '../hooks/useSaveAppearance.js';
import {ACCENTS, CORNERS} from '../utils/appearance.js';
import {coverUrl} from '../utils/games.js';
import {cn} from '@/lib/utils';
import {apiFetch} from '../utils/api.js';
import {getToken, setToken} from '../utils/auth.js';
const BACKEND_ERROR_KEY = {
STEAM_ID_ALREADY_IN_USE: 'REGISTRATION.ERROR_STEAM_ID_IN_USE',
@@ -61,18 +63,16 @@ export default function SettingsPage() {
});
useEffect(() => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return;
}
const load = async () => {
try {
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
const response = await apiFetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
@@ -107,17 +107,15 @@ export default function SettingsPage() {
}, [navigate, expireSession, reset, t]);
const persistSettings = async (payload) => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return {ok: false};
}
try {
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/users/settings`, {
const response = await apiFetch(`${import.meta.env.VITE_API_URL}/api/users/settings`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
@@ -136,7 +134,7 @@ export default function SettingsPage() {
}
const data = await response.json();
localStorage.setItem('tbp_jwt', data.token); // Core reads the steamId from the token
setToken(data.token); // Core reads the steamId from the token
toast.success(t('SETTINGS.SAVED'));
return {ok: true};
} catch (err) {
+4 -5
View File
@@ -11,6 +11,8 @@ import GameCover from '../components/GameCover.jsx';
import LibraryTabs from '../components/LibraryTabs.jsx';
import useSessionExpired from '../hooks/useSessionExpired.js';
import PageContainer from '../components/PageContainer.jsx';
import {apiFetch} from '../utils/api.js';
import {getToken} from '../utils/auth.js';
const formatAdded = (instant, locale) =>
instant ? new Date(instant).toLocaleDateString(locale, {year: 'numeric', month: 'short', day: 'numeric'}) : null;
@@ -24,17 +26,14 @@ export default function WishlistPage() {
const [onlyOnSale, setOnlyOnSale] = useState(false);
useEffect(() => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
if (!getToken()) {
navigate('/login');
return undefined;
}
let cancelled = false;
(async () => {
try {
const response = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/wishlist`, {
headers: {'Authorization': `Bearer ${token}`},
});
const response = await apiFetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/wishlist`);
if (cancelled) return;
if (response.status === 401) {
expireSession();