From 2d48adc9f9238c94152017156b5494245d7a7f71 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 16 Jul 2026 22:06:43 -0400 Subject: [PATCH] use proxy for logout on org auth page session expire --- server/routers/auth/logout.ts | 25 ++++++++++++--------- src/actions/server.ts | 33 ++++++++++++++++++++++++++++ src/components/OrgPolicyRequired.tsx | 27 +++++++++++++---------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/server/routers/auth/logout.ts b/server/routers/auth/logout.ts index b9a1431aa..b36d0c73a 100644 --- a/server/routers/auth/logout.ts +++ b/server/routers/auth/logout.ts @@ -16,18 +16,26 @@ export async function logout( next: NextFunction ): Promise { const { user, session } = await verifySession(req); + const isSecure = req.protocol === "https"; + + // Always clear the session cookie so logout is idempotent, even when + // the session is already missing or invalid + res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); + if (!user || !session) { if (config.getRawConfig().app.log_failed_attempts) { logger.info( - `Log out failed because missing or invalid session. IP: ${req.ip}.` + `Log out with missing or invalid session. IP: ${req.ip}.` ); } - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "You must be logged in to sign out" - ) - ); + + return response(res, { + data: null, + success: true, + error: false, + message: "Logged out successfully", + status: HttpCode.OK + }); } try { @@ -37,9 +45,6 @@ export async function logout( logger.error("Failed to invalidate session", error); } - const isSecure = req.protocol === "https"; - res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); - return response(res, { data: null, success: true, diff --git a/src/actions/server.ts b/src/actions/server.ts index 2759e6213..cd75c2c57 100644 --- a/src/actions/server.ts +++ b/src/actions/server.ts @@ -248,6 +248,39 @@ export async function loginProxy( return await makeApiRequest(url, "POST", request); } +export async function logoutProxy(): Promise> { + const env = pullEnv(); + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/logout`; + + const result = await makeApiRequest(url, "POST"); + + try { + const headersList = await reqHeaders(); + const host = headersList.get("host")?.split(":")[0]; + const allCookies = await cookies(); + const clearOptions = { + httpOnly: true, + secure: true, + sameSite: "lax" as const, + path: "/", + maxAge: 0 + }; + // Clear both host-only and domain-scoped variants. + allCookies.set(env.server.sessionCookieName, "", clearOptions); + if (host) { + allCookies.set(env.server.sessionCookieName, "", { + ...clearOptions, + domain: host + }); + } + } catch (cookieError) { + console.error("Failed to clear session cookie:", cookieError); + } + + return result; +} + export async function securityKeyStartProxy( request: SecurityKeyStartRequest, forceLogin?: boolean diff --git a/src/components/OrgPolicyRequired.tsx b/src/components/OrgPolicyRequired.tsx index 3765cd1bf..6d28ddbf5 100644 --- a/src/components/OrgPolicyRequired.tsx +++ b/src/components/OrgPolicyRequired.tsx @@ -12,8 +12,8 @@ import { Shield, ArrowRight } from "lucide-react"; import Link from "next/link"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useState } from "react"; +import { logoutProxy } from "@app/actions/server"; type OrgPolicyRequiredProps = { orgId: string; @@ -40,21 +40,23 @@ export default function OrgPolicyRequired({ }: OrgPolicyRequiredProps) { const t = useTranslations(); const router = useRouter(); - - const api = createApiClient(useEnvContext()); + const [loading, setLoading] = useState(false); const sessionExpired = policies?.maxSessionLength && policies.maxSessionLength.compliant === false; - function reauthenticate() { - api.post("/auth/logout") - .catch(() => {}) - .then(() => { - const destination = redirectAfterAuth ?? `/${orgId}`; - router.push(destination); - router.refresh(); - }); + async function reauthenticate() { + setLoading(true); + try { + await logoutProxy(); + } catch (error) { + console.error("Error during logout:", error); + } finally { + const destination = redirectAfterAuth ?? `/${orgId}`; + router.push(destination); + router.refresh(); + } } if (sessionExpired) { @@ -76,6 +78,7 @@ export default function OrgPolicyRequired({