fix: keep terminal stable across auth refresh

This commit is contained in:
2569718930@qq.com
2026-05-30 14:33:26 +08:00
parent ed8c898e0e
commit 1c250906ed
4 changed files with 37 additions and 2 deletions
@@ -119,6 +119,15 @@ export function runTests() {
middlewareSource.indexOf("await refreshMiddlewareSession(request)"), middlewareSource.indexOf("await refreshMiddlewareSession(request)"),
"middleware must redirect no-cookie page requests without calling Supabase auth", "middleware must redirect no-cookie page requests without calling Supabase auth",
); );
const terminalGateSource = middlewareSource.slice(
middlewareSource.indexOf("async function handleTerminalGate"),
middlewareSource.indexOf("async function handleSupabaseAuthGate"),
);
assert(
terminalGateSource.includes("return response;") &&
!terminalGateSource.includes("return redirectToLogin(request, pathname);\n}"),
"terminal middleware must not navigate long-lived dashboards away when a session cookie exists but claims refresh is transiently unavailable",
);
assert( assert(
middlewareSource.includes("unauthorizedSupabaseSessionResponse()"), middlewareSource.includes("unauthorizedSupabaseSessionResponse()"),
"middleware must reject no-cookie protected API requests without calling Supabase auth", "middleware must reject no-cookie protected API requests without calling Supabase auth",
@@ -954,6 +954,18 @@ function ScanTerminalScreen() {
data: { subscription }, data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, session) => { } = supabase.auth.onAuthStateChange(async (event, session) => {
if (event === "SIGNED_OUT") { if (event === "SIGNED_OUT") {
try {
const {
data: { session: currentSession },
} = await supabase.auth.getSession();
const accessToken =
currentSession?.access_token || session?.access_token || null;
if (accessToken) {
const payload = await loadAuthProfile(accessToken);
setProAccess((prev) => mergeAccessStateWithAuthPayload(prev, payload));
return;
}
} catch {}
setProAccess(createEmptyAccess(false)); setProAccess(createEmptyAccess(false));
} else if (event === "TOKEN_REFRESHED" || event === "SIGNED_IN") { } else if (event === "TOKEN_REFRESHED" || event === "SIGNED_IN") {
try { try {
@@ -65,4 +65,15 @@ export function runTests() {
chartCanvasSource.includes('compact ? "min-h-[120px]" : "min-h-[220px]"'), chartCanvasSource.includes('compact ? "min-h-[120px]" : "min-h-[220px]"'),
"compact grid charts must not force desktop minimum heights that get clipped inside 3x3 terminal slots", "compact grid charts must not force desktop minimum heights that get clipped inside 3x3 terminal slots",
); );
const signedOutBlock = dashboardSource.slice(
dashboardSource.indexOf('if (event === "SIGNED_OUT")'),
dashboardSource.indexOf('} else if (event === "TOKEN_REFRESHED"'),
);
assert(
signedOutBlock.includes("await supabase.auth.getSession()") &&
signedOutBlock.includes("mergeAccessStateWithAuthPayload(prev, payload)") &&
signedOutBlock.indexOf("await supabase.auth.getSession()") <
signedOutBlock.indexOf("setProAccess(createEmptyAccess(false))"),
"terminal auth listener must re-check the current Supabase session before clearing access on SIGNED_OUT events",
);
} }
+5 -2
View File
@@ -104,8 +104,11 @@ async function handleTerminalGate(request: NextRequest): Promise<NextResponse> {
return response; return response;
} }
// Layer 1: Not logged in → redirect to /auth/login?next=/terminal // A session cookie exists, but the edge/server refresh can occasionally fail
return redirectToLogin(request, pathname); // during a long-lived terminal tab. Do not navigate an active dashboard away
// on a transient claims failure; the client can still verify via bearer auth
// and render the in-product access gate if the session is truly gone.
return response;
} }
async function handleSupabaseAuthGate(request: NextRequest) { async function handleSupabaseAuthGate(request: NextRequest) {