Compare commits

...

2 commits

4 changed files with 44 additions and 22 deletions

View file

@ -94,6 +94,16 @@ app.post("/post/create", [
app.get("/channel/:channel/messages", [ app.get("/channel/:channel/messages", [
param("channel").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) param("channel").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 })
], authenticateEndpoint(async (req, res) => { ], authenticateEndpoint(async (req, res) => {
if (!config.policies.allowSavingMessages) {
// TODO: hack
res.status(200).json({
error: false,
message: "SUCCESS_CHANNEL_MESSAGES_FETCHED",
channelMessages: []
});
return;
}
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() });

View file

@ -1,5 +1,6 @@
const websockets = require("ws"); const websockets = require("ws");
const uuid = require("uuid"); const { v4 } = require("uuid");
const mongoose = require("mongoose");
const { policies, gatewayPingInterval, gatewayPingCheckInterval, clientFacingPingInterval } = require("../../../config"); const { policies, gatewayPingInterval, gatewayPingCheckInterval, clientFacingPingInterval } = require("../../../config");
const { experiments } = require("../../../experiments"); const { experiments } = require("../../../experiments");
@ -18,21 +19,21 @@ const wsCloseCodes = {
NOT_AUTHORIZED: [4006, "Not authorized"], NOT_AUTHORIZED: [4006, "Not authorized"],
FLOODING: [4007, "Flooding"], FLOODING: [4007, "Flooding"],
NO_PING: [4008, "No ping"], NO_PING: [4008, "No ping"],
UNSUPPORTED_ATTRIBUTE: [4009, "Unsupported attribute."],
}; };
const attributes = { const attributes = {
PRESENCE_UPDATES: "PRESENCE_UPDATES", PRESENCE_UPDATES: "PRESENCE_UPDATES",
SAVE_MESSAGES: "SAVE_MESSAGES"
}; };
const supportedAttributes = [attributes.PRESENCE_UPDATES, attributes.SAVE_MESSAGES]; const supportedAttributes = [attributes.PRESENCE_UPDATES];
class GatewaySession { class GatewaySession {
constructor() { constructor() {
this.authenticated = false; this.authenticated = false;
this.user = null; this.user = null;
this.token = null; this.token = null;
this.sessionId = uuid.v4(); this.sessionId = v4();
this.attributes = []; this.attributes = [];
// Specific to websocket sessions // Specific to websocket sessions
@ -110,7 +111,7 @@ class GatewayHandler {
const session = new GatewaySession(); const session = new GatewaySession();
session.setWebsocketClient(ws); session.setWebsocketClient(ws);
session.send("HELLO", { pingInterval: clientFacingPingInterval }); session.send("HELLO", { pingInterval: clientFacingPingInterval, supportedAttributes });
return session; return session;
} }
@ -177,8 +178,9 @@ class GatewayHandler {
if (data.attributes) { if (data.attributes) {
if (!Array.isArray(data.attributes) || data.attributes.length > 8) return {error: wsCloseCodes.PAYLOAD_ERROR}; if (!Array.isArray(data.attributes) || data.attributes.length > 8) return {error: wsCloseCodes.PAYLOAD_ERROR};
for (let i = 0; i < data.attributes; i++) { for (let i = 0; i < data.attributes.length; i++) {
if (!supportedAttributes.includes(data[i])) return {error: wsCloseCodes.PAYLOAD_ERROR}; if (!supportedAttributes.includes(data.attributes[i]))
return {error: wsCloseCodes.UNSUPPORTED_ATTRIBUTE};
} }
session.attributes = data.attributes; session.attributes = data.attributes;
} }
@ -232,7 +234,20 @@ class GatewayHandler {
// Check if the user is in that channel before broadcasting the message // Check if the user is in that channel before broadcasting the message
if (!session.channels.includes(data.channel._id)) return {error: wsCloseCodes.NOT_AUTHORIZED}; if (!session.channels.includes(data.channel._id)) return {error: wsCloseCodes.NOT_AUTHORIZED};
this.eachInChannel({channelId: data.channel._id}, ({ session: remoteSession }) => { this.eachInChannel({channelId: data.channel._id}, async ({ session: remoteSession }) => {
let id;
if (policies.allowSavingMessages) {
const message = await Message.create({
author: session.user._id,
channel: data.channel._id,
content: messageContent,
createdAt: new Date().getTime()
});
id = message._id;
} else {
id = new mongoose.Types.ObjectId();
}
remoteSession.send("EVENT_CREATE_MESSAGE", { remoteSession.send("EVENT_CREATE_MESSAGE", {
content: messageContent, content: messageContent,
channel: { channel: {
@ -242,18 +257,9 @@ class GatewayHandler {
_id: session.user._id, _id: session.user._id,
username: session.user.username username: session.user.username
}, },
_id: uuid.v4() _id: id
}); });
}); });
if (session.hasAttribute(attributes.SAVE_MESSAGES)) {
await Message.create({
author: session.user._id,
channel: data.channel._id,
content: messageContent,
createdAt: new Date().getTime()
});
}
} }
} }

View file

@ -18,6 +18,9 @@ module.exports = {
allowAccountCreation: true, allowAccountCreation: true,
allowLogin: true, allowLogin: true,
allowGatewayConnection: true, allowGatewayConnection: true,
// The policy below will make all messages sent over the gateway to be in plain text saved to the database.
// This is experimental and dangerous, and, as such, should generally not be used.
allowSavingMessages: true,
perUserMaxGatewayConnections: 4 perUserMaxGatewayConnections: 4
}, },
/* /*
@ -35,7 +38,6 @@ module.exports = {
gatewayPingInterval: 15000, gatewayPingInterval: 15000,
gatewayPingCheckInterval: 4500, gatewayPingCheckInterval: 4500,
clientFacingPingInterval: 14750, clientFacingPingInterval: 14750,
unsafeStoreMessages: false,
bcryptRounds: 10, bcryptRounds: 10,
roleMap: { roleMap: {
"BANNED": 0, "BANNED": 0,

View file

@ -26,11 +26,15 @@ Packets can also have JSON as a payload:
Sent by the server to the client as soon as possible after they connect to the gateway. Sent by the server to the client as soon as possible after they connect to the gateway.
This payload contains a `pingInterval` property. Every *pingInterval*, the client must send a packet simply containing `7@1`. This is the ACTION_PING payload. If the client does not send this payload at the right time, it is disconnected. JSON data format:
| Field | Description |
| - | - |
| pingInterval | Every *pingInterval*, the client must send a packet simply containing `7@1`. This is the ACTION_PING payload. If the client does not send this payload at the right time, it is disconnected. |
| supportedAttributes | An array of attributes supported by the server. If a client requests an unsupported attribute, it is disconnected from the server. |
Example: Example:
```json ```json
0@{"pingInterval":14750} 0@{"pingInterval":14750,"supportedAttributes":["PRESENCE_UPDATES"]}
``` ```
## 1:YOO ## 1:YOO
@ -194,7 +198,7 @@ Voice server signaling is done through a websocket gateway. This gateway is spec
| content | The text content of the message (max 2000 characters, min 1 character, trimmed) | | content | The text content of the message (max 2000 characters, min 1 character, trimmed) |
| channel | A [message channel object](#message-channel-object) | | channel | A [message channel object](#message-channel-object) |
| author | A [message author object](#message-author-object) | | author | A [message author object](#message-author-object) |
| _id | A UUIDv4 | | _id | An ObjectId |
## Message channel object ## Message channel object