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 1d77459..624ec21 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,17 +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
- },
- "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/.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/.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 84e1e48..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)
@@ -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
new file mode 100644
index 0000000..c667f09
--- /dev/null
+++ b/config.example.hjson
@@ -0,0 +1,500 @@
+{
+ /* _ _
+ _ __ ___ _ _ ___(_) __ _ _ __ __ _ __| |
+ | '_ ` _ \| | | / __| |/ _` | '_ \ / _` |/ _` |
+ | | | | | | |_| \__ \ | (_| | |_) | (_| | (_| |
+ |_| |_| |_|\__,_|___/_|\__, | .__/ \__,_|\__,_|
+ |_|_|
+
+ 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.
+
+ 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
+ '''
+ 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"
+ }
+ 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, 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
+ 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
+ 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.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: [
+ ]
+ }
+ 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: [
+ ]
+ }
+ }
+ tokenSecret: "" // This should be a random string that needs to be kept private. Automatically generated if empty.
+}
diff --git a/mqp.js b/mqp.js
index 38ca176..8bdeb7e 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,25 +34,26 @@ 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'),
+ stderr: fs.openSync(path.join(process.cwd(), 'log.txt'), 'a'),
});
}
});
break;
case 'stop':
- getRunningPid(function (err, pid) {
+ getRunningPid((err, pid) => {
if (!err) {
process.kill(pid, 'SIGTERM');
console.log('Stopping musiqpad!');
@@ -65,32 +64,30 @@ 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'),
+ 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.');
}
});
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..8d946fc 100644
--- a/package.json
+++ b/package.json
@@ -1,63 +1,69 @@
-{
- "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.8.0",
+ "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",
+ "bcrypt-nodejs": "0.0.3",
+ "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",
+ "ejs": "^2.4.2",
+ "express": "^4.13.3",
+ "extend": "^3.0.0",
+ "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.2.4",
+ "mysql": "^2.10.2",
+ "nconf": "^0.8.4",
+ "nodemailer": "^2.1.0",
+ "ps-tree": "^1.0.1",
+ "request": "^2.74.0",
+ "update-notifier": "^1.0.2",
+ "ws": "^1.1.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.11.1",
+ "eslint-plugin-jsx-a11y": "^1.5.5",
+ "eslint-plugin-react": "^5.2.2"
+ },
+ "ava": {
+ "concurrency": 5,
+ "failFast": true
+ }
+}
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/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..06ae7a9 100644
--- a/socketserver/YT.js
+++ b/socketserver/YT.js
@@ -1,10 +1,10 @@
-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");
-var config = require('../serverconfig');
-var key = key = config.apis.YT.key;
+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');
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..da5932f 100644
--- a/socketserver/database.js
+++ b/socketserver/database.js
@@ -1,16 +1,100 @@
-var config = require('../serverconfig');
+'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(){
- config.db.dbType = config.db.dbType.toLowerCase() || 'level';
+var util = require('util');
- switch(config.db.dbType){
- case 'level':
- return require('./db_level');
- case 'mysql':
- return require('./db_mysql');
- case 'mongo':
- return require('./db_mongo');
- }
+switch (nconf.get('db:dbType')) {
+ case 'level':
+ Database = LevelDB;
+ break;
+ case 'mysql':
+ Database = MySQL;
+ break;
+ case 'mongo':
+ Database = MongoDB;
+ break;
+ default:
+ Database = LevelDB;
}
-module.exports = new Database();
\ No newline at end of file
+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');
+ }
+ }
+}
+
+const db = new DB();
+module.exports = db;
diff --git a/socketserver/database_util.js b/socketserver/database_util.js
deleted file mode 100644
index 04ad32d..0000000
--- a/socketserver/database_util.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var 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();
\ No newline at end of file
diff --git a/socketserver/db_level.js b/socketserver/db_level.js
index cb8bc1a..c808e02 100644
--- a/socketserver/db_level.js
+++ b/socketserver/db_level.js
@@ -1,101 +1,112 @@
-//Modules
-var levelup = require('levelup');
-var path = require('path');
-var util = require('util');
-var fs = require('fs');
-var log = new(require('basic-logger'))({
+'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('./mail/mailer');
+const DBUtils = require('./utils').db;
+const User = require('./user');
+
+// 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);
- });
-
- //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);
- });
+ // 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);
+ });
+ });
+ }
- //UserDB
- if(!this.UserDB)
- this.UserDB = setupDB(dbdir + '/users',
+ // 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);
+ });
+ }
+
+ // 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 +114,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 +126,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,15 +194,15 @@ 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);
} catch (e) {
- console.log('Database key "' + key + '" returned malformed JSON object');
+ console.log('Database key ' + key + ' returned malformed JSON object');
val = null;
}
}
@@ -231,17 +219,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 +245,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,60 +259,26 @@ 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) {
- this.TokenDB.del(tok);
-};
-
-LevelDB.prototype.createToken = function(email) {
- var tok = DBUtils.makePass(email, Date.now());
-
- this.putJSON(this.TokenDB, tok, {
- email: 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 (config.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());
}
@@ -341,8 +295,7 @@ function usernameExists(un) {
return ((ind = usernames.indexOf(un)) != -1 ? ind : false);
}
-LevelDB.prototype.createUser = function(obj, callback) {
- var User = require('./user');
+LevelDB.prototype.createUser = function (obj, callback) {
var that = this;
var defaultCreateObj = {
@@ -355,7 +308,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;
@@ -368,159 +321,98 @@ 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) {
+ // 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.pw = inData.pw;
user.data.created = Date.now();
- if (config.room.email.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) {
+ 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:mail: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);
+ 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) {
+LevelDB.prototype.putUser = function (email, data, callback) {
this.putJSON(this.UserDB, email, data, callback);
};
-LevelDB.prototype.getUser = function(email, callback){
- var User = require('./user');
+LevelDB.prototype.getUser = function (email, callback) {
+ this.getJSON(this.UserDB, email, function (err, data) {
+ if ((err && err.notFound) || data == null) { callback('UserNotFound'); return; }
- this.getJSON(this.UserDB, email, function(err, data){
- if ((err && err.notFound) || data == null) {callback('UserNotFound'); return; }
-
- if (err) {callback(err); 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) {
- var User = require('./user');
+LevelDB.prototype.getUserByUid = function (uid, opts, callback) {
var done = false;
if (typeof opts === 'function') {
@@ -542,7 +434,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 +447,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 +464,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,8 +482,7 @@ LevelDB.prototype.getUserByUid = function(uid, opts, callback) {
});
};
-LevelDB.prototype.getUserByName = function(name, opts, callback) {
- var User = require('./user');
+LevelDB.prototype.getUserByName = function (name, opts, callback) {
var done = false;
if (typeof opts === 'function') {
@@ -600,7 +491,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 +506,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 +528,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 +567,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 +583,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 +596,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 +621,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 = LevelDB;
diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js
index edac477..011022e 100644
--- a/socketserver/db_mongo.js
+++ b/socketserver/db_mongo.js
@@ -1,40 +1,41 @@
-//Modules
-var mongodb = require('mongodb').MongoClient;
-var util = require('util');
-var log = new(require('basic-logger'))({
- showTimestamp: true,
- prefix: "MongoDB"
+'use strict';
+// Modules
+const mongodb = require('mongodb').MongoClient;
+const util = require('util');
+const log = new(require('basic-logger'))({
+ showTimestamp: true,
+ 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('./mail/mailer');
+const DBUtils = require('./utils').db;
+const User = require('./user');
+
+// 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 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);
}
@@ -44,111 +45,104 @@ function dbQueue(callback) {
function createCollectionsIfNoExist(callback) {
var step = 0;
- var total = 7;
-
- db.collection('playlists', {strict:true}, function(err, col) {
+ var total = 6;
+
+ 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) {
- 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) {
+
+ 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 +153,237 @@ 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(){});
- });
-};
-
-MongoDB.prototype.createToken = function(email) {
- var tok = DBUtils.makePass(email, Date.now());
-
- dbQueue(function(){
- tokenscol.insert({
- tok: tok,
- email: email,
- time: Date.now(),
- }, function() {});
- });
-
- return tok;
-};
-
-MongoDB.prototype.isTokenValid = function(tok, callback) {
- var that = this;
-
- dbQueue(function(){
- tokenscol.findOne({tok: tok}, function(err, data) {
- if (err || data == null) {
- callback('InvalidToken');
- return;
- }
-
- if (config.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,8 +395,7 @@ function usernameExists(un) {
return ((ind = usernames.indexOf(un)) != -1 ? ind : false);
}
-MongoDB.prototype.createUser = function(obj, callback) {
- var User = require('./user');
+MongoDB.prototype.createUser = function (obj, callback) {
var that = this;
var defaultCreateObj = {
@@ -410,7 +408,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;
@@ -423,186 +421,140 @@ MongoDB.prototype.createUser = function(obj, callback) {
callback('UsernameExists');
return;
}
- if (!inData.pw || inData.pw == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') {
- callback('PasswordBlank');
- 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.pw = inData.pw;
user.data.created = Date.now();
- if (config.room.email.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) {
+
+ 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:mail: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);
- });
+ 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: 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) {
+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');
-
- dbQueue(function(){
- userscol.findOne({email: email}, {_id: 0}, function(err, data) {
- if (err) {
+MongoDB.prototype.getUser = function (email, callback) {
+ 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) {
- var User = require('./user');
-
+MongoDB.prototype.getUserByUid = function (uid, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
@@ -618,32 +570,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 +610,102 @@ MongoDB.prototype.getUserByUid = function(uid, opts, callback) {
});
};
-MongoDB.prototype.getUserByName = function(name, opts, callback) {
- var User = require('./user');
-
+MongoDB.prototype.getUserByName = function (name, opts, callback) {
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 +713,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 +742,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 +765,7 @@ MongoDB.prototype.getConversations = function(uid, callback) {
callback(null, out);
}
});
- }
- else {
+ } else {
callback(null, out);
}
}
@@ -772,32 +773,53 @@ 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;
+ }));
});
});
};
-module.exports = new MongoDB();
+module.exports = MongoDB;
\ No newline at end of file
diff --git a/socketserver/db_mysql.js b/socketserver/db_mysql.js
index 244d095..bc43f7c 100644
--- a/socketserver/db_mysql.js
+++ b/socketserver/db_mysql.js
@@ -1,30 +1,31 @@
+'use strict';
//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 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');
-var Roles = require('./role.js');
-
-var db = null;
-var pool = null;
-
-var MysqlDB = function(){
- var that = this;
-
- var mysqlConfig = {
- host: config.db.mysqlHost,
- user: config.db.mysqlUser,
- password: config.db.mysqlPassword,
- database: config.db.mysqlDatabase,
+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;
+
+const MysqlDB = function(){
+ const that = this;
+
+ const mysqlConfig = {
+ 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,
@@ -43,7 +44,7 @@ var 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 @@ var 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,\
@@ -429,36 +431,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, }, config.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 +481,6 @@ MysqlDB.prototype.usernameExists = function(name, callback){
};
MysqlDB.prototype.createUser = function(obj, callback) {
- var User = require('./user');
var that = this;
var defaultCreateObj = {
@@ -547,14 +521,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 (config.room.email.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) {
@@ -564,7 +535,7 @@ MysqlDB.prototype.createUser = function(obj, callback) {
}
//Send confirmation email
- if (config.room.email.confirmation) {
+ if (nconf.get('room:mail:confirmation')) {
Mailer.sendEmail('signup', {
code: user.data.confirmation,
user: inData.un,
@@ -576,71 +547,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 +609,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 +649,6 @@ MysqlDB.prototype.getUserByUid = function(uid, opts, callback) {
return;
}
}
-
- var User = require('./user');
-
if(Array.isArray(uid)){
var out = {};
var initialized = 0;
@@ -764,9 +688,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 +810,4 @@ MysqlDB.prototype.getIpHistory = function(uid, callback) {
});
};
-module.exports = new MysqlDB;
+module.exports = MysqlDB;
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/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/mail/mailer.js b/socketserver/mail/mailer.js
new file mode 100644
index 0000000..e397714
--- /dev/null
+++ b/socketserver/mail/mailer.js
@@ -0,0 +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');
+
+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;
+ }
+ }
+ }
+ }
+
+ sendEmail(type, opts, receiver, callback) {
+ const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), {
+ opts,
+ room: nconf.get('room'),
+ });
+
+ 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,
+ };
+
+ 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/mailer.js b/socketserver/mailer.js
deleted file mode 100644
index 914488d..0000000
--- a/socketserver/mailer.js
+++ /dev/null
@@ -1,59 +0,0 @@
-var NM = require('nodemailer');
-var util = require('util');
-var config = require('../serverconfig');
-var xoauth2 = require('xoauth2');
-var fs = require('fs');
-
-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;
- 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: config.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
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/role.js b/socketserver/role.js
index 47207af..9d0e3dc 100644
--- a/socketserver/role.js
+++ b/socketserver/role.js
@@ -1,94 +1,94 @@
-var config = require('../serverconfig.js');
-var roles = config.roles;
+'use strict';
+const nconf = require('nconf');
+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;
-};
-
-Role.prototype.getOrder = function(){
- if (roleOrder) return roleOrder;
-
- if (config.roleOrder && Array.isArray(config.roleOrder)){
- for (var i in roles){
- if (config.roleOrder.indexOf(i) == -1) config.roleOrder.push(i);
+
+ checkCanGrant(inRole, inPerm) {
+ const role = this.getRole(inRole);
+
+ 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;
+ }
+
+ return true;
}
-
- roleOrder = config.roleOrder;
- return config.roleOrder;
- }
-
- var temp = [];
-
- for (var i in roles){
- temp.push(i);
+ return false;
}
-
- roleOrder = temp;
- return temp;
-};
-
-Role.prototype.getStaffRoles = function(){
- if(staffRoles) return staffRoles;
-
- if (config.staffRoles && Array.isArray(config.staffRoles)) {
- staffRoles = config.staffRoles;
- return staffRoles;
+
+ makeClientObj() {
+ return roles;
+ }
+
+ 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();
\ No newline at end of file
+module.exports = new Role();
diff --git a/socketserver/room.js b/socketserver/room.js
index 07b28ae..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 config = require('../serverconfig');
-var DB = require('./database');
-
-var defaultDBObj = function(){
+'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');
+
+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 (!config.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 > config.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(-config.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(config.room.history.limit_save !== 0)
- while(this.data.history.length >= config.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 (!config.apis.musiqpad.sendLobbyStats) {
- if (callback) callback();
- return;
+ getHistoryObj() {
+ return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse();
}
- 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.";
- 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': config.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 d737e0c..5beec20 100644
--- a/socketserver/socketserver.js
+++ b/socketserver/socketserver.js
@@ -1,25 +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 config = require('../serverconfig');
-var DB = require("./database");
-var Room = require('./room');
-var User = require('./user');
-var Mailer = require('./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){
@@ -195,23 +194,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 +364,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 +404,7 @@ var SocketServer = function(server){
}
*/
//Check if recovery is enabled
- if (!(config.room.allowrecovery)){
+ if (!(nconf.get('room:allowrecovery'))){
returnObj.data = {
error: 'RecoveryDisabled'
};
@@ -418,15 +421,17 @@ 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);
+ user.recovery = utils.db.randomBytes(36, 'base64');
Mailer.sendEmail('recovery', {
user: user.un,
code: user.recovery.code,
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',
@@ -664,16 +669,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 +1385,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/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