diff options
32 files changed, 123 insertions, 164 deletions
diff --git a/server/.gitignore b/.gitignore index 46de862..4bf93f7 100644 --- a/server/.gitignore +++ b/.gitignore @@ -60,4 +60,4 @@ typings/ # next.js build output .next -config/
\ No newline at end of file +server/config/
\ No newline at end of file diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..4fa9055 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,46 @@ +You can programmatically access hack.chat using the following commands via a websocket. To prevent getting disconnected, ping the server every 50 seconds. A list of wrappers written for accessing hack.chat can be found [here](https://github.com/hack-chat/3rd-party-software-list#libraries). + +The commands are to be sent through a websocket to the URL `wss://hack.chat/chat-ws` (everything sent and received are `string`s). If you are sending messages locally or to another domain, replace 'hack.chat' with the respective domain. If you're running your own instance of hack.chat, you can retain backwards-compatibility in order to ensure that software created for the main server will work on yours too. + +All commands sent must be JSON objects with the command specified in the `"cmd"` key. For example: +```json +{ + "cmd": "join", + "channel": "programming", + "nick": "johndoe#fag" +} +``` + +hack.chat has three permission levels. When you access a command, hack.chat automatically knows your permission level from your trip code. The lowest permission level is `core`. `mod` is above `core`, so it can access `core` commands in addition to `mod` commands. `admin` is similarly above `mod`. + +# `core` + +|Command|Parameters|Explanation| +|-------|----------|-----------| +|`changenick`|`nick`|Changes the current connection's nickname.| +|`chat`|`text`|This broadcasts `text` to the channel the user is connected to.| +|`disconnect`||An event handler or forced disconnect.| +|`invite`|`nick`|Generates a pseudo-unique channel name and passes it to both the calling user and `nick`.| +|`join`|`channel`, `nick`|Places the calling socket into the target channel with the target nick and broadcasts the event to the channel.| +|`morestats`||Sends back the current server's stats to the calling client.| +|`move`|`channel`|This will change the current channel to `channel`.| +|`stats`||Sends back legacy server stats to the calling client. Use `morestats` when possible.| +|`help`|`category` or `command`|Gives documentation programmatically. If `category` (the permission level, such as `mod`) is sent, a list of commands available to that permission level will be sent back (as a `string` and not an `array`). This list only includes what is unique to that category and not every command a user with that permission level could perform. If `command` (e.g., `chat`) is sent, a description of the command will be sent back.| + +# `mod` + +|Command|Parameters|Explanation| +|-------|----------|-----------| +|`ban`|`nick`|Disconnects the target nickname in the same channel as the calling socket and adds it to the rate limiter.| +|`kick`|`nick`|Silently forces target client(s) into another channel. `nick` may be `string` or `array` of `string`s.| +|`unban`|`ip` or `hash`|Removes the target ip from the rate limiter.| + +# `admin` + +|Command|Parameters|Explanation| +|-------|----------|-----------| +|`addmod`|`nick`|Adds the target trip to the config as a mod and upgrades the socket type.| +|`listusers`||Outputs all current channels and sockets in those channels.| +|`reload`||(Re)loads any new commands into memory and outputs errors, if any.| +|`saveconfig`||Saves the current config.| +|`shout`|`text`|Displays the passed text to each client connected.|
\ No newline at end of file @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. @@ -1,107 +1,55 @@ -# Hack.Chat +# hack.chat -[https://hack.chat/](https://hack.chat/) is a minimal, distraction-free, account-less, log-less, disappearing chat service that is easily deployable as your own service. The client comes bundled with LaTeX rendering provided by [https://github.com/Khan/KaTeX](https://github.com/Khan/KaTeX). +[hack.chat](https://hack.chat/) is a minimal, distraction-free, accountless, logless, disappearing chat service which is easily deployable as your own service. The client comes bundled with LaTeX rendering provided by [KaTeX](https://github.com/Khan/KaTeX). -A list of software developed for the hack.chat framework can be found at: [https://github.com/hack-chat/3rd-party-software-list](https://github.com/hack-chat/3rd-party-software-list). This includes bots, clients, docker containers & more. +A list of software developed for the hack.chat framework can be found at the [3rd party software list](https://github.com/hack-chat/3rd-party-software-list) repository. This includes bots, clients, docker containers, etc. -This is a backwards compatible continuation of the work by Andrew Belt [https://github.com/AndrewBelt/hack.chat](https://github.com/AndrewBelt/hack.chat). The server code has been updated to ES6 along with several new features- including new commands and hot-reload of the commands/protocol. +This is a backwards compatible continuation of the [work by Andrew Belt](https://github.com/AndrewBelt/hack.chat). The server code has been updated to ES6 along with several new features including new commands and hot-reload of the commands/protocol. There is also [documentation](DOCUMENTATION.md) and a [changelog](CHANGELOG.md). -## Getting Started +# Installation -These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. +## Prerequisites -### Prerequisites +- [node.js 8.10.0](https://nodejs.org/en/download/package-manager/#windows) or higher -The following versions are __required__: +## Installing -``` - node >= 8.10.0 - npm >= 5.7.1 -``` +1. [Clone](https://help.github.com/articles/cloning-a-repository/) the repository: `git clone https://github.com/hack-chat/main.git` +1. Change the directory: `cd main` +1. Install the dependencies using a package manager of your choice: + - npm: `npm install` + - yarn: `yarn install` +1. Configure: `node server/main.js` -An installation guide for your operating system can be found at: [https://nodejs.org/en/download/package-manager/](https://nodejs.org/en/download/package-manager/) + If you change the `websocketPort` option during the config setup then these changes will need to be reflected on [line 59 of client.js](https://github.com/hack-chat/main/blob/master/client/client.js#L59). -### Installing +# Usage -First you will first need to clone this git, if you are unfamiliar with this process read [https://help.github.com/articles/cloning-a-repository/](https://help.github.com/articles/cloning-a-repository/), or to clone with git: +1. `cd` into the repository: `cd main` +1. Start the server with a process manager. For example, with [PM2](https://github.com/Unitech/pm2): `pm2 start server/main.js --name HackChat` +1. Launch: `client/index.html` +1. (OPTIONAL) If you want to deploy your hack.chat instance to a server, push everything except the `node_modules` directory and install the dependencies using a package manager of your choice: + - npm: `npm install` + - yarn: `yarn install` + + You can now run start the server software with a process manager. The client code will need to be copied into your http server directory. If you plan on using SSL to serve the client; you will need to use a reverse proxy, as TLS is not natively supported by the hack.chat server software (this may change in future releases). -``` -git clone https://github.com/hack-chat/main.git -``` +# Contributing -Once cloned, the server will need to be setup. Using your terminal run: +- If you are modifying commands, make sure it is backwards compatible with the legacy client and you update the documentation accordingly. +- Use [the template](templateCommand.js) to learn how to create new commands. +- Use two space indents. +- Name files in camelCase. +- Scripts that do not default to strict mode (such as modules) must use the `'use strict'` directive. -``` -cd main/server/ -npm install -``` +# Credits -Or on a Windows machine with Yarn installed: +* [**Marzavec**](https://github.com/marzavec) - *Initial work* +* [**MinusGix**](https://github.com/MinusGix) - *Base updates* +* Andrew Belt, https://github.com/AndrewBelt/hack.chat, for original base work +* [wwandrew](https://github.com/wwandrew/), for finding server flaws (including attack vectors) and submitting ~~___incredibly detailed___~~ bug reports +* [Everyone else](https://github.com/hack-chat/main/graphs/contributors) who participated in this project. -``` -cd main/server/ -yarn install -``` +# License -This will install the required packages to run hack.chat. Next the server will need to be configured, again in your terminal run: - -``` -node main.js -``` - -The configuration script will execute the initial server setup by requesting input. Follow the steps until it finishes: - -``` -Note: npm/yarn run config will re-run this utility. - -You will now be asked for the following: -- Admin Name, the initial admin username -- Admin Pass, the initial admin password -- Port, the port for the websocket -- Salt, the salt for username trip - -prompt: adminName: admin -prompt: adminPass: **** - -prompt: websocketPort: (6060) -prompt: tripSalt: ************ - -Config generated! You may now start the server normally. -``` - -___Note:___ if you change the `websocketPort` option during the config setup then these changes will need to be reflected on line 64 of the [client.js](https://github.com/hack-chat/main/blob/master/client/client.js#L64). - -After the config script runs, the process will exit & the server will need to be relaunched. For a production environment we recommend using [PM2](https://github.com/Unitech/pm2) to start the server: - -``` -cd main/server/ -pm2 start main.js --name HackChat -``` - -[Launch the client](./client/README.md) `main/client/index.html`, you may now begin development or deploy to production environment. - -## Deployment - -After the initial installation and configuration, push everything except the node_modules folder to the live server and re-run: - -``` -npm install -``` - -You can now run start the server software with a process manager like [PM2](https://github.com/Unitech/pm2). The client code will need to be copied into your http server directory. If you plan on using SSL to serve the client; you will need to use a reverse proxy, as TLS is not natively supported by the hack.chat server software (this may change in future releases). - -## Authors - -* **Marzavec** - *Initial work* - [https://github.com/marzavec](https://github.com/marzavec) -* **MinusGix** - *Base updates* - [https://github.com/MinusGix](https://github.com/MinusGix) - -See also the list of [contributors](https://github.com/hack-chat/main/graphs/contributors) who participated in this project. - -## License - -This project is licensed under the WTFPL License - see the [http://www.wtfpl.net/txt/copying/](http://www.wtfpl.net/txt/copying/) file for details - -## Acknowledgments - -* Andrew Belt, [https://github.com/AndrewBelt/hack.chat](https://github.com/AndrewBelt/hack.chat), for original base work -* wwandrew [https://github.com/wwandrew/](https://github.com/wwandrew/), for finding server flaws (including attack vectors) and submitting ~~___incredibly detailed___~~ bug reports +This project is licensed under the [WTFPL License](LICENSE). diff --git a/client/client.js b/client/client.js index 1c40f01..53f2856 100644 --- a/client/client.js +++ b/client/client.js @@ -36,13 +36,13 @@ function $(query) { function localStorageGet(key) { try { return window.localStorage[key] - } catch(e) { } + } catch (e) { } } function localStorageSet(key, val) { try { window.localStorage[key] = val - } catch(e) { } + } catch (e) { } } var ws; @@ -214,10 +214,12 @@ function pushMessage(args) { // Temporary hotfix for \rule spamming, see https://github.com/Khan/KaTeX/issues/109 textEl.innerHTML = textEl.innerHTML.replace(/\\rule|\\\\\s*\[.*?\]/g, ''); try { - renderMathInElement(textEl, {delimiters: [ - { left: "$$", right: "$$", display: true }, - { left: "$", right: "$", display: false }, - ]}) + renderMathInElement(textEl, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + ] + }) } catch (e) { console.warn(e); } @@ -601,4 +603,4 @@ if (myChannel == '') { $('#sidebar').classList.add('hidden'); } else { join(myChannel); -} +}
\ No newline at end of file diff --git a/server/package-lock.json b/package-lock.json index 8a8228d..df5ebe8 100644 --- a/server/package-lock.json +++ b/package-lock.json @@ -393,4 +393,4 @@ } } } -} +}
\ No newline at end of file diff --git a/server/package.json b/package.json index 8417c13..8417c13 100644 --- a/server/package.json +++ b/package.json diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 292daab..0000000 --- a/server/README.md +++ /dev/null @@ -1 +0,0 @@ -(TODO) diff --git a/server/src/commands/admin/addmod.js b/server/src/commands/admin/addmod.js index e9dde2c..4c13b22 100644 --- a/server/src/commands/admin/addmod.js +++ b/server/src/commands/admin/addmod.js @@ -2,8 +2,6 @@ Description: Adds the target trip to the mod list then elevates the uType */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType != 'admin') { // ignore if not admin diff --git a/server/src/commands/admin/listusers.js b/server/src/commands/admin/listusers.js index 226b000..a539a3c 100644 --- a/server/src/commands/admin/listusers.js +++ b/server/src/commands/admin/listusers.js @@ -2,8 +2,6 @@ Description: Outputs all current channels and their user nicks */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType != 'admin') { // ignore if not admin diff --git a/server/src/commands/admin/reload.js b/server/src/commands/admin/reload.js index 387ae97..e2cfbe6 100644 --- a/server/src/commands/admin/reload.js +++ b/server/src/commands/admin/reload.js @@ -2,8 +2,6 @@ Description: Clears and resets the command modules, outputting any errors */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType != 'admin') { // ignore if not admin @@ -34,3 +32,4 @@ exports.info = { name: '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 0c4e0c9..ed3a312 100644 --- a/server/src/commands/admin/saveconfig.js +++ b/server/src/commands/admin/saveconfig.js @@ -2,8 +2,6 @@ Description: Writes any changes to the config to the disk */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType != 'admin') { // ignore if not admin diff --git a/server/src/commands/admin/shout.js b/server/src/commands/admin/shout.js index 736134a..1358dd9 100644 --- a/server/src/commands/admin/shout.js +++ b/server/src/commands/admin/shout.js @@ -2,8 +2,6 @@ Description: Emmits a server-wide message as `info` */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType != 'admin') { // ignore if not admin @@ -22,4 +20,4 @@ exports.info = { name: 'shout', usage: 'shout {text}', description: 'Displays passed text to every client connected' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/changenick.js b/server/src/commands/core/changenick.js index 6b2932c..4041bb0 100644 --- a/server/src/commands/core/changenick.js +++ b/server/src/commands/core/changenick.js @@ -2,8 +2,6 @@ Description: Generates a semi-unique channel name then broadcasts it to each client */ -'use strict'; - const verifyNickname = (nick) => { return /^[a-zA-Z0-9_]{1,24}$/.test(nick); }; diff --git a/server/src/commands/core/chat.js b/server/src/commands/core/chat.js index ee45425..bce6adb 100644 --- a/server/src/commands/core/chat.js +++ b/server/src/commands/core/chat.js @@ -2,8 +2,6 @@ Description: Rebroadcasts any `text` to all clients in a `channel` */ -'use strict'; - const parseText = (text) => { if (typeof text !== 'string') { return false; @@ -61,4 +59,4 @@ exports.info = { name: 'chat', usage: 'chat {text}', description: 'Broadcasts passed `text` field to the calling users channel' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/disconnect.js b/server/src/commands/core/disconnect.js index 1a9c635..9b54214 100644 --- a/server/src/commands/core/disconnect.js +++ b/server/src/commands/core/disconnect.js @@ -4,8 +4,6 @@ by a client to have the connection severed. */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.channel) { server.broadcast({ @@ -20,4 +18,4 @@ exports.run = async (core, server, socket, data) => { 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/help.js b/server/src/commands/core/help.js index 71cc745..7f63d3d 100644 --- a/server/src/commands/core/help.js +++ b/server/src/commands/core/help.js @@ -2,8 +2,6 @@ Description: Outputs the current command module list or command categories */ -'use strict'; - const stripIndents = require('common-tags').stripIndents; exports.run = async (core, server, socket, data) => { @@ -48,4 +46,4 @@ exports.info = { name: 'help', usage: 'help ([ type:categories] | [category:<category name> | command:<command name> ])', description: 'Outputs information about the servers current protocol' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/invite.js b/server/src/commands/core/invite.js index bd85812..bcf9097 100644 --- a/server/src/commands/core/invite.js +++ b/server/src/commands/core/invite.js @@ -2,8 +2,6 @@ Description: Generates a semi-unique channel name then broadcasts it to each client */ -'use strict'; - const verifyNickname = (nick) => { return /^[a-zA-Z0-9_]{1,24}$/.test(nick); }; @@ -64,4 +62,4 @@ exports.info = { name: 'invite', usage: 'invite {nick}', description: 'Generates a unique (more or less) room name and passes it to two clients' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/join.js b/server/src/commands/core/join.js index 82b48d2..f2b2c9d 100644 --- a/server/src/commands/core/join.js +++ b/server/src/commands/core/join.js @@ -2,8 +2,6 @@ Description: Initial entry point, applies `channel` and `nick` to the calling socket */ -'use strict'; - const crypto = require('crypto'); const hash = (password) => { @@ -134,4 +132,4 @@ exports.info = { name: 'join', usage: 'join {channel} {nick}', description: 'Place calling socket into target channel with target nick & broadcast event to channel' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/morestats.js b/server/src/commands/core/morestats.js index d8bc23d..5510cb1 100644 --- a/server/src/commands/core/morestats.js +++ b/server/src/commands/core/morestats.js @@ -2,8 +2,6 @@ Description: Outputs more info than the legacy stats command */ -'use strict'; - const stripIndents = require('common-tags').stripIndents; const formatTime = (time) => { @@ -52,4 +50,4 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'morestats', description: 'Sends back current server stats to the calling client' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/move.js b/server/src/commands/core/move.js index 862025c..c5efafd 100644 --- a/server/src/commands/core/move.js +++ b/server/src/commands/core/move.js @@ -2,8 +2,6 @@ Description: Generates a semi-unique channel name then broadcasts it to each client */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (server._police.frisk(socket.remoteAddress, 6)) { server.reply({ @@ -82,4 +80,4 @@ exports.info = { name: 'move', usage: 'move {channel}', description: 'This will change the current channel to the new one provided' -}; +};
\ No newline at end of file diff --git a/server/src/commands/core/stats.js b/server/src/commands/core/stats.js index a1abddb..b9dc002 100644 --- a/server/src/commands/core/stats.js +++ b/server/src/commands/core/stats.js @@ -2,8 +2,6 @@ Description: Legacy stats output, kept for compatibility, outputs user and channel count */ -'use strict'; - exports.run = async (core, server, socket, data) => { let ips = {}; let channels = {}; @@ -31,4 +29,4 @@ exports.run = async (core, server, socket, data) => { exports.info = { name: 'stats', description: 'Sends back legacy server stats to the calling client' -}; +};
\ No newline at end of file diff --git a/server/src/commands/mod/ban.js b/server/src/commands/mod/ban.js index 409217a..e19efc2 100644 --- a/server/src/commands/mod/ban.js +++ b/server/src/commands/mod/ban.js @@ -2,8 +2,6 @@ Description: Adds the target socket's ip to the ratelimiter */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType == 'user') { // ignore if not mod or admin diff --git a/server/src/commands/mod/kick.js b/server/src/commands/mod/kick.js index f32eaf5..157592d 100644 --- a/server/src/commands/mod/kick.js +++ b/server/src/commands/mod/kick.js @@ -2,8 +2,6 @@ Description: Forces a change on the target socket's channel, then broadcasts event */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType === 'user') { // ignore if not mod or admin @@ -77,4 +75,4 @@ exports.info = { name: 'kick', usage: 'kick {nick}', description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings' -}; +};
\ No newline at end of file diff --git a/server/src/commands/mod/unban.js b/server/src/commands/mod/unban.js index 2406644..e82f0b9 100644 --- a/server/src/commands/mod/unban.js +++ b/server/src/commands/mod/unban.js @@ -2,8 +2,6 @@ Description: Removes a target ip from the ratelimiter */ -'use strict'; - exports.run = async (core, server, socket, data) => { if (socket.uType == 'user') { // ignore if not mod or admin @@ -54,4 +52,4 @@ exports.info = { name: 'unban', usage: 'unban {[ip || hash]}', description: 'Removes target ip from the ratelimiter' -}; +};
\ No newline at end of file diff --git a/server/src/core/rateLimiter.js b/server/src/core/rateLimiter.js index 0d94ef2..0c2a384 100644 --- a/server/src/core/rateLimiter.js +++ b/server/src/core/rateLimiter.js @@ -8,15 +8,13 @@ * */ -'use strict'; - class Police { /** * Create a ratelimiter instance. */ constructor () { this._records = {}; - this._halflife = 30000; // ms + this._halflife = 30 * 1000; // milliseconds this._threshold = 25; this._hashes = []; } diff --git a/server/src/core/server.js b/server/src/core/server.js index 9bea738..855aeba 100644 --- a/server/src/core/server.js +++ b/server/src/core/server.js @@ -7,8 +7,6 @@ * */ -'use strict'; - const wsServer = require('ws').Server; const socketReady = require('ws').OPEN; const crypto = require('crypto'); diff --git a/server/src/managers/commands.js b/server/src/managers/commands.js index 569206d..c38fb4d 100644 --- a/server/src/managers/commands.js +++ b/server/src/managers/commands.js @@ -7,8 +7,6 @@ * */ -'use strict'; - const path = require('path'); const chalk = require('chalk'); const didYouMean = require('didyoumean2'); diff --git a/server/src/managers/config.js b/server/src/managers/config.js index 1848cac..2865d00 100644 --- a/server/src/managers/config.js +++ b/server/src/managers/config.js @@ -8,8 +8,6 @@ * */ -'use strict'; - const stripIndents = require('common-tags').stripIndents; const dateFormat = require('dateformat'); const chalk = require('chalk'); diff --git a/server/src/managers/imports-manager.js b/server/src/managers/imports-manager.js index f4096b7..d8b2144 100644 --- a/server/src/managers/imports-manager.js +++ b/server/src/managers/imports-manager.js @@ -7,8 +7,6 @@ * */ -'use strict'; - const read = require('readdir-recursive'); const path = require('path'); diff --git a/server/src/managers/stats.js b/server/src/managers/stats.js index c3b6f9f..20f1ae3 100644 --- a/server/src/managers/stats.js +++ b/server/src/managers/stats.js @@ -7,8 +7,6 @@ * */ -'use strict'; - class Stats { /** * Create a stats instance. diff --git a/server/src/commands/core/showcase.js b/templateCommand.js index 5eca4a2..1f2cdd9 100644 --- a/server/src/commands/core/showcase.js +++ b/templateCommand.js @@ -2,8 +2,6 @@ Description: This is a template module that should not be on prod */ -'use strict'; - // you can require() modules here // this function will only be only in the scope of the module @@ -45,9 +43,7 @@ exports.init = (core) => { exports.requiredData = ['echo']; // optional parameters are marked, all others are required -exports.info = { +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, can be ommited if no parameters are required - description: 'Simple command module template & info' // used for help output + aliases: ['templateModule'] }; |