Files
hongjr03 7f09fb1f13 feat(hub): drop redundant /admin/org/:slug path + release v0.0.35 (#11)
Silo hostname already carries tenancy. Admin SPA routes become /admin/..., legacy bookmarks redirect, login lands on /admin.

Co-authored-by: Hong Jiarong <me@jrhim.com>
Co-committed-by: Hong Jiarong <me@jrhim.com>
2026-07-19 01:36:10 +08:00

115 lines
3.4 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" },
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");
});
});