refactor: Improve cookie security and centralized utility (#9240)

*  (authContext.tsx): Add setCookieWithOptions function to set cookies with specific options for better security and control
📝 (use-post-refresh-access.ts): Update cookies.set calls to use setCookieWithOptions function for consistent cookie settings
♻️ (utils.ts): Refactor setCookieWithOptions function to include httpOnly option and update sameSite values to lowercase for consistency

* 📝 (frontend): add useGetCookieAuth and useSetCookieAuth hooks for managing cookies in auth context
🔧 (frontend): refactor authStore to use new cookie hooks for managing access token and api key cookies
🔧 (frontend): refactor use-post-logout and use-post-refresh-access to use new cookie hooks for cookie management

* 📝 (frontend): Remove redundant useGetCookieAuth hook and use direct access to cookies in authStore and related components
🔧 (utils): Refactor getAuthCookie and setAuthCookie functions to use react-cookie directly for better code organization and readability
This commit is contained in:
Cristhian Zanforlin Lousa 2025-08-01 11:38:01 -03:00 committed by GitHub
commit bdcc238618
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 802 additions and 14 deletions

View file

@ -10,6 +10,7 @@ import { useGetUserData } from "@/controllers/API/queries/auth";
import { useGetGlobalVariablesMutation } from "@/controllers/API/queries/variables/use-get-mutation-global-variables";
import useAuthStore from "@/stores/authStore";
import { setLocalStorage } from "@/utils/local-storage-util";
import { getAuthCookie, setAuthCookie } from "@/utils/utils";
import { useStoreStore } from "../stores/storeStore";
import type { Users } from "../types/api";
import type { AuthContextType } from "../types/contexts/auth";
@ -31,11 +32,11 @@ export const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const cookies = new Cookies();
const [accessToken, setAccessToken] = useState<string | null>(
cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
getAuthCookie(cookies, LANGFLOW_ACCESS_TOKEN) ?? null,
);
const [userData, setUserData] = useState<Users | null>(null);
const [apiKey, setApiKey] = useState<string | null>(
cookies.get(LANGFLOW_API_TOKEN),
getAuthCookie(cookies, LANGFLOW_API_TOKEN),
);
const checkHasStore = useStoreStore((state) => state.checkHasStore);
@ -46,14 +47,14 @@ export function AuthProvider({ children }): React.ReactElement {
const { mutate: mutateGetGlobalVariables } = useGetGlobalVariablesMutation();
useEffect(() => {
const storedAccessToken = cookies.get(LANGFLOW_ACCESS_TOKEN);
const storedAccessToken = getAuthCookie(cookies, LANGFLOW_ACCESS_TOKEN);
if (storedAccessToken) {
setAccessToken(storedAccessToken);
}
}, []);
useEffect(() => {
const apiKey = cookies.get(LANGFLOW_API_TOKEN);
const apiKey = getAuthCookie(cookies, LANGFLOW_API_TOKEN);
if (apiKey) {
setApiKey(apiKey);
}
@ -82,12 +83,12 @@ export function AuthProvider({ children }): React.ReactElement {
autoLogin: string,
refreshToken?: string,
) {
cookies.set(LANGFLOW_ACCESS_TOKEN, newAccessToken, { path: "/" });
cookies.set(LANGFLOW_AUTO_LOGIN_OPTION, autoLogin, { path: "/" });
setAuthCookie(cookies, LANGFLOW_ACCESS_TOKEN, newAccessToken);
setAuthCookie(cookies, LANGFLOW_AUTO_LOGIN_OPTION, autoLogin);
setLocalStorage(LANGFLOW_ACCESS_TOKEN, newAccessToken);
if (refreshToken) {
cookies.set(LANGFLOW_REFRESH_TOKEN, refreshToken, { path: "/" });
setAuthCookie(cookies, LANGFLOW_REFRESH_TOKEN, refreshToken);
}
setAccessToken(newAccessToken);
setIsAuthenticated(true);

View file

@ -0,0 +1,167 @@
// Logout functionality tests
// Mock all dependencies before imports
const mockLogout = jest.fn();
const mockResetFlowState = jest.fn();
const mockResetFlowsManagerStore = jest.fn();
const mockResetFolderStore = jest.fn();
const mockQueryClient = { invalidateQueries: jest.fn() };
const mockGetAuthCookie = jest.fn();
const mockApiPost = jest.fn();
jest.mock("@/stores/authStore", () => {
const mockState = { autoLogin: false };
const mockStore = jest.fn((selector) => {
if (selector.toString().includes("logout")) return mockLogout;
return false;
});
mockStore.getState = jest.fn(() => mockState);
return mockStore;
});
jest.mock("@/stores/flowStore", () => {
const mockStore = jest.fn();
mockStore.getState = jest.fn(() => ({ resetFlowState: mockResetFlowState }));
return mockStore;
});
jest.mock("@/stores/flowsManagerStore", () => {
const mockStore = jest.fn();
mockStore.getState = jest.fn(() => ({
resetStore: mockResetFlowsManagerStore,
}));
return mockStore;
});
jest.mock("@/stores/foldersStore", () => ({
useFolderStore: {
getState: jest.fn(() => ({ resetStore: mockResetFolderStore })),
},
}));
jest.mock("@/utils/utils", () => ({
getAuthCookie: mockGetAuthCookie,
}));
jest.mock("@/controllers/API/api", () => ({
api: {
post: mockApiPost,
},
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
UseRequestProcessor: jest.fn(() => ({
mutate: jest.fn((key, fn, options) => ({
mutate: async () => {
try {
await fn();
if (options?.onSuccess) options.onSuccess();
} catch (error) {
if (options?.onError) options.onError(error);
throw error;
}
},
})),
queryClient: mockQueryClient,
})),
}));
jest.mock("react-cookie", () => ({
Cookies: jest.fn().mockImplementation(() => ({})),
}));
jest.mock("@/constants/constants", () => ({
...jest.requireActual("@/constants/constants"),
IS_AUTO_LOGIN: false, // Override to disable auto login for testing
LANGFLOW_AUTO_LOGIN_OPTION: "auto_login_lf",
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn((key) => `/api/v1/${key.toLowerCase()}`),
}));
import { useLogout } from "../use-post-logout";
describe("logout functionality", () => {
beforeEach(() => {
jest.clearAllMocks();
mockGetAuthCookie.mockReturnValue(null);
});
describe("logout behavior with auto login disabled", () => {
it("should call API logout when auto login is disabled", async () => {
mockGetAuthCookie.mockReturnValue(null); // Not "auto", so autoLogin is false
mockApiPost.mockResolvedValue({ data: { success: true } });
const logoutMutation = useLogout();
await logoutMutation.mutate();
expect(mockApiPost).toHaveBeenCalledWith(
expect.stringContaining("logout"),
);
});
it("should reset all stores on successful logout", async () => {
mockGetAuthCookie.mockReturnValue(null); // Not "auto", so autoLogin is false
mockApiPost.mockResolvedValue({ data: { success: true } });
const logoutMutation = useLogout();
await logoutMutation.mutate();
expect(mockLogout).toHaveBeenCalled();
expect(mockResetFlowState).toHaveBeenCalled();
expect(mockResetFlowsManagerStore).toHaveBeenCalled();
expect(mockResetFolderStore).toHaveBeenCalled();
});
it("should invalidate queries on successful logout", async () => {
mockGetAuthCookie.mockReturnValue(null); // Not "auto", so autoLogin is false
mockApiPost.mockResolvedValue({ data: { success: true } });
const logoutMutation = useLogout();
await logoutMutation.mutate();
expect(mockQueryClient.invalidateQueries).toHaveBeenCalledWith({
queryKey: ["useGetRefreshFlowsQuery"],
});
expect(mockQueryClient.invalidateQueries).toHaveBeenCalledWith({
queryKey: ["useGetFolders"],
});
expect(mockQueryClient.invalidateQueries).toHaveBeenCalledWith({
queryKey: ["useGetFolder"],
});
});
});
describe("logout behavior with auto login enabled", () => {
it("should skip API call when auto login is enabled via cookie", async () => {
mockGetAuthCookie.mockReturnValue("auto");
const logoutMutation = useLogout();
await logoutMutation.mutate();
expect(mockApiPost).not.toHaveBeenCalled();
});
it("should still reset stores even when skipping API call", async () => {
mockGetAuthCookie.mockReturnValue("auto");
const logoutMutation = useLogout();
await logoutMutation.mutate();
expect(mockLogout).toHaveBeenCalled();
expect(mockResetFlowState).toHaveBeenCalled();
});
});
describe("error handling", () => {
it("should handle API errors gracefully", async () => {
mockGetAuthCookie.mockReturnValue(null); // Not "auto", so autoLogin is false
const mockError = new Error("API Error");
mockApiPost.mockRejectedValue(mockError);
const logoutMutation = useLogout();
await expect(logoutMutation.mutate()).rejects.toThrow("API Error");
});
});
});

View file

@ -0,0 +1,150 @@
// Refresh token functionality tests
// Mock all dependencies before imports
jest.mock("@/utils/utils", () => ({
setAuthCookie: jest.fn(),
}));
jest.mock(
"@/stores/authStore",
() => jest.fn((selector) => false), // autoLogin = false
);
jest.mock("@/controllers/API/api", () => ({
api: {
post: jest.fn(),
},
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
UseRequestProcessor: jest.fn(() => ({
mutate: jest.fn((key, fn, options) => ({
mutate: async () => {
return await fn();
},
})),
})),
}));
jest.mock("react-cookie", () => ({
Cookies: jest.fn().mockImplementation(() => ({})),
}));
import { useRefreshAccessToken } from "../use-post-refresh-access";
const mockSetAuthCookie = require("@/utils/utils").setAuthCookie;
const mockApiPost = require("@/controllers/API/api").api.post;
describe("refresh token functionality", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("successful token refresh", () => {
it("should call refresh API and set new refresh token cookie", async () => {
const mockRefreshResponse = {
access_token: "new-access-token",
refresh_token: "new-refresh-token",
token_type: "bearer",
};
mockApiPost.mockResolvedValue({ data: mockRefreshResponse });
const refreshMutation = useRefreshAccessToken();
const result = await refreshMutation.mutate();
expect(mockApiPost).toHaveBeenCalledWith(
expect.stringContaining("refresh"),
);
expect(mockSetAuthCookie).toHaveBeenCalledWith(
expect.any(Object), // cookies instance
"refresh_token_lf",
"new-refresh-token",
);
expect(result).toEqual(mockRefreshResponse);
});
it("should return the refresh response data", async () => {
const mockRefreshResponse = {
access_token: "new-access-token-123",
refresh_token: "new-refresh-token-456",
token_type: "bearer",
};
mockApiPost.mockResolvedValue({ data: mockRefreshResponse });
const refreshMutation = useRefreshAccessToken();
const result = await refreshMutation.mutate();
expect(result).toEqual(mockRefreshResponse);
});
});
describe("error handling", () => {
it("should throw error when refresh API fails", async () => {
const mockError = new Error("Refresh failed");
mockApiPost.mockRejectedValue(mockError);
const refreshMutation = useRefreshAccessToken();
await expect(refreshMutation.mutate()).rejects.toThrow("Refresh failed");
});
it("should not set cookie when API fails", async () => {
const mockError = new Error("API Error");
mockApiPost.mockRejectedValue(mockError);
const refreshMutation = useRefreshAccessToken();
try {
await refreshMutation.mutate();
} catch (error) {
// Expected to throw
}
expect(mockSetAuthCookie).not.toHaveBeenCalled();
});
});
describe("cookie management", () => {
it("should use useSetCookieAuth hook for setting refresh token", async () => {
const mockRefreshResponse = {
access_token: "access-token",
refresh_token: "refresh-token-xyz",
token_type: "bearer",
};
mockApiPost.mockResolvedValue({ data: mockRefreshResponse });
const refreshMutation = useRefreshAccessToken();
await refreshMutation.mutate();
expect(mockSetAuthCookie).toHaveBeenCalledTimes(1);
expect(mockSetAuthCookie).toHaveBeenCalledWith(
expect.any(Object), // cookies instance
"refresh_token_lf",
"refresh-token-xyz",
);
});
it("should set refresh token cookie before returning response", async () => {
const mockRefreshResponse = {
access_token: "access-token",
refresh_token: "refresh-token-abc",
token_type: "bearer",
};
mockApiPost.mockResolvedValue({ data: mockRefreshResponse });
const refreshMutation = useRefreshAccessToken();
const response = await refreshMutation.mutate();
// Verify cookie was set before response was returned
expect(mockSetAuthCookie).toHaveBeenCalledWith(
expect.any(Object), // cookies instance
"refresh_token_lf",
"refresh-token-abc",
);
expect(response).toEqual(mockRefreshResponse);
});
});
});

View file

@ -8,6 +8,7 @@ import useFlowStore from "@/stores/flowStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore";
import type { useMutationFunctionType } from "@/types/api";
import { getAuthCookie } from "@/utils/utils";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
@ -23,7 +24,7 @@ export const useLogout: useMutationFunctionType<undefined, void> = (
async function logoutUser(): Promise<any> {
const autoLogin =
useAuthStore.getState().autoLogin ||
cookies.get(LANGFLOW_AUTO_LOGIN_OPTION) === "auto" ||
getAuthCookie(cookies, LANGFLOW_AUTO_LOGIN_OPTION) === "auto" ||
isAutoLoginEnv;
if (autoLogin) {

View file

@ -2,6 +2,7 @@ import { Cookies } from "react-cookie";
import { IS_AUTO_LOGIN, LANGFLOW_REFRESH_TOKEN } from "@/constants/constants";
import useAuthStore from "@/stores/authStore";
import type { useMutationFunctionType } from "@/types/api";
import { setAuthCookie } from "@/utils/utils";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
@ -18,12 +19,12 @@ export const useRefreshAccessToken: useMutationFunctionType<
IRefreshAccessToken
> = (options?) => {
const { mutate } = UseRequestProcessor();
const cookies = new Cookies();
const autoLogin = useAuthStore((state) => state.autoLogin);
async function refreshAccess(): Promise<IRefreshAccessToken> {
const res = await api.post<IRefreshAccessToken>(`${getURL("REFRESH")}`);
cookies.set(LANGFLOW_REFRESH_TOKEN, res.data.refresh_token, { path: "/" });
const cookies = new Cookies();
setAuthCookie(cookies, LANGFLOW_REFRESH_TOKEN, res.data.refresh_token);
return res.data;
}

View file

@ -0,0 +1,109 @@
import { Cookies } from "react-cookie";
// Mock all complex dependencies to avoid import issues
jest.mock("@/stores/authStore", () => ({
__esModule: true,
default: jest.fn(() => ({})),
}));
jest.mock("@/stores/darkStore", () => ({
useDarkStore: {
getState: () => ({ refreshStars: jest.fn() }),
setState: jest.fn(),
subscribe: jest.fn(),
destroy: jest.fn(),
},
}));
jest.mock("@/stores/alertStore", () => ({
__esModule: true,
default: jest.fn(() => ({})),
}));
jest.mock("@/utils/styleUtils", () => ({}));
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender",
() => () => null,
);
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableDropdownCellEditor",
() => () => null,
);
// Jest can't find this module to mock it, let's skip this mock
// Jest can't find this module either
// Mock react-cookie
jest.mock("react-cookie");
import { getAuthCookie } from "@/utils/utils";
describe("getAuthCookie", () => {
let mockCookies: jest.Mocked<Cookies>;
beforeEach(() => {
mockCookies = {
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
} as any;
});
afterEach(() => {
jest.clearAllMocks();
});
it("should return the cookie value when cookie exists", () => {
const mockTokenValue = "test-access-token";
mockCookies.get.mockReturnValue(mockTokenValue);
const result = getAuthCookie(mockCookies, "access_token");
expect(mockCookies.get).toHaveBeenCalledWith("access_token");
expect(result).toBe(mockTokenValue);
});
it("should return undefined when cookie does not exist", () => {
mockCookies.get.mockReturnValue(undefined);
const result = getAuthCookie(mockCookies, "nonexistent_token");
expect(mockCookies.get).toHaveBeenCalledWith("nonexistent_token");
expect(result).toBeUndefined();
});
it("should handle different token names", () => {
const testCases = [
{ tokenName: "access_token_lf", value: "access-123" },
{ tokenName: "refresh_token_lf", value: "refresh-456" },
{ tokenName: "apikey_tkn_lflw", value: "api-789" },
];
testCases.forEach(({ tokenName, value }) => {
mockCookies.get.mockReturnValue(value);
const result = getAuthCookie(mockCookies, tokenName);
expect(mockCookies.get).toHaveBeenCalledWith(tokenName);
expect(result).toBe(value);
});
});
it("should handle empty string token names", () => {
const result = getAuthCookie(mockCookies, "");
expect(mockCookies.get).toHaveBeenCalledWith("");
});
it("should handle null values from cookies", () => {
mockCookies.get.mockReturnValue(null);
const result = getAuthCookie(mockCookies, "test_token");
expect(mockCookies.get).toHaveBeenCalledWith("test_token");
expect(result).toBeNull();
});
});

View file

@ -0,0 +1,130 @@
import { Cookies } from "react-cookie";
// Mock all complex dependencies to avoid import issues
jest.mock("@/stores/authStore", () => ({
__esModule: true,
default: jest.fn(() => ({})),
}));
jest.mock("@/stores/darkStore", () => ({
useDarkStore: {
getState: () => ({ refreshStars: jest.fn() }),
setState: jest.fn(),
subscribe: jest.fn(),
destroy: jest.fn(),
},
}));
jest.mock("@/stores/alertStore", () => ({
__esModule: true,
default: jest.fn(() => ({})),
}));
jest.mock("@/utils/styleUtils", () => ({}));
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender",
() => () => null,
);
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableDropdownCellEditor",
() => () => null,
);
// Jest can't find this module to mock it, let's skip this mock
// Jest can't find this module either
// Mock react-cookie
jest.mock("react-cookie");
import { setAuthCookie } from "@/utils/utils";
describe("setAuthCookie", () => {
let mockCookies: jest.Mocked<Cookies>;
beforeEach(() => {
mockCookies = {
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
} as any;
});
afterEach(() => {
jest.clearAllMocks();
});
it("should set a cookie with correct options", () => {
const tokenName = "access_token_lf";
const tokenValue = "test-access-token";
setAuthCookie(mockCookies, tokenName, tokenValue);
expect(mockCookies.set).toHaveBeenCalledWith(tokenName, tokenValue, {
path: "/",
secure: true,
sameSite: "strict",
});
});
it("should handle different token types", () => {
const testCases = [
{ tokenName: "access_token_lf", value: "access-123" },
{ tokenName: "refresh_token_lf", value: "refresh-456" },
{ tokenName: "apikey_tkn_lflw", value: "api-789" },
];
testCases.forEach(({ tokenName, value }) => {
setAuthCookie(mockCookies, tokenName, value);
expect(mockCookies.set).toHaveBeenCalledWith(tokenName, value, {
path: "/",
secure: true,
sameSite: "strict",
});
});
});
it("should handle empty string values", () => {
const tokenName = "test_token";
const tokenValue = "";
setAuthCookie(mockCookies, tokenName, tokenValue);
expect(mockCookies.set).toHaveBeenCalledWith(tokenName, tokenValue, {
path: "/",
secure: true,
sameSite: "strict",
});
});
it("should use correct cookie options for security", () => {
setAuthCookie(mockCookies, "test_token", "test_value");
const cookieOptions = mockCookies.set.mock.calls[0][2];
expect(cookieOptions).toEqual({
path: "/",
secure: true,
sameSite: "strict",
});
// Ensure httpOnly is NOT set (removed from utils.ts)
expect(cookieOptions).not.toHaveProperty("httpOnly");
});
it("should handle special characters in token values", () => {
const tokenName = "test_token";
const tokenValue = "token-with-special-chars_@#$%";
setAuthCookie(mockCookies, tokenName, tokenValue);
expect(mockCookies.set).toHaveBeenCalledWith(tokenName, tokenValue, {
path: "/",
secure: true,
sameSite: "strict",
});
});
});

View file

@ -0,0 +1,206 @@
import { act, renderHook } from "@testing-library/react";
// Mock the darkStore to avoid import.meta issues
jest.mock("../darkStore", () => ({
useDarkStore: {
getState: () => ({ refreshStars: jest.fn() }),
setState: jest.fn(),
subscribe: jest.fn(),
destroy: jest.fn(),
},
}));
// Mock all complex dependencies to avoid import issues
jest.mock("@/stores/alertStore", () => ({
__esModule: true,
default: jest.fn(() => ({})),
}));
jest.mock("@/utils/styleUtils", () => ({}));
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender",
() => () => null,
);
jest.mock(
"@/components/core/parameterRenderComponent/components/tableComponent/components/tableDropdownCellEditor",
() => () => null,
);
// Jest can't find this module to mock it, let's skip this mock
// Jest can't find this module either
import useAuthStore from "../authStore";
// We can't easily mock the cookie hook at initialization time, so we'll test actual behavior
describe("useAuthStore", () => {
describe("state management", () => {
it("should update isAdmin state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setIsAdmin(true);
});
expect(result.current.isAdmin).toBe(true);
act(() => {
result.current.setIsAdmin(false);
});
expect(result.current.isAdmin).toBe(false);
});
it("should update isAuthenticated state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setIsAuthenticated(true);
});
expect(result.current.isAuthenticated).toBe(true);
act(() => {
result.current.setIsAuthenticated(false);
});
expect(result.current.isAuthenticated).toBe(false);
});
it("should update accessToken state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setAccessToken("new-access-token");
});
expect(result.current.accessToken).toBe("new-access-token");
act(() => {
result.current.setAccessToken(null);
});
expect(result.current.accessToken).toBeNull();
});
it("should update userData state", () => {
const { result } = renderHook(() => useAuthStore());
const mockUserData = {
id: "123",
username: "testuser",
is_superuser: false,
is_active: true,
profile_image: "",
create_at: new Date(),
updated_at: new Date(),
};
act(() => {
result.current.setUserData(mockUserData);
});
expect(result.current.userData).toEqual(mockUserData);
act(() => {
result.current.setUserData(null);
});
expect(result.current.userData).toBeNull();
});
it("should update autoLogin state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setAutoLogin(true);
});
expect(result.current.autoLogin).toBe(true);
act(() => {
result.current.setAutoLogin(false);
});
expect(result.current.autoLogin).toBe(false);
});
it("should update apiKey state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setApiKey("new-api-key");
});
expect(result.current.apiKey).toBe("new-api-key");
act(() => {
result.current.setApiKey(null);
});
expect(result.current.apiKey).toBeNull();
});
it("should update authenticationErrorCount state", () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.setAuthenticationErrorCount(5);
});
expect(result.current.authenticationErrorCount).toBe(5);
act(() => {
result.current.setAuthenticationErrorCount(0);
});
expect(result.current.authenticationErrorCount).toBe(0);
});
});
describe("logout function", () => {
it("should reset auth-related state on logout", async () => {
const { result } = renderHook(() => useAuthStore());
// Set up some state first
act(() => {
result.current.setIsAuthenticated(true);
result.current.setIsAdmin(true);
result.current.setAccessToken("access-token");
result.current.setApiKey("api-key");
result.current.setUserData({
id: "123",
username: "test",
is_superuser: true,
is_active: true,
profile_image: "",
create_at: new Date(),
updated_at: new Date(),
});
result.current.setAutoLogin(true);
});
// Verify state is set
expect(result.current.isAuthenticated).toBe(true);
expect(result.current.isAdmin).toBe(true);
expect(result.current.accessToken).toBe("access-token");
expect(result.current.apiKey).toBe("api-key");
expect(result.current.userData).toBeTruthy();
expect(result.current.autoLogin).toBe(true);
// Perform logout
await act(async () => {
await result.current.logout();
});
// Verify state is reset
expect(result.current.isAuthenticated).toBe(false);
expect(result.current.isAdmin).toBe(false);
expect(result.current.accessToken).toBeNull();
expect(result.current.apiKey).toBeNull();
expect(result.current.userData).toBeNull();
expect(result.current.autoLogin).toBe(false);
});
});
});

