From 2b6e771383f4c6f392b32ce26e4d759b56791132 Mon Sep 17 00:00:00 2001 From: marzavec Date: Thu, 12 Mar 2020 13:28:20 -0500 Subject: Protocol Updates and Bug Fixes --- server/package-lock.json | 39 ++++++++------------- server/package.json | 6 ++-- server/src/commands/admin/reload.js | 2 +- server/src/commands/admin/saveconfig.js | 2 +- server/src/commands/core/emote.js | 8 +++-- server/src/commands/core/join.js | 61 +++++++++++++++++++++++++-------- server/src/commands/core/move.js | 45 +++++++++++++++++++++++- server/src/commands/core/ping.js | 4 +-- server/src/commands/core/session.js | 13 +++++++ server/src/commands/core/whisper.js | 3 +- server/src/commands/mod/dumb.js | 34 ++++++++++++++++-- server/src/commands/mod/moveuser.js | 1 + server/src/commands/mod/speak.js | 18 ++++++++++ server/src/commands/mod/unban.js | 4 +-- server/src/commands/mod/unbanall.js | 2 ++ 15 files changed, 187 insertions(+), 55 deletions(-) create mode 100644 server/src/commands/core/session.js (limited to 'server') diff --git a/server/package-lock.json b/server/package-lock.json index 6957232..6a1b8c3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,24 +10,24 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "ansi-styles": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", - "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, + "ascii-captcha": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ascii-captcha/-/ascii-captcha-0.0.3.tgz", + "integrity": "sha1-NAtO1oVYOHEHsJVzBC/kc4v0mPk=" + }, "async": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -95,13 +95,12 @@ "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" }, "didyoumean2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-3.1.2.tgz", - "integrity": "sha512-5j2ZwqqXoNIUxgNWcwsqOXECB+2br7pXCBf7E3bymh9pHINvcL8knXkP67iMK/CNGZk/tf40ixCfjFV4osdFBA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-4.0.0.tgz", + "integrity": "sha512-7+OMIHqPDJ4uxeExQx8cSk26oD3KUloAQzi2R+3rmTU4IHvSDDmWZTQ6bmC4+MTw61DkYoh5ARxwS9MRoz0t2A==", "requires": { "leven": "^3.1.0", - "lodash.deburr": "^4.1.0", - "ramda": "^0.26.1" + "lodash.deburr": "^4.1.0" } }, "esm": { @@ -256,11 +255,6 @@ "winston": "2.1.x" } }, - "ramda": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" - }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -355,12 +349,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", - "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", - "requires": { - "async-limiter": "^1.0.0" - } + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" } } } diff --git a/server/package.json b/server/package.json index 311a591..f6a9a90 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "hack.chat-v2", - "version": "2.1.9", + "version": "2.1.93", "description": "a minimal distraction free chat application", "main": "main.js", "repository": { @@ -21,11 +21,11 @@ "chalk": "^3.0.0", "common-tags": "^1.8.0", "dateformat": "^3.0.3", - "didyoumean2": "^3.1.2", + "didyoumean2": "^4.0.0", "esm": "^3.2.25", "fs-extra": "^8.1.0", "prompt": "^1.0.0", "readdir-recursive": "0.0.4", - "ws": "^7.2.0" + "ws": "^7.2.3" } } diff --git a/server/src/commands/admin/reload.js b/server/src/commands/admin/reload.js index f74624e..a1d22ac 100644 --- a/server/src/commands/admin/reload.js +++ b/server/src/commands/admin/reload.js @@ -31,7 +31,7 @@ export async function run(core, server, socket, data) { } // send results to moderators (which the user using this command is higher than) - server.reply({ + server.broadcast({ cmd: 'info', text: loadResult, }, { level: UAC.isModerator }); diff --git a/server/src/commands/admin/saveconfig.js b/server/src/commands/admin/saveconfig.js index b9411af..260ff14 100644 --- a/server/src/commands/admin/saveconfig.js +++ b/server/src/commands/admin/saveconfig.js @@ -20,7 +20,7 @@ export async function run(core, server, socket) { } // return success message to moderators and admins - server.reply({ + server.broadcast({ cmd: 'info', text: 'Config saved!', }, { level: UAC.isModerator }); diff --git a/server/src/commands/core/emote.js b/server/src/commands/core/emote.js index 19a5fbe..21d14d1 100644 --- a/server/src/commands/core/emote.js +++ b/server/src/commands/core/emote.js @@ -22,7 +22,7 @@ const parseText = (text) => { // module main export async function run(core, server, socket, payload) { // check user input - const text = parseText(payload.text); + let text = parseText(payload.text); if (!text) { // lets not send objects or empty text, yea? @@ -38,11 +38,15 @@ export async function run(core, server, socket, payload) { }, socket); } + if (!text.startsWith("'")) { + text = ` ${text}`; + } + const newPayload = { cmd: 'info', type: 'emote', nick: socket.nick, - text: `@${socket.nick} ${text}`, + text: `@${socket.nick}${text}`, }; if (socket.trip) { newPayload.trip = socket.trip; diff --git a/server/src/commands/core/join.js b/server/src/commands/core/join.js index ddbdf6d..b17e1fc 100644 --- a/server/src/commands/core/join.js +++ b/server/src/commands/core/join.js @@ -18,7 +18,7 @@ const hash = (password) => { export function parseNickname(core, data) { const userInfo = { nick: '', - uType: 'user', + uType: 'user', /* @legacy */ trip: null, level: UAC.levels.default, }; @@ -41,7 +41,7 @@ export function parseNickname(core, data) { } if (hash(password + core.config.tripSalt) === core.config.adminTrip) { - userInfo.uType = 'admin'; + userInfo.uType = 'admin'; /* @legacy */ userInfo.trip = 'Admin'; userInfo.level = UAC.levels.admin; } else if (userInfo.nick.toLowerCase() === core.config.adminName.toLowerCase()) { @@ -55,7 +55,7 @@ export function parseNickname(core, data) { // for (const mod of core.config.mods) { core.config.mods.forEach((mod) => { if (userInfo.trip === mod.trip) { - userInfo.uType = 'mod'; + userInfo.uType = 'mod'; /* @legacy */ userInfo.level = UAC.levels.moderator; } }); @@ -111,40 +111,73 @@ export async function run(core, server, socket, data) { }, socket); } - userInfo.userHash = server.getSocketHash(socket); + userInfo.hash = server.getSocketHash(socket); + // assign "unique" socket ID + if (typeof socket.userid === 'undefined') { + userInfo.userid = Math.floor(Math.random() * 9999999999999); + } + + // TODO: place this within it's own function allowing import // prepare to notify channel peers const newPeerList = server.findSockets({ channel: data.channel }); - const nicks = []; + const nicks = []; /* @legacy */ + const users = []; const joinAnnouncement = { cmd: 'onlineAdd', nick: userInfo.nick, trip: userInfo.trip || 'null', - hash: userInfo.userHash, + utype: userInfo.uType, /* @legacy */ + hash: userInfo.hash, level: userInfo.level, + userid: userInfo.userid, + channel: data.channel, }; // send join announcement and prep online set for (let i = 0, l = newPeerList.length; i < l; i += 1) { server.reply(joinAnnouncement, newPeerList[i]); - nicks.push(newPeerList[i].nick); + nicks.push(newPeerList[i].nick); /* @legacy */ + + users.push({ + nick: newPeerList[i].nick, + trip: newPeerList[i].trip, + utype: newPeerList[i].uType, /* @legacy */ + hash: newPeerList[i].userHash, + level: newPeerList[i].level, + userid: newPeerList[i].userid, + channel: data.channel, + isme: false, + }); } // store user info - socket.uType = userInfo.uType; + socket.uType = userInfo.uType; /* @legacy */ socket.nick = userInfo.nick; - socket.channel = data.channel; - socket.hash = userInfo.userHash; + socket.trip = userInfo.trip; + socket.channel = data.channel; /* @legacy */ + socket.hash = userInfo.hash; socket.level = userInfo.level; - if (userInfo.trip !== null) socket.trip = userInfo.trip; - - nicks.push(socket.nick); + socket.userid = userInfo.userid; + + nicks.push(socket.nick); /* @legacy */ + users.push({ + nick: socket.nick, + trip: socket.trip, + utype: socket.uType, + hash: socket.userHash, + level: socket.level, + userid: socket.userid, + channel: data.channel, + isme: true, + }); // reply with channel peer list server.reply({ cmd: 'onlineSet', - nicks, + nicks, /* @legacy */ + users, }, socket); // stats are fun diff --git a/server/src/commands/core/move.js b/server/src/commands/core/move.js index 7eda88c..fdafab4 100644 --- a/server/src/commands/core/move.js +++ b/server/src/commands/core/move.js @@ -17,6 +17,13 @@ export async function run(core, server, socket, data) { return true; } + if (data.channel === '') { + return server.reply({ + cmd: 'warn', + text: 'Cannot move to an empty channel.', + }, socket); + } + if (data.channel === socket.channel) { // they are trying to rejoin the channel return true; @@ -53,6 +60,7 @@ export async function run(core, server, socket, data) { } } + // TODO: import function from join module // broadcast join notice to new peers const newPeerList = server.findSockets({ channel: data.channel }); const moveAnnouncement = { @@ -82,10 +90,45 @@ export async function run(core, server, socket, data) { return true; } +// module hook functions +export function initHooks(server) { + server.registerHook('in', 'chat', this.moveCheck.bind(this), 29); +} + +export function moveCheck(core, server, socket, payload) { + if (typeof payload.text !== 'string') { + return false; + } + + if (payload.text.startsWith('/move ')) { + const input = payload.text.split(' '); + + // If there is no channel target parameter + if (input[1] === undefined) { + server.reply({ + cmd: 'warn', + text: 'Refer to `/help move` for instructions on how to use this command.', + }, socket); + + return false; + } + + this.run(core, server, socket, { + cmd: 'move', + channel: input[1], + }); + + return false; + } + + return payload; +} + export const requiredData = ['channel']; export const info = { name: 'move', description: 'This will change your current channel to the new one provided', usage: ` - API: { cmd: 'move', channel: '' }`, + API: { cmd: 'move', channel: '' } + Text: /move `, }; diff --git a/server/src/commands/core/ping.js b/server/src/commands/core/ping.js index 7d8623d..6266f4c 100644 --- a/server/src/commands/core/ping.js +++ b/server/src/commands/core/ping.js @@ -3,9 +3,7 @@ */ // module main -export async function run() { - -} +export async function run() { } export const info = { name: 'ping', diff --git a/server/src/commands/core/session.js b/server/src/commands/core/session.js new file mode 100644 index 0000000..677e9a4 --- /dev/null +++ b/server/src/commands/core/session.js @@ -0,0 +1,13 @@ +/* + Description: Create a new socket session or restore previous session +*/ + +// module main +export async function run() { } + +export const info = { + name: 'session', + description: 'Restore previous state by session id or return new session id (currently unavailable)', + usage: ` + API: { cmd: 'session', id: '' }` +}; diff --git a/server/src/commands/core/whisper.js b/server/src/commands/core/whisper.js index 0c2e2d3..4424cd2 100644 --- a/server/src/commands/core/whisper.js +++ b/server/src/commands/core/whisper.js @@ -88,7 +88,7 @@ export function whisperCheck(core, server, socket, payload) { return false; } - if (payload.text.startsWith('/whisper')) { + if (payload.text.startsWith('/whisper') || payload.text.startsWith('/w ')) { const input = payload.text.split(' '); // If there is no nickname target parameter @@ -147,5 +147,6 @@ export const info = { usage: ` API: { cmd: 'whisper', nick: '', text: '' } Text: /whisper + Text: /w Alt Text: /r `, }; diff --git a/server/src/commands/mod/dumb.js b/server/src/commands/mod/dumb.js index 51fc745..ba2886d 100644 --- a/server/src/commands/mod/dumb.js +++ b/server/src/commands/mod/dumb.js @@ -66,9 +66,9 @@ export async function run(core, server, socket, data) { // module hook functions export function initHooks(server) { - server.registerHook('in', 'chat', this.chatCheck.bind(this), 25); - server.registerHook('in', 'invite', this.inviteCheck.bind(this), 25); - // TODO: add whisper hook, need hook priorities todo finished first + server.registerHook('in', 'chat', this.chatCheck.bind(this), 10); + server.registerHook('in', 'invite', this.inviteCheck.bind(this), 10); + server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 10); } // hook incoming chat commands, shadow-prevent chat if they are muzzled @@ -140,6 +140,34 @@ export function inviteCheck(core, server, socket, payload) { return payload; } +// shadow-prevent all whispers from muzzled users +export function whisperCheck(core, server, socket, payload) { + if (typeof payload.nick !== 'string') { + return false; + } + + if (typeof payload.text !== 'string') { + return false; + } + + if (core.muzzledHashes[socket.hash]) { + const targetNick = payload.nick; + + server.reply({ + cmd: 'info', + type: 'whisper', + text: `You whispered to @${targetNick}: ${payload.text}`, + }, socket); + + // blanket "spam" protection, may expose the ratelimiting lines from `chat` and use that, TODO: one day #lazydev + server.police.frisk(socket.address, 9); + + return false; + } + + return payload; +} + export const requiredData = ['nick']; export const info = { name: 'dumb', diff --git a/server/src/commands/mod/moveuser.js b/server/src/commands/mod/moveuser.js index 4b5d52c..b86edb4 100644 --- a/server/src/commands/mod/moveuser.js +++ b/server/src/commands/mod/moveuser.js @@ -68,6 +68,7 @@ export async function run(core, server, socket, data) { } } + // TODO: import from join module const newPeerList = server.findSockets({ channel: data.channel }); const moveAnnouncement = { cmd: 'onlineAdd', diff --git a/server/src/commands/mod/speak.js b/server/src/commands/mod/speak.js index f184ef5..e5ff8e2 100644 --- a/server/src/commands/mod/speak.js +++ b/server/src/commands/mod/speak.js @@ -27,6 +27,24 @@ export async function run(core, server, socket, data) { }, socket); } + if (typeof data.ip === 'string') { + if (data.ip === '*') { + core.muzzledHashes = {}; + + return server.broadcast({ + cmd: 'info', + text: `${socket.nick} unmuzzled all users`, + }, { level: UAC.isModerator }); + } + } else if (data.hash === '*') { + core.muzzledHashes = {}; + + return server.broadcast({ + cmd: 'info', + text: `${socket.nick} unmuzzled all users`, + }, { level: UAC.isModerator }); + } + // find target & remove mute status let target; if (typeof data.ip === 'string') { diff --git a/server/src/commands/mod/unban.js b/server/src/commands/mod/unban.js index 9f50e92..3b72fdc 100644 --- a/server/src/commands/mod/unban.js +++ b/server/src/commands/mod/unban.js @@ -20,8 +20,8 @@ export async function run(core, server, socket, data) { } // find target - let mode; let - target; + let mode; + let target; if (typeof data.ip === 'string') { mode = 'ip'; target = data.ip; diff --git a/server/src/commands/mod/unbanall.js b/server/src/commands/mod/unbanall.js index a5de3c8..9d417aa 100644 --- a/server/src/commands/mod/unbanall.js +++ b/server/src/commands/mod/unbanall.js @@ -14,6 +14,8 @@ export async function run(core, server, socket) { // remove arrest records server.police.clear(); + core.stats.set('users-banned', 0); + console.log(`${socket.nick} [${socket.trip}] unbanned all`); // reply with success -- cgit v1.2.1