2020-11-16 21:16:25 +02:00
|
|
|
const User = require('../../../models/User');
|
|
|
|
const secret = require('../../../secret');
|
|
|
|
const config = require('../../../config');
|
2020-11-17 15:27:23 +02:00
|
|
|
const Category = require('../../../models/Category');
|
2020-11-16 21:16:25 +02:00
|
|
|
|
|
|
|
const jwt = require('jsonwebtoken');
|
2020-11-17 15:27:23 +02:00
|
|
|
const siolib = require('socket.io');
|
|
|
|
const uuid = require('uuid');
|
2020-11-16 21:16:25 +02:00
|
|
|
|
|
|
|
class GatewayServer {
|
|
|
|
constructor(httpServer) {
|
|
|
|
this._io = siolib(httpServer);
|
|
|
|
this._gateway = this._io.of('/gateway');
|
|
|
|
this.eventSetup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GatewayServer.prototype.authDisconnect = function(socket, callback) {
|
2020-11-16 22:08:23 +02:00
|
|
|
console.log('[E] [gateway] [handshake] User disconnected due to failed authentication');
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.isConnected = false;
|
2020-11-16 21:33:03 +02:00
|
|
|
socket.disconnect();
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.disconnect(true);
|
|
|
|
callback(new Error('ERR_GATEWAY_AUTH_FAIL'));
|
|
|
|
};
|
|
|
|
|
|
|
|
GatewayServer.prototype.eventSetup = function() {
|
|
|
|
this._gateway.use((socket, callback) => {
|
2020-11-16 22:08:23 +02:00
|
|
|
console.log('[*] [gateway] [handshake] User authentication attempt');
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.isConnected = false;
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2020-11-16 21:35:38 +02:00
|
|
|
if (socket.isConnected) return;
|
2020-11-20 13:25:08 +02:00
|
|
|
console.log('[E] [gateway] [handshake] User still not connected after timeout, removing...');
|
2020-11-16 21:33:03 +02:00
|
|
|
socket.disconnect();
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.disconnect(true);
|
|
|
|
}, config.gatewayStillNotConnectedTimeoutMS);
|
|
|
|
|
|
|
|
// TODO: Maybe passing the token in the query is not the best idea?
|
|
|
|
const token = socket.handshake.query.token;
|
|
|
|
|
|
|
|
if (!token) return this.authDisconnect(socket, callback);
|
|
|
|
|
|
|
|
jwt.verify(token, secret.jwtPrivateKey, {}, async (err, data) => {
|
|
|
|
if (err) return this.authDisconnect(socket, callback);
|
|
|
|
if (!data) return this.authDisconnect(socket, callback);
|
|
|
|
if (!data.username) return this.authDisconnect(socket, callback);
|
|
|
|
|
|
|
|
const user = await User.findByUsername(data.username);
|
|
|
|
|
|
|
|
if (!user) return this.authDisconnect(socket, callback);
|
|
|
|
|
|
|
|
let permissionLevel = config.roleMap[user.role];
|
|
|
|
if (!permissionLevel) {
|
|
|
|
permissionLevel = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (permissionLevel < config.roleMap.USER) return this.authDisconnect(socket, callback);
|
|
|
|
|
2020-11-17 18:13:07 +02:00
|
|
|
socket.user = {
|
|
|
|
username: data.username,
|
|
|
|
_id: user._id
|
|
|
|
};
|
2020-11-16 22:08:23 +02:00
|
|
|
console.log(`[*] [gateway] [handshake] User ${data.username} has successfully authenticated`);
|
2020-11-16 21:16:25 +02:00
|
|
|
return callback();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
this._gateway.on('connection', (socket) => {
|
2020-11-17 18:13:07 +02:00
|
|
|
console.log(`[*] [gateway] [handshake] User ${socket.user.username} connected, sending hello and waiting for yoo...`);
|
|
|
|
|
|
|
|
socket.emit('hello', {
|
|
|
|
gatewayStillNotConnectedTimeoutMS: config.gatewayStillNotConnectedTimeoutMS,
|
|
|
|
resolvedUser: {
|
|
|
|
username: socket.user.username,
|
|
|
|
_id: socket.user._id
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.once('yoo', () => {
|
2020-11-17 18:13:07 +02:00
|
|
|
console.log(`[*] [gateway] [handshake] Got yoo from ${socket.user.username}, connection is finally completed!`);
|
2020-11-16 21:16:25 +02:00
|
|
|
socket.isConnected = true;
|
2020-11-16 22:08:23 +02:00
|
|
|
|
2020-11-17 15:27:23 +02:00
|
|
|
socket.on('message', ({ category, content }) => {
|
2020-11-20 19:25:56 +02:00
|
|
|
if (!category || !content || !socket.joinedCategories || !socket.isConnected || !socket.user) return;
|
2020-11-17 18:13:07 +02:00
|
|
|
content = content.trim();
|
2020-11-20 21:35:43 +02:00
|
|
|
if (!content || content === '' || content === ' ' || content.length >= 2000) return;
|
2020-11-17 18:13:07 +02:00
|
|
|
|
2020-11-17 15:27:23 +02:00
|
|
|
// TODO: When/if category permissions are added, check if the user has permissions for that category
|
|
|
|
const categoryTitle = socket.joinedCategories[category._id];
|
|
|
|
if (!categoryTitle) return;
|
|
|
|
|
|
|
|
const messageObject = {
|
|
|
|
author: {
|
2020-11-17 18:13:07 +02:00
|
|
|
username: socket.user.username,
|
|
|
|
_id: socket.user._id
|
2020-11-17 15:27:23 +02:00
|
|
|
},
|
|
|
|
category: {
|
|
|
|
title: categoryTitle,
|
|
|
|
_id: category._id
|
|
|
|
},
|
|
|
|
content: content,
|
|
|
|
_id: uuid.v4()
|
2020-11-17 11:28:11 +02:00
|
|
|
};
|
2020-11-20 20:15:20 +02:00
|
|
|
|
|
|
|
this._gateway.in(category._id).emit('message', messageObject);
|
2020-11-16 22:08:23 +02:00
|
|
|
});
|
|
|
|
|
2020-11-17 11:28:11 +02:00
|
|
|
socket.on('subscribe', async (categories) => {
|
2020-11-20 19:25:56 +02:00
|
|
|
if ( !socket.isConnected || !socket.user || !categories || !Array.isArray(categories) || categories === []) return;
|
2020-11-20 13:25:08 +02:00
|
|
|
for (const v of categories) {
|
|
|
|
if (!v) continue;
|
2020-11-17 15:27:23 +02:00
|
|
|
// TODO: When/if category permissions are added, check if the user has permissions for that category
|
|
|
|
const category = await Category.findById(v);
|
|
|
|
if (category && category.title && category._id) {
|
|
|
|
if (!socket.joinedCategories) socket.joinedCategories = {};
|
2020-11-20 20:15:20 +02:00
|
|
|
if (socket.joinedCategories[v]) continue;
|
|
|
|
socket.joinedCategories[v] = category.title;
|
|
|
|
await socket.join(v);
|
2020-11-17 15:27:23 +02:00
|
|
|
}
|
2020-11-16 22:08:23 +02:00
|
|
|
}
|
|
|
|
});
|
2020-11-16 21:16:25 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = GatewayServer;
|