fix: keep terminal stable across auth refresh
This commit is contained in:
@@ -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",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user