diff --git a/src/data/permissions.test.ts b/src/data/permissions.test.ts index 61e3e16..8b2fd0e 100644 --- a/src/data/permissions.test.ts +++ b/src/data/permissions.test.ts @@ -88,3 +88,46 @@ describe("PermissionStore", () => { }); }); }); + +import { GUEST_PERMISSION_FLAGS } from "./permissions.js"; + +describe("resolvePermissionContext guest branch", () => { + const noStore = { + getCapabilities: () => [], + getBotAccess: () => [] as string[], + setPermissions: () => {}, + pruneBot: () => {}, + }; + + it("guest has no member capabilities and exposes the guest permissions + bots", () => { + const ctx = resolvePermissionContext("guest", "__guest__", noStore, { + bots: ["bot1"], + permissions: { + addToQueue: true, playNext: false, playNow: false, + skip: true, transport: false, removeClear: false, playMode: false, + }, + }); + expect([...ctx.capabilities]).toEqual([]); + expect(ctx.bots).toBeInstanceOf(Set); + expect((ctx.bots as Set).has("bot1")).toBe(true); + expect(ctx.guest?.addToQueue).toBe(true); + expect(ctx.guest?.skip).toBe(true); + }); + + it("guest with bots:'all' resolves to 'all'", () => { + const ctx = resolvePermissionContext("guest", "__guest__", noStore, { + bots: "all", + permissions: { + addToQueue: true, playNext: false, playNow: false, + skip: false, transport: false, removeClear: false, playMode: false, + }, + }); + expect(ctx.bots).toBe("all"); + }); + + it("exposes the 7 canonical flags", () => { + expect([...GUEST_PERMISSION_FLAGS].sort()).toEqual( + ["addToQueue", "playMode", "playNext", "playNow", "removeClear", "skip", "transport"].sort() + ); + }); +}); diff --git a/src/data/permissions.ts b/src/data/permissions.ts index 556bc79..8bece14 100644 --- a/src/data/permissions.ts +++ b/src/data/permissions.ts @@ -21,6 +21,27 @@ export function isCapability(x: string): x is Capability { export type BotAccess = "all" | string[]; +export interface GuestPermissions { + addToQueue: boolean; + playNext: boolean; + playNow: boolean; + skip: boolean; + transport: boolean; + removeClear: boolean; + playMode: boolean; +} + +export const GUEST_PERMISSION_FLAGS = [ + "addToQueue", + "playNext", + "playNow", + "skip", + "transport", + "removeClear", + "playMode", +] as const; +export type GuestFlag = (typeof GUEST_PERMISSION_FLAGS)[number]; + export interface PermissionStore { getCapabilities(userId: string): Capability[]; getBotAccess(userId: string): BotAccess; @@ -71,16 +92,26 @@ export function createPermissionStore(db: Database.Database): PermissionStore { export interface PermissionContext { capabilities: Set; bots: "all" | Set; + guest?: GuestPermissions; } export function resolvePermissionContext( - role: "admin" | "member", + role: "admin" | "member" | "guest", userId: string, - store: PermissionStore + store: PermissionStore, + guest?: { bots: BotAccess; permissions: GuestPermissions } ): PermissionContext { if (role === "admin") { return { capabilities: new Set(CAPABILITIES), bots: "all" }; } + if (role === "guest") { + const bots = guest?.bots ?? []; + return { + capabilities: new Set(), + bots: bots === "all" ? "all" : new Set(bots), + guest: guest?.permissions, + }; + } const access = store.getBotAccess(userId); return { capabilities: new Set(store.getCapabilities(userId)),