import { describe, expect, it, vi } from "vitest"; import { createHmac } from "node:crypto"; import { authorizeChannel, parseTopic, verifyTicket } from "./chat.js"; const workspaceId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; const channelId = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"; const userId = "cccccccc-cccc-4ccc-8ccc-cccccccccccc"; const secret = "a".repeat(40); const claims = { iss: "echodo-web", aud: "echodo-chat-v1", scope: "chat:subscribe", sub: userId, workspaceId, exp: Math.floor(Date.now() / 1000) + 30, }; function ticket(payload: object, key = secret) { const body = Buffer.from(JSON.stringify(payload)).toString("base64url"); return `${body}.${createHmac("sha256", key).update(body).digest("base64url")}`; } describe("chat ticket", () => { it("rejects unsigned, altered, expired and wrong-scope tickets", () => { expect(verifyTicket(ticket(claims), secret)).toEqual(claims); expect(verifyTicket(ticket(claims).replace(/.$/, "x"), secret)).toBeNull(); expect(verifyTicket(ticket({ ...claims, exp: 1 }), secret)).toBeNull(); expect( verifyTicket(ticket({ ...claims, scope: "yjs" }), secret), ).toBeNull(); expect( verifyTicket(ticket({ ...claims, aud: "other" }), secret), ).toBeNull(); expect(verifyTicket(ticket(claims, "b".repeat(40)), secret)).toBeNull(); }); }); describe("topic and authorization", () => { it("accepts only exact workspace and channel UUID topics", () => { expect( parseTopic(`chat:v1:workspace:${workspaceId}:channel:${channelId}`), ).toEqual({ workspaceId, channelId }); expect( parseTopic(`chat:v1:workspace:${workspaceId}:channel:${channelId}:evil`), ).toBeNull(); }); it("requires workspace membership, and channel membership for private/direct (no admin bypass)", async () => { const lookup = vi .fn() .mockResolvedValueOnce({ kind: "public", workspaceMember: true, channelMember: false, }) .mockResolvedValueOnce({ kind: "private", workspaceMember: true, channelMember: false, }) .mockResolvedValueOnce({ kind: "direct", workspaceMember: true, channelMember: true, }) .mockResolvedValueOnce({ kind: "public", workspaceMember: false, channelMember: true, }) .mockRejectedValueOnce(new Error("db down")); expect(await authorizeChannel(lookup, workspaceId, channelId, userId)).toBe( true, ); expect(await authorizeChannel(lookup, workspaceId, channelId, userId)).toBe( false, ); expect(await authorizeChannel(lookup, workspaceId, channelId, userId)).toBe( true, ); expect(await authorizeChannel(lookup, workspaceId, channelId, userId)).toBe( false, ); expect(await authorizeChannel(lookup, workspaceId, channelId, userId)).toBe( false, ); }); });