From f9c60951c933e3807f4190c1f9d2e56c0f12ac3b Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 23 Apr 2025 19:53:47 -0400 Subject: [PATCH] Removed Old CSRF logic --- CHANGELOG.md | 9 +++++++++ public/js/auth.js | 38 +++++++++++++++++++------------------- public/js/main.js | 33 ++++++++------------------------- 3 files changed, 36 insertions(+), 44 deletions(-) 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');