fix(ui): use shared getCookie in page.tsx and user_dashboard.tsx

Replace local getCookie functions in page.tsx and user_dashboard.tsx
with the shared one from cookieUtils that has the sessionStorage
fallback. Without this, the HttpOnly cookie fix was incomplete —
page.tsx (the dashboard entry point) could not read the token,
causing the redirect loop to persist.

Also scope the sessionStorage fallback to the "token" key only,
and clear sessionStorage in page.tsx deleteCookie.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hendrik Jaks 2026-03-13 11:06:43 +02:00
parent 461cf00408
commit 523d1c388f
4 changed files with 14 additions and 19 deletions

View file

@ -43,6 +43,7 @@ import ToolPoliciesView from "@/components/ToolPoliciesView";
import SpendLogsTable from "@/components/view_logs";
import ViewUserDashboard from "@/components/view_users";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { getCookie } from "@/utils/cookieUtils";
import { isJwtExpired } from "@/utils/jwtUtils";
import { buildLoginUrlWithReturn, consumeReturnUrl, normalizeUrlForCompare, storeReturnUrl } from "@/utils/returnUrlUtils";
import { formatUserRole, isAdminRole } from "@/utils/roles";
@ -52,21 +53,14 @@ import { useSearchParams } from "next/navigation";
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { ConfigProvider, theme } from "antd";
function getCookie(name: string) {
// Safer cookie read + decoding; handles '=' inside values
const match = document.cookie.split("; ").find((row) => row.startsWith(name + "="));
if (!match) return null;
const value = match.slice(name.length + 1);
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function deleteCookie(name: string, path = "/") {
// Best-effort client-side clear (works for non-HttpOnly cookies without Domain)
document.cookie = `${name}=; Max-Age=0; Path=${path}`;
try {
sessionStorage.removeItem(name);
} catch {
// sessionStorage may be unavailable
}
}
interface ProxySettings {

View file

@ -1,5 +1,5 @@
"use client";
import { clearTokenCookies } from "@/utils/cookieUtils";
import { clearTokenCookies, getCookie } from "@/utils/cookieUtils";
import { Col, Grid } from "@tremor/react";
import { Typography } from "antd";
import { jwtDecode } from "jwt-decode";
@ -35,11 +35,7 @@ export type UserInfo = {
spend: number;
};
function getCookie(name: string) {
console.log("COOKIES", document.cookie);
const cookieValue = document.cookie.split("; ").find((row) => row.startsWith(name + "="));
return cookieValue ? cookieValue.split("=")[1] : null;
}
interface UserDashboardProps {
userID: string | null;

View file

@ -188,5 +188,10 @@ describe("cookieUtils", () => {
sessionStorage.setItem("token", "session-value");
expect(getCookie("token")).toBe("cookie-value");
});
it("should not fall back to sessionStorage for non-token keys", () => {
sessionStorage.setItem("other", "other-value");
expect(getCookie("other")).toBeNull();
});
});
});

View file

@ -84,7 +84,7 @@ export function getCookie(name: string) {
}
// Fallback to sessionStorage — covers the case where a reverse proxy
// added HttpOnly to the server-set cookie, making it invisible to JS.
if (typeof window !== "undefined") {
if (name === "token" && typeof window !== "undefined") {
try {
return sessionStorage.getItem(name);
} catch {