From e82ccbde62c7997779ac7ef561e004f2d92d0ee4 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:27:43 +0200 Subject: [PATCH] fix(federation): tighten peering-notifications GET response to spec shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit db.select() returned every column including userId; spec ยง4.9 defined the response row WITHOUT userId. The leak is harmless today (user queries their own rows) but expands the public API surface beyond the spec, and would become part of the contract once Task 10 generates client types. Switching to explicit column projection. --- .../src/routes/federation.peeringNotifications.test.ts | 7 +++---- packages/server/src/routes/federation.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/server/src/routes/federation.peeringNotifications.test.ts b/packages/server/src/routes/federation.peeringNotifications.test.ts index 9938de33..02ac0027 100644 --- a/packages/server/src/routes/federation.peeringNotifications.test.ts +++ b/packages/server/src/routes/federation.peeringNotifications.test.ts @@ -195,7 +195,6 @@ describe('GET /api/federation/peering-notifications', () => { const body = response.json() as { notifications: Array<{ id: string; - userId: string; kind: string; peerOrigin: string; triggerReason: string; @@ -211,7 +210,6 @@ describe('GET /api/federation/peering-notifications', () => { // Shape check on the newest row. expect(body.notifications[0]).toEqual({ id: 'notif-new', - userId: 'alice', kind: 'denied', peerOrigin: 'https://other.example', triggerReason: 'space_join', @@ -299,10 +297,11 @@ describe('GET /api/federation/peering-notifications', () => { }); expect(response.statusCode).toBe(200); - const body = response.json() as { notifications: Array<{ id: string; userId: string }> }; + const body = response.json() as { notifications: Array<{ id: string }> }; expect(body.notifications).toHaveLength(1); + // Alice's row comes back (isolation: bob-notif is excluded by userId filter + // in the WHERE clause; the response shape itself no longer surfaces userId). expect(body.notifications[0]!.id).toBe('alice-notif'); - expect(body.notifications[0]!.userId).toBe('alice'); }); }); diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index 8acf51d2..e77fa108 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -1873,7 +1873,15 @@ export async function federationRoutes(app: FastifyInstance): Promise { : eq(schema.peerApprovalNotifications.userId, userId); const notifications = db - .select() + .select({ + id: schema.peerApprovalNotifications.id, + kind: schema.peerApprovalNotifications.kind, + peerOrigin: schema.peerApprovalNotifications.peerOrigin, + triggerReason: schema.peerApprovalNotifications.triggerReason, + triggerTarget: schema.peerApprovalNotifications.triggerTarget, + createdAt: schema.peerApprovalNotifications.createdAt, + readAt: schema.peerApprovalNotifications.readAt, + }) .from(schema.peerApprovalNotifications) .where(whereClause) .orderBy(desc(schema.peerApprovalNotifications.createdAt))