diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30b14c6..42c3bab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,15 @@
- **start.sh**
- Session directory setup
+- Always sends `credentials: 'include'` and `X-CSRF-Token: window.csrfToken` s
+- On HTTP 403, automatically fetches a fresh CSRF token (from the response header or `/api/auth/token.php`) and retries the request once
+- Always returns the real `Response` object (no more “clone.json” on every 200)
+- Now calls `fetchWithCsrf('/api/auth/token.php')` to guarantee a fresh token
+- Checks `res.ok`, then parses JSON to extract `csrf_token` and `share_url`
+- Updates both `window.csrfToken` and the `` & `` tags
+- Removed Old CSRF logic that cloned every successful response and parsed its JSON body
+- Removed Any “soft-failure” JSON peek on non-403 responses
+
## Changes 4/22/2025 v1.2.3
- Support for custom PUID/PGID via `PUID`/`PGID` environment variables, replacing the need to run the container with `--user`
diff --git a/public/js/auth.js b/public/js/auth.js
index d2d6cee..b3c8025 100644
--- a/public/js/auth.js
+++ b/public/js/auth.js
@@ -52,28 +52,24 @@ const originalFetch = window.fetch;
* @returns {Promise}
*/
export async function fetchWithCsrf(url, options = {}) {
- options = { credentials: 'include', headers: {}, ...options };
- options.headers['X-CSRF-Token'] = window.csrfToken;
+ // 1) Merge in credentials + header
+ options = {
+ credentials: 'include',
+ ...options,
+ };
+ options.headers = {
+ ...(options.headers || {}),
+ 'X-CSRF-Token': window.csrfToken,
+ };
- // 1) First attempt using the original fetch
+ // 2) First attempt
let res = await originalFetch(url, options);
- // 2) Soft‐failure JSON check (200 + {csrf_expired})
- if (res.ok && res.headers.get('content-type')?.includes('application/json')) {
- const clone = res.clone();
- const data = await clone.json();
- if (data.csrf_expired) {
- const newToken = data.csrf_token;
- window.csrfToken = newToken;
- document.querySelector('meta[name="csrf-token"]').content = newToken;
- options.headers['X-CSRF-Token'] = newToken;
- return originalFetch(url, options);
- }
- }
-
- // 3) HTTP 403 fallback
+ // 3) If we got a 403, try to refresh token & retry
if (res.status === 403) {
+ // 3a) See if the server gave us a new token header
let newToken = res.headers.get('X-CSRF-Token');
+ // 3b) Otherwise fall back to the /api/auth/token endpoint
if (!newToken) {
const tokRes = await originalFetch('/api/auth/token.php', { credentials: 'include' });
if (tokRes.ok) {
@@ -82,17 +78,21 @@ export async function fetchWithCsrf(url, options = {}) {
}
}
if (newToken) {
+ // 3c) Update global + meta
window.csrfToken = newToken;
- document.querySelector('meta[name="csrf-token"]').content = newToken;
+ const meta = document.querySelector('meta[name="csrf-token"]');
+ if (meta) meta.content = newToken;
+
+ // 3d) Retry the original request with the new token
options.headers['X-CSRF-Token'] = newToken;
res = await originalFetch(url, options);
}
}
+ // 4) Return the real Response—no body peeking here!
return res;
}
-
// wrap the TOTP modal opener to disable other login buttons only for Basic/OIDC flows
function openTOTPLoginModal() {
originalOpenTOTPLoginModal();
diff --git a/public/js/main.js b/public/js/main.js
index bc17dbf..e1e1043 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -14,36 +14,20 @@ import { initFileActions, renameFile, openDownloadModal, confirmSingleDownload }
import { editFile, saveFile } from './fileEditor.js';
import { t, applyTranslations, setLocale } from './i18n.js';
-// Remove the retry logic version and just use loadCsrfToken directly:
-/**
- * Fetches the current CSRF token (and share URL), updates window globals
- * and tags, and returns the data.
- *
- * @returns {Promise<{csrf_token: string, share_url: string}>}
- */
+
export function loadCsrfToken() {
- return fetch('/api/auth/token.php', {
- method: 'GET',
- credentials: 'include'
+ return fetchWithCsrf('/api/auth/token.php', {
+ method: 'GET'
})
- .then(response => {
- if (!response.ok) {
- throw new Error(`Token fetch failed with status: ${response.status}`);
+ .then(res => {
+ if (!res.ok) {
+ throw new Error(`Token fetch failed with status ${res.status}`);
}
- // Prefer header if set, otherwise fall back to body
- const headerToken = response.headers.get('X-CSRF-Token');
- return response.json()
- .then(body => ({
- csrf_token: headerToken || body.csrf_token,
- share_url: body.share_url
- }));
+ return res.json();
})
.then(({ csrf_token, share_url }) => {
- // Update globals
+ // Update global and
window.csrfToken = csrf_token;
- window.SHARE_URL = share_url;
-
- // Sync
let meta = document.querySelector('meta[name="csrf-token"]');
if (!meta) {
meta = document.createElement('meta');
@@ -52,7 +36,6 @@ export function loadCsrfToken() {
}
meta.content = csrf_token;
- // Sync
let shareMeta = document.querySelector('meta[name="share-url"]');
if (!shareMeta) {
shareMeta = document.createElement('meta');