use proxy for logout on org auth page session expire

This commit is contained in:
miloschwartz
2026-07-16 22:06:43 -04:00
parent ca279ee3e0
commit 2d48adc9f9
3 changed files with 63 additions and 22 deletions
+15 -10
View File
@@ -16,18 +16,26 @@ export async function logout(
next: NextFunction next: NextFunction
): Promise<any> { ): Promise<any> {
const { user, session } = await verifySession(req); 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 (!user || !session) {
if (config.getRawConfig().app.log_failed_attempts) { if (config.getRawConfig().app.log_failed_attempts) {
logger.info( 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( return response<null>(res, {
HttpCode.BAD_REQUEST, data: null,
"You must be logged in to sign out" success: true,
) error: false,
); message: "Logged out successfully",
status: HttpCode.OK
});
} }
try { try {
@@ -37,9 +45,6 @@ export async function logout(
logger.error("Failed to invalidate session", error); logger.error("Failed to invalidate session", error);
} }
const isSecure = req.protocol === "https";
res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure));
return response<null>(res, { return response<null>(res, {
data: null, data: null,
success: true, success: true,
+33
View File
@@ -248,6 +248,39 @@ export async function loginProxy(
return await makeApiRequest<LoginResponse>(url, "POST", request); return await makeApiRequest<LoginResponse>(url, "POST", request);
} }
export async function logoutProxy(): Promise<ResponseT<null>> {
const env = pullEnv();
const serverPort = process.env.SERVER_EXTERNAL_PORT;
const url = `http://localhost:${serverPort}/api/v1/auth/logout`;
const result = await makeApiRequest<null>(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( export async function securityKeyStartProxy(
request: SecurityKeyStartRequest, request: SecurityKeyStartRequest,
forceLogin?: boolean forceLogin?: boolean
+15 -12
View File
@@ -12,8 +12,8 @@ import { Shield, ArrowRight } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { createApiClient } from "@app/lib/api"; import { useState } from "react";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { logoutProxy } from "@app/actions/server";
type OrgPolicyRequiredProps = { type OrgPolicyRequiredProps = {
orgId: string; orgId: string;
@@ -40,21 +40,23 @@ export default function OrgPolicyRequired({
}: OrgPolicyRequiredProps) { }: OrgPolicyRequiredProps) {
const t = useTranslations(); const t = useTranslations();
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false);
const api = createApiClient(useEnvContext());
const sessionExpired = const sessionExpired =
policies?.maxSessionLength && policies?.maxSessionLength &&
policies.maxSessionLength.compliant === false; policies.maxSessionLength.compliant === false;
function reauthenticate() { async function reauthenticate() {
api.post("/auth/logout") setLoading(true);
.catch(() => {}) try {
.then(() => { await logoutProxy();
const destination = redirectAfterAuth ?? `/${orgId}`; } catch (error) {
router.push(destination); console.error("Error during logout:", error);
router.refresh(); } finally {
}); const destination = redirectAfterAuth ?? `/${orgId}`;
router.push(destination);
router.refresh();
}
} }
if (sessionExpired) { if (sessionExpired) {
@@ -76,6 +78,7 @@ export default function OrgPolicyRequired({
<Button <Button
className="w-full" className="w-full"
onClick={reauthenticate} onClick={reauthenticate}
loading={loading}
> >
{t("reauthenticate")} {t("reauthenticate")}
<ArrowRight className="ml-2 h-4 w-4" /> <ArrowRight className="ml-2 h-4 w-4" />