From a6a64032ec4f0ac06f2124096d66177006ed558e Mon Sep 17 00:00:00 2001 From: Steffen Schuhmann Date: Sat, 28 Mar 2026 22:03:16 +0100 Subject: [PATCH] fix: auto-logout on 401, raise nginx timeout for nc-scan 401 Unauthorized loop: - client.ts had no 401 handler, leaving stale expired tokens in localStorage. The PrivateRoute guard only checked token existence, so the app would render but every API call would fail silently. - Fix: on any 401 response, clear the token and redirect to /login. 504 Gateway Timeout on nc-scan: - nginx default proxy_read_timeout is 60s. The scan endpoint makes one Nextcloud request per audio file (list + metadata), which easily exceeds that on larger libraries. - Fix: add a dedicated location block for nc-scan with 300s timeouts. General /api/ block gets explicit 60s timeouts for clarity. Co-Authored-By: Claude Sonnet 4.6 --- web/nginx.conf | 15 +++++++++++++++ web/src/api/client.ts | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/web/nginx.conf b/web/nginx.conf index adb1283..b054424 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -14,6 +14,21 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; + + proxy_read_timeout 60s; + proxy_send_timeout 60s; + } + + # NC scan hits Nextcloud for every file — can take several minutes on large libraries + location ~ ^/api/v1/bands/[^/]+/nc-scan { + proxy_pass http://api:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + + proxy_read_timeout 300s; + proxy_send_timeout 300s; } # SPA routing — all other paths fall back to index.html diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 1979bb8..bacb488 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -26,6 +26,11 @@ async function request( } const resp = await fetch(`${BASE}${path}`, { ...options, headers }); if (!resp.ok) { + if (resp.status === 401) { + clearToken(); + window.location.href = "/login"; + throw new Error("Session expired"); + } const error = await resp.json().catch(() => ({ detail: resp.statusText })); throw new Error(error.detail ?? resp.statusText); }