diff --git a/services/frontend/server.mjs b/services/frontend/server.mjs index f324208..6ae0a59 100644 --- a/services/frontend/server.mjs +++ b/services/frontend/server.mjs @@ -187,6 +187,15 @@ function resolveAuthUser(req) { return value || null; } +function hasCookie(req, name, value) { + const raw = typeof req.headers.cookie === 'string' ? req.headers.cookie : ''; + if (!raw) return false; + return raw + .split(';') + .map((p) => p.trim()) + .some((kv) => kv === `${name}=${value}`); +} + function proxyApi(req, res, apiReadToken) { const upstreamBase = new URL(API_UPSTREAM); const inUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`); @@ -256,9 +265,19 @@ function handler(req, res) { if (req.method === 'GET' && url.pathname === '/logout') { // NOTE: With HTTP basic auth handled upstream (Traefik), browser "logout" is best-effort. - // This endpoint forces a 401 so the browser prompts again, allowing the user to switch accounts. - res.setHeader('www-authenticate', 'Basic realm="trade"'); - send(res, 401, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }, 'logged_out'); + // We force a single 401 to show the browser prompt, then on retry we redirect back to '/'. + const marker = 'trade_logout'; + if (!hasCookie(req, marker, '1')) { + res.setHeader('set-cookie', `${marker}=1; Path=/logout; Max-Age=10; SameSite=Lax`); + res.setHeader('www-authenticate', 'Basic realm="trade"'); + send(res, 401, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }, 'logged_out'); + return; + } + + res.setHeader('set-cookie', `${marker}=; Path=/logout; Max-Age=0; SameSite=Lax`); + res.statusCode = 302; + res.setHeader('location', '/'); + res.end(); return; }