From 92e7b88009b96aed6bbe59cbf879fc0958959f00 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 8 Jul 2016 21:57:29 +0200 Subject: [PATCH 01/15] First working version with nconf --- .eslintrc.json | 3 +- .gitignore | 1 + mqp.js | 38 +++++++-------- package.json | 129 +++++++++++++++++++++++++------------------------ start.js | 99 +++++++++++++++++++------------------ 5 files changed, 139 insertions(+), 131 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1d77459..abb10b1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,8 @@ "no-param-reassign": 0, "no-shadow": 0, "consistent-return": 0, - "func-names": 0 + "func-names": 0, + "indent": 0 }, "env": { "browser": true, diff --git a/.gitignore b/.gitignore index 61fd71f..63d331a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ webconfig.js webserver/public/lib/js/webconfig.js pidfile log.txt +config.hjson # IDE files .idea diff --git a/mqp.js b/mqp.js index 38ca176..714934a 100644 --- a/mqp.js +++ b/mqp.js @@ -1,5 +1,4 @@ const chalk = require('chalk'); -const cproc = require('child_process'); const fs = require('fs'); const daemon = require('daemon'); const path = require('path'); @@ -13,14 +12,13 @@ const notifier = updateNotifier({ updateCheckInterval: 0, }); if (notifier.update) { - console.log('Update available ' + chalk.dim(notifier.update.current) + chalk.reset(' → ') + chalk.green(notifier.update.latest)); -} else { + console.log(`Update available ${chalk.dim(notifier.update.current)}${chalk.reset(' → ')}${chalk.green(notifier.update.latest)}`); } function getRunningPid(callback) { - fs.readFile(__dirname + '/pidfile', { + fs.readFile(`${__dirname}/pidfile`, { encoding: 'utf-8', - }, function (err, pid) { + }, (err, pid) => { if (err) { return callback(err); } @@ -36,17 +34,17 @@ function getRunningPid(callback) { switch (process.argv[2]) { case 'start': - getRunningPid(function (err, pid) { + getRunningPid((err, pid) => { if (!err) { console.log('Musiqpad is already running!'); } else { console.log('\nStarting musiqpad'); - console.log(' "' + chalk.yellow.bold('npm stop') + '" to stop the musiqpad server'); - console.log(' "' + chalk.yellow.bold('npm run log') + '" to view server output'); - console.log(' "' + chalk.yellow.bold('npm restart') + '" to restart musiqpad'); + console.log(` "${chalk.yellow.bold('npm stop')}" to stop the musiqpad server`); + console.log(` "${chalk.yellow.bold('npm run log')}" to view server output`); + console.log(` "${chalk.yellow.bold('npm restart')}" to restart musiqpad`); // Spawn a new musiqpad daemon process, might need some more settings but I'm waiting for the new config storage for that. - daemon.daemon(__dirname + '/start.js', '--daemon', { + daemon.daemon(`${__dirname}/start.js`, '--daemon', { stdout: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), }); } @@ -54,7 +52,7 @@ switch (process.argv[2]) { break; case 'stop': - getRunningPid(function (err, pid) { + getRunningPid((err, pid) => { if (!err) { process.kill(pid, 'SIGTERM'); console.log('Stopping musiqpad!'); @@ -65,11 +63,11 @@ switch (process.argv[2]) { break; case 'restart': - getRunningPid(function (err, pid) { + getRunningPid((err, pid) => { if (!err) { process.kill(pid, 'SIGTERM'); console.log('\nRestarting musiqpad'); - daemon.daemon(__dirname + '/start.js', '--daemon', { + daemon.daemon(`${__dirname}/start.js`, '--daemon', { stdout: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), }); @@ -79,18 +77,16 @@ switch (process.argv[2]) { }); break; - case 'log': - console.log('Type ' + 'Ctrl-C ' + 'to exit'); - - ft = tail.startTailing('./log.txt'); - ft.on('line', function (line) { + case 'log': { + console.log('Type Ctrl-C to exit'); + const ft = tail.startTailing('./log.txt'); + ft.on('line', line => { console.log(line); }); - break; - + } case 'update': - getRunningPid(function (err, pid) { + getRunningPid((err, pid) => { if (!err) { process.kill(pid, 'SIGTERM'); console.log('Stopping musiqpad!'); diff --git a/package.json b/package.json index aac3fc9..f6197d6 100644 --- a/package.json +++ b/package.json @@ -1,63 +1,66 @@ -{ - "name": "mqp-server", - "version": "0.7.1", - "description": "musiqpad self-hosted server", - "main": "server-package.js", - "author": "musiqpad Team ", - "private": false, - "repository": { - "type": "git", - "url": "git+https://github.com/musiqpad/mqp-server.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/musiqpad/mqp-server/issues" - }, - "homepage": "https://github.com/musiqpad/mqp-server#readme", - "scripts": { - "start": "node ./mqp.js start", - "stop": "node ./mqp.js stop", - "restart": "node ./mqp.js restart", - "log": "node ./mqp.js log", - "update": "node ./mqp.js update", - "test": "ava --verbose" - }, - "dependencies": { - "basic-logger": "^0.4.4", - "chalk": "^1.0.0", - "clean-css": "^3.4.9", - "compression": "^1.6.2", - "daemon": "^1.1.0", - "deasync": "^0.1.4", - "download-git-repo": "^0.1.2", - "durationjs": "^1.1.1", - "express": "^4.13.3", - "extend": "^3.0.0", - "file-tail": "^0.3.0", - "forever": "^0.15.1", - "leveldown": "1.4.4", - "levelup": "^1.3.1", - "mongodb": "^2.1.16", - "mysql": "^2.10.2", - "nodemailer": "^2.1.0", - "path": "^0.12.7", - "ps-tree": "^1.0.1", - "request": "^2.67.0", - "update-notifier": "^0.7.0", - "ws": "^1.0.1", - "xoauth2": "^1.1.0", - "yesno": "0.0.1" - }, - "devDependencies": { - "ava": "^0.15.2", - "eslint": "^2.13.1", - "eslint-config-airbnb": "^9.0.1", - "eslint-plugin-import": "^1.9.2", - "eslint-plugin-jsx-a11y": "^1.5.3", - "eslint-plugin-react": "^5.2.2" - }, - "ava": { - "concurrency": 5, - "failFast": true - } -} +{ + "name": "mqp-server", + "version": "0.7.1", + "description": "musiqpad self-hosted server", + "main": "server-package.js", + "author": "musiqpad Team ", + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/musiqpad/mqp-server.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/musiqpad/mqp-server/issues" + }, + "homepage": "https://github.com/musiqpad/mqp-server#readme", + "scripts": { + "start": "node ./mqp.js start", + "stop": "node ./mqp.js stop", + "restart": "node ./mqp.js restart", + "log": "node ./mqp.js log", + "update": "node ./mqp.js update", + "test": "ava --verbose" + }, + "dependencies": { + "basic-logger": "^0.4.4", + "chalk": "^1.0.0", + "clean-css": "^3.4.9", + "compression": "^1.6.2", + "daemon": "^1.1.0", + "deasync": "^0.1.4", + "download-git-repo": "^0.1.2", + "durationjs": "^1.1.1", + "express": "^4.13.3", + "extend": "^3.0.0", + "file-tail": "^0.3.0", + "forever": "^0.15.1", + "fs-extra": "^0.30.0", + "hjson": "^1.8.4", + "leveldown": "1.4.4", + "levelup": "^1.3.1", + "mongodb": "^2.1.16", + "mysql": "^2.10.2", + "nconf": "^0.8.4", + "nodemailer": "^2.1.0", + "path": "^0.12.7", + "ps-tree": "^1.0.1", + "request": "^2.67.0", + "update-notifier": "^0.7.0", + "ws": "^1.0.1", + "xoauth2": "^1.1.0", + "yesno": "0.0.1" + }, + "devDependencies": { + "ava": "^0.15.2", + "eslint": "^2.13.1", + "eslint-config-airbnb": "^9.0.1", + "eslint-plugin-import": "^1.9.2", + "eslint-plugin-jsx-a11y": "^1.5.3", + "eslint-plugin-react": "^5.2.2" + }, + "ava": { + "concurrency": 5, + "failFast": true + } +} diff --git a/start.js b/start.js index df8d1b8..1e80aa6 100644 --- a/start.js +++ b/start.js @@ -1,74 +1,81 @@ -var config = require('./serverconfig'); -var fs = require('fs'); -var SocketServer = require("./socketserver/socketserver"); -var log = new(require('basic-logger'))({ - showTimestamp: true, - prefix: "SocketServer" +// eslint-disable-next-line +'use strict'; +const fs = require('fs-extra'); +const SocketServer = require('./socketserver/socketserver'); +const path = require('path'); +const webserver = require('./webserver/app'); +const nconf = require('nconf'); +const hjson = require('hjson'); +const log = new(require('basic-logger'))({ + showTimestamp: true, + prefix: 'SocketServer', }); -var path = require('path'); +let server; +const hjsonWrapper = { + parse: (text) => hjson.parse(text, { keepWsc: true, }), + stringify: (text) => hjson.stringify(text, { keepWsc: true, quotes: 'always', bracesSameLine: true }), +}; -if(!config.setup){ - log.error("Please, setup your server by editing the 'serverconfig.js' file"); - return; +function fileExistsSync(path) { + let exists = false; + try { + exists = fs.statSync(path); + } catch (err) { + exists = false; + } + return !!exists; } -var server = null; +if (!fileExistsSync('config.hjson')) { + fs.copySync('config.example.hjson', 'config.hjson'); +} +nconf.argv().env().file({ file: 'config.hjson', format: hjsonWrapper }); +nconf.set('hostWebserver', true) +nconf.save(function (err) { + console.log(err); +}); -var webConfig = '// THIS IS AN AUTOMATICALLY GENERATED FILE\n\nvar config=JSON.parse(\'' + JSON.stringify( - { - useSSL: config.useSSL, - serverPort: config.socketServer.port, - selfHosted: true, - serverHost: config.socketServer.host - } - ) + '\')'; -if (config.hostWebserver){ - fs.writeFileSync(path.join(__dirname, '/webserver/public/lib/js', 'webconfig.js'), webConfig); - var webserver = require('./webserver/app'); - server = (config.socketServer.port == config.webServer.port || config.socketServer.port == '') ? webserver.server : null; +const webConfig = `// THIS IS AN AUTOMATICALLY GENERATED FILE\n\nvar config=JSON.parse('${JSON.stringify({ + useSSL: nconf.get('useSSL'), + serverPort: nconf.get('socketServer:port'), + selfHosted: !0, + serverHost: nconf.get('socketServer:host') +})}')`; + +if (nconf.get('hostWebserver')) { + fs.writeFileSync(path.join(__dirname, '/webserver/public/lib/js', 'webconfig.js'), webConfig); + server = (nconf.get('socketServer:port') === nconf.get('webServer:port') || nconf.get('socketServer:port') === '') ? webserver.server : null; } -if (config.apis.musiqpad.sendLobbyStats && (!config.apis.musiqpad.key || config.apis.musiqpad.key == '')) { - throw 'In order to send stats to the lobby you must generate an key here: https://musiqpad.com/lounge'; +if (nconf.get('apis:musiqpad:sendLobbyStats') && (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') === '')) { + log.error('In order to send stats to the lobby you must generate an key here: https://musiqpad.com/lounge'); + process.exit(); } fs.writeFileSync(path.join(__dirname, '', 'webconfig.js'), webConfig); -var socketServer = new SocketServer(server); +const socketServer = new SocketServer(server); -process.on('uncaughtException', function(err) { +process.on('uncaughtException', (err) => { console.log(err); console.log(err.stack); socketServer.gracefulExit(); }); process.on('exit', socketServer.gracefulExit); - -//catches ctrl+c event process.on('SIGINT', socketServer.gracefulExit); -function fileExistsSync() { - var exists = false; - try { - exists = fs.statSync(path); - } catch(err) { - exists = false; - } - - return !!exists; -} - -if(process.argv[2] === "--daemon") { - if (fileExistsSync(__dirname + '/pidfile')) { +if (process.argv[2] === '--daemon') { + if (fileExistsSync(`${__dirname}/pidfile`)) { try { - var pid = fs.readFileSync(__dirname + '/pidfile', { encoding: 'utf-8' }); + const pid = fs.readFileSync(`${__dirname}/pidfile`, { encoding: 'utf-8' }); process.kill(pid, 0); process.exit(); } catch (e) { - fs.unlinkSync(__dirname + '/pidfile'); + fs.unlinkSync(`${__dirname}/pidfile`); } } - fs.writeFile(__dirname + '/pidfile', process.pid); -} \ No newline at end of file + fs.writeFile(`${__dirname}/pidfile`, process.pid); +} From a9f8758d495941f256651857adae45f76f4d18b9 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 10 Jul 2016 00:03:10 +0200 Subject: [PATCH 02/15] Added default config Signed-off-by: Henry Gressmann --- config.example.hjson | 423 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 config.example.hjson diff --git a/config.example.hjson b/config.example.hjson new file mode 100644 index 0000000..d35b084 --- /dev/null +++ b/config.example.hjson @@ -0,0 +1,423 @@ +{ + /* + Set this flag to false to disable web server hosting or true to enable web server hosting. + This is useful if you want to host static files in another web server such as nginx. + + If you are only hosting the socket and want musiqpad to host the frontend set this to false. + */ + hostWebserver: true + socketServer: { + host: "" // Host name or IP that the socket server is located at. Leave blank to bind to process IP address + port: 8082// Leave blank to bind to process PORT + } + webServer: { + address: "" // Leave blank to bind to process IP address. + port: 8080// Leave blank to bind to process PORT. + + redirectHTTP: false// Set to true if you want HTTP redirect to HTTPS. + redirectPort: 80// Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). + } + useSSL: false// If you want your pad to be accesible over HTTPS set SSL to true and add the path of your certificates + certificate: { + key: "path-to-key" + cert: "path-to-cert" + } + room: { + name: "Pad Name" // This is your pad name. It is shown as a user friendly description on the lounge and tab name. + slug: "this-is-your-slug" // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. + greet: "Welcome to musiqpad!" + bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. + maxCon: 0 + ownerEmail: "mail@explodingcamera.com" // This needs to be set then the server restarted to take effect. + guestCanSeeChat: true + bannedCanSeeChat: false + lastmsglimit: 6// How many messages a user can see after joining. + signupcd: 0// How many miliseconds the user cannot do certain things after they sign up. + allowemojis: true + allowrecovery: false + recaptcha: false + queue: { + cycle: true + lock: false + limit: 50 + } + history: { + limit_save: 0 + limit_send: 50 + } + email: { + confirmation: false// Whether to force user to confirm his email address before he is able to do anything + sender: "your@email.tld" + /* + description: Email server setup please refer to https://github.com/nodemailer/nodemailer documention on what the options are supports xOAuth 2.0 + default: {} + */ + options: { + } + } + description: + ''' +

Pad Description

+ Here you can put anything you want in HTML! + ''' + } + apis: { + YT: { + key: "" // Required api key in order for YouTube search to work. + restrictSearchToMusic: false + } + SC: { + key: "" + } + reCaptcha: { + key: "" + secret: "" + } + musiqpad: { + key: "" // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge + sendLobbyStats: false + } + } + + // The amount of time users stay logged in for before having to login again in days. + // 0 = login every time + loginExpire: 7 + db: { + dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB + dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db + mysqlUser: "" // Only used for MySQL. Database username + mysqlPassword: "" // Only used for MySQL. Database password + mysqlHost: "" // Only used for MySQL. Host address + mysqlDatabase: "" // Only used for MySQL. Database being used + mongoUser: "" // Only used for MongoDB. Database username + mongoPassword: "" // Only used for MongoDB. Database password + mongoHost: "" // Only used for MongoDB. Host address + mongoDatabase: "" // Only used for MongoDB. Database being used + } + + /* + "djqueue.join": Ability to join queue + "djqueue.joinlocked": Ability to join locked queue + "djqueue.leave": Ability to leave queue + "djqueue.skip.self": Ability to skip self + "djqueue.skip.other": Ability to skip others + "djqueue.lock": Ability to lock/unlock queue + "djqueue.limit": Ability to change waitlist limit + "djqueue.cycle": Ability to enable/disable queue cycle + "djqueue.move": Ability to move, swap, add and remove people in the queue + "djqueue.playLiveVideos": Ability to play live videos with undefined duration + "djqueue.lock.bypass": Bypass locked queue + "djqueue.limit.bypass": Bypass queue limit + "chat.send": Abilty to send chat messages + "chat.delete": Ability to delete others" chat messages + "chat.specialMention": Ability to use @everyone, @guest and @djs as mention + "chat.broadcast": Ability to send a highlighted broadcast message + "chat.private": Ability to send PMs + "chat.staff": Ability to send and receive special staff chat + "playlist.create": Ability to create playlists + "playlist.delete": Ability to delete playlists + "playlist.rename": Ability to rename playlists + "playlist.import": Ability to import playlists + "playlist.shuffle": Ability to shuffle playlists + "room.grantroles": Ability to change user roles (requires canGrantPerms property) + "room.restrict.ban": Ability to ban and unban users + "room.restrict.mute": Ability to mute and unmute users + "room.restrict.mute_silent": Ability to shadow mute and unmute users + "room.ratelimit.bypass": Will bypass ratelimit + "room.whois": Possibility to request additional information about a user + "room.whois.iphistory": Possibility to request all IP addresses that the user logged from since account creation + + NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed + without breaking things, but property name must stay the same. + */ + + + // Defines the order that roles will appear on the user list + // PROPERTY names. NOT title. (case-sensitive) + roleOrder: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + + // Defines which roles are "staff" members + // PROPERTY names. NOT title. (case-sensitive) + staffRoles: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + ] + + + + /* + + Role Options: + + rolename:{ + title: "", // This is the title that gets displayed on the frontend. + showtitle: true/false, // This is whether or not to display the title on the frontend. + badge: "", // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com + style: {}, // This can be used to set specific styles to the Username of a user with this role. + permissions: [], // A list of permissions a user with this role is allowed to use. + canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. + mention: "" // A custom mention. I.e. "owner" would mention this group when someone typed @owner. + } + + Below are a list of roles we suggest using. + + */ + + // Defines roles and permissions + + owner: { // REQUIRED ROLE + title: "Owner" + showtitle: true + style: { + color: "#F46B40" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + "server.checkForUpdates" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + } + dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS + title: "Dev" + showtitle: true + style: { + color: "#A77DC2" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + mention: "devs" + } + coowner: { + title: "Co-owner" + showtitle: true + style: { + color: "#89BE6C" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.broadcast" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + ] + canGrantRoles: [ + "supervisor" + "bot" + "regular" + "default" + ] + } + supervisor: { + title: "Supervisor" + showtitle: true + style: { + color: "#009CDD" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "regular" + "default" + ] + } + bot: { + title: "Bot" + showtitle: true + badge: "android" + style: { + color: "#964B74" + } + permissions: [ + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "chat.send" + "chat.delete" + "chat.specialMention" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + ] + canGrantRoles: [ + ] + } + regular: { + title: "Regular" + showtitle: false + style: { + color: "#925AFF" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } + default: { // REQUIRED ROLE + title: "Default" + showtitle: false + style: { + color: "#ffffff" + } + permissions: [ + "djqueue.join" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } +} \ No newline at end of file From d9e2bb4dc9b4b88134c7f1a74ba0716fab4abf2a Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 10 Jul 2016 15:32:33 +0200 Subject: [PATCH 03/15] Switched everything to nconf & cleaned up some files Signed-off-by: Henry Gressmann --- socketserver/SC.js | 12 +- socketserver/YT.js | 6 +- socketserver/database.js | 15 +- socketserver/database_util.js | 10 +- socketserver/db_level.js | 477 +++++++++-------- socketserver/db_mongo.js | 929 ++++++++++++++++++++-------------- socketserver/db_mysql.js | 24 +- socketserver/djqueue.js | 8 +- socketserver/mailer.js | 16 +- socketserver/role.js | 29 +- socketserver/room.js | 20 +- socketserver/socketserver.js | 43 +- start.js | 46 +- webserver/app.js | 25 +- 14 files changed, 914 insertions(+), 746 deletions(-) diff --git a/socketserver/SC.js b/socketserver/SC.js index 3dce6ea..ee1bd96 100644 --- a/socketserver/SC.js +++ b/socketserver/SC.js @@ -1,11 +1,11 @@ // API reference: https://developers.soundcloud.com/docs/api/reference#track -var https = require('https'); -var util = require('util'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "SC"}); -var querystring = require('querystring'); -var config = require('../serverconfig'); -var key = config.apis.SC.key; +const https = require('https'); +const util = require('util'); +const log = new (require('basic-logger'))({showTimestamp: true, prefix: "SC"}); +const querystring = require('querystring'); +const nconf = require('nconf'); +const key = nconf.get('apis:SC:key'); var SC = function(){ }; diff --git a/socketserver/YT.js b/socketserver/YT.js index 89cfb0e..e6d5812 100644 --- a/socketserver/YT.js +++ b/socketserver/YT.js @@ -3,8 +3,8 @@ var util = require('util'); var log = new (require('basic-logger'))({showTimestamp: true, prefix: "YT"}); var querystring = require('querystring'); var Duration = require("durationjs"); -var config = require('../serverconfig'); -var key = key = config.apis.YT.key; +const nconf = require('nconf'); +const key = nconf.get('apis:YT:key'); https.globalAgent.keepAlive = true; https.globalAgent.keepAliveMsecs = 60e3; @@ -146,7 +146,7 @@ YT.prototype.search = function(query, callback){ key: key }; - if (config.apis.YT.restrictSearchToMusic) + if (nconf.get('apis:YT:restrictSearchToMusic')) inObj.videoCategoryId = 10; // This is restricting the search to things categorized as music var url = "https://www.googleapis.com/youtube/v3/search?" + querystring.stringify(inObj); diff --git a/socketserver/database.js b/socketserver/database.js index 9f2cfc0..5f73840 100644 --- a/socketserver/database.js +++ b/socketserver/database.js @@ -1,16 +1,19 @@ -var config = require('../serverconfig'); +const nconf = require('nconf'); -function Database(){ - config.db.dbType = config.db.dbType.toLowerCase() || 'level'; - - switch(config.db.dbType){ +function Database() { + nconf.defaults({ + 'db:dbType': 'level' + }); + switch (nconf.get('db:dbType')) { case 'level': return require('./db_level'); case 'mysql': return require('./db_mysql'); case 'mongo': return require('./db_mongo'); + default: + return require('./db_level'); } } -module.exports = new Database(); \ No newline at end of file +module.exports = new Database(); diff --git a/socketserver/database_util.js b/socketserver/database_util.js index 04ad32d..95b2376 100644 --- a/socketserver/database_util.js +++ b/socketserver/database_util.js @@ -1,16 +1,16 @@ -var Hash = require('./hash'); +const Hash = require('./hash'); -function DBUtils(){} +function DBUtils() {} -DBUtils.prototype.makePass = function(inPass, salt) { +DBUtils.prototype.makePass = function (inPass, salt) { return Hash.md5(('' + inPass) + (salt || '')).toString(); }; -DBUtils.prototype.validateEmail = function(email) { +DBUtils.prototype.validateEmail = function (email) { return /^.+@.+\..+$/.test(email); }; -DBUtils.prototype.validateUsername = function(un) { +DBUtils.prototype.validateUsername = function (un) { return /^[a-z0-9_-]{3,20}$/i.test(un); }; diff --git a/socketserver/db_level.js b/socketserver/db_level.js index cb8bc1a..ebc28cd 100644 --- a/socketserver/db_level.js +++ b/socketserver/db_level.js @@ -1,101 +1,123 @@ -//Modules -var levelup = require('levelup'); -var path = require('path'); -var util = require('util'); -var fs = require('fs'); -var log = new(require('basic-logger'))({ +// eslint-disable-next-line +'use strict'; +// Modules +const levelup = require('levelup'); +const path = require('path'); +const util = require('util'); +const fs = require('fs'); +const log = new(require('basic-logger'))({ showTimestamp: true, - prefix: "LevelDB" + prefix: 'LevelDB' }); +const nconf = require('nconf'); + +// Files +const Mailer = require('./mailer'); +const DBUtils = require('./database_util'); + +// Variables +let currentPID = 0; +let currentUID = 0; +let currentCID = 0; +const expires = 1000 * 60 * 60 * 24 * nconf.get('loginExpire'); +let usernames = []; + +function setupDB(dir, setup, callback) { + setup = setup || function () {}; + callback = callback || function () {}; + + return levelup(dir, null, (err, newdb) => { + if (err) { + log.error('Could not open db'); + callback(err); + return; + } -//Files -var config = require('../serverconfig.js'); -var Mailer = require('./mailer'); -var DBUtils = require('./database_util'); - -//Variables -var currentPID = 0; -var currentUID = 0; -var currentCID = 0; -var expires = 1000 * 60 * 60 * 24 * config.loginExpire; -var usernames = []; + newdb.get('setup', (err) => { + if (err && err.notFound) { + newdb.put('setup', 1); + setup(newdb); + callback(null, newdb); + } else { + callback(null, newdb); + } + }); + }); +} function LevelDB(callback) { - var dbdir = path.resolve(config.db.dbDir || './socketserver/db'); + const dbdir = path.resolve(nconf.get('db:dbDir') || './socketserver/db'); try { fs.statSync(dbdir); - } catch(e) { + } catch (e) { fs.mkdirSync(dbdir); } - //PlaylistDB - if(!this.PlaylistDB) - this.PlaylistDB = setupDB(dbdir + '/playlists', - - //If new DB is created - function(newdb) { - currentPID = 1; - log.debug('PIDCOUNTER set to 1'); - newdb.put('PIDCOUNTER', 1); - }, - - //Callback - function(err, db) { - if (err) log.error('Could not open PlaylistDB: ' + err); - - if (currentPID != 0) return; - - db.get('PIDCOUNTER', function(err, val) { - if (err) { - throw new Error('Cannot get PIDCOUNTER from UserDB. Might be corrupt'); - } - currentPID = parseInt(val); - }); - }); - - //RoomDB - if(!this.RoomDB) - this.RoomDB = setupDB(dbdir + '/room', - - //If new DB is created - function(newdb) {}, - - //Callback - function(err, db) { - if (err) throw new Error('Could not open RoomDB: ' + err); - if (callback) callback(null, db); - }); + // PlaylistDB + if (!this.PlaylistDB) { + this.PlaylistDB = setupDB(`${dbdir}/playlists`, + // If new DB is created + (newdb) => { + currentPID = 1; + log.debug('PIDCOUNTER set to 1'); + newdb.put('PIDCOUNTER', 1); + }, + + // Callback + (err, db) => { + if (err) log.error(`Could not open PlaylistDB: ${err}`); + if (currentPID !== 0) return; + db.get('PIDCOUNTER', (err, val) => { + if (err) { + throw new Error('Cannot get PIDCOUNTER from UserDB. Might be corrupt'); + } + currentPID = parseInt(val, 10); + }); + }); + } - //TokenDB - if(!this.TokenDB) - this.TokenDB = setupDB(dbdir + '/tokens', + // RoomDB + if (!this.RoomDB) { + this.RoomDB = setupDB(`${dbdir}/room`, + // If new DB is created + () => {}, + + // Callback + (err, db) => { + if (err) throw new Error(`Could not open RoomDB: ${err}`); + if (callback) callback(null, db); + }); + } + // TokenDB + if (!this.TokenDB) + this.TokenDB = setupDB(`${dbdir}/tokens`, - //If new DB is created - function(newdb) {}, + // If new DB is created + function (newdb) {}, - //Callback - function(err, db) { + // Callback + function (err, db) { if (err) log.error('Could not open TokenDB: ' + err); }); - //UserDB - if(!this.UserDB) - this.UserDB = setupDB(dbdir + '/users', + // UserDB + if (!this.UserDB) + this.UserDB = setupDB(`${dbdir}/users`, - //If new DB is created - function(newdb) { + // If new DB is created + function (newdb) { currentUID = 1; log.debug('UIDCOUNTER set to 1'); newdb.put('UIDCOUNTER', 1); }, - //Callback - function(err, newdb) { + // Callback + function (err, newdb) { if (err) { throw new Error('Could not open UserDB: ' + err); } if (currentUID != 0) return; - newdb.get('UIDCOUNTER', function(err, val) { + newdb.get('UIDCOUNTER', function (err, val) { if (err) { throw new Error('Cannot get UIDCOUNTER from UserDB. Might be corrupt'); } @@ -103,7 +125,7 @@ function LevelDB(callback) { }); newdb.createReadStream() - .on('data', function(data) { + .on('data', function (data) { if (data.key.indexOf('@') == -1) return; try { var user = JSON.parse(data.value); @@ -115,88 +137,65 @@ function LevelDB(callback) { user.lastdj = false; newdb.put(data.key, JSON.stringify(user)); }) - .on('end', function() { + .on('end', function () { return false; }); }); - - //ChatDB - if(!this.ChatDB) + + // ChatDB + if (!this.ChatDB) this.ChatDB = setupDB(dbdir + '/chat', - //If new DB is created - function(newdb) { + // If new DB is created + function (newdb) { currentCID = 1; log.debug('CIDCOUNTER set to 1'); newdb.put('CIDCOUNTER', 1); }, - //Callback - function(err, newdb) { + // Callback + function (err, newdb) { if (err) { throw new Error('Could not open ChatDB: ' + err); } if (currentCID != 0) return; - newdb.get('CIDCOUNTER', function(err, val) { + newdb.get('CIDCOUNTER', function (err, val) { if (err) { throw new Error('Cannot get CIDCOUNTER from PmDB. Might be corrupt'); } currentCID = parseInt(val); }); }); - - //PmDB - if(!this.PmDB) + // PmDB + if (!this.PmDB) this.PmDB = setupDB(dbdir + '/pm', - //If new DB is created - function(newdb) {}, + // If new DB is created + function (newdb) {}, - //Callback - function(err, newdb) { + // Callback + function (err, newdb) { if (err) { throw new Error('Could not open PmDB: ' + err); } }); - - //IpDB - if(!this.IpDB) + + // IpDB + if (!this.IpDB) this.IpDB = setupDB(dbdir + '/ip', - //If new DB is created - function(newdb) {}, + // If new DB is created + function (newdb) {}, - //Callback - function(err, newdb) { + // Callback + function (err, newdb) { if (err) { throw new Error('Could not open IpDB: ' + err); } }); } -function setupDB(dir, setup, callback){ - setup = setup || function(){}; - callback = callback || function(){}; - - return levelup(dir, null, function(err, newdb){ - if (err){ - log.error('Could not open db'); - callback(err); - return; - } - - newdb.get("setup", function( err, val ){ - if (err && err.notFound){ - newdb.put('setup', 1); - setup(newdb); - callback(null, newdb); - }else{ - callback(null, newdb); - } - }); - }); -} /** * getJSON() gives the callback function a parsed JSON object @@ -206,10 +205,10 @@ function setupDB(dir, setup, callback){ * @param {Function} callback * @return {Object} this */ -LevelDB.prototype.getJSON = function(db, key, callback) { - callback = callback || function() {}; +LevelDB.prototype.getJSON = function (db, key, callback) { + callback = callback || function () {}; - db.get(key, function(err, val) { + db.get(key, function (err, val) { if (val) { try { val = JSON.parse(val); @@ -231,17 +230,17 @@ LevelDB.prototype.getJSON = function(db, key, callback) { * @param {Function} callback * @return {Object} this */ -LevelDB.prototype.putJSON = function(db, key, val, callback) { - callback = callback || function() {}; +LevelDB.prototype.putJSON = function (db, key, val, callback) { + callback = callback || function () {}; db.put(key, JSON.stringify(val), callback); return this; }; -//PlaylistDB -LevelDB.prototype.getPlaylist = function(pid, callback) { +// PlaylistDB +LevelDB.prototype.getPlaylist = function (pid, callback) { var Playlist = require('./playlist'); - this.getJSON(this.PlaylistDB, pid, function(err, data) { + this.getJSON(this.PlaylistDB, pid, function (err, data) { if (err) { callback('PlaylistNotFound'); return; @@ -257,7 +256,7 @@ LevelDB.prototype.getPlaylist = function(pid, callback) { return this; }; -LevelDB.prototype.createPlaylist = function(owner, name, callback) { +LevelDB.prototype.createPlaylist = function (owner, name, callback) { var Playlist = require('./playlist'); var pl = new Playlist(); @@ -271,51 +270,51 @@ LevelDB.prototype.createPlaylist = function(owner, name, callback) { callback(null, pl); }; -LevelDB.prototype.deletePlaylist = function(pid, callback) { +LevelDB.prototype.deletePlaylist = function (pid, callback) { this.PlaylistDB.del(pid.toString(), callback); }; -LevelDB.prototype.putPlaylist = function(pid, data, callback) { +LevelDB.prototype.putPlaylist = function (pid, data, callback) { this.putJSON(this.PlaylistDB, pid, data, callback); }; -//RoomDB -LevelDB.prototype.getRoom = function(slug, callback) { +// RoomDB +LevelDB.prototype.getRoom = function (slug, callback) { this.getJSON(this.RoomDB, slug, callback); return this; }; -LevelDB.prototype.setRoom = function(slug, val, callback) { +LevelDB.prototype.setRoom = function (slug, val, callback) { this.putJSON(this.RoomDB, slug, val, callback); return this; }; -//TokenDB -LevelDB.prototype.deleteToken = function(tok) { +// TokenDB +LevelDB.prototype.deleteToken = function (tok) { this.TokenDB.del(tok); }; -LevelDB.prototype.createToken = function(email) { +LevelDB.prototype.createToken = function (email) { var tok = DBUtils.makePass(email, Date.now()); this.putJSON(this.TokenDB, tok, { - email: email, + email, time: Date.now(), }); return tok; }; -LevelDB.prototype.isTokenValid = function(tok, callback) { +LevelDB.prototype.isTokenValid = function (tok, callback) { var that = this; - this.getJSON(this.TokenDB, tok, function(err, data) { + this.getJSON(this.TokenDB, tok, function (err, data) { if (err || data == null) { callback('InvalidToken'); return; } - if (config.loginExpire && (Date.now() - data.time) < expires) { + if (nconf.get('loginExpire') && (Date.now() - data.time) < expires) { callback(null, data.email); } else { that.deleteToken(data.token); @@ -324,7 +323,7 @@ LevelDB.prototype.isTokenValid = function(tok, callback) { }); }; -//UserDB +// UserDB function addUsername(un) { usernames.push(un.toLowerCase()); } @@ -341,7 +340,7 @@ function usernameExists(un) { return ((ind = usernames.indexOf(un)) != -1 ? ind : false); } -LevelDB.prototype.createUser = function(obj, callback) { +LevelDB.prototype.createUser = function (obj, callback) { var User = require('./user'); var that = this; @@ -355,7 +354,7 @@ LevelDB.prototype.createUser = function(obj, callback) { var inData = defaultCreateObj; inData.email = inData.email.toLowerCase(); - //Validation + // Validation if (!inData.email || !DBUtils.validateEmail(inData.email)) { callback('InvalidEmail'); return; @@ -373,43 +372,43 @@ LevelDB.prototype.createUser = function(obj, callback) { return; } - //Check for existing account - this.userEmailExists(inData.email, function(err, res) { + // Check for existing account + this.userEmailExists(inData.email, function (err, res) { if (!err) { if (callback) callback('AccountExists'); return; } var user = new User(); - + user.data.uid = currentUID++; that.UserDB.put('UIDCOUNTER', currentUID); user.data.un = inData.un; user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (config.room.email.confirmation) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:email:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); var updatedUserObj = user.makeDbObj(); var tok = that.createToken(inData.email); - that.putJSON(that.UserDB, inData.email, updatedUserObj, function(err) { + that.putJSON(that.UserDB, inData.email, updatedUserObj, function (err) { if (err) { callback(err); return; } - //Send confirmation email - if (config.room.email.confirmation) { + // Send confirmation email + if (nconf.get('room:email:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, - }, inData.email, function(data) { + }, inData.email, function (data) { console.log(data); }); } - //Do other ~messy~ stuff + // Do other ~messy~ stuff addUsername(inData.un); user.login(inData.email); callback(null, user, tok); @@ -417,7 +416,7 @@ LevelDB.prototype.createUser = function(obj, callback) { }); }; -LevelDB.prototype.loginUser = function(obj, callback) { +LevelDB.prototype.loginUser = function (obj, callback) { var User = require('./user'); var that = this; @@ -433,7 +432,7 @@ LevelDB.prototype.loginUser = function(obj, callback) { if (inData.email && inData.pw) { inData.email = inData.email.toLowerCase(); - this.getJSON(this.UserDB, inData.email, function(err, data) { + this.getJSON(this.UserDB, inData.email, function (err, data) { if ((err && err.notFound) || data == null) { callback('UserNotFound'); return; @@ -452,19 +451,18 @@ LevelDB.prototype.loginUser = function(obj, callback) { var tok = that.createToken(inData.email); var user = new User(); - user.login(inData.email, data, function() { - + user.login(inData.email, data, function () { callback(null, user, tok); }); }); } else if (inData.token) { - that.isTokenValid(inData.token, function(err, email) { + that.isTokenValid(inData.token, function (err, email) { if (err) { callback(err); return; } - that.getJSON(that.UserDB, email, function(err, data) { + that.getJSON(that.UserDB, email, function (err, data) { if ((err && err.notFound) || data == null) { callback('UserNotFound'); return; @@ -476,8 +474,7 @@ LevelDB.prototype.loginUser = function(obj, callback) { } var user = new User(); - user.login(email, data, function() { - + user.login(email, data, function () { callback(null, user); }); }); @@ -487,39 +484,38 @@ LevelDB.prototype.loginUser = function(obj, callback) { } }; -LevelDB.prototype.putUser = function(email, data, callback) { +LevelDB.prototype.putUser = function (email, data, callback) { this.putJSON(this.UserDB, email, data, callback); }; -LevelDB.prototype.getUser = function(email, callback){ +LevelDB.prototype.getUser = function (email, callback) { var User = require('./user'); - this.getJSON(this.UserDB, email, function(err, data){ - if ((err && err.notFound) || data == null) {callback('UserNotFound'); return; } - - if (err) {callback(err); return; } + this.getJSON(this.UserDB, email, function (err, data) { + if ((err && err.notFound) || data == null) { callback('UserNotFound'); return; } + + if (err) { callback(err); return; } var user = new User(); - - user.login(email, data, function(){ + user.login(email, data, function () { callback(null, user); }); }); }; -LevelDB.prototype.deleteUser = function(email, callback){ +LevelDB.prototype.deleteUser = function (email, callback) { var that = this; - - this.getUser(email, function(err, user){ - if (err){ if (callback) callback(err); return; } - + + this.getUser(email, function (err, user) { + if (err) { if (callback) callback(err); return; } + that.UserDB.del(email); - + callback(null, true); }); }; -LevelDB.prototype.getUserByUid = function(uid, opts, callback) { +LevelDB.prototype.getUserByUid = function (uid, opts, callback) { var User = require('./user'); var done = false; @@ -542,7 +538,7 @@ LevelDB.prototype.getUserByUid = function(uid, opts, callback) { var len = 0; var stream = this.UserDB.createReadStream() - .on('data', function(data) { + .on('data', function (data) { var obj = {}; try { @@ -555,7 +551,7 @@ LevelDB.prototype.getUserByUid = function(uid, opts, callback) { if (uid.indexOf(obj.uid) > -1) { var user = new User(); - user.login(data.key, obj, opts, function() { + user.login(data.key, obj, opts, function () { out[obj.uid] = user; len++; @@ -572,13 +568,13 @@ LevelDB.prototype.getUserByUid = function(uid, opts, callback) { stream.destroy(); var user = new User(); - user.login(data.key, obj, opts, function() { + user.login(data.key, obj, opts, function () { callback(null, user); }); } } }) - .on('end', function() { + .on('end', function () { if (!done) { if (typeof uid === 'number') { callback('UserNotFound'); @@ -590,7 +586,7 @@ LevelDB.prototype.getUserByUid = function(uid, opts, callback) { }); }; -LevelDB.prototype.getUserByName = function(name, opts, callback) { +LevelDB.prototype.getUserByName = function (name, opts, callback) { var User = require('./user'); var done = false; @@ -600,7 +596,7 @@ LevelDB.prototype.getUserByName = function(name, opts, callback) { } var stream = this.UserDB.createReadStream() - .on('data', function(data) { + .on('data', function (data) { var obj = {}; try { @@ -615,20 +611,19 @@ LevelDB.prototype.getUserByName = function(name, opts, callback) { stream.destroy(); var user = new User(); - user.login(data.key, obj, opts, function() { + user.login(data.key, obj, opts, function () { if (callback) callback(null, user); }); } }) - .on('end', function() { + .on('end', function () { if (!done && callback) callback('UserNotFound'); }); }; -LevelDB.prototype.userEmailExists = function(key, callback) { - this.getJSON(this.UserDB, key, function(err, data) { - +LevelDB.prototype.userEmailExists = function (key, callback) { + this.getJSON(this.UserDB, key, function (err, data) { if (err && err.notFound) { if (callback) callback(err, false); return; @@ -638,38 +633,38 @@ LevelDB.prototype.userEmailExists = function(key, callback) { }); }; -//ChatDB -LevelDB.prototype.logChat = function(uid, msg, special, callback) { - this.putJSON(this.ChatDB, currentCID, { uid: uid, msg: msg, special: special }); +// ChatDB +LevelDB.prototype.logChat = function (uid, msg, special, callback) { + this.putJSON(this.ChatDB, currentCID, { uid, msg, special }); callback(null, currentCID++); }; -//PmDB -LevelDB.prototype.logPM = function(from, to, msg, callback) { +// PmDB +LevelDB.prototype.logPM = function (from, to, msg, callback) { var that = this; - var key = Math.min(from, to) + ":" + Math.max(from, to); - - this.getJSON(this.PmDB, key, function(err, res){ + var key = Math.min(from, to) + ':' + Math.max(from, to); + + this.getJSON(this.PmDB, key, function (err, res) { var out = []; - - if(!err) out = res; - + + if (!err) out = res; + out.push({ message: msg, time: new Date(), - from: from, + from, unread: true, }); - + that.putJSON(that.PmDB, key, out); }); }; -LevelDB.prototype.getConversation = function(from, to, callback) { - var key = Math.min(from, to) + ":" + Math.max(from, to); - - this.getJSON(this.PmDB, key, function(err, res){ - if(err){ +LevelDB.prototype.getConversation = function (from, to, callback) { + var key = Math.min(from, to) + ':' + Math.max(from, to); + + this.getJSON(this.PmDB, key, function (err, res) { + if (err) { callback(null, []); } else { callback(null, res); @@ -677,15 +672,15 @@ LevelDB.prototype.getConversation = function(from, to, callback) { }); }; -LevelDB.prototype.getConversations = function(uid, callback) { +LevelDB.prototype.getConversations = function (uid, callback) { var that = this; - + var out = {}; var uids; uid = uid.toString(); - + this.PmDB.createReadStream() - .on('data', function(data) { + .on('data', function (data) { if (data.key.indexOf(':') == -1 || (uids = data.key.split(':')).indexOf(uid) == -1) return; try { @@ -693,10 +688,10 @@ LevelDB.prototype.getConversations = function(uid, callback) { } catch (e) { return; } - + var unread = 0; - convo.map(function(e){ - if(e.unread && e.from != uid) unread++; + convo.map(function (e) { + if (e.unread && e.from != uid) unread++; return { messages: e.messages, time: e.time, @@ -706,15 +701,15 @@ LevelDB.prototype.getConversations = function(uid, callback) { out[uids[(uids.indexOf(uid) + 1) % 2]] = { user: null, - messages: [ convo.pop() ], - unread: unread, + messages: [convo.pop()], + unread, }; }) - .on('end', function() { - var uids = Object.keys(out).map(function(e){ return parseInt(e); }); - + .on('end', function () { + var uids = Object.keys(out).map(function (e) { return parseInt(e); }); + if (uids.length > 0) { - that.getUserByUid(uids, function(err, result){ + that.getUserByUid(uids, function (err, result) { if (err) { callback(err); } else { @@ -731,45 +726,47 @@ LevelDB.prototype.getConversations = function(uid, callback) { }); }; -LevelDB.prototype.markConversationRead = function(uid, uid2, time) { +LevelDB.prototype.markConversationRead = function (uid, uid2, time) { var that = this; - var key = Math.min(uid, uid2) + ":" + Math.max(uid, uid2); - - this.getJSON(this.PmDB, key, function(err, res) { - if(err) return; - - res.map(function(e){ - if(e.from == uid2 && new Date(e.time) < new Date(time)) e.unread = false; + var key = Math.min(uid, uid2) + ':' + Math.max(uid, uid2); + + this.getJSON(this.PmDB, key, function (err, res) { + if (err) return; + + res.map(function (e) { + if (e.from == uid2 && new Date(e.time) < new Date(time)) e.unread = false; return e; }); - + that.putJSON(that.PmDB, key, res); }); }; -//IpDB -LevelDB.prototype.logIp = function(address, uid) { - var that = this; +// IpDB +LevelDB.prototype.logIp = function (address, uid) { + let that = this; + + this.getJSON(this.IpDB, uid, function (err, res) { + let out = res || []; - this.getJSON(this.IpDB, uid, function(err, res){ - var out = res || []; - out.push({ - address: address, + address, time: new Date(), }); - + that.putJSON(that.IpDB, uid, out); }); }; -LevelDB.prototype.getIpHistory = function(uid, callback) { - this.getJSON(this.IpDB, uid, function(err, data) { - if(err) - callback(err) - else - callback(null, data.sort(function(a, b){ return a.address > b.address; }).reverse().filter(function(e, i, a){ return i == 0 || a[i - 1].address != e.address; }).sort(function(a, b){ return a.time < b.time; })); +LevelDB.prototype.getIpHistory = function (uid, callback) { + this.getJSON(this.IpDB, uid, (err, data) => { + if (err) { + callback(err); + } + else { + callback(null, data.sort(function (a, b) { return a.address > b.address; }).reverse().filter(function (e, i, a) { return i == 0 || a[i - 1].address != e.address; }).sort(function (a, b) { return a.time < b.time; })); + } }); }; -module.exports = new LevelDB(); \ No newline at end of file +module.exports = new LevelDB(); diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index edac477..7312da9 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -1,40 +1,42 @@ -//Modules -var mongodb = require('mongodb').MongoClient; -var util = require('util'); -var log = new(require('basic-logger'))({ +// eslint-disable-next-line +'use strict'; +// Modules +const mongodb = require('mongodb').MongoClient; +const util = require('util'); +const log = new(require('basic-logger'))({ showTimestamp: true, - prefix: "MongoDB" + prefix: 'MongoDB' }); -//Files -var config = require('../serverconfig.js'); -var Mailer = require('./mailer'); -var DBUtils = require('./database_util'); - -//Variables -var expires = 1000 * 60 * 60 * 24 * config.loginExpire; -var usernames = []; -var db = null; -var poolqueue = []; -var ready = false; - -var playlistscol = null; -var roomcol = null; -var tokenscol = null; -var userscol = null; -var chatcol = null; -var pmscol = null; -var ipcol = null; +// Files +const nconf = require('nconf'); +const Mailer = require('./mailer'); +const DBUtils = require('./database_util'); + +// Variables +const expires = 1000 * 60 * 60 * 24 * nconf.get('loginExpire'); +let usernames = []; +let db = null; +let poolqueue = []; +let ready = false; + +let playlistscol = null; +let roomcol = null; +let tokenscol = null; +let userscol = null; +let chatcol = null; +let pmscol = null; +let ipcol = null; function dbQueue(callback) { if (callback === true) { while (poolqueue.length > 0) (poolqueue.shift())(); - + ready = true; return; } - + if (!ready) { return poolqueue.push(callback); } @@ -45,110 +47,120 @@ function dbQueue(callback) { function createCollectionsIfNoExist(callback) { var step = 0; var total = 7; - - db.collection('playlists', {strict:true}, function(err, col) { + + db.collection('playlists', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('playlists', function(errc, result) { - if (errc) - throw new Error('Failed to create the playlists collection'); - - playlistscol = result; - if (++step == total) callback(); - }); + db.createCollection('playlists', function (errc, result) { + if (errc) + throw new Error('Failed to create the playlists collection'); + + playlistscol = result; + if (++step == total) callback(); + }); } else { playlistscol = col; if (++step == total) callback(); } - }); - - db.collection('room', {strict:true}, function(err, col) { + + db.collection('room', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('room', function(errc, result) { - if (errc) - throw new Error('Failed to create the room collection'); - - roomcol = result; - if (++step == total) callback(); - }); + db.createCollection('room', function (errc, result) { + if (errc) + throw new Error('Failed to create the room collection'); + + roomcol = result; + if (++step == total) callback(); + }); } else { roomcol = col; if (++step == total) callback(); } - }); - - db.collection('tokens', {strict:true}, function(err, col) { + + db.collection('tokens', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('tokens', function(errc, result) { - if (errc) - throw new Error('Failed to create the tokens collection'); - - tokenscol = result; - if (++step == total) callback(); - }); + db.createCollection('tokens', function (errc, result) { + if (errc) + throw new Error('Failed to create the tokens collection'); + + tokenscol = result; + if (++step == total) callback(); + }); } else { tokenscol = col; if (++step == total) callback(); } - }); - - db.collection('users', {strict:true}, function(err, col) { + + db.collection('users', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('users', function(errc, result) { - if (errc) - throw new Error('Failed to create the users collection'); - - userscol = result; - if (++step == total) callback(); - }); + db.createCollection('users', function (errc, result) { + if (errc) + throw new Error('Failed to create the users collection'); + + userscol = result; + if (++step == total) callback(); + }); } else { userscol = col; if (++step == total) callback(); } - }); - - db.collection('chat', {strict:true}, function(err, col) { + + db.collection('chat', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('chat', function(errc, result) { - if (errc) - throw new Error('Failed to create the chat collection'); - - chatcol = result; - if (++step == total) callback(); - }); + db.createCollection('chat', function (errc, result) { + if (errc) + throw new Error('Failed to create the chat collection'); + + chatcol = result; + if (++step == total) callback(); + }); } else { chatcol = col; if (++step == total) callback(); } }); - - db.collection('pms', {strict:true}, function(err, col) { + + db.collection('pms', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('pms', function(errc, result) { - if (errc) - throw new Error('Failed to create the pms collection'); - - pmscol = result; - if (++step == total) callback(); - }); + db.createCollection('pms', function (errc, result) { + if (errc) + throw new Error('Failed to create the pms collection'); + + pmscol = result; + if (++step == total) callback(); + }); } else { pmscol = col; if (++step == total) callback(); } }); - - db.collection('ip', {strict:true}, function(err, col) { + + db.collection('ip', { + strict: true + }, function (err, col) { if (err) { - db.createCollection('ip', function(errc, result) { - if (errc) - throw new Error('Failed to create the ip collection'); - - ipcol = result; - if (++step == total) callback(); - }); + db.createCollection('ip', function (errc, result) { + if (errc) + throw new Error('Failed to create the ip collection'); + + ipcol = result; + if (++step == total) callback(); + }); } else { ipcol = col; if (++step == total) callback(); @@ -159,232 +171,282 @@ function createCollectionsIfNoExist(callback) { function initCollections(callback) { var step = 0; var total = 4; - - //Playlists - playlistscol.findOne({_id: 'PIDCOUNTER'}, function(err, pidobj) { - if (err) { - throw new Error('Cannot get PIDCOUNTER from playlists'); - } - - if (!pidobj) { - playlistscol.insert({_id: "PIDCOUNTER", seq: 1}, function(error, data) { - if (error) { - throw new Error('Cannot set PIDCOUNTER to playlists'); - } - if (++step == total) callback(); - }); - } else { - if (++step == total) callback(); - } - }); - - //Users - userscol.findOne({_id: 'UIDCOUNTER'}, function(err, pidobj) { - if (err) { - throw new Error('Cannot get UIDCOUNTER from users'); - } - - if (!pidobj) { - userscol.insert({_id: "UIDCOUNTER", seq: 1}, function(error, data) { - if (error) { - throw new Error('Cannot set UIDCOUNTER to users'); - } - if (++step == total) callback(); - }); - } else { - if (++step == total) callback(); - } - }); - - //Chat - chatcol.findOne({_id: 'CIDCOUNTER'}, function(err, pidobj) { - if (err) { - throw new Error('Cannot get CIDCOUNTER from chat'); - } - if (!pidobj) { - chatcol.insert({_id: "CIDCOUNTER", seq: 1}, function(error, data) { - if (error) { - throw new Error('Cannot set CIDCOUNTER to chat'); - } - if (++step == total) callback(); - }); - } else { - if (++step == total) callback(); - } - }); - - //PMs - pmscol.findOne({_id: 'PMIDCOUNTER'}, function(err, pidobj) { - if (err) { - throw new Error('Cannot get PMIDCOUNTER from pms'); - } - if (!pidobj) { - pmscol.insert({_id: "PMIDCOUNTER", seq: 1}, function(error, data) { - if (error) { - throw new Error('Cannot set PMIDCOUNTER to pms'); - } - if (++step == total) callback(); - }); - } else { - if (++step == total) callback(); - } - }); + + // Playlists + playlistscol.findOne({ + _id: 'PIDCOUNTER' + }, function (err, pidobj) { + if (err) { + throw new Error('Cannot get PIDCOUNTER from playlists'); + } + + if (!pidobj) { + playlistscol.insert({ + _id: 'PIDCOUNTER', + seq: 1 + }, function (error, data) { + if (error) { + throw new Error('Cannot set PIDCOUNTER to playlists'); + } + if (++step == total) callback(); + }); + } else { + if (++step == total) callback(); + } + }); + + // Users + userscol.findOne({ + _id: 'UIDCOUNTER' + }, function (err, pidobj) { + if (err) { + throw new Error('Cannot get UIDCOUNTER from users'); + } + + if (!pidobj) { + userscol.insert({ + _id: 'UIDCOUNTER', + seq: 1 + }, function (error, data) { + if (error) { + throw new Error('Cannot set UIDCOUNTER to users'); + } + if (++step == total) callback(); + }); + } else { + if (++step == total) callback(); + } + }); + + // Chat + chatcol.findOne({ + _id: 'CIDCOUNTER' + }, function (err, pidobj) { + if (err) { + throw new Error('Cannot get CIDCOUNTER from chat'); + } + if (!pidobj) { + chatcol.insert({ + _id: 'CIDCOUNTER', + seq: 1 + }, function (error, data) { + if (error) { + throw new Error('Cannot set CIDCOUNTER to chat'); + } + if (++step == total) callback(); + }); + } else { + if (++step == total) callback(); + } + }); + + // PMs + pmscol.findOne({ + _id: 'PMIDCOUNTER' + }, function (err, pidobj) { + if (err) { + throw new Error('Cannot get PMIDCOUNTER from pms'); + } + if (!pidobj) { + pmscol.insert({ + _id: 'PMIDCOUNTER', + seq: 1 + }, function (error, data) { + if (error) { + throw new Error('Cannot set PMIDCOUNTER to pms'); + } + if (++step == total) callback(); + }); + } else { + if (++step == total) callback(); + } + }); } function MongoDB(cb) { - var dburl = 'mongodb://' + config.db.mongoUser + ':' + config.db.mongoPassword + '@' + config.db.mongoHost + ':27017/' + config.db.mongoDatabase; - - mongodb.connect(dburl, function(err, database) { - if (err) { - throw new Error('Could not connect to database: ' + err); - } - - db = database; - - createCollectionsIfNoExist(function() { - initCollections(function() { - dbQueue(true); - }); - }); - }); -} + const dburl = `mongodb://${nconf.get('db:mongoUser')}:${nconf.get('db:mongoPassword')}@${nconf.get('db:mongoHost')}:27017/${nconf.get('db:mongoDatabase')}`; -function getNextSequence(collection, id, callback) { - dbQueue(function(){ - db.collection(collection).findOneAndUpdate({_id: id}, { $inc: { seq: 1 } }, function(err, r) { - if (err) throw new Error('Cannot update index counter'); - callback(r.value.seq); + mongodb.connect(dburl, function (err, database) { + if (err) { + throw new Error(`Could not connect to database: ${err}`); + } + + db = database; + + createCollectionsIfNoExist(() => { + initCollections(() => { + dbQueue(true); + }); }); }); } -//PlaylistDB -MongoDB.prototype.getPlaylist = function(pid, callback) { +function getNextSequence(collection, id, callback) { + dbQueue(() => { + db.collection(collection).findOneAndUpdate({ + _id: id + }, { + $inc: { + seq: 1 + } + }, (err, r) => { + if (err) throw new Error('Cannot update index counter'); + callback(r.value.seq); + }); + }); +} + +// PlaylistDB +MongoDB.prototype.getPlaylist = function (pid, callback) { var Playlist = require('./playlist'); - - dbQueue(function(){ - playlistscol.findOne({_id: pid}, {_id: 0}, function(err, data) { + + dbQueue(function () { + playlistscol.findOne({ + _id: pid + }, { + _id: 0 + }, function (err, data) { if (err || !data) { - callback('PlaylistNotFound'); - return; + callback('PlaylistNotFound'); + return; } - + var pl = new Playlist(); pl.id = pid; util._extend(pl.data, data); - + callback(err, pl); - }); + }); }); - + return this; }; -MongoDB.prototype.createPlaylist = function(owner, name, callback) { +MongoDB.prototype.createPlaylist = function (owner, name, callback) { var Playlist = require('./playlist'); - dbQueue(function(){ - getNextSequence('playlists', 'PIDCOUNTER', function(currentPID) { - var pl = new Playlist(); - - pl.id = currentPID; - pl.data.created = Date.now(); - pl.data.owner = owner; - pl.data.name = name.substr(0, 100); - - var updatedPlObj = pl.makeDbObj(); - updatedPlObj._id = currentPID; - - playlistscol.insert(updatedPlObj, function(error, data) { - callback(error, pl); - }); + dbQueue(function () { + getNextSequence('playlists', 'PIDCOUNTER', function (currentPID) { + var pl = new Playlist(); + + pl.id = currentPID; + pl.data.created = Date.now(); + pl.data.owner = owner; + pl.data.name = name.substr(0, 100); + + var updatedPlObj = pl.makeDbObj(); + updatedPlObj._id = currentPID; + + playlistscol.insert(updatedPlObj, function (error, data) { + callback(error, pl); + }); }); }); }; -MongoDB.prototype.deletePlaylist = function(pid, callback) { - dbQueue(function(){ - playlistscol.remove({_id: pid}, callback); - }); +MongoDB.prototype.deletePlaylist = function (pid, callback) { + dbQueue(function () { + playlistscol.remove({ + _id: pid + }, callback); + }); }; -MongoDB.prototype.putPlaylist = function(pid, data, callback) { +MongoDB.prototype.putPlaylist = function (pid, data, callback) { var newData = {}; util._extend(newData, data); - + newData._id = pid; - - dbQueue(function(){ - playlistscol.updateOne({_id: pid}, newData, {upsert:true, w: 1}, function(error, res) { - callback(data); + + dbQueue(function () { + playlistscol.updateOne({ + _id: pid + }, newData, { + upsert: true, + w: 1 + }, function (error, res) { + callback(data); }); }); }; -//RoomDB -MongoDB.prototype.getRoom = function(slug, callback) { - dbQueue(function(){ - roomcol.findOne({slug: slug}, {_id: 0}, callback); +// RoomDB +MongoDB.prototype.getRoom = function (slug, callback) { + dbQueue(function () { + roomcol.findOne({ + slug + }, { + _id: 0 + }, callback); }); return this; }; -MongoDB.prototype.setRoom = function(slug, val, callback) { - dbQueue(function(){ +MongoDB.prototype.setRoom = function (slug, val, callback) { + dbQueue(function () { var newData = {}; util._extend(newData, val); - + newData.slug = slug; - roomcol.updateOne({slug: slug}, newData, {upsert:true, w: 1}, function(error, data) { - if (callback) callback(error, data); + roomcol.updateOne({ + slug + }, newData, { + upsert: true, + w: 1 + }, function (error, data) { + if (callback) callback(error, data); }); }); return this; }; -//TokenDB -MongoDB.prototype.deleteToken = function(tok) { - dbQueue(function(){ - tokenscol.remove({tok: tok}, function(){}); - }); +// TokenDB +MongoDB.prototype.deleteToken = function (tok) { + dbQueue(function () { + tokenscol.remove({ + tok + }, function () {}); + }); }; -MongoDB.prototype.createToken = function(email) { +MongoDB.prototype.createToken = function (email) { var tok = DBUtils.makePass(email, Date.now()); - dbQueue(function(){ - tokenscol.insert({ - tok: tok, - email: email, + dbQueue(function () { + tokenscol.insert({ + tok, + email, time: Date.now(), - }, function() {}); - }); + }, function () {}); + }); return tok; }; -MongoDB.prototype.isTokenValid = function(tok, callback) { +MongoDB.prototype.isTokenValid = function (tok, callback) { var that = this; - dbQueue(function(){ - tokenscol.findOne({tok: tok}, function(err, data) { - if (err || data == null) { + dbQueue(function () { + tokenscol.findOne({ + tok + }, function (err, data) { + if (err || data == null) { callback('InvalidToken'); return; } - - if (config.loginExpire && (Date.now() - data.time) < expires) { + + if (nconf.get('loginExpire') && (Date.now() - data.time) < expires) { callback(null, data.email); } else { that.deleteToken(data.token); callback('InvalidToken'); } - }); + }); }); }; -//UserDB +// UserDB function addUsername(un) { usernames.push(un.toLowerCase()); } @@ -396,7 +458,7 @@ function usernameExists(un) { return ((ind = usernames.indexOf(un)) != -1 ? ind : false); } -MongoDB.prototype.createUser = function(obj, callback) { +MongoDB.prototype.createUser = function (obj, callback) { var User = require('./user'); var that = this; @@ -410,7 +472,7 @@ MongoDB.prototype.createUser = function(obj, callback) { var inData = defaultCreateObj; inData.email = inData.email.toLowerCase(); - //Validation + // Validation if (!inData.email || !DBUtils.validateEmail(inData.email)) { callback('InvalidEmail'); return; @@ -428,57 +490,58 @@ MongoDB.prototype.createUser = function(obj, callback) { return; } - dbQueue(function(){ - //Check for existing account - that.userEmailExists(inData.email, function(err, res) { + dbQueue(function () { + // Check for existing account + that.userEmailExists(inData.email, function (err, res) { if (err) { if (callback) callback('AccountExists'); return; } - - getNextSequence('users', 'UIDCOUNTER', function(currentUID) { + + getNextSequence('users', 'UIDCOUNTER', function (currentUID) { var user = new User(); - + user.data.uid = currentUID; user.data.un = inData.un; user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (config.room.email.confirmation) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:email:confirmation')) { + user.data.confirmation = DBUtils.makePass(Date.now()); + } var updatedUserObj = user.makeDbObj(); updatedUserObj._id = currentUID; updatedUserObj.email = inData.email; - + var tok = that.createToken(inData.email); - - userscol.insert(updatedUserObj, function(error, data) { - if (error) { + + userscol.insert(updatedUserObj, function (error, data) { + if (error) { callback(error); return; } - - //Send confirmation email - if (config.room.email.confirmation) { + + // Send confirmation email + if (nconf.get('room:email:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, - }, inData.email, function(data) { + }, inData.email, function (data) { console.log(data); }); } - - //Do other ~messy~ stuff + + // Do other ~messy~ stuff addUsername(inData.un); user.login(inData.email); callback(null, user, tok); - }); + }); }); - }); }); }; -MongoDB.prototype.loginUser = function(obj, callback) { +MongoDB.prototype.loginUser = function (obj, callback) { var User = require('./user'); var that = this; @@ -491,54 +554,61 @@ MongoDB.prototype.loginUser = function(obj, callback) { var inData = defaultLoginObj; - dbQueue(function(){ + dbQueue(function () { if (inData.email && inData.pw) { inData.email = inData.email.toLowerCase(); - - userscol.findOne({email: inData.email}, {_id: 0}, function(err, data) { + + userscol.findOne({ + email: inData.email + }, { + _id: 0 + }, function (err, data) { if (err) { callback(err); return; } - + if (!data) { callback('UserNotFound'); return; } - + if (DBUtils.makePass(inData.pw, data.salt) != data.pw) { callback('IncorrectPassword'); return; } - + var tok = that.createToken(inData.email); var user = new User(); - - user.login(inData.email, data, function() { + + user.login(inData.email, data, function () { callback(null, user, tok); }); }); } else if (inData.token) { - that.isTokenValid(inData.token, function(err, email) { + that.isTokenValid(inData.token, function (err, email) { if (err) { callback(err); return; } - - userscol.findOne({email: email}, {_id: 0}, function(err, data) { + + userscol.findOne({ + email + }, { + _id: 0 + }, function (err, data) { if (err) { callback(err); return; } - + if (!data) { callback('UserNotFound'); return; } - + var user = new User(); - user.login(email, data, function() { - + user.login(email, data, function () { callback(null, user); }); }); @@ -549,58 +619,71 @@ MongoDB.prototype.loginUser = function(obj, callback) { }); }; -MongoDB.prototype.putUser = function(email, data, callback) { +MongoDB.prototype.putUser = function (email, data, callback) { var newData = {}; util._extend(newData, data); - + newData._id = data.uid; newData.email = email; - - dbQueue(function(){ - userscol.updateOne({email: email}, newData, {upsert: true, w: 1}, callback); + + dbQueue(function () { + userscol.updateOne({ + email + }, newData, { + upsert: true, + w: 1 + }, callback); }); }; -MongoDB.prototype.getUser = function(email, callback){ - var User = require('./user'); +MongoDB.prototype.getUser = function (email, callback) { + var User = require('./user'); - dbQueue(function(){ - userscol.findOne({email: email}, {_id: 0}, function(err, data) { - if (err) { + dbQueue(function () { + userscol.findOne({ + email + }, { + _id: 0 + }, function (err, data) { + if (err) { callback(err); return; } - + if (!data) { callback('UserNotFound'); return; } - - var user = new User(); - - user.login(email, data, function(){ - - callback(null, user); - }); - }); + + var user = new User(); + + user.login(email, data, function () { + callback(null, user); + }); + }); }); }; -MongoDB.prototype.deleteUser = function(email, callback) { +MongoDB.prototype.deleteUser = function (email, callback) { var that = this; - - dbQueue(function(){ - that.getUser(email, function(err, user){ - if (err){ if (callback) callback(err); return; } - - userscol.remove({email: email}, function(error, data){ - callback(error || null, error ? false : true); - }); - }); + + dbQueue(function () { + that.getUser(email, function (err, user) { + if (err) { + if (callback) callback(err); + return; + } + + userscol.remove({ + email + }, function (error, data) { + callback(error || null, error ? false : true); + }); + }); }); }; -MongoDB.prototype.getUserByUid = function(uid, opts, callback) { +MongoDB.prototype.getUserByUid = function (uid, opts, callback) { var User = require('./user'); if (typeof opts === 'function') { @@ -618,32 +701,38 @@ MongoDB.prototype.getUserByUid = function(uid, opts, callback) { } var isArray = Array.isArray(uid); - + if (!Array.isArray(uid)) uid = [uid]; - + var out = {}; var len = 0; - dbQueue(function(){ - userscol.find({_id: { $in: uid}}, {_id: 0}).toArray(function(err, data) { - if(err || !data || data.length == 0){ + dbQueue(function () { + userscol.find({ + _id: { + $in: uid + } + }, { + _id: 0 + }).toArray(function (err, data) { + if (err || !data || data.length == 0) { callback('SomeUsersNotFound', out); return; } - - data.forEach(function(userobj) { + + data.forEach(function (userobj) { var user = new User(); - - user.login(userobj.email, userobj, opts, function(){ + + user.login(userobj.email, userobj, opts, function () { if (isArray) out[userobj.uid] = user; else out = user; - - console.log("Initialized user " + user.email); - if(++len == data.length){ - if(uid.length == data.length) callback(null, out); + + console.log('Initialized user ' + user.email); + if (++len == data.length) { + if (uid.length == data.length) callback(null, out); else callback('SomeUsersNotFound', out); } }); @@ -652,70 +741,104 @@ MongoDB.prototype.getUserByUid = function(uid, opts, callback) { }); }; -MongoDB.prototype.getUserByName = function(name, opts, callback) { +MongoDB.prototype.getUserByName = function (name, opts, callback) { var User = require('./user'); if (typeof opts === 'function') { callback = opts; opts = {}; } - - dbQueue(function(){ - userscol.findOne({un: name}, {_id: 0}, function(err, userobj) { - if(err || !userobj){ + + dbQueue(function () { + userscol.findOne({ + un: name + }, { + _id: 0 + }, function (err, userobj) { + if (err || !userobj) { if (callback) callback('UserNotFound'); return; } - + var user = new User(); - - user.login(userobj.email, userobj, opts, function() { + + user.login(userobj.email, userobj, opts, function () { if (callback) callback(null, user); }); }); }); }; -MongoDB.prototype.userEmailExists = function(key, callback) { - dbQueue(function(){ - userscol.findOne({email: key}, {_id: 0}, function(err, data) { +MongoDB.prototype.userEmailExists = function (key, callback) { + dbQueue(function () { + userscol.findOne({ + email: key + }, { + _id: 0 + }, function (err, data) { if (callback) callback(err, data ? true : false); }); }); }; -//ChatDB -MongoDB.prototype.logChat = function(uid, msg, special, callback) { - dbQueue(function(){ - getNextSequence('chat', 'CIDCOUNTER', function(currentCID) { - chatcol.insert({_id: currentCID, uid: uid, msg: msg, special: special}, function(error, data) { - if (callback) callback(error, currentCID); - }); +// ChatDB +MongoDB.prototype.logChat = function (uid, msg, special, callback) { + dbQueue(function () { + getNextSequence('chat', 'CIDCOUNTER', function (currentCID) { + chatcol.insert({ + _id: currentCID, + uid, + msg, + special + }, function (error, data) { + if (callback) callback(error, currentCID); + }); }); }); }; -//PmDB -MongoDB.prototype.logPM = function(from, to, msg, callback) { - dbQueue(function(){ - getNextSequence('pms', 'PMIDCOUNTER', function(currentCID) { - pmscol.insert({_id: currentCID, msg: msg, from: from, to: to, time: new Date(), unread: true }, function(error, data) { - if (error) log.error("Error logging chat message"); - if (callback) callback(error, currentCID); - }); +// PmDB +MongoDB.prototype.logPM = function (from, to, msg, callback) { + dbQueue(function () { + getNextSequence('pms', 'PMIDCOUNTER', function (currentCID) { + pmscol.insert({ + _id: currentCID, + msg, + from, + to, + time: new Date(), + unread: true + }, function (error, data) { + if (error) log.error('Error logging chat message'); + if (callback) callback(error, currentCID); + }); }); }); }; -MongoDB.prototype.getConversation = function(from, to, callback) { - dbQueue(function(){ - pmscol.find({ $or: [ {from: from, to: to}, {from: to, to: from}] }, {_id: 0}).toArray(function(err, data) { - if(err){ +MongoDB.prototype.getConversation = function (from, to, callback) { + dbQueue(function () { + pmscol.find({ + $or: [{ + from, + to + }, { + from: to, + to: from + }] + }, { + _id: 0 + }).toArray(function (err, data) { + if (err) { callback(err); } else { var out = []; - for(var key in data){ - out.push({message:data[key].msg,time:data[key].time,from:data[key].from}); + for (var key in data) { + out.push({ + message: data[key].msg, + time: data[key].time, + from: data[key].from + }); } callback(null, out); } @@ -723,19 +846,27 @@ MongoDB.prototype.getConversation = function(from, to, callback) { }); }; -MongoDB.prototype.getConversations = function(uid, callback) { +MongoDB.prototype.getConversations = function (uid, callback) { var that = this; - - dbQueue(function(){ - pmscol.find({ $or: [ {from: uid}, {to: uid}] }, {_id: 0}).toArray(function(err, data) { - if(err){ + + dbQueue(function () { + pmscol.find({ + $or: [{ + from: uid + }, { + to: uid + }] + }, { + _id: 0 + }).toArray(function (err, data) { + if (err) { callback(err); } else { var out = {}; var uids = []; for (var key in data) { var otherUid = data[key].to == uid ? data[key].from : data[key].to; - + if (out[otherUid] === undefined) { uids.push(otherUid); out[otherUid] = { @@ -744,14 +875,18 @@ MongoDB.prototype.getConversations = function(uid, callback) { unread: 0 }; } - out[otherUid].messages.push({ message: data[key].msg, time: data[key].time, from: data[key].from }); - + out[otherUid].messages.push({ + message: data[key].msg, + time: data[key].time, + from: data[key].from + }); + if (data[key].unread && data[key].from != uid) out[otherUid].unread++; } - + if (uids.length > 0) { - that.getUserByUid(uids, function(err, result){ + that.getUserByUid(uids, function (err, result) { if (err) { callback(err); } else { @@ -763,8 +898,7 @@ MongoDB.prototype.getConversations = function(uid, callback) { callback(null, out); } }); - } - else { + } else { callback(null, out); } } @@ -772,30 +906,51 @@ MongoDB.prototype.getConversations = function(uid, callback) { }); }; -MongoDB.prototype.markConversationRead = function(uid, uid2, time) { - dbQueue(function(){ - pmscol.updateMany({to: uid, from: uid2, time: {$lt: new Date(time)}}, {$set: {unread: false}}, function(){}); +MongoDB.prototype.markConversationRead = function (uid, uid2, time) { + dbQueue(function () { + pmscol.updateMany({ + to: uid, + from: uid2, + time: { + $lt: new Date(time) + } + }, { + $set: { + unread: false + } + }, function () {}); }); }; -//IpDB -MongoDB.prototype.logIp = function(address, uid) { - dbQueue(function(){ +// IpDB +MongoDB.prototype.logIp = function (address, uid) { + dbQueue(function () { ipcol.insert({ - uid: uid, - address: address, + uid, + address, time: new Date() }); }); }; -MongoDB.prototype.getIpHistory = function(uid, callback) { - dbQueue(function(){ - ipcol.find({uid: uid}, {_id: 0, uid: 0}).toArray(function(err, data) { - if(err) +MongoDB.prototype.getIpHistory = function (uid, callback) { + dbQueue(function () { + ipcol.find({ + uid + }, { + _id: 0, + uid: 0 + }).toArray(function (err, data) { + if (err) callback(err); else - callback(null, data.sort(function(a, b){ return a.address > b.address; }).reverse().filter(function(e, i, a){ return i == 0 || a[i - 1].address != e.address; }).sort(function(a, b){ return a.time < b.time; })); + callback(null, data.sort(function (a, b) { + return a.address > b.address; + }).reverse().filter(function (e, i, a) { + return i == 0 || a[i - 1].address != e.address; + }).sort(function (a, b) { + return a.time < b.time; + })); }); }); }; diff --git a/socketserver/db_mysql.js b/socketserver/db_mysql.js index 244d095..d1727fe 100644 --- a/socketserver/db_mysql.js +++ b/socketserver/db_mysql.js @@ -1,14 +1,14 @@ //Modules -var mysql = require('mysql'); -var util = require('util'); -var _ = require('underscore'); -var log = new(require('basic-logger'))({ +const mysql = require('mysql'); +const util = require('util'); +const _ = require('underscore'); +const log = new(require('basic-logger'))({ showTimestamp: true, prefix: "MysqlDB" }); +const nconf = require('nconf'); //Files -var config = require('../serverconfig.js'); var Hash = require('./hash'); var Mailer = require('./mailer'); var DBUtils = require('./database_util'); @@ -21,10 +21,10 @@ var MysqlDB = function(){ var that = this; var mysqlConfig = { - host: config.db.mysqlHost, - user: config.db.mysqlUser, - password: config.db.mysqlPassword, - database: config.db.mysqlDatabase, + host: nconf.get('db:mysqlHost'), + user: nconf.get('db:mysqlUser'), + password: nconf.get('db:mysqlPassword'), + database: nconf.get('db:mysqlDatabase'), charset: "UTF8_GENERAL_CI", multipleStatements: true, connectionLimit: 1, @@ -443,7 +443,7 @@ MysqlDB.prototype.createToken = function(email) { }; MysqlDB.prototype.isTokenValid = function(tok, callback) { - this.execute("SELECT `token`, `email` FROM `tokens` WHERE ? AND DATEDIFF(NOW(), `created`) < ?;", [{ token: tok, }, config.loginExpire || 365], function(err, res) { + this.execute("SELECT `token`, `email` FROM `tokens` WHERE ? AND DATEDIFF(NOW(), `created`) < ?;", [{ token: tok, }, nconf.get('loginExpire') || 365], function(err, res) { if (err || res.length == 0) { callback('InvalidToken'); return; @@ -550,7 +550,7 @@ MysqlDB.prototype.createUser = function(obj, callback) { user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (config.room.email.confirmation) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:email:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); var updatedUserObj = user.makeDbObj(); var tok = that.createToken(inData.email); @@ -564,7 +564,7 @@ MysqlDB.prototype.createUser = function(obj, callback) { } //Send confirmation email - if (config.room.email.confirmation) { + if (nconf.get('room:email:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, diff --git a/socketserver/djqueue.js b/socketserver/djqueue.js index 97182f2..3c18973 100644 --- a/socketserver/djqueue.js +++ b/socketserver/djqueue.js @@ -1,5 +1,5 @@ var Roles = require('./role'); -var config = require('../serverconfig'); +const nconf = require('nconf'); var defaultVoteObj = function(){ return { @@ -30,9 +30,9 @@ function djqueue(room){ this.currentsong = null; this.songstart = null; this.lasttimer = null; - this.limit = config.room.queue.limit; - this.cycle = config.room.queue.cycle; - this.lock = config.room.queue.lock; + this.limit = nconf.get('room:queue:limit'); + this.cycle = nconf.get('room:queue:cycle'); + this.lock = nconf.get('room:queue:lock'); this.votes = new defaultVoteObj; } diff --git a/socketserver/mailer.js b/socketserver/mailer.js index 914488d..c5e3e1f 100644 --- a/socketserver/mailer.js +++ b/socketserver/mailer.js @@ -1,13 +1,13 @@ var NM = require('nodemailer'); var util = require('util'); -var config = require('../serverconfig'); var xoauth2 = require('xoauth2'); var fs = require('fs'); +const nconf = require('nconf'); function Mailer(){ - //Check if we need to authorize against email server - if(config.room.allowrecovery || config.room.email.confirmation){ - var opts = config.room.email.options; + // Check if we need to authorize against email server + if (nconf.get('room:allowrecovery') || nconf.get('room:email:confirmation')) { + const opts = nconf.get('room:email:options'); this.trans = NM.createTransport(((opts || {}).auth || {}).xoauth2 ? util._extend(opts, { auth: { xoauth2: xoauth2.createXOAuth2Generator(opts.auth.xoauth2), @@ -32,10 +32,10 @@ Mailer.prototype.makeEmailObj = function(type, receiver, opts){ //Return email options return { - from: config.room.email.sender, - to: receiver, - subject: type.subject, - html: type.body, + from: nconf.get('room:email:sender'), + to: receiver, + subject: type.subject, + html: type.body, }; }; diff --git a/socketserver/role.js b/socketserver/role.js index 47207af..e7805ac 100644 --- a/socketserver/role.js +++ b/socketserver/role.js @@ -1,5 +1,7 @@ -var config = require('../serverconfig.js'); -var roles = config.roles; +// eslint-disable-next-line +'use strict'; +const nconf = require('nconf'); +var roles = nconf.get('roles'); // Caching the value so we don't have to loop through something every login var roleOrder = null; @@ -53,20 +55,21 @@ Role.prototype.checkCanGrant = function(inRole, inPerm){ return false; }; -Role.prototype.makeClientObj = function(){ +Role.prototype.makeClientObj = function () { return roles; }; -Role.prototype.getOrder = function(){ +Role.prototype.getOrder = function () { if (roleOrder) return roleOrder; - - if (config.roleOrder && Array.isArray(config.roleOrder)){ + + let roleOrderTemp = nconf.get('roleOrder'); + if (roleOrderTemp && Array.isArray(roleOrderTemp)){ for (var i in roles){ - if (config.roleOrder.indexOf(i) == -1) config.roleOrder.push(i); + if (roleOrderTemp.indexOf(i) == -1) roleOrderTemp.push(i); } - - roleOrder = config.roleOrder; - return config.roleOrder; + + roleOrder = roleOrderTemp; + return roleOrderTemp; } var temp = []; @@ -82,8 +85,8 @@ Role.prototype.getOrder = function(){ Role.prototype.getStaffRoles = function(){ if(staffRoles) return staffRoles; - if (config.staffRoles && Array.isArray(config.staffRoles)) { - staffRoles = config.staffRoles; + if (nconf.get('staffRoles') && Array.isArray(nconf.get('staffRoles'))) { + staffRoles = nconf.get('staffRoles'); return staffRoles; } return []; @@ -91,4 +94,4 @@ Role.prototype.getStaffRoles = function(){ -module.exports = new Role(); \ No newline at end of file +module.exports = new Role(); diff --git a/socketserver/room.js b/socketserver/room.js index 07b28ae..0c80083 100644 --- a/socketserver/room.js +++ b/socketserver/room.js @@ -5,8 +5,8 @@ var http = require('http'); var log = new (require('basic-logger'))({showTimestamp: true, prefix: "Room"}); var DJQueue = require('./djqueue.js'); var Roles = require('./role'); -var config = require('../serverconfig'); var DB = require('./database'); +const nconf = require('nconf'); var defaultDBObj = function(){ return { @@ -74,7 +74,7 @@ Room.prototype.getRoomMeta = function(){ }; Room.prototype.makeOwner = function(){ - if (!config.room.ownerEmail) return; + if (!nconf.get('room:ownerEmail')) return; var that = this; @@ -523,7 +523,7 @@ Room.prototype.sendMessage = function( sock, message, ext, specdata, callback ){ time: Date.now(), cid: cid, }); - if(that.lastChat.length > config.room.lastmsglimit) that.lastChat.shift(); + if(that.lastChat.length > nconf.get('room:lastmsglimit')) that.lastChat.shift(); } callback(cid); @@ -608,13 +608,13 @@ Room.prototype.getUsersObj = function(){ }; Room.prototype.getHistoryObj = function() { - return this.data.history.slice(-config.room.history.limit_send).reverse(); + return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse(); }; Room.prototype.addToHistory = function(historyObj) { //Limit history - if(config.room.history.limit_save !== 0) - while(this.data.history.length >= config.room.history.limit_save) { + if(nconf.get('room:history:limit_save') !== 0) + while (this.data.history.length >= nconf.get('room:history:limit_save')) { this.data.history.shift(); } @@ -624,12 +624,12 @@ Room.prototype.addToHistory = function(historyObj) { }; Room.prototype.updateLobbyServer = function(song, dj, callback) { - if (!config.apis.musiqpad.sendLobbyStats) { + if (!nconf.get('apis:musiqpad:sendLobbyStats')) { if (callback) callback(); return; } - else if (!config.apis.musiqpad.key || config.apis.musiqpad.key == "") { - throw "A musiqpad key must be defined in the config for updating the lobby server."; + else if (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') == "") { + console.log("A musiqpad key must be defined in the config for updating the lobby server."); return; } var postData = { @@ -645,7 +645,7 @@ Room.prototype.updateLobbyServer = function(song, dj, callback) { method: 'POST', headers: { 'Content-Type': 'application/json', - 'apikey': config.apis.musiqpad.key + 'apikey': nconf.get('apis:musiqpad:key') } }; try { diff --git a/socketserver/socketserver.js b/socketserver/socketserver.js index d737e0c..ff1fcd5 100644 --- a/socketserver/socketserver.js +++ b/socketserver/socketserver.js @@ -1,3 +1,5 @@ +// eslint-disable-next-line +'use strict'; //Modules var ws = require('ws'); var http = require('http'); @@ -8,9 +10,10 @@ var util = require('util'); var extend = require('extend'); var updateNotifier = require('update-notifier'); var _ = require('underscore'); +const fs = require('fs-extra'); +const nconf = require('nconf'); //Files -var config = require('../serverconfig'); var DB = require("./database"); var Room = require('./room'); var User = require('./user'); @@ -195,23 +198,27 @@ var SocketServer = function(server){ if (server){ settings.server = server; }else{ - var port = config.socketServer.port || undefined; - var ip = config.socketServer.host || undefined; + var port = nconf.get('socketServer:port') || undefined; + var ip = nconf.get('socketServer:host') || undefined; - if (config.certificate && config.certificate.key && config.certificate.cert){ - settings.server = https.createServer(config.certificate).listen(port,ip); - }else{ + if (nconf.get('useSSL') && nconf.get('certificate') && nconf.get('certificate:key') && nconf.get('certificate:cert')) { + let certificates = { + key: fs.readFileSync(nconf.get('certificate:key')), + cert: fs.readFileSync(nconf.get('certificate:cert')), + } + settings.server = https.createServer(certificates).listen(port, ip); + } else { settings.server = http.createServer().listen(port,ip); } } this.wss = new WebSocketServer(settings); - log.info('Socket server listening on port ' + (config.socketServer.port || config.webServer.port)); + log.info('Socket server listening on port ' + (nconf.get('socketServer:port') || nconf.get('webServer:port'))); // this.wss = new WebSocketServer({ port: config.socketServer.port }); // log.info('Socket server listening on port ' + config.socketServer.port); - this.room = new Room(this, config.room); + this.room = new Room(this, nconf.get('room')); // Keepalive packets. This.... is messy. setInterval( function(){ @@ -361,7 +368,7 @@ var SocketServer = function(server){ } else if(socket.room && that.room.isUserRestricted(socket.user.uid, 'BAN')){ returnObj.data = { error: 'UserBanned' }; - } else if((Date.now() - socket.user.created) <= config.room.signupcd){ + } else if((Date.now() - socket.user.created) <= nconf.get('room:signupcd')){ returnObj.data = { error: 'UserOnCooldown' }; } else if(socket.user.confirmation){ @@ -401,7 +408,7 @@ var SocketServer = function(server){ } */ //Check if recovery is enabled - if (!(config.room.allowrecovery)){ + if (!(nconf.get('room:allowrecovery'))){ returnObj.data = { error: 'RecoveryDisabled' }; @@ -664,16 +671,16 @@ var SocketServer = function(server){ votes: that.room.queue.makeVoteObj(), vote: that.room.queue.getUserVote( socket ), }, - historylimit: config.room.history.limit_send, + historylimit: nconf.get('room:history:limit_send'), roles: Roles.makeClientObj(), roleOrder: Roles.getOrder(), staffRoles: Roles.getStaffRoles(), - lastChat: ((!socket.user && !config.room.guestCanSeeChat) || (that.room.isUserRestricted((socket.user || {}).uid, 'BAN') && !config.room.bannedCanSeeChat)) ? [] : that.room.makePrevChatObj(), + lastChat: ((!socket.user && !nconf.get('room:guestCanSeeChat')) || (that.room.isUserRestricted((socket.user || {}).uid, 'BAN') && !nconf.get('room:bannedCanSeeChat'))) ? [] : that.room.makePrevChatObj(), time: new Date().getTime(), - captchakey: config.apis.reCaptcha.key, - allowemojis: config.room.allowemojis, - description: config.room.description, - recaptcha: config.room.recaptcha, + captchakey: nconf.get('apis:reCaptcha:key'), + allowemojis: nconf.get('room:allowemojis'), + description: nconf.get('room:description'), + recaptcha: nconf.get('room:recaptcha'), }; socket.sendJSON(returnObj); @@ -1380,12 +1387,12 @@ var SocketServer = function(server){ if(data.type == 'login'){ DB.loginUser(data.data, callback); } else { - if(config.room.recaptcha){ + if (nconf.get('room:recaptcha')) { request.post( 'https://www.google.com/recaptcha/api/siteverify', { form: { - secret: config.apis.reCaptcha.secret, + secret: nconf.get('apis:reCaptcha:secret'), response: data.data.captcha, remoteip: socket.upgradeReq.connection.remoteAddress, } diff --git a/start.js b/start.js index 1e80aa6..3b7357a 100644 --- a/start.js +++ b/start.js @@ -1,40 +1,28 @@ // eslint-disable-next-line 'use strict'; -const fs = require('fs-extra'); -const SocketServer = require('./socketserver/socketserver'); -const path = require('path'); -const webserver = require('./webserver/app'); +// NCONF const nconf = require('nconf'); +const fs = require('fs-extra'); const hjson = require('hjson'); -const log = new(require('basic-logger'))({ - showTimestamp: true, - prefix: 'SocketServer', -}); -let server; + const hjsonWrapper = { parse: (text) => hjson.parse(text, { keepWsc: true, }), stringify: (text) => hjson.stringify(text, { keepWsc: true, quotes: 'always', bracesSameLine: true }), }; - -function fileExistsSync(path) { - let exists = false; - try { - exists = fs.statSync(path); - } catch (err) { - exists = false; - } - return !!exists; -} - if (!fileExistsSync('config.hjson')) { fs.copySync('config.example.hjson', 'config.hjson'); } nconf.argv().env().file({ file: 'config.hjson', format: hjsonWrapper }); -nconf.set('hostWebserver', true) -nconf.save(function (err) { - console.log(err); -}); +// Modules +const SocketServer = require('./socketserver/socketserver'); +const path = require('path'); +const webserver = require('./webserver/app'); +const log = new(require('basic-logger'))({ + showTimestamp: true, + prefix: 'SocketServer', +}); +let server; const webConfig = `// THIS IS AN AUTOMATICALLY GENERATED FILE\n\nvar config=JSON.parse('${JSON.stringify({ useSSL: nconf.get('useSSL'), @@ -79,3 +67,13 @@ if (process.argv[2] === '--daemon') { fs.writeFile(`${__dirname}/pidfile`, process.pid); } + +function fileExistsSync(path) { + let exists = false; + try { + exists = fs.statSync(path); + } catch (err) { + exists = false; + } + return !!exists; +} diff --git a/webserver/app.js b/webserver/app.js index 3fcfb52..9cf49fd 100644 --- a/webserver/app.js +++ b/webserver/app.js @@ -4,17 +4,22 @@ var path = require('path'); var http = require('http'); var https = require('https'); var fs = require('fs'); -var config = require('../serverconfig.js'); +const nconf = require('nconf'); var app = express(); var server = null; var server2 = null; var socketServer = null; -if (config.certificate && config.certificate.key && config.certificate.cert){ - server = https.createServer(config.certificate, app); - if(config.webServer.redirectHTTP && config.webServer.redirectPort != '') +if (nconf.get('useSSL') && nconf.get('certificate') && nconf.get('certificate:key') && nconf.get('certificate:cert')) { + const certificate = { + key: fs.readFileSync(nconf.get('certificate:key')), + cert: fs.readFileSync(nconf.get('certificate:cert')), + }; + server = https.createServer(certificate, app); + if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { server2 = http.createServer(app); + } } else { server = http.createServer(app); @@ -22,10 +27,10 @@ else { app.use(compression()); -if(config.webServer.redirectHTTP) +if(nconf.get('webServer:redirectHTTP')) app.use(function(req, res, next) { if(!req.secure) { - return res.redirect(['https://', req.hostname, ":", config.webServer.port || process.env.PORT, req.url].join('')); + return res.redirect(['https://', req.hostname, ":", nconf.get('webServer:port') || process.env.PORT, req.url].join('')); } next(); }); @@ -38,8 +43,8 @@ app.get('/config', function(req, res) { }); app.get('/api/room', function(req, res) { var roomInfo = { - "slug": config.room.slug, - "name": config.room.name, + "slug": nconf.get('room:slug'), + "name": nconf.get('room:name'), "people": null, "queue": null, "media": null, @@ -47,13 +52,13 @@ app.get('/api/room', function(req, res) { res.send(roomInfo); }); -server.listen(config.webServer.port || process.env.PORT, config.webServer.address || process.env.IP, function(){ +server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, function(){ var addr = server.address(); console.log("Webserver listening at", addr.address + ":" + addr.port); }); if(server2 != null){ - server2.listen(config.webServer.redirectPort || 80, config.webServer.address || process.env.IP, function(){ + server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, function(){ var addr2 = server2.address(); console.log("HTTP Webserver listening at", addr2.address + ":" + addr2.port); }); From b7de1a762de1c496f1ac18636c53b17700384435 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 11 Jul 2016 13:30:12 +0200 Subject: [PATCH 04/15] Added errors to log.txt Signed-off-by: Henry Gressmann --- mqp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mqp.js b/mqp.js index 714934a..8bdeb7e 100644 --- a/mqp.js +++ b/mqp.js @@ -46,6 +46,7 @@ switch (process.argv[2]) { // Spawn a new musiqpad daemon process, might need some more settings but I'm waiting for the new config storage for that. daemon.daemon(`${__dirname}/start.js`, '--daemon', { stdout: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), + stderr: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), }); } }); @@ -69,8 +70,8 @@ switch (process.argv[2]) { console.log('\nRestarting musiqpad'); daemon.daemon(`${__dirname}/start.js`, '--daemon', { stdout: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), + stderr: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'), }); - } else { console.log('musiqpad could not be restarted, as a running instance could not be found.'); } From 51c8e6913d8591ee3112e22067a6a6587f357526 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 11 Jul 2016 14:28:14 +0200 Subject: [PATCH 05/15] Fixes #103, #102 & #51 --- README.md | 10 +- config.example.hjson | 160 +++++++------- package.json | 1 + server-package.js | 10 +- serverconfig.example.js | 418 ------------------------------------ webserver/app.js | 82 ++++--- webserver/public/index.html | 18 +- 7 files changed, 167 insertions(+), 532 deletions(-) delete mode 100644 serverconfig.example.js diff --git a/README.md b/README.md index 84e1e48..0a21975 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ The base for creating a self-hosted pad. 2. Download the [latest stable version](https://github.com/musiqpad/mqp-server/releases/latest) 3. Unzip it in the location you want to install 4. Open a terminal and `npm install --production` it -5. Copy the `serverconfig.example.js` to create the file `serverconfig.js` -6. Start the server by running `npm start` -7. If everything went well, there should be no error messages. +5. Start the server by running `npm start` +6. If everything went well, there should be no error messages! + +To change the settings, edit the config.hjson file! If you want to start musiqpad using an application manager like forever, start the app.js file. To see server logs, run `npm run log` You can also download the latest pre-release [here](https://github.com/musiqpad/mqp-server/releases) (rc = release candidate, exp = experimental) @@ -57,7 +58,8 @@ Params: debug: false, stream: false } - } + }, + config: fs.readFileSync('config.hjson'), // example config: config.example.hjson } ``` diff --git a/config.example.hjson b/config.example.hjson index d35b084..52fac31 100644 --- a/config.example.hjson +++ b/config.example.hjson @@ -60,6 +60,18 @@

Pad Description

Here you can put anything you want in HTML! ''' + tags: { // Tags for Google & co + keywords: "musiqpad" + description: "" + image: "https://cdn.musiqpad.com/img/icon-256.png" // Image on twitter/facebook/slack/... + twitter: "@musiqpad" + description: // A one to two sentence description for search engines & co + ''' + + ''' + themeColor: "" // a hex color for the theme on chrome for android + favicon: "/pads/lib/img/icon.png" + } } apis: { YT: { @@ -154,7 +166,6 @@ "bot" ] - /* @@ -175,14 +186,14 @@ */ // Defines roles and permissions - - owner: { // REQUIRED ROLE - title: "Owner" - showtitle: true - style: { - color: "#F46B40" - } - permissions: [ + roles: { + owner: { // REQUIRED ROLE + title: "Owner" + showtitle: true + style: { + color: "#F46B40" + } + permissions: [ "djqueue.join" "djqueue.joinlocked" "djqueue.leave" @@ -214,23 +225,23 @@ "room.whois" "room.whois.iphistory" "server.checkForUpdates" - ] - canGrantRoles: [ + ] + canGrantRoles: [ "dev" "coowner" "supervisor" "bot" "regular" "default" - ] - } - dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS - title: "Dev" - showtitle: true - style: { - color: "#A77DC2" + ] } - permissions: [ + dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS + title: "Dev" + showtitle: true + style: { + color: "#A77DC2" + } + permissions: [ "djqueue.join" "djqueue.joinlocked" "djqueue.leave" @@ -260,24 +271,24 @@ "room.restrict.mute_silent" "room.ratelimit.bypass" "room.whois" - ] - canGrantRoles: [ + ] + canGrantRoles: [ "dev" "coowner" "supervisor" "bot" "regular" "default" - ] - mention: "devs" - } - coowner: { - title: "Co-owner" - showtitle: true - style: { - color: "#89BE6C" + ] + mention: "devs" } - permissions: [ + coowner: { + title: "Co-owner" + showtitle: true + style: { + color: "#89BE6C" + } + permissions: [ "djqueue.join" "djqueue.joinlocked" "djqueue.leave" @@ -308,21 +319,21 @@ "room.ratelimit.bypass" "room.whois" "room.whois.iphistory" - ] - canGrantRoles: [ + ] + canGrantRoles: [ "supervisor" "bot" "regular" "default" - ] - } - supervisor: { - title: "Supervisor" - showtitle: true - style: { - color: "#009CDD" + ] } - permissions: [ + supervisor: { + title: "Supervisor" + showtitle: true + style: { + color: "#009CDD" + } + permissions: [ "djqueue.join" "djqueue.joinlocked" "djqueue.leave" @@ -350,20 +361,20 @@ "room.restrict.mute_silent" "room.ratelimit.bypass" "room.whois" - ] - canGrantRoles: [ + ] + canGrantRoles: [ "regular" "default" - ] - } - bot: { - title: "Bot" - showtitle: true - badge: "android" - style: { - color: "#964B74" + ] } - permissions: [ + bot: { + title: "Bot" + showtitle: true + badge: "android" + style: { + color: "#964B74" + } + permissions: [ "djqueue.skip.other" "djqueue.lock" "djqueue.cycle" @@ -375,17 +386,17 @@ "room.restrict.mute" "room.restrict.mute_silent" "room.ratelimit.bypass" - ] - canGrantRoles: [ - ] - } - regular: { - title: "Regular" - showtitle: false - style: { - color: "#925AFF" + ] + canGrantRoles: [ + ] } - permissions: [ + regular: { + title: "Regular" + showtitle: false + style: { + color: "#925AFF" + } + permissions: [ "djqueue.join" "djqueue.joinlocked" "djqueue.leave" @@ -396,17 +407,17 @@ "playlist.delete" "playlist.rename" "playlist.import" - ] - canGrantRoles: [ - ] - } - default: { // REQUIRED ROLE - title: "Default" - showtitle: false - style: { - color: "#ffffff" + ] + canGrantRoles: [ + ] } - permissions: [ + default: { // REQUIRED ROLE + title: "Default" + showtitle: false + style: { + color: "#ffffff" + } + permissions: [ "djqueue.join" "djqueue.leave" "chat.send" @@ -416,8 +427,9 @@ "playlist.delete" "playlist.rename" "playlist.import" - ] - canGrantRoles: [ - ] + ] + canGrantRoles: [ + ] + } } } \ No newline at end of file diff --git a/package.json b/package.json index f6197d6..20b30e3 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "deasync": "^0.1.4", "download-git-repo": "^0.1.2", "durationjs": "^1.1.1", + "ejs": "^2.4.2", "express": "^4.13.3", "extend": "^3.0.0", "file-tail": "^0.3.0", diff --git a/server-package.js b/server-package.js index 5fb5553..bb5937f 100644 --- a/server-package.js +++ b/server-package.js @@ -5,6 +5,7 @@ var log = new(require('basic-logger'))({ showTimestamp: true, prefix: "ServerContainer" }); +var fs = require('fs'); var extend = require('extend'); @@ -23,8 +24,11 @@ var server = function (params) { } } extend(true, this.settings, params); - + this.start = function() { + if(this.settings.config) { + fs.writeFileSync('./config.hjson', this.settings.config, 'utf8'); + } if (this.settings.forever.enabled) { forever.load(this.settings.forever.options); that.pid = forever.start('./start.js'); @@ -35,11 +39,11 @@ var server = function (params) { }); } }; - + this.stop = function() { stopServer(); }; - + function stopServer() { if (that.settings.forever.enabled) { forever.stop(); diff --git a/serverconfig.example.js b/serverconfig.example.js deleted file mode 100644 index df0d5e5..0000000 --- a/serverconfig.example.js +++ /dev/null @@ -1,418 +0,0 @@ -var fs = require('fs'); -var config = {}; - -// IMPORTANT: In order to be able to launch the musiqpad server, set this to true -config.setup = false; - -/* - Set this flag to false to disable web server hosting or true to enable web server hosting. - This is useful if you want to host static files in another web server such as nginx. - - If you are only hosting the socket and want musiqpad to host the frontend set this to false. -*/ -config.hostWebserver = false; - -config.socketServer = { - host: '', // Host name or IP that the socket server is located at. Leave blank to bind to process IP address - port: '8082', // Leave blank to bind to process PORT -}; - -config.webServer = { - address: '', // Leave blank to bind to process IP address - port: '8080', // Leave blank to bind to process PORT - - redirectHTTP: false, // Set to true if you want HTTP redirect to HTTPS. - redirectPort: '80' // Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). -}; - -config.useSSL = true; - -config.certificate = { -// key: fs.readFileSync('../cert.key'), -// cert: fs.readFileSync('../cert.crt') -}; - -config.room = { - name: 'Pad Name', // This is your pad name. It is shown as a user friendly description on the lounge and tab name. - slug: 'this-is-your-slug', // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. - greet: 'Welcome to musiqpad!', - //bg: null, // Background image file path. Accepts external images. If this is undefined the default background will be used. - maxCon: 0, - ownerEmail: 'pad.owner@self-hosted.com', // This needs to be set, then the server restarted to take effect. - guestCanSeeChat: true, - bannedCanSeeChat: false, - lastmsglimit: 6, // How many messages a user can see after joining. - signupcd: 0, // How many miliseconds the user cannot do certain things after they sign up. - allowemojis: true, - allowrecovery: false, - recaptcha: false, - queue: { - cycle: true, - lock: false, - limit: 50, - }, - history: { - limit_save: 0, - limit_send: 50, - }, - email: { - confirmation: false, // Whether to force user to confirm his email address before he is able to do anything - sender: 'your@email.tld', - /* - description: Email server setup, please refer to https://github.com/nodemailer/nodemailer documention on what the options are, supports xOAuth 2.0 - default: {} - */ - options: {}, - }, - description: '\ -

Pad Description

\ - Here you can put anything you want in HTML!\ - ', -}; - -config.apis = { - YT: { - key: '', // Required api key in order for YouTube search to work. - restrictSearchToMusic: false, - }, - SC: { - key: '', - }, - reCaptcha: { - key: '', - secret: '', - }, - musiqpad: { - key: '', // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge - sendLobbyStats: true, - }, -}; - -// The amount of time users stay logged in for before having to login again in days. -// 0 = login every time; -config.loginExpire = 7; - -// Database config -config.db = { - dbType: 'level', // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB - dbDir: './socketserver/db', // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db - mysqlUser: '', // Only used for MySQL. Database username - mysqlPassword: '', // Only used for MySQL. Database password - mysqlHost: '', // Only used for MySQL. Host address - mysqlDatabase: '', // Only used for MySQL. Database being used - mongoUser: '', // Only used for MongoDB. Database username - mongoPassword: '', // Only used for MongoDB. Database password - mongoHost: '', // Only used for MongoDB. Host address - mongoDatabase: '' // Only used for MongoDB. Database being used -}; - -/* - 'djqueue.join': Ability to join queue - 'djqueue.joinlocked': Ability to join locked queue - 'djqueue.leave': Ability to leave queue - 'djqueue.skip.self': Ability to skip self - 'djqueue.skip.other': Ability to skip others - 'djqueue.lock': Ability to lock/unlock queue - 'djqueue.limit': Ability to change waitlist limit - 'djqueue.cycle': Ability to enable/disable queue cycle - 'djqueue.move': Ability to move, swap, add and remove people in the queue - 'djqueue.playLiveVideos': Ability to play live videos with undefined duration - 'djqueue.lock.bypass': Bypass locked queue - 'djqueue.limit.bypass': Bypass queue limit - 'chat.send': Abilty to send chat messages - 'chat.delete': Ability to delete others' chat messages - 'chat.specialMention': Ability to use @everyone, @guest and @djs as mention - 'chat.broadcast': Ability to send a highlighted broadcast message - 'chat.private': Ability to send PMs - 'chat.staff': Ability to send and receive special staff chat - 'playlist.create': Ability to create playlists - 'playlist.delete': Ability to delete playlists - 'playlist.rename': Ability to rename playlists - 'playlist.import': Ability to import playlists - 'playlist.shuffle': Ability to shuffle playlists - 'room.grantroles': Ability to change user roles (requires canGrantPerms property) - 'room.restrict.ban': Ability to ban and unban users - 'room.restrict.mute': Ability to mute and unmute users - 'room.restrict.mute_silent': Ability to shadow mute and unmute users - 'room.ratelimit.bypass': Will bypass ratelimit - 'room.whois': Possibility to request additional information about a user - 'room.whois.iphistory': Possibility to request all IP addresses that the user logged from since account creation - - NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed - without breaking things, but property name must stay the same. -*/ - -// Defines the order that roles will appear on the user list -// PROPERTY names. NOT title. (case-sensitive) -config.roleOrder = ['dev', 'owner', 'coowner', 'supervisor', 'bot', 'regular', 'default']; - - -// Defines which roles are 'staff' members -// PROPERTY names. NOT title. (case-sensitive) -config.staffRoles = ['dev', 'owner', 'coowner', 'supervisor', 'bot']; - - -/* - -Role Options: - -rolename:{ - title: '', // This is the title that gets displayed on the frontend. - showtitle: true/false, // This is whether or not to display the title on the frontend. - badge: '', // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com - style: {}, // This can be used to set specific styles to the Username of a user with this role. - permissions: [], // A list of permissions a user with this role is allowed to use. - canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. - mention: '' // A custom mention. I.e. 'owner' would mention this group when someone typed @owner. -} - -Below are a list of roles we suggest using. - -*/ - -// Defines roles and permissions -config.roles = { - owner: { // REQUIRED ROLE - title: 'Owner', - showtitle: true, - style: { - 'color': '#F46B40' - }, - permissions: [ - 'djqueue.join', - 'djqueue.joinlocked', - 'djqueue.leave', - 'djqueue.skip.self', - 'djqueue.skip.other', - 'djqueue.lock', - 'djqueue.cycle', - 'djqueue.limit', - 'djqueue.move', - 'djqueue.playLiveVideos', - 'djqueue.limit.bypass', - 'djqueue.lock.bypass', - 'chat.send', - 'chat.private', - 'chat.broadcast', - 'chat.delete', - 'chat.specialMention', - 'chat.staff', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import', - 'playlist.shuffle', - 'room.grantroles', - 'room.restrict.ban', - 'room.restrict.mute', - 'room.restrict.mute_silent', - 'room.ratelimit.bypass', - 'room.whois', - 'room.whois.iphistory', - 'server.checkForUpdates', - ], - canGrantRoles: [ - 'dev', - 'coowner', - 'supervisor', - 'bot', - 'regular', - 'default', - ], - }, - dev: { // OPTIONAL ROLE - FOR MUSIQPAD DEVELOPERS - title: 'Dev', - showtitle: true, - style: { - 'color': '#A77DC2' - }, - permissions: [ - 'djqueue.join', - 'djqueue.joinlocked', - 'djqueue.leave', - 'djqueue.skip.self', - 'djqueue.skip.other', - 'djqueue.lock', - 'djqueue.cycle', - 'djqueue.limit', - 'djqueue.move', - 'djqueue.playLiveVideos', - 'djqueue.limit.bypass', - 'djqueue.lock.bypass', - 'chat.send', - 'chat.private', - 'chat.broadcast', - 'chat.delete', - 'chat.specialMention', - 'chat.staff', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import', - 'playlist.shuffle', - 'room.grantroles', - 'room.restrict.ban', - 'room.restrict.mute', - 'room.restrict.mute_silent', - 'room.ratelimit.bypass', - 'room.whois', - ], - canGrantRoles: [ - 'dev', - 'coowner', - 'supervisor', - 'bot', - 'regular', - 'default' - ], - mention: 'devs', - }, - coowner: { - title: 'Co-owner', - showtitle: true, - style: { - 'color': '#89BE6C' - }, - permissions: [ - 'djqueue.join', - 'djqueue.joinlocked', - 'djqueue.leave', - 'djqueue.skip.self', - 'djqueue.skip.other', - 'djqueue.lock', - 'djqueue.cycle', - 'djqueue.limit', - 'djqueue.move', - 'djqueue.playLiveVideos', - 'djqueue.limit.bypass', - 'djqueue.lock.bypass', - 'chat.send', - 'chat.private', - 'chat.delete', - 'chat.specialMention', - 'chat.broadcast', - 'chat.staff', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import', - 'playlist.shuffle', - 'room.grantroles', - 'room.restrict.ban', - 'room.restrict.mute', - 'room.restrict.mute_silent', - 'room.ratelimit.bypass', - 'room.whois', - 'room.whois.iphistory', - ], - canGrantRoles: [ - 'supervisor', - 'bot', - 'regular', - 'default', - ], - }, - supervisor: { - title: 'Supervisor', - showtitle: true, - style: { - 'color': '#009CDD' - }, - permissions: [ - 'djqueue.join', - 'djqueue.joinlocked', - 'djqueue.leave', - 'djqueue.skip.self', - 'djqueue.skip.other', - 'djqueue.lock', - 'djqueue.cycle', - 'djqueue.move', - 'djqueue.playLiveVideos', - 'djqueue.limit.bypass', - 'djqueue.lock.bypass', - 'chat.send', - 'chat.private', - 'chat.delete', - 'chat.specialMention', - 'chat.staff', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import', - 'playlist.shuffle', - 'room.grantroles', - 'room.restrict.ban', - 'room.restrict.mute', - 'room.restrict.mute_silent', - 'room.ratelimit.bypass', - 'room.whois', - ], - canGrantRoles: [ - 'regular', - 'default' - ], - }, - bot: { - title: 'Bot', - showtitle: true, - badge: 'android', - style: { - 'color': '#964B74' - }, - permissions: [ - 'djqueue.skip.other', - 'djqueue.lock', - 'djqueue.cycle', - 'djqueue.move', - 'chat.send', - 'chat.delete', - 'chat.specialMention', - 'room.restrict.ban', - 'room.restrict.mute', - 'room.restrict.mute_silent', - 'room.ratelimit.bypass', - ], - canGrantRoles: [], - }, - regular: { - title: 'Regular', - showtitle: false, - style: { - 'color': '#925AFF' - }, - permissions: [ - 'djqueue.join', - 'djqueue.joinlocked', - 'djqueue.leave', - 'chat.send', - 'chat.private', - 'djqueue.skip.self', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import', - ], - canGrantRoles: [], - }, - default: { // REQUIRED ROLE - title: 'Default', - showtitle: false, - style: { - 'color': '#ffffff' - }, - permissions: [ - 'djqueue.join', - 'djqueue.leave', - 'chat.send', - 'chat.private', - 'djqueue.skip.self', - 'playlist.create', - 'playlist.delete', - 'playlist.rename', - 'playlist.import' - ], - canGrantRoles: [], - } -}; - -module.exports = config; diff --git a/webserver/app.js b/webserver/app.js index 9cf49fd..0cbe913 100644 --- a/webserver/app.js +++ b/webserver/app.js @@ -1,21 +1,27 @@ -var express = require('express'); -var compression = require('compression'); -var path = require('path'); -var http = require('http'); -var https = require('https'); -var fs = require('fs'); +// eslint-disable-next-line +'use strict'; +const express = require('express'); +const compression = require('compression'); +const path = require('path'); +const http = require('http'); +const https = require('https'); +const fs = require('fs'); const nconf = require('nconf'); +const ejs = require('ejs'); -var app = express(); -var server = null; -var server2 = null; -var socketServer = null; +const app = express(); +let server2 = null; +let server = null; +let socketServer = null; + +/* SSL */ if (nconf.get('useSSL') && nconf.get('certificate') && nconf.get('certificate:key') && nconf.get('certificate:cert')) { const certificate = { key: fs.readFileSync(nconf.get('certificate:key')), cert: fs.readFileSync(nconf.get('certificate:cert')), }; + server = https.createServer(certificate, app); if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { server2 = http.createServer(app); @@ -25,48 +31,64 @@ else { server = http.createServer(app); } +app.set('view engine', 'html'); +app.engine('html', ejs.renderFile); +app.set('views', __dirname + '/public'); app.use(compression()); -if(nconf.get('webServer:redirectHTTP')) - app.use(function(req, res, next) { - if(!req.secure) { - return res.redirect(['https://', req.hostname, ":", nconf.get('webServer:port') || process.env.PORT, req.url].join('')); +if (nconf.get('webServer:redirectHTTP')) { + app.use((req, res, next) => { + if (!req.secure) { + return res.redirect(['https://', req.hostname, ':', nconf.get('webServer:port') || process.env.PORT, req.url].join('')); } next(); }); +} +app.get(['/', '/index.html'], (req, res) => { + res.render('index', { + tags: nconf.get('room:tags'), + room: nconf.get('room'), + }); +}); app.use(express.static(path.resolve(__dirname, 'public'))); app.use('/pads', express.static(path.resolve(__dirname, 'public'))); -app.get('/config', function(req, res) { - res.setHeader("Content-Type", "application/javascript"); +app.get('/config', (req, res) => { + res.setHeader('Content-Type', 'application/javascript'); res.send(fs.readFileSync(__dirname + '/public/lib/js/webconfig.js')); }); -app.get('/api/room', function(req, res) { - var roomInfo = { - "slug": nconf.get('room:slug'), - "name": nconf.get('room:name'), - "people": null, - "queue": null, - "media": null, + +app.get('/api/room', (req, res) => { + const roomInfo = { + slug: nconf.get('room:slug'), + name: nconf.get('room:name'), + people: null, + queue: null, + media: null, }; res.send(roomInfo); }); server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, function(){ - var addr = server.address(); - console.log("Webserver listening at", addr.address + ":" + addr.port); + const addr = server.address(); + console.log('Webserver listening at', addr.address + ':' + addr.port); }); -if(server2 != null){ +if (server2 != null) { server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, function(){ - var addr2 = server2.address(); - console.log("HTTP Webserver listening at", addr2.address + ":" + addr2.port); + const addr2 = server2.address(); + console.log('HTTP Webserver listening at', addr2.address + ':' + addr2.port); }); } -var setSocketServer = function(ss){ +const setSocketServer = function (ss) { socketServer = ss; }; -module.exports = {app: app, server: server, server2: server2, setSocketServer: setSocketServer}; +module.exports = { + app, + server, + server2, + setSocketServer, +}; diff --git a/webserver/public/index.html b/webserver/public/index.html index ef92eef..ce04284 100644 --- a/webserver/public/index.html +++ b/webserver/public/index.html @@ -2,9 +2,21 @@ - - - musiqpad - join us! + + + <% if (tags) { %> + + + + + + + + + <% } else { %> + <% } %> + + <%- room.name -%> From 5cae0b474ea86324007c1310d627d5b43a52ceda Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Wed, 13 Jul 2016 22:55:20 +0200 Subject: [PATCH 06/15] Complete overhaul of emails Signed-off-by: Henry Gressmann --- config.example.hjson | 46 +++++- socketserver/database_util.js | 2 +- socketserver/db_level.js | 6 +- socketserver/db_mongo.js | 6 +- socketserver/db_mysql.js | 6 +- socketserver/mail/Mailer.js | 58 +++++++ socketserver/mail/templates/recovery.html | 182 ++++++++++++++++++++++ socketserver/mail/templates/signup.html | 147 +++++++++++++++++ socketserver/socketserver.js | 4 +- socketserver/templates/recovery.html | 7 - socketserver/templates/signup.html | 4 - 11 files changed, 440 insertions(+), 28 deletions(-) create mode 100644 socketserver/mail/Mailer.js create mode 100644 socketserver/mail/templates/recovery.html create mode 100644 socketserver/mail/templates/signup.html delete mode 100644 socketserver/templates/recovery.html delete mode 100644 socketserver/templates/signup.html diff --git a/config.example.hjson b/config.example.hjson index 52fac31..f75be08 100644 --- a/config.example.hjson +++ b/config.example.hjson @@ -28,7 +28,7 @@ greet: "Welcome to musiqpad!" bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. maxCon: 0 - ownerEmail: "mail@explodingcamera.com" // This needs to be set then the server restarted to take effect. + ownerEmail: "user@domain.tld" // This needs to be set then the server restarted to take effect. guestCanSeeChat: true bannedCanSeeChat: false lastmsglimit: 6// How many messages a user can see after joining. @@ -45,15 +45,49 @@ limit_save: 0 limit_send: 50 } - email: { + mail: { confirmation: false// Whether to force user to confirm his email address before he is able to do anything - sender: "your@email.tld" + sender: "user@domain.tld" // Domain should point to this box when using direct mode + + // DIRECT PROBABLY WON'T WORK BECAUSE YOUR IP DOESN'T HAVE A GOOD REPUTATION! + + transport: "direct" // 'smtp', 'direct' or 'xoauth' + options: {} /* - description: Email server setup please refer to https://github.com/nodemailer/nodemailer documention on what the options are supports xOAuth 2.0 - default: {} - */ + EXAMPLES: + + -- SMTP -- + transport: "smtp" + options: { + service: "gmail" + auth: { + user: "mail@somewebsite.com", + pass: "pass", + } + } + ---------- + + -- XOAUTH2 -- + transport: "xoauth" options: { + service: "gmail" + auth: { + xoauth: { + user: '{username}' + clientId: '{Client ID}' + clientSecret: '{Client Secret}' + refreshToken: '{refresh-token}' + accessToken: '{cached access token} + } + } } + ------------- + + -- DIRECT -- + transport: "direct" + options: {} + ------------- + */ } description: ''' diff --git a/socketserver/database_util.js b/socketserver/database_util.js index 95b2376..c6ef8b1 100644 --- a/socketserver/database_util.js +++ b/socketserver/database_util.js @@ -14,4 +14,4 @@ DBUtils.prototype.validateUsername = function (un) { return /^[a-z0-9_-]{3,20}$/i.test(un); }; -module.exports = new DBUtils(); \ No newline at end of file +module.exports = new DBUtils(); diff --git a/socketserver/db_level.js b/socketserver/db_level.js index ebc28cd..20b65d7 100644 --- a/socketserver/db_level.js +++ b/socketserver/db_level.js @@ -12,7 +12,7 @@ const log = new(require('basic-logger'))({ const nconf = require('nconf'); // Files -const Mailer = require('./mailer'); +var Mailer = require('./mail/Mailer'); const DBUtils = require('./database_util'); // Variables @@ -387,7 +387,7 @@ LevelDB.prototype.createUser = function (obj, callback) { user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (nconf.get('room:email:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); var updatedUserObj = user.makeDbObj(); var tok = that.createToken(inData.email); @@ -399,7 +399,7 @@ LevelDB.prototype.createUser = function (obj, callback) { } // Send confirmation email - if (nconf.get('room:email:confirmation')) { + if (nconf.get('room:mail:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index 7312da9..a9779af 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -10,7 +10,7 @@ const log = new(require('basic-logger'))({ // Files const nconf = require('nconf'); -const Mailer = require('./mailer'); +const Mailer = require('./mail/Mailer'); const DBUtils = require('./database_util'); // Variables @@ -506,7 +506,7 @@ MongoDB.prototype.createUser = function (obj, callback) { user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (nconf.get('room:email:confirmation')) { + if (nconf.get('room:mail:confirmation')) { user.data.confirmation = DBUtils.makePass(Date.now()); } var updatedUserObj = user.makeDbObj(); @@ -522,7 +522,7 @@ MongoDB.prototype.createUser = function (obj, callback) { } // Send confirmation email - if (nconf.get('room:email:confirmation')) { + if (nconf.get('room:mail:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, diff --git a/socketserver/db_mysql.js b/socketserver/db_mysql.js index d1727fe..603f233 100644 --- a/socketserver/db_mysql.js +++ b/socketserver/db_mysql.js @@ -10,7 +10,7 @@ const nconf = require('nconf'); //Files var Hash = require('./hash'); -var Mailer = require('./mailer'); +const Mailer = require('./mail/Mailer'); var DBUtils = require('./database_util'); var Roles = require('./role.js'); @@ -550,7 +550,7 @@ MysqlDB.prototype.createUser = function(obj, callback) { user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); user.data.created = Date.now(); - if (nconf.get('room:email:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); var updatedUserObj = user.makeDbObj(); var tok = that.createToken(inData.email); @@ -564,7 +564,7 @@ MysqlDB.prototype.createUser = function(obj, callback) { } //Send confirmation email - if (nconf.get('room:email:confirmation')) { + if (nconf.get('room:mail:confirmation')) { Mailer.sendEmail('signup', { code: user.data.confirmation, user: inData.un, diff --git a/socketserver/mail/Mailer.js b/socketserver/mail/Mailer.js new file mode 100644 index 0000000..c2dc940 --- /dev/null +++ b/socketserver/mail/Mailer.js @@ -0,0 +1,58 @@ +const nodemailer = require('nodemailer'); +const xoauth2 = require('xoauth2'); +const fs = require('fs-extra'); +const nconf = require('nconf'); +const ejs = require('ejs'); + +var Mailer = function () { + const options = nconf.get('room:mail:options'); + var _this = this; + _this.test = 1; + if (nconf.get('room:allowrecovery') || nconf.get('room:mail:confirmation')) { + switch (nconf.get('room:mail:transport')) { + case 'smtp': { + _this.transporter = nodemailer.createTransport(options); + break; + } + case 'xoauth': { + const xoauth = xoauth2.createXOAuth2Generator({ + user: options.auth.xoauth.user, + clientId: options.auth.xoauth.clientId, + clientSecret: options.auth.xoauth.clientSecret, + refreshToken: options.auth.xoauth.refreshToken, + accessToken: options.auth.xoauth.accessToken + }); + options.auth.xoauth = xoauth; + _this.transporter = nodemailer.createTransport(options); + break; + } + case 'direct': { + const domain = nconf.get('room:mail:sender').split('@')[1]; + options.name = domain; + _this.transporter = nodemailer.createTransport(options); + break; + } + } + } +} + +Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { + const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { + opts, + room: nconf.get('room'), + }); + + const emailObj = { + from: nconf.get('room:mail:sender'), + to: receiver, + subject: type.subject, + html, + }; + + this.transporter.sendMail(emailObj, (error, response) => { + if (error) callback(error); + else callback(null, response); + }); +}; + +module.exports = new Mailer(); diff --git a/socketserver/mail/templates/recovery.html b/socketserver/mail/templates/recovery.html new file mode 100644 index 0000000..e04d60e --- /dev/null +++ b/socketserver/mail/templates/recovery.html @@ -0,0 +1,182 @@ + + + + + + + <%- room.name -%> | Password recovery + + + + + + + + + + +
+
+ + + + + + +
+ + + + + + +
+ + + + + + + +
+ + + + + + +
+
+

+ <%- room.name -%> +

+
+
+
+ + + + + + +
+ + + + + + + +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+

+ Forgot Your Password, <%- opts.user -%>? +

+ + + + + + +
+
+

+ It happens. To reset your password, go to <%- room.name -%>, open the login / signup dialog, click "Forgot password" button + and fill in the following: +


+
+ + + + + + + + + + + + + + + +
+ E-mail + + <%- opts.email -%> +
+ Recovery Code + + <%- opts.code -%> +
+ New Password + + your new password
+
+
+

+ The recovery code will be valid until <%- opts.timeout -%> +

+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/socketserver/mail/templates/signup.html b/socketserver/mail/templates/signup.html new file mode 100644 index 0000000..8ce83ae --- /dev/null +++ b/socketserver/mail/templates/signup.html @@ -0,0 +1,147 @@ + + + + + + + <%- room.name -%> + + + + + + + + + + +
+
+ + + + + + +
+ + + + + + +
+ + + + + + + +
+

+ Welcome to <%- room.name -%>! +

+
+ + + +
+
+
+
+
+ + + + + + +
+ + + + + + +
+ + + + + + + +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+

+ Before you are able to do anything, you are required to confirm your email address.
+ To do so, type the following line in chat:
+
+

+

+ /confirm <%- opts.code -%> +

+
+
+
+ + + + + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/socketserver/socketserver.js b/socketserver/socketserver.js index ff1fcd5..78bd26e 100644 --- a/socketserver/socketserver.js +++ b/socketserver/socketserver.js @@ -17,7 +17,7 @@ const nconf = require('nconf'); var DB = require("./database"); var Room = require('./room'); var User = require('./user'); -var Mailer = require('./mailer'); +var Mailer = require('./mail/Mailer'); var YT = require('./YT'); var Roles = require('./role'); var Hash = require('./hash'); @@ -425,6 +425,7 @@ var SocketServer = function(server){ break; } + console.log("Sending recovery email"); var sendRecovery = function(user){ //Generate new code and send email user.recovery = Hash.md5(Date.now() + '', user.un); @@ -434,6 +435,7 @@ var SocketServer = function(server){ email: data.data.email, timeout: (new Date().addDays(1)).toISOString().replace(/T/, ' ').replace(/\..+/, '') + ' UTC', }, data.data.email, function(err, data){ + console.log("Recovery email send!"); if(err){ returnObj.data = { error: 'EmailAuthIssue', diff --git a/socketserver/templates/recovery.html b/socketserver/templates/recovery.html deleted file mode 100644 index 8d59b91..0000000 --- a/socketserver/templates/recovery.html +++ /dev/null @@ -1,7 +0,0 @@ -There is a pending request to reset your password on musiqpad for your account %%USER%%
-If you did not request this, plese ignore this email
-To reset your password, go to musiqpad, open the login / signup dialog, click "Forgot password" button and fill in the following:
-E-mail: %%EMAIL%%
-Recovery Code: %%CODE%%
-New Password: <your new password>
-The recovery code will be valid until %%TIMEOUT%% \ No newline at end of file diff --git a/socketserver/templates/signup.html b/socketserver/templates/signup.html deleted file mode 100644 index 65c028c..0000000 --- a/socketserver/templates/signup.html +++ /dev/null @@ -1,4 +0,0 @@ -Thanks you for registering on musiqpad, %%USER%%!
-Before you are able to do anything, you are required to confirm your email address.
-To do so, type the following line in chat

-

/confirm %%CODE%%

\ No newline at end of file From 6af9da34a4ea9ab39d6434e8057d7ab4b20770d0 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Wed, 13 Jul 2016 22:59:13 +0200 Subject: [PATCH 07/15] Removed old Mailer file + added email subjects Signed-off-by: Henry Gressmann --- socketserver/mail/Mailer.js | 9 ++++++ socketserver/mailer.js | 59 ------------------------------------- 2 files changed, 9 insertions(+), 59 deletions(-) delete mode 100644 socketserver/mailer.js diff --git a/socketserver/mail/Mailer.js b/socketserver/mail/Mailer.js index c2dc940..ee817b4 100644 --- a/socketserver/mail/Mailer.js +++ b/socketserver/mail/Mailer.js @@ -42,6 +42,15 @@ Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { room: nconf.get('room'), }); + switch(type) { + case 'signup': + type.subject = 'Welcome to musiqpad!'; + break; + case 'recovery': + type.subject = 'Password recovery'; + break; + } + const emailObj = { from: nconf.get('room:mail:sender'), to: receiver, diff --git a/socketserver/mailer.js b/socketserver/mailer.js deleted file mode 100644 index c5e3e1f..0000000 --- a/socketserver/mailer.js +++ /dev/null @@ -1,59 +0,0 @@ -var NM = require('nodemailer'); -var util = require('util'); -var xoauth2 = require('xoauth2'); -var fs = require('fs'); -const nconf = require('nconf'); - -function Mailer(){ - // Check if we need to authorize against email server - if (nconf.get('room:allowrecovery') || nconf.get('room:email:confirmation')) { - const opts = nconf.get('room:email:options'); - this.trans = NM.createTransport(((opts || {}).auth || {}).xoauth2 ? util._extend(opts, { - auth: { - xoauth2: xoauth2.createXOAuth2Generator(opts.auth.xoauth2), - }, - }) : opts) - } -} - -Mailer.prototype.sendEmail = function(type, opts, receiver, callback){ - this.trans.sendMail(this.makeEmailObj(type, receiver, opts), function(error, info){ - if(error) callback(error); - else callback(null, info); - }); -}; - -Mailer.prototype.makeEmailObj = function(type, receiver, opts){ - //Get email type - type = this.getType(type); - - //Replace all variables - type.body = type.body.replace(/%%[A-Z]+%%/g, function(k){ return opts[k.slice(2, -2).toLowerCase()] || k; }); - - //Return email options - return { - from: nconf.get('room:email:sender'), - to: receiver, - subject: type.subject, - html: type.body, - }; -}; - -Mailer.prototype.getType = function(type){ - var returnObj = { - body: fs.readFileSync('socketserver/templates/' + type + '.html', 'utf8'), - }; - - switch(type){ - case 'signup': - returnObj.subject = 'Welcome to musiqpad!'; - break; - case 'recovery': - returnObj.subject = 'Password recovery'; - break; - } - - return returnObj; -} - -module.exports = new Mailer(); \ No newline at end of file From 580b49a52dd4f61efe30893c6a35b6dbb776298a Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Thu, 14 Jul 2016 14:44:10 +0200 Subject: [PATCH 08/15] Fixed subject Signed-off-by: Henry Gressmann --- socketserver/mail/Mailer.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/socketserver/mail/Mailer.js b/socketserver/mail/Mailer.js index ee817b4..857ce83 100644 --- a/socketserver/mail/Mailer.js +++ b/socketserver/mail/Mailer.js @@ -34,7 +34,7 @@ var Mailer = function () { } } } -} +}; Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { @@ -42,19 +42,20 @@ Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { room: nconf.get('room'), }); + var subject; switch(type) { case 'signup': - type.subject = 'Welcome to musiqpad!'; + subject = 'Welcome to musiqpad!'; break; case 'recovery': - type.subject = 'Password recovery'; + subject = 'Password recovery'; break; } const emailObj = { from: nconf.get('room:mail:sender'), to: receiver, - subject: type.subject, + subject, html, }; From 983f8ba5bc561290d9bf23e8d22fae7128ceb3d6 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Thu, 14 Jul 2016 16:38:57 +0200 Subject: [PATCH 09/15] Renamed mailer to fit naming scheme Signed-off-by: Henry Gressmann --- socketserver/mail/{Mailer.js => mailer.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename socketserver/mail/{Mailer.js => mailer.js} (100%) diff --git a/socketserver/mail/Mailer.js b/socketserver/mail/mailer.js similarity index 100% rename from socketserver/mail/Mailer.js rename to socketserver/mail/mailer.js From 1fd13fb0420e46b3191d8be3f349ff1a9a467fc9 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 15 Jul 2016 23:26:15 +0200 Subject: [PATCH 10/15] Changed indent rule to 4 spaces Signed-off-by: Henry Gressmann --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index abb10b1..05d8046 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,7 @@ "no-shadow": 0, "consistent-return": 0, "func-names": 0, - "indent": 0 + "indent": ["error", 4] }, "env": { "browser": true, From 77f8a080fa617ed17db3913b3ba440c7761ee735 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 18 Jul 2016 21:10:48 +0200 Subject: [PATCH 11/15] Added scripts config option + cleaned up code Cleaned up role, mailer + room.js. Also added .editorconfig and updated the eslintrc file. Signed-off-by: Henry Gressmann --- .editorconfig | 15 + .eslintrc.json | 35 +- config.example.hjson | 918 +++++++++++++-------------- socketserver/database_util.js | 6 +- socketserver/db_level.js | 1 - socketserver/db_mongo.js | 1 - socketserver/mail/mailer.js | 118 ++-- socketserver/role.js | 151 +++-- socketserver/room.js | 1105 ++++++++++++++++----------------- socketserver/socketserver.js | 1 - webserver/app.js | 102 +-- webserver/public/index.html | 16 +- 12 files changed, 1246 insertions(+), 1223 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a1e3ce9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*.js] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*] +insert_final_newline = true + +[{package.json,*.yml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 05d8046..624ec21 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,18 +1,21 @@ { - "extends": "airbnb", - "rules": { - "comma-dangle": 0, - "max-len": 0, - "no-console": 0, - "no-param-reassign": 0, - "no-shadow": 0, - "consistent-return": 0, - "func-names": 0, - "indent": ["error", 4] - }, - "env": { - "browser": true, - "node": true, - "jquery": true - } + "extends": "airbnb", + "rules": { + "comma-dangle": 0, + "max-len": 0, + "no-console": 0, + "no-param-reassign": 0, + "no-shadow": 0, + "consistent-return": 0, + "func-names": 0, + "indent": ["error", "tab", { "SwitchCase": 1 }], + "strict": 0, + "guard-for-in": "warn", + "no-restricted-syntax": ["warn", "ForInStatement"] + }, + "env": { + "browser": true, + "node": true, + "jquery": true + } } \ No newline at end of file diff --git a/config.example.hjson b/config.example.hjson index f75be08..0cd6562 100644 --- a/config.example.hjson +++ b/config.example.hjson @@ -1,469 +1,473 @@ { - /* - Set this flag to false to disable web server hosting or true to enable web server hosting. - This is useful if you want to host static files in another web server such as nginx. - - If you are only hosting the socket and want musiqpad to host the frontend set this to false. - */ - hostWebserver: true - socketServer: { - host: "" // Host name or IP that the socket server is located at. Leave blank to bind to process IP address - port: 8082// Leave blank to bind to process PORT - } - webServer: { - address: "" // Leave blank to bind to process IP address. - port: 8080// Leave blank to bind to process PORT. + /* + Set this flag to false to disable web server hosting or true to enable web server hosting. + This is useful if you want to host static files in another web server such as nginx. + + If you are only hosting the socket and want musiqpad to host the frontend set this to false. + */ + hostWebserver: true + socketServer: { + host: "" // Host name or IP that the socket server is located at. Leave blank to bind to process IP address + port: 8082// Leave blank to bind to process PORT + } + webServer: { + address: "" // Leave blank to bind to process IP address. + port: 8080// Leave blank to bind to process PORT. - redirectHTTP: false// Set to true if you want HTTP redirect to HTTPS. - redirectPort: 80// Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). - } - useSSL: false// If you want your pad to be accesible over HTTPS set SSL to true and add the path of your certificates - certificate: { - key: "path-to-key" - cert: "path-to-cert" - } - room: { - name: "Pad Name" // This is your pad name. It is shown as a user friendly description on the lounge and tab name. - slug: "this-is-your-slug" // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. - greet: "Welcome to musiqpad!" - bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. - maxCon: 0 - ownerEmail: "user@domain.tld" // This needs to be set then the server restarted to take effect. - guestCanSeeChat: true - bannedCanSeeChat: false - lastmsglimit: 6// How many messages a user can see after joining. - signupcd: 0// How many miliseconds the user cannot do certain things after they sign up. - allowemojis: true - allowrecovery: false - recaptcha: false - queue: { - cycle: true - lock: false - limit: 50 - } - history: { - limit_save: 0 - limit_send: 50 - } - mail: { - confirmation: false// Whether to force user to confirm his email address before he is able to do anything - sender: "user@domain.tld" // Domain should point to this box when using direct mode - - // DIRECT PROBABLY WON'T WORK BECAUSE YOUR IP DOESN'T HAVE A GOOD REPUTATION! - - transport: "direct" // 'smtp', 'direct' or 'xoauth' - options: {} - /* - EXAMPLES: - - -- SMTP -- - transport: "smtp" - options: { - service: "gmail" - auth: { - user: "mail@somewebsite.com", - pass: "pass", - } - } - ---------- - - -- XOAUTH2 -- - transport: "xoauth" - options: { - service: "gmail" - auth: { - xoauth: { - user: '{username}' - clientId: '{Client ID}' - clientSecret: '{Client Secret}' - refreshToken: '{refresh-token}' - accessToken: '{cached access token} - } - } - } - ------------- - - -- DIRECT -- - transport: "direct" - options: {} - ------------- - */ - } - description: - ''' -

Pad Description

- Here you can put anything you want in HTML! - ''' - tags: { // Tags for Google & co - keywords: "musiqpad" - description: "" - image: "https://cdn.musiqpad.com/img/icon-256.png" // Image on twitter/facebook/slack/... - twitter: "@musiqpad" - description: // A one to two sentence description for search engines & co - ''' - - ''' - themeColor: "" // a hex color for the theme on chrome for android - favicon: "/pads/lib/img/icon.png" - } - } - apis: { - YT: { - key: "" // Required api key in order for YouTube search to work. - restrictSearchToMusic: false - } - SC: { - key: "" - } - reCaptcha: { - key: "" - secret: "" - } - musiqpad: { - key: "" // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge - sendLobbyStats: false - } - } + redirectHTTP: false// Set to true if you want HTTP redirect to HTTPS. + redirectPort: 80// Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). + } + useSSL: false// If you want your pad to be accesible over HTTPS set SSL to true and add the path of your certificates + certificate: { + key: "path-to-key" + cert: "path-to-cert" + } + room: { + name: "Pad Name" // This is your pad name. It is shown as a user friendly description on the lounge and tab name. + slug: "this-is-your-slug" // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. + greet: "Welcome to musiqpad!" + bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. + maxCon: 0 + ownerEmail: "user@domain.tld" // This needs to be set then the server restarted to take effect. + guestCanSeeChat: true + bannedCanSeeChat: false + lastmsglimit: 6// How many messages a user can see after joining. + signupcd: 0// How many miliseconds the user cannot do certain things after they sign up. + allowemojis: true + allowrecovery: false + recaptcha: false + queue: { + cycle: true + lock: false + limit: 50 + } + history: { + limit_save: 0 + limit_send: 50 + } + mail: { + confirmation: false// Whether to force user to confirm his email address before he is able to do anything + sender: "user@domain.tld" // Domain should point to this box when using direct mode + + // DIRECT PROBABLY WON'T WORK BECAUSE YOUR IP DOESN'T HAVE A GOOD REPUTATION! + + transport: "direct" // 'smtp', 'direct' or 'xoauth' + options: {} + /* + EXAMPLES: + + -- SMTP -- + transport: "smtp" + options: { + service: "gmail" + auth: { + user: "mail@somewebsite.com", + pass: "pass", + } + } + ---------- + + -- XOAUTH2 -- + transport: "xoauth" + options: { + service: "gmail" + auth: { + xoauth: { + user: '{username}' + clientId: '{Client ID}' + clientSecret: '{Client Secret}' + refreshToken: '{refresh-token}' + accessToken: '{cached access token} + } + } + } + ------------- + + -- DIRECT -- + transport: "direct" + options: {} + ------------- + */ + } + description: + ''' +

Pad Description

+ Here you can put anything you want in HTML! + ''' + tags: { // Tags for Google & co + keywords: "musiqpad" + description: "" + image: "https://cdn.musiqpad.com/img/icon-256.png" // Image on twitter/facebook/slack/... + twitter: "@musiqpad" + description: // A one to two sentence description for search engines & co + ''' + + ''' + themeColor: "" // a hex color for the theme on chrome for android + favicon: "/pads/lib/img/icon.png" + } + scripts: { // Only if you host the frontend yourself + js: [], // Example: ["https://exapmple.com/musiqpad.js", "lib/js/custom.js"] + css: [] + } + } + apis: { + YT: { + key: "" // Required api key in order for YouTube search to work. + restrictSearchToMusic: false + } + SC: { + key: "" + } + reCaptcha: { + key: "" + secret: "" + } + musiqpad: { + key: "" // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge + sendLobbyStats: false + } + } - // The amount of time users stay logged in for before having to login again in days. - // 0 = login every time - loginExpire: 7 - db: { - dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB - dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db - mysqlUser: "" // Only used for MySQL. Database username - mysqlPassword: "" // Only used for MySQL. Database password - mysqlHost: "" // Only used for MySQL. Host address - mysqlDatabase: "" // Only used for MySQL. Database being used - mongoUser: "" // Only used for MongoDB. Database username - mongoPassword: "" // Only used for MongoDB. Database password - mongoHost: "" // Only used for MongoDB. Host address - mongoDatabase: "" // Only used for MongoDB. Database being used - } - - /* - "djqueue.join": Ability to join queue - "djqueue.joinlocked": Ability to join locked queue - "djqueue.leave": Ability to leave queue - "djqueue.skip.self": Ability to skip self - "djqueue.skip.other": Ability to skip others - "djqueue.lock": Ability to lock/unlock queue - "djqueue.limit": Ability to change waitlist limit - "djqueue.cycle": Ability to enable/disable queue cycle - "djqueue.move": Ability to move, swap, add and remove people in the queue - "djqueue.playLiveVideos": Ability to play live videos with undefined duration - "djqueue.lock.bypass": Bypass locked queue - "djqueue.limit.bypass": Bypass queue limit - "chat.send": Abilty to send chat messages - "chat.delete": Ability to delete others" chat messages - "chat.specialMention": Ability to use @everyone, @guest and @djs as mention - "chat.broadcast": Ability to send a highlighted broadcast message - "chat.private": Ability to send PMs - "chat.staff": Ability to send and receive special staff chat - "playlist.create": Ability to create playlists - "playlist.delete": Ability to delete playlists - "playlist.rename": Ability to rename playlists - "playlist.import": Ability to import playlists - "playlist.shuffle": Ability to shuffle playlists - "room.grantroles": Ability to change user roles (requires canGrantPerms property) - "room.restrict.ban": Ability to ban and unban users - "room.restrict.mute": Ability to mute and unmute users - "room.restrict.mute_silent": Ability to shadow mute and unmute users - "room.ratelimit.bypass": Will bypass ratelimit - "room.whois": Possibility to request additional information about a user - "room.whois.iphistory": Possibility to request all IP addresses that the user logged from since account creation + // The amount of time users stay logged in for before having to login again in days. + // 0 = login every time + loginExpire: 7 + db: { + dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB + dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db + mysqlUser: "" // Only used for MySQL. Database username + mysqlPassword: "" // Only used for MySQL. Database password + mysqlHost: "" // Only used for MySQL. Host address + mysqlDatabase: "" // Only used for MySQL. Database being used + mongoUser: "" // Only used for MongoDB. Database username + mongoPassword: "" // Only used for MongoDB. Database password + mongoHost: "" // Only used for MongoDB. Host address + mongoDatabase: "" // Only used for MongoDB. Database being used + } + + /* + "djqueue.join": Ability to join queue + "djqueue.joinlocked": Ability to join locked queue + "djqueue.leave": Ability to leave queue + "djqueue.skip.self": Ability to skip self + "djqueue.skip.other": Ability to skip others + "djqueue.lock": Ability to lock/unlock queue + "djqueue.limit": Ability to change waitlist limit + "djqueue.cycle": Ability to enable/disable queue cycle + "djqueue.move": Ability to move, swap, add and remove people in the queue + "djqueue.playLiveVideos": Ability to play live videos with undefined duration + "djqueue.lock.bypass": Bypass locked queue + "djqueue.limit.bypass": Bypass queue limit + "chat.send": Abilty to send chat messages + "chat.delete": Ability to delete others" chat messages + "chat.specialMention": Ability to use @everyone, @guest and @djs as mention + "chat.broadcast": Ability to send a highlighted broadcast message + "chat.private": Ability to send PMs + "chat.staff": Ability to send and receive special staff chat + "playlist.create": Ability to create playlists + "playlist.delete": Ability to delete playlists + "playlist.rename": Ability to rename playlists + "playlist.import": Ability to import playlists + "playlist.shuffle": Ability to shuffle playlists + "room.grantroles": Ability to change user roles (requires canGrantPerms property) + "room.restrict.ban": Ability to ban and unban users + "room.restrict.mute": Ability to mute and unmute users + "room.restrict.mute_silent": Ability to shadow mute and unmute users + "room.ratelimit.bypass": Will bypass ratelimit + "room.whois": Possibility to request additional information about a user + "room.whois.iphistory": Possibility to request all IP addresses that the user logged from since account creation - NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed - without breaking things, but property name must stay the same. - */ + NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed + without breaking things, but property name must stay the same. + */ - - // Defines the order that roles will appear on the user list - // PROPERTY names. NOT title. (case-sensitive) - roleOrder: [ - "dev" - "owner" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - - // Defines which roles are "staff" members - // PROPERTY names. NOT title. (case-sensitive) - staffRoles: [ - "dev" - "owner" - "coowner" - "supervisor" - "bot" - ] - + + // Defines the order that roles will appear on the user list + // PROPERTY names. NOT title. (case-sensitive) + roleOrder: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + + // Defines which roles are "staff" members + // PROPERTY names. NOT title. (case-sensitive) + staffRoles: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + ] + - /* + /* - Role Options: + Role Options: - rolename:{ - title: "", // This is the title that gets displayed on the frontend. - showtitle: true/false, // This is whether or not to display the title on the frontend. - badge: "", // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com - style: {}, // This can be used to set specific styles to the Username of a user with this role. - permissions: [], // A list of permissions a user with this role is allowed to use. - canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. - mention: "" // A custom mention. I.e. "owner" would mention this group when someone typed @owner. - } + rolename:{ + title: "", // This is the title that gets displayed on the frontend. + showtitle: true/false, // This is whether or not to display the title on the frontend. + badge: "", // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com + style: {}, // This can be used to set specific styles to the Username of a user with this role. + permissions: [], // A list of permissions a user with this role is allowed to use. + canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. + mention: "" // A custom mention. I.e. "owner" would mention this group when someone typed @owner. + } - Below are a list of roles we suggest using. + Below are a list of roles we suggest using. - */ + */ - // Defines roles and permissions - roles: { - owner: { // REQUIRED ROLE - title: "Owner" - showtitle: true - style: { - color: "#F46B40" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.broadcast" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - "room.whois.iphistory" - "server.checkForUpdates" - ] - canGrantRoles: [ - "dev" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - } - dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS - title: "Dev" - showtitle: true - style: { - color: "#A77DC2" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.broadcast" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - ] - canGrantRoles: [ - "dev" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - mention: "devs" - } - coowner: { - title: "Co-owner" - showtitle: true - style: { - color: "#89BE6C" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.delete" - "chat.specialMention" - "chat.broadcast" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - "room.whois.iphistory" - ] - canGrantRoles: [ - "supervisor" - "bot" - "regular" - "default" - ] - } - supervisor: { - title: "Supervisor" - showtitle: true - style: { - color: "#009CDD" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - ] - canGrantRoles: [ - "regular" - "default" - ] - } - bot: { - title: "Bot" - showtitle: true - badge: "android" - style: { - color: "#964B74" - } - permissions: [ - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.move" - "chat.send" - "chat.delete" - "chat.specialMention" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - ] - canGrantRoles: [ - ] - } - regular: { - title: "Regular" - showtitle: false - style: { - color: "#925AFF" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "chat.send" - "chat.private" - "djqueue.skip.self" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - ] - canGrantRoles: [ - ] - } - default: { // REQUIRED ROLE - title: "Default" - showtitle: false - style: { - color: "#ffffff" - } - permissions: [ - "djqueue.join" - "djqueue.leave" - "chat.send" - "chat.private" - "djqueue.skip.self" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - ] - canGrantRoles: [ - ] - } - } + // Defines roles and permissions + roles: { + owner: { // REQUIRED ROLE + title: "Owner" + showtitle: true + style: { + color: "#F46B40" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + "server.checkForUpdates" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + } + dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS + title: "Dev" + showtitle: true + style: { + color: "#A77DC2" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + mention: "devs" + } + coowner: { + title: "Co-owner" + showtitle: true + style: { + color: "#89BE6C" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.broadcast" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + ] + canGrantRoles: [ + "supervisor" + "bot" + "regular" + "default" + ] + } + supervisor: { + title: "Supervisor" + showtitle: true + style: { + color: "#009CDD" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "regular" + "default" + ] + } + bot: { + title: "Bot" + showtitle: true + badge: "android" + style: { + color: "#964B74" + } + permissions: [ + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "chat.send" + "chat.delete" + "chat.specialMention" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + ] + canGrantRoles: [ + ] + } + regular: { + title: "Regular" + showtitle: false + style: { + color: "#925AFF" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } + default: { // REQUIRED ROLE + title: "Default" + showtitle: false + style: { + color: "#ffffff" + } + permissions: [ + "djqueue.join" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } + } } \ No newline at end of file diff --git a/socketserver/database_util.js b/socketserver/database_util.js index c6ef8b1..e32b9ba 100644 --- a/socketserver/database_util.js +++ b/socketserver/database_util.js @@ -3,15 +3,15 @@ const Hash = require('./hash'); function DBUtils() {} DBUtils.prototype.makePass = function (inPass, salt) { - return Hash.md5(('' + inPass) + (salt || '')).toString(); + return Hash.md5(('' + inPass) + (salt || '')).toString(); }; DBUtils.prototype.validateEmail = function (email) { - return /^.+@.+\..+$/.test(email); + return /^.+@.+\..+$/.test(email); }; DBUtils.prototype.validateUsername = function (un) { - return /^[a-z0-9_-]{3,20}$/i.test(un); + return /^[a-z0-9_-]{3,20}$/i.test(un); }; module.exports = new DBUtils(); diff --git a/socketserver/db_level.js b/socketserver/db_level.js index 20b65d7..f0fc439 100644 --- a/socketserver/db_level.js +++ b/socketserver/db_level.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; // Modules const levelup = require('levelup'); diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index a9779af..96ca9f2 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; // Modules const mongodb = require('mongodb').MongoClient; diff --git a/socketserver/mail/mailer.js b/socketserver/mail/mailer.js index 857ce83..e397714 100644 --- a/socketserver/mail/mailer.js +++ b/socketserver/mail/mailer.js @@ -1,68 +1,76 @@ +'use strict'; const nodemailer = require('nodemailer'); const xoauth2 = require('xoauth2'); const fs = require('fs-extra'); const nconf = require('nconf'); const ejs = require('ejs'); -var Mailer = function () { - const options = nconf.get('room:mail:options'); - var _this = this; - _this.test = 1; - if (nconf.get('room:allowrecovery') || nconf.get('room:mail:confirmation')) { - switch (nconf.get('room:mail:transport')) { - case 'smtp': { - _this.transporter = nodemailer.createTransport(options); - break; - } - case 'xoauth': { - const xoauth = xoauth2.createXOAuth2Generator({ - user: options.auth.xoauth.user, - clientId: options.auth.xoauth.clientId, - clientSecret: options.auth.xoauth.clientSecret, - refreshToken: options.auth.xoauth.refreshToken, - accessToken: options.auth.xoauth.accessToken - }); - options.auth.xoauth = xoauth; - _this.transporter = nodemailer.createTransport(options); - break; - } - case 'direct': { - const domain = nconf.get('room:mail:sender').split('@')[1]; - options.name = domain; - _this.transporter = nodemailer.createTransport(options); - break; - } - } +class Mailer { + constructor() { + const options = nconf.get('room:mail:options'); + const self = this; + + if (nconf.get('room:allowrecovery') || nconf.get('room:mail:confirmation')) { + switch (nconf.get('room:mail:transport')) { + case 'smtp': { + self.transporter = nodemailer.createTransport(options); + break; + } + case 'xoauth': { + const xoauth = xoauth2.createXOAuth2Generator({ + user: options.auth.xoauth.user, + clientId: options.auth.xoauth.clientId, + clientSecret: options.auth.xoauth.clientSecret, + refreshToken: options.auth.xoauth.refreshToken, + accessToken: options.auth.xoauth.accessToken + }); + options.auth.xoauth = xoauth; + self.transporter = nodemailer.createTransport(options); + break; + } + case 'direct': { + const domain = nconf.get('room:mail:sender').split('@')[1]; + options.name = domain; + self.transporter = nodemailer.createTransport(options); + break; + } + default: { + break; + } + } + } } -}; -Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { - const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { - opts, - room: nconf.get('room'), - }); + sendEmail(type, opts, receiver, callback) { + const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { + opts, + room: nconf.get('room'), + }); - var subject; - switch(type) { - case 'signup': - subject = 'Welcome to musiqpad!'; - break; - case 'recovery': - subject = 'Password recovery'; - break; - } + let subject; + switch (type) { + case 'signup': + subject = 'Welcome to musiqpad!'; + break; + case 'recovery': + subject = 'Password recovery'; + break; + default: + break; + } - const emailObj = { - from: nconf.get('room:mail:sender'), - to: receiver, - subject, - html, - }; + const emailObj = { + from: nconf.get('room:mail:sender'), + to: receiver, + subject, + html, + }; - this.transporter.sendMail(emailObj, (error, response) => { - if (error) callback(error); - else callback(null, response); - }); -}; + this.transporter.sendMail(emailObj, (error, response) => { + if (error) callback(error); + else callback(null, response); + }); + } +} module.exports = new Mailer(); diff --git a/socketserver/role.js b/socketserver/role.js index e7805ac..9d0e3dc 100644 --- a/socketserver/role.js +++ b/socketserver/role.js @@ -1,97 +1,94 @@ -// eslint-disable-next-line 'use strict'; const nconf = require('nconf'); -var roles = nconf.get('roles'); +let roles = nconf.get('roles'); +let roleOrder = null; +let staffRoles = null; -// Caching the value so we don't have to loop through something every login -var roleOrder = null; -var staffRoles = null; +class Role { + getRole(inRole) { + if (this.roleExists(inRole)) { + return roles[inRole]; + } -function Role(){ - -} + return roles.default; + } -Role.prototype.getRole = function(inRole){ - if (this.roleExists(inRole)) - return roles[inRole]; - - return roles.default; -}; - -Role.prototype.roleExists = function(inRole){ - if (typeof roles[inRole] !== 'undefined') - return true; - return false; -}; - -Role.prototype.checkPermission = function(inRole, inPerm){ - var role = this.getRole(inRole); - if (role){ - if((typeof inPerm) == 'string') { - return role.permissions.indexOf(inPerm) != -1; - }else{ - for(var i = 0; i < inPerm.length; i++) - if(role.permissions.indexOf(inPerm[i]) == -1) return false; + roleExists(inRole) { + if (typeof roles[inRole] !== 'undefined') { + return true; } - return true; + return false; } - return false; -}; - -Role.prototype.checkCanGrant = function(inRole, inPerm){ - var role = this.getRole(inRole); - - if (role){ - if((typeof inPerm) == 'string') { - inPerm = inPerm.toLowerCase(); - return role.canGrantRoles.indexOf(inPerm) != -1; - }else{ - for(var i = 0; i < inPerm.length; i++) - if(role.canGrantRoles.indexOf(inPerm[i].toLowerCase()) == -1) return false; + + checkPermission(inRole, inPerm) { + const role = this.getRole(inRole); + if (role) { + if ((typeof inPerm) === 'string') { + return role.permissions.indexOf(inPerm) !== -1; + } + for (let i = 0; i < inPerm.length; i++) { + if (role.permissions.indexOf(inPerm[i]) === -1) { + return false; + } + } + return true; } - - return true; + return false; } - return false; -}; -Role.prototype.makeClientObj = function () { - return roles; -}; + checkCanGrant(inRole, inPerm) { + const role = this.getRole(inRole); -Role.prototype.getOrder = function () { - if (roleOrder) return roleOrder; + if (role) { + if ((typeof inPerm) === 'string') { + inPerm = inPerm.toLowerCase(); + return role.canGrantRoles.indexOf(inPerm) !== -1; + } + for (let i = 0; i < inPerm.length; i++) { + if (role.canGrantRoles.indexOf(inPerm[i].toLowerCase()) === -1) return false; + } - let roleOrderTemp = nconf.get('roleOrder'); - if (roleOrderTemp && Array.isArray(roleOrderTemp)){ - for (var i in roles){ - if (roleOrderTemp.indexOf(i) == -1) roleOrderTemp.push(i); + return true; } + return false; + } - roleOrder = roleOrderTemp; - return roleOrderTemp; - } - - var temp = []; - - for (var i in roles){ - temp.push(i); + makeClientObj() { + return roles; } - - roleOrder = temp; - return temp; -}; - -Role.prototype.getStaffRoles = function(){ - if(staffRoles) return staffRoles; - - if (nconf.get('staffRoles') && Array.isArray(nconf.get('staffRoles'))) { - staffRoles = nconf.get('staffRoles'); - return staffRoles; + + getOrder() { + if (roleOrder) return roleOrder; + + const roleOrderTemp = nconf.get('roleOrder'); + if (roleOrderTemp && Array.isArray(roleOrderTemp)) { + for (let i in roles) { + if (roleOrderTemp.indexOf(i) === -1) roleOrderTemp.push(i); + } + + roleOrder = roleOrderTemp; + return roleOrderTemp; + } + + const temp = []; + + for (var i in roles) { + temp.push(i); + } + + roleOrder = temp; + return temp; } - return []; -}; + getStaffRoles() { + if (staffRoles) return staffRoles; + if (nconf.get('staffRoles') && Array.isArray(nconf.get('staffRoles'))) { + staffRoles = nconf.get('staffRoles'); + return staffRoles; + } + return []; + } +} module.exports = new Role(); diff --git a/socketserver/room.js b/socketserver/room.js index 0c80083..1abedb0 100644 --- a/socketserver/room.js +++ b/socketserver/room.js @@ -1,710 +1,699 @@ -var extend = require('extend'); -var ws = require('ws'); -var https = require('https'); -var http = require('http'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "Room"}); -var DJQueue = require('./djqueue.js'); -var Roles = require('./role'); -var DB = require('./database'); +'use strict'; +const extend = require('extend'); +const ws = require('ws'); +const https = require('https'); +const log = new (require('basic-logger'))({ showTimestamp: true, prefix: 'Room' }); +const DJQueue = require('./djqueue.js'); +const Roles = require('./role'); +const DB = require('./database'); const nconf = require('nconf'); -var defaultDBObj = function(){ +const DefaultDBObj = function () { return { - roles: {}, + roles: {}, restrictions: {}, // Uses UID as key, object containing reason and end time as value. history: [] }; }; -var Room = function(socketServer, options){ - var that = this; - - this.roomInfo = extend(true, { - name: "", // Room name - slug: "", // Room name shorthand (no spaces, alphanumeric with dashes) - greet: "", // Room greetings - maxCon: 0, // Max connections; 0 = unlimited - ownerEmail: "", // Owner email for owner promotion - guestCanSeeChat: true, // Whether guests can see the chat or not - bannedCanSeeChat: true, // Whether banned users can see the chat - roomOwnerUN: null, // Username of the room owner to use with lobby API - }, options); - - this.socketServer = socketServer; - this.queue = new DJQueue( this ); - this.attendeeList = []; - this.data = new defaultDBObj(); - this.apiUpdateTimeout = null; - this.lastChat = []; - this.createApiTimeout(); - - this.restrictiontypes = [ - 'BAN', - 'MUTE', - 'SILENT_MUTE' +class Room { + constructor(socketServer, options) { + const that = this; + + this.roomInfo = extend(true, { + name: '', // Room name + slug: '', // Room name shorthand (no spaces, alphanumeric with dashes) + greet: '', // Room greetings + maxCon: 0, // Max connections; 0 = unlimited + ownerEmail: '', // Owner email for owner promotion + guestCanSeeChat: true, // Whether guests can see the chat or not + bannedCanSeeChat: true, // Whether banned users can see the chat + roomOwnerUN: null, // Username of the room owner to use with lobby API + }, options); + + this.socketServer = socketServer; + this.queue = new DJQueue(this); + this.attendeeList = []; + this.data = new DefaultDBObj(); + this.apiUpdateTimeout = null; + this.lastChat = []; + this.createApiTimeout(); + + this.restrictiontypes = [ + 'BAN', + 'MUTE', + 'SILENT_MUTE' ]; - DB.getRoom(this.roomInfo.slug, function(err, data){ - // Just in case the slug doesn't exist yet - data = data || {}; + DB.getRoom(this.roomInfo.slug, (err, data) => { + // Just in case the slug doesn't exist yet + data = data || {}; - // If the slug doesn't exist, make owner will make the slug - if (err && !err.notFound){ - console.log(err); - return; - } - - extend(true, that.data, data); + // If the slug doesn't exist, make owner will make the slug + if (err && !err.notFound) { + console.log(err); + return; + } - that.makeOwner(); - }); -}; + extend(true, that.data, data); -Room.prototype.getRoomMeta = function(){ - return { - name: this.roomInfo.name, - slug: this.roomInfo.slug, - greet: this.roomInfo.greet, - bg: this.roomInfo.bg, - guestCanSeeChat: this.roomInfo.guestCanSeeChat, - bannedCanSeeChat: this.roomInfo.bannedCanSeeChat, - roomOwnerUN: this.roomInfo.roomOwnerUN - }; -}; + that.makeOwner(); + }); + } -Room.prototype.makeOwner = function(){ - if (!nconf.get('room:ownerEmail')) return; + getRoomMeta() { + return { + name: this.roomInfo.name, + slug: this.roomInfo.slug, + greet: this.roomInfo.greet, + bg: this.roomInfo.bg, + guestCanSeeChat: this.roomInfo.guestCanSeeChat, + bannedCanSeeChat: this.roomInfo.bannedCanSeeChat, + roomOwnerUN: this.roomInfo.roomOwnerUN + }; + } - var that = this; + makeOwner() { + if (!nconf.get('room:ownerEmail')) return; - DB.getUser(this.roomInfo.ownerEmail, function(err, data){ - if (err == 'UserNotFound') { console.log('Owner does not exist yet.'); that.data.roles.owner = []; return; } - if (err) { console.log('Cannot make Room Owner: ' + err); return; } + const that = this; - if (typeof data.uid !== 'number') { console.log('Cannot make room owner: UserUIDError'); return; } + DB.getUser(this.roomInfo.ownerEmail, (err, data) => { + if (err === 'UserNotFound') { + console.log('Owner does not exist yet.'); + that.data.roles.owner = []; + return; + } + if (err) { + console.log(`Cannot make Room Owner: ${err}`); + return; + } - log.info('Granting ' + data.un + ' (' + data.uid + ') Owner permissions'); + if (typeof data.uid !== 'number') { + console.log('Cannot make room owner: UserUIDError'); + return; + } - // Remove user from other roles to avoid interesting bugs - for (var i in that.data.roles){ - var ind = that.data.roles[i].indexOf(data.uid); - if ( ind > -1 ) that.data.roles[i].splice(ind, 1); - } + log.info(`Granting ${data.un} (${data.uid}) Owner permissions`); - // Only one owner, set entire array to one UID and set owner username for API - that.data.roles.owner = [ data.uid ]; - that.data.roomOwnerUN = data.un; - that.roomInfo.roomOwnerUN = data.un; - data.role = that.findRole(data.uid); - that.sendUserUpdate(data); - that.save(); - }); -}; + // Remove user from other roles to avoid interesting bugs + for (const i in that.data.roles) { + const ind = that.data.roles[i].indexOf(data.uid); + if (ind > -1) that.data.roles[i].splice(ind, 1); + } -Room.prototype.addUser = function( sock ){ - this.attendeeList.push( sock ); - var userSend = null; - var numGuests = 0; - sock.room = this.roomInfo.slug; + // Only one owner, set entire array to one UID and set owner username for API + that.data.roles.owner = [data.uid]; + that.data.roomOwnerUN = data.un; + that.roomInfo.roomOwnerUN = data.un; + data.role = that.findRole(data.uid); + that.sendUserUpdate(data); + that.save(); + }); + } - if (sock.user){ - this.checkMakeOwner(); - sock.user.data.role = this.findRole(sock.user.data.uid); - userSend = sock.user.getClientObj(); + addUser(sock) { + this.attendeeList.push(sock); + let userSend = null; + let numGuests = 0; + sock.room = this.roomInfo.slug; + + if (sock.user) { + this.checkMakeOwner(); + sock.user.data.role = this.findRole(sock.user.data.uid); + userSend = sock.user.getClientObj(); - for (var i = 0; i < this.attendeeList.length; i++){ - var sockObj = this.attendeeList[i]; + for (let i = 0; i < this.attendeeList.length; i++) { + const sockObj = this.attendeeList[i]; - if (!sockObj.user){ - numGuests++; - continue; - } + if (!sockObj.user) { + numGuests++; + continue; + } - if (sockObj == sock) continue; + if (sockObj === sock) continue; - if (sockObj.user && sock.user && sockObj.user.data.uid == sock.user.data.uid){ - this.removeUser(sockObj); - sockObj.close(1000, JSON.stringify({ - type: 'ConnectedElsewhere' - })); + if (sockObj.user && sock.user && sockObj.user.data.uid === sock.user.data.uid) { + this.removeUser(sockObj); + sockObj.close(1000, JSON.stringify({ + type: 'ConnectedElsewhere' + })); + } } - } - }else{ - for (var i = 0; i < this.attendeeList.length; i++){ - var sockObj = this.attendeeList[i]; + } else { + for (let i = 0; i < this.attendeeList.length; i++) { + const sockObj = this.attendeeList[i]; - if (!sockObj.user){ - numGuests++; + if (!sockObj.user) { + numGuests++; + } } } - } - //TODO: Find and add role key to user object from room db - - this.sendAll({ - type: 'userJoined', - data: { - user: userSend, - guests: numGuests - } - }, - function(sockObj){ - return sockObj != sock; - }); + // TODO: Find and add role key to user object from room db -}; + this.sendAll({ + type: 'userJoined', + data: { + user: userSend, + guests: numGuests + } + }, + sockObj => sockObj !== sock); + } -Room.prototype.replaceUser = function( sock_old, sock_new ){ - if (!sock_old || !sock_old.user || !sock_new || !sock_new.user || sock_old.user.data.uid != sock_new.user.data.uid) return false; - var ind = this.attendeeList.indexOf(sock_old); - this.checkMakeOwner(); + replaceUser(sockOld, sockNew) { + if (!sockOld || !sockOld.user || !sockNew || !sockNew.user || sockOld.user.data.uid !== sockNew.user.data.uid) return false; + const ind = this.attendeeList.indexOf(sockOld); + this.checkMakeOwner(); + if (ind === -1) return false; - if (ind == -1 ) return false; + sockNew.room = this.roomInfo.slug; + sockNew.user.data.role = this.findRole(sockOld.user.data.uid); + this.attendeeList[ind] = sockNew; + this.queue.replaceSocket(sockOld, sockNew); - sock_new.room = this.roomInfo.slug; + return true; + } - sock_new.user.data.role = this.findRole(sock_old.user.data.uid); + removeUser(sock) { + const that = this; + const ind = this.attendeeList.indexOf(sock); - this.attendeeList[ind] = sock_new; + if (ind > -1) { + sock.room = null; - this.queue.replaceSocket(sock_old, sock_new); + let userSend = null; - return true; -}; + this.queue.remove(sock); -Room.prototype.removeUser = function( sock ){ - var that = this; - var ind = this.attendeeList.indexOf(sock); + if (sock.user) { + userSend = sock.user.getClientObj(); + sock.user.data.role = null; + } - if (ind > -1) { - sock.room = null; + this.attendeeList.splice(ind, 1); - var userSend = null; + this.sendAll({ + type: 'userLeft', + data: { + user: userSend, + guests: ((() => { + let num = 0; + for (let i = 0; i < that.attendeeList.length; i++) { + if (!that.attendeeList[i].user) num++; + } + return num; + }))() + } + }); + } + } - this.queue.remove( sock ); + restrictUser(restrictObj, callback) { + /* + Expects { + restrictObj: { + uid: uid, + end: int, + start: int, + reason: '', + type: '', + source: { + uid: uid, + role: role + } + } + } + */ + const that = this; - if (sock.user) { - userSend = sock.user.getClientObj(); - sock.user.data.role = null; + if (this.restrictiontypes.indexOf(restrictObj.type) === -1) { + if (callback) callback('InvalidRestrictionType'); + return; } - this.attendeeList.splice( ind, 1 ); - - this.sendAll({ - type: 'userLeft', - data: { - user: userSend, - guests: (function(){ - var num = 0; - for (var i = 0; i < that.attendeeList.length; i++){ - if (!that.attendeeList[i].user) num++; - } - return num; - })() + DB.getUserByUid(restrictObj.uid, (err, user) => { + if (err) { + if (callback) { + callback(err); + } + return; } - }); - } -}; -Room.prototype.restrictUser = function(restrictObj, callback){ - /* - Expects { - restrictObj: { - uid: uid, - end: int, - start: int, - reason: '', - type: '', - source: { - uid: uid, - role: role + if (that.isUserRestricted(restrictObj.uid, restrictObj.type)) { + if (callback) callback('UserAlreadyRestricted'); + return; } - } - } - */ - var that = this; - - if(this.restrictiontypes.indexOf(restrictObj.type) == -1) - { - if (callback) callback("InvalidRestrictionType"); - return; - } - - DB.getUserByUid(restrictObj.uid, function(err, user) { - if (err) { - if (callback) - callback(err); - return; - } - - if (that.isUserRestricted(restrictObj.uid, restrictObj.type)){ - if (callback) callback('UserAlreadyRestricted'); - return; - } - user.role = that.findRole(user.uid); - - if (!Roles.checkCanGrant(restrictObj.source.role, [user.role])) { - if (callback) callback('UserCannotBeRestricted'); - return; - } - - restrictObj.reason = restrictObj.reason.substr(0, 50); - - that.data.restrictions[restrictObj.uid] = that.data.restrictions[restrictObj.uid] || {}; - that.data.restrictions[restrictObj.uid][restrictObj.type] = restrictObj; - that.save(); - - that.sendAll({ - type: 'userRestricted', - data: { - uid: restrictObj.uid, - type: restrictObj.type, - source: restrictObj.source.uid, + user.role = that.findRole(user.uid); + + if (!Roles.checkCanGrant(restrictObj.source.role, [user.role])) { + if (callback) callback('UserCannotBeRestricted'); + return; } - }, function(obj){ - return restrictObj.type != 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute')); - }); - var userSock = that.findSocketByUid(restrictObj.uid); + restrictObj.reason = restrictObj.reason.substr(0, 50); + + that.data.restrictions[restrictObj.uid] = that.data.restrictions[restrictObj.uid] || {}; + that.data.restrictions[restrictObj.uid][restrictObj.type] = restrictObj; + that.save(); - //Check if user is online - if (userSock && restrictObj.type == 'BAN'){ - that.removeUser(userSock); - userSock.close(1000, JSON.stringify({ - type: 'banned', + that.sendAll({ + type: 'userRestricted', data: { - end: restrictObj.end, - reason: restrictObj.reason + uid: restrictObj.uid, + type: restrictObj.type, + source: restrictObj.source.uid, } - })); - } - - if (callback) callback(null); - }); -}; + }, obj => restrictObj.type !== 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute'))); + + const userSock = that.findSocketByUid(restrictObj.uid); + + // Check if user is online + if (userSock && restrictObj.type === 'BAN') { + that.removeUser(userSock); + userSock.close(1000, JSON.stringify({ + type: 'banned', + data: { + end: restrictObj.end, + reason: restrictObj.reason + } + })); + } -Room.prototype.getRestrictions = function(arr, uid){ - var out = {}; - - for(var key in this.data.restrictions[uid]){ - if(key.indexOf(arr)) - out[key] = this.data.restrictions[uid][key]; + if (callback) callback(null); + }); } - - return out; -}; -Room.prototype.unrestrictUser = function(uid, type, sock){ - if (this.data.restrictions[uid][type]){ - delete this.data.restrictions[uid][type]; - this.save(); + getRestrictions(arr, uid) { + const out = {}; - this.sendAll({ - type: 'userUnrestricted', - data: { - uid: uid, - type: type, - source: (sock ? sock.user.data.uid : null) - } - }, function(obj){ - return type != 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute')); - }); + for (const key in this.data.restrictions[uid]) { + if (key.indexOf(arr)) + out[key] = this.data.restrictions[uid][key]; + } - return true; + return out; } - return false; -}; -Room.prototype.isUserRestricted = function(uid, type){ - if ((this.data.restrictions[uid] || {})[type]) { - if (this.data.restrictions[uid][type].end < new Date(Date.now())) { - this.unrestrictUser(uid, type); - return false; + unrestrictUser(uid, type, sock) { + if (this.data.restrictions[uid][type]) { + delete this.data.restrictions[uid][type]; + this.save(); + + this.sendAll({ + type: 'userUnrestricted', + data: { + uid, + type, + source: (sock ? sock.user.data.uid : null) + } + }, obj => type !== 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute'))); + + return true; } - else { + return false; + } + + isUserRestricted(uid, type) { + if ((this.data.restrictions[uid] || {})[type]) { + if (this.data.restrictions[uid][type].end < new Date(Date.now())) { + this.unrestrictUser(uid, type); + return false; + } return true; } + return false; } - return false; -}; -Room.prototype.setRole = function(user, role){ - if (!user) return false; + setRole(user, role) { + if (!user) return false; - if (!role) role = 'default'; + if (!role) role = 'default'; - role = role.toLowerCase(); + role = role.toLowerCase(); - if (Roles.roleExists(role)){ - if (typeof this.data.roles[role] === 'undefined') this.data.roles[role] = []; + if (Roles.roleExists(role)) { + if (typeof this.data.roles[role] === 'undefined') this.data.roles[role] = []; - var userSock = this.findSocketByUid(user.uid); + const userSock = this.findSocketByUid(user.uid); - // Remove user from other role - this.removeRole(user); + // Remove user from other role + this.removeRole(user); - if (role != 'default') - this.data.roles[role].push(user.uid); + if (role !== 'default') this.data.roles[role].push(user.uid); - user.role = role; + user.role = role; - - // Save the changes - this.save(); + // Save the changes + this.save(); - if (userSock){ - // We can't assign this user object to the socket because it lacks playlists - userSock.user.data.role = role; - } + if (userSock) { + // We can't assign this user object to the socket because it lacks playlists + userSock.user.data.role = role; + } - this.sendUserUpdate(user); + this.sendUserUpdate(user); - return true; + return true; + } + return false; } - return false; -}; + removeRole(user) { + if (!user) return; -Room.prototype.removeRole = function(user){ - if (!user) return; - - for (var i in this.data.roles){ - var ind = this.data.roles[i].indexOf(user.uid); - if ( ind > -1){ - this.data.roles[i].splice(ind, 1); + for (const i in this.data.roles) { + const ind = this.data.roles[i].indexOf(user.uid); + if (ind > -1) { + this.data.roles[i].splice(ind, 1); + } } } -}; -Room.prototype.findRole = function(uid){ - if (!uid) return 'default'; + findRole(uid) { + if (!uid) return 'default'; - for (var i in this.data.roles){ - var ind = this.data.roles[i].indexOf(uid); - if ( ind > -1 && Roles.roleExists(i) ){ - return i; + for (const i in this.data.roles) { + const ind = this.data.roles[i].indexOf(uid); + if (ind > -1 && Roles.roleExists(i)) { + return i; + } } + + return 'default'; } - return 'default'; -}; + findSocketByUid(uid) { + for (const i in this.attendeeList) { + if (!this.attendeeList[i].user) continue; -Room.prototype.findSocketByUid = function( uid ){ + if (this.attendeeList[i].user.data.uid === uid) return this.attendeeList[i]; + } - for (var i in this.attendeeList){ - if (!this.attendeeList[i].user) continue; + return null; + } - if (this.attendeeList[i].user.data.uid == uid) return this.attendeeList[i]; + getAttendees() { + return this.attendeeList; } - return null; -}; + getBannedUsers(callback) { + const banned = []; + const rawBanned = []; + const that = this; -Room.prototype.getAttendees = function(){ - return this.attendeeList; -}; + for (const i in this.data.restrictions) { + // This will unban appropriately when the list is viewed. + if (this.isUserRestricted(i, 'BAN')) + rawBanned.push(i); + } -Room.prototype.getBannedUsers = function(callback){ - var banned = []; - var rawBanned = []; - var that = this; - - for (var i in this.data.restrictions){ - // This will unban appropriately when the list is viewed. - if (this.isUserRestricted(i, 'BAN')) - rawBanned.push(i); - } + if (!rawBanned.length) { + callback('NoBans'); + return; + } + + DB.getUserByUid(rawBanned, { getPlaylists: false }, (err, users) => { + for (const j in users) { + const usr = users[j].getClientObj(); + usr.role = that.findRole(usr.uid); + banned.push(usr); + } - if (!rawBanned.length){ - callback('NoBans'); - return; + callback(err, banned); + }); } - DB.getUserByUid(rawBanned, {getPlaylists: false}, function (err, users) { - for (var j in users){ - var usr = users[j].getClientObj(); - usr.role = that.findRole(usr.uid); - banned.push(usr); + getRoomStaff(callback) { + const staff = []; + let rawStaff = []; + const that = this; + + for (const i in this.data.roles) { + if (Roles.getStaffRoles().indexOf(i) > -1) { + rawStaff = rawStaff.concat(this.data.roles[i]); + } } - callback(err, banned); - }); -}; + if (!rawStaff.length) { + callback('NoStaff'); + return; + } -Room.prototype.getRoomStaff = function(callback){ - var staff = []; - var rawStaff = []; - var that = this; + DB.getUserByUid(rawStaff, { getPlaylists: false }, (err, users) => { + for (const j in users) { + const usr = users[j].getClientObj(); + usr.role = that.findRole(usr.uid); + staff.push(usr); + } - for (var i in this.data.roles){ - if (Roles.getStaffRoles().indexOf(i) > -1) { - rawStaff = rawStaff.concat(this.data.roles[i]); - } + callback(err, staff); + }); } - if (!rawStaff.length){ - callback('NoStaff'); - return; + sendSystemMessage(message) { + this.sendAll({ type: 'systemMessage', data: message }); } - DB.getUserByUid(rawStaff, { getPlaylists: false }, function (err, users) { - for (var j in users){ - var usr = users[j].getClientObj(); - usr.role = that.findRole(usr.uid); - staff.push(usr); - } + sendBroadcastMessage(message) { + this.sendAll({ type: 'broadcastMessage', data: message }); + } - callback(err, staff); - }); -}; + sendMessage(sock, message, ext, specdata, callback) { + const that = this; -Room.prototype.sendSystemMessage = function(message) { - this.sendAll({type:'systemMessage', data:message}); -}; + message = message.substring(0, 255).replace(//g, '>'); -Room.prototype.sendBroadcastMessage = function(message) { - this.sendAll({type:'broadcastMessage', data:message}); -}; + callback = callback || (() => {}); -Room.prototype.sendMessage = function( sock, message, ext, specdata, callback ){ - var that = this; + if (this.isUserRestricted(sock.user.uid, 'SILENT_MUTE')) { + DB.logChat(sock.user.uid, message, 'res:mute_s', (err, cid) => { + sock.sendJSON({ + type: 'chat', + data: { + uid: sock.user.uid, + message, + time: Date.now(), + cid, + special: specdata, + } + }); + callback(cid); + }); + } else if (this.isUserRestricted(sock.user.uid, 'MUTE')) { + callback(null); + } else { + DB.logChat(sock.user.uid, message, specdata, (err, cid) => { + that.sendAll({ + type: 'chat', + data: { + uid: sock.user.uid, // Will always be present. Unauthd can't send messages + message, + time: Date.now(), + cid, + special: specdata + } + }, obj => { + // Guests can't see chat with config variable set + if (!that.roomInfo.guestCanSeeChat && !obj.user) return false; - message = message.substring(0,255).replace(//g, '>'); + // Banned users can't see chat with config variable set + if (!that.roomInfo.bannedCanSeeChat && obj.user && that.isUserRestricted(obj.user.uid, 'BAN')) return false; - callback = callback || function(){}; - - if(this.isUserRestricted(sock.user.uid, 'SILENT_MUTE')){ - DB.logChat(sock.user.uid, message, 'res:mute_s', function(err, cid){ - sock.sendJSON({ - type: 'chat', - data: { - uid: sock.user.uid, - message: message, - time: Date.now(), - cid: cid, - special: specdata, - } - }); - callback(cid); - }); - } else if(this.isUserRestricted(sock.user.uid, 'MUTE')){ - callback(null); - } else { - DB.logChat(sock.user.uid, message, specdata, function(err, cid){ - that.sendAll({ - type: 'chat', - data: { - uid: sock.user.uid, // Will always be present. Unauthd can't send messages - message: message, - time: Date.now(), - cid: cid, - special: specdata - } - }, function(obj){ - // Guests can't see chat with config variable set - if (!that.roomInfo.guestCanSeeChat && !obj.user) return false; - - // Banned users can't see chat with config variable set - if (!that.roomInfo.bannedCanSeeChat && obj.user && that.isUserRestricted(obj.user.uid, 'BAN')) return false; - - // Check for extensive function - if("function" === typeof ext) if(!ext(obj)) return false; - - return true; - }); - - //Save last X messages to show newly connected users - if(!specdata){ - that.lastChat.push({ - user: sock.user.getClientObj(), - message: message, - time: Date.now(), - cid: cid, + // Check for extensive function + if (typeof ext === 'function') if (!ext(obj)) return false; + + return true; }); - if(that.lastChat.length > nconf.get('room:lastmsglimit')) that.lastChat.shift(); - } - - callback(cid); - }); - } -}; -Room.prototype.makePrevChatObj = function(){ - var uids = []; - var temp = extend(true, [], this.lastChat); + // Save last X messages to show newly connected users + if (!specdata) { + that.lastChat.push({ + user: sock.user.getClientObj(), + message, + time: Date.now(), + cid, + }); + if (that.lastChat.length > nconf.get('room:lastmsglimit')) that.lastChat.shift(); + } - for (var i = 0; i < temp.length; i++){ - var ind = uids.indexOf(temp[i].user.uid); - if ( ind == -1 ){ - uids.push( temp[i].user.uid ); - continue; + callback(cid); + }); } - - temp[i].user = { uid: temp[i].user.uid }; } - return temp; -}; + makePrevChatObj() { + const uids = []; + const temp = extend(true, [], this.lastChat); -Room.prototype.deleteChat = function(cid, uid){ - for (var i = 0; i < this.lastChat.length; i++){ - if (this.lastChat[i].cid == cid){ - this.lastChat.splice(i, 1); - break; - } - } + for (let i = 0; i < temp.length; i++) { + const ind = uids.indexOf(temp[i].user.uid); + if (ind === -1) { + uids.push(temp[i].user.uid); + continue; + } - this.sendAll({ - type: 'deleteChat', - data: { - cid: cid, - mid : uid + temp[i].user = { uid: temp[i].user.uid }; } - }); -}; -Room.prototype.sendAll = function (message, condition){ - condition = condition || function(){return true;}; - for (var i in this.attendeeList){ - var obj = this.attendeeList[i]; + return temp; + } - if (obj.readyState != ws.OPEN || !condition(obj)) continue; + deleteChat(cid, uid) { + for (let i = 0; i < this.lastChat.length; i++) { + if (this.lastChat[i].cid === cid) { + this.lastChat.splice(i, 1); + break; + } + } - obj.sendJSON(message); + this.sendAll({ + type: 'deleteChat', + data: { + cid, + mid: uid + } + }); } -}; -Room.prototype.sendUserUpdate = function(user){ - if (!user) return; + sendAll(message, condition) { + if (!condition) condition = () => true; + for (const i in this.attendeeList) { + const obj = this.attendeeList[i]; - this.sendAll({ - type: 'userUpdate', - data: { - user: user.getClientObj() - } - }); -}; - -Room.prototype.getUsersObj = function(){ - var temp = { - guests: 0, - users: {} - }; - var guestCounter = 0; + if (obj.readyState !== ws.OPEN || !condition(obj)) continue; - for (var i = 0; i < this.attendeeList.length; i++){ - var obj = this.attendeeList[i]; - if (!obj.user){ - temp.guests++; - continue; + obj.sendJSON(message); } + } + + sendUserUpdate(user) { + if (!user) return; - temp.users[ obj.user.uid ] = obj.user.getClientObj(); + this.sendAll({ + type: 'userUpdate', + data: { + user: user.getClientObj() + } + }); } - return temp; -}; + getUsersObj() { + const temp = { + guests: 0, + users: {} + }; -Room.prototype.getHistoryObj = function() { - return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse(); -}; + for (let i = 0; i < this.attendeeList.length; i++) { + const obj = this.attendeeList[i]; + if (!obj.user) { + temp.guests++; + continue; + } -Room.prototype.addToHistory = function(historyObj) { - //Limit history - if(nconf.get('room:history:limit_save') !== 0) - while (this.data.history.length >= nconf.get('room:history:limit_save')) { - this.data.history.shift(); + temp.users[obj.user.uid] = obj.user.getClientObj(); } - //Add to history and save - this.data.history.push(historyObj); - this.save(); -}; + return temp; + } -Room.prototype.updateLobbyServer = function(song, dj, callback) { - if (!nconf.get('apis:musiqpad:sendLobbyStats')) { - if (callback) callback(); - return; + getHistoryObj() { + return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse(); } - else if (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') == "") { - console.log("A musiqpad key must be defined in the config for updating the lobby server."); - return; + + addToHistory(historyObj) { + // Limit history + if (nconf.get('room:history:limit_save') !== 0) { + while (this.data.history.length >= nconf.get('room:history:limit_save')) { + this.data.history.shift(); + } + } + + // Add to history and save + this.data.history.push(historyObj); + this.save(); } - var postData = { - song: song, - dj: dj, - room: this.getRoomMeta(), - userCount: this.attendeeList.length - }; - var postOptions = { - host: 'api.musiqpad.com', - port: '443', - path: '/pad/' + this.roomInfo.slug, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': nconf.get('apis:musiqpad:key') - } - }; - try { - var postReq = https.request(postOptions, function (response) { - if (response.statusCode < 200 || response.statusCode > 299) { - console.log('Request Failed with Status Code: ' + response.statusCode); - } - if (callback) callback(); - }); - postReq.write(JSON.stringify(postData)); - postReq.on('error', function() { + + updateLobbyServer(song, dj, callback) { + if (!nconf.get('apis:musiqpad:sendLobbyStats')) { + if (callback) callback(); + return; + } else if (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') === '') { + console.log('A musiqpad key must be defined in the config for updating the lobby server.'); + return; + } + const postData = { + song, + dj, + room: this.getRoomMeta(), + userCount: this.attendeeList.length + }; + const postOptions = { + host: 'api.musiqpad.com', + port: '443', + path: `/pad/${this.roomInfo.slug}`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + apikey: nconf.get('apis:musiqpad:key') + } + }; + try { + const postReq = https.request(postOptions, response => { + if (response.statusCode < 200 || response.statusCode > 299) { + console.log(`Request Failed with Status Code: ${response.statusCode}`); + } + if (callback) callback(); + }); + postReq.write(JSON.stringify(postData)); + postReq.on('error', () => { + postReq.end(); + console.log('Lobby Update errored.'); + }); + postReq.setTimeout(3000, () => { + console.log('Lobby Update timed out.'); + postReq.abort(); + }); postReq.end(); - console.log('Lobby Update errored.'); - }); - postReq.setTimeout(3000, function() { - console.log('Lobby Update timed out.'); - postReq.abort(); - }); - postReq.end(); - } - catch (e) { + } catch (e) { } + this.createApiTimeout(); } - this.createApiTimeout(); -}; - -Room.prototype.createApiTimeout = function() { - var that = this; - clearTimeout(this.apiUpdateTimeout); + createApiTimeout() { + const that = this; + clearTimeout(this.apiUpdateTimeout); - this.apiUpdateTimeout = setTimeout(function() { - if (that.queue.currentsong && that.queue.currentdj) { - that.updateLobbyServer(that.queue.currentsong, that.queue.currentdj ? that.queue.currentdj.user.getClientObj() : null); - } - else { - that.updateLobbyServer(null, null); - } - }, 300000); - return this.apiUpdateTimeout; -}; + this.apiUpdateTimeout = setTimeout(() => { + if (that.queue.currentsong && that.queue.currentdj) { + that.updateLobbyServer(that.queue.currentsong, that.queue.currentdj ? that.queue.currentdj.user.getClientObj() : null); + } else { + that.updateLobbyServer(null, null); + } + }, 300000); + return this.apiUpdateTimeout; + } -Room.prototype.sockIsJoined = function(sock){ - if (this.attendeeList.indexOf(sock) > -1) - return true; - return false; -}; + sockIsJoined(sock) { + if (this.attendeeList.indexOf(sock) > -1) return true; + return false; + } -Room.prototype.makeDbObject = function(){ - return this.data; -}; + makeDbObject() { + return this.data; + } -Room.prototype.save = function(){ - DB.setRoom(this.roomInfo.slug, this.makeDbObject()); -}; + save() { + DB.setRoom(this.roomInfo.slug, this.makeDbObject()); + } -Room.prototype.checkMakeOwner = function() { - if (this.data.roles.owner && this.data.roles.owner.length == 0) { - this.makeOwner(); + checkMakeOwner() { + if (this.data.roles.owner && this.data.roles.owner.length === 0) { + this.makeOwner(); + } } } diff --git a/socketserver/socketserver.js b/socketserver/socketserver.js index 78bd26e..55cb67d 100644 --- a/socketserver/socketserver.js +++ b/socketserver/socketserver.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; //Modules var ws = require('ws'); diff --git a/webserver/app.js b/webserver/app.js index 0cbe913..c0c4ea4 100644 --- a/webserver/app.js +++ b/webserver/app.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; const express = require('express'); const compression = require('compression'); @@ -12,83 +11,84 @@ const ejs = require('ejs'); const app = express(); let server2 = null; let server = null; +// eslint-disable-next-line let socketServer = null; - /* SSL */ if (nconf.get('useSSL') && nconf.get('certificate') && nconf.get('certificate:key') && nconf.get('certificate:cert')) { - const certificate = { - key: fs.readFileSync(nconf.get('certificate:key')), - cert: fs.readFileSync(nconf.get('certificate:cert')), - }; + const certificate = { + key: fs.readFileSync(nconf.get('certificate:key')), + cert: fs.readFileSync(nconf.get('certificate:cert')), + }; - server = https.createServer(certificate, app); - if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { - server2 = http.createServer(app); - } + server = https.createServer(certificate, app); + if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { + server2 = http.createServer(app); + } +} else { + server = http.createServer(app); } -else { - server = http.createServer(app); + +if (nconf.get('webServer:redirectHTTP')) { + app.use((req, res, next) => { + if (!req.secure) { + return res.redirect(['https://', req.hostname, ':', nconf.get('webServer:port') || process.env.PORT, req.url].join('')); + } + next(); + }); } app.set('view engine', 'html'); +app.set('views', `${__dirname}/public`); app.engine('html', ejs.renderFile); -app.set('views', __dirname + '/public'); app.use(compression()); -if (nconf.get('webServer:redirectHTTP')) { - app.use((req, res, next) => { - if (!req.secure) { - return res.redirect(['https://', req.hostname, ':', nconf.get('webServer:port') || process.env.PORT, req.url].join('')); - } - next(); - }); -} - app.get(['/', '/index.html'], (req, res) => { - res.render('index', { - tags: nconf.get('room:tags'), - room: nconf.get('room'), - }); + res.render('index', { + tags: nconf.get('room:tags'), + room: nconf.get('room'), + scripts: nconf.get('room:scripts'), + }); }); -app.use(express.static(path.resolve(__dirname, 'public'))); + app.use('/pads', express.static(path.resolve(__dirname, 'public'))); +app.use(express.static(path.resolve(__dirname, 'public'))); + app.get('/config', (req, res) => { - res.setHeader('Content-Type', 'application/javascript'); - res.send(fs.readFileSync(__dirname + '/public/lib/js/webconfig.js')); + res.setHeader('Content-Type', 'application/javascript'); + res.send(fs.readFileSync(`${__dirname}/public/lib/js/webconfig.js`)); }); app.get('/api/room', (req, res) => { - const roomInfo = { - slug: nconf.get('room:slug'), - name: nconf.get('room:name'), - people: null, - queue: null, - media: null, - }; - res.send(roomInfo); + const roomInfo = { + slug: nconf.get('room:slug'), + name: nconf.get('room:name'), + people: null, + queue: null, + media: null, + }; + res.send(roomInfo); }); -server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, function(){ - const addr = server.address(); - console.log('Webserver listening at', addr.address + ':' + addr.port); +server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, () => { + const addr = server.address(); + console.log('Webserver listening at', `${addr.address}:${addr.port}`); }); if (server2 != null) { - server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, function(){ - const addr2 = server2.address(); - console.log('HTTP Webserver listening at', addr2.address + ':' + addr2.port); - }); + server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, () => { + const addr2 = server2.address(); + console.log('HTTP Webserver listening at', `${addr2.address}:${addr2.port}`); + }); } -const setSocketServer = function (ss) { - socketServer = ss; +const setSocketServer = ss => { + socketServer = ss; }; - module.exports = { - app, - server, - server2, - setSocketServer, + app, + server, + server2, + setSocketServer, }; diff --git a/webserver/public/index.html b/webserver/public/index.html index ce04284..902e5bd 100644 --- a/webserver/public/index.html +++ b/webserver/public/index.html @@ -4,7 +4,7 @@ - <% if (tags) { %> + <% if (tags) { -%> @@ -13,10 +13,15 @@ - <% } else { %> - <% } %> + <% } else { -%> + <% } -%> <%- room.name -%> + <% if (scripts.css) { -%> + <% scripts.css.forEach(function(url){ -%> + + <% }); -%> + <% } -%> @@ -757,5 +762,10 @@ + <% if (scripts.js) { -%> + <% scripts.js.forEach(function(url){ -%> + + <% }); -%> + <% } -%> From c0b69960d7669c550809db213af39bd283752b69 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 24 Jul 2016 15:59:09 +0200 Subject: [PATCH 12/15] Fixed a lot of security issues - Now using bcrypt to hash passwords - Switched from md5ed timestamps to JWT's (Login) and true random strings (Email confirmation / Password resets) for tokens. - Now using helmet for xss and iframe protection - Updated dependencys - Added Dependency badge Signed-off-by: Henry Gressmann --- .travis.yml | 3 +- README.md | 2 +- config.example.hjson | 39 +++- package.json | 16 +- socketserver/YT.js | 10 +- socketserver/database.js | 111 ++++++++-- socketserver/database_util.js | 17 -- socketserver/db_level.js | 158 +++----------- socketserver/db_mongo.js | 194 +++--------------- socketserver/db_mysql.js | 143 +++---------- socketserver/hash.js | 9 - socketserver/playlist.js | 8 +- socketserver/socketserver.js | 35 ++-- socketserver/user.js | 16 +- socketserver/utils/database.js | 22 ++ socketserver/utils/hash.js | 18 ++ socketserver/utils/index.js | 8 + socketserver/utils/token.js | 11 + start.js | 7 + test/socketserver/hash.js | 9 - .../{database_util.js => utils/database.js} | 48 ++--- webserver/app.js | 4 + 22 files changed, 352 insertions(+), 536 deletions(-) delete mode 100644 socketserver/database_util.js delete mode 100644 socketserver/hash.js create mode 100644 socketserver/utils/database.js create mode 100644 socketserver/utils/hash.js create mode 100644 socketserver/utils/index.js create mode 100644 socketserver/utils/token.js delete mode 100644 test/socketserver/hash.js rename test/socketserver/{database_util.js => utils/database.js} (60%) diff --git a/.travis.yml b/.travis.yml index f9b90b4..9838d30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,9 @@ language: node_js node_js: +- '6' - '5' -- '5.1' - '4' -- '4.1' env: - CXX=g++-4.8 diff --git a/README.md b/README.md index 0a21975..27d0acd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mqp-server [![Version npm](https://img.shields.io/npm/v/mqp-server.svg?style=flat-square)](https://www.npmjs.com/package/mqp-server) [![npm Downloads](https://img.shields.io/npm/dm/mqp-server.svg?style=flat-square)](https://www.npmjs.com/package/mqp-server) [![Build Status](https://img.shields.io/travis/musiqpad/mqp-server/master.svg?style=flat-square)](https://travis-ci.org/musiqpad/mqp-server) +# mqp-server [![Version npm](https://img.shields.io/npm/v/mqp-server.svg?style=flat-square)](https://www.npmjs.com/package/mqp-server) [![npm Downloads](https://img.shields.io/npm/dm/mqp-server.svg?style=flat-square)](https://www.npmjs.com/package/mqp-server) [![Build Status](https://img.shields.io/travis/musiqpad/mqp-server/master.svg?style=flat-square)](https://travis-ci.org/musiqpad/mqp-server) [![devDependency Status](https://david-dm.org/musiqpad/mqp-server/dev-status.svg?style=flat-square)](https://david-dm.org/musiqpad/mqp-server#info=devDependencies) [![NPM](https://nodei.co/npm/mqp-server.png)](https://npmjs.org/package/mqp-server) diff --git a/config.example.hjson b/config.example.hjson index 0cd6562..c667f09 100644 --- a/config.example.hjson +++ b/config.example.hjson @@ -1,5 +1,13 @@ { - /* + /* _ _ + _ __ ___ _ _ ___(_) __ _ _ __ __ _ __| | + | '_ ` _ \| | | / __| |/ _` | '_ \ / _` |/ _` | + | | | | | | |_| \__ \ | (_| | |_) | (_| | (_| | + |_| |_| |_|\__,_|___/_|\__, | .__/ \__,_|\__,_| + |_|_| + + More infos about the config syntax: https://hjson.org/ + Set this flag to false to disable web server hosting or true to enable web server hosting. This is useful if you want to host static files in another web server such as nginx. @@ -101,7 +109,7 @@ twitter: "@musiqpad" description: // A one to two sentence description for search engines & co ''' - + Real time music streaming and chat with friends. Musiqpad is a place where people can discover new music. ''' themeColor: "" // a hex color for the theme on chrome for android favicon: "/pads/lib/img/icon.png" @@ -129,9 +137,8 @@ } } - // The amount of time users stay logged in for before having to login again in days. - // 0 = login every time - loginExpire: 7 + // The amount of time users stay logged in for before having to login again, eg "2 days", "10h", "7d" + loginExpire: "5d", db: { dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db @@ -413,17 +420,36 @@ color: "#964B74" } permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" "djqueue.skip.other" "djqueue.lock" "djqueue.cycle" + "djqueue.limit" "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" "chat.send" + "chat.private" "chat.delete" "chat.specialMention" + "chat.broadcast" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" "room.restrict.ban" "room.restrict.mute" "room.restrict.mute_silent" "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" ] canGrantRoles: [ ] @@ -470,4 +496,5 @@ ] } } -} \ No newline at end of file + tokenSecret: "" // This should be a random string that needs to be kept private. Automatically generated if empty. +} diff --git a/package.json b/package.json index 20b30e3..c657275 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "basic-logger": "^0.4.4", + "bcrypt-nodejs": "0.0.3", "chalk": "^1.0.0", "clean-css": "^3.4.9", "compression": "^1.6.2", @@ -37,18 +38,19 @@ "file-tail": "^0.3.0", "forever": "^0.15.1", "fs-extra": "^0.30.0", + "helmet": "^2.1.1", "hjson": "^1.8.4", + "jsonwebtoken": "^7.1.6", "leveldown": "1.4.4", "levelup": "^1.3.1", - "mongodb": "^2.1.16", + "mongodb": "^2.2.4", "mysql": "^2.10.2", "nconf": "^0.8.4", "nodemailer": "^2.1.0", - "path": "^0.12.7", "ps-tree": "^1.0.1", - "request": "^2.67.0", - "update-notifier": "^0.7.0", - "ws": "^1.0.1", + "request": "^2.74.0", + "update-notifier": "^1.0.2", + "ws": "^1.1.1", "xoauth2": "^1.1.0", "yesno": "0.0.1" }, @@ -56,8 +58,8 @@ "ava": "^0.15.2", "eslint": "^2.13.1", "eslint-config-airbnb": "^9.0.1", - "eslint-plugin-import": "^1.9.2", - "eslint-plugin-jsx-a11y": "^1.5.3", + "eslint-plugin-import": "^1.11.1", + "eslint-plugin-jsx-a11y": "^1.5.5", "eslint-plugin-react": "^5.2.2" }, "ava": { diff --git a/socketserver/YT.js b/socketserver/YT.js index e6d5812..06ae7a9 100644 --- a/socketserver/YT.js +++ b/socketserver/YT.js @@ -1,8 +1,8 @@ -var https = require('https'); -var util = require('util'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "YT"}); -var querystring = require('querystring'); -var Duration = require("durationjs"); +const https = require('https'); +const util = require('util'); +const log = new (require('basic-logger'))({showTimestamp: true, prefix: "YT"}); +const querystring = require('querystring'); +const Duration = require("durationjs"); const nconf = require('nconf'); const key = nconf.get('apis:YT:key'); diff --git a/socketserver/database.js b/socketserver/database.js index 5f73840..da5932f 100644 --- a/socketserver/database.js +++ b/socketserver/database.js @@ -1,19 +1,100 @@ +'use strict'; const nconf = require('nconf'); +const LevelDB = require('./db_level'); +const MySQL = require('./db_mysql'); +const MongoDB = require('./db_mongo'); +const utils = require('./utils'); +let Database; -function Database() { - nconf.defaults({ - 'db:dbType': 'level' - }); - switch (nconf.get('db:dbType')) { - case 'level': - return require('./db_level'); - case 'mysql': - return require('./db_mysql'); - case 'mongo': - return require('./db_mongo'); - default: - return require('./db_level'); - } +var util = require('util'); + +switch (nconf.get('db:dbType')) { + case 'level': + Database = LevelDB; + break; + case 'mysql': + Database = MySQL; + break; + case 'mongo': + Database = MongoDB; + break; + default: + Database = LevelDB; +} + +function loginCallback(callback) { + return function (err, user, email) { + if (email) { + callback(null, user, utils.token.createToken({ email }, nconf.get('tokenSecret'), nconf.get('loginExpire'))); + return; + } + callback(err); + }; +} + +class DB extends Database { + loginUser(obj, callback) { + if (obj.token) { + try { + obj.email = utils.token.verify(obj.token, nconf.get('tokenSecret')).email; + } catch (e) { + if (e) { + callback('InvalidToken'); + return; + } + } + } + + this.getUser(obj.email, (err, user) => { + if ((err && err.notFound) || user == null) { + callback('UserNotFound'); + return; + } + + if (err) { + callback(err); + return; + } + // If the user has an old md5 password saved in the db + if (typeof user.data.pw === 'string' && utils.hash.isMD5(user.data.pw) && !obj.token) { + // And if that md5 password matches with the supplied pw + if (utils.db.makePassMD5(obj.pw, user.data.salt) !== user.data.pw) { + callback('IncorrectPassword'); + return; + } + // Update the pw to a new bcrypt password + user.pw = obj.pw; + super.loginUser(obj.email, loginCallback(callback)); + // If user has an md5 password and only supplied a token + } else if (utils.hash.isMD5(user.data.pw) && obj.token) { + // Say token is invalid so we get the password instead of the token next time + callback('InvalidToken'); + } else if (obj.token) { + // Check if the token is correct + utils.token.verify(obj.token, nconf.get('tokenSecret'), (err, decoded) => { + if (err) { + callback('InvalidToken'); + return; + } + const email = decoded.email; + super.loginUser(email, loginCallback(callback)); + }); + } else if (obj.pw && utils.hash.compareBcrypt(obj.pw, user.data.pw)) { + super.loginUser(obj.email, loginCallback(callback)); + } else { + callback('IncorrectPassword'); + } + }); + } + createUser(obj, callback) { + if (obj.pw) { + obj.pw = utils.hash.bcrypt(obj.pw); + super.createUser(obj, loginCallback(callback)); + } else { + callback('InvalidPassword'); + } + } } -module.exports = new Database(); +const db = new DB(); +module.exports = db; diff --git a/socketserver/database_util.js b/socketserver/database_util.js deleted file mode 100644 index e32b9ba..0000000 --- a/socketserver/database_util.js +++ /dev/null @@ -1,17 +0,0 @@ -const Hash = require('./hash'); - -function DBUtils() {} - -DBUtils.prototype.makePass = function (inPass, salt) { - return Hash.md5(('' + inPass) + (salt || '')).toString(); -}; - -DBUtils.prototype.validateEmail = function (email) { - return /^.+@.+\..+$/.test(email); -}; - -DBUtils.prototype.validateUsername = function (un) { - return /^[a-z0-9_-]{3,20}$/i.test(un); -}; - -module.exports = new DBUtils(); diff --git a/socketserver/db_level.js b/socketserver/db_level.js index f0fc439..c808e02 100644 --- a/socketserver/db_level.js +++ b/socketserver/db_level.js @@ -11,8 +11,9 @@ const log = new(require('basic-logger'))({ const nconf = require('nconf'); // Files -var Mailer = require('./mail/Mailer'); -const DBUtils = require('./database_util'); +const Mailer = require('./mail/mailer'); +const DBUtils = require('./utils').db; +const User = require('./user'); // Variables let currentPID = 0; @@ -86,18 +87,7 @@ function LevelDB(callback) { if (callback) callback(null, db); }); } - // TokenDB - if (!this.TokenDB) - this.TokenDB = setupDB(`${dbdir}/tokens`, - - // If new DB is created - function (newdb) {}, - - // Callback - function (err, db) { - if (err) log.error('Could not open TokenDB: ' + err); - }); - + // UserDB if (!this.UserDB) this.UserDB = setupDB(`${dbdir}/users`, @@ -212,7 +202,7 @@ LevelDB.prototype.getJSON = function (db, key, callback) { try { val = JSON.parse(val); } catch (e) { - console.log('Database key "' + key + '" returned malformed JSON object'); + console.log('Database key ' + key + ' returned malformed JSON object'); val = null; } } @@ -288,40 +278,6 @@ LevelDB.prototype.setRoom = function (slug, val, callback) { return this; }; -// TokenDB -LevelDB.prototype.deleteToken = function (tok) { - this.TokenDB.del(tok); -}; - -LevelDB.prototype.createToken = function (email) { - var tok = DBUtils.makePass(email, Date.now()); - - this.putJSON(this.TokenDB, tok, { - email, - time: Date.now(), - }); - - return tok; -}; - -LevelDB.prototype.isTokenValid = function (tok, callback) { - var that = this; - - this.getJSON(this.TokenDB, tok, function (err, data) { - if (err || data == null) { - callback('InvalidToken'); - return; - } - - if (nconf.get('loginExpire') && (Date.now() - data.time) < expires) { - callback(null, data.email); - } else { - that.deleteToken(data.token); - callback('InvalidToken'); - } - }); -}; - // UserDB function addUsername(un) { usernames.push(un.toLowerCase()); @@ -340,7 +296,6 @@ function usernameExists(un) { } LevelDB.prototype.createUser = function (obj, callback) { - var User = require('./user'); var that = this; var defaultCreateObj = { @@ -366,10 +321,6 @@ LevelDB.prototype.createUser = function (obj, callback) { callback('UsernameExists'); return; } - if (!inData.pw || inData.pw == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') { - callback('PasswordBlank'); - return; - } // Check for existing account this.userEmailExists(inData.email, function (err, res) { @@ -383,14 +334,11 @@ LevelDB.prototype.createUser = function (obj, callback) { user.data.uid = currentUID++; that.UserDB.put('UIDCOUNTER', currentUID); user.data.un = inData.un; - user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); - user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); + user.data.pw = inData.pw; user.data.created = Date.now(); - if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.randomBytes(18, 'base64'); var updatedUserObj = user.makeDbObj(); - var tok = that.createToken(inData.email); - that.putJSON(that.UserDB, inData.email, updatedUserObj, function (err) { if (err) { callback(err); @@ -410,77 +358,29 @@ LevelDB.prototype.createUser = function (obj, callback) { // Do other ~messy~ stuff addUsername(inData.un); user.login(inData.email); - callback(null, user, tok); + callback(null, user, inData.email); }); }); }; -LevelDB.prototype.loginUser = function (obj, callback) { - var User = require('./user'); - var that = this; - - var defaultLoginObj = { - email: null, - pw: null, - token: null, - }; - util._extend(defaultLoginObj, obj); - - var inData = defaultLoginObj; - - if (inData.email && inData.pw) { - inData.email = inData.email.toLowerCase(); - - this.getJSON(this.UserDB, inData.email, function (err, data) { - if ((err && err.notFound) || data == null) { - callback('UserNotFound'); - return; - } - - if (err) { - callback(err); - return; - } - - if (DBUtils.makePass(inData.pw, data.salt) != data.pw) { - callback('IncorrectPassword'); - return; - } - - var tok = that.createToken(inData.email); - var user = new User(); - - user.login(inData.email, data, function () { - callback(null, user, tok); - }); - }); - } else if (inData.token) { - that.isTokenValid(inData.token, function (err, email) { - if (err) { - callback(err); - return; - } - - that.getJSON(that.UserDB, email, function (err, data) { - if ((err && err.notFound) || data == null) { - callback('UserNotFound'); - return; - } - - if (err) { - callback(err); - return; - } - - var user = new User(); - user.login(email, data, function () { - callback(null, user); - }); - }); - }); - } else { - callback('InvalidArgs'); - } +LevelDB.prototype.loginUser = function (email, callback) { + if (email) { + email = email.toLowerCase(); + this.getJSON(this.UserDB, email, (err, data) => { + if ((err && err.notFound) || data == null) { + callback('UserNotFound'); + return; + } + if (err) { + callback(err); + return; + } + const user = new User(); + user.login(email, data, () => { + callback(null, user, email); + }); + }); + } }; LevelDB.prototype.putUser = function (email, data, callback) { @@ -488,8 +388,6 @@ LevelDB.prototype.putUser = function (email, data, callback) { }; LevelDB.prototype.getUser = function (email, callback) { - var User = require('./user'); - this.getJSON(this.UserDB, email, function (err, data) { if ((err && err.notFound) || data == null) { callback('UserNotFound'); return; } @@ -515,7 +413,6 @@ LevelDB.prototype.deleteUser = function (email, callback) { }; LevelDB.prototype.getUserByUid = function (uid, opts, callback) { - var User = require('./user'); var done = false; if (typeof opts === 'function') { @@ -586,7 +483,6 @@ LevelDB.prototype.getUserByUid = function (uid, opts, callback) { }; LevelDB.prototype.getUserByName = function (name, opts, callback) { - var User = require('./user'); var done = false; if (typeof opts === 'function') { @@ -768,4 +664,4 @@ LevelDB.prototype.getIpHistory = function (uid, callback) { }); }; -module.exports = new LevelDB(); +module.exports = LevelDB; diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index 96ca9f2..479a8f6 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -3,14 +3,15 @@ const mongodb = require('mongodb').MongoClient; const util = require('util'); const log = new(require('basic-logger'))({ - showTimestamp: true, - prefix: 'MongoDB' + showTimestamp: true, + prefix: 'MongoDB' }); // Files const nconf = require('nconf'); -const Mailer = require('./mail/Mailer'); -const DBUtils = require('./database_util'); +const Mailer = require('./mail/mailer'); +const DBUtils = require('./utils').db; +const User = require('./user'); // Variables const expires = 1000 * 60 * 60 * 24 * nconf.get('loginExpire'); @@ -21,7 +22,6 @@ let ready = false; let playlistscol = null; let roomcol = null; -let tokenscol = null; let userscol = null; let chatcol = null; let pmscol = null; @@ -81,23 +81,6 @@ function createCollectionsIfNoExist(callback) { } }); - db.collection('tokens', { - strict: true - }, function (err, col) { - if (err) { - db.createCollection('tokens', function (errc, result) { - if (errc) - throw new Error('Failed to create the tokens collection'); - - tokenscol = result; - if (++step == total) callback(); - }); - } else { - tokenscol = col; - if (++step == total) callback(); - } - }); - db.collection('users', { strict: true }, function (err, col) { @@ -400,51 +383,6 @@ MongoDB.prototype.setRoom = function (slug, val, callback) { return this; }; -// TokenDB -MongoDB.prototype.deleteToken = function (tok) { - dbQueue(function () { - tokenscol.remove({ - tok - }, function () {}); - }); -}; - -MongoDB.prototype.createToken = function (email) { - var tok = DBUtils.makePass(email, Date.now()); - - dbQueue(function () { - tokenscol.insert({ - tok, - email, - time: Date.now(), - }, function () {}); - }); - - return tok; -}; - -MongoDB.prototype.isTokenValid = function (tok, callback) { - var that = this; - - dbQueue(function () { - tokenscol.findOne({ - tok - }, function (err, data) { - if (err || data == null) { - callback('InvalidToken'); - return; - } - - if (nconf.get('loginExpire') && (Date.now() - data.time) < expires) { - callback(null, data.email); - } else { - that.deleteToken(data.token); - callback('InvalidToken'); - } - }); - }); -}; - // UserDB function addUsername(un) { usernames.push(un.toLowerCase()); @@ -458,7 +396,6 @@ function usernameExists(un) { } MongoDB.prototype.createUser = function (obj, callback) { - var User = require('./user'); var that = this; var defaultCreateObj = { @@ -484,10 +421,6 @@ MongoDB.prototype.createUser = function (obj, callback) { callback('UsernameExists'); return; } - if (!inData.pw || inData.pw == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') { - callback('PasswordBlank'); - return; - } dbQueue(function () { // Check for existing account @@ -502,18 +435,14 @@ MongoDB.prototype.createUser = function (obj, callback) { user.data.uid = currentUID; user.data.un = inData.un; - user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); - user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); + user.data.pw = inData.pw; user.data.created = Date.now(); - if (nconf.get('room:mail:confirmation')) { - user.data.confirmation = DBUtils.makePass(Date.now()); - } + if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.randomBytes(18, 'base64'); + var updatedUserObj = user.makeDbObj(); updatedUserObj._id = currentUID; updatedUserObj.email = inData.email; - var tok = that.createToken(inData.email); - userscol.insert(updatedUserObj, function (error, data) { if (error) { callback(error); @@ -533,89 +462,34 @@ MongoDB.prototype.createUser = function (obj, callback) { // Do other ~messy~ stuff addUsername(inData.un); user.login(inData.email); - callback(null, user, tok); + callback(null, user, inData.email); }); }); }); }); }; -MongoDB.prototype.loginUser = function (obj, callback) { - var User = require('./user'); - var that = this; - - var defaultLoginObj = { - email: null, - pw: null, - token: null, - }; - util._extend(defaultLoginObj, obj); - - var inData = defaultLoginObj; - - dbQueue(function () { - if (inData.email && inData.pw) { - inData.email = inData.email.toLowerCase(); - - userscol.findOne({ - email: inData.email - }, { - _id: 0 - }, function (err, data) { - if (err) { - callback(err); - return; - } - - if (!data) { - callback('UserNotFound'); - return; - } - - if (DBUtils.makePass(inData.pw, data.salt) != data.pw) { - callback('IncorrectPassword'); - return; - } - - var tok = that.createToken(inData.email); - var user = new User(); - - user.login(inData.email, data, function () { - callback(null, user, tok); - }); - }); - } else if (inData.token) { - that.isTokenValid(inData.token, function (err, email) { - if (err) { - callback(err); - return; - } - - userscol.findOne({ - email - }, { - _id: 0 - }, function (err, data) { - if (err) { - callback(err); - return; - } - - if (!data) { - callback('UserNotFound'); - return; - } - - var user = new User(); - user.login(email, data, function () { - callback(null, user); - }); - }); - }); - } else { - callback('InvalidArgs'); - } - }); +MongoDB.prototype.loginUser = function (email, callback) { + if (email) { + email = email.toLowerCase(); + dbQueue(() => { + userscol.findOne({ email }, { _id: 0 }, (err, data) => { + if (err) { + callback(err); + return; + } + if (!data) { + callback('UserNotFound'); + return; + } + + const user = new User(); + user.login(email, data, () => { + callback(null, user, email); + }); + }); + }); + } }; MongoDB.prototype.putUser = function (email, data, callback) { @@ -636,8 +510,6 @@ MongoDB.prototype.putUser = function (email, data, callback) { }; MongoDB.prototype.getUser = function (email, callback) { - var User = require('./user'); - dbQueue(function () { userscol.findOne({ email @@ -683,8 +555,6 @@ MongoDB.prototype.deleteUser = function (email, callback) { }; MongoDB.prototype.getUserByUid = function (uid, opts, callback) { - var User = require('./user'); - if (typeof opts === 'function') { callback = opts; opts = {}; @@ -741,8 +611,6 @@ MongoDB.prototype.getUserByUid = function (uid, opts, callback) { }; MongoDB.prototype.getUserByName = function (name, opts, callback) { - var User = require('./user'); - if (typeof opts === 'function') { callback = opts; opts = {}; @@ -954,4 +822,4 @@ MongoDB.prototype.getIpHistory = function (uid, callback) { }); }; -module.exports = new MongoDB(); +module.exports = MongoDB; diff --git a/socketserver/db_mysql.js b/socketserver/db_mysql.js index 603f233..d6be746 100644 --- a/socketserver/db_mysql.js +++ b/socketserver/db_mysql.js @@ -1,7 +1,7 @@ +'use strict'; //Modules const mysql = require('mysql'); const util = require('util'); -const _ = require('underscore'); const log = new(require('basic-logger'))({ showTimestamp: true, prefix: "MysqlDB" @@ -9,18 +9,18 @@ const log = new(require('basic-logger'))({ const nconf = require('nconf'); //Files -var Hash = require('./hash'); -const Mailer = require('./mail/Mailer'); -var DBUtils = require('./database_util'); -var Roles = require('./role.js'); +const Mailer = require('./mail/mailer'); +const DBUtils = require('./utils').db; +const Roles = require('./role.js'); +const User = require('./user'); -var db = null; -var pool = null; +let db = null; +let pool = null; -var MysqlDB = function(){ - var that = this; +const MysqlDB = function(){ + const that = this; - var mysqlConfig = { + const mysqlConfig = { host: nconf.get('db:mysqlHost'), user: nconf.get('db:mysqlUser'), password: nconf.get('db:mysqlPassword'), @@ -429,36 +429,9 @@ MysqlDB.prototype.setRoom = function(slug, val, callback) { return that; }; -//TokenDB -MysqlDB.prototype.deleteToken = function(tok) { - this.execute("DELETE FROM `tokens` WHERE ?;", { token: tok, }); -}; - -MysqlDB.prototype.createToken = function(email) { - var tok = DBUtils.makePass(email, Date.now()); - - this.execute("DELETE FROM `tokens` WHERE ?; INSERT INTO `tokens` SET ?;", [ { email: email, }, { token: tok, email: email, created: new Date() } ]); - - return tok; -}; - -MysqlDB.prototype.isTokenValid = function(tok, callback) { - this.execute("SELECT `token`, `email` FROM `tokens` WHERE ? AND DATEDIFF(NOW(), `created`) < ?;", [{ token: tok, }, nconf.get('loginExpire') || 365], function(err, res) { - if (err || res.length == 0) { - callback('InvalidToken'); - return; - } - - callback(null, res[0].email); - }); -}; - //UserDB MysqlDB.prototype.getUserNoLogin = function(uid, callback){ var that = this; - - var User = require('./user'); - this.execute("SELECT `salt`, `lastdj`, `uptime`, `recovery`, UNIX_TIMESTAMP(`recovery_timeout`) as `recovery_timeout`, `confirmation`, `badge_top`, `badge_bottom`, `created`, `activepl`, `pw`, `un`, `id` FROM `users` WHERE ?", { id: uid, }, function(err, res){ if (err || res.length == 0) { callback('UserNotFound'); return; } @@ -506,7 +479,6 @@ MysqlDB.prototype.usernameExists = function(name, callback){ }; MysqlDB.prototype.createUser = function(obj, callback) { - var User = require('./user'); var that = this; var defaultCreateObj = { @@ -547,14 +519,11 @@ MysqlDB.prototype.createUser = function(obj, callback) { var user = new User(); user.data.un = inData.un; - user.data.salt = DBUtils.makePass(Date.now()).slice(0, 10); - user.data.pw = DBUtils.makePass(inData.pw, user.data.salt); + user.data.pw = inData.pw; user.data.created = Date.now(); - if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.makePass(Date.now()); + if (nconf.get('room:mail:confirmation')) user.data.confirmation = DBUtils.randomBytes(18, 'base64'); var updatedUserObj = user.makeDbObj(); - var tok = that.createToken(inData.email); - delete updatedUserObj.uid; that.putUser(inData.email, updatedUserObj, function(err, id) { @@ -576,71 +545,30 @@ MysqlDB.prototype.createUser = function(obj, callback) { //Login user user.data.uid = id; user.login(inData.email); - callback(null, user, tok); + callback(null, user, inData.email); }); }); } }); }; -MysqlDB.prototype.loginUser = function(obj, callback) { - var User = require('./user'); - var that = this; - - var defaultLoginObj = { - email: null, - pw: null, - token: null, - }; - util._extend(defaultLoginObj, obj); - var inData = defaultLoginObj; - - if (inData.email && inData.pw) { - inData.email = inData.email.toLowerCase(); - that.execute("SELECT `id` FROM `users` WHERE ?;", { email: inData.email, }, function(err, res) { - if(err || res.length == 0) callback("UserNotFound"); - else { - that.getUserNoLogin(res[0].id, function(err, data) { - if (DBUtils.makePass(inData.pw, data.salt) != data.pw) { - callback('IncorrectPassword'); - return; - } - - var tok = that.createToken(inData.email); - - var user = new User(); - - user.login(inData.email, data, function() { - - callback(null, user, tok); - }); - }); - } - }); - } else if (inData.token) { - that.isTokenValid(inData.token, function(err, email) { - if (err) { - callback(err); - return; - } - - that.execute("SELECT `id` FROM `users` WHERE ?;", { email: email, }, function(err, res) { - if(err || res.length == 0) callback("UserNotFound"); - else { - that.getUserNoLogin(res[0].id, function(err, data) { - var user = new User(); - - user.login(email, data, function() { - - callback(null, user); - }); - }); - } - }); - }); - } else { - callback('InvalidArgs'); - } +MysqlDB.prototype.loginUser = function (email, callback) { + const that = this; + if (email) { + email = email.toLowerCase(); + that.execute('SELECT `id` FROM `users` WHERE ?;', { email }, (err, res) => { + if (err || res.length === 0) { + callback('UserNotFound'); + } else { + that.getUserNoLogin(res[0].id, (err, data) => { + const user = new User(); + user.login(email, data, () => { + callback(null, user, email); + }); + }); + } + }); + } }; MysqlDB.prototype.putUser = function(email, data, callback) { @@ -679,9 +607,6 @@ MysqlDB.prototype.putUser = function(email, data, callback) { MysqlDB.prototype.getUser = function(email, callback){ var that = this; - - var User = require('./user'); - this.execute("SELECT `id` FROM `users` WHERE ?;", { email: email, }, function(err, res) { if(err || res.length == 0){ callback('UserNotFound'); @@ -722,9 +647,6 @@ MysqlDB.prototype.getUserByUid = function(uid, opts, callback) { return; } } - - var User = require('./user'); - if(Array.isArray(uid)){ var out = {}; var initialized = 0; @@ -764,9 +686,6 @@ MysqlDB.prototype.getUserByUid = function(uid, opts, callback) { MysqlDB.prototype.getUserByName = function(name, opts, callback) { var that = this; - - var User = require('./user'); - if (typeof opts === 'function') { callback = opts; opts = {}; @@ -889,4 +808,4 @@ MysqlDB.prototype.getIpHistory = function(uid, callback) { }); }; -module.exports = new MysqlDB; +module.exports = MysqlDB; diff --git a/socketserver/hash.js b/socketserver/hash.js deleted file mode 100644 index ac2489b..0000000 --- a/socketserver/hash.js +++ /dev/null @@ -1,9 +0,0 @@ -function md5(str){ - var crypto = require('crypto'); - - var hash = crypto.createHash('md5'); - hash.update(str); - return hash.digest('hex'); -} - -module.exports.md5 = md5; \ No newline at end of file diff --git a/socketserver/playlist.js b/socketserver/playlist.js index f0c7c73..1ae09eb 100644 --- a/socketserver/playlist.js +++ b/socketserver/playlist.js @@ -1,7 +1,7 @@ -var util = require('util'); -var DB = require('./database'); -var YT = require('./YT'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "Playlist"}); +const util = require('util'); +const DB = require('./database'); +const YT = require('./YT'); +const log = new (require('basic-logger'))({showTimestamp: true, prefix: "Playlist"}); // Every user obj starts with this, then gets extended by what's in the db diff --git a/socketserver/socketserver.js b/socketserver/socketserver.js index 55cb67d..5beec20 100644 --- a/socketserver/socketserver.js +++ b/socketserver/socketserver.js @@ -1,27 +1,24 @@ 'use strict'; //Modules -var ws = require('ws'); -var http = require('http'); -var https = require('https'); -var Duration = require('durationjs'); -var request = require('request'); -var util = require('util'); -var extend = require('extend'); -var updateNotifier = require('update-notifier'); -var _ = require('underscore'); +const ws = require('ws'); +const http = require('http'); +const https = require('https'); +const Duration = require('durationjs'); +const request = require('request'); +const extend = require('extend'); +const updateNotifier = require('update-notifier'); const fs = require('fs-extra'); const nconf = require('nconf'); +const crypto = require('crypto'); //Files -var DB = require("./database"); -var Room = require('./room'); -var User = require('./user'); -var Mailer = require('./mail/Mailer'); -var YT = require('./YT'); -var Roles = require('./role'); -var Hash = require('./hash'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "SocketServer"}); -var WebSocketServer = ws.Server; +const DB = require("./database"); +const Room = require('./room'); +const Mailer = require('./mail/Mailer'); +const YT = require('./YT'); +const Roles = require('./role'); +const log = new (require('basic-logger'))({showTimestamp: true, prefix: "SocketServer"}); +const WebSocketServer = ws.Server; ws.prototype.sendJSON = function(obj){ @@ -427,7 +424,7 @@ var SocketServer = function(server){ console.log("Sending recovery email"); var sendRecovery = function(user){ //Generate new code and send email - user.recovery = Hash.md5(Date.now() + '', user.un); + user.recovery = utils.db.randomBytes(36, 'base64'); Mailer.sendEmail('recovery', { user: user.un, code: user.recovery.code, diff --git a/socketserver/user.js b/socketserver/user.js index a37a2c9..5ab67ee 100644 --- a/socketserver/user.js +++ b/socketserver/user.js @@ -1,13 +1,14 @@ -var util = require('util'); -var DB = require('./database'); -var DBUtils = require('./database_util'); +'use strict' +const util = require('util'); +let DB; +const utils = require('./utils'); // Every user obj starts with this, then gets extended by what's in the db var defaultObj = function(){ return { uid: 0, un: "", - pw: "", // MD5(SHA256(pass) + SALT) + pw: "", role: null, activepl: null, created: 0, @@ -85,6 +86,7 @@ function removeFields(obj, fields){ * */ function User(){ + DB = require('./database'); this.userExists = false; this.data = new defaultObj; } @@ -270,10 +272,8 @@ Object.defineProperty( User.prototype, 'pw', { return this.data.pw; }, set: function(val) { - - this.data.pw = DBUtils.makePass(val, this.data.salt); + this.data.pw = utils.hash.bcrypt(val); this.updateUser(); - return this; } }); @@ -402,4 +402,4 @@ Object.defineProperty( User.prototype, 'blocked', { } }); -module.exports = User; \ No newline at end of file +module.exports = User; diff --git a/socketserver/utils/database.js b/socketserver/utils/database.js new file mode 100644 index 0000000..6588897 --- /dev/null +++ b/socketserver/utils/database.js @@ -0,0 +1,22 @@ +const Hash = require('./hash'); +const crypto = require("crypto"); + +const DBUtils = { + validateEmail(email) { + return /^.+@.+\..+$/.test(email); + }, + + validateUsername(un) { + return /^[a-z0-9_-]{3,20}$/i.test(un); + }, + + makePassMD5(inPass, salt) { + return Hash.md5(('' + inPass) + (salt || '')).toString(); + }, + + randomBytes(bytes, format) { + return crypto.randomBytes(bytes).toString(format); + } +}; + +module.exports = DBUtils; diff --git a/socketserver/utils/hash.js b/socketserver/utils/hash.js new file mode 100644 index 0000000..85d8134 --- /dev/null +++ b/socketserver/utils/hash.js @@ -0,0 +1,18 @@ +const crypto = require('crypto'); +const bcrypt = require('bcrypt-nodejs'); + +module.exports = { + md5(str) { + const hash = crypto.createHash('md5'); + hash.update(str); + return hash.digest('hex'); + }, + isMD5(hash) { + return (/[a-fA-F0-9]{32}/).test(hash); + }, + bcrypt(str) { + const salt = bcrypt.genSaltSync(12); + return bcrypt.hashSync(str, salt); + }, + compareBcrypt: bcrypt.compareSync, +} \ No newline at end of file diff --git a/socketserver/utils/index.js b/socketserver/utils/index.js new file mode 100644 index 0000000..d864eab --- /dev/null +++ b/socketserver/utils/index.js @@ -0,0 +1,8 @@ +const database = require('./database'); +const hash = require('./hash'); +const token = require('./token'); + +const utils = { + +}; +module.exports = Object.assign(utils, { db: database }, { hash }, { token }); diff --git a/socketserver/utils/token.js b/socketserver/utils/token.js new file mode 100644 index 0000000..b8824d0 --- /dev/null +++ b/socketserver/utils/token.js @@ -0,0 +1,11 @@ +const jwt = require("jsonwebtoken"); + +module.exports = { + createToken(payload, secret, expires) { + return jwt.sign(payload, secret, { + expiresIn: expires + }); + }, + verify: jwt.verify, + decode: jwt.decode, +} \ No newline at end of file diff --git a/start.js b/start.js index 3b7357a..a118484 100644 --- a/start.js +++ b/start.js @@ -4,6 +4,7 @@ const nconf = require('nconf'); const fs = require('fs-extra'); const hjson = require('hjson'); +const crypto = require('crypto'); const hjsonWrapper = { parse: (text) => hjson.parse(text, { keepWsc: true, }), @@ -14,6 +15,12 @@ if (!fileExistsSync('config.hjson')) { } nconf.argv().env().file({ file: 'config.hjson', format: hjsonWrapper }); +if (!nconf.get('tokenSecret')) { + const random = crypto.randomBytes(256); + nconf.set('tokenSecret', random.toString('hex')); + nconf.save(); +} + // Modules const SocketServer = require('./socketserver/socketserver'); const path = require('path'); diff --git a/test/socketserver/hash.js b/test/socketserver/hash.js deleted file mode 100644 index ecfc9ba..0000000 --- a/test/socketserver/hash.js +++ /dev/null @@ -1,9 +0,0 @@ -const test = require('ava'); -const md5 = require('./../../socketserver/hash.js').md5; - -test.before(() => { -}); - -test('hash', t => { - t.is(md5('test'), '098f6bcd4621d373cade4e832627b4f6'); -}); diff --git a/test/socketserver/database_util.js b/test/socketserver/utils/database.js similarity index 60% rename from test/socketserver/database_util.js rename to test/socketserver/utils/database.js index 055b9ab..83e3ed9 100644 --- a/test/socketserver/database_util.js +++ b/test/socketserver/utils/database.js @@ -1,28 +1,20 @@ -const test = require('ava'); -const DBUtils = require('./../../socketserver/database_util'); - -function makePass(t, input, expected) { - t.is(DBUtils.makePass(input[0], input[1]), expected); -} - -function validateEmail(t, input, expected) { - t.is(DBUtils.validateEmail(input), expected); -} - -function validateUsername(t, input, expected) { - t.is(DBUtils.validateUsername(input), expected); -} - -test('Creates correct password hash with salt', makePass, ['test', 'randomSalt'], '4b4e47738ba3b7aab65e421787b519ff'); -test('Creates correct hash without salt', makePass, ['test', null], '098f6bcd4621d373cade4e832627b4f6'); -test('Hash is always converted to a string', makePass, ['ximaz', null], '61529519452809720693702583126814'); - -test('user@example.com is a valid email', validateEmail, 'user@example.com', true); -test('@example.com isn\'t a valid email', validateEmail, '@example.com', false); -test('user@example. isn\'t a valid email', validateEmail, 'user@example.', false); - -test('123 is a valid username', validateUsername, '123', true); -test('123456789101112131415 is\n a valid username', validateUsername, '123456789101112131415', false); -test('*user* is\n a valid username', validateUsername, '*user*', false); -test('test_user is a valid username', validateUsername, 'test_user', true); - +const test = require('ava'); +const DBUtils = require('./../../../socketserver/utils/index').db; + +function validateEmail(t, input, expected) { + t.is(DBUtils.validateEmail(input), expected); +} + +function validateUsername(t, input, expected) { + t.is(DBUtils.validateUsername(input), expected); +} + +test('user@example.com is a valid email', validateEmail, 'user@example.com', true); +test('@example.com isn\'t a valid email', validateEmail, '@example.com', false); +test('user@example. isn\'t a valid email', validateEmail, 'user@example.', false); + +test('123 is a valid username', validateUsername, '123', true); +test('123456789101112131415 is\n a valid username', validateUsername, '123456789101112131415', false); +test('*user* is\n a valid username', validateUsername, '*user*', false); +test('test_user is a valid username', validateUsername, 'test_user', true); + diff --git a/webserver/app.js b/webserver/app.js index c0c4ea4..fdf669e 100644 --- a/webserver/app.js +++ b/webserver/app.js @@ -7,6 +7,7 @@ const https = require('https'); const fs = require('fs'); const nconf = require('nconf'); const ejs = require('ejs'); +const helmet = require('helmet'); const app = express(); let server2 = null; @@ -42,6 +43,9 @@ app.set('view engine', 'html'); app.set('views', `${__dirname}/public`); app.engine('html', ejs.renderFile); app.use(compression()); +app.use(helmet.frameguard()); +app.use(helmet.xssFilter()); +app.use(helmet.hidePoweredBy()); app.get(['/', '/index.html'], (req, res) => { res.render('index', { From ccd72ebec23f77275b17a0f79cbfff57f035e75e Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 25 Jul 2016 16:31:26 +0200 Subject: [PATCH 13/15] Fixed mongodb Signed-off-by: Henry Gressmann --- socketserver/db_mongo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index 479a8f6..011022e 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -45,7 +45,7 @@ function dbQueue(callback) { function createCollectionsIfNoExist(callback) { var step = 0; - var total = 7; + var total = 6; db.collection('playlists', { strict: true @@ -822,4 +822,4 @@ MongoDB.prototype.getIpHistory = function (uid, callback) { }); }; -module.exports = MongoDB; +module.exports = MongoDB; \ No newline at end of file From b02098f92025a678b15100f31438992e7e4b6e39 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 31 Jul 2016 15:14:27 +0200 Subject: [PATCH 14/15] Fixed mysql password collum length Signed-off-by: Henry Gressmann --- socketserver/db_mysql.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/socketserver/db_mysql.js b/socketserver/db_mysql.js index d6be746..bc43f7c 100644 --- a/socketserver/db_mysql.js +++ b/socketserver/db_mysql.js @@ -13,6 +13,7 @@ const Mailer = require('./mail/mailer'); const DBUtils = require('./utils').db; const Roles = require('./role.js'); const User = require('./user'); +const _ = require('lodash'); let db = null; let pool = null; @@ -43,7 +44,7 @@ const MysqlDB = function(){ `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,\ `email` VARCHAR(254) UNIQUE NOT NULL DEFAULT 'NULL',\ `un` VARCHAR(20) UNIQUE NOT NULL DEFAULT 'NULL',\ - `pw` VARCHAR(32) NOT NULL DEFAULT 'NULL',\ + `pw` VARCHAR(60) NOT NULL DEFAULT 'NULL',\ `salt` VARCHAR(10),\ `activepl` INTEGER UNSIGNED NULL DEFAULT NULL,\ `created` DATETIME NULL,\ @@ -56,6 +57,7 @@ const MysqlDB = function(){ `lastdj` TINYINT(1) NOT NULL DEFAULT 0,\ PRIMARY KEY (`id`)\ );\ + ALTER TABLE `users` MODIFY `pw` VARCHAR(60);\ \ CREATE TABLE IF NOT EXISTS `playlists` (\ `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,\ From a431e8ca50f14b7bcda00a028a0ac630c3ff9b29 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 31 Jul 2016 16:19:29 +0200 Subject: [PATCH 15/15] Bumped version number to 0.8.0 Signed-off-by: Henry Gressmann --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c657275..8d946fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mqp-server", - "version": "0.7.1", + "version": "0.8.0", "description": "musiqpad self-hosted server", "main": "server-package.js", "author": "musiqpad Team ",