Submatter/apps/collab-server/src/chat.test.ts

87 lines
2.9 KiB
TypeScript
Raw Normal View History

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,
);
});
});