From 62daa4893f374c1cbbecc63e4f1d4eec3fd29325 Mon Sep 17 00:00:00 2001 From: marzavec Date: Sun, 3 Jun 2018 11:08:35 -0700 Subject: Completed protocol decoupling --- server/package-lock.json | 100 ++++++++++++++-------------- server/package.json | 2 +- server/src/commands/core/chat.js | 1 + server/src/commands/core/disconnect.js | 21 ------ server/src/commands/core/ping.js | 12 ++++ server/src/commands/internal/disconnect.js | 30 +++++++++ server/src/commands/internal/socketreply.js | 22 ++++++ server/src/commands/mod/dumb.js | 14 ++-- server/src/commands/mod/speak.js | 11 ++- server/src/core/server.js | 16 ++++- server/src/managers/commands.js | 26 ++++---- 11 files changed, 155 insertions(+), 100 deletions(-) delete mode 100644 server/src/commands/core/disconnect.js create mode 100644 server/src/commands/core/ping.js create mode 100644 server/src/commands/internal/disconnect.js create mode 100644 server/src/commands/internal/socketreply.js (limited to 'server') diff --git a/server/package-lock.json b/server/package-lock.json index a709b1b..aa8283d 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "async": { @@ -27,8 +27,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.6", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "balanced-match": { @@ -46,7 +46,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -55,9 +55,9 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "color-convert": { @@ -65,7 +65,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -83,7 +83,7 @@ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.7.2.tgz", "integrity": "sha512-joj9ZlUOjCrwdbmiLqafeUSgkUM74NqhLsZtSqDmhKudaIY197zTrb8JMl31fMnCUuxwFT23eC/oWvrZzDLRJQ==", "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.26.0" } }, "concat-map": { @@ -111,8 +111,8 @@ "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.13.tgz", "integrity": "sha512-/6ngYM7AapueqLtvOzjv9+11N2fHDSrkxeMF1YPE20WIfaaawiBg+HZH1E5lHrcJxlKR42t6XPOEmMmqcAsU1g==", "requires": { - "bindings": "1.2.1", - "nan": "2.10.0" + "bindings": "~1.2.1", + "nan": "^2.0.7" } }, "deep-equal": { @@ -125,8 +125,8 @@ "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-1.3.0.tgz", "integrity": "sha1-bjT0AUM1HJIGlulyO7oB/8C5ZAI=", "requires": { - "leven": "2.1.0", - "lodash": "4.17.10" + "leven": "^2.0.0", + "lodash": "^4.17.2" } }, "escape-string-regexp": { @@ -144,9 +144,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs.realpath": { @@ -159,12 +159,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "graceful-fs": { @@ -187,8 +187,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -206,7 +206,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "leven": { @@ -224,7 +224,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -260,7 +260,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { @@ -278,12 +278,12 @@ "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", "requires": { - "colors": "1.2.5", - "pkginfo": "0.4.1", - "read": "1.0.7", - "revalidator": "0.1.8", - "utile": "0.3.0", - "winston": "2.1.1" + "colors": "^1.1.2", + "pkginfo": "0.x.x", + "read": "1.0.x", + "revalidator": "0.1.x", + "utile": "0.3.x", + "winston": "2.1.x" } }, "read": { @@ -291,7 +291,7 @@ "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "requires": { - "mute-stream": "0.0.7" + "mute-stream": "~0.0.4" } }, "readdir-recursive": { @@ -314,7 +314,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "stack-trace": { @@ -327,7 +327,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "universalify": { @@ -340,12 +340,12 @@ "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", "requires": { - "async": "0.9.2", - "deep-equal": "0.2.2", - "i": "0.3.6", - "mkdirp": "0.5.1", - "ncp": "1.0.1", - "rimraf": "2.6.2" + "async": "~0.9.0", + "deep-equal": "~0.2.1", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "1.0.x", + "rimraf": "2.x.x" } }, "winston": { @@ -353,13 +353,13 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", "requires": { - "async": "1.0.0", - "colors": "1.0.3", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "pkginfo": "0.3.1", - "stack-trace": "0.0.10" + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "pkginfo": "0.3.x", + "stack-trace": "0.0.x" }, "dependencies": { "async": { @@ -389,7 +389,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", "requires": { - "async-limiter": "1.0.0" + "async-limiter": "~1.0.0" } } } diff --git a/server/package.json b/server/package.json index c9750f9..8094564 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "hack.chat-v2", - "version": "2.0.1", + "version": "2.0.3", "description": "a minimal distraction free chat application", "main": "main.js", "repository": { diff --git a/server/src/commands/core/chat.js b/server/src/commands/core/chat.js index 80241d5..168c0bb 100644 --- a/server/src/commands/core/chat.js +++ b/server/src/commands/core/chat.js @@ -48,6 +48,7 @@ exports.run = async (core, server, socket, data) => { payload.trip = socket.trip; } + // TODO: Add a more contained way for modules to interact, event hooks or something? if(core.muzzledHashes && core.muzzledHashes[socket.hash]){ server.broadcast( payload, { channel: socket.channel, hash: socket.hash }); if(core.muzzledHashes[socket.hash].allies){ diff --git a/server/src/commands/core/disconnect.js b/server/src/commands/core/disconnect.js deleted file mode 100644 index 9b54214..0000000 --- a/server/src/commands/core/disconnect.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - Description: This module will be directly called by the server event handler - when a socket connection is closed or lost. It can calso be called - by a client to have the connection severed. -*/ - -exports.run = async (core, server, socket, data) => { - if (socket.channel) { - server.broadcast({ - cmd: 'onlineRemove', - nick: socket.nick - }, { channel: socket.channel }); - } - - socket.terminate(); -}; - -exports.info = { - name: 'disconnect', - description: 'Event handler or force disconnect (if your into that kind of thing)' -}; \ No newline at end of file diff --git a/server/src/commands/core/ping.js b/server/src/commands/core/ping.js new file mode 100644 index 0000000..cf1d8a4 --- /dev/null +++ b/server/src/commands/core/ping.js @@ -0,0 +1,12 @@ +/* + Description: This module is only in place to supress error notices legacy sources may get +*/ + +exports.run = async (core, server, socket, data) => { + return; +}; + +exports.info = { + name: 'ping', + description: 'This module is only in place to supress error notices legacy sources may get' +}; diff --git a/server/src/commands/internal/disconnect.js b/server/src/commands/internal/disconnect.js new file mode 100644 index 0000000..bb3981b --- /dev/null +++ b/server/src/commands/internal/disconnect.js @@ -0,0 +1,30 @@ +/* + Description: This module will be directly called by the server event handler + when a socket connection is closed or lost. +*/ + +exports.run = async (core, server, socket, data) => { + if (data.cmdKey !== server._cmdKey) { + // internal command attempt by client, increase rate limit chance and ignore + server._police.frisk(socket.remoteAddress, 20); + + return; + } + + if (socket.channel) { + server.broadcast({ + cmd: 'onlineRemove', + nick: socket.nick + }, { channel: socket.channel }); + } + + socket.terminate(); +}; + +exports.requiredData = ['cmdKey']; + +exports.info = { + name: 'disconnect', + usage: 'Internal Use Only', + description: 'Internally used to relay `onlineRemove` event to clients' +}; diff --git a/server/src/commands/internal/socketreply.js b/server/src/commands/internal/socketreply.js new file mode 100644 index 0000000..82834b7 --- /dev/null +++ b/server/src/commands/internal/socketreply.js @@ -0,0 +1,22 @@ +/* + Description: Used to relay warnings to clients internally +*/ + +exports.run = async (core, server, socket, data) => { + if (data.cmdKey !== server._cmdKey) { + // internal command attempt by client, increase rate limit chance and ignore + server._police.frisk(socket.remoteAddress, 20); + + return; + } + + server.reply({ cmd: 'warn', text: data.text }, socket); +}; + +exports.requiredData = ['cmdKey', 'text']; + +exports.info = { + name: 'socketreply', + usage: 'Internal Use Only', + description: 'Internally used to relay warnings to clients' +}; diff --git a/server/src/commands/mod/dumb.js b/server/src/commands/mod/dumb.js index ef2c5b2..0708fb8 100644 --- a/server/src/commands/mod/dumb.js +++ b/server/src/commands/mod/dumb.js @@ -1,4 +1,4 @@ -/* +/* * Description: Make a user (spammer) dumb * Author: simple */ @@ -16,7 +16,7 @@ exports.run = async (core, server, socket, data) => { if (typeof data.nick !== 'string') { return; } - + let badClient = server.findSockets({ channel: socket.channel, nick: data.nick }); if (badClient.length === 0) { @@ -38,20 +38,20 @@ exports.run = async (core, server, socket, data) => { return; } - + let record = core.muzzledHashes[badClient.hash] = { dumb:true } - + if(data.allies && Array.isArray(data.allies)){ record.allies = data.allies; } - + server.broadcast({ cmd: 'info', text: `${socket.nick} muzzled ${data.nick} in ${socket.channel}, userhash: ${badClient.hash}` }, { uType: 'mod' }); - + } exports.requiredData = ['nick']; @@ -59,5 +59,5 @@ exports.requiredData = ['nick']; exports.info = { name: 'dumb', usage: 'dumb {nick} [allies...]', - description: 'Cleanly disable a user messages and make him dumb' + description: 'Globally shadow mute a connection. Optional allies array will see muted messages.' }; diff --git a/server/src/commands/mod/speak.js b/server/src/commands/mod/speak.js index 422ad2c..643280b 100644 --- a/server/src/commands/mod/speak.js +++ b/server/src/commands/mod/speak.js @@ -1,9 +1,8 @@ -/* +/* * Description: Pardon a dumb user to be able to speak again * Author: simple */ - exports.run = async (core, server, socket, data) => { if (socket.uType == 'user') { // ignore if not mod or admin @@ -18,7 +17,7 @@ exports.run = async (core, server, socket, data) => { return; } - + let target; if (typeof data.ip === 'string') { @@ -26,14 +25,14 @@ exports.run = async (core, server, socket, data) => { } else { target = data.hash; } - + delete core.muzzledHashes[target]; - + server.broadcast({ cmd: 'info', text: `${socket.nick} unmuzzled : ${target}` }, { uType: 'mod' }); - + } exports.info = { diff --git a/server/src/core/server.js b/server/src/core/server.js index 855aeba..ded7cac 100644 --- a/server/src/core/server.js +++ b/server/src/core/server.js @@ -11,6 +11,7 @@ const wsServer = require('ws').Server; const socketReady = require('ws').OPEN; const crypto = require('crypto'); const ipSalt = (Math.random().toString(36).substring(2, 16) + Math.random().toString(36).substring(2, (Math.random() * 16))).repeat(16); +const internalCmdKey = (Math.random().toString(36).substring(2, 16) + Math.random().toString(36).substring(2, (Math.random() * 16))).repeat(16); const Police = require('./rateLimiter'); const pulseSpeed = 16000; // ping all clients every X ms @@ -26,6 +27,8 @@ class server extends wsServer { this._core = core; this._police = new Police(); this._cmdBlacklist = {}; + this._cmdKey = internalCmdKey; + this._heartBeat = setInterval(((data) => { this.beatHeart(); }).bind(this), pulseSpeed); @@ -90,7 +93,11 @@ class server extends wsServer { handleData (socket, data) { // Don't penalize yet, but check whether IP is rate-limited if (this._police.frisk(socket.remoteAddress, 0)) { - this.reply({ cmd: 'warn', text: "Your IP is being rate-limited or blocked." }, socket); + this._core.commands.handleCommand(this, socket, { + cmd: 'socketreply', + cmdKey: this._cmdKey, + text: 'Your IP is being rate-limited or blocked.' + }); return; } @@ -116,7 +123,7 @@ class server extends wsServer { return; } - if (typeof args.cmd === 'undefined' || args.cmd == 'ping') { + if (typeof args.cmd === 'undefined') { return; } @@ -142,7 +149,10 @@ class server extends wsServer { * @param {Object} socket Closing socket object */ handleClose (socket) { - this._core.commands.handleCommand(this, socket, { cmd: 'disconnect' }); + this._core.commands.handleCommand(this, socket, { + cmd: 'disconnect', + cmdKey: this._cmdKey + }); } /** diff --git a/server/src/managers/commands.js b/server/src/managers/commands.js index c38fb4d..fd743fb 100644 --- a/server/src/managers/commands.js +++ b/server/src/managers/commands.js @@ -182,10 +182,11 @@ class CommandManager { if (maybe) { // Found a suggestion, pass it on to their dyslexic self - return server.reply({ - cmd: 'warn', + return this.handleCommand(server, socket, { + cmd: 'socketreply', + cmdKey: server._cmdKey, text: `Command not found, did you mean: \`${maybe}\`?` - }, socket); + }); } // Request so mangled that I don't even, silently fail @@ -209,12 +210,12 @@ class CommandManager { } if (missing.length > 0) { - let errText = `Failed to execute '${command.info.name}': missing required ${missing.join(', ')}\n\n`; - - server.reply({ - cmd: 'warn', - text: errText - }, socket); + console.log(`Failed to execute '${command.info.name}': missing required ${missing.join(', ')}\n\n`); + this.handleCommand(server, socket, { + cmd: 'socketreply', + cmdKey: server._cmdKey, + text: `Failed to execute '${command.info.name}': missing required ${missing.join(', ')}\n\n` + }); return null; } @@ -226,10 +227,11 @@ class CommandManager { let errText = `Failed to execute '${command.info.name}': ${err}`; console.log(errText); - server.reply({ - cmd: 'warn', + this.handleCommand(server, socket, { + cmd: 'socketreply', + cmdKey: server._cmdKey, text: errText - }, socket); + }); return null; } -- cgit v1.2.1