From baaae2b850edd19b5e87717c28051ad7343408f7 Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 28 Oct 2026 16:08:00 +0000 Subject: [PATCH] Allow users to subscribe to community chat topics --- Dockers/socket-broker/README.md | 5 +-- Dockers/socket-broker/index.js | 8 ++++- .../socket-broker/test/socket-broker.test.js | 31 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Dockers/socket-broker/README.md b/Dockers/socket-broker/README.md index 62df01c..eb81bc1 100644 --- a/Dockers/socket-broker/README.md +++ b/Dockers/socket-broker/README.md @@ -19,7 +19,8 @@ Frontend / mobile clients │ Socket.IO (connection token + topic token) ▼ Socket broker ── rooms: user/{uuid}/notifications - ▲ event/{slug}/ticket_statistics + ▲ community/user/{uuid} + │ event/{slug}/ticket_statistics │ POST /publish (API key) Laravel backend ``` @@ -74,7 +75,7 @@ const socket = io("https://bram-sockets.comedykit.be", { }); ``` -A user may always subscribe to `user/{their-own-id}/notifications`. Any other topic requires a topic token whose `sub` matches the connection subject and whose `topic` matches the subscription exactly. +A user may always subscribe to `user/{their-own-id}/notifications` and `community/user/{their-own-id}`. Any other topic requires a topic token whose `sub` matches the connection subject and whose `topic` matches the subscription exactly. ## WebSocket API diff --git a/Dockers/socket-broker/index.js b/Dockers/socket-broker/index.js index 0d27da1..e35454d 100644 --- a/Dockers/socket-broker/index.js +++ b/Dockers/socket-broker/index.js @@ -172,7 +172,7 @@ function createBroker(env = process.env) { return false; } - return /^(user\/[0-9a-fA-F-]{36}\/notifications|event\/[A-Za-z0-9_-]+\/ticket_statistics)$/.test(topic); + return /^(user\/[0-9a-fA-F-]{36}\/notifications|community\/user\/[0-9a-fA-F-]{36}|event\/[A-Za-z0-9_-]+\/ticket_statistics)$/.test(topic); } function normalizeSubscription(payload) { @@ -205,6 +205,12 @@ function createBroker(env = process.env) { return userTopic[1] === socket.data.auth.sub; } + const communityUserTopic = topic.match(/^community\/user\/([^/]+)$/); + + if (communityUserTopic) { + return communityUserTopic[1] === socket.data.auth.sub; + } + const verifiedTopicToken = verifyToken(topicToken, 'topic'); if (!verifiedTopicToken) { diff --git a/Dockers/socket-broker/test/socket-broker.test.js b/Dockers/socket-broker/test/socket-broker.test.js index 0e4dc71..9589675 100644 --- a/Dockers/socket-broker/test/socket-broker.test.js +++ b/Dockers/socket-broker/test/socket-broker.test.js @@ -203,6 +203,37 @@ test('socket broker', async t => { socket.close(); }); + await t.test('a user can subscribe to their own community topic only', async () => { + const userId = randomUuid(); + const otherUserId = randomUuid(); + const socket = connect({ token: connectToken(userId) }); + + await once(socket, 'connect'); + + socket.emit('subscribe', `community/user/${userId}`); + const subscribed = await once(socket, 'subscribed'); + assert.equal(subscribed.topic, `community/user/${userId}`); + + socket.emit('subscribe', `community/user/${otherUserId}`); + const error = await once(socket, 'error'); + assert.equal(error.code, 'FORBIDDEN_TOPIC'); + + socket.close(); + }); + + await t.test('invalid community topics are rejected', async () => { + const userId = randomUuid(); + const socket = connect({ token: connectToken(userId) }); + + await once(socket, 'connect'); + + socket.emit('subscribe', 'community/user/not-a-uuid'); + const error = await once(socket, 'error'); + assert.equal(error.code, 'INVALID_TOPIC'); + + socket.close(); + }); + await t.test('event stats require a matching topic token', async () => { const userId = randomUuid(); const eventTopic = 'event/test-event/ticket_statistics';