diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..677ccea --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +module.exports = { + "env": { + "commonjs": true, + "es2021": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 12 + }, + "rules": { + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +}; diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..6aa054c --- /dev/null +++ b/DOCS.md @@ -0,0 +1,200 @@ +# Waffle API docs (currently not implemented) + +# Gateway + +Waffle uses a WebSocket gateway to transmit various updates from the server to the client and vice-versa (new message, user disconnecting, connect to voice, etc.) + +# Gateway Payload format + +All payloads sent through the gateway are *string payloads*. They all start with a number represeting the "instruction", followed by a "@" character (to delimit the instruction number from the rest of the payload), after which comes the data for the specific instruction. + +Example packet: +```json +23@this is my payload data +``` +Note for the example above: The "this is my payload data" text should not be user controlled. If user controlled input is required, it must be sanitized and/or passed as a JSON field. + +Packets can also have JSON as a payload: +```json +32@{"name":"Alice"} +``` + +## Instructions + +The terms "channel" and "category" are used interchangeably. + +## 0:HELLO +*Part of handshake, Server to client* + +Sent by the server to the client as soon as possible after they connect to the gateway. + +This payload can contain a JSON object with various information about the server. It should be relied on only when absolutely necessary. + +Example: +```json +0@{} +``` + +## 1:YOO +*Part of handshake, Client to server* + +Sent by the client as soon as possible after receiving the HELLO instruction. + + +JSON data format: +| Field | Description | +| - | - | +| token | The authentication token | +| client | The name of the application the client is connecting from (e.g. browser_react_app) | + +Example: +```json +1@{"token":"my totally real token","client":"amazing client"} +``` + +If the token is invalid, or the connection is otherwise rejected, the client should be disconnected as soon as possible, and no YOO\_ACK should be sent. + +## 2:YOO\_ACK +*Part of handshake, Server to client* + +Sent by the server as soon as possible after processing the YOO payload from the client. + +This payload contains an empty JSON object, that may have data added to it in later versions of this software. + +Example: +```json +2@{} +``` + +## 3:ACTION\_CREATE\_MESSAGE +*Auth required, Client to server* + +Sent by the client when sending a message. + +JSON data format: +| Field | Description | +| - | - | +| content | The text content of the message | +| channel | An object that contains "_id", the id of the channel to send the message to | + +Example: +```json +3@{"content":"i hate you","channel_id":"totally real channel id"} +``` + +## 4:EVENT\_NEW\_MESSAGE +*Auth required, Server to client* + +Sent by the server when a new message is created in a channel the client is subscribed to. + +JSON data format: +[Message object](#message-object) + +## 5:ACTION\_UPDATE\_STATUS +*Auth required, Client to server* + +Sent by the client when updating status. Usually, this packet is sent as soon as possible after getting YOO\_ACK to indicate the client is online. + +JSON data format: +| Field | Description | +| - | - | +| status | Can be 0(STATUS\_OFFLINE) or 1(STATUS\_ONLINE) | +| status_text | Custom status text, max 50 characters, min 1 non-whitespace character, trimmed | + +Example: +```json +5@{"status":1,"status_text":"hello"} +``` + +## 6:EVENT\_CHANNEL\_MEMBERS + +*Auth required, Server to client* + +Sent by the server after YOO\_ACK, and otherwise when the entire status needs to be updated. If only one user's status changes, use EVENT\_CHANNEL\_MEMBER\_UPDATE. + +JSON data format (this ones tough): +Contains a JSON object, where each key is a channel id and each property is an array of [user presence objects](#user-presence-object). + +Example: +```json +6@{"totallyrealid":[{_id:"totallyrealuserid",username:"totallyrealusername",status:1,status_text:"hello"}]} +``` + +## 7:EVENT\_CHANNEL\_MEMBER\_UPDATE + +*Auth required, Server to client* + +Sent by the server when the status of a member changes. Of course, the client has to be in a channel with that member in order to receive this update. + +JSON data format: +It's just a [user presence object](#user-presence-object). + +Example: +```json +7@{_id:"totallyrealuserid",username:"totallyrealusername",status:1,status_text:"hello"} +``` + +## 21:ACTION\_VOICE\_BEGIN\_SESSION + +*Auth required, Client to server* + +Sent by the client whenever it wants to initiate a voice session in a specific channel. + +JSON data format: + +| Field | Description | +| - | - | +| channel | An object that contains "_id", the id of the channel to connect to | + +(unfinished) + +## 22:ACTION\_VOICE\_BEGIN\_SESSION + +*Auth required, Client to server* + +Sent by the server when the voice session request has been accepted. If it is rejected, the client MUST be disconnected from the gateway as soon as possible. + +JSON data format: + +| Field | Description | +| - | - | +| reportTo | The IP and port of the voice server to connect to | + +(unfinished) + +# Objects and data types + +## Message object + +| Field | Description | +| - | - | +| content | The text content of the message (max 2000 characters, min 1 character, trimmed) | +| channel | A [message channel object](#message-channel-object) | +| author | A [message author object](#message-author-object) | + +## Message channel object + +Used mostly for messages. + +| Field | Description | +| - | - | +| _id | The id of the channel | +| name | The name of the channel | + +## Message author object + +Used mostly for messages. + +| Field | Description | +| - | - | +| _id | The id of the user | +| name | The name of the user | + +## User presence object + +| Field | Description | +| - | - | +| _id | The id of the user | +| name | The name of the user | +| status | The status of the user (see ACTION_UPDATE_STATUS) | +| status_text | The text status of the user (see ACTION_UPDATE_STATUS) | \ No newline at end of file diff --git a/api/v1/authfunctions.js b/api/v1/authfunctions.js index 1f9d088..5033a72 100755 --- a/api/v1/authfunctions.js +++ b/api/v1/authfunctions.js @@ -1,64 +1,64 @@ -const User = require('../../models/User'); -const secret = require('../../secret'); -const config = require('../../config'); - -const jwt = require('jsonwebtoken'); - -const redirect = (res, status=401, url=undefined) => { - if (!url) { - res.status(status).json({ - error: true, - message: 'ERROR_ACCESS_DENIED' - }); - return; - } - res.redirect(url); -} - -function authenticateEndpoint(callback, url=undefined, minPermissionLevel=config.roleMap.RESTRICTED) { - return (req, res) => { - const token = req.cookies.token; - if (!token) { - redirect(res, 403, url); - return; - } - - jwt.verify(token, secret.jwtPrivateKey, {}, async (err, data) => { - if (err) { - redirect(res, 401, url); - return; - } - - if (!data) { - redirect(res, 401, url); - return - } - - if (!data.username) { - redirect(res, 401, url); - return; - } - - const user = await User.findByUsername(data.username); - - if (!user) { - redirect(res, 401, url); - return; - } - - let permissionLevel = config.roleMap[user.role]; - if (!permissionLevel) { - permissionLevel = 0; - } - - if (permissionLevel < minPermissionLevel) { - redirect(res, 401, url); - return; - } - - callback(req, res, user); - }); - }; -} - +const User = require("../../models/User"); +const secret = require("../../secret"); +const config = require("../../config"); + +const jwt = require("jsonwebtoken"); + +const redirect = (res, status=401, url=undefined) => { + if (!url) { + res.status(status).json({ + error: true, + message: "ERROR_ACCESS_DENIED" + }); + return; + } + res.redirect(url); +}; + +function authenticateEndpoint(callback, url=undefined, minPermissionLevel=config.roleMap.RESTRICTED) { + return (req, res) => { + const token = req.cookies.token; + if (!token) { + redirect(res, 403, url); + return; + } + + jwt.verify(token, secret.jwtPrivateKey, {}, async (err, data) => { + if (err) { + redirect(res, 401, url); + return; + } + + if (!data) { + redirect(res, 401, url); + return; + } + + if (!data.username) { + redirect(res, 401, url); + return; + } + + const user = await User.findByUsername(data.username); + + if (!user) { + redirect(res, 401, url); + return; + } + + let permissionLevel = config.roleMap[user.role]; + if (!permissionLevel) { + permissionLevel = 0; + } + + if (permissionLevel < minPermissionLevel) { + redirect(res, 401, url); + return; + } + + callback(req, res, user); + }); + }; +} + module.exports = { authenticateEndpoint }; \ No newline at end of file diff --git a/api/v1/content.js b/api/v1/content.js index d62ce82..b495dd2 100755 --- a/api/v1/content.js +++ b/api/v1/content.js @@ -1,141 +1,141 @@ -const User = require('../../models/User'); -const Category = require('../../models/Category'); -const Post = require('../../models/Post'); -const config = require('../../config'); - -const { authenticateEndpoint } = require('./authfunctions'); - -const mongoose = require('mongoose'); -const { body, param, validationResult } = require('express-validator'); -const express = require('express'); - -const app = express.Router(); -mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); - -const rateLimit = require("express-rate-limit"); - -const createLimiter = rateLimit({ - windowMs: 2 * 60 * 1000, - max: 10, -}); - -app.post('/category/create', [ - createLimiter, - body('title').not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape() -], authenticateEndpoint(async (req, res, user) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); - return; - } - - const title = req.body.title; - const category = await Category.create({ - title: title, - creator: user._id, - posts: [] - }); - - res.status(200).json({ - error: false, - message: 'SUCCESS_CATEGORY_CREATED', - category: category.getPublicObject() - }); -}, undefined, config.roleMap.USER)); - -app.post('/post/create', [ - createLimiter, - body('category').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }), - body('title').not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape(), - body('body').not().isEmpty().trim().isLength({ min: 3, max: 1000 }).escape(), -], authenticateEndpoint(async (req, res, user) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); - return; - } - - const category = req.body.category; - const title = req.body.title; - const content = req.body.body; - - const post = new Post(); - post.title = title; - post.body = content; - post.creator = user._id; - post.category = category; - - const r = await Category.updateOne({ - _id: category - }, { - $push: { posts: post } - }); - - if (r.n < 1) { - res.status(404).json({ - error: true, - message: 'ERROR_CATEGORY_NOT_FOUND' - }); - return; - } - - res.status(200).json({ - error: false, - message: 'SUCCESS_POST_CREATED', - post: { - _id: post._id - } - }); -}, undefined, config.roleMap.USER)); - -app.get('/category/:category/info', [ - param('category').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) -], authenticateEndpoint(async (req, res, user) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); - return; - } - - const categoryId = req.params.category; - const category = await Category.findById(categoryId).populate('posts.creator', User.getPulicFields()); - - // TODO: Implement subscribing to a channel and stuff - const users = await User.find().sort({ _id: -1 }).limit(50).select(User.getPulicFields()) - - if (!category) { - res.status(404).json({ - error: true, - message: 'ERROR_CATEGORY_NOT_FOUND' - }); - return; - } - - res.status(200).json({ - error: false, - message: 'SUCCESS_CATEGORY_DATA_FETCHED', - category: category.getPublicObject(), - userInfo: { - userListLimit: 50, - users: users - } - }); -}, undefined, config.roleMap.USER)); - -app.get('/category/list', authenticateEndpoint(async (req, res, user) => { - let count = parseInt(req.query.count); - if (!Number.isInteger(count)) { - count = 10; - } - - // TODO: This is probably not efficient - const categories = await Category.find().sort({ _id: -1 }).limit(count).select('-posts -__v').populate('creator', User.getPulicFields()); - - res.status(200).json({ - error: false, - message: 'SUCCESS_CATEGORY_LIST_FETCHED', - categories - }); -}, undefined, config.roleMap.USER)); - +const User = require("../../models/User"); +const Category = require("../../models/Category"); +const Post = require("../../models/Post"); +const config = require("../../config"); + +const { authenticateEndpoint } = require("./authfunctions"); + +const mongoose = require("mongoose"); +const { body, param, validationResult } = require("express-validator"); +const express = require("express"); + +const app = express.Router(); +mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); + +const rateLimit = require("express-rate-limit"); + +const createLimiter = rateLimit({ + windowMs: 2 * 60 * 1000, + max: 10, +}); + +app.post("/category/create", [ + createLimiter, + body("title").not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape() +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); + return; + } + + const title = req.body.title; + const category = await Category.create({ + title: title, + creator: user._id, + posts: [] + }); + + res.status(200).json({ + error: false, + message: "SUCCESS_CATEGORY_CREATED", + category: category.getPublicObject() + }); +}, undefined, config.roleMap.USER)); + +app.post("/post/create", [ + createLimiter, + body("category").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }), + body("title").not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape(), + body("body").not().isEmpty().trim().isLength({ min: 3, max: 1000 }).escape(), +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); + return; + } + + const category = req.body.category; + const title = req.body.title; + const content = req.body.body; + + const post = new Post(); + post.title = title; + post.body = content; + post.creator = user._id; + post.category = category; + + const r = await Category.updateOne({ + _id: category + }, { + $push: { posts: post } + }); + + if (r.n < 1) { + res.status(404).json({ + error: true, + message: "ERROR_CATEGORY_NOT_FOUND" + }); + return; + } + + res.status(200).json({ + error: false, + message: "SUCCESS_POST_CREATED", + post: { + _id: post._id + } + }); +}, undefined, config.roleMap.USER)); + +app.get("/category/:category/info", [ + param("category").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) +], authenticateEndpoint(async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); + return; + } + + const categoryId = req.params.category; + const category = await Category.findById(categoryId).populate("posts.creator", User.getPulicFields()); + + // TODO: Implement subscribing to a channel and stuff + const users = await User.find().sort({ _id: -1 }).limit(50).select(User.getPulicFields()); + + if (!category) { + res.status(404).json({ + error: true, + message: "ERROR_CATEGORY_NOT_FOUND" + }); + return; + } + + res.status(200).json({ + error: false, + message: "SUCCESS_CATEGORY_DATA_FETCHED", + category: category.getPublicObject(), + userInfo: { + userListLimit: 50, + users: users + } + }); +}, undefined, config.roleMap.USER)); + +app.get("/category/list", authenticateEndpoint(async (req, res) => { + let count = parseInt(req.query.count); + if (!Number.isInteger(count)) { + count = 10; + } + + // TODO: This is probably not efficient + const categories = await Category.find().sort({ _id: -1 }).limit(count).select("-posts -__v").populate("creator", User.getPulicFields()); + + res.status(200).json({ + error: false, + message: "SUCCESS_CATEGORY_LIST_FETCHED", + categories + }); +}, undefined, config.roleMap.USER)); + module.exports = app; \ No newline at end of file diff --git a/api/v1/gateway/index.js b/api/v1/gateway/index.js index b5f75dc..8beeace 100644 --- a/api/v1/gateway/index.js +++ b/api/v1/gateway/index.js @@ -1,17 +1,17 @@ -const User = require('../../../models/User'); -const secret = require('../../../secret'); -const config = require('../../../config'); -const Category = require('../../../models/Category'); -const RateLimiter = require('./ratelimiter'); +const User = require("../../../models/User"); +const secret = require("../../../secret"); +const config = require("../../../config"); +const Category = require("../../../models/Category"); +const RateLimiter = require("./ratelimiter"); -const jwt = require('jsonwebtoken'); -const siolib = require('socket.io'); -const uuid = require('uuid'); +const jwt = require("jsonwebtoken"); +const siolib = require("socket.io"); +const uuid = require("uuid"); class GatewayServer { constructor(httpServer) { this._io = siolib(httpServer); - this._gateway = this._io.of('/gateway'); + this._gateway = this._io.of("/gateway"); this.rateLimiter = new RateLimiter({ points: 5, time: 1000, @@ -19,15 +19,15 @@ class GatewayServer { }); this.eventSetup(); - this._commandPrefix = '/'; + this._commandPrefix = "/"; } } GatewayServer.prototype._sendSystemMessage = function(socket, message, category) { const messageObject = { author: { - username: '__SYSTEM', - _id: '5fc69864f15a7c5e504c9a1f' + username: "__SYSTEM", + _id: "5fc69864f15a7c5e504c9a1f" }, category: { title: category.title, @@ -37,78 +37,78 @@ GatewayServer.prototype._sendSystemMessage = function(socket, message, category) _id: uuid.v4() }; - socket.emit('message', messageObject); + socket.emit("message", messageObject); }; GatewayServer.prototype.notifyClientsOfUpdate = function(reason) { - this._gateway.emit('refreshClient', { reason: reason || 'REFRESH' }); + this._gateway.emit("refreshClient", { reason: reason || "REFRESH" }); }; GatewayServer.prototype._processCommand = async function(socket, message) { const content = message.content; const fullCommandString = content.slice(this._commandPrefix.length, content.length); - const fullCommand = fullCommandString.split(' '); - const command = fullCommand[0] || 'INVALID_COMMAND'; + const fullCommand = fullCommandString.split(" "); + const command = fullCommand[0] || "INVALID_COMMAND"; const args = fullCommand.length - 1; switch (command) { - case 'INVALID_COMMAND': { - this._sendSystemMessage(socket, 'Invalid command.', message.category); - break; - } - case 'admin/fr': { - if (args === 1) { - if (socket.user.permissionLevel >= config.roleMap.ADMIN) { - this._gateway.emit('refreshClient', { reason: fullCommand[1] || 'REFRESH' }); - } else { - this._sendSystemMessage(socket, 'how about no', message.category); - } + case "INVALID_COMMAND": { + this._sendSystemMessage(socket, "Invalid command.", message.category); + break; + } + case "admin/fr": { + if (args === 1) { + if (socket.user.permissionLevel >= config.roleMap.ADMIN) { + this._gateway.emit("refreshClient", { reason: fullCommand[1] || "REFRESH" }); } else { - this._sendSystemMessage(socket, 'Invalid number of arguments.', message.category); + this._sendSystemMessage(socket, "how about no", message.category); } - break; + } else { + this._sendSystemMessage(socket, "Invalid number of arguments.", message.category); } - case 'admin/fru': { - if (args === 1) { - if (socket.user.permissionLevel >= config.roleMap.ADMIN) { - const user = await this._findSocketInRoom(message.category._id, fullCommand[1]); - if (!user) { - this._sendSystemMessage(socket, 'User not found.', message.category); - break; - } + break; + } + case "admin/fru": { + if (args === 1) { + if (socket.user.permissionLevel >= config.roleMap.ADMIN) { + const user = await this._findSocketInRoom(message.category._id, fullCommand[1]); + if (!user) { + this._sendSystemMessage(socket, "User not found.", message.category); + break; + } - this._gateway.in(user.user.sid).emit('refreshClient', { reason: 'REFRESH' }); - } else { - this._sendSystemMessage(socket, 'how about no', message.category); - } + this._gateway.in(user.user.sid).emit("refreshClient", { reason: "REFRESH" }); } else { - this._sendSystemMessage(socket, 'Invalid number of arguments.', message.category); + this._sendSystemMessage(socket, "how about no", message.category); } - break; - } - default: { - this._sendSystemMessage(socket, 'That command does not exist.', message.category); - break; + } else { + this._sendSystemMessage(socket, "Invalid number of arguments.", message.category); } + break; + } + default: { + this._sendSystemMessage(socket, "That command does not exist.", message.category); + break; + } } }; GatewayServer.prototype.authDisconnect = function(socket, callback) { - console.log('[E] [gateway] [handshake] User disconnected due to failed authentication'); + console.log("[E] [gateway] [handshake] User disconnected due to failed authentication"); socket.isConnected = false; socket.disconnect(); socket.disconnect(true); - callback(new Error('ERR_GATEWAY_AUTH_FAIL')); + callback(new Error("ERR_GATEWAY_AUTH_FAIL")); }; GatewayServer.prototype.eventSetup = function() { this._gateway.use((socket, callback) => { - console.log('[*] [gateway] [handshake] User authentication attempt'); + console.log("[*] [gateway] [handshake] User authentication attempt"); socket.isConnected = false; setTimeout(() => { if (socket.isConnected) return; - console.log('[E] [gateway] [handshake] User still not connected after timeout, removing...'); + console.log("[E] [gateway] [handshake] User still not connected after timeout, removing..."); socket.disconnect(); socket.disconnect(true); }, config.gatewayStillNotConnectedTimeoutMS); @@ -117,7 +117,7 @@ GatewayServer.prototype.eventSetup = function() { const token = socket.handshake.query.token; if (!token) return this.authDisconnect(socket, callback); - if (!(typeof token === 'string')) return this.authDisconnect(socket, callback); + if (!(typeof token === "string")) return this.authDisconnect(socket, callback); const allSockets = this._gateway.sockets; for (let [_, e] of allSockets) { @@ -155,10 +155,10 @@ GatewayServer.prototype.eventSetup = function() { }); }); - this._gateway.on('connection', (socket) => { + this._gateway.on("connection", (socket) => { console.log(`[*] [gateway] [handshake] User ${socket.user.username} connected, sending hello and waiting for yoo...`); - socket.emit('hello', { + socket.emit("hello", { gatewayStillNotConnectedTimeoutMS: config.gatewayStillNotConnectedTimeoutMS, resolvedUser: { username: socket.user.username, @@ -166,14 +166,14 @@ GatewayServer.prototype.eventSetup = function() { } }); - socket.once('yoo', () => { + socket.once("yoo", () => { console.log(`[*] [gateway] [handshake] Got yoo from ${socket.user.username}, connection is finally completed!`); socket.isConnected = true; - socket.on('message', async ({ category, content, nickAuthor, destUser }) => { - if (!category || !content || !socket.joinedCategories || !socket.isConnected || !socket.user || !(typeof content === 'string') || !(typeof category._id === 'string')) return; + socket.on("message", async ({ category, content, nickAuthor, destUser }) => { + if (!category || !content || !socket.joinedCategories || !socket.isConnected || !socket.user || !(typeof content === "string") || !(typeof category._id === "string")) return; content = content.trim(); - if (!content || content === '' || content === ' ' || content.length >= 2000) return; + if (!content || content === "" || content === " " || content.length >= 2000) return; if (!this.rateLimiter.consoom(socket.user.token)) { // TODO: maybe user ip instead of token? console.log(`[E] [gateway] Rate limiting ${socket.user.username}`); return; @@ -181,7 +181,7 @@ GatewayServer.prototype.eventSetup = function() { // TODO: When/if category permissions are added, check if the user has permissions for that category const categoryTitle = socket.joinedCategories[category._id]; - if (!categoryTitle || !(typeof categoryTitle === 'string')) return; + if (!categoryTitle || !(typeof categoryTitle === "string")) return; let messageObject = { author: { @@ -197,14 +197,14 @@ GatewayServer.prototype.eventSetup = function() { _id: uuid.v4() }; - if (nickAuthor && nickAuthor.username && (typeof nickAuthor.username) === 'string' && nickAuthor.username.length <= 32 && nickAuthor.username.length >= 3) { + if (nickAuthor && nickAuthor.username && (typeof nickAuthor.username) === "string" && nickAuthor.username.length <= 32 && nickAuthor.username.length >= 3) { if (socket.user.permissionLevel === config.roleMap.BOT) { messageObject = { nickAuthor: { username: nickAuthor.username }, ...messageObject - } + }; } } @@ -213,22 +213,22 @@ GatewayServer.prototype.eventSetup = function() { return; } - if (destUser && destUser._id && (typeof destUser._id) === 'string') { + if (destUser && destUser._id && (typeof destUser._id) === "string") { const user = await this._findSocketInRoom(messageObject.category._id, destUser._id); if (!user) return; - this._gateway.in(user.user.sid).emit('message', messageObject); + this._gateway.in(user.user.sid).emit("message", messageObject); return; } - this._gateway.in(category._id).emit('message', messageObject); + this._gateway.in(category._id).emit("message", messageObject); }); - socket.on('subscribe', async (categories) => { + socket.on("subscribe", async (categories) => { if ( !socket.isConnected || !socket.user || !categories || !Array.isArray(categories) || categories === []) return; try { for (const v of categories) { - if (!v && !(typeof v === 'string')) continue; + if (!v && !(typeof v === "string")) continue; // TODO: When/if category permissions are added, check if the user has permissions for that category const category = await Category.findById(v); if (category && category.title && category._id) { @@ -240,7 +240,7 @@ GatewayServer.prototype.eventSetup = function() { console.log(`[*] [gateway] User ${socket.user.username} subscribed to room ${v} (${category.title}), sending updated user list to all members of that room...`); const upd = await this._generateClientListUpdateObject(v, category.title); - this._gateway.in(v).emit('clientListUpdate', upd); + this._gateway.in(v).emit("clientListUpdate", upd); } } } catch (e) { @@ -248,18 +248,18 @@ GatewayServer.prototype.eventSetup = function() { } }); - socket.on('disconnecting', async () => { + socket.on("disconnecting", async () => { console.log(`[*] [gateway] User ${socket.user.username} is disconnecting, broadcasting updated user list to all of the rooms they have been in...`); const rooms = socket.rooms; rooms.forEach(async (room) => { // Socket io automatically adds a user to a room with their own id if (room === socket.id) return; - const categoryTitle = socket.joinedCategories[room] || 'UNKNOWN'; + const categoryTitle = socket.joinedCategories[room] || "UNKNOWN"; await socket.leave(room); const upd = await this._generateClientListUpdateObject(room, categoryTitle); - socket.in(room).emit('clientListUpdate', upd); + socket.in(room).emit("clientListUpdate", upd); }); }); }); @@ -309,7 +309,7 @@ GatewayServer.prototype._findSocketInRoom = async function(room, userid) { return updatedClientList[0] || undefined; }; -GatewayServer.prototype._generateClientListUpdateObject = async function(room, categoryTitle='UNKNOWN') { +GatewayServer.prototype._generateClientListUpdateObject = async function(room, categoryTitle="UNKNOWN") { const clientList = await this._getSocketsInRoom(room); return { category: { diff --git a/api/v1/index.js b/api/v1/index.js index b483573..d81acae 100755 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -1,16 +1,16 @@ -const usersAPI = require('./users'); -const contentAPI = require('./content'); - -const express = require('express'); - -const app = express.Router(); - -app.use('/users', usersAPI); -app.use('/content', contentAPI); - -app.get('/', (req, res) => { - // TODO: Add more checks for this, or maybe remove - res.json({ apiStatus: 'OK', apis: [ 'users', 'content' ] }); -}); - +const usersAPI = require("./users"); +const contentAPI = require("./content"); + +const express = require("express"); + +const app = express.Router(); + +app.use("/users", usersAPI); +app.use("/content", contentAPI); + +app.get("/", (req, res) => { + // TODO: Add more checks for this, or maybe remove + res.json({ apiStatus: "OK", apis: [ "users", "content" ] }); +}); + module.exports = app; \ No newline at end of file diff --git a/api/v1/users.js b/api/v1/users.js index 3a9fa5c..c6f8804 100755 --- a/api/v1/users.js +++ b/api/v1/users.js @@ -1,216 +1,216 @@ -const User = require('../../models/User'); -const config = require('../../config'); -const secret = require('../../secret'); - -const { authenticateEndpoint } = require('./authfunctions'); - -// TODO: Might want to use something else (https://blog.benpri.me/blog/2019/01/13/why-you-shouldnt-be-using-bcrypt-and-scrypt/) -const bcrypt = require('bcrypt'); -const mongoose = require('mongoose'); -const { body, query, param, validationResult } = require('express-validator'); -const express = require('express'); -const jwt = require('jsonwebtoken'); - -const app = express.Router(); -mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); - -const rateLimit = require("express-rate-limit"); - -const createAccountLimiter = rateLimit({ - windowMs: 60 * 60 * 1000, // 1 hour window - max: 10, // start blocking after 5 requests - message: "You are being rate limited" -}); - -app.get('/account/create/info', async (req, res) => { - let requiresCode = false; - if (config.restrictions) { - const restrictions = config.restrictions.signup; - if (restrictions && restrictions.specialCode) { - requiresCode = true; - } - } - - res.json({ - error: false, - message: 'SUCCESS_ACCOUNT_CREATE_INFO_FETCH', - requiresSpecialCode: requiresCode - }); -}); - -app.post('/account/create', [ - createAccountLimiter, - body('username').not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), - body('email').not().isEmpty().isEmail().normalizeEmail(), - body('password').not().isEmpty().isLength({ min: 8, max: 128 }), - body('specialCode').optional().isLength({ min: 12, max: 12 }).isAlphanumeric() -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); - return; - } - - if (config.restrictions) { - const restrictions = config.restrictions.signup; - if (restrictions && restrictions.specialCode) { - const passedSpecialCode = req.body.specialCode; - const specialCode = restrictions.specialCode; - - if (passedSpecialCode && specialCode) { - if (specialCode !== passedSpecialCode) { - res.status(401).json({ error: true, message: 'ERROR_REQUEST_SPECIAL_CODE_MISSING', errors: [{ msg: 'No specialCode passed', param: 'specialCode', location: 'body' }] }); - return false; - } - } else { - res.status(401).json({ error: true, message: 'ERROR_REQUEST_SPECIAL_CODE_MISSING', errors: [{ msg: 'No specialCode passed', param: 'specialCode', location: 'body' }] }); - return false; - } - } - } - - const username = req.body.username; - - const existingUser = await User.findByUsername(username); - if (existingUser) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_USERNAME_EXISTS', errors: [{ value: username, msg: 'Username exists', param: 'username', location: 'body' }] }); - return; - } - - const unhashedPassword = req.body.password; - const email = req.body.email; - const startingRole = 'USER'; - - const hashedPassword = await bcrypt.hash(unhashedPassword, config.bcryptRounds); - - const user = await User.create({ - username, - email, - password: hashedPassword, - role: startingRole, - color: User.generateColorFromUsername(username) - }); - - const userObject = await user.getPublicObject(); - - console.log('[*] [logger] [users] [create] User created', userObject); - - res.status(200).json({ - error: false, - message: 'SUCCESS_USER_CREATED', - user: userObject - }); - } catch (e) { - console.error('Internal server error', e); - res.status(500).json({ error: true, message: 'INTERNAL_SERVER_ERROR' }); - return; - } -}); - -app.post('/token/create', [ - createAccountLimiter, - body('username').not().isEmpty().trim().isAlphanumeric(), - body('password').not().isEmpty() -], async (req, res) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); - return; - } - - const username = req.body.username; - - const existingUser = await User.findByUsername(username); - if (!existingUser) { - res.status(403).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); - return; - } - - const password = req.body.password; - - let passwordCheck; - try { - passwordCheck = await bcrypt.compare(password, existingUser.password); - } catch(e) { - passwordCheck = false; - } - if (!passwordCheck) { - res.status(403).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); - return; - } - - jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: '3h' }, async (err, token) => { - if (err) { - res.status(500).json({ - error: true, - message: 'INTERNAL_SERVER_ERROR' - }); - return; - } - - // TODO: Ugly fix for setting httponly cookies - if (req.body.alsoSetCookie) { - res.cookie('token', token, { - maxAge: 3 * 60 * 60 * 1000, httpOnly: true, domain: config.address, - }); - } - - const userObject = await existingUser.getPublicObject(); - - console.log('[*] [logger] [users] [token create] Token created', userObject); - - res.status(200).json({ - error: false, - message: 'SUCCESS_TOKEN_CREATED', - user: userObject, - token - }); - }); -}); - -app.get('/current/info', authenticateEndpoint(async (req, res, user) => { - const userObject = await user.getPublicObject(); - - res.status(200).json({ - error: false, - message: 'SUCCESS_USER_DATA_FETCHED', - user: { - token: req.cookies.token, // TODO: Passing the token like this is *terribly* insecure - ...userObject - }, - }); -}, undefined, 0)); - -app.get('/user/:userid/info', [ - param('userid').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) -], authenticateEndpoint(async (req, res, user) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); - return; - } - - const userid = req.params.userid; - if (!userid) { - res.sendStatus(400).json({ - error: true, - message: 'ERROR_REQUEST_INVALID_DATA' - }); - return; - } - const otherUser = await User.findById(userid); - - res.status(200).json({ - error: false, - message: 'SUCCESS_USER_DATA_FETCHED', - user: await otherUser.getPublicObject(), - }); -}, undefined, config.roleMap.USER)); - -app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => { - res.clearCookie('token'); - res.sendStatus(200); -})); - +const User = require("../../models/User"); +const config = require("../../config"); +const secret = require("../../secret"); + +const { authenticateEndpoint } = require("./authfunctions"); + +// TODO: Might want to use something else (https://blog.benpri.me/blog/2019/01/13/why-you-shouldnt-be-using-bcrypt-and-scrypt/) +const bcrypt = require("bcrypt"); +const mongoose = require("mongoose"); +const { body, param, validationResult } = require("express-validator"); +const express = require("express"); +const jwt = require("jsonwebtoken"); + +const app = express.Router(); +mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); + +const rateLimit = require("express-rate-limit"); + +const createAccountLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, // 1 hour window + max: 10, // start blocking after 5 requests + message: "You are being rate limited" +}); + +app.get("/account/create/info", async (req, res) => { + let requiresCode = false; + if (config.restrictions) { + const restrictions = config.restrictions.signup; + if (restrictions && restrictions.specialCode) { + requiresCode = true; + } + } + + res.json({ + error: false, + message: "SUCCESS_ACCOUNT_CREATE_INFO_FETCH", + requiresSpecialCode: requiresCode + }); +}); + +app.post("/account/create", [ + createAccountLimiter, + body("username").not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), + body("email").not().isEmpty().isEmail().normalizeEmail(), + body("password").not().isEmpty().isLength({ min: 8, max: 128 }), + body("specialCode").optional().isLength({ min: 12, max: 12 }).isAlphanumeric() +], async (req, res) => { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); + return; + } + + if (config.restrictions) { + const restrictions = config.restrictions.signup; + if (restrictions && restrictions.specialCode) { + const passedSpecialCode = req.body.specialCode; + const specialCode = restrictions.specialCode; + + if (passedSpecialCode && specialCode) { + if (specialCode !== passedSpecialCode) { + res.status(401).json({ error: true, message: "ERROR_REQUEST_SPECIAL_CODE_MISSING", errors: [{ msg: "No specialCode passed", param: "specialCode", location: "body" }] }); + return false; + } + } else { + res.status(401).json({ error: true, message: "ERROR_REQUEST_SPECIAL_CODE_MISSING", errors: [{ msg: "No specialCode passed", param: "specialCode", location: "body" }] }); + return false; + } + } + } + + const username = req.body.username; + + const existingUser = await User.findByUsername(username); + if (existingUser) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_USERNAME_EXISTS", errors: [{ value: username, msg: "Username exists", param: "username", location: "body" }] }); + return; + } + + const unhashedPassword = req.body.password; + const email = req.body.email; + const startingRole = "USER"; + + const hashedPassword = await bcrypt.hash(unhashedPassword, config.bcryptRounds); + + const user = await User.create({ + username, + email, + password: hashedPassword, + role: startingRole, + color: User.generateColorFromUsername(username) + }); + + const userObject = await user.getPublicObject(); + + console.log("[*] [logger] [users] [create] User created", userObject); + + res.status(200).json({ + error: false, + message: "SUCCESS_USER_CREATED", + user: userObject + }); + } catch (e) { + console.error("Internal server error", e); + res.status(500).json({ error: true, message: "INTERNAL_SERVER_ERROR" }); + return; + } +}); + +app.post("/token/create", [ + createAccountLimiter, + body("username").not().isEmpty().trim().isAlphanumeric(), + body("password").not().isEmpty() +], async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); + return; + } + + const username = req.body.username; + + const existingUser = await User.findByUsername(username); + if (!existingUser) { + res.status(403).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); + return; + } + + const password = req.body.password; + + let passwordCheck; + try { + passwordCheck = await bcrypt.compare(password, existingUser.password); + } catch(e) { + passwordCheck = false; + } + if (!passwordCheck) { + res.status(403).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); + return; + } + + jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: "3h" }, async (err, token) => { + if (err) { + res.status(500).json({ + error: true, + message: "INTERNAL_SERVER_ERROR" + }); + return; + } + + // TODO: Ugly fix for setting httponly cookies + if (req.body.alsoSetCookie) { + res.cookie("token", token, { + maxAge: 3 * 60 * 60 * 1000, httpOnly: true, domain: config.address, + }); + } + + const userObject = await existingUser.getPublicObject(); + + console.log("[*] [logger] [users] [token create] Token created", userObject); + + res.status(200).json({ + error: false, + message: "SUCCESS_TOKEN_CREATED", + user: userObject, + token + }); + }); +}); + +app.get("/current/info", authenticateEndpoint(async (req, res, user) => { + const userObject = await user.getPublicObject(); + + res.status(200).json({ + error: false, + message: "SUCCESS_USER_DATA_FETCHED", + user: { + token: req.cookies.token, // TODO: Passing the token like this is *terribly* insecure + ...userObject + }, + }); +}, undefined, 0)); + +app.get("/user/:userid/info", [ + param("userid").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) +], authenticateEndpoint(async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); + return; + } + + const userid = req.params.userid; + if (!userid) { + res.sendStatus(400).json({ + error: true, + message: "ERROR_REQUEST_INVALID_DATA" + }); + return; + } + const otherUser = await User.findById(userid); + + res.status(200).json({ + error: false, + message: "SUCCESS_USER_DATA_FETCHED", + user: await otherUser.getPublicObject(), + }); +}, undefined, config.roleMap.USER)); + +app.post("/browser/token/clear", authenticateEndpoint((req, res) => { + res.clearCookie("token"); + res.sendStatus(200); +})); + module.exports = app; \ No newline at end of file diff --git a/api/v2/gateway/index.js b/api/v2/gateway/index.js new file mode 100644 index 0000000..794ed3f --- /dev/null +++ b/api/v2/gateway/index.js @@ -0,0 +1,15 @@ +const websockets = require("ws"); + +class GatewayServer { + constructor({ server }) { + this.wss = new websockets.Server({ server }); + + this.wss.on("connection", (ws) => { + ws.on("message", (data) => { + + }); + }); + } +} + +module.exports = GatewayServer; \ No newline at end of file diff --git a/api/v2/gateway/messageparser.js b/api/v2/gateway/messageparser.js new file mode 100644 index 0000000..415e16a --- /dev/null +++ b/api/v2/gateway/messageparser.js @@ -0,0 +1,18 @@ +const config = require("../../../config"); + +const opcodes = { + 0: "HELLO" +}; + +const parseMessage = (message) => { + if (typeof message !== "string") throw new Error("GatewayMessageParser: Message is not a string"); + if (message.length < 1) throw new Error("GatewayMessageParser: Message has less than 1 character"); + if (message.length > config.gatewayMaxStringPayloadLength) throw new Error(`GatewayMessageParser: Message has more than ${config.gatewayMaxStringPayloadLength} characters`); + const op = parseInt(message[0]); + if (!op || isNaN(op)) throw new Error("GatewayMessageParser: Message has invalid opcode"); + const opcodeName = opcodes[op]; + if (!opcodeName) throw new Error("GatewayMessageParser: Message has unknown opcode"); + +}; + +module.exports = { parseMessage }; \ No newline at end of file diff --git a/config.js b/config.js index 0259e48..2e3e0d3 100755 --- a/config.js +++ b/config.js @@ -1,22 +1,25 @@ -module.exports = { - ports: { - mainServerPort: 3005, - }, - address: 'localhost', - //restrictions: { - // signup: { - // specialCode: '' - // } - //}, - corsAllowList: [ 'localhost' ], - mongoUrl: 'mongodb://localhost:27017/app', - bcryptRounds: 10, - roleMap: { - 'BANNED': 0, - 'RESTRICTED': 1, - 'USER': 2, - 'BOT': 3, - 'ADMIN': 4 - }, - gatewayStillNotConnectedTimeoutMS: 15*1000 -}; +module.exports = { + ports: { + mainServerPort: 3005, + }, + address: "localhost", + //restrictions: { + // signup: { + // specialCode: '' + // } + //}, + corsAllowList: [ "http://localhost:3000", "http://localhost:3005" ], + mongoUrl: "mongodb://192.168.0.105:27017/app", + + // Internal stuff - only touch if you know what you're doing + bcryptRounds: 10, + roleMap: { + "BANNED": 0, + "RESTRICTED": 1, + "USER": 2, + "BOT": 3, + "ADMIN": 4 + }, + gatewayStillNotConnectedTimeoutMS: 15*1000, + gatewayMaxStringPayloadLength: +}; diff --git a/index.js b/index.js index 9d57495..5300cd9 100755 --- a/index.js +++ b/index.js @@ -1,58 +1,58 @@ -const config = require('./config'); -const apiRoute = require('./api/v1'); - -const express = require('express'); -const cookieParser = require('cookie-parser'); -const cors = require('cors') -const http = require('http'); - -const { authenticateEndpoint } = require('./api/v1/authfunctions'); -const GatewayServer = require('./api/v1/gateway/index'); - -const app = express(); -const httpServer = http.createServer(app); - -const gateway = new GatewayServer(httpServer); - -app.use(express.urlencoded({ extended: false })); -app.use(express.json()); -app.use(cookieParser()); -app.use(cors({ - origin: function (origin, callback) { - if (config.corsAllowList.indexOf(origin) !== -1 || !origin) { - callback(null, true); - } else { - callback(new Error('Not allowed by CORS')); - } - }, - credentials: true, - optionsSuccessStatus: 200 -})); -app.use('/api/v1', apiRoute); -app.use(express.static('app')); - -app.get('/', authenticateEndpoint((req, res, user) => { - res.redirect('/app.html'); -}, '/auth.html')); - -app.get('/admin', (req, res) => { - res.send('Keanu chungus wholesome 100 reddit moment 😀i beat up a kid that said minecraft bad 😂and my doggo bit him so i gave him snaccos😉 and we watched pewdiepie together while in elon musk’s cyber truck 😳talking about how superior reddit memers are : “haha emojis bad” 😲i said and keanu reeves came outta nowhere and said “this is wholesome 100, updoot this wholesome boy” 😗so i got alot of updoots and edit: thanks for the gold kind stranger😣. but the kind stranger revealed himself to be baby yoda eating chiccy nuggies😨 and drinking choccy milk😎 so we went to the cinema to see our (communism funny) favorite movies avengers endgame😆 but then thor played fortnite and fortnite bad😡, so then i said “reality is often dissappointing” and then baby yoda replied r/unexpectedthanos and i replied by r/expectedthanos😖 for balance and then danny devito came to pick us up from the cinema😩 and all the insta normies and gay mods stood watching😵 ,as we,superior redditors went home with danny devito to suck on his magnum dong😫 but i said no homo and started sucking,not like those gay mods😮,then the next morning we woke up to MrBeast telling us to plant 69420 million trees😌, me, baby yoda and danny said nice, and then on our way to plant 69420 million trees😊 (nice) we saw a kid doing a tiktok so keanu reeves appeared and said “we have a kid to burn” and i replied “you’re breathtaking”😄 so i said “i need a weapon” and baby yoda gave me an RPG so i blew the kid (DESTRUCTION 100)😎 and posted it on r/memes and r/dankmemes and r/pewdiepiesubmissions and got 1000000000 updoots😘,i’m sure pewds will give me a big pp, then we shat on emoji users😂😂 and started dreaming about girls that will never like me😢 and posted a lie on r/teenagers about how i got a GF after my doggo died by the hands of fortnite players😳 so i exploited his death for updoots😜, but i watched the sunset with the wholesome gang😁 (keanu,danny,Mrbeast, pewds, spongebob,stefan karl , bob ross, steve irwin, baby yoda and other artists that reddit exploits them) [Everyone liked that] WHOLESOME 100 REDDIT 100🤡'); -}); - -app.use((err, req, res, next) => { - console.error('[E] Internal server error', err); - res.status(500).json({ error: true, status: 500, message: 'ERR_INTERNAL_SERVER_ERROR' }); -}); - -const onServerClosing = (evt) => { - gateway.notifyClientsOfUpdate('exit'); - process.exit(); -}; - -['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'].forEach((eventType) => { - process.on(eventType, onServerClosing.bind(null, eventType)); -}) - -httpServer.listen(config.ports.mainServerPort, () => { - console.log(`[*] [server] Main server is listening on port ${config.ports.mainServerPort}`); +const config = require("./config"); +const apiRoute = require("./api/v1"); + +const express = require("express"); +const cookieParser = require("cookie-parser"); +const cors = require("cors"); +const http = require("http"); + +const { authenticateEndpoint } = require("./api/v1/authfunctions"); +const GatewayServer = require("./api/v1/gateway/index"); + +const app = express(); +const httpServer = http.createServer(app); + +const gateway = new GatewayServer(httpServer); + +app.use(express.urlencoded({ extended: false })); +app.use(express.json()); +app.use(cookieParser()); +app.use(cors({ + origin: function (origin, callback) { + if (config.corsAllowList.indexOf(origin) !== -1 || !origin) { + callback(null, true); + } else { + callback(new Error("Not allowed by CORS")); + } + }, + credentials: true, + optionsSuccessStatus: 200 +})); +app.use("/api/v1", apiRoute); +app.use(express.static("app")); + +app.get("/", authenticateEndpoint((req, res) => { + res.redirect("/app.html"); +}, "/auth.html")); + +app.get("/admin", (req, res) => { + res.send("Keanu chungus wholesome 100 reddit moment 😀i beat up a kid that said minecraft bad 😂and my doggo bit him so i gave him snaccos😉 and we watched pewdiepie together while in elon musk’s cyber truck 😳talking about how superior reddit memers are : “haha emojis bad” 😲i said and keanu reeves came outta nowhere and said “this is wholesome 100, updoot this wholesome boy” 😗so i got alot of updoots and edit: thanks for the gold kind stranger😣. but the kind stranger revealed himself to be baby yoda eating chiccy nuggies😨 and drinking choccy milk😎 so we went to the cinema to see our (communism funny) favorite movies avengers endgame😆 but then thor played fortnite and fortnite bad😡, so then i said “reality is often dissappointing” and then baby yoda replied r/unexpectedthanos and i replied by r/expectedthanos😖 for balance and then danny devito came to pick us up from the cinema😩 and all the insta normies and gay mods stood watching😵 ,as we,superior redditors went home with danny devito to suck on his magnum dong😫 but i said no homo and started sucking,not like those gay mods😮,then the next morning we woke up to MrBeast telling us to plant 69420 million trees😌, me, baby yoda and danny said nice, and then on our way to plant 69420 million trees😊 (nice) we saw a kid doing a tiktok so keanu reeves appeared and said “we have a kid to burn” and i replied “you’re breathtaking”😄 so i said “i need a weapon” and baby yoda gave me an RPG so i blew the kid (DESTRUCTION 100)😎 and posted it on r/memes and r/dankmemes and r/pewdiepiesubmissions and got 1000000000 updoots😘,i’m sure pewds will give me a big pp, then we shat on emoji users😂😂 and started dreaming about girls that will never like me😢 and posted a lie on r/teenagers about how i got a GF after my doggo died by the hands of fortnite players😳 so i exploited his death for updoots😜, but i watched the sunset with the wholesome gang😁 (keanu,danny,Mrbeast, pewds, spongebob,stefan karl , bob ross, steve irwin, baby yoda and other artists that reddit exploits them) [Everyone liked that] WHOLESOME 100 REDDIT 100🤡"); +}); + +app.use((err, req, res, next) => { + console.error("[E] Internal server error", err); + res.status(500).json({ error: true, status: 500, message: "ERR_INTERNAL_SERVER_ERROR" }); +}); + +const onServerClosing = () => { + gateway.notifyClientsOfUpdate("exit"); + process.exit(); +}; + +["exit", "SIGINT", "SIGUSR1", "SIGUSR2", "uncaughtException", "SIGTERM"].forEach((eventType) => { + process.on(eventType, onServerClosing.bind(null, eventType)); +}); + +httpServer.listen(config.ports.mainServerPort, () => { + console.log(`[*] [server] Main server is listening on port ${config.ports.mainServerPort}`); }); \ No newline at end of file diff --git a/libbrainlet/test.js b/libbrainlet/test.js deleted file mode 100644 index 8ad19e5..0000000 --- a/libbrainlet/test.js +++ /dev/null @@ -1,142 +0,0 @@ -const Client = require('./index'); - -const fetch = require('node-fetch'); - -const LISTEN_ON = '5fcbf598b39160080e797ad6'; -const PREFIX = '::'; -const ADMIN_ID = '5fc828ea4e96e00725c17fd7'; - -const joined = []; -const selected = []; - -// https://stackoverflow.com/questions/17619741/randomly-generating-unique-number-in-pairs-multiple-times - -const randomPairs = (players) => { - const pairs = []; - while(players.length) { - pairs.push([ - pluckRandomElement(players), - pluckRandomElement(players) - ]); - } - return pairs; -}; - -const pluckRandomElement = (array) => { - const i = randomInt(array.length); - return array.splice(i, 1)[0]; -}; - -const randomInt = (limit) => { - return Math.floor(Math.random() * limit); -}; - -const getRandomUser = (self, count=0) => { - if (count > 3) return joined[0]; - count++; - - let final; - - let chosen = joined[Math.floor(Math.random() * joined.length)]; - final = chosen; - if (chosen._id === self._id) return final = getRandomUser(self, count); - if ((selected.indexOf(chosen)) !== 1) return final = getRandomUser(self, count); - if (!final) return final = getRandomUser(self, count); - - - - return final || null; -}; - -const main = async () => { - const client = new Client('https://b.hippoz.xyz', { - throwErrors: true - }); - - const res = await fetch(`${client.url}/api/v1/users/token/create`, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - username: 'D', - password: 'D' - }) - }); - - const json = await res.json(); - - if (!res.ok || json.error) throw new Error('Failed to generate token from API endpoint'); - - await client.setToken(json.token); - await client.gatewayConnect(); - - client.gateway.on('connect', () => { - const category = client.gateway.subscribeToCategoryChat(LISTEN_ON); - - client.gateway.on('message', (e) => { - if (e.author._id === client.user._id) return; - if (!e.content.startsWith(PREFIX)) return; - if (e.category._id !== category) return; - - const cmdString = e.content.substring(PREFIX.length); - const cmdFull = cmdString.split(' '); - const cmd = cmdFull[0] || 'NO_CMD'; - - console.log(cmdFull); - - switch (cmd) { - case 'join': { - const existing = joined.findIndex((o) => { - return o._id === e.author._id; - }); - - if (existing !== -1) { - client.gateway.sendMessage(category, 'Already joined', { - nickAuthor: { username: 'Error' }, - destUser: { _id: e.author._id } - }); - break; - } - - joined.push(e.author); - console.log(`[*] User joined`, e.author); - client.gateway.sendMessage(category, `${e.author.username} joined!`, { - nickAuthor: { username: 'New join!' } - }); - break; - } - case 'roll': { - if (e.author._id !== ADMIN_ID) { - client.gateway.sendMessage(category, 'Access denied', { - nickAuthor: { username: 'Error' }, - destUser: { _id: e.author._id } - }); - break; - } - client.gateway.sendMessage(category, 'Rolling...', { - nickAuthor: { username: 'Woo' } - }); - - const pairs = randomPairs(joined); - - for (const pair of pairs) { - const p1 = pair[0]; - const p2 = pair[1]; - if (!p1 || !p2) continue; - for (const player of pair) { - client.gateway.sendMessage(category, `${p1.username} with ${p2.username}`, { - nickAuthor: { username: 'Your result' }, - destUser: { _id: player._id } - }); - } - } - break; - } - } - }); - }); -}; - -main(); \ No newline at end of file diff --git a/models/Category.js b/models/Category.js index 2641b79..129eb42 100755 --- a/models/Category.js +++ b/models/Category.js @@ -1,22 +1,22 @@ -const mongoose = require('mongoose'); -const Post = require('./Post'); -const User = require('./User'); - -const categorySchema = new mongoose.Schema({ - title: String, - creator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, - posts: [Post.schema] -}); - -categorySchema.method('getPublicObject', function() { - return { - title: this.title, - creator: this.populate('creator', User.getPulicFields()).creator, - posts: this.posts, - _id: this._id - } -}); - -const Category = mongoose.model('Category', categorySchema); - +const mongoose = require("mongoose"); +const Post = require("./Post"); +const User = require("./User"); + +const categorySchema = new mongoose.Schema({ + title: String, + creator: {type: mongoose.Schema.Types.ObjectId, ref: "User"}, + posts: [Post.schema] +}); + +categorySchema.method("getPublicObject", function() { + return { + title: this.title, + creator: this.populate("creator", User.getPulicFields()).creator, + posts: this.posts, + _id: this._id + }; +}); + +const Category = mongoose.model("Category", categorySchema); + module.exports = Category; \ No newline at end of file diff --git a/models/Post.js b/models/Post.js index 5bf1350..59530db 100755 --- a/models/Post.js +++ b/models/Post.js @@ -1,10 +1,10 @@ -const mongoose = require('mongoose'); - -const Post = mongoose.model('Post', { - title: String, - body: String, - creator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, - categoryId: {type: mongoose.Schema.Types.ObjectId, ref: 'Category'} -}); - +const mongoose = require("mongoose"); + +const Post = mongoose.model("Post", { + title: String, + body: String, + creator: {type: mongoose.Schema.Types.ObjectId, ref: "User"}, + categoryId: {type: mongoose.Schema.Types.ObjectId, ref: "Category"} +}); + module.exports = Post; \ No newline at end of file diff --git a/models/User.js b/models/User.js index 3bfe5e8..50c26c9 100755 --- a/models/User.js +++ b/models/User.js @@ -1,74 +1,74 @@ -const config = require('../config'); - -const mongoose = require('mongoose'); - -const userSchema = new mongoose.Schema({ - username: String, - password: String, - email: String, - role: String, - color: String -}); - -userSchema.method('getPublicObject', function() { - return { - username: this.username, - permissionLevel: config.roleMap[this.role], - role: this.role, - color: this.color, - _id: this._id - } -}); - -userSchema.method('getFullObject', function() { - return { - username: this.username, - password: this.password, - email: this.email, - permissionLevel: config.roleMap[this.role], - role: this.role, - color: this.color, - _id: this._id - } -}); - -const User = mongoose.model('User', userSchema); - -// NOTE(hippoz): These are all actually material design colors, taken from https://material-ui.com/customization/color/#playground -const colors = [ - '#f44336', - '#e91e63', - '#9c27b0', - '#673ab7', - '#3f51b5', - '#2196f3', - '#03a9f4', - '#00bcd4', - '#009688', - '#4caf50', - '#8bc34a', - '#cddc39', - '#ffeb3b', - '#ffc107', - '#ff9800', - '#ff5722' -]; - -User.generateColorFromUsername = function(username) { - let sum = 0; - for (let i in username) { - sum += username.charCodeAt(i); - } - const colorIndex = sum % colors.length; - return colors[colorIndex]; -}; - -User.findByUsername = async function(username) { - return await User.findOne({ username }).exec(); -}; - -User.getPulicFields = function() { - return 'username role _id color'; -}; - +const config = require("../config"); + +const mongoose = require("mongoose"); + +const userSchema = new mongoose.Schema({ + username: String, + password: String, + email: String, + role: String, + color: String +}); + +userSchema.method("getPublicObject", function() { + return { + username: this.username, + permissionLevel: config.roleMap[this.role], + role: this.role, + color: this.color, + _id: this._id + }; +}); + +userSchema.method("getFullObject", function() { + return { + username: this.username, + password: this.password, + email: this.email, + permissionLevel: config.roleMap[this.role], + role: this.role, + color: this.color, + _id: this._id + }; +}); + +const User = mongoose.model("User", userSchema); + +// NOTE(hippoz): These are all actually material design colors, taken from https://material-ui.com/customization/color/#playground +const colors = [ + "#f44336", + "#e91e63", + "#9c27b0", + "#673ab7", + "#3f51b5", + "#2196f3", + "#03a9f4", + "#00bcd4", + "#009688", + "#4caf50", + "#8bc34a", + "#cddc39", + "#ffeb3b", + "#ffc107", + "#ff9800", + "#ff5722" +]; + +User.generateColorFromUsername = function(username) { + let sum = 0; + for (let i in username) { + sum += username.charCodeAt(i); + } + const colorIndex = sum % colors.length; + return colors[colorIndex]; +}; + +User.findByUsername = async function(username) { + return await User.findOne({ username }).exec(); +}; + +User.getPulicFields = function() { + return "username role _id color"; +}; + module.exports = User; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fad6d82..f794322 100755 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,85 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", + "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", + "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -18,11 +97,50 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -37,11 +155,26 @@ "readable-stream": "^2.0.6" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -116,6 +249,63 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -126,6 +316,21 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -187,6 +392,17 @@ "vary": "^1" } }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -200,6 +416,12 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -225,6 +447,15 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -238,6 +469,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -282,11 +519,222 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", + "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -343,6 +791,33 @@ "validator": "^13.1.1" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -357,6 +832,33 @@ "unpipe": "~1.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -380,6 +882,12 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -408,6 +916,30 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -433,6 +965,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "ignore-walk": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", @@ -441,6 +979,30 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -465,6 +1027,12 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -473,11 +1041,54 @@ "number-is-nan": "^1.0.0" } }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -526,6 +1137,16 @@ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", @@ -566,6 +1187,23 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -723,6 +1361,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "needle": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", @@ -844,6 +1488,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -863,6 +1521,15 @@ "os-tmpdir": "^1.0.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -873,16 +1540,34 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -892,6 +1577,12 @@ "ipaddr.js": "1.9.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -943,6 +1634,18 @@ "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require_optional": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", @@ -1042,6 +1745,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "sift": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", @@ -1052,6 +1770,49 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -1123,6 +1884,12 @@ "memory-pager": "^1.0.2" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -1159,6 +1926,79 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "dev": true, + "requires": { + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ajv": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", + "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -1173,11 +2013,32 @@ "yallist": "^3.0.3" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1192,6 +2053,15 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1207,6 +2077,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, "validator": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz", @@ -1217,6 +2093,15 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -1225,15 +2110,21 @@ "string-width": "^1.0.2 || 2" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", + "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" }, "yallist": { "version": "3.1.1", diff --git a/package.json b/package.json index 8ba4638..2524f1a 100755 --- a/package.json +++ b/package.json @@ -19,6 +19,10 @@ "jsonwebtoken": "^8.5.1", "mongoose": "^5.10.0", "socket.io": "^3.0.1", - "uuid": "^8.3.1" + "uuid": "^8.3.1", + "ws": "^7.4.3" + }, + "devDependencies": { + "eslint": "^7.21.0" } } diff --git a/secret.js b/secret.js index 726b570..c6a4e1c 100755 --- a/secret.js +++ b/secret.js @@ -1,10 +1,10 @@ -module.exports = { - jwtPrivateKey: 'KEY' -}; - -// Set default values -// You shouldn't need to touch this for configuring this -if (module.exports.jwtPrivateKey === 'KEY') { - console.error('[*] [config] jwtPrivateKey was not specified in secret.js. A randomly generated private key will be used instead'); - module.exports.jwtPrivateKey = require('crypto').randomBytes(129).toString('base64'); -} +module.exports = { + jwtPrivateKey: "KEY" +}; + +// Set default values +// You shouldn't need to touch this for configuring this +if (module.exports.jwtPrivateKey === "KEY") { + console.error("[*] [config] jwtPrivateKey was not specified in secret.js. A randomly generated private key will be used instead"); + module.exports.jwtPrivateKey = require("crypto").randomBytes(129).toString("base64"); +}