From 7d8220d838f82f7ad1ffedbcbd3b955aef6d71a2 Mon Sep 17 00:00:00 2001 From: marzavec Date: Sat, 10 Mar 2018 22:41:17 -0800 Subject: stabilized modules and server cmd field --- README.md | 4 ++- server/package.json | 5 ++++ server/src/commands/admin/listusers.js | 1 - server/src/commands/admin/reload.js | 4 +-- server/src/commands/admin/saveconfig.js | 6 ++++- server/src/commands/core/chat.js | 16 +++++++++--- server/src/commands/core/help.js | 45 +++++++++++++++++++++++---------- server/src/commands/core/invite.js | 12 +++++---- server/src/commands/core/join.js | 8 ++++-- server/src/commands/core/showcase.js | 4 ++- server/src/commands/core/stats.js | 1 - server/src/commands/mod/ban.js | 11 +++++--- server/src/commands/mod/kick.js | 6 ++++- server/src/commands/mod/unban.js | 12 ++++++--- server/src/core/server.js | 16 +++++++----- 15 files changed, 106 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ee00267..d09ae24 100644 --- a/README.md +++ b/README.md @@ -79,4 +79,6 @@ This project is licensed under the WTFPL License - see the [http://www.wtfpl.net ## Acknowledgments -* Andrew Belt [https://github.com/AndrewBelt/hack.chat](https://github.com/AndrewBelt/hack.chat) +* Andrew Belt, [https://github.com/AndrewBelt/hack.chat](https://github.com/AndrewBelt/hack.chat), for original base work + +* wwAndrew [https://github.com/sendMeYourGitOrSomething](https://youtu.be/oHg5SJYRHA0), for finding server flaws including attack vectors diff --git a/server/package.json b/server/package.json index db47e43..219c6b3 100644 --- a/server/package.json +++ b/server/package.json @@ -3,12 +3,17 @@ "version": "2.0.0", "description": "a minimal distraction free chat application", "main": "main.js", + "repository": { + "type": "git", + "url": "git+https://github.com/hack-chat/main.git" + }, "engines": { "node": ">= 8.10.0", "npm": ">= 5.7.1" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js", "config": "node src/scripts/configure.js", "debug": "node src/scripts/debug.js", "dev": "node src/scripts/debug.js" diff --git a/server/src/commands/admin/listusers.js b/server/src/commands/admin/listusers.js index a853518..1ddb16b 100644 --- a/server/src/commands/admin/listusers.js +++ b/server/src/commands/admin/listusers.js @@ -36,6 +36,5 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'listusers', - usage: 'listusers', description: 'Outputs all current channels and sockets in those channels' }; diff --git a/server/src/commands/admin/reload.js b/server/src/commands/admin/reload.js index 7aefbcf..88e40c7 100644 --- a/server/src/commands/admin/reload.js +++ b/server/src/commands/admin/reload.js @@ -13,8 +13,9 @@ exports.run = async (core, server, socket, data) => { let loadResult = core.managers.dynamicImports.reloadDirCache('src/commands'); loadResult += core.commands.loadCommands(); - if (loadResult == '') + if (loadResult == '') { loadResult = 'Commands reloaded without errors!'; + } server.reply({ cmd: 'info', @@ -29,6 +30,5 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'reload', - usage: 'reload', description: '(Re)loads any new commands into memory, outputs errors if any' }; diff --git a/server/src/commands/admin/saveconfig.js b/server/src/commands/admin/saveconfig.js index e1a3ebe..4c6dff8 100644 --- a/server/src/commands/admin/saveconfig.js +++ b/server/src/commands/admin/saveconfig.js @@ -21,6 +21,11 @@ exports.run = async (core, server, socket, data) => { return; } + server.reply({ + cmd: 'info', + text: 'Config saved!' + }, socket); + server.broadcast({ cmd: 'info', text: 'Config saved!' @@ -29,6 +34,5 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'saveconfig', - usage: 'saveconfig', description: 'Saves current config' }; diff --git a/server/src/commands/core/chat.js b/server/src/commands/core/chat.js index 4fe3b80..c086077 100644 --- a/server/src/commands/core/chat.js +++ b/server/src/commands/core/chat.js @@ -4,15 +4,23 @@ 'use strict'; -exports.run = async (core, server, socket, data) => { - // process text - let text = String(data.text); +function parseText(text) { + if (typeof text !== 'string') { + return false; + } + // strip newlines from beginning and end text = text.replace(/^\s*\n|^\s+$|\n\s*$/g, ''); // replace 3+ newlines with just 2 newlines text = text.replace(/\n{3,}/g, "\n\n"); + + return text; +} + +exports.run = async (core, server, socket, data) => { + let text = parseText(data.text); if (!text) { - // lets not send empty text? + // lets not send objects or empty text, yea? return; } diff --git a/server/src/commands/core/help.js b/server/src/commands/core/help.js index 17478d0..7b5237c 100644 --- a/server/src/commands/core/help.js +++ b/server/src/commands/core/help.js @@ -4,19 +4,38 @@ 'use strict'; +const stripIndents = require('common-tags').stripIndents; + exports.run = async (core, server, socket, data) => { - let reply = `Help usage: { cmd: 'help', type: 'categories'} or { cmd: 'help', type: 'commandname'}`; - - if (typeof data.type === 'undefined') { - // - } else { - if (data.type == 'categories') { - let categories = core.commands.categories(); - // TODO: bad output, fix this - reply = `Command Categories:\n${categories}`; - } else { - // TODO: finish this module later - } + // verify passed arguments + let typeDt = typeof data.type; + let catDt = typeof data.category; + let cmdDt = typeof data.command; + if (typeDt !== 'undefined' && typeDt !== 'string' ) { + return; + } else if (catDt !== 'undefined' && catDt !== 'string' ) { + return; + } else if (cmdDt !== 'undefined' && cmdDt !== 'string' ) { + return; + } + + // set default reply + let reply = stripIndents`Help usage: + Show all categories -> { cmd: 'help', type: 'categories' } + Show all commands in category -> { cmd: 'help', category: '' } + Show specific command -> { cmd: 'help', command: '' }`; + + if (typeDt !== 'undefined') { + let categories = core.commands.categories().sort(); + reply = `Command Categories:\n${categories.map(c => `- ${c.replace('../src/commands/', '')}`).join('\n')}`; + } else if (catDt !== 'undefined') { + let catCommands = core.commands.all('../src/commands/' + data.category).sort((a, b) => a.info.name.localeCompare(b.info.name)); + reply = `${data.category} commands:\n${catCommands.map(c => `- ${c.info.name}`).join('\n')}`; + } else if (cmdDt !== 'undefined') { + let command = core.commands.get(data.command); + reply = stripIndents` + Usage: ${command.info.usage || command.info.name} + Description: ${command.info.description || '¯\_(ツ)_/¯'}`; } server.reply({ @@ -28,6 +47,6 @@ exports.run = async (core, server, socket, data) => { // optional parameters are marked, all others are required exports.info = { name: 'help', // actual command name - usage: 'help ([type:categories] | [type:command])', + usage: 'help ([ type:categories] | [category: | command: ])', description: 'Outputs information about the servers current protocol' }; diff --git a/server/src/commands/core/invite.js b/server/src/commands/core/invite.js index 1c70ac1..7d162d6 100644 --- a/server/src/commands/core/invite.js +++ b/server/src/commands/core/invite.js @@ -9,14 +9,16 @@ function verifyNickname(nick) { } exports.run = async (core, server, socket, data) => { - let targetNick = String(data.nick); + if (typeof data.nick !== 'string') { + return; + } - if (!verifyNickname(targetNick)) { + if (!verifyNickname(data.nick)) { // Not a valid nickname? Chances are we won't find them return; } - if (targetNick == socket.nick) { + if (data.nick == socket.nick) { // TODO: reply with something witty? They invited themself return; } @@ -36,7 +38,7 @@ exports.run = async (core, server, socket, data) => { cmd: 'info', text: `${socket.nick} invited you to ?${channel}` }; - let inviteSent = server.broadcast( payload, { channel: socket.channel, nick: targetNick }); + let inviteSent = server.broadcast( payload, { channel: socket.channel, nick: data.nick }); if (!inviteSent) { server.reply({ @@ -49,7 +51,7 @@ exports.run = async (core, server, socket, data) => { server.reply({ cmd: 'info', - text: `You invited ${targetNick} to ?${channel}` + text: `You invited ${data.nick} to ?${channel}` }, socket); core.managers.stats.increment('invites-sent'); diff --git a/server/src/commands/core/join.js b/server/src/commands/core/join.js index 6a65851..609e39d 100644 --- a/server/src/commands/core/join.js +++ b/server/src/commands/core/join.js @@ -32,14 +32,18 @@ exports.run = async (core, server, socket, data) => { return; } - let channel = String(data.channel).trim(); + if (typeof data.channel !== 'string' || typeof data.nick !== 'string') { + return; + } + + let channel = data.channel.trim(); if (!channel) { // Must join a non-blank channel return; } // Process nickname - let nick = String(data.nick); + let nick = data.nick; let nickArray = nick.split('#', 2); nick = nickArray[0].trim(); diff --git a/server/src/commands/core/showcase.js b/server/src/commands/core/showcase.js index aaa474c..5d15ea2 100644 --- a/server/src/commands/core/showcase.js +++ b/server/src/commands/core/showcase.js @@ -16,6 +16,7 @@ const createReply = (echoInput) => { // `exports.run()` is required and will always be passed (core, server, socket, data) // be sure it's asyn too +// this is the main function exports.run = async (core, server, socket, data) => { server.reply({ @@ -35,12 +36,13 @@ exports.init = (core) => { } // optional, if `data.echo` is missing `exports.run()` will never be called & the user will be alerted +// remember; this will only verify that the data is not undefined, not the type of data exports.requiredData = ['echo']; // optional parameters are marked, all others are required exports.info = { name: 'showcase', // actual command name aliases: ['templateModule'], // optional, an array of other names this module can be executed by - usage: 'showcase {echo}', // used for help output + usage: 'showcase {echo}', // used for help output, can be ommited if no parameters are required description: 'Simple command module template & info' // used for help output }; diff --git a/server/src/commands/core/stats.js b/server/src/commands/core/stats.js index 841675f..ba28319 100644 --- a/server/src/commands/core/stats.js +++ b/server/src/commands/core/stats.js @@ -50,6 +50,5 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'stats', - usage: 'stats', description: 'Sends back current server stats to the calling client' }; diff --git a/server/src/commands/mod/ban.js b/server/src/commands/mod/ban.js index fde1ad8..1880ef3 100644 --- a/server/src/commands/mod/ban.js +++ b/server/src/commands/mod/ban.js @@ -10,7 +10,11 @@ exports.run = async (core, server, socket, data) => { return; } - let targetNick = String(data.nick); + if (typeof data.nick !== 'string') { + return; + } + + let targetNick = data.nick; let badClient = null; for (let client of server.clients) { // Find badClient's socket @@ -38,11 +42,12 @@ exports.run = async (core, server, socket, data) => { return; } - // TODO: ratelimiting here // TODO: add reference to banned users nick or unban by nick cmd - //POLICE.arrest(getAddress(badClient)) + server._police.arrest(badClient.remoteAddress); // TODO: add event to log? + console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`); + server.broadcast({ cmd: 'info', text: `Banned ${targetNick}` diff --git a/server/src/commands/mod/kick.js b/server/src/commands/mod/kick.js index 5cd524d..a730caf 100644 --- a/server/src/commands/mod/kick.js +++ b/server/src/commands/mod/kick.js @@ -10,7 +10,11 @@ exports.run = async (core, server, socket, data) => { return; } - let targetNick = String(data.nick); + if (typeof data.nick !== 'string') { + return; + } + + let targetNick = data.nick; let badClient = null; for (let client of server.clients) { // Find badClient's socket diff --git a/server/src/commands/mod/unban.js b/server/src/commands/mod/unban.js index cc1016a..193b614 100644 --- a/server/src/commands/mod/unban.js +++ b/server/src/commands/mod/unban.js @@ -10,11 +10,15 @@ exports.run = async (core, server, socket, data) => { return; } - let ip = String(data.ip); - let nick = String(data.nick); // for future upgrade + if (typeof data.ip !== 'string') { + return; + } + + let ip = data.ip; + let nick = data.nick; // for future upgrade - // TODO: remove ip from ratelimiter - // POLICE.pardon(ip) + // TODO: support remove by nick future upgrade + server._police.pardon(badClient.remoteAddress); console.log(`${socket.nick} [${socket.trip}] unbanned ${/*nick || */ip} in ${socket.channel}`); server.reply({ diff --git a/server/src/core/server.js b/server/src/core/server.js index c16aec8..98763af 100644 --- a/server/src/core/server.js +++ b/server/src/core/server.js @@ -63,7 +63,6 @@ class server extends wsServer { * @param {String} data Message sent from client */ handleData (socket, data) { - // TODO: Rate limit here // 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); @@ -87,18 +86,23 @@ class server extends wsServer { socket.close(); } - if (args === null) + if (args === null) { return; + } - if (typeof args.cmd === 'undefined' || args.cmd == 'ping') + if (typeof args.cmd === 'undefined' || args.cmd == 'ping') { return; + } - var cmd = args.cmd; + if (typeof args.cmd !== 'string') { + return; + } - if (typeof socket.channel === 'undefined' && cmd !== 'join') + if (typeof socket.channel === 'undefined' && args.cmd !== 'join') { return; + } - if (typeof this._cmdBlacklist[cmd] === 'function') { + if (typeof this._cmdBlacklist[args.cmd] === 'function') { return; } -- cgit v1.2.1