From f353ecbd9e9e8e409303bb4f5c5590e95f579869 Mon Sep 17 00:00:00 2001 From: marzavec Date: Sun, 7 Apr 2019 17:04:10 -0700 Subject: refactoring 2 of 2 --- server/src/serverLib/MainServer.js | 138 +++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 53 deletions(-) (limited to 'server/src/serverLib/MainServer.js') diff --git a/server/src/serverLib/MainServer.js b/server/src/serverLib/MainServer.js index 628d8ab..c7a4bbe 100644 --- a/server/src/serverLib/MainServer.js +++ b/server/src/serverLib/MainServer.js @@ -7,15 +7,15 @@ * */ -const wsServer = require('ws').Server; -const socketReady = require('ws').OPEN; -const crypto = require('crypto'); -const ipSalt = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join(''); -const internalCmdKey = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join(''); +const WsServer = require('ws').Server; +const SocketReady = require('ws').OPEN; +const Crypto = require('crypto'); const RateLimiter = require('./RateLimiter'); -const pulseSpeed = 16000; // ping all clients every X ms +const PulseSpeed = 16000; // ping all clients every X ms +const IpSalt = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join(''); +const InternalCmdKey = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join(''); -class MainServer extends wsServer { +class MainServer extends WsServer { /** * Create a HackChat server instance. * @@ -24,13 +24,32 @@ class MainServer extends wsServer { constructor (core) { super({ port: core.config.websocketPort }); - this._core = core; - this._hooks = {}; - this._police = new RateLimiter(); - this._cmdBlacklist = {}; - this._cmdKey = internalCmdKey; + this.core = core; + this.hooks = {}; + this.police = new RateLimiter(); + this.cmdBlacklist = {}; - this._heartBeat = setInterval(() => this.beatHeart(), pulseSpeed); + this.setupServer(); + this.loadHooks(); + } + + /** + * Internal command key getter. Used to verify that internal only commands + * originate internally and not from a connected client. + * TODO: update to a structure that cannot be passed through json + * + * @type {String} readonly + */ + get cmdKey () { + return InternalCmdKey; + } + + /** + * Create ping interval and setup server event listeners + * + */ + setupServer () { + this.heartBeat = setInterval(() => this.beatHeart(), PulseSpeed); this.on('error', (err) => { this.handleError('server', err); @@ -39,8 +58,6 @@ class MainServer extends wsServer { this.on('connection', (socket, request) => { this.newConnection(socket, request); }); - - this.loadHooks(); } /** @@ -56,7 +73,7 @@ class MainServer extends wsServer { for (let i = 0, l = targetSockets.length; i < l; i++) { try { - if (targetSockets[i].readyState === socketReady) { + if (targetSockets[i].readyState === SocketReady) { targetSockets[i].ping(); } } catch (e) { } @@ -93,18 +110,18 @@ class MainServer extends wsServer { */ handleData (socket, data) { // Don't penalize yet, but check whether IP is rate-limited - if (this._police.frisk(socket.remoteAddress, 0)) { - this._core.commands.handleCommand(this, socket, { + if (this.police.frisk(socket.remoteAddress, 0)) { + this.core.commands.handleCommand(this, socket, { cmd: 'socketreply', - cmdKey: this._cmdKey, - text: 'Your IP is being rate-limited or blocked.' + cmdKey: this.cmdKey, + text: 'You are being rate-limited or blocked.' }); return; } // Penalize here, but don't do anything about it - this._police.frisk(socket.remoteAddress, 1); + this.police.frisk(socket.remoteAddress, 1); // Ignore ridiculously large packets if (data.length > 65536) { @@ -112,7 +129,7 @@ class MainServer extends wsServer { } // Start sent data verification - var payload = null; + let payload = null; try { payload = JSON.parse(data); } catch (e) { @@ -124,6 +141,11 @@ class MainServer extends wsServer { return; } + // TODO: make this more flexible + /* + * Issue #1: hard coded `cmd` check + * Issue #2: hard coded `cmd` value checks + */ if (typeof payload.cmd === 'undefined') { return; } @@ -136,18 +158,19 @@ class MainServer extends wsServer { return; } - if (typeof this._cmdBlacklist[payload.cmd] === 'function') { + if (typeof this.cmdBlacklist[payload.cmd] === 'function') { return; } + // End TODO // // Execute `in` (incoming data) hooks and process results payload = this.executeHooks('in', socket, payload); if (typeof payload === 'string') { // A hook malfunctioned, reply with error - this._core.commands.handleCommand(this, socket, { + this.core.commands.handleCommand(this, socket, { cmd: 'socketreply', - cmdKey: this._cmdKey, + cmdKey: this.cmdKey, text: payload }); @@ -158,7 +181,7 @@ class MainServer extends wsServer { } // Finished verification & hooks, pass to command modules - this._core.commands.handleCommand(this, socket, payload); + this.core.commands.handleCommand(this, socket, payload); } /** @@ -167,9 +190,9 @@ class MainServer extends wsServer { * @param {Object} socket Closing socket object */ handleClose (socket) { - this._core.commands.handleCommand(this, socket, { + this.core.commands.handleCommand(this, socket, { cmd: 'disconnect', - cmdKey: this._cmdKey + cmdKey: this.cmdKey }); } @@ -198,9 +221,9 @@ class MainServer extends wsServer { if (typeof payload === 'string') { // A hook malfunctioned, reply with error - this._core.commands.handleCommand(this, socket, { + this.core.commands.handleCommand(this, socket, { cmd: 'socketreply', - cmdKey: this._cmdKey, + cmdKey: this.cmdKey, text: payload }); @@ -211,7 +234,7 @@ class MainServer extends wsServer { } try { - if (socket.readyState === socketReady) { + if (socket.readyState === SocketReady) { socket.send(JSON.stringify(payload)); } } catch (e) { } @@ -232,6 +255,8 @@ class MainServer extends wsServer { * * @param {Object} payload Object to convert to json for transmission * @param {Object} filter see `this.findSockets()` + * + * @return {Boolean} False if no clients matched the filter, true if data sent */ broadcast (payload, filter) { let targetSockets = this.findSockets(filter); @@ -255,6 +280,8 @@ class MainServer extends wsServer { * = {} // matches all * = { channel: 'programming' } // matches any socket where (`socket.channel` === 'programming') * = { channel: 'programming', nick: 'Marzavec' } // matches any socket where (`socket.channel` === 'programming' && `socket.nick` === 'Marzavec') + * + * @return {Array} Clients who matched the filter requirements */ findSockets (filter) { let filterAttribs = Object.keys(filter); @@ -310,14 +337,16 @@ class MainServer extends wsServer { * encodes and shortens the output, returns that value * * @param {Object||String} target Either the target socket or ip as string + * + * @return {String} Hashed client connection string */ getSocketHash (target) { - let sha = crypto.createHash('sha256'); + let sha = Crypto.createHash('sha256'); if (typeof target === 'string') { - sha.update(target + ipSalt); + sha.update(target + IpSalt); } else { - sha.update(target.remoteAddress + ipSalt); + sha.update(target.remoteAddress + IpSalt); } return sha.digest('base64').substr(0, 15); @@ -332,26 +361,26 @@ class MainServer extends wsServer { // clear current hooks (if any) this.clearHooks(); // notify each module to register their hooks (if any) - this._core.commands.initCommandHooks(this); + this.core.commands.initCommandHooks(this); - if (typeof this._hooks['in'] !== 'undefined') { + if (typeof this.hooks['in'] !== 'undefined') { // start sorting, with incoming first - let curHooks = [ ...this._hooks['in'].keys() ]; + let curHooks = [ ...this.hooks['in'].keys() ]; let hookObj = []; for (let i = 0, j = curHooks.length; i < j; i++) { - hookObj = this._hooks['in'].get(curHooks[i]); + hookObj = this.hooks['in'].get(curHooks[i]); hookObj.sort( (h1, h2) => h1.priority - h2.priority ); - this._hooks['in'].set(hookObj); + this.hooks['in'].set(hookObj); } } - if (typeof this._hooks['out'] !== 'undefined') { + if (typeof this.hooks['out'] !== 'undefined') { // then outgoing - curHooks = [ ...this._hooks['out'].keys() ]; + curHooks = [ ...this.hooks['out'].keys() ]; for (let i = 0, j = curHooks.length; i < j; i++) { - hookObj = this._hooks['out'].get(curHooks[i]); + hookObj = this.hooks['out'].get(curHooks[i]); hookObj.sort( (h1, h2) => h1.priority - h2.priority ); - this._hooks['out'].set(hookObj); + this.hooks['out'].set(hookObj); } } } @@ -371,15 +400,15 @@ class MainServer extends wsServer { priority = 25; } - if (typeof this._hooks[type] === 'undefined') { - this._hooks[type] = new Map(); + if (typeof this.hooks[type] === 'undefined') { + this.hooks[type] = new Map(); } - if (!this._hooks[type].has(command)) { - this._hooks[type].set(command, []); + if (!this.hooks[type].has(command)) { + this.hooks[type].set(command, []); } - this._hooks[type].get(command).push({ + this.hooks[type].get(command).push({ run: hookFunction, priority: priority }); @@ -395,17 +424,19 @@ class MainServer extends wsServer { * @param {String} type The type of event, typically `in` (incoming) or `out` (outgoing) * @param {Object} socket Either the target client or the client triggering the hook (depending on `type`) * @param {Object} payload Either incoming data from client or outgoing data (depending on `type`) + * + * @return {Object || Boolean} */ executeHooks (type, socket, payload) { let command = payload.cmd; - if (typeof this._hooks[type] !== 'undefined') { - if (this._hooks[type].has(command)) { - let hooks = this._hooks[type].get(command); + if (typeof this.hooks[type] !== 'undefined') { + if (this.hooks[type].has(command)) { + let hooks = this.hooks[type].get(command); for (let i = 0, j = hooks.length; i < j; i++) { try { - payload = hooks[i].run(this._core, this, socket, payload); + payload = hooks[i].run(this.core, this, socket, payload); } catch (err) { let errText = `Hook failure, '${type}', '${command}': ${err}`; console.log(errText); @@ -425,9 +456,10 @@ class MainServer extends wsServer { /** * Wipe server hooks to make ready for module reload calls + * */ clearHooks () { - this._hooks = {}; + this.hooks = {}; } } -- cgit v1.2.1