Compare commits

...
Author SHA1 Message Date
zeekayandClaude Opus 4.8 7c16902cf0 fix(chat): keep guests on chat surface — don't hard-redirect 401s to /login
The axios 401 interceptor hard-redirected to /login whenever a one-shot
refresh yielded no token. A guest (anonymous preview) session carries a
{guest:true} JWT that is valid ONLY on the chat-completion route; every
other endpoint (/api/mcp/servers, /api/files/config, ...) answers 401 by
design. Those expected 401s bounced the guest to /login, wiping the guest
session in an infinite loop, so the landing never rendered the composer.

Derive guest-ness from the active bearer (isGuestSession) and skip the
hard redirect for guests — real users with a truly-expired session still
redirect. Fixes anonymous-guest landing on hanzo.chat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:30:20 -07:00
2 changed files with 23 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hanzochat/chat",
"version": "0.9.3",
"version": "0.9.4",
"description": "Chat - Unified interface to Agents, LLMs, MCPs and any AI model or OpenAPI documented API with full RAG stack",
"packageManager": "pnpm@10.27.0",
"workspaces": [
+22 -1
View File
@@ -102,6 +102,27 @@ const processQueue = (error: AxiosError | null, token: string | null = null) =>
failedQueue = [];
};
/**
* A guest (anonymous preview) session carries a `{ guest: true }` JWT that is
* only valid on the chat-completion route; every other endpoint answers 401 by
* design. Those expected 401s must NOT hard-redirect the guest to /login — that
* wipes the guest session and loops. Derive guest-ness from the active bearer so
* the interceptor leaves guests on the chat surface.
*/
function isGuestSession(): boolean {
try {
const auth = axios.defaults.headers.common['Authorization'];
if (typeof auth !== 'string' || !auth.startsWith('Bearer ')) {
return false;
}
const b64 = auth.slice(7).split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
const payload = JSON.parse(atob(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '=')));
return payload?.guest === true;
} catch {
return false;
}
}
// Auto-retry on 401 (access token expired): refresh once, then replay.
if (typeof window !== 'undefined') {
axios.interceptors.response.use(
@@ -154,7 +175,7 @@ if (typeof window !== 'undefined') {
console.log(
`Refresh token failed from shared link, attempting request to ${originalRequest.url}`,
);
} else {
} else if (!isGuestSession()) {
window.location.href = endpoints.loginPage();
}
} catch (err) {