mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-26 09:12:16 +00:00
Closes #11006 # Which Problems Are Solved This PR addresses an issue where the ignoreUnknownUsernames setting was not being respected in certain scenarios during the login flow. Specifically: - When a user was found but rejected due to login settings (e.g., disableLoginWithEmail), the system would return a "User not found" error instead of redirecting to the password page as dictated by ignoreUnknownUsernames. - When a user was not found, and the flow fell through the registration checks (e.g., IDP redirect failed), it would also return "User not found" without checking ignoreUnknownUsernames. # How the Problems Are Solved - Introduced a helper function `handleUserNotFound` in `apps/login/src/lib/server/loginname.ts` to centralize the logic for checking ignoreUnknownUsernames and redirecting to the password page. - Updated the user validation logic to call handleUserNotFound when a user is found but rejected by policy. - Updated the registration fallback logic to call handleUserNotFound instead of directly returning an error. - Added new test cases in `apps/login/src/lib/server/loginname.test.ts` to verify the fix and ensure no regressions. # Additional Changes - Fixed inconsistency in translation files --------- Co-authored-by: Livio Spring <[email protected]>
911 lines
26 KiB
TypeScript
911 lines
26 KiB
TypeScript
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
import {
|
|
Cookie,
|
|
addSessionToCookie,
|
|
updateSessionCookie,
|
|
removeSessionFromCookie,
|
|
getMostRecentSessionCookie,
|
|
getSessionCookieById,
|
|
getSessionCookieByLoginName,
|
|
getAllSessionCookieIds,
|
|
getAllSessions,
|
|
getMostRecentCookieWithLoginname,
|
|
setLanguageCookie,
|
|
} from "./cookies";
|
|
|
|
// Mock dependencies
|
|
vi.mock("next/headers", () => ({
|
|
cookies: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@zitadel/client", () => ({
|
|
timestampDate: vi.fn((ts: any) => new Date(Number(ts.seconds) * 1000)),
|
|
timestampFromMs: vi.fn((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any),
|
|
}));
|
|
|
|
import { cookies } from "next/headers";
|
|
import { timestampDate, timestampFromMs } from "@zitadel/client";
|
|
|
|
describe("cookies", () => {
|
|
let mockCookies: any;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockCookies = {
|
|
get: vi.fn(),
|
|
set: vi.fn(),
|
|
};
|
|
vi.mocked(cookies).mockResolvedValue(mockCookies);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("setLanguageCookie", () => {
|
|
it("should set language cookie with correct parameters", async () => {
|
|
await setLanguageCookie("en");
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith({
|
|
name: "NEXT_LOCALE",
|
|
value: "en",
|
|
httpOnly: true,
|
|
path: "/",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("addSessionToCookie", () => {
|
|
const mockSession: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
organization: "org-1",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
it("should add new session to empty cookie", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await addSessionToCookie({ session: mockSession });
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
name: "sessions",
|
|
value: JSON.stringify([mockSession]),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should prepend new session to existing sessions", async () => {
|
|
const existingSession: Cookie = {
|
|
id: "session-0",
|
|
token: "token-0",
|
|
loginName: "[email protected]",
|
|
creationTs: "1600000000000",
|
|
expirationTs: "1700000000000",
|
|
changeTs: "1600000000000",
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([existingSession]),
|
|
});
|
|
|
|
await addSessionToCookie({ session: mockSession });
|
|
|
|
const expectedSessions = [mockSession, existingSession];
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
value: JSON.stringify(expectedSessions),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should update existing session with same loginName", async () => {
|
|
const existingSession: Cookie = {
|
|
id: "session-old",
|
|
token: "token-old",
|
|
loginName: "[email protected]",
|
|
creationTs: "1600000000000",
|
|
expirationTs: "1700000000000",
|
|
changeTs: "1600000000000",
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([existingSession]),
|
|
});
|
|
|
|
const updatedSession: Cookie = {
|
|
...mockSession,
|
|
id: "session-new",
|
|
token: "token-new",
|
|
};
|
|
|
|
await addSessionToCookie({ session: updatedSession });
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
expect(sessions).toHaveLength(1);
|
|
expect(sessions[0].id).toBe("session-new");
|
|
expect(sessions[0].token).toBe("token-new");
|
|
});
|
|
|
|
it("should handle cookie overflow by replacing oldest session", async () => {
|
|
// Create many sessions to exceed MAX_COOKIE_SIZE (2048)
|
|
const manySessions: Cookie[] = Array.from({ length: 10 }, (_, i) => ({
|
|
id: `session-${i}`,
|
|
token: `token-${i}-very-long-token-to-increase-size-padding-padding-padding`,
|
|
loginName: `user${i}@example.com`,
|
|
creationTs: `${1600000000000 + i * 1000}`,
|
|
expirationTs: `${1700000000000 + i * 1000}`,
|
|
changeTs: `${1600000000000 + i * 1000}`,
|
|
}));
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify(manySessions),
|
|
});
|
|
|
|
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
await addSessionToCookie({ session: mockSession });
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith("WARNING COOKIE OVERFLOW");
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
// Should have new session and all but the first old session
|
|
expect(sessions[0]).toEqual(mockSession);
|
|
expect(sessions).not.toContain(manySessions[0]);
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it("should cleanup expired sessions when cleanup is true", async () => {
|
|
const now = Date.now();
|
|
const expiredSession: Cookie = {
|
|
id: "session-expired",
|
|
token: "token-expired",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now - 10000000}`,
|
|
expirationTs: `${now - 1000}`, // Expired
|
|
changeTs: `${now - 10000000}`,
|
|
};
|
|
|
|
const validSession: Cookie = {
|
|
id: "session-valid",
|
|
token: "token-valid",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now}`,
|
|
expirationTs: `${now + 10000000}`, // Not expired
|
|
changeTs: `${now}`,
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([expiredSession, validSession]),
|
|
});
|
|
|
|
vi.mocked(timestampDate).mockImplementation((ts: any) => new Date(Number(ts.seconds) * 1000));
|
|
vi.mocked(timestampFromMs).mockImplementation((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any);
|
|
|
|
await addSessionToCookie({ session: mockSession, cleanup: true });
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
// Should not include expired session
|
|
const hasExpired = sessions.some((s: Cookie) => s.id === "session-expired");
|
|
expect(hasExpired).toBe(false);
|
|
});
|
|
|
|
it("should set sameSite to 'none' when iFrameEnabled is true", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await addSessionToCookie({ session: mockSession, iFrameEnabled: true });
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sameSite: "none",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should set sameSite to 'lax' when iFrameEnabled is false or undefined", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await addSessionToCookie({ session: mockSession, iFrameEnabled: false });
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sameSite: "lax",
|
|
}),
|
|
);
|
|
|
|
mockCookies.set.mockClear();
|
|
await addSessionToCookie({ session: mockSession });
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sameSite: "lax",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("updateSessionCookie", () => {
|
|
const mockSession: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
it("should update existing session by id", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([mockSession]),
|
|
});
|
|
|
|
const updatedSession: Cookie = {
|
|
...mockSession,
|
|
token: "new-token",
|
|
changeTs: "1700000001000",
|
|
};
|
|
|
|
await updateSessionCookie({
|
|
id: "session-1",
|
|
session: updatedSession,
|
|
});
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
expect(sessions[0].token).toBe("new-token");
|
|
expect(sessions[0].changeTs).toBe("1700000001000");
|
|
});
|
|
|
|
it("should throw error if session id not found", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([mockSession]),
|
|
});
|
|
|
|
await expect(
|
|
updateSessionCookie({
|
|
id: "non-existent-id",
|
|
session: mockSession,
|
|
}),
|
|
).rejects.toThrow("updateSessionCookie<T>: session id not found");
|
|
});
|
|
|
|
it("should cleanup expired sessions when cleanup is true", async () => {
|
|
const now = Date.now();
|
|
const expiredSession: Cookie = {
|
|
id: "session-expired",
|
|
token: "token-expired",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now - 10000000}`,
|
|
expirationTs: `${now - 1000}`,
|
|
changeTs: `${now - 10000000}`,
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([mockSession, expiredSession]),
|
|
});
|
|
|
|
vi.mocked(timestampDate).mockImplementation((ts: any) => new Date(Number(ts.seconds) * 1000));
|
|
vi.mocked(timestampFromMs).mockImplementation((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any);
|
|
|
|
await updateSessionCookie({
|
|
id: "session-1",
|
|
session: { ...mockSession, token: "updated" },
|
|
cleanup: true,
|
|
});
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
const hasExpired = sessions.some((s: Cookie) => s.id === "session-expired");
|
|
expect(hasExpired).toBe(false);
|
|
});
|
|
|
|
it("should respect iFrameEnabled parameter", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([mockSession]),
|
|
});
|
|
|
|
await updateSessionCookie({
|
|
id: "session-1",
|
|
session: mockSession,
|
|
iFrameEnabled: true,
|
|
});
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sameSite: "none",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("removeSessionFromCookie", () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000001000",
|
|
};
|
|
|
|
it("should remove session by id", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
await removeSessionFromCookie({ session: session1 });
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
expect(sessions).toHaveLength(1);
|
|
expect(sessions[0].id).toBe("session-2");
|
|
});
|
|
|
|
it("should handle removing non-existent session gracefully", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1]),
|
|
});
|
|
|
|
const nonExistentSession: Cookie = {
|
|
...session2,
|
|
id: "non-existent",
|
|
};
|
|
|
|
await removeSessionFromCookie({ session: nonExistentSession });
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
expect(sessions).toHaveLength(1);
|
|
expect(sessions[0].id).toBe("session-1");
|
|
});
|
|
|
|
it("should cleanup expired sessions when cleanup is true", async () => {
|
|
const now = Date.now();
|
|
const expiredSession: Cookie = {
|
|
id: "session-expired",
|
|
token: "token-expired",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now - 10000000}`,
|
|
expirationTs: `${now - 1000}`,
|
|
changeTs: `${now - 10000000}`,
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2, expiredSession]),
|
|
});
|
|
|
|
vi.mocked(timestampDate).mockImplementation((ts: any) => new Date(Number(ts.seconds) * 1000));
|
|
vi.mocked(timestampFromMs).mockImplementation((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any);
|
|
|
|
await removeSessionFromCookie({ session: session1, cleanup: true });
|
|
|
|
const setCall = mockCookies.set.mock.calls[0][0];
|
|
const sessions = JSON.parse(setCall.value);
|
|
|
|
expect(sessions).toHaveLength(1);
|
|
expect(sessions[0].id).toBe("session-2");
|
|
});
|
|
|
|
it("should respect iFrameEnabled parameter", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
await removeSessionFromCookie({
|
|
session: session1,
|
|
iFrameEnabled: true,
|
|
});
|
|
|
|
expect(mockCookies.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sameSite: "none",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("getMostRecentSessionCookie", () => {
|
|
it("should return session with most recent changeTs", async () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000005000", // Most recent
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getMostRecentSessionCookie();
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-2");
|
|
});
|
|
|
|
it("should return undefined when no session cookie exists", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await expect(getMostRecentSessionCookie()).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should handle single session", async () => {
|
|
const session: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session]),
|
|
});
|
|
|
|
const result = await getMostRecentSessionCookie();
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-1");
|
|
});
|
|
});
|
|
|
|
describe("getSessionCookieById", () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
organization: "org-1",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
organization: "org-2",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000001000",
|
|
};
|
|
|
|
it("should find session by id", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getSessionCookieById({ sessionId: "session-1" });
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-1");
|
|
expect(result?.loginName).toBe("[email protected]");
|
|
});
|
|
|
|
it("should filter by organization when provided", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getSessionCookieById({
|
|
sessionId: "session-2",
|
|
organization: "org-2",
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-2");
|
|
expect(result?.organization).toBe("org-2");
|
|
});
|
|
|
|
it("should return undefined if session not found", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1]),
|
|
});
|
|
|
|
await expect(getSessionCookieById({ sessionId: "non-existent" })).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should return undefined if organization doesn't match", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1]),
|
|
});
|
|
|
|
await expect(
|
|
getSessionCookieById({
|
|
sessionId: "session-1",
|
|
organization: "wrong-org",
|
|
}),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should return undefined when no session cookie exists", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await expect(getSessionCookieById({ sessionId: "session-1" })).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("getSessionCookieByLoginName", () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
organization: "org-1",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
organization: "org-2",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000001000",
|
|
};
|
|
|
|
it("should find session by loginName", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getSessionCookieByLoginName({
|
|
loginName: "[email protected]",
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-1");
|
|
expect(result?.loginName).toBe("[email protected]");
|
|
});
|
|
|
|
it("should filter by organization when provided", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getSessionCookieByLoginName({
|
|
loginName: "[email protected]",
|
|
organization: "org-2",
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe("session-2");
|
|
expect(result?.organization).toBe("org-2");
|
|
});
|
|
|
|
it("should return undefined if session not found", async () => {
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1]),
|
|
});
|
|
|
|
await expect(getSessionCookieByLoginName({ loginName: "[email protected]" })).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should return undefined when no session cookie exists", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await expect(getSessionCookieByLoginName({ loginName: "[email protected]" })).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("getAllSessionCookieIds", () => {
|
|
it("should return all session IDs", async () => {
|
|
const sessions: Cookie[] = [
|
|
{
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
},
|
|
{
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000001000",
|
|
},
|
|
];
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify(sessions),
|
|
});
|
|
|
|
const result = await getAllSessionCookieIds();
|
|
|
|
expect(result).toEqual(["session-1", "session-2"]);
|
|
});
|
|
|
|
it("should filter expired sessions when cleanup is true", async () => {
|
|
const now = Date.now();
|
|
const sessions: Cookie[] = [
|
|
{
|
|
id: "session-valid",
|
|
token: "token-valid",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now}`,
|
|
expirationTs: `${now + 10000000}`,
|
|
changeTs: `${now}`,
|
|
},
|
|
{
|
|
id: "session-expired",
|
|
token: "token-expired",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now - 10000000}`,
|
|
expirationTs: `${now - 1000}`,
|
|
changeTs: `${now - 10000000}`,
|
|
},
|
|
];
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify(sessions),
|
|
});
|
|
|
|
vi.mocked(timestampDate).mockImplementation((ts: any) => new Date(Number(ts.seconds) * 1000));
|
|
vi.mocked(timestampFromMs).mockImplementation((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any);
|
|
|
|
const result = await getAllSessionCookieIds(true);
|
|
|
|
expect(result).toEqual(["session-valid"]);
|
|
});
|
|
|
|
it("should return empty array when no sessions exist", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
const result = await getAllSessionCookieIds();
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("getAllSessions", () => {
|
|
it("should return all sessions", async () => {
|
|
const sessions: Cookie[] = [
|
|
{
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
},
|
|
{
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000001000",
|
|
},
|
|
];
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify(sessions),
|
|
});
|
|
|
|
const result = await getAllSessions();
|
|
|
|
expect(result).toEqual(sessions);
|
|
});
|
|
|
|
it("should filter expired sessions when cleanup is true", async () => {
|
|
const now = Date.now();
|
|
const validSession: Cookie = {
|
|
id: "session-valid",
|
|
token: "token-valid",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now}`,
|
|
expirationTs: `${now + 10000000}`,
|
|
changeTs: `${now}`,
|
|
};
|
|
|
|
const expiredSession: Cookie = {
|
|
id: "session-expired",
|
|
token: "token-expired",
|
|
loginName: "[email protected]",
|
|
creationTs: `${now - 10000000}`,
|
|
expirationTs: `${now - 1000}`,
|
|
changeTs: `${now - 10000000}`,
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([validSession, expiredSession]),
|
|
});
|
|
|
|
vi.mocked(timestampDate).mockImplementation((ts: any) => new Date(Number(ts.seconds) * 1000));
|
|
vi.mocked(timestampFromMs).mockImplementation((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any);
|
|
|
|
const result = await getAllSessions(true);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].id).toBe("session-valid");
|
|
});
|
|
|
|
it("should return empty array when no sessions exist", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
const result = await getAllSessions();
|
|
|
|
expect(result).toEqual([]);
|
|
expect(consoleSpy).toHaveBeenCalledWith("getAllSessions: No session cookie found, returning empty array");
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("getMostRecentCookieWithLoginname", () => {
|
|
it("should return most recent session for loginName", async () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000005000", // Most recent
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getMostRecentCookieWithLoginname({
|
|
loginName: "[email protected]",
|
|
});
|
|
|
|
expect(result.id).toBe("session-2");
|
|
});
|
|
|
|
it("should return undefined when no matching session found", async () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000005000",
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
await expect(
|
|
getMostRecentCookieWithLoginname({
|
|
loginName: "[email protected]",
|
|
}),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("should filter by organization when provided", async () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
organization: "org-1",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000005000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
organization: "org-2",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000010000", // More recent but different org
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getMostRecentCookieWithLoginname({
|
|
loginName: "[email protected]",
|
|
organization: "org-1",
|
|
});
|
|
|
|
expect(result.id).toBe("session-1");
|
|
expect(result.organization).toBe("org-1");
|
|
});
|
|
|
|
it("should return most recent session when no loginName provided", async () => {
|
|
const session1: Cookie = {
|
|
id: "session-1",
|
|
token: "token-1",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000000000",
|
|
expirationTs: "1800000000000",
|
|
changeTs: "1700000000000",
|
|
};
|
|
|
|
const session2: Cookie = {
|
|
id: "session-2",
|
|
token: "token-2",
|
|
loginName: "[email protected]",
|
|
creationTs: "1700000001000",
|
|
expirationTs: "1800000001000",
|
|
changeTs: "1700000005000",
|
|
};
|
|
|
|
mockCookies.get.mockReturnValue({
|
|
value: JSON.stringify([session1, session2]),
|
|
});
|
|
|
|
const result = await getMostRecentCookieWithLoginname({});
|
|
|
|
expect(result.id).toBe("session-2");
|
|
});
|
|
|
|
it("should return undefined when no session cookie exists", async () => {
|
|
mockCookies.get.mockReturnValue(undefined);
|
|
|
|
await expect(
|
|
getMostRecentCookieWithLoginname({
|
|
loginName: "[email protected]",
|
|
}),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
});
|