Files
curriculum-project-hub/hub/test/unit/session.test.ts
T

112 lines
3.2 KiB
TypeScript

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/org/acme" },
SECRET,
600,
1_700_000_000,
);
expect(verifyOAuthState(token, SECRET, 1_700_000_100)).toEqual({
nonce: "abc",
returnTo: "/admin/org/acme",
exp: 1_700_000_600,
});
});
it("binds state to an Organization and connection as one inseparable scope", () => {
const token = signOAuthState({
nonce: "scoped",
returnTo: "/admin/org/acme",
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/org/acme",
organizationId: "org-acme",
connectionId: "connection-acme",
exp: 1_700_000_600,
});
});
});
describe("sanitizeReturnTo", () => {
it("allows admin paths", () => {
expect(sanitizeReturnTo("/admin/org/acme")).toBe("/admin/org/acme");
});
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");
});
});