import { describe, expect, it } from "vitest"; import { signOAuthState, signSession, verifyOAuthState, verifySession, } from "../../src/admin/auth/session.js"; import { sanitizeReturnTo } from "../../src/admin/routes/authRoutes.js"; const SECRET = "test-session-secret-not-for-production"; describe("session cookie signing", () => { it("round-trips a valid session", () => { const token = signSession( { userId: "u1", feishuOpenId: "ou_1" }, SECRET, 3600, 1_700_000_000, ); const payload = verifySession(token, SECRET, 1_700_000_100); expect(payload).toEqual({ userId: "u1", feishuOpenId: "ou_1", iat: 1_700_000_000, exp: 1_700_003_600, }); }); it("rejects tampered tokens", () => { const token = signSession({ userId: "u1", feishuOpenId: "ou_1" }, SECRET); const [body] = token.split("."); expect(verifySession(`${body}.deadbeef`, SECRET)).toBeNull(); }); it("rejects expired sessions", () => { const token = signSession( { userId: "u1", feishuOpenId: "ou_1" }, SECRET, 10, 1_700_000_000, ); expect(verifySession(token, SECRET, 1_700_000_011)).toBeNull(); }); it("rejects wrong secret", () => { const token = signSession({ userId: "u1", feishuOpenId: "ou_1" }, SECRET); expect(verifySession(token, "other-secret")).toBeNull(); }); it("round-trips a connection-scoped identity", () => { const token = signSession({ userId: "u-scoped", feishuIdentityId: "identity-1", feishuConnectionId: "connection-1", feishuOrganizationId: "organization-1", }, SECRET, 3600, 1_700_000_000); expect(verifySession(token, SECRET, 1_700_000_100)).toEqual({ userId: "u-scoped", feishuIdentityId: "identity-1", feishuConnectionId: "connection-1", feishuOrganizationId: "organization-1", iat: 1_700_000_000, exp: 1_700_003_600, }); }); }); describe("oauth state signing", () => { it("round-trips state with returnTo", () => { const token = signOAuthState( { nonce: "abc", returnTo: "/admin" }, SECRET, 600, 1_700_000_000, ); expect(verifyOAuthState(token, SECRET, 1_700_000_100)).toEqual({ nonce: "abc", returnTo: "/admin", exp: 1_700_000_600, }); }); it("binds state to an Organization and connection as one inseparable scope", () => { const token = signOAuthState({ nonce: "scoped", returnTo: "/admin", organizationId: "org-acme", connectionId: "connection-acme", }, SECRET, 600, 1_700_000_000); expect(verifyOAuthState(token, SECRET, 1_700_000_100)).toEqual({ nonce: "scoped", returnTo: "/admin", organizationId: "org-acme", connectionId: "connection-acme", exp: 1_700_000_600, }); }); }); describe("sanitizeReturnTo", () => { it("allows admin paths and rewrites legacy org slug prefixes", () => { expect(sanitizeReturnTo("/admin")).toBe("/admin"); expect(sanitizeReturnTo("/admin/usage")).toBe("/admin/usage"); expect(sanitizeReturnTo("/admin/org/acme")).toBe("/admin"); expect(sanitizeReturnTo("/admin/org/acme/usage")).toBe("/admin/usage"); }); it("blocks open redirects", () => { expect(sanitizeReturnTo("https://evil.example/")).toBe("/admin"); expect(sanitizeReturnTo("//evil.example")).toBe("/admin"); expect(sanitizeReturnTo("/api/me")).toBe("/admin"); expect(sanitizeReturnTo("")).toBe("/admin"); }); });