View file

@ -2,7 +2,10 @@
import { Cookies } from "react-cookie";
import { create } from "zustand";
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
import {
LANGFLOW_ACCESS_TOKEN,
LANGFLOW_API_TOKEN,
} from "@/constants/constants";
import type { AuthStoreType } from "@/types/zustand/auth";
const cookies = new Cookies();
@ -12,7 +15,7 @@ const useAuthStore = create<AuthStoreType>((set, get) => ({
accessToken: cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
userData: null,
autoLogin: null,
apiKey: cookies.get("apikey_tkn_lflw"),
apiKey: cookies.get(LANGFLOW_API_TOKEN),
authenticationErrorCount: 0,
setIsAdmin: (isAdmin) => set({ isAdmin }),

View file

@ -6,6 +6,7 @@ import TableDropdownCellEditor from "@/components/core/parameterRenderComponent/
import useAlertStore from "@/stores/alertStore";
import { type ColumnField, FormatterType } from "@/types/utils/functions";
import "moment-timezone";
import { Cookies } from "react-cookie";
import { twMerge } from "tailwind-merge";
import {
DRAG_EVENTS_CUSTOM_TYPESS,
@ -536,7 +537,9 @@ export function brokenEdgeMessage({
field: string;
};
}) {
return `${source.nodeDisplayName}${source.outputDisplayName ? " | " + source.outputDisplayName : ""} -> ${target.displayName}${target.field ? " | " + target.field : ""}`;
return `${source.nodeDisplayName}${
source.outputDisplayName ? " | " + source.outputDisplayName : ""
} -> ${target.displayName}${target.field ? " | " + target.field : ""}`;
}
export function FormatColumns(columns: ColumnField[]): ColDef<any>[] {
if (!columns) return [];
@ -823,7 +826,8 @@ export interface CookieOptions {
maxAge?: number;
expires?: Date;
secure?: boolean;
sameSite?: "Strict" | "Lax" | "None";
sameSite?: "strict" | "lax" | "none";
httpOnly?: boolean;
}
/**
@ -1002,3 +1006,19 @@ export const stripReleaseStageFromVersion = (version: string): string => {
}
return version;
};
export const getAuthCookie = (cookies: Cookies, tokenName: string) => {
return cookies.get(tokenName);
};
export const setAuthCookie = (
cookies: Cookies,
tokenName: string,
value: string,
) => {
cookies.set(tokenName, value, {
path: "/",
secure: true,
sameSite: "strict",
});
};