fix(panel): the /tg surface is exempt from the global 401→login redirect (#600)

The app-level agent-roster sync fires /api/agents on every surface; on
/tg it races the initData sign-in, takes the cloud-auth 401, and the
interceptor bounced the Telegram webview to the password /login page it
cannot complete — hijacking the cockpit into the dashboard. The Mini App
owns its auth UX (initData sign-in + its own wall), so the redirect now
exempts /tg via an exported, tested path predicate.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-20 02:01:10 +02:00
committed by GitHub
co-authored by Renn F
parent 81bd5e722b
commit 4a8050bf43
2 changed files with 28 additions and 1 deletions
+15 -1
View File
@@ -30,7 +30,7 @@ vi.mock("@/store/rate-limit-store", () => ({
// ---------------------------------------------------------------------------
// Import the function under test AFTER mocks are in place
// ---------------------------------------------------------------------------
import { getErrorMessage } from "@/lib/api/client";
import { getErrorMessage, isTgSurfacePath } from "@/lib/api/client";
// ---------------------------------------------------------------------------
// Helpers
@@ -212,3 +212,17 @@ describe("getErrorMessage — non-Axios fallbacks", () => {
);
});
});
describe("isTgSurfacePath — the /tg login-redirect exemption", () => {
it("matches the cockpit root and subpaths", () => {
expect(isTgSurfacePath("/tg")).toBe(true);
expect(isTgSurfacePath("/tg/")).toBe(true);
});
it("does not match the dashboard or lookalike segments", () => {
expect(isTgSurfacePath("/overview")).toBe(false);
expect(isTgSurfacePath("/tgsomething")).toBe(false);
expect(isTgSurfacePath("/login")).toBe(false);
expect(isTgSurfacePath("/")).toBe(false);
});
});
+13
View File
@@ -61,6 +61,16 @@ api.interceptors.request.use(
},
);
/**
* The Telegram Mini App owns its auth UX: /tg signs in via initData and
* renders its own wall. Global mounts (the agent-roster sync) can 401 there
* before that sign-in lands — bouncing the webview to the password /login
* page it cannot complete would hijack the cockpit.
*/
export function isTgSurfacePath(pathname: string): boolean {
return /^\/tg(\/|$)/.test(pathname);
}
// A 401 only means "log in" when cloud auth is actually on. In header-trust /
// secure mode (cloud auth off) a 401 is a misconfigured agent token, not a
// missing session — bouncing to /login would dead-end on a page whose backend
@@ -70,6 +80,9 @@ async function redirectToLoginIfCloudAuth(): Promise<void> {
if (typeof window === "undefined" || window.location.pathname === "/login") {
return;
}
if (isTgSurfacePath(window.location.pathname)) {
return;
}
try {
const res = await fetch(`${API_URL}/auth/status`, {
credentials: "include",