aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README.md4
-rw-r--r--server/package.json5
-rw-r--r--server/src/commands/admin/listusers.js1
-rw-r--r--server/src/commands/admin/reload.js4
-rw-r--r--server/src/commands/admin/saveconfig.js6
-rw-r--r--server/src/commands/core/chat.js16
-rw-r--r--server/src/commands/core/help.js45
-rw-r--r--server/src/commands/core/invite.js12
-rw-r--r--server/src/commands/core/join.js8
-rw-r--r--server/src/commands/core/showcase.js4
-rw-r--r--server/src/commands/core/stats.js1
-rw-r--r--server/src/commands/mod/ban.js11
-rw-r--r--server/src/commands/mod/kick.js6
-rw-r--r--server/src/commands/mod/unban.js12
-rw-r--r--server/src/core/server.js16
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: '<category name>' }
+ Show specific command -> { cmd: 'help', command: '<command name>' }`;
+
+ 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:<category name> | command:<command name> ])',
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;
}