fix(dashboard): exempt SPA shell from bearer-token auth

The bearer-token middleware was mounted globally on the Express app, so
the initial GET /?token=<token> request for index.html was rejected with
401 before the frontend JS could even load and capture the token from
the URL. The server-side query-string fallback only accepts fn_token=,
while the banner URL uses ?token= (which only the frontend captures), so
the bootstrap request had no valid auth path.

Fix: gate auth on /api/* only. The SPA shell (index.html, /assets/*,
favicon) is public — it contains no secrets, and the frontend auth.ts
module captures the token into localStorage and installs a fetch wrapper
that injects Authorization: Bearer on every /api/* call.

Also: add .env / .env.local to .gitignore so local FUSION_DAEMON_TOKEN
values don't accidentally get committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-21 20:44:13 -07:00
parent cc1dd84399
commit ed2a973e4d
3 changed files with 29 additions and 3 deletions

View File

@@ -392,8 +392,12 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
}));
// Daemon mode: bearer token authentication middleware
// Auth is enabled when daemon option is provided OR FUSION_DAEMON_TOKEN env var is set
// The middleware itself exempts /api/health for liveness probes
// Auth is enabled when daemon option is provided OR FUSION_DAEMON_TOKEN env var is set.
// The middleware exempts /api/health and everything outside /api/ — the SPA shell
// (index.html + built assets) is public so the browser can load the frontend JS
// that then captures ?token= from the URL and injects a Bearer header on every
// /api/* call. WebSocket upgrades are gated separately in setupTerminalWebSocket /
// setupBadgeWebSocket.
const daemonToken = options?.daemon?.token ?? process.env.FUSION_DAEMON_TOKEN;
if (daemonToken) {
app.use(createAuthMiddleware(daemonToken));