From 931afa05cf6ea9ba5e68dcde5ef6f0ca3603f45b Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Wed, 6 May 2026 13:56:04 +0800 Subject: [PATCH] fix(workspace): break NoAccessPage redirect loop by clearing stale cookie The web proxy redirects / to //issues based on the last_workspace_slug cookie alone, with no access check. When a user gets evicted from a workspace, the cookie still points at it; clicking "Go to my workspaces" then loops: NoAccessPage -> / -> proxy -> same bad slug -> NoAccessPage. Clear the cookie on mount so the proxy falls through to the landing page, which resolves the correct destination via the workspace list. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/views/workspace/no-access-page.test.tsx | 10 ++++++++++ packages/views/workspace/no-access-page.tsx | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/views/workspace/no-access-page.test.tsx b/packages/views/workspace/no-access-page.test.tsx index 2a477f0c19..f79962d508 100644 --- a/packages/views/workspace/no-access-page.test.tsx +++ b/packages/views/workspace/no-access-page.test.tsx @@ -32,6 +32,16 @@ describe("NoAccessPage", () => { expect(navigate).toHaveBeenCalledWith("/"); }); + it("clears last_workspace_slug cookie on mount so the proxy stops looping us back", () => { + document.cookie = "last_workspace_slug=stale; path=/"; + render(); + // Assert empty value, not just absence of "stale" — the proxy reads any + // truthy value as a redirect target, so a buggy clear that left e.g. + // `last_workspace_slug=other` would still trap users. + const value = document.cookie.match(/last_workspace_slug=([^;]*)/)?.[1]; + expect(value ?? "").toBe(""); + }); + it("fully logs out on 'Sign in as a different user' instead of just navigating", () => { render(); fireEvent.click( diff --git a/packages/views/workspace/no-access-page.tsx b/packages/views/workspace/no-access-page.tsx index 0afbd38036..aecf053e57 100644 --- a/packages/views/workspace/no-access-page.tsx +++ b/packages/views/workspace/no-access-page.tsx @@ -1,5 +1,6 @@ "use client"; +import { useEffect } from "react"; import { Button } from "@multica/ui/components/ui/button"; import { paths } from "@multica/core/paths"; import { useNavigation } from "../navigation"; @@ -15,6 +16,19 @@ import { DragStrip } from "../platform"; export function NoAccessPage() { const nav = useNavigation(); const logout = useLogout(); + + // Clear stale `last_workspace_slug` cookie. The web proxy redirects `/` to + // `//issues` based on this cookie alone (no access check). When + // the cookie points at a workspace the user has just lost access to, the + // user gets trapped in a loop: NoAccessPage → click "Go to my workspaces" + // → `/` → proxy redirects back to the same bad slug → NoAccessPage. + // Clearing the cookie here lets the proxy fall through to the landing page, + // which then resolves the correct destination via the workspace list. + // No-op outside the browser (desktop renderer also has document, harmless). + useEffect(() => { + if (typeof document === "undefined") return; + document.cookie = "last_workspace_slug=; path=/; max-age=0; SameSite=Lax"; + }, []); return (