feat(platform): harden access scoping and delivery baseline

This commit is contained in:
2026-03-30 00:27:31 +02:00
parent 00b936fa1f
commit 819345acfa
109 changed files with 26142 additions and 8081 deletions
@@ -4,7 +4,9 @@ import {
cancelPendingEvents,
eventBus,
flushPendingEvents,
permissionAudience,
type SseEvent,
userAudience,
} from "../sse/event-bus.js";
// Mock Redis so the module loads without a real connection.
@@ -153,4 +155,80 @@ describe("event-bus debounce", () => {
// The timestamp should be from the first event (not later)
expect(received[0]!.timestamp).toBe(before);
});
it("delivers scoped events only to matching audiences", () => {
const managerReceived: SseEvent[] = [];
const userReceived: SseEvent[] = [];
const unsubscribeManager = eventBus.subscribe((event) => {
managerReceived.push(event);
}, {
audiences: [permissionAudience("manageAllocations")],
includeUnscoped: false,
});
const unsubscribeUser = eventBus.subscribe((event) => {
userReceived.push(event);
}, {
audiences: [userAudience("user_1")],
includeUnscoped: false,
});
eventBus.emit(
SSE_EVENT_TYPES.ALLOCATION_CREATED,
{ id: "a1" },
[permissionAudience("manageAllocations")],
);
eventBus.emit(
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
{ notificationId: "n1" },
[userAudience("user_1")],
);
vi.advanceTimersByTime(50);
expect(managerReceived).toHaveLength(1);
expect(managerReceived[0]!.type).toBe(SSE_EVENT_TYPES.ALLOCATION_CREATED);
expect(userReceived).toHaveLength(1);
expect(userReceived[0]!.type).toBe(SSE_EVENT_TYPES.NOTIFICATION_CREATED);
unsubscribeManager();
unsubscribeUser();
});
it("does not batch events from different audiences together", () => {
const firstUserReceived: SseEvent[] = [];
const secondUserReceived: SseEvent[] = [];
const unsubscribeFirst = eventBus.subscribe((event) => {
firstUserReceived.push(event);
}, {
audiences: [userAudience("user_1")],
includeUnscoped: false,
});
const unsubscribeSecond = eventBus.subscribe((event) => {
secondUserReceived.push(event);
}, {
audiences: [userAudience("user_2")],
includeUnscoped: false,
});
eventBus.emit(
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
{ notificationId: "n1" },
[userAudience("user_1")],
);
eventBus.emit(
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
{ notificationId: "n2" },
[userAudience("user_2")],
);
vi.advanceTimersByTime(50);
expect(firstUserReceived).toHaveLength(1);
expect(firstUserReceived[0]!.payload).toEqual({ notificationId: "n1" });
expect(secondUserReceived).toHaveLength(1);
expect(secondUserReceived[0]!.payload).toEqual({ notificationId: "n2" });
unsubscribeFirst();
unsubscribeSecond();
});
});