From b2a725d084664ef424cd11dc07b80804e9a2ae31 Mon Sep 17 00:00:00 2001 From: hippoz Date: Wed, 15 Sep 2021 16:39:08 +0300 Subject: [PATCH] feat!: add `roles` system and lock presence updates behind a `PRESENCE_UPDATES` role --- brainlet/api/v2/gateway/index.js | 36 +++++++++++++++++++++++++------- resources/Docs/DOCS.md | 7 ++++++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/brainlet/api/v2/gateway/index.js b/brainlet/api/v2/gateway/index.js index 9500c1a..8c6106d 100644 --- a/brainlet/api/v2/gateway/index.js +++ b/brainlet/api/v2/gateway/index.js @@ -19,12 +19,19 @@ const wsCloseCodes = { NO_PING: [4008, "No ping"], }; +const roles = { + PRESENCE_UPDATES: "PRESENCE_UPDATES" +}; + +const supportedRoles = [roles.PRESENCE_UPDATES]; + class GatewaySession { constructor() { this.authenticated = false; this.user = null; this.token = null; this.sessionId = uuid.v4(); + this.roles = []; // Specific to websocket sessions this.isWebsocketConnection = false; @@ -33,6 +40,11 @@ class GatewaySession { this.channels = []; } + hasRole(roleName) { + if (roleName.length < 1) return true; // TODO: HACK + return this.roles.includes(roleName); + } + setWebsocketClient(ws) { this.ws = ws; this.isWebsocketConnection = true; @@ -103,7 +115,7 @@ class GatewayHandler { handleConnectionClose(ws) { if (ws.session && ws.session.user && ws.session.channels) { if (this.sessionCounters[ws.session.user._id] <= 1) { - this.eachInChannel(ws.session.channels[0], (client) => { + this.eachInChannel({channelId: ws.session.channels[0], role: roles.PRESENCE_UPDATES}, (client) => { if (client.session && client.session.isReady()) { client.session.send("EVENT_CHANNEL_MEMBERS", { [ws.session.user._id]: { @@ -140,10 +152,10 @@ class GatewayHandler { return this.wss.clients; } - eachInChannel(channelId, callback) { + eachInChannel({channelId, role=""}, callback) { const clients = this.getClients(); clients.forEach((client) => { - if (client.session && client.session.isReady() && client.session.channels.includes(channelId)) + if (client.session && client.session.isReady() && client.session.hasRole(role) && client.session.channels.includes(channelId)) callback(client); }); } @@ -161,20 +173,28 @@ class GatewayHandler { const channels = (await Channel.find().lean().sort({ _id: -1 }).limit(50).select("-posts -__v").populate("creator", User.getPulicFields(true))) || []; session.channels = channels.map(x => x._id.toString()); + if (data.roles) { + if (!Array.isArray(data.roles) || data.roles.length > 8) return {error: wsCloseCodes.PAYLOAD_ERROR}; + for (let i = 0; i < data.roles; i++) { + if (!supportedRoles.includes(data[i])) return {error: wsCloseCodes.PAYLOAD_ERROR}; + } + session.roles = data.roles; + } + session.send("YOO_ACK", { session_id: session.sessionId, channels, user: { username: session.user.username, _id: session.user._id }, __global_experiments: experiments }); const channel = session.channels[0]; if (channel) { const presence = {}; - this.eachInChannel(channel, ({ session: remoteSession }) => { + this.eachInChannel({channelId: channel}, ({ session: remoteSession }) => { presence[remoteSession.user._id] = { _id: remoteSession.user._id, username: remoteSession.user.username, status: 1, status_text: "Online" }; - if (remoteSession.sessionId !== session.sessionId) { + if (remoteSession.sessionId !== session.sessionId && remoteSession.hasRole(roles.PRESENCE_UPDATES)) { remoteSession.send("EVENT_CHANNEL_MEMBERS", { [session.user._id]: { _id: session.user._id, @@ -186,7 +206,7 @@ class GatewayHandler { } }); - session.send("EVENT_CHANNEL_MEMBERS", presence); + (session.hasRole(roles.PRESENCE_UPDATES)) && session.send("EVENT_CHANNEL_MEMBERS", presence); } } @@ -204,13 +224,13 @@ class GatewayHandler { if (typeof data.content !== "string" || typeof data.channel !== "object" || typeof data.channel._id !== "string") throw new Error("msg: invalid fields in json payload"); const messageContent = data.content.trim(); - if (messageContent.length > 2000 || messageContent === "") return; + if (messageContent.length > 2000 || messageContent.length < 1) return; if (data.channel._id.length !== 24) throw new Error("msg: payload has invalid id"); // MONGODB ONLY!! // Check if the user is in that channel before broadcasting the message if (!session.channels.includes(data.channel._id)) return {error: wsCloseCodes.NOT_AUTHORIZED}; - this.eachInChannel(data.channel._id, ({ session: remoteSession }) => { + this.eachInChannel({channelId: data.channel._id}, ({ session: remoteSession }) => { remoteSession.send("EVENT_CREATE_MESSAGE", { content: messageContent, channel: { diff --git a/resources/Docs/DOCS.md b/resources/Docs/DOCS.md index 216bb93..c2dbdb5 100644 --- a/resources/Docs/DOCS.md +++ b/resources/Docs/DOCS.md @@ -43,10 +43,11 @@ JSON data format: | Field | Description | | - | - | | token | The authentication token | +| roles | An array of attributes the client wants the server to enable. The current possible values are: `PRESENCE_UPDATES` (required for presence updates to be sent to the client) | Example: ```json -1@{"token":"my totally real token"} +1@{"token":"my totally real token","roles":["PRESENCE_UPDATES"]} ``` If the token is invalid, or the connection is otherwise rejected, the client should be disconnected as soon as possible, and no YOO\_ACK should be sent. @@ -97,6 +98,8 @@ JSON data format: Sent by the client when updating status. Usually, this packet is sent as soon as possible after getting YOO\_ACK to indicate the client is online. +**This packet is deprecated and reserved for future use** + JSON data format: | Field | Description | | - | - | @@ -114,6 +117,8 @@ Example: An object containing a list of user presences which were updated, where the keys are the user ids and the values are [user presence objects](#user-presence-object). +**This packet is only sent if the user has `PRESENCE_UPDATES` in their `roles`, specified in YOO.** + Example: ```json 6@{"userid":{_id:"userid",username:"username123",status:1,status_text:"hello"}}