Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/admin-sso/logout.server.test.ts
T

112 lines
4.0 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const { testWebConfig } = vi.hoisted(() => ({
testWebConfig: {
nodeEnv: "test" as string,
cryptoSecret: "test-crypto-secret-do-not-use-in-prod",
sessionSecret: "test-session-secret-do-not-use-in-prod",
routing: {
appBaseDomain: "example.test",
webAppHost: "app.example.test",
adminAppHost: "admin.example.test",
apiHost: "api.example.test",
protocol: "https" as "http" | "https",
},
},
}));
vi.mock("@hivegrid/config", async () => {
const actual = await vi.importActual<typeof import("@hivegrid/config")>("@hivegrid/config");
return {
...actual,
loadRequiredWebConfig: () => testWebConfig,
};
});
import {
ADMIN_SSO_ID_TOKEN_COOKIE,
clearIdTokenCookie,
LOCAL_ADMIN_SSO_ID_TOKEN_COOKIE,
readIdTokenFromRequest,
serializeIdTokenCookie,
} from "./logout.server";
const SECRET = "test-secret-do-not-use-in-prod";
function requestWithCookie(pair: string): Request {
return new Request("https://admin.example.test/auth/admin/logout", { headers: { cookie: pair } });
}
beforeEach(() => {
testWebConfig.nodeEnv = "test";
testWebConfig.routing.protocol = "https";
testWebConfig.routing.appBaseDomain = "example.test";
});
describe("admin SSO id-token cookie", () => {
it("uses a host-prefixed HTTPS cookie without a Domain attribute", () => {
const cookie = serializeIdTokenCookie("id-token-value", { secret: SECRET });
expect(cookie).toContain(`${ADMIN_SSO_ID_TOKEN_COOKIE}=`);
expect(cookie).toContain("Path=/");
expect(cookie).toContain("HttpOnly");
expect(cookie).toContain("SameSite=Lax");
expect(cookie).toContain("Secure");
expect(cookie).not.toContain("Domain=");
});
it("round-trips a valid signed id token", () => {
const pair = serializeIdTokenCookie("id-token-value", { secret: SECRET }).split(";")[0] ?? "";
expect(readIdTokenFromRequest(requestWithCookie(pair), { secret: SECRET })).toBe("id-token-value");
});
it("rejects tampering", () => {
const pair = serializeIdTokenCookie("id-token-value", { secret: SECRET }).split(";")[0] ?? "";
expect(readIdTokenFromRequest(requestWithCookie(`${pair}tampered`), { secret: SECRET })).toBeNull();
});
it("ignores a legacy parent-domain cookie on HTTPS", () => {
const pair = serializeIdTokenCookie("id-token-value", { secret: SECRET })
.split(";")[0]
?.replace(ADMIN_SSO_ID_TOKEN_COOKIE, LOCAL_ADMIN_SSO_ID_TOKEN_COOKIE);
expect(readIdTokenFromRequest(requestWithCookie(pair ?? ""), { secret: SECRET })).toBeNull();
});
it("does not let a legacy cookie shadow the host-prefixed id token", () => {
const securePair = serializeIdTokenCookie("id-token-value", { secret: SECRET }).split(";")[0] ?? "";
const request = requestWithCookie(`${LOCAL_ADMIN_SSO_ID_TOKEN_COOKIE}=attacker; ${securePair}`);
expect(readIdTokenFromRequest(request, { secret: SECRET })).toBe("id-token-value");
});
it("clears the exact secure cookie scope", () => {
const cookie = clearIdTokenCookie();
expect(cookie).toContain(`${ADMIN_SSO_ID_TOKEN_COOKIE}=`);
expect(cookie).toContain("Path=/");
expect(cookie).toContain("Max-Age=0");
expect(cookie).toContain("Secure");
expect(cookie).not.toContain("Domain=");
});
it("uses a distinct localhost-compatible fallback on local HTTP", () => {
testWebConfig.nodeEnv = "development";
testWebConfig.routing.protocol = "http";
testWebConfig.routing.appBaseDomain = "localhost:3000";
const cookie = serializeIdTokenCookie("id-token-value", { secret: SECRET });
const pair = cookie.split(";")[0] ?? "";
expect(cookie).toContain(`${LOCAL_ADMIN_SSO_ID_TOKEN_COOKIE}=`);
expect(cookie).not.toContain(ADMIN_SSO_ID_TOKEN_COOKIE);
expect(cookie).not.toContain("Secure");
expect(cookie).not.toContain("Domain=");
expect(readIdTokenFromRequest(new Request("http://localhost:3000/auth/admin/logout", { headers: { cookie: pair } }), { secret: SECRET })).toBe(
"id-token-value"
);
});
});