From d266175d5bfbd1e2f4cb870dddcc9c4858944f71 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Wed, 7 Aug 2013 23:44:41 -0400 Subject: [PATCH 01/60] Start working on API refactor --- api.js | 297 ++++++++++++++++++++++-------------------------------- config.js | 6 +- server.js | 3 + 3 files changed, 126 insertions(+), 180 deletions(-) diff --git a/api.js b/api.js index 607b8041..ebf5ec65 100644 --- a/api.js +++ b/api.js @@ -28,183 +28,144 @@ module.exports = function (Server) { return raw; } - var API = function () { + function getChannelData(channel) { + var data = { + name: channel.name, + loaded: true + }; + data.pagetitle = channel.opts.pagetitle; + data.media = channel.playlist.current ? + channel.playlist.current.media.pack() : + {}; + data.usercount = channel.users.length; + data.afkcount = channel.afkers.length; + data.users = []; + for(var i in channel.users) + if(channel.users[i].name !== "") + data.users.push(channel.users[i].name); + + data.chat = []; + for(var i in channel.chatbuffer) + data.chat.push(channel.chatbuffer[i]); + + return data; } - API.prototype = { - handle: function (path, req, res) { - var parts = path.split("/"); - var last = parts[parts.length - 1]; - var params = {}; - if(last.indexOf("?") != -1) { - parts[parts.length - 1] = last.substring(0, last.indexOf("?")); - var plist = last.substring(last.indexOf("?") + 1).split("&"); - for(var i = 0; i < plist.length; i++) { - var kv = plist[i].split("="); - if(kv.length != 2) { - res.send(400); - return; - } - params[unescape(kv[0])] = unescape(kv[1]); - } - } - for(var i = 0; i < parts.length; i++) { - parts[i] = unescape(parts[i]); - } - if(parts.length != 2) { - res.send(400); - return; - } + var app = Server.app; - if(parts[0] == "json") { - res.callback = params.callback || false; - if(!(parts[1] in this.jsonHandlers)) { - res.end(JSON.stringify({ - error: "Unknown endpoint: " + parts[1] - }, null, 4)); - return; - } - this.jsonHandlers[parts[1]](params, req, res); - } - else if(parts[0] == "plain") { - if(!(parts[1] in this.plainHandlers)) { - res.send(404); - return; - } - this.plainHandlers[parts[1]](params, req, res); - } - else { - res.send(400); - } - }, + /* REGION channels */ + + /* data about a specific channel */ + app.get("/api/channels/:channel", function (req, res) { + var name = req.params.channel; + if(!name.match(/^[\w-_]+$/)) { + res.send(404); + return; + } + + var data = { + name: name, + loaded: false + }; - sendJSON: function (res, obj) { - var response = JSON.stringify(obj, null, 4); - if(res.callback) { - response = res.callback + "(" + response + ")"; - } - var len = unescape(encodeURIComponent(response)).length; + if(Server.channelLoaded(name)) + data = getChannelData(name); - res.setHeader("Content-Type", "application/json"); - res.setHeader("Content-Length", len); - res.end(response); - }, + res.type("application/json"); + res.jsonp(data); + }); - sendPlain: function (res, str) { - if(res.callback) { - str = res.callback + "('" + str + "')"; - } - var len = unescape(encodeURIComponent(str)).length; + /* data about all channels (filter= public or all) */ + app.get("/api/allchannels/:filter", function (req, res) { + var filter = req.params.filter; + if(filter !== "public" && filter !== "all") { + res.send(400); + return; + } - res.setHeader("Content-Type", "text/plain"); - res.setHeader("Content-Length", len); - res.end(response); - }, + var query = req.query; - handleChannelData: function (params, req, res) { - var clist = params.channel || ""; - clist = clist.split(","); - var data = []; - for(var j = 0; j < clist.length; j++) { - var cname = clist[j]; - if(!cname.match(/^[a-zA-Z0-9-_]+$/)) { - continue; - } - - var d = { - name: cname, - loaded: Server.channelLoaded(cname) - }; - - if(d.loaded) { - var chan = Server.getChannel(cname); - d.pagetitle = chan.opts.pagetitle; - d.media = chan.playlist.current ? chan.playlist.current.media.pack() : {}; - d.usercount = chan.users.length; - d.users = []; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name) { - d.users.push(chan.users[i].name); - } - } - d.chat = []; - for(var i = 0; i < chan.chatbuffer.length; i++) { - d.chat.push(chan.chatbuffer[i]); - } - } - data.push(d); - } - - this.sendJSON(res, data); - }, - - handleChannelList: function (params, req, res) { - if(params.filter == "public") { - var all = Server.channels; - var clist = []; - for(var key in all) { - if(all[key].opts.show_public) { - clist.push(all[key].name); - } - } - this.handleChannelData({channel: clist.join(",")}, req, res); - } - var session = params.session || ""; - var name = params.name || ""; - var pw = params.pw || ""; - var row = Auth.login(name, pw, session); + // Listing non-public channels requires authenticating as an admin + if(filter !== "public") { + var name = query.name || ""; + var session = query.session || ""; + var row = Auth.login(name, "", session); if(!row || row.global_rank < 255) { res.send(403); return; } - var clist = []; - for(var key in Server.channels) { - clist.push(Server.channels[key].name); + } + + var channels = []; + for(var key in Server.channels) { + var channel = Server.channels[key]; + if(channel.opts.show_public) { + channels.push(getChannelData(channel)); + } else if(filter !== "public") { + channels.push(getChannelData(channel)); } - this.handleChannelData({channel: clist.join(",")}, req, res); - }, + } - handleLogin: function (params, req, res) { - var session = params.session || ""; - var name = params.name || ""; - var pw = params.pw || ""; + res.type("application/jsonp"); + res.jsonp(channels); + }); - if(pw == "" && session == "") { - if(!Auth.isRegistered(name)) { - this.sendJSON(res, { - success: true, - session: "" - }); - return; - } - else { - this.sendJSON(res, { - success: false, - error: "That username is already taken" - }); - return; - } - } + /* ENDREGION channels */ - var row = Auth.login(name, pw, session); - if(row) { - if(row.global_rank >= 255) - ActionLog.record(getIP(req), name, "login-success"); - this.sendJSON(res, { - success: true, - session: row.session_hash + /* REGION authentication */ + + /* login */ + app.post("/api/login", function (req, res) { + res.type("application/jsonp"); + var name = req.body.name; + var pw = req.body.pw; + var session = req.body.session; + + // for some reason CyTube previously allowed guest logins + // over the API...wat + if(!pw && !session) { + res.jsonp({ + success: false, + error_code: "need_pw_or_session", + error: "You must provide a password" + }); + return; + } + + var row = Auth.login(name, pw, session); + if(!row) { + if(session && !pw) { + res.jsonp({ + success: false, + error_code: "invalid_session", + error: "Session expired" }); - } - else { - ActionLog.record(getIP(req), name, "login-failure"); - this.sendJSON(res, { - error: "Invalid username/password", - success: false + return; + } else { + ActionLog.record(getIP(req), name, "login-failure", + "invalid_password"); + res.jsonp({ + success: false, + error_code: "invalid_password", + error: "Provided username/password pair is invalid" }); + return; } - }, + } + // record the login if the user is an administrator + if(row.global_rank >= 255) + ActionLog.record(getIP(req), name, "login-success"); + + res.jsonp({ + success: true, + name: name, + session: row.session_hash + }); + }); + + var x = { handlePasswordChange: function (params, req, res) { var name = params.name || ""; var oldpw = params.oldpw || ""; @@ -593,27 +554,5 @@ module.exports = function (Server) { } }; - var api = new API(); - - api.plainHandlers = { - "readlog" : api.handleReadLog.bind(api) - }; - - api.jsonHandlers = { - "channeldata" : api.handleChannelData.bind(api), - "listloaded" : api.handleChannelList.bind(api), - "login" : api.handleLogin.bind(api), - "register" : api.handleRegister.bind(api), - "changepass" : api.handlePasswordChange.bind(api), - "resetpass" : api.handlePasswordReset.bind(api), - "recoverpw" : api.handlePasswordRecover.bind(api), - "setprofile" : api.handleProfileChange.bind(api), - "getprofile" : api.handleProfileGet.bind(api), - "listuserchannels": api.handleListUserChannels.bind(api), - "setemail" : api.handleEmailChange.bind(api), - "admreports" : api.handleAdmReports.bind(api), - "readactionlog" : api.handleReadActionLog.bind(api) - }; - - return api; + return null; } diff --git a/config.js b/config.js index abea1605..b8c5b69a 100644 --- a/config.js +++ b/config.js @@ -42,9 +42,11 @@ var defaults = { } function save(cfg, file) { + if(!cfg.loaded) + return; var x = {}; for(var k in cfg) { - if(k !== "nodemailer") + if(k !== "nodemailer" && k !== "loaded") x[k] = cfg[k]; } fs.writeFile(file, JSON.stringify(x, null, 4), function (err) { @@ -92,6 +94,8 @@ exports.load = function (Server, file, callback) { ); } + cfg["loaded"] = true; + save(cfg, file); Server.cfg = cfg; callback(); diff --git a/server.js b/server.js index fa744a35..0fccc6ce 100644 --- a/server.js +++ b/server.js @@ -90,6 +90,7 @@ var Server = { init: function () { this.httpaccess = new Logger.Logger("httpaccess.log"); this.app = express(); + this.app.use(express.bodyParser()); // channel path this.app.get("/r/:channel(*)", function (req, res, next) { var c = req.params.channel; @@ -104,10 +105,12 @@ var Server = { // api path this.api = require("./api")(this); + /* this.app.get("/api/:apireq(*)", function (req, res, next) { this.logHTTP(req); this.api.handle(req.url.substring(5), req, res); }.bind(this)); + */ this.app.get("/", function (req, res, next) { this.logHTTP(req); From c4588fab49dcfe652982982bec86a670cbbc67ae Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 11 Aug 2013 18:23:20 -0400 Subject: [PATCH 02/60] Refactor password change and reset --- api.js | 211 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 112 insertions(+), 99 deletions(-) diff --git a/api.js b/api.js index ebf5ec65..3f6e6397 100644 --- a/api.js +++ b/api.js @@ -54,6 +54,11 @@ module.exports = function (Server) { var app = Server.app; + /* */ + app.get("/api/coffee", function (req, res) { + res.send(418); // 418 I'm a teapot + }); + /* REGION channels */ /* data about a specific channel */ @@ -165,109 +170,117 @@ module.exports = function (Server) { }); }); - var x = { - handlePasswordChange: function (params, req, res) { - var name = params.name || ""; - var oldpw = params.oldpw || ""; - var newpw = params.newpw || ""; - if(oldpw == "" || newpw == "") { - this.sendJSON(res, { - success: false, - error: "Old password and new password cannot be empty" - }); - return; - } - var row = Auth.login(name, oldpw); - if(row) { - ActionLog.record(getIP(req), name, "password-change"); - var success = Auth.setUserPassword(name, newpw); - this.sendJSON(res, { - success: success, - error: success ? "" : "Change password failed", - session: row.session_hash - }); - } - else { - this.sendJSON(res, { - success: false, - error: "Invalid username/password" - }); - } - }, + /* password change */ + app.get("/api/account/passwordchange", function (req, res) { + res.type("application/jsonp"); - handlePasswordReset: function (params, req, res) { - var name = params.name || ""; - var email = params.email || ""; - var ip = getIP(req); + var name = req.body.name; + var oldpw = req.body.oldpw; + var newpw = req.body.newpw; - var hash = false; - try { - hash = Server.db.generatePasswordReset(ip, name, email); - ActionLog.record(ip, name, "password-reset-generate", email); - } - catch(e) { - this.sendJSON(res, { - success: false, - error: e - }); - return; - } - - if(!Server.cfg["enable-mail"]) { - this.sendJSON(res, { - success: false, - error: "This server does not have email enabled. Contact an administrator" - }); - return; - } - if(!email) { - this.sendJSON(res, { - success: false, - error: "You don't have a recovery email address set. Contact an administrator" - }); - return; - } - var msg = [ - "A password reset request was issued for your account `", - name, - "` on ", - Server.cfg["domain"], - ". This request is valid for 24 hours. ", - "If you did not initiate this, there is no need to take action. ", - "To reset your password, copy and paste the following link into ", - "your browser: ", - Server.cfg["domain"], - "/reset.html?", - hash - ].join(""); - - var mail = { - from: "CyTube Services <" + Server.cfg["mail-from"] + ">", - to: email, - subject: "Password reset request", - text: msg - }; - var api = this; - Server.cfg["nodemailer"].sendMail(mail, function(err, response) { - if(err) { - Logger.errlog.log("Mail fail: " + err); - api.sendJSON(res, { - success: false, - error: "Email failed. Contact an admin if this persists." - }); - } - else { - api.sendJSON(res, { - success: true - }); - - if(Server.cfg["debug"]) { - Logger.syslog.log(response); - } - } + if(!oldpw || !newpw) { + res.jsonp({ + success: false, + error: "Password cannot be empty" }); - }, + return; + } + var row = Auth.login(name, oldpw); + if(!row) { + res.jsonp({ + success: false, + error: "Invalid username/password combination" + }); + return; + } + + ActionLog.record(getIP(req), name, "password-change"); + var success = Auth.setUserPassword(name, newpw); + + if(!success) { + res.jsonp({ + success: false, + error: "Server error. Please try again or ask an "+ + "administrator for assistance." + }); + return; + } + + res.jsonp({ + success: true, + session: row.session_hash + }); + }); + + /* password reset */ + app.get("/api/account/passwordreset", function (req, res) { + res.type("application/jsonp"); + var name = req.body.name; + var email = req.body.email; + var ip = getIP(req); + var hash = false; + + try { + hash = Server.db.generatePasswordReset(ip, name, email); + ActionLog.record(ip, name, "password-reset-generate", email); + } catch(e) { + res.jsonp({ + success: false, + error: e + }); + return; + } + + if(!Server.cfg["enable-mail"]) { + res.jsonp({ + success: false, + error: "This server does not have email recovery enabled."+ + " Contact an administrator for assistance." + }); + return; + } + + if(!email) { + res.jsonp({ + success: false, + error: "You don't have a recovery email address set. "+ + "Contact an administrator for assistance." + }); + return; + } + + var msg = "A password reset request was issued for your account '"+ + name + "' on " + Server.cfg["domain"] + ". This request"+ + " is valid for 24 hours. If you did not initiate this, "+ + "there is no need to take action. To reset your "+ + "password, copy and paste the following link into your "+ + "browser: " + Server.cfg["domain"] + "/reset.html?"+hash; + + var mail = { + from: "CyTube Services <" + Server.cfg["mail-from"] + ">", + to: emial, + subject: "Password reset request", + text: msg + }; + + Server.cfg["nodemailer"].sendMail(mai, function (err, response) { + if(err) { + Logger.errlog.log("mail fail: " + err); + res.jsonp({ + success: false, + error: "Email send failed. Contact an administrator "+ + "if this persists" + }); + } else { + res.jsonp({ + success: true + }); + } + }); + }); + + var x = { handlePasswordRecover: function (params, req, res) { var hash = params.hash || ""; var ip = getIP(req); From 25a877dc3c971c0857527b75a314a3b8fcd16193 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 11 Aug 2013 18:55:53 -0400 Subject: [PATCH 03/60] Refactor password recover, email set, profile get/set --- api.js | 265 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 139 insertions(+), 126 deletions(-) diff --git a/api.js b/api.js index 3f6e6397..752e09aa 100644 --- a/api.js +++ b/api.js @@ -171,7 +171,7 @@ module.exports = function (Server) { }); /* password change */ - app.get("/api/account/passwordchange", function (req, res) { + app.post("/api/account/passwordchange", function (req, res) { res.type("application/jsonp"); var name = req.body.name; @@ -214,7 +214,7 @@ module.exports = function (Server) { }); /* password reset */ - app.get("/api/account/passwordreset", function (req, res) { + app.post("/api/account/passwordreset", function (req, res) { res.type("application/jsonp"); var name = req.body.name; var email = req.body.email; @@ -280,139 +280,152 @@ module.exports = function (Server) { }); }); - var x = { - handlePasswordRecover: function (params, req, res) { - var hash = params.hash || ""; - var ip = getIP(req); + /* password recovery */ + app.get("/api/account/passwordrecover", function (req, res) { + res.type("application/jsonp"); + var hash = req.query.hash; + var ip = getIP(req); - try { - var info = Server.db.recoverPassword(hash); - this.sendJSON(res, { - success: true, - name: info[0], - pw: info[1] - }); - ActionLog.record(ip, info[0], "password-recover-success"); - Logger.syslog.log(ip + " recovered password for " + info[0]); - return; - } - catch(e) { - ActionLog.record(ip, "", "password-recover-failure"); - this.sendJSON(res, { - success: false, - error: e - }); - } - }, - - handleProfileGet: function (params, req, res) { - var name = params.name || ""; - - try { - var prof = Server.db.getProfile(name); - this.sendJSON(res, { - success: true, - profile_image: prof.profile_image, - profile_text: prof.profile_text - }); - } - catch(e) { - this.sendJSON(res, { - success: false, - error: e - }); - } - }, - - handleProfileChange: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - var session = params.session || ""; - var img = params.profile_image || ""; - var text = params.profile_text || ""; - - var row = Auth.login(name, pw, session); - if(!row) { - this.sendJSON(res, { - success: false, - error: "Invalid login" - }); - return; - } - - var result = Server.db.setProfile(name, { - image: img, - text: text + try { + var info = Server.db.recoverPassword(hash); + res.jsonp({ + success: true, + name: info[0], + pw: info[1] }); - - this.sendJSON(res, { - success: result, - error: result ? "" : "Internal error. Contact an administrator" + ActionLog.record(ip, info[0], "password-recover-success"); + } catch(e) { + ActionLog.record(ip, "", "password-recover-failure", hash); + res.jsonp({ + success: false, + error: e }); + } + }); - var all = Server.channels; - for(var n in all) { - var chan = all[n]; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == name) { - chan.users[i].profile = { - image: img, - text: text - }; - chan.broadcastUserUpdate(chan.users[i]); - break; - } + /* profile retrieval */ + app.get("/api/users/:user/profile", function (req, res) { + res.type("application/jsonp"); + var name = req.params.user; + + try { + var prof = Server.db.getProfile(name); + res.jsonp({ + success: true, + profile_image: prof.profile_image, + profile_text: prof.profile_text + }); + } catch(e) { + res.jsonp({ + success: false, + error: e + }); + } + }); + + /* profile change */ + app.post("/api/account/profile", function (req, res) { + res.type("application/jsonp"); + var name = req.body.name; + var pw = req.body.pw; + var session = req.body.session; + var img = req.body.profile_image; + var text = req.body.profile_text; + + var row = Auth.login(name, pw, session); + if(!row) { + res.jsonp({ + success: false, + error: "Invalid login" + }); + return; + } + + var result = Server.db.setProfile(name, { + image: img, + text: text + }); + + if(!result) { + res.jsonp({ + success: false, + error: "Server error. Contact an administrator for assistance" + }); + return; + } + + res.jsonp({ + success: true + }); + + // Update profile on all channels the user is connected to + name = name.toLowerCase(); + for(var i in Server.channels) { + var chan = Server.channels[i]; + for(var j in chan.users) { + var user = chan.users[j]; + if(user.name.toLowerCase() == name) { + user.profile = { + image: img, + text: text + }; + chan.broadcastUserUpdate(user); } } - }, + } - handleEmailChange: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - var email = params.email || ""; - // perhaps my email regex isn't perfect, but there's no freaking way - // I'm implementing this monstrosity: - // - if(!email.match(/^[a-z0-9_\.]+@[a-z0-9_\.]+[a-z]+$/)) { - this.sendJSON(res, { - success: false, - error: "Invalid email" - }); - return; - } + }); - if(email.match(/.*@(localhost|127\.0\.0\.1)/i)) { - this.sendJSON(res, { - success: false, - error: "Nice try, but no." - }); - return; - } + /* set email */ + app.post("/api/account/email", function (req, res) { + res.type("application/jsonp"); + var name = req.body.name; + var pw = req.body.pw; + var email = req.body.email; - if(pw == "") { - this.sendJSON(res, { - success: false, - error: "Password cannot be empty" - }); - return; - } - var row = Auth.login(name, pw); - if(row) { - var success = Server.db.setUserEmail(name, email); - ActionLog.record(getIP(req), name, "email-update", email); - this.sendJSON(res, { - success: success, - error: success ? "" : "Email update failed", - session: row.session_hash - }); - } - else { - this.sendJSON(res, { - success: false, - error: "Invalid username/password" - }); - } - }, + if(!email.match(/^[\w_\.]+@[\w_\.]+[a-z]+$/i)) { + res.jsonp({ + success: false, + error: "Invalid email address" + }); + return; + } + + if(email.match(/.*@(localhost|127\.0\.0\.1)/i)) { + res.jsonp({ + success: false, + error: "Nice try, but no" + }); + return; + } + + var row = Auth.login(name, pw); + if(!row) { + res.jsonp({ + success: false, + error: "Invalid login credentials" + }); + return; + } + + var success = Server.db.setUserEmail(name, email); + if(!success) { + res.jsonp({ + success: false, + error: "Email update failed. Contact an administrator "+ + "for assistance." + }); + return false; + } + + ActionLog.record(getIP(req), name, "email-update", email); + res.jsonp({ + success: true, + session: row.session_hash + }); + }); + + var x = { handleRegister: function (params, req, res) { var name = params.name || ""; From b06d8ff09f984450598d15126f34bd55337efc89 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sun, 11 Aug 2013 22:20:09 -0400 Subject: [PATCH 04/60] Refactor register, my channels --- api.js | 179 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 90 insertions(+), 89 deletions(-) diff --git a/api.js b/api.js index 752e09aa..20b8731b 100644 --- a/api.js +++ b/api.js @@ -11,7 +11,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var Auth = require("./auth"); var Logger = require("./logger"); -var apilog = new Logger.Logger("api.log"); var ActionLog = require("./actionlog"); var fs = require("fs"); @@ -118,7 +117,7 @@ module.exports = function (Server) { /* ENDREGION channels */ - /* REGION authentication */ + /* REGION authentication, account management */ /* login */ app.post("/api/login", function (req, res) { @@ -170,6 +169,72 @@ module.exports = function (Server) { }); }); + /* register an account */ + app.post("/api/register", function (req, res) { + res.type("application/jsonp"); + var name = req.body.name; + var pw = req.body.pw; + var ip = getIP(req); + + // Limit registrations per IP within a certain time period + if(ActionLog.tooManyRegistrations(ip)) { + ActionLog.record(ip, name, "register-failure", + "Too many recent registrations"); + res.jsonp({ + success: false, + error: "Your IP address has registered too many accounts "+ + "in the past 48 hours. Please wait a while before"+ + " registering another." + }); + return; + } + + if(!pw) { + // costanza.jpg + res.jsonp({ + success: false, + error: "You must provide a password" + }); + return; + } + + if(!Auth.validateName(name)) { + ActionLog.record(ip, name, "register-failure", "Invalid name"); + res.jsonp({ + success: false, + error: "Invalid username. Valid usernames must be 1-20 "+ + "characters long and consist only of alphanumeric "+ + "characters and underscores (_)" + }); + return; + } + + if(Auth.isRegistered(name)) { + ActionLog.record(ip, name, "register-failure", "Name taken"); + res.jsonp({ + success: false, + error: "That username is already taken" + }); + return; + } + + var session = Auth.register(name, pw); + if(!session) { + res.jsonp({ + success: false, + error: "Registration error. Contact an administrator "+ + "for assistance." + }); + return; + } + + ActionLog.record(ip, name, "register-success"); + res.jsonp({ + success: true, + session: session + }); + }); + /* password change */ app.post("/api/account/passwordchange", function (req, res) { res.type("application/jsonp"); @@ -425,94 +490,30 @@ module.exports = function (Server) { }); }); + /* my channels */ + app.get("/api/account/mychannels", function (req, res) { + res.type("application/jsonp"); + var name = req.query.name; + var session = req.query.session; + + var row = Auth.login(name, session); + if(!row) { + res.jsonp({ + success: false, + error: "Invalid login" + }); + return; + } + + var channels = Server.db.listUserChannels(name); + res.jsonp({ + success: true, + channels: channels + }); + }); + + var x = { - - handleRegister: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - if(ActionLog.tooManyRegistrations(getIP(req))) { - ActionLog.record(getIP(req), name, "register-failure", - "Too many recent registrations from this IP"); - this.sendJSON(res, { - success: false, - error: "Your IP address has registered several accounts in "+ - "the past 48 hours. Please wait a while or ask an "+ - "administrator for assistance." - }); - return; - } - - if(pw == "") { - this.sendJSON(res, { - success: false, - error: "You must provide a password" - }); - return; - } - else if(Auth.isRegistered(name)) { - ActionLog.record(getIP(req), name, "register-failure", - "Name taken"); - this.sendJSON(res, { - success: false, - error: "That username is already taken" - }); - return false; - } - else if(!Auth.validateName(name)) { - ActionLog.record(getIP(req), name, "register-failure", - "Invalid name"); - this.sendJSON(res, { - success: false, - error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores" - }); - } - else { - var session = Auth.register(name, pw); - if(session) { - ActionLog.record(getIP(req), name, "register-success"); - Logger.syslog.log(getIP(req) + " registered " + name); - this.sendJSON(res, { - success: true, - session: session - }); - } - else { - this.sendJSON(res, { - success: false, - error: "Registration error. Contact an admin for assistance." - }); - } - } - }, - - handleListUserChannels: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - var session = params.session || ""; - - var row = Auth.login(name, pw, session); - if(!row) { - this.sendJSON(res, { - success: false, - error: "Invalid login" - }); - return; - } - - var channels = Server.db.listUserChannels(name); - - this.sendJSON(res, { - success: true, - channels: channels - }); - }, - - handleAdmReports: function (params, req, res) { - this.sendJSON(res, { - error: "Not implemented" - }); - }, - handleReadActionLog: function (params, req, res) { var name = params.name || ""; var pw = params.pw || ""; From 0bf80a375d6f7b4fcce258f16f9478d584085026 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sun, 11 Aug 2013 22:32:02 -0400 Subject: [PATCH 05/60] Refactor log reading --- api.js | 164 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/api.js b/api.js index 20b8731b..55a72e9c 100644 --- a/api.js +++ b/api.js @@ -251,7 +251,7 @@ module.exports = function (Server) { return; } - var row = Auth.login(name, oldpw); + var row = Auth.login(name, oldpw, ""); if(!row) { res.jsonp({ success: false, @@ -464,7 +464,7 @@ module.exports = function (Server) { return; } - var row = Auth.login(name, pw); + var row = Auth.login(name, pw, ""); if(!row) { res.jsonp({ success: false, @@ -496,7 +496,7 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; - var row = Auth.login(name, session); + var row = Auth.login(name, "", session); if(!row) { res.jsonp({ success: false, @@ -512,74 +512,104 @@ module.exports = function (Server) { }); }); + /* END REGION */ - var x = { - handleReadActionLog: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - var session = params.session || ""; - var types = params.actions || ""; - var row = Auth.login(name, pw, session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } + /* REGION log reading */ - var actiontypes = types.split(","); - var actions = ActionLog.readLog(actiontypes); - this.sendJSON(res, actions); - }, + /* action log */ + app.get("/api/logging/actionlog", function (req, res) { + res.type("application/jsonp"); + var name = req.query.name; + var session = req.query.session; + var types = req.query.actions; - // Helper function - pipeLast: function (res, file, len) { - fs.stat(file, function(err, data) { - if(err) { - res.send(500); - return; - } - var start = data.size - len; - if(start < 0) { - start = 0; - } - var end = data.size - 1; - fs.createReadStream(file, {start: start, end: end}).pipe(res); - }); - }, - - handleReadLog: function (params, req, res) { - var name = params.name || ""; - var pw = params.pw || ""; - var session = params.session || ""; - var row = Auth.login(name, pw, session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } - res.setHeader("Access-Control-Allow-Origin", "*"); - - var type = params.type || ""; - if(type == "sys") { - this.pipeLast(res, "sys.log", 1024*1024); - } - else if(type == "err") { - this.pipeLast(res, "error.log", 1024*1024); - } - else if(type == "channel") { - var chan = params.channel || ""; - fs.exists("chanlogs/" + chan + ".log", function(exists) { - if(exists) { - this.pipeLast(res, "chanlogs/" + chan + ".log", 1024*1024); - } - else { - res.send(404); - } - }.bind(this)); - } - else { - res.send(400); - } + var row = Auth.login(name, "", session); + if(!row || row.global_rank < 255) { + res.send(403); + return; } - }; + + types = types.split(","); + var actions = ActionLog.readLog(actiontypes); + res.jsonp(actions); + }); + + /* helper function to pipe the last N bytes of a file */ + function pipeLast(res, file, len) { + fs.stat(file, function (err, data) { + if(err) { + res.send(500); + return; + } + var start = data.size - len; + if(start < 0) { + start = 0; + } + var end = data.size - 1; + fs.createReadStream(file, { start: start, end: end }) + .pipe(res); + }); + } + + app.get("/api/logging/syslog", function (req, res) { + res.type("text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + + var name = req.query.name; + var session = req.query.session; + + var row = Auth.login(name, "", session); + if(!row || row.global_rank < 255) { + res.send(403); + return; + } + + pipeLast(res, "sys.log", 1048576); + }); + + app.get("/api/logging/errorlog", function (req, res) { + res.type("text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + + var name = req.query.name; + var session = req.query.session; + + var row = Auth.login(name, "", session); + if(!row || row.global_rank < 255) { + res.send(403); + return; + } + + pipeLast(res, "error.log", 1048576); + }); + + app.get("/api/logging/channels/:channel", function (req, res) { + res.type("text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + + var name = req.query.name; + var session = req.query.session; + + var row = Auth.login(name, "", session); + if(!row || row.global_rank < 255) { + res.send(403); + return; + } + + var chan = req.params.channel || ""; + if(!chan.match(/^[\w-_]+$/)) { + res.send(400); + return; + } + + fs.exists("chanlogs/" + chan + ".log", function(exists) { + if(exists) { + pipeLast(res, "chanlogs/" + chan + ".log", 1048576); + } else { + res.send(404); + } + }); + }); return null; } From 4aa0e7a4efb38431583fb35c13c55199a5dc36c7 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sun, 11 Aug 2013 23:10:55 -0400 Subject: [PATCH 06/60] Start updating to new API --- api.js | 4 +- www/assets/js/account.js | 104 ++++++++++---------- www/channel-new.html | 200 --------------------------------------- www/channelwidget.html | 55 ----------- www/index.html | 3 +- www/login.html | 8 +- www/reset.html | 3 +- 7 files changed, 63 insertions(+), 314 deletions(-) delete mode 100644 www/channel-new.html delete mode 100644 www/channelwidget.html diff --git a/api.js b/api.js index 55a72e9c..4ec207cc 100644 --- a/api.js +++ b/api.js @@ -131,18 +131,17 @@ module.exports = function (Server) { if(!pw && !session) { res.jsonp({ success: false, - error_code: "need_pw_or_session", error: "You must provide a password" }); return; } var row = Auth.login(name, pw, session); + console.log(row); if(!row) { if(session && !pw) { res.jsonp({ success: false, - error_code: "invalid_session", error: "Session expired" }); return; @@ -151,7 +150,6 @@ module.exports = function (Server) { "invalid_password"); res.jsonp({ success: false, - error_code: "invalid_password", error: "Provided username/password pair is invalid" }); return; diff --git a/www/assets/js/account.js b/www/assets/js/account.js index 1f1d8fe0..125c48a6 100644 --- a/www/assets/js/account.js +++ b/www/assets/js/account.js @@ -11,17 +11,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var uname = readCookie("cytube_uname") || ""; var session = readCookie("cytube_session") || ""; -var api = WEB_URL + "/api/json/"; var loggedin = false; if(uname && session) { - var loginstr = "name=" + encodeURIComponent(uname) - + "&session=" + session; - var url = api + "login?" + loginstr + "&callback=?"; - $.getJSON(url, function(data) { - if(data.success) { + var data = { + name: uname, + session: session + }; + $.post(WEB_URL + "/api/login?callback=?", data, function (data) { + if(data.success) onLogin(); - } }); } @@ -57,7 +56,8 @@ $("#email").click(makeTabCallback("#email", "#changeemailpane")); $("#profile").click(makeTabCallback("#profile", "#profilepane")); $("#profile").click(function() { if(uname != "") { - $.getJSON(api + "getprofile?name=" + encodeURIComponent(uname) + "&callback=?", function(data) { + $.getJSON(WEB_URL+"/api/users/"+uname+"/profile?callback=?", + function (data) { if(data.success) { $("#profiletext").val(data.profile_text); $("#profileimg").val(data.profile_image); @@ -82,8 +82,10 @@ $("#channels").click(function () { return; } - $.getJSON(api + "listuserchannels?name=" + encodeURIComponent(uname) + - "&session=" + session + "&callback=?", function(data) { + var auth = "name=" + encodeURIComponent(uname) + "&session=" + + encodeURIComponent(session); + $.getJSON(WEB_URL+"/api/account/mychannels?"+auth+"&callback=?", + function (data) { $("#channellist tbody").remove(); data.channels.forEach(function (chan) { var tr = $("").appendTo($("#channellist")); @@ -134,12 +136,12 @@ $("#registerbtn").click(function() { } // Input valid, try registering - var url = api + "register?" + [ - "name=" + encodeURIComponent(name), - "pw=" + encodeURIComponent(pw) - ].join("&") + "&callback=?"; - - $.getJSON(url, function(data) { + var data = { + name: name, + pw: pw + }; + + $.pos(WEB_URL + "/api/register?callback=?", data, function (data) { if(data.success) { uname = name; session = data.session; @@ -170,10 +172,11 @@ $("#loginbtn").click(function() { return; } uname = $("#loginusername").val(); - var loginstr = "name=" + encodeURIComponent(uname) - + "&pw=" + encodeURIComponent($("#loginpw").val()); - var url = api + "login?" + loginstr + "&callback=?"; - $.getJSON(url, function(data) { + var data = { + name: uname, + pw: $("#loginpw").val() + }; + $.getJSON(WEB_URL+"/api/login?callback=?", data, function(data) { if(data.success) { session = data.session; onLogin(); @@ -230,12 +233,13 @@ $("#cpwbtn").click(function() { } // Input valid, try changing password - var url = api + "changepass?" + [ - "name=" + encodeURIComponent(name), - "oldpw=" + encodeURIComponent(oldpw), - "newpw=" + encodeURIComponent(newpw) - ].join("&") + "&callback=?"; - $.getJSON(url, function(data) { + var data = { + name: name, + oldpw: oldpw, + newpw: newpw + }; + $.post(WEB_URL + "/api/account/passwordchange?callback=?", data, + function (data) { if(data.success) { $("
").addClass("alert alert-success") .text("Password changed.") @@ -266,7 +270,7 @@ $("#cebtn").click(function() { return; } - if(!email.match(/^[a-z0-9_\.]+@[a-z0-9_\.]+[a-z]+$/)) { + if(!email.match(/^[\w_\.]+@[\w_\.]+[a-zA-Z]+$/)) { $("
").addClass("alert alert-error") .text("Invalid email") .insertAfter($("#ceemail").parent().parent()); @@ -282,12 +286,13 @@ $("#cebtn").click(function() { return; } - var url = api + "setemail?" + [ - "name=" + encodeURIComponent(name), - "pw=" + encodeURIComponent(pw), - "email=" + encodeURIComponent(email) - ].join("&") + "&callback=?"; - $.getJSON(url, function(data) { + var data = { + name: name, + pw: pw, + email: email + }; + $.post(WEB_URL + "/api/account/email?callback=?", data, + function (data) { if(data.success) { $("
").addClass("alert alert-success") .text("Email updated") @@ -312,11 +317,12 @@ $("#rpbtn").click(function() { var name = $("#rpusername").val(); var email = $("#rpemail").val(); - var url = api + "resetpass?" + [ - "name=" + encodeURIComponent(name), - "email=" + encodeURIComponent(email) - ].join("&") + "&callback=?"; - $.getJSON(url, function(data) { + var data = { + name: name, + email: email + }; + $.post(WEB_URL + "/api/account/passwordreset?callback=?", data, + function (data) { $("#rpbtn").text("Send Reset"); if(data.success) { $("
").addClass("alert alert-success") @@ -336,20 +342,16 @@ $("#profilesave").click(function() { $("#profilepane").find(".alert-error").remove(); $("#profilepane").find(".alert-success").remove(); var img = $("#profileimg").val(); - /* - img = escape(img).replace(/\//g, "%2F") - .replace(/&/g, "%26") - .replace(/=/g, "%3D") - .replace(/\?/g, "%3F"); - */ - var url = api + "setprofile?" + [ - "name=" + encodeURIComponent(uname), - "session=" + session, - "profile_image=" + encodeURIComponent(img), - "profile_text=" + encodeURIComponent($("#profiletext").val()) - ].join("&") + "&callback=?"; + var text = $("#profiletext").val(); + var data = { + name: uname, + session: session, + profile_image: img, + profile_text: text + }; - $.getJSON(url, function(data) { + $.post(WEB_URL+"/api/account/profile?callback=?", data, + function (data) { if(data.success) { $("
").addClass("alert alert-success") .text("Profile updated.") diff --git a/www/channel-new.html b/www/channel-new.html deleted file mode 100644 index 228e302b..00000000 --- a/www/channel-new.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - CyTube - - - - - - - - - - - -
- - - - -
- -
-
-
-
-

-
-
- -
-
- -
- -
- -
- -

Not connected

-
- -
-
- -
-
- - -
- -
- -

Nothing playing

- -
-
-
-
- -
- -
-
- -
- - -
- -
- -

Show Library

-
-
-
- -
-
- - -
-
- -
- -

Show Playlist Manager

-
-
-
- -
-
- -
-
    -
-
-
-
- -
-
- -
- -
- -

Show Playlist Controls

-
-
-
- -
-
- - -
-
- - - - -
-
- -
    -
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - diff --git a/www/channelwidget.html b/www/channelwidget.html deleted file mode 100644 index a4e918b1..00000000 --- a/www/channelwidget.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - CyTube - - - - - - - - - - - - - - -
ChannelConnectedPlaying
- - - - - diff --git a/www/index.html b/www/index.html index f2ae309e..1cf512fa 100644 --- a/www/index.html +++ b/www/index.html @@ -130,7 +130,8 @@ return entry; } function refresh() { - $.getJSON(WEB_URL+"/api/json/listloaded?filter=public&callback=?", function(data) { + $.getJSON(WEB_URL+"/api/allchannels/public?callback=?", + function(data) { $("#channeldata").find("tbody").remove(); data.sort(function(a, b) { var x = a.usercount; diff --git a/www/login.html b/www/login.html index 02fe6b72..d255cfa8 100644 --- a/www/login.html +++ b/www/login.html @@ -66,9 +66,11 @@ window.addEventListener("message", respond, false); $("#login").click(function() { - var u = encodeURIComponent($("#username").val()); - var p = encodeURIComponent($("#pw").val()); - $.getJSON(WEB_URL+"/api/json/login?name="+u+"&pw="+p+"&callback=?", function(data) { + var data = { + name: $("#username").val(), + pw: $("#pw").val() + }; + $.post(WEB_URL+"/api/login", data, function (data) { data.uname = $("#username").val(); source.postMessage("cytube-login:"+JSON.stringify(data), document.location); }); diff --git a/www/reset.html b/www/reset.html index 76aaf280..a9f9529f 100644 --- a/www/reset.html +++ b/www/reset.html @@ -71,7 +71,8 @@ hash = loc.substring(loc.indexOf("?") + 1); })(); - var url = WEB_URL+"/api/json/recoverpw?hash="+hash+"&callback=?"; + var url = WEB_URL+"/api/account/passwordrecover?hash="+hash+ + "&callback=?"; $.getJSON(url, function(data) { if(data.success) { $("
").addClass("alert alert-success") From 03e27a77208fbd5eea5fb466c4ccb0381fcdf7c4 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sun, 11 Aug 2013 23:36:42 -0400 Subject: [PATCH 07/60] Various fixes to the API --- api.js | 5 ++--- www/assets/js/account.js | 18 +++++++++--------- www/assets/js/acp.js | 11 ++++++----- www/login.html | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/api.js b/api.js index 4ec207cc..5a580d5f 100644 --- a/api.js +++ b/api.js @@ -74,7 +74,7 @@ module.exports = function (Server) { }; if(Server.channelLoaded(name)) - data = getChannelData(name); + data = getChannelData(Server.getChannel(name)); res.type("application/json"); res.jsonp(data); @@ -137,7 +137,6 @@ module.exports = function (Server) { } var row = Auth.login(name, pw, session); - console.log(row); if(!row) { if(session && !pw) { res.jsonp({ @@ -528,7 +527,7 @@ module.exports = function (Server) { } types = types.split(","); - var actions = ActionLog.readLog(actiontypes); + var actions = ActionLog.readLog(types); res.jsonp(actions); }); diff --git a/www/assets/js/account.js b/www/assets/js/account.js index 125c48a6..49bc7d4a 100644 --- a/www/assets/js/account.js +++ b/www/assets/js/account.js @@ -21,7 +21,7 @@ if(uname && session) { $.post(WEB_URL + "/api/login?callback=?", data, function (data) { if(data.success) onLogin(); - }); + }, "jsonp"); } function onLogin() { @@ -141,7 +141,7 @@ $("#registerbtn").click(function() { pw: pw }; - $.pos(WEB_URL + "/api/register?callback=?", data, function (data) { + $.post(WEB_URL + "/api/register?callback=?", data, function (data) { if(data.success) { uname = name; session = data.session; @@ -157,7 +157,7 @@ $("#registerbtn").click(function() { .text(data.error) .insertBefore($("#registerpane form")); } - }); + }, "jsonp"); }); $("#loginbtn").click(function() { @@ -176,7 +176,7 @@ $("#loginbtn").click(function() { name: uname, pw: $("#loginpw").val() }; - $.getJSON(WEB_URL+"/api/login?callback=?", data, function(data) { + $.post(WEB_URL+"/api/login?callback=?", data, function(data) { if(data.success) { session = data.session; onLogin(); @@ -191,7 +191,7 @@ $("#loginbtn").click(function() { .text(data.error) .insertBefore($("#loginpane form")); } - }); + }, "jsonp"); }); $("#cpwbtn").click(function() { @@ -253,7 +253,7 @@ $("#cpwbtn").click(function() { .text(data.error) .insertBefore($("#changepwpane form")); } - }); + }, "jsonp"); }); $("#cebtn").click(function() { @@ -306,7 +306,7 @@ $("#cebtn").click(function() { .text(data.error) .insertBefore($("#changeemailpane form")); } - }); + }, "jsonp"); }); @@ -334,7 +334,7 @@ $("#rpbtn").click(function() { .text(data.error) .insertBefore($("#pwresetpane form")); } - }); + }, "jsonp"); }); @@ -362,7 +362,7 @@ $("#profilesave").click(function() { .text(data.error) .insertBefore($("#profilepane form")); } - }); + }, "jsonp"); }); $("#login").click(function() { diff --git a/www/assets/js/acp.js b/www/assets/js/acp.js index babc69eb..f96eb1f7 100644 --- a/www/assets/js/acp.js +++ b/www/assets/js/acp.js @@ -1,4 +1,3 @@ -var BASE = WEB_URL + "/api/json/"; var AUTH = ""; var NO_WEBSOCKETS = false; @@ -156,20 +155,21 @@ function reverseLog() { $("#log_reverse").click(reverseLog); function getSyslog() { - $.ajax(WEB_URL+"/api/plain/readlog?type=sys&"+AUTH).done(function(data) { + $.ajax(WEB_URL+"/api/logging/syslog?"+AUTH).done(function(data) { $("#log").text(data); }); } $("#syslog").click(getSyslog); function getErrlog() { - $.ajax(WEB_URL+"/api/plain/readlog?type=err&"+AUTH).done(function(data) { + $.ajax(WEB_URL+"/api/logging/errorlog?"+AUTH).done(function(data) { $("#log").text(data); }); } $("#errlog").click(getErrlog); function getActionLog() { var types = "&actions=" + $("#actionlog_filter").val().join(","); - $.getJSON(WEB_URL+"/api/json/readactionlog?"+AUTH+types+"&callback=?").done(function(entries) { + $.getJSON(WEB_URL+"/api/logging/actionlog?"+AUTH+types+"&callback=?") + .done(function(entries) { var tbl = $("#actionlog table"); entries.forEach(function (e) { e.time = parseInt(e.time); @@ -217,7 +217,8 @@ function getActionLog() { } function getChanlog() { var chan = $("#channame").val(); - $.ajax(WEB_URL+"/api/plain/readlog?type=channel&channel="+chan+"&"+AUTH).done(function(data) { + $.ajax(WEB_URL+"/api/logging/channels/"+chan+"?"+AUTH) + .done(function(data) { $("#log").text(data); }); } diff --git a/www/login.html b/www/login.html index d255cfa8..2ace9b2d 100644 --- a/www/login.html +++ b/www/login.html @@ -73,7 +73,7 @@ $.post(WEB_URL+"/api/login", data, function (data) { data.uname = $("#username").val(); source.postMessage("cytube-login:"+JSON.stringify(data), document.location); - }); + }, "jsonp"); }); From 0d31d6eea29859f00798b441b734847af47127af Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 12 Aug 2013 00:09:43 -0400 Subject: [PATCH 08/60] Fix stupid typos --- api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api.js b/api.js index 5a580d5f..0ff1c544 100644 --- a/api.js +++ b/api.js @@ -321,12 +321,12 @@ module.exports = function (Server) { var mail = { from: "CyTube Services <" + Server.cfg["mail-from"] + ">", - to: emial, + to: email, subject: "Password reset request", text: msg }; - Server.cfg["nodemailer"].sendMail(mai, function (err, response) { + Server.cfg["nodemailer"].sendMail(mail, function (err, response) { if(err) { Logger.errlog.log("mail fail: " + err); res.jsonp({ From 01eeab071174adb8b59c132ed02467a01b050e92 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 12 Aug 2013 00:24:48 -0400 Subject: [PATCH 09/60] Clear queued playlist actions on new playlist --- www/assets/js/callbacks.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 04f9928c..36f0bc25 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -696,6 +696,7 @@ Callbacks = { /* REGION Playlist Stuff */ playlist: function(data) { + PL_QUEUED_ACTIONS = []; // Clear the playlist first var q = $("#queue"); q.html(""); @@ -741,6 +742,7 @@ Callbacks = { else { var liafter = playlistFind(data.after); if(!liafter) { + socket.emit("requestPlaylist"); return false; } li.insertAfter(liafter); From 0ceb362f0bdd521be0c7f4227b67fe015e80fca5 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 12 Aug 2013 00:27:30 -0400 Subject: [PATCH 10/60] Remove faulty requestPlaylist --- www/assets/js/callbacks.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 36f0bc25..74bd8aab 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -742,7 +742,6 @@ Callbacks = { else { var liafter = playlistFind(data.after); if(!liafter) { - socket.emit("requestPlaylist"); return false; } li.insertAfter(liafter); From 44d5f42a36ddd606df5e85d6d60cd7dc091da7f9 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Mon, 12 Aug 2013 10:34:57 -0400 Subject: [PATCH 11/60] Start switching to node-mysql --- database-old.js | 1084 +++++++++++++++++++++++++++++++++++++++++++++ database.js | 1128 +++-------------------------------------------- package.json | 2 +- 3 files changed, 1158 insertions(+), 1056 deletions(-) create mode 100644 database-old.js diff --git a/database-old.js b/database-old.js new file mode 100644 index 00000000..9da571c3 --- /dev/null +++ b/database-old.js @@ -0,0 +1,1084 @@ +/* +The MIT License (MIT) +Copyright (c) 2013 Calvin Montgomery + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +var mysql = require("mysql-libmysqlclient"); +var Logger = require("./logger"); +var Media = require("./media").Media; +var bcrypt = require("bcrypt"); +var hashlib = require("node_hash"); + +var db = false; +var SERVER = ""; +var USER = ""; +var DATABASE = ""; +var PASSWORD = ""; +var CONFIG = {}; +var global_bans = {}; + +function setup(cfg) { + SERVER = cfg["mysql-server"]; + USER = cfg["mysql-user"]; + DATABASE = cfg["mysql-db"]; + PASSWORD = cfg["mysql-pw"]; + CONFIG = cfg; +} + +function getConnection() { + if(db && db.connectedSync()) { + return db; + } + db = mysql.createConnectionSync(); + db.connectSync(SERVER, USER, PASSWORD, DATABASE); + if(!db.connectedSync()) { + Logger.errlog.log("DB connection failed"); + return false; + } + if(CONFIG["debug"]) { + db._querySync = db.querySync; + db.querySync = function(q) { + Logger.syslog.log("DEBUG: " + q); + return this._querySync(q); + } + } + return db; +} + +function sqlEscape(obj) { + if(obj === undefined || obj === null) + return "NULL"; + + if(typeof obj === "boolean") + return obj ? "true" : "false"; + + if(typeof obj === "number") + return obj + ""; + + if(typeof obj === "object") + return "'object'"; + + if(typeof obj === "string") { + obj = obj.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { + switch(s) { + case "\0": return "\\0"; + case "\n": return "\\n"; + case "\r": return "\\r"; + case "\b": return "\\b"; + case "\t": return "\\t"; + case "\x1a": return "\\Z"; + default: return "\\" + s; + } + }); + return "'" + obj + "'"; + } +} + +function createQuery(template, args) { + var last = -1; + while(template.indexOf("?", last) >= 0) { + var idx = template.indexOf("?", last); + var arg = args.shift(); + arg = sqlEscape(arg); + // Stupid workaround because even if I call replace() with a string + // and not a regex, '$' still holds special meaning + // this actually replaces '$' with '$$' + // What the hell, Javascript? + arg = arg.replace(/\$/g, "$$$$"); + var first = template.substring(0, idx); + template = first + template.substring(idx).replace("?", arg); + last = idx + arg.length; + } + template = template.replace(/`'/g, "`"); + template = template.replace(/'`/g, "`"); + return template; +} + +function init() { + var db = getConnection(); + if(!db) { + return false; + } + + // Create channel table + var query = ["CREATE TABLE IF NOT EXISTS `channels` (", + "`id` INT NOT NULL AUTO_INCREMENT,", + "`name` VARCHAR(255) NOT NULL,", + "`owner` VARCHAR(20) NOT NULL,", + "PRIMARY KEY(`id`))", + "ENGINE = MyISAM;"].join(""); + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create channels table"); + } + + // Create registration table + query = ["CREATE TABLE IF NOT EXISTS `registrations` (", + "`id` INT NOT NULL AUTO_INCREMENT,", + "`uname` VARCHAR(20) NOT NULL,", + "`pw` VARCHAR(64) NOT NULL,", + "`global_rank` INT NOT NULL,", + "`session_hash` VARCHAR(64) NOT NULL,", + "`expire` BIGINT NOT NULL,", + "`profile_image` VARCHAR(255) NOT NULL,", + "`profile_text` TEXT NOT NULL,", + "`email` VARCHAR(255) NOT NULL,", + "PRIMARY KEY (`id`))", + "ENGINE = MyISAM;"].join(""); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create registrations table"); + } + + // Create global bans table + query = ["CREATE TABLE IF NOT EXISTS `global_bans` (", + "`ip` VARCHAR(15) NOT NULL,", + "`note` VARCHAR(255) NOT NULL,", + "PRIMARY KEY (`ip`))", + "ENGINE = MyISAM;"].join(""); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create global ban table"); + } + + refreshGlobalBans(); + + // Create password reset table + query = ["CREATE TABLE IF NOT EXISTS `password_reset` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`hash` VARCHAR(64) NOT NULL,", + "`email` VARCHAR(255) NOT NULL,", + "`expire` BIGINT NOT NULL,", + "PRIMARY KEY (`name`))", + "ENGINE = MyISAM;"].join(""); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create password reset table"); + } + + // Create user playlist table + query = ["CREATE TABLE IF NOT EXISTS `user_playlists` (", + "`user` VARCHAR(20) NOT NULL,", + "`name` VARCHAR(255) NOT NULL,", + "`contents` MEDIUMTEXT NOT NULL,", + "`count` INT NOT NULL,", + "`time` INT NOT NULL,", + "PRIMARY KEY (`user`, `name`))", + "ENGINE = MyISAM;"].join(""); + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create playlist table"); + } + + // Create user aliases table + query = ["CREATE TABLE IF NOT EXISTS `aliases` (", + "`visit_id` INT NOT NULL AUTO_INCREMENT,", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`time` BIGINT NOT NULL,", + "PRIMARY KEY (`visit_id`), INDEX (`ip`))", + "ENGINE = MyISAM;"].join(""); + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create aliases table"); + } + + // Create action log table + query = ["CREATE TABLE IF NOT EXISTS `actionlog` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`action` VARCHAR(255) NOT NULL,", + "`args` TEXT NOT NULL,", + "`time` BIGINT NOT NULL,", + "PRIMARY KEY (`ip`, `time`), INDEX (`action`))", + "ENGINE = MyISAM;"].join(""); + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create actionlog table"); + } + + // Create stats table + query = ["CREATE TABLE IF NOT EXISTS `stats` (", + "`time` BIGINT NOT NULL,", + "`usercount` INT NOT NULL,", + "`chancount` INT NOT NULL,", + "`mem` INT NOT NULL,", + "PRIMARY KEY (`time`))", + "ENGINE = MyISAM;"].join(""); + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create stats table"); + } +} + +/* REGION Global Bans */ + +function checkGlobalBan(ip) { + const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/; + var s16 = ip.replace(re, "$1.$2"); + var s24 = ip.replace(re, "$1.$2.$3"); + return (ip in global_bans || + s16 in global_bans || + s24 in global_bans); +} + +function refreshGlobalBans() { + var db = getConnection(); + if(!db) { + return; + } + + var query = "SELECT * FROM `global_bans` WHERE 1"; + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to load global bans"); + } + else { + var rows = results.fetchAllSync(); + global_bans = {}; + for(var i = 0; i < rows.length; i++) { + global_bans[rows[i].ip] = rows[i].note; + } + } + return global_bans; +} + +function globalBanIP(ip, reason) { + var db = getConnection(); + if(!db) { + return; + } + + var query = createQuery( + "INSERT INTO `global_bans` VALUES (?, ?)", + [ip, reason] + ); + return db.querySync(query); +} + +function globalUnbanIP(ip) { + var db = getConnection(); + if(!db) { + return; + } + + var query = createQuery( + "DELETE FROM `global_bans` WHERE ip=?", + [ip] + ); + + return db.querySync(query); +} + +/* REGION Channel Registration/Loading */ + +function registerChannel(name, owner) { + if(!name.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + // Library table + var query = ["CREATE TABLE `?` (", + "`id` VARCHAR(255) NOT NULL,", + "`title` VARCHAR(255) NOT NULL,", + "`seconds` INT NOT NULL,", + "`type` VARCHAR(2) NOT NULL,", + "PRIMARY KEY (`id`))", + "ENGINE = MyISAM;"].join(""); + query = createQuery(query, ["chan_" + name + "_library"]); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create table: chan_"+name+"_library"); + return false; + } + + // Rank table + query = ["CREATE TABLE `?` (", + "`name` VARCHAR(32) NOT NULL,", + "`rank` INT NOT NULL,", + "UNIQUE (`name`))", + "ENGINE = MyISAM;"].join(""); + query = createQuery(query, ["chan_" + name + "_ranks"]); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create table: chan_"+name+"_ranks"); + return false; + } + + // Ban table + query = ["CREATE TABLE `?` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(32) NOT NULL,", + "`banner` VARCHAR(32) NOT NULL,", + "PRIMARY KEY (`ip`))", + "ENGINE = MyISAM;"].join(""); + query = createQuery(query, ["chan_" + name + "_bans"]); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to create table: chan_"+name+"_bans"); + return false; + } + + // Insert into channel table + query = createQuery( + "INSERT INTO `channels` VALUES (NULL, ?, ?)", + [name, owner] + ); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to insert into channel table: " + name); + return false; + } + + return true; +} + +function loadChannel(chan) { + if(!chan.name.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "SELECT * FROM `channels` WHERE name=?", + [chan.name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to query channel table"); + } + else { + var rows = results.fetchAllSync(); + if(rows.length == 0) { + // Unregistered + Logger.syslog.log("Channel " + chan.name + " is unregistered"); + return; + } + // Database is case insensitive + else if(rows[0].name != chan.name) { + chan.name = rows[0].name; + } + chan.registered = true; + } + + // Load channel library + query = createQuery( + "SELECT * FROM `?`", + ["chan_" + chan.name + "_library"] + ); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to load channel library: " + chan.name); + } + else { + var rows = results.fetchAllSync(); + for(var i = 0; i < rows.length; i++) { + var r = rows[i]; + var m = new Media(r.id, r.title, r.seconds, r.type); + chan.library[r.id] = m; + } + } + + // Load channel bans + query = createQuery( + "SELECT * FROM `?`", + ["chan_" + chan.name + "_bans"] + ); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to load channel bans: " + chan.name); + } + else { + var rows = results.fetchAllSync(); + for(var i = 0; i < rows.length; i++) { + var r = rows[i]; + if(r.ip == "*") { + chan.namebans[r.name] = r.banner; + } + else { + chan.ipbans[r.ip] = [r.name, r.banner]; + } + } + } + + chan.logger.log("*** Loaded channel from database"); + Logger.syslog.log("Loaded channel " + chan.name + " from database"); +} + +function deleteChannel(name) { + if(!name.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + + var db = getConnection(); + if(!db) { + return false; + } + + var query = "DROP TABLE `chan_?_bans`, `chan_?_ranks`, `chan_?_library`" + .replace(/\?/g, name); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to delete channel tables for " + name); + return false; + } + + query = createQuery( + "DELETE FROM `channels` WHERE name=?", + [name] + ); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to delete row from channel table: " + name); + return false; + } + + return true; +} + +/* REGION Channel data */ + +function getChannelRank(chan, name) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return 0; + } + + var query; + if(typeof name == "object") { + var n = "(?"; + for(var i = 1; i < name.length; i++) { + n += ",?"; + } + n += ")" + name.unshift("chan_" + chan + "_ranks"); + query = createQuery( + "SELECT * FROM `?` WHERE name IN " + n, + name + ); + } + else { + query = createQuery( + "SELECT * FROM `?` WHERE name=?", + ["chan_"+chan+"_ranks", name] + ); + } + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to lookup chan_"+chan+"_ranks"); + return 0; + } + + var rows = results.fetchAllSync(); + if(typeof name == "object") { + var ranks = []; + for(var i = 0; i < rows.length; i++) { + ranks.push(rows[i].rank); + } + while(ranks.length < rows.length) { + ranks.push(0); + } + return ranks; + } + if(rows.length == 0) { + return 0; + } + + return rows[0].rank; +} + +function setChannelRank(chan, name, rank) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + ["INSERT INTO `?` ", + "(`name`, `rank`) ", + "VALUES ", + "(?, ?) ", + "ON DUPLICATE KEY UPDATE ", + "`rank`=?"].join(""), + ["chan_"+chan+"_ranks", name, rank, rank] + ); + + return db.querySync(query); +} + +function listChannelRanks(chan) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return []; + } + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT * FROM `?` WHERE 1", + ["chan_"+chan+"_ranks"] + ); + + var results = db.querySync(query); + if(!results) { + return []; + } + + return results.fetchAllSync(); +} + +function addToLibrary(chan, media) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + ["INSERT INTO `?` ", + "(`id`, `title`, `seconds`, `type`) ", + "VALUES ", + "(?, ?, ?, ?)"].join(""), + ["chan_"+chan+"_library", media.id, media.title, media.seconds, media.type] + ); + + return db.querySync(query); +} + +function removeFromLibrary(chan, id) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "DELETE FROM `?` WHERE id=?", + ["chan_"+chan+"_library", id] + ); + + return db.querySync(query); +} + +function channelBan(chan, ip, name, banby) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + ["INSERT INTO `?` ", + "(`ip`, `name`, `banner`) ", + "VALUES ", + "(?, ?, ?)"].join(""), + ["chan_"+chan+"_bans", ip, name, banby] + ); + + return db.querySync(query); +} + +function channelUnbanIP(chan, ip) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "DELETE FROM `?` WHERE `ip`=?", + ["chan_"+chan+"_bans", ip] + ); + + return db.querySync(query); +} + +function channelUnbanName(chan, name) { + if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { + return false; + } + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "DELETE FROM `?` WHERE `ip`='*' AND `name`=?", + ["chan_"+chan+"_bans", name] + ); + + return db.querySync(query); +} + +/* REGION Users */ + +function getProfile(name) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "SELECT profile_image,profile_text FROM registrations WHERE uname=?", + [name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve user profile"); + throw "Database failure. Contact an administrator."; + } + + var rows = results.fetchAllSync(); + if(rows.length == 0) { + throw "User not found"; + } + + return { + profile_image: rows[0].profile_image, + profile_text: rows[0].profile_text + }; +} + +function setProfile(name, data) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + ["UPDATE `registrations` SET ", + "`profile_image`=?,", + "`profile_text`=? ", + "WHERE uname=?"].join(""), + [data.image, data.text, name] + ); + + return db.querySync(query); +} + +function setUserEmail(name, email) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "UPDATE `registrations` SET `email`=? WHERE `uname`=?", + [email, name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to set user email"); + return false; + } + return true; +} + +function genSalt() { + var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "0123456789!@#$%^&*_+=~"; + var salt = []; + for(var i = 0; i < 32; i++) { + salt.push(chars[parseInt(Math.random()*chars.length)]); + } + return salt.join(''); +} + +function generatePasswordReset(ip, name, email) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "SELECT `email` FROM `registrations` WHERE `uname`=?", + [name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve user email"); + return false; + } + + var rows = results.fetchAllSync(); + if(rows.length == 0) { + throw "Provided username does not exist"; + } + if(rows[0].email != email) { + throw "Provided email does not match user's email"; + } + + // Validation complete, now time to reset it + var hash = hashlib.sha256(genSalt() + name); + var exp = Date.now() + 24*60*60*1000; + query = createQuery( + ["INSERT INTO `password_reset` (", + "`ip`, `name`, `hash`, `email`, `expire`", + ") VALUES (", + "?, ?, ?, ?, ?", + ") ON DUPLICATE KEY UPDATE `hash`=?,`expire`=?"].join(""), + [ip, name, hash, email, exp, hash, exp] + ); + + results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to insert password reset"); + return false; + } + + return hash; +} + +function recoverPassword(hash) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "SELECT * FROM password_reset WHERE hash=?", + [hash] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve from password_reset"); + throw "Database error. Contact an administrator"; + } + + var rows = results.fetchAllSync(); + if(rows.length == 0) { + throw "Invalid password reset link"; + } + + db.querySync(createQuery( + "DELETE FROM password_reset WHERE hash=?", + [hash] + )); + + if(Date.now() > rows[0].expire) { + throw "Link expired. Password resets are valid for 24 hours"; + } + + var pw; + if(!(pw = resetPassword(rows[0].name))) { + throw "Operation failed. Contact an administrator."; + } + + return [rows[0].name, pw]; +} + +function resetPassword(name) { + var db = getConnection(); + if(!db) { + return false; + } + + var pw = ""; + for(var i = 0; i < 10; i++) { + pw += "abcdefghijklmnopqrstuvwxyz"[parseInt(Math.random() * 25)]; + } + var hash = bcrypt.hashSync(pw, 10); + var query = createQuery( + "UPDATE `registrations` SET `pw`=? WHERE `uname`=?", + [hash, name] + ); + + var results = db.querySync(query); + if(!results) { + return false; + } + + return pw; +} + +/* REGION User Playlists */ +function getUserPlaylists(user) { + var db = getConnection(); + if(!db) { + []; + } + + var query = createQuery( + "SELECT name,count,time FROM user_playlists WHERE user=?", + [user] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to query user playlists"); + return []; + } + + return results.fetchAllSync(); +} + +function loadUserPlaylist(user, name) { + var db = getConnection(); + if(!db) { + []; + } + + var query = createQuery( + "SELECT contents FROM user_playlists WHERE user=? AND name=?", + [user, name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to query playlists"); + return []; + } + + var row = results.fetchAllSync()[0]; + var pl; + try { + pl = JSON.parse(row.contents); + } + catch(e) { + Logger.errlog.log("! Failed to load playlist "+user+"."+name); + return []; + } + + return pl; +} + +function saveUserPlaylist(pl, user, name) { + var db = getConnection(); + if(!db) { + return false; + } + + // Strip out unnecessary data + var pl2 = []; + var time = 0; + for(var i = 0; i < pl.length; i++) { + var e = { + id: pl[i].media.id, + title: pl[i].media.title, + seconds: pl[i].media.seconds, + type: pl[i].media.type + }; + time += pl[i].media.seconds; + pl2.push(e); + } + var count = pl2.length; + var plstr = JSON.stringify(pl2); + + var query = createQuery( + "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?)" + + "ON DUPLICATE KEY UPDATE contents=?,count=?,time=?", + [user, name, plstr, count, time, plstr, count, time] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to insert into playlists"); + return false; + } + + return true; +} + +function deleteUserPlaylist(user, name) { + var db = getConnection(); + if(!db) { + return false; + } + + var query = createQuery( + "DELETE FROM user_playlists WHERE user=? AND name=?", + [user, name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to delete from user_playlists"); + } + return results; +} + +function listUserChannels(user) { + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT * FROM channels WHERE owner=? ORDER BY id ASC", + [user] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to list user channels"); + return []; + } + + return results.fetchAllSync(); +} + +/* User Aliases */ + +function recordVisit(ip, name) { + var db = getConnection(); + if(!db) { + return false; + } + + var time = Date.now(); + db.querySync(createQuery( + "DELETE FROM aliases WHERE ip=? AND name=?", + [ip, name] + )); + var query = createQuery( + "INSERT INTO aliases VALUES (NULL, ?, ?, ?)", + [ip, name, time] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to record visit"); + } + + // Keep most recent 5 records per IP + results = db.querySync(createQuery( + ["DELETE FROM aliases WHERE ip=? AND visit_id NOT IN (", + "SELECT visit_id FROM (", + "SELECT visit_id,time FROM aliases WHERE ip=? ORDER BY time DESC LIMIT 5", + ") foo", + ");"].join(""), + [ip, ip] + )); + + return results; +} + +function getAliases(ip) { + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT name FROM aliases WHERE ip=?", + [ip] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve aliases"); + return []; + } + + var names = []; + results.fetchAllSync().forEach(function(row) { + names.push(row.name); + }); + + return names; +} + +function ipForName(name) { + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT ip FROM aliases WHERE name=?", + [name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve IP for name"); + return []; + } + + var ips = []; + results.fetchAllSync().forEach(function(row) { + ips.push(row.ip); + }); + + return ips; +} + +exports.setup = setup; +exports.getConnection = getConnection; +exports.createQuery = createQuery; +exports.init = init; +exports.checkGlobalBan = checkGlobalBan; +exports.refreshGlobalBans = refreshGlobalBans; +exports.globalBanIP = globalBanIP; +exports.globalUnbanIP = globalUnbanIP; +exports.registerChannel = registerChannel; +exports.loadChannel = loadChannel; +exports.deleteChannel = deleteChannel; +exports.getChannelRank = getChannelRank; +exports.setChannelRank = setChannelRank; +exports.listChannelRanks = listChannelRanks; +exports.addToLibrary = addToLibrary; +exports.removeFromLibrary = removeFromLibrary; +exports.channelBan = channelBan; +exports.channelUnbanIP = channelUnbanIP; +exports.channelUnbanName = channelUnbanName; +exports.setProfile = setProfile; +exports.getProfile = getProfile; +exports.setUserEmail = setUserEmail; +exports.generatePasswordReset = generatePasswordReset; +exports.recoverPassword = recoverPassword; +exports.resetPassword = resetPassword; +exports.getUserPlaylists = getUserPlaylists; +exports.loadUserPlaylist = loadUserPlaylist; +exports.saveUserPlaylist = saveUserPlaylist; +exports.deleteUserPlaylist = deleteUserPlaylist; +exports.listUserChannels = listUserChannels; +exports.recordVisit = recordVisit; +exports.getAliases = getAliases; +exports.ipForName = ipForName; diff --git a/database.js b/database.js index 9da571c3..c445601a 100644 --- a/database.js +++ b/database.js @@ -1,122 +1,80 @@ -/* -The MIT License (MIT) -Copyright (c) 2013 Calvin Montgomery - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +var mysql = require("mysql"); -var mysql = require("mysql-libmysqlclient"); -var Logger = require("./logger"); -var Media = require("./media").Media; -var bcrypt = require("bcrypt"); -var hashlib = require("node_hash"); - -var db = false; -var SERVER = ""; -var USER = ""; -var DATABASE = ""; -var PASSWORD = ""; -var CONFIG = {}; -var global_bans = {}; - -function setup(cfg) { - SERVER = cfg["mysql-server"]; - USER = cfg["mysql-user"]; - DATABASE = cfg["mysql-db"]; - PASSWORD = cfg["mysql-pw"]; - CONFIG = cfg; -} - -function getConnection() { - if(db && db.connectedSync()) { - return db; - } - db = mysql.createConnectionSync(); - db.connectSync(SERVER, USER, PASSWORD, DATABASE); - if(!db.connectedSync()) { - Logger.errlog.log("DB connection failed"); - return false; - } - if(CONFIG["debug"]) { - db._querySync = db.querySync; - db.querySync = function(q) { - Logger.syslog.log("DEBUG: " + q); - return this._querySync(q); +var Logger = { + errlog: { + log: function () { + console.log(arguments[0]); } } - return db; -} +}; -function sqlEscape(obj) { - if(obj === undefined || obj === null) - return "NULL"; +var Database = function (cfg) { + this.cfg = cfg; + this.pool = mysql.createPool({ + host: cfg["mysql-server"], + user: cfg["mysql-user"], + password: cfg["mysql-pw"], + database: cfg["mysql-db"] + }); - if(typeof obj === "boolean") - return obj ? "true" : "false"; + // Test the connection + this.pool.getConnection(function (err, conn) { + if(err) { + Logger.errlog.log("! DB connection failed"); + } + conn.end(); + }); +}; - if(typeof obj === "number") - return obj + ""; +Database.prototype.query = function (query, sub, callback) { + // 2nd argument is optional + if(typeof sub === "function") { + callback = sub; + sub = false; + } - if(typeof obj === "object") - return "'object'"; - - if(typeof obj === "string") { - obj = obj.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { - switch(s) { - case "\0": return "\\0"; - case "\n": return "\\n"; - case "\r": return "\\r"; - case "\b": return "\\b"; - case "\t": return "\\t"; - case "\x1a": return "\\Z"; - default: return "\\" + s; + var self = this; + self.pool.getConnection(function (err, conn) { + if(err) { + callback("Database failure", null); + conn.end(); + } else { + function cback(err, res) { + if(err) { + callback("Database failure", null); + } else { + callback(null, res); + } + conn.end(); } - }); - return "'" + obj + "'"; - } + + if(sub) + conn.query(query, sub, cback); + else { + conn.query(query, cback); + } + } + }); } -function createQuery(template, args) { - var last = -1; - while(template.indexOf("?", last) >= 0) { - var idx = template.indexOf("?", last); - var arg = args.shift(); - arg = sqlEscape(arg); - // Stupid workaround because even if I call replace() with a string - // and not a regex, '$' still holds special meaning - // this actually replaces '$' with '$$' - // What the hell, Javascript? - arg = arg.replace(/\$/g, "$$$$"); - var first = template.substring(0, idx); - template = first + template.substring(idx).replace("?", arg); - last = idx + arg.length; - } - template = template.replace(/`'/g, "`"); - template = template.replace(/'`/g, "`"); - return template; -} - -function init() { - var db = getConnection(); - if(!db) { - return false; - } - +Database.prototype.init = function () { + var self = this; + var query; // Create channel table - var query = ["CREATE TABLE IF NOT EXISTS `channels` (", - "`id` INT NOT NULL AUTO_INCREMENT,", - "`name` VARCHAR(255) NOT NULL,", - "`owner` VARCHAR(20) NOT NULL,", - "PRIMARY KEY(`id`))", - "ENGINE = MyISAM;"].join(""); - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create channels table"); - } + query = ["CREATE TABLE IF NOT EXISTS `channels` (", + "`id` INT NOT NULL AUTO_INCREMENT,", + "`name` VARCHAR(255) NOT NULL,", + "`owner` VARCHAR(20) NOT NULL,", + "PRIMARY KEY(`id`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create channels table"); + } else { + console.log("Created channels table"); + } + }); // Create registration table query = ["CREATE TABLE IF NOT EXISTS `registrations` (", @@ -132,953 +90,13 @@ function init() { "PRIMARY KEY (`id`))", "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create registrations table"); - } - - // Create global bans table - query = ["CREATE TABLE IF NOT EXISTS `global_bans` (", - "`ip` VARCHAR(15) NOT NULL,", - "`note` VARCHAR(255) NOT NULL,", - "PRIMARY KEY (`ip`))", - "ENGINE = MyISAM;"].join(""); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create global ban table"); - } - - refreshGlobalBans(); - - // Create password reset table - query = ["CREATE TABLE IF NOT EXISTS `password_reset` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`hash` VARCHAR(64) NOT NULL,", - "`email` VARCHAR(255) NOT NULL,", - "`expire` BIGINT NOT NULL,", - "PRIMARY KEY (`name`))", - "ENGINE = MyISAM;"].join(""); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create password reset table"); - } - - // Create user playlist table - query = ["CREATE TABLE IF NOT EXISTS `user_playlists` (", - "`user` VARCHAR(20) NOT NULL,", - "`name` VARCHAR(255) NOT NULL,", - "`contents` MEDIUMTEXT NOT NULL,", - "`count` INT NOT NULL,", - "`time` INT NOT NULL,", - "PRIMARY KEY (`user`, `name`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create playlist table"); - } - - // Create user aliases table - query = ["CREATE TABLE IF NOT EXISTS `aliases` (", - "`visit_id` INT NOT NULL AUTO_INCREMENT,", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`visit_id`), INDEX (`ip`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create aliases table"); - } - - // Create action log table - query = ["CREATE TABLE IF NOT EXISTS `actionlog` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`action` VARCHAR(255) NOT NULL,", - "`args` TEXT NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`ip`, `time`), INDEX (`action`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create actionlog table"); - } - - // Create stats table - query = ["CREATE TABLE IF NOT EXISTS `stats` (", - "`time` BIGINT NOT NULL,", - "`usercount` INT NOT NULL,", - "`chancount` INT NOT NULL,", - "`mem` INT NOT NULL,", - "PRIMARY KEY (`time`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create stats table"); - } -} - -/* REGION Global Bans */ - -function checkGlobalBan(ip) { - const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/; - var s16 = ip.replace(re, "$1.$2"); - var s24 = ip.replace(re, "$1.$2.$3"); - return (ip in global_bans || - s16 in global_bans || - s24 in global_bans); -} - -function refreshGlobalBans() { - var db = getConnection(); - if(!db) { - return; - } - - var query = "SELECT * FROM `global_bans` WHERE 1"; - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load global bans"); - } - else { - var rows = results.fetchAllSync(); - global_bans = {}; - for(var i = 0; i < rows.length; i++) { - global_bans[rows[i].ip] = rows[i].note; + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create registration table"); + } else if(self.cfg["debug"]) { + console.log("Created registrations table"); } - } - return global_bans; -} - -function globalBanIP(ip, reason) { - var db = getConnection(); - if(!db) { - return; - } - - var query = createQuery( - "INSERT INTO `global_bans` VALUES (?, ?)", - [ip, reason] - ); - return db.querySync(query); -} - -function globalUnbanIP(ip) { - var db = getConnection(); - if(!db) { - return; - } - - var query = createQuery( - "DELETE FROM `global_bans` WHERE ip=?", - [ip] - ); - - return db.querySync(query); -} - -/* REGION Channel Registration/Loading */ - -function registerChannel(name, owner) { - if(!name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - // Library table - var query = ["CREATE TABLE `?` (", - "`id` VARCHAR(255) NOT NULL,", - "`title` VARCHAR(255) NOT NULL,", - "`seconds` INT NOT NULL,", - "`type` VARCHAR(2) NOT NULL,", - "PRIMARY KEY (`id`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_library"]); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_library"); - return false; - } - - // Rank table - query = ["CREATE TABLE `?` (", - "`name` VARCHAR(32) NOT NULL,", - "`rank` INT NOT NULL,", - "UNIQUE (`name`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_ranks"]); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_ranks"); - return false; - } - - // Ban table - query = ["CREATE TABLE `?` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(32) NOT NULL,", - "`banner` VARCHAR(32) NOT NULL,", - "PRIMARY KEY (`ip`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_bans"]); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_bans"); - return false; - } - - // Insert into channel table - query = createQuery( - "INSERT INTO `channels` VALUES (NULL, ?, ?)", - [name, owner] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert into channel table: " + name); - return false; - } - - return true; -} - -function loadChannel(chan) { - if(!chan.name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT * FROM `channels` WHERE name=?", - [chan.name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query channel table"); - } - else { - var rows = results.fetchAllSync(); - if(rows.length == 0) { - // Unregistered - Logger.syslog.log("Channel " + chan.name + " is unregistered"); - return; - } - // Database is case insensitive - else if(rows[0].name != chan.name) { - chan.name = rows[0].name; - } - chan.registered = true; - } - - // Load channel library - query = createQuery( - "SELECT * FROM `?`", - ["chan_" + chan.name + "_library"] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load channel library: " + chan.name); - } - else { - var rows = results.fetchAllSync(); - for(var i = 0; i < rows.length; i++) { - var r = rows[i]; - var m = new Media(r.id, r.title, r.seconds, r.type); - chan.library[r.id] = m; - } - } - - // Load channel bans - query = createQuery( - "SELECT * FROM `?`", - ["chan_" + chan.name + "_bans"] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load channel bans: " + chan.name); - } - else { - var rows = results.fetchAllSync(); - for(var i = 0; i < rows.length; i++) { - var r = rows[i]; - if(r.ip == "*") { - chan.namebans[r.name] = r.banner; - } - else { - chan.ipbans[r.ip] = [r.name, r.banner]; - } - } - } - - chan.logger.log("*** Loaded channel from database"); - Logger.syslog.log("Loaded channel " + chan.name + " from database"); -} - -function deleteChannel(name) { - if(!name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - - var db = getConnection(); - if(!db) { - return false; - } - - var query = "DROP TABLE `chan_?_bans`, `chan_?_ranks`, `chan_?_library`" - .replace(/\?/g, name); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete channel tables for " + name); - return false; - } - - query = createQuery( - "DELETE FROM `channels` WHERE name=?", - [name] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete row from channel table: " + name); - return false; - } - - return true; -} - -/* REGION Channel data */ - -function getChannelRank(chan, name) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return 0; - } - - var query; - if(typeof name == "object") { - var n = "(?"; - for(var i = 1; i < name.length; i++) { - n += ",?"; - } - n += ")" - name.unshift("chan_" + chan + "_ranks"); - query = createQuery( - "SELECT * FROM `?` WHERE name IN " + n, - name - ); - } - else { - query = createQuery( - "SELECT * FROM `?` WHERE name=?", - ["chan_"+chan+"_ranks", name] - ); - } - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to lookup chan_"+chan+"_ranks"); - return 0; - } - - var rows = results.fetchAllSync(); - if(typeof name == "object") { - var ranks = []; - for(var i = 0; i < rows.length; i++) { - ranks.push(rows[i].rank); - } - while(ranks.length < rows.length) { - ranks.push(0); - } - return ranks; - } - if(rows.length == 0) { - return 0; - } - - return rows[0].rank; -} - -function setChannelRank(chan, name, rank) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`name`, `rank`) ", - "VALUES ", - "(?, ?) ", - "ON DUPLICATE KEY UPDATE ", - "`rank`=?"].join(""), - ["chan_"+chan+"_ranks", name, rank, rank] - ); - - return db.querySync(query); -} - -function listChannelRanks(chan) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return []; - } - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT * FROM `?` WHERE 1", - ["chan_"+chan+"_ranks"] - ); - - var results = db.querySync(query); - if(!results) { - return []; - } - - return results.fetchAllSync(); -} - -function addToLibrary(chan, media) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`id`, `title`, `seconds`, `type`) ", - "VALUES ", - "(?, ?, ?, ?)"].join(""), - ["chan_"+chan+"_library", media.id, media.title, media.seconds, media.type] - ); - - return db.querySync(query); -} - -function removeFromLibrary(chan, id) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE id=?", - ["chan_"+chan+"_library", id] - ); - - return db.querySync(query); -} - -function channelBan(chan, ip, name, banby) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`ip`, `name`, `banner`) ", - "VALUES ", - "(?, ?, ?)"].join(""), - ["chan_"+chan+"_bans", ip, name, banby] - ); - - return db.querySync(query); -} - -function channelUnbanIP(chan, ip) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE `ip`=?", - ["chan_"+chan+"_bans", ip] - ); - - return db.querySync(query); -} - -function channelUnbanName(chan, name) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE `ip`='*' AND `name`=?", - ["chan_"+chan+"_bans", name] - ); - - return db.querySync(query); -} - -/* REGION Users */ - -function getProfile(name) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT profile_image,profile_text FROM registrations WHERE uname=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve user profile"); - throw "Database failure. Contact an administrator."; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "User not found"; - } - - return { - profile_image: rows[0].profile_image, - profile_text: rows[0].profile_text - }; -} - -function setProfile(name, data) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["UPDATE `registrations` SET ", - "`profile_image`=?,", - "`profile_text`=? ", - "WHERE uname=?"].join(""), - [data.image, data.text, name] - ); - - return db.querySync(query); -} - -function setUserEmail(name, email) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "UPDATE `registrations` SET `email`=? WHERE `uname`=?", - [email, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to set user email"); - return false; - } - return true; -} - -function genSalt() { - var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - + "0123456789!@#$%^&*_+=~"; - var salt = []; - for(var i = 0; i < 32; i++) { - salt.push(chars[parseInt(Math.random()*chars.length)]); - } - return salt.join(''); -} - -function generatePasswordReset(ip, name, email) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT `email` FROM `registrations` WHERE `uname`=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve user email"); - return false; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "Provided username does not exist"; - } - if(rows[0].email != email) { - throw "Provided email does not match user's email"; - } - - // Validation complete, now time to reset it - var hash = hashlib.sha256(genSalt() + name); - var exp = Date.now() + 24*60*60*1000; - query = createQuery( - ["INSERT INTO `password_reset` (", - "`ip`, `name`, `hash`, `email`, `expire`", - ") VALUES (", - "?, ?, ?, ?, ?", - ") ON DUPLICATE KEY UPDATE `hash`=?,`expire`=?"].join(""), - [ip, name, hash, email, exp, hash, exp] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert password reset"); - return false; - } - - return hash; -} - -function recoverPassword(hash) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT * FROM password_reset WHERE hash=?", - [hash] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve from password_reset"); - throw "Database error. Contact an administrator"; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "Invalid password reset link"; - } - - db.querySync(createQuery( - "DELETE FROM password_reset WHERE hash=?", - [hash] - )); - - if(Date.now() > rows[0].expire) { - throw "Link expired. Password resets are valid for 24 hours"; - } - - var pw; - if(!(pw = resetPassword(rows[0].name))) { - throw "Operation failed. Contact an administrator."; - } - - return [rows[0].name, pw]; -} - -function resetPassword(name) { - var db = getConnection(); - if(!db) { - return false; - } - - var pw = ""; - for(var i = 0; i < 10; i++) { - pw += "abcdefghijklmnopqrstuvwxyz"[parseInt(Math.random() * 25)]; - } - var hash = bcrypt.hashSync(pw, 10); - var query = createQuery( - "UPDATE `registrations` SET `pw`=? WHERE `uname`=?", - [hash, name] - ); - - var results = db.querySync(query); - if(!results) { - return false; - } - - return pw; -} - -/* REGION User Playlists */ -function getUserPlaylists(user) { - var db = getConnection(); - if(!db) { - []; - } - - var query = createQuery( - "SELECT name,count,time FROM user_playlists WHERE user=?", - [user] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query user playlists"); - return []; - } - - return results.fetchAllSync(); -} - -function loadUserPlaylist(user, name) { - var db = getConnection(); - if(!db) { - []; - } - - var query = createQuery( - "SELECT contents FROM user_playlists WHERE user=? AND name=?", - [user, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query playlists"); - return []; - } - - var row = results.fetchAllSync()[0]; - var pl; - try { - pl = JSON.parse(row.contents); - } - catch(e) { - Logger.errlog.log("! Failed to load playlist "+user+"."+name); - return []; - } - - return pl; -} - -function saveUserPlaylist(pl, user, name) { - var db = getConnection(); - if(!db) { - return false; - } - - // Strip out unnecessary data - var pl2 = []; - var time = 0; - for(var i = 0; i < pl.length; i++) { - var e = { - id: pl[i].media.id, - title: pl[i].media.title, - seconds: pl[i].media.seconds, - type: pl[i].media.type - }; - time += pl[i].media.seconds; - pl2.push(e); - } - var count = pl2.length; - var plstr = JSON.stringify(pl2); - - var query = createQuery( - "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?)" + - "ON DUPLICATE KEY UPDATE contents=?,count=?,time=?", - [user, name, plstr, count, time, plstr, count, time] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert into playlists"); - return false; - } - - return true; -} - -function deleteUserPlaylist(user, name) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM user_playlists WHERE user=? AND name=?", - [user, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete from user_playlists"); - } - return results; -} - -function listUserChannels(user) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT * FROM channels WHERE owner=? ORDER BY id ASC", - [user] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to list user channels"); - return []; - } - - return results.fetchAllSync(); -} - -/* User Aliases */ - -function recordVisit(ip, name) { - var db = getConnection(); - if(!db) { - return false; - } - - var time = Date.now(); - db.querySync(createQuery( - "DELETE FROM aliases WHERE ip=? AND name=?", - [ip, name] - )); - var query = createQuery( - "INSERT INTO aliases VALUES (NULL, ?, ?, ?)", - [ip, name, time] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to record visit"); - } - - // Keep most recent 5 records per IP - results = db.querySync(createQuery( - ["DELETE FROM aliases WHERE ip=? AND visit_id NOT IN (", - "SELECT visit_id FROM (", - "SELECT visit_id,time FROM aliases WHERE ip=? ORDER BY time DESC LIMIT 5", - ") foo", - ");"].join(""), - [ip, ip] - )); - - return results; -} - -function getAliases(ip) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT name FROM aliases WHERE ip=?", - [ip] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve aliases"); - return []; - } - - var names = []; - results.fetchAllSync().forEach(function(row) { - names.push(row.name); }); +}; - return names; -} - -function ipForName(name) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT ip FROM aliases WHERE name=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve IP for name"); - return []; - } - - var ips = []; - results.fetchAllSync().forEach(function(row) { - ips.push(row.ip); - }); - - return ips; -} - -exports.setup = setup; -exports.getConnection = getConnection; -exports.createQuery = createQuery; -exports.init = init; -exports.checkGlobalBan = checkGlobalBan; -exports.refreshGlobalBans = refreshGlobalBans; -exports.globalBanIP = globalBanIP; -exports.globalUnbanIP = globalUnbanIP; -exports.registerChannel = registerChannel; -exports.loadChannel = loadChannel; -exports.deleteChannel = deleteChannel; -exports.getChannelRank = getChannelRank; -exports.setChannelRank = setChannelRank; -exports.listChannelRanks = listChannelRanks; -exports.addToLibrary = addToLibrary; -exports.removeFromLibrary = removeFromLibrary; -exports.channelBan = channelBan; -exports.channelUnbanIP = channelUnbanIP; -exports.channelUnbanName = channelUnbanName; -exports.setProfile = setProfile; -exports.getProfile = getProfile; -exports.setUserEmail = setUserEmail; -exports.generatePasswordReset = generatePasswordReset; -exports.recoverPassword = recoverPassword; -exports.resetPassword = resetPassword; -exports.getUserPlaylists = getUserPlaylists; -exports.loadUserPlaylist = loadUserPlaylist; -exports.saveUserPlaylist = saveUserPlaylist; -exports.deleteUserPlaylist = deleteUserPlaylist; -exports.listUserChannels = listUserChannels; -exports.recordVisit = recordVisit; -exports.getAliases = getAliases; -exports.ipForName = ipForName; +module.exports = Database; diff --git a/package.json b/package.json index faa921eb..320f7c7f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "socket.io": ">=0.9", "express": ">=3.2", - "mysql-libmysqlclient": "*", + "mysql": "2.0.0-alpha8", "node_hash": "*", "bcrypt": "*", "nodemailer": "*", From 8822fc5206d9583207ff2023b58cf3c96db086c8 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Mon, 12 Aug 2013 10:58:21 -0400 Subject: [PATCH 12/60] Continue work on db --- database.js | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/database.js b/database.js index c445601a..c51bb8f0 100644 --- a/database.js +++ b/database.js @@ -24,6 +24,8 @@ var Database = function (cfg) { } conn.end(); }); + + this.global_ipbans = {}; }; Database.prototype.query = function (query, sub, callback) { @@ -41,6 +43,9 @@ Database.prototype.query = function (query, sub, callback) { } else { function cback(err, res) { if(err) { + if(self.cfg["debug"]) { + console.log(err); + } callback("Database failure", null); } else { callback(null, res); @@ -97,6 +102,183 @@ Database.prototype.init = function () { console.log("Created registrations table"); } }); + + // Create global bans table + query = ["CREATE TABLE IF NOT EXISTS `global_bans` (", + "`ip` VARCHAR(15) NOT NULL,", + "`note` VARCHAR(255) NOT NULL,", + "PRIMARY KEY (`ip`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create global ban table"); + } else if(self.cfg["debug"]) { + console.log("Created global ban table"); + } + }); + + // Create password reset table + query = ["CREATE TABLE IF NOT EXISTS `password_reset` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`hash` VARCHAR(64) NOT NULL,", + "`email` VARCHAR(255) NOT NULL,", + "`expire` BIGINT NOT NULL,", + "PRIMARY KEY (`name`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create password reset table"); + } else if(self.cfg["debug"]) { + console.log("Created password reset table"); + } + }); + + // Create user playlist table + query = ["CREATE TABLE IF NOT EXISTS `user_playlists` (", + "`user` VARCHAR(20) NOT NULL,", + "`name` VARCHAR(255) NOT NULL,", + "`contents` MEDIUMTEXT NOT NULL,", + "`count` INT NOT NULL,", + "`time` INT NOT NULL,", + "PRIMARY KEY (`user`, `name`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create user playlist table"); + } else if(self.cfg["debug"]) { + console.log("Created user playlist table"); + } + }); + + // Create user aliases table + query = ["CREATE TABLE IF NOT EXISTS `aliases` (", + "`visit_id` INT NOT NULL AUTO_INCREMENT,", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`time` BIGINT NOT NULL,", + "PRIMARY KEY (`visit_id`), INDEX (`ip`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create user aliases table"); + } else if(self.cfg["debug"]) { + console.log("Created user aliases table"); + } + }); + + // Create action log table + query = ["CREATE TABLE IF NOT EXISTS `actionlog` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(20) NOT NULL,", + "`action` VARCHAR(255) NOT NULL,", + "`args` TEXT NOT NULL,", + "`time` BIGINT NOT NULL,", + "PRIMARY KEY (`ip`, `time`), INDEX (`action`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create action log table"); + } else if(self.cfg["debug"]) { + console.log("Created action log table"); + } + }); + + // Create stats table + query = ["CREATE TABLE IF NOT EXISTS `stats` (", + "`time` BIGINT NOT NULL,", + "`usercount` INT NOT NULL,", + "`chancount` INT NOT NULL,", + "`mem` INT NOT NULL,", + "PRIMARY KEY (`time`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to create stats table"); + } else if(self.cfg["debug"]) { + console.log("Created stats table"); + } + }); + + // Refresh global IP bans + self.getGlobalIPBans(); }; +/* REGION global bans */ + +Database.prototype.isGlobalIPBanned = function (ip, callback) { + if(typeof callback !== "function") + return; + const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/; + // Account for range banning + var s16 = ip.replace(re, "$1.$2"); + var s24 = ip.replace(re, "$1.$2.$3"); + + var banned = ip in this.global_ipbans || + s16 in this.global_ipbans || + s24 in this.global_ipbans; + + callback(null, banned); +}; + +Database.prototype.getGlobalIPBans = function (callback) { + if(typeof callback !== "function") + callback = function () { } + var self = this; + self.query("SELECT * FROM global_bans WHERE 1", function (err, res) { + if(err) { + callback(err, null); + return; + } + + self.global_ipbans = {}; + for(var i in res) { + self.global_ipbans[res[i].ip] = res[i].note; + } + + callback(null, self.global_ipbans); + }); +}; + +Database.prototype.setGlobalIPBan = function (ip, reason, callback) { + if(typeof callback !== "function") + callback = function () { } + var query = "INSERT INTO global_bans VALUES (?, ?)" + + " ON DUPLICATE KEY UPDATE note=?"; + var self = this; + self.query(query, [ip, reason, reason], function (err, res) { + if(err) { + callback(err, null); + return; + } + + self.getGlobalIPBans(); + callback(null, res); + }); +}; + +Database.prototype.clearGlobalIPBan = function (ip, callback) { + if(typeof callback !== "function") + callback = function () { } + var self = this; + + var query = "DELETE FROM global_bans WHERE ip=?"; + self.query(query, [ip], function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, res); + }); +}; + +/* END REGION */ + module.exports = Database; From a1127006003897ae3f0917fc571968ef3894ca2a Mon Sep 17 00:00:00 2001 From: calzoneman Date: Mon, 12 Aug 2013 14:23:32 -0400 Subject: [PATCH 13/60] Work on channel db stuff --- database.js | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/database.js b/database.js index c51bb8f0..57f773be 100644 --- a/database.js +++ b/database.js @@ -281,4 +281,203 @@ Database.prototype.clearGlobalIPBan = function (ip, callback) { /* END REGION */ +/* REGION channels */ +Database.prototype.registerChannel = function (name, owner, callback) { + if(typeof callback !== "function") + callback = function () { } + + if(!name.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var self = this; + + // I'm tempted to add a promise library to the dependencies + // just to solve this mess + + var query = "SELECT * FROM channels WHERE name=?"; + self.query(query, [name], function (err, res) { + if(!err && res.length > 0) { + callback("Channel already exists", null); + return; + } + + // Library table + query = ["CREATE TABLE `chan_" + name + "_library` (", + "`id` VARCHAR(255) NOT NULL,", + "`title` VARCHAR(255) NOT NULL,", + "`seconds` INT NOT NULL,", + "`type` VARCHAR(2) NOT NULL,", + "PRIMARY KEY (`id`))", + "ENGINE = MyISAM;"].join(""); + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + // Rank table + query = ["CREATE TABLE `chan_" + name + "_ranks` (", + "`name` VARCHAR(32) NOT NULL,", + "`rank` INT NOT NULL,", + "UNIQUE (`name`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + // Ban table + query = ["CREATE TABLE `chan_" + name + "_bans` (", + "`ip` VARCHAR(15) NOT NULL,", + "`name` VARCHAR(32) NOT NULL,", + "`banner` VARCHAR(32) NOT NULL,", + "PRIMARY KEY (`ip`))", + "ENGINE = MyISAM;"].join(""); + + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + query = "INSERT INTO channels VALUES (NULL, ?, ?)"; + self.query(query, [name, owner], function (err, res) { + callback(err, res); + }); + }); + }); + }); + }); +}; + +Database.prototype.loadChannelData = function (chan, callback) { + if(!chan.name.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var self = this; + var query = "SELECT * FROM channels WHERE name=?"; + + self.query(query, [chan.name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("Channel is unregistered", null); + return; + } + + if(res[0].name != chan.name) + chan.name = rows[0].name; + chan.registered = true; + + // Load bans + query = "SELECT * FROM `chan_" + chan.name + "_bans`"; + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + for(var i in res) { + var r = res[i]; + if(r.ip === "*") + chan.namebans[r.name] = r.banner; + else + chan.ipbans[r.ip] = [r.name, r.banner]; + } + + chan.logger.log("*** Loaded channel from database"); + callback(null, true); + }); + }); +}; + +Database.prototype.dropChannel = function (name, callback) { + if(!name.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var self = this; + var query = "DROP TABLE `chan_?_bans`,`chan_?_ranks`,`chan_?_library`" + .replace(/\?/g, name); + + self.query(query, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to drop channel tables for "+name); + callback(err, null); + return; + } + + query = "DELETE FROM channels WHERE name=?"; + self.query(query, [name], function (err, res) { + callback(err, res); + if(err) { + Logger.errlog.log("! Failed to delete channel "+name); + } + }); + }); +}; + +Database.prototype.getChannelRank = function (channame, names, callback) { + if(typeof names === "string") + names = [names]; + + var self = this; + + // Build the query template (?, ?, ?, ?, ...) + var nlist = []; + for(var i in names) + nlist.push("?"); + nlist = "(" + nlist.join(",") + ")"; + + var query = "SELECT name, rank FROM `chan_" + channame + "_ranks`" + + "WHERE name IN " + nlist; + + self.query(query, names, function (err, res) { + if(err) { + Logger.errlog.log("! Failed to lookup " + channame + " ranks"); + if(names.length == 1) + callback(err, 0); + else + callback(err, []); + return; + } + + if(names.length == 1) { + if(res.length == 0) + callback(null, 0); + else + callback(null, res[0].rank); + return; + } + + callback(null, res); + }); +}; + +Database.prototype.setChannelRank = function (channame, name, rank, callback) { + if(!channame.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var self = this; + var query = "INSERT INTO `chan_" + channame + "_ranks` " + + "(name, rank) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE rank=?"; + + self.query(query, [name, rank, rank], function (err, res) { + callback(err, res); + }); +}; + module.exports = Database; From 9675edadf555013dee86fc849588047857a1faed Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 12 Aug 2013 23:23:10 -0400 Subject: [PATCH 14/60] Continue migrating/refactoring --- database.js | 137 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 119 insertions(+), 18 deletions(-) diff --git a/database.js b/database.js index 57f773be..384dcfc8 100644 --- a/database.js +++ b/database.js @@ -228,9 +228,10 @@ Database.prototype.isGlobalIPBanned = function (ip, callback) { }; Database.prototype.getGlobalIPBans = function (callback) { - if(typeof callback !== "function") - callback = function () { } var self = this; + if(typeof callback !== "function") + callback = function () { }; + self.query("SELECT * FROM global_bans WHERE 1", function (err, res) { if(err) { callback(err, null); @@ -247,11 +248,12 @@ Database.prototype.getGlobalIPBans = function (callback) { }; Database.prototype.setGlobalIPBan = function (ip, reason, callback) { + var self = this; if(typeof callback !== "function") - callback = function () { } + callback = function () { }; + var query = "INSERT INTO global_bans VALUES (?, ?)" + " ON DUPLICATE KEY UPDATE note=?"; - var self = this; self.query(query, [ip, reason, reason], function (err, res) { if(err) { callback(err, null); @@ -264,9 +266,10 @@ Database.prototype.setGlobalIPBan = function (ip, reason, callback) { }; Database.prototype.clearGlobalIPBan = function (ip, callback) { - if(typeof callback !== "function") - callback = function () { } var self = this; + if(typeof callback !== "function") + callback = function () { }; + var query = "DELETE FROM global_bans WHERE ip=?"; self.query(query, [ip], function (err, res) { @@ -282,20 +285,28 @@ Database.prototype.clearGlobalIPBan = function (ip, callback) { /* END REGION */ /* REGION channels */ -Database.prototype.registerChannel = function (name, owner, callback) { +Database.prototype.channelExists = function (name, callback) { + var self = this; if(typeof callback !== "function") - callback = function () { } + return; + + var query = "SELECT name FROM channels WHERE name=?"; + self.query(query, [name], function (err, res) { + callback(err, res.length > 0); + }); +}; + +Database.prototype.registerChannel = function (name, owner, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; if(!name.match(/^[\w-_]+$/)) { callback("Invalid channel name", null); return; } - var self = this; - - // I'm tempted to add a promise library to the dependencies - // just to solve this mess - + // Messy, but I can't think of a better async solution atm var query = "SELECT * FROM channels WHERE name=?"; self.query(query, [name], function (err, res) { if(!err && res.length > 0) { @@ -355,12 +366,15 @@ Database.prototype.registerChannel = function (name, owner, callback) { }; Database.prototype.loadChannelData = function (chan, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + if(!chan.name.match(/^[\w-_]+$/)) { callback("Invalid channel name", null); return; } - var self = this; var query = "SELECT * FROM channels WHERE name=?"; self.query(query, [chan.name], function (err, res) { @@ -401,12 +415,15 @@ Database.prototype.loadChannelData = function (chan, callback) { }; Database.prototype.dropChannel = function (name, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + if(!name.match(/^[\w-_]+$/)) { callback("Invalid channel name", null); return; } - var self = this; var query = "DROP TABLE `chan_?_bans`,`chan_?_ranks`,`chan_?_library`" .replace(/\?/g, name); @@ -428,11 +445,13 @@ Database.prototype.dropChannel = function (name, callback) { }; Database.prototype.getChannelRank = function (channame, names, callback) { + var self = this; + if(typeof callback !== "function") + return; + if(typeof names === "string") names = [names]; - var self = this; - // Build the query template (?, ?, ?, ?, ...) var nlist = []; for(var i in names) @@ -465,12 +484,15 @@ Database.prototype.getChannelRank = function (channame, names, callback) { }; Database.prototype.setChannelRank = function (channame, name, rank, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + if(!channame.match(/^[\w-_]+$/)) { callback("Invalid channel name", null); return; } - var self = this; var query = "INSERT INTO `chan_" + channame + "_ranks` " + "(name, rank) VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE rank=?"; @@ -480,4 +502,83 @@ Database.prototype.setChannelRank = function (channame, name, rank, callback) { }); }; +Database.prototype.listChannelRanks = function (channame, callback) { + var self = this; + if(typeof callback !== "function") + return; + + if(!channame.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var query = "SELECT * FROM `chan_" + channame + "_ranks` WHERE 1"; + self.query(query, function (err, res) { + callback(err, res); + }); +}; + +Database.prototype.addToLibrary = function (channame, media, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + + if(!channame.match(/^[\w-_]+$/)) { + callback("Invalid channel name"); + return; + } + + var query = "INSERT INTO `chan_" + channame + "_ranks`" + + "(id, title, seconds, type) " + + "VALUES (?, ?, ?, ?)"; + var params = [ + media.id, + media.title, + media.seconds, + media.type + ]; + self.query(query, params, function (err, res) { + callback(err, res); + }); +}; + +Database.prototype.removeFromLibrary = function (channame, id, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + + if(!channame.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var query = "DELETE FROM `chan_" + channame + "_library` WHERE id=?"; + self.query(query, [id], function (err, res) { + callback(err, res); + }); +}; + +Database.prototype.getLibraryItem = function (channame, id, callback) { + var self = this; + if(typeof callback !== "function") + callback = function () { }; + + if(!channame.match(/^[\w-_]+$/)) { + callback("Invalid channel name", null); + return; + } + + var query = "SELECT id, title, seconds, type FROM " + + "`chan_" + channame + "_library` WHERE id=?"; + + self.query(query, [id], function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, res.length > 0 ? res[0] : null); + }); +}; + module.exports = Database; From 8a2dfa004d2e46afc02bd69a2310f5c4d1a2784b Mon Sep 17 00:00:00 2001 From: calzoneman Date: Wed, 14 Aug 2013 22:51:59 -0500 Subject: [PATCH 15/60] Continue work on refactoring, add utilities module --- database.js | 158 +++++++++++++++++++++++++++++++++++++++++---------- utilities.js | 15 +++++ 2 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 utilities.js diff --git a/database.js b/database.js index 384dcfc8..2987f703 100644 --- a/database.js +++ b/database.js @@ -1,4 +1,5 @@ var mysql = require("mysql"); +var $util = require("./utilities"); var Logger = { errlog: { @@ -62,6 +63,10 @@ Database.prototype.query = function (query, sub, callback) { }); } +function blackHole() { + +} + Database.prototype.init = function () { var self = this; var query; @@ -230,7 +235,7 @@ Database.prototype.isGlobalIPBanned = function (ip, callback) { Database.prototype.getGlobalIPBans = function (callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; self.query("SELECT * FROM global_bans WHERE 1", function (err, res) { if(err) { @@ -250,7 +255,7 @@ Database.prototype.getGlobalIPBans = function (callback) { Database.prototype.setGlobalIPBan = function (ip, reason, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; var query = "INSERT INTO global_bans VALUES (?, ?)" + " ON DUPLICATE KEY UPDATE note=?"; @@ -268,7 +273,7 @@ Database.prototype.setGlobalIPBan = function (ip, reason, callback) { Database.prototype.clearGlobalIPBan = function (ip, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; var query = "DELETE FROM global_bans WHERE ip=?"; @@ -289,6 +294,10 @@ Database.prototype.channelExists = function (name, callback) { var self = this; if(typeof callback !== "function") return; + if(!$util.isValidChannelName(name)) { + callback("Invalid channel name", null); + return; + } var query = "SELECT name FROM channels WHERE name=?"; self.query(query, [name], function (err, res) { @@ -299,9 +308,9 @@ Database.prototype.channelExists = function (name, callback) { Database.prototype.registerChannel = function (name, owner, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!name.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(name)) { callback("Invalid channel name", null); return; } @@ -368,9 +377,9 @@ Database.prototype.registerChannel = function (name, owner, callback) { Database.prototype.loadChannelData = function (chan, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!chan.name.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(chan.name)) { callback("Invalid channel name", null); return; } @@ -417,9 +426,9 @@ Database.prototype.loadChannelData = function (chan, callback) { Database.prototype.dropChannel = function (name, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!name.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(name)) { callback("Invalid channel name", null); return; } @@ -449,6 +458,11 @@ Database.prototype.getChannelRank = function (channame, names, callback) { if(typeof callback !== "function") return; + if(!$util.isValidChannelName(channame)) { + callback("Invalid channel name", null); + return; + } + if(typeof names === "string") names = [names]; @@ -486,9 +500,9 @@ Database.prototype.getChannelRank = function (channame, names, callback) { Database.prototype.setChannelRank = function (channame, name, rank, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!channame.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name", null); return; } @@ -497,9 +511,7 @@ Database.prototype.setChannelRank = function (channame, name, rank, callback) { "(name, rank) VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE rank=?"; - self.query(query, [name, rank, rank], function (err, res) { - callback(err, res); - }); + self.query(query, [name, rank, rank], callback); }; Database.prototype.listChannelRanks = function (channame, callback) { @@ -507,23 +519,21 @@ Database.prototype.listChannelRanks = function (channame, callback) { if(typeof callback !== "function") return; - if(!channame.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name", null); return; } var query = "SELECT * FROM `chan_" + channame + "_ranks` WHERE 1"; - self.query(query, function (err, res) { - callback(err, res); - }); + self.query(query, callback); }; Database.prototype.addToLibrary = function (channame, media, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!channame.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name"); return; } @@ -537,33 +547,29 @@ Database.prototype.addToLibrary = function (channame, media, callback) { media.seconds, media.type ]; - self.query(query, params, function (err, res) { - callback(err, res); - }); + self.query(query, params, callback); }; Database.prototype.removeFromLibrary = function (channame, id, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!channame.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name", null); return; } var query = "DELETE FROM `chan_" + channame + "_library` WHERE id=?"; - self.query(query, [id], function (err, res) { - callback(err, res); - }); + self.query(query, [id], callback); }; Database.prototype.getLibraryItem = function (channame, id, callback) { var self = this; if(typeof callback !== "function") - callback = function () { }; + callback = blackHole; - if(!channame.match(/^[\w-_]+$/)) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name", null); return; } @@ -581,4 +587,96 @@ Database.prototype.getLibraryItem = function (channame, id, callback) { }); }; +Database.prototype.addChannelBan = function (channame, ip, name, banBy, + callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + if(!$util.isValidChannelName(channame))) { + callback("Invalid channel name", null); + return; + } + + var query = "INSERT INTO `chan_" + channame + "_bans`" + + "(ip, name, banner) VALUES (?, ?, ?)"; + + self.query(query, [ip, name, banBy], callback); +}; + +Database.prototype.clearChannelIPBan = function (channame, ip, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + if(!$util.isValidChannelName(channame)) { + callback("Invalid channel name", null); + return; + } + + var query = "DELETE FROM `chan_" + channame + "_bans` WHERE ip=?"; + self.query(query, [ip], callback); +}; + +Database.prototype.clearChannelNameBan = function (channame, name, + callback) { + var self = this; + if(typeof callback !== "function") { + callback = blackHole; + return; + } + + var query = "DELETE FROM `chan_" + channame + "_bans` WHERE ip='*'" + + "AND name=?"; + + self.query(query, [name], callback); +}; + +/* END REGION */ + +/* REGION users */ + +Database.prototype.getUserProfile = function (name, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "SELECT profile_image, profile_text FROM registrations " + + "WHERE uname=?"; + + self.query(query, [name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + var def = { + profile_image: "", + profile_text: "" + }; + + callback(null, res.length > 0 ? res[0] : def); + }); +}; + +Database.prototype.setUserProfile = function (name, data, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "UPDATE registrations SET profile_image=?, profile_text=?" + + "WHERE uname=?"; + + self.query(query, [data.image, data.text, name], callback); +}; + +Database.prototype.setUserEmail = function (name, email, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "UPDATE registrations SET email=? WHERE uname=?"; + + self.query(query, [email, name], callback); +}; module.exports = Database; diff --git a/utilities.js b/utilities.js new file mode 100644 index 00000000..d0c08596 --- /dev/null +++ b/utilities.js @@ -0,0 +1,15 @@ +module.exports = { + isValidChannelName: function (name) { + return name.match(/^[\w-_]+$/); + }, + + randomSalt: function (length) { + var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "0123456789!@#$%^&*_+=~"; + var salt = []; + for(var i = 0; i < length; i++) { + salt.push(chars[parseInt(Math.random()*chars.length)]); + } + return salt.join(''); + } +}; From be315e9e23e5aee0c25048ff799ae01b96186633 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Wed, 14 Aug 2013 22:52:21 -0500 Subject: [PATCH 16/60] Remove syntax error --- database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database.js b/database.js index 2987f703..a64be2b5 100644 --- a/database.js +++ b/database.js @@ -593,7 +593,7 @@ Database.prototype.addChannelBan = function (channame, ip, name, banBy, if(typeof callback !== "function") callback = blackHole; - if(!$util.isValidChannelName(channame))) { + if(!$util.isValidChannelName(channame)) { callback("Invalid channel name", null); return; } From 1b9c707bdf0156ed491c3688fd1eeafb55494f44 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 12:18:13 -0500 Subject: [PATCH 17/60] Refactor some password functions and user playlists --- database.js | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/database.js b/database.js index a64be2b5..06b29790 100644 --- a/database.js +++ b/database.js @@ -1,4 +1,6 @@ var mysql = require("mysql"); +var hashlib = require("node_hash"); +var bcrypt = require("bcrypt"); var $util = require("./utilities"); var Logger = { @@ -679,4 +681,182 @@ Database.prototype.setUserEmail = function (name, email, callback) { self.query(query, [email, name], callback); }; + +Database.prototype.genPasswordReset = function (ip, name, email, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "SELECT email FROM registrations WHERE uname=?"; + self.query(query, [name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("Provided username does not exist", null); + return; + } + + if(res[0].email != email) { + callback("Provided email does not match user's email", null); + return; + } + + var hash = hashlib.sha256($util.randomSalt(32) + name); + var expire = Date.now() + 24*60*60*1000; + query = "INSERT INTO password_reset " + + "(ip, name, hash, email, expire) VALUES (?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE hash=?, expire=?"; + self.query(query, [ip, name, hash, email, exp, hash, exp], + function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, hash); + }); + }); +}; + +Database.prototype.recoverUserPassword = function (hash, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "SELECT * FROM password_reset WHERE hash=?"; + self.query(query, [hash], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(rows.length == 0) { + callback("Invalid password reset link", null); + return; + } + + if(Date.now() > res[0].expire) { + self.query("DELETE FROM password_reset WHERE hash=?", [hash]); + callback("Link expired. Password resets are valid for 24hr", + null); + return; + } + + var name = res[0].name; + + self.resetUserPassword(res[0].name, function (err, pw) { + if(err) { + callback(err, null); + return; + } + + self.query("DELETE FROM password_reset WHERE hash=?", [hash]); + callback(null, { + name: name, + pw: pw + }); + }); + }); +}; + +Database.prototype.resetUserPassword = function (name, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var pwChars = "abcdefghijkmnopqrstuvwxyz023456789"; + var pw = ""; + for(var i = 0; i < 10; i++) + pw += pwChars[parseInt(Math.random() * 33)]; + + bcrypt.hash(pw, 10, function (err, data) { + if(err) { + Logger.errlog.log("bcrypt error: " + err); + callback("Password reset failure", null); + return; + } + + var query = "UPDATE registrations SET pw=? WHERE uname=?"; + self.query(query, [data, name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, pw); + }); + }); +}; + +Database.prototype.listUserPlaylists = function (name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT name, count, time FROM user_playlists WHERE user=?"; + self.query(query, [name], callback); +}; + +Database.prototype.getUserPlaylist = function (username, plname, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT contents FROM user_playlists WHERE " + + "user=? AND name=?"; + + self.query(query, [username, plname], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("Playlist does not exist", null); + return; + } + + var pl = null; + try { + pl = JSON.parse(res[0].contents); + } catch(e) { + callback("Malformed playlist JSON", null); + return; + } + callback(null, pl); + }); +}; + +Database.prototype.saveUserPlaylist = function (pl, username, plname, + callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var tmp = [], time = 0; + for(var i in pl) { + var e = { + id: pl[i].media.id, + title: pl[i].media.title, + seconds: pl[i].media.seconds, + type: pl[i].media.type + }; + time += pl[i].media.seconds; + tmp.push(e); + } + var count = tmp.length; + var plText = JSON.stringify(tmp); + + var query = "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE contents=?, count=?, time=?"; + + var params = [username, plname, plText, count, time, + plText, count, time]; + + self.query(query, params, callback); +}; + module.exports = Database; From d883445ed4d362537fa7e5f923805acc8d43567b Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 13:39:32 -0500 Subject: [PATCH 18/60] Finish refactoring existing functions from database.js --- database.js | 130 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 112 insertions(+), 18 deletions(-) diff --git a/database.js b/database.js index 06b29790..72f60b76 100644 --- a/database.js +++ b/database.js @@ -66,7 +66,7 @@ Database.prototype.query = function (query, sub, callback) { } function blackHole() { - + } Database.prototype.init = function () { @@ -230,7 +230,7 @@ Database.prototype.isGlobalIPBanned = function (ip, callback) { var banned = ip in this.global_ipbans || s16 in this.global_ipbans || s24 in this.global_ipbans; - + callback(null, banned); }; @@ -259,7 +259,7 @@ Database.prototype.setGlobalIPBan = function (ip, reason, callback) { if(typeof callback !== "function") callback = blackHole; - var query = "INSERT INTO global_bans VALUES (?, ?)" + + var query = "INSERT INTO global_bans VALUES (?, ?)" + " ON DUPLICATE KEY UPDATE note=?"; self.query(query, [ip, reason, reason], function (err, res) { if(err) { @@ -338,7 +338,7 @@ Database.prototype.registerChannel = function (name, owner, callback) { callback(err, null); return; } - + // Rank table query = ["CREATE TABLE `chan_" + name + "_ranks` (", "`name` VARCHAR(32) NOT NULL,", @@ -365,7 +365,7 @@ Database.prototype.registerChannel = function (name, owner, callback) { callback(err, null); return; } - + query = "INSERT INTO channels VALUES (NULL, ?, ?)"; self.query(query, [name, owner], function (err, res) { callback(err, res); @@ -393,7 +393,7 @@ Database.prototype.loadChannelData = function (chan, callback) { callback(err, null); return; } - + if(res.length == 0) { callback("Channel is unregistered", null); return; @@ -402,7 +402,7 @@ Database.prototype.loadChannelData = function (chan, callback) { if(res[0].name != chan.name) chan.name = rows[0].name; chan.registered = true; - + // Load bans query = "SELECT * FROM `chan_" + chan.name + "_bans`"; self.query(query, function (err, res) { @@ -494,7 +494,7 @@ Database.prototype.getChannelRank = function (channame, names, callback) { callback(null, res[0].rank); return; } - + callback(null, res); }); }; @@ -512,7 +512,7 @@ Database.prototype.setChannelRank = function (channame, name, rank, callback) { var query = "INSERT INTO `chan_" + channame + "_ranks` " + "(name, rank) VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE rank=?"; - + self.query(query, [name, rank, rank], callback); }; @@ -541,7 +541,7 @@ Database.prototype.addToLibrary = function (channame, media, callback) { } var query = "INSERT INTO `chan_" + channame + "_ranks`" + - "(id, title, seconds, type) " + + "(id, title, seconds, type) " + "VALUES (?, ?, ?, ?)"; var params = [ media.id, @@ -600,7 +600,7 @@ Database.prototype.addChannelBan = function (channame, ip, name, banBy, return; } - var query = "INSERT INTO `chan_" + channame + "_bans`" + + var query = "INSERT INTO `chan_" + channame + "_bans`" + "(ip, name, banner) VALUES (?, ?, ?)"; self.query(query, [ip, name, banBy], callback); @@ -615,7 +615,7 @@ Database.prototype.clearChannelIPBan = function (channame, ip, callback) { callback("Invalid channel name", null); return; } - + var query = "DELETE FROM `chan_" + channame + "_bans` WHERE ip=?"; self.query(query, [ip], callback); }; @@ -628,7 +628,7 @@ Database.prototype.clearChannelNameBan = function (channame, name, return; } - var query = "DELETE FROM `chan_" + channame + "_bans` WHERE ip='*'" + + var query = "DELETE FROM `chan_" + channame + "_bans` WHERE ip='*'" + "AND name=?"; self.query(query, [name], callback); @@ -638,6 +638,8 @@ Database.prototype.clearChannelNameBan = function (channame, name, /* REGION users */ +/* email and profile */ + Database.prototype.getUserProfile = function (name, callback) { var self = this; if(typeof callback !== "function") @@ -682,6 +684,8 @@ Database.prototype.setUserEmail = function (name, email, callback) { self.query(query, [email, name], callback); }; +/* password recovery */ + Database.prototype.genPasswordReset = function (ip, name, email, callback) { var self = this; if(typeof callback !== "function") @@ -693,7 +697,7 @@ Database.prototype.genPasswordReset = function (ip, name, email, callback) { callback(err, null); return; } - + if(res.length == 0) { callback("Provided username does not exist", null); return; @@ -766,7 +770,7 @@ Database.prototype.resetUserPassword = function (name, callback) { var self = this; if(typeof callback !== "function") callback = blackHole; - + var pwChars = "abcdefghijkmnopqrstuvwxyz023456789"; var pw = ""; for(var i = 0; i < 10; i++) @@ -778,7 +782,7 @@ Database.prototype.resetUserPassword = function (name, callback) { callback("Password reset failure", null); return; } - + var query = "UPDATE registrations SET pw=? WHERE uname=?"; self.query(query, [data, name], function (err, res) { if(err) { @@ -791,6 +795,8 @@ Database.prototype.resetUserPassword = function (name, callback) { }); }; +/* user playlists */ + Database.prototype.listUserPlaylists = function (name, callback) { var self = this; if(typeof callback !== "function") @@ -835,7 +841,7 @@ Database.prototype.saveUserPlaylist = function (pl, username, plname, var self = this; if(typeof callback !== "function") callback = blackHole; - + var tmp = [], time = 0; for(var i in pl) { var e = { @@ -852,11 +858,99 @@ Database.prototype.saveUserPlaylist = function (pl, username, plname, var query = "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE contents=?, count=?, time=?"; - + var params = [username, plname, plText, count, time, plText, count, time]; self.query(query, params, callback); }; +Database.prototype.deleteUserPlaylist = function (username, plname, + callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "DELETE FROM user_playlists WHERE user=? AND name=?"; + self.query(query, [username, plname], callback); +}; + +/* user channels */ + +Database.prototype.listUserChannels = function (username, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT * FROM channels WHERE owner=? ORDER BY id ASC"; + self.query(query, [username], callback); +}; + +/* aliases */ + +Database.prototype.recordVisit = function (ip, name, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var time = Date.now(); + var query = "DELETE FROM aliases WHERE ip=? AND name=?;" + + "INSERT INTO aliases VALUES (NULL, ?, ?, ?)"; + + self.query(query, [ip, name, ip, name, time], function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, res); + query = "DELETE FROM aliases WHERE ip=? AND visit_id NOT IN (" + + "SELECT visit_id FROM (" + + "SELECT visit_id, time FROM aliases WHERE ip=?" + + "ORDER BY time DESC LIMIT 5" + + ") foo" + // The 'foo' here is actually necessary + ")"; + + self.query(query, [ip, ip]); + }); +}; + +Database.prototype.listAliases = function (ip, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT name FROM aliases WHERE ip=?"; + self.query(query, [ip], function (err, res) { + var names = null; + if(!err) { + names = []; + res.forEach(function (row) { + names.append(row.name); + }); + } + + callback(err, names); + }); +}; + +Database.prototype.listIPsForName = function (name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT ip FROM aliases WHERE name=?"; + self.query(query, [name], function (err, res) { + var ips = null; + if(!err) { + ips = []; + res.forEach(function (row) { + ips.push(row.ip); + }); + } + + callback(err, ips); + }); +}; + module.exports = Database; From 6d842228715743ee7661bd2a766366457c5b05f1 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 13:53:58 -0500 Subject: [PATCH 19/60] Start refactoring acp.js database calls --- acp.js | 72 +++++++++++++++++++---------------------------------- database.js | 32 +++++++++++++++++++++++- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/acp.js b/acp.js index b69138fd..7115e413 100644 --- a/acp.js +++ b/acp.js @@ -13,6 +13,7 @@ var Auth = require("./auth"); var ActionLog = require("./actionlog"); module.exports = function (Server) { + var db = Server.db; return { init: function(user) { ActionLog.record(user.ip, user.name, "acp-init"); @@ -29,64 +30,41 @@ module.exports = function (Server) { user.socket.on("acp-global-ban", function(data) { ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip); - Server.db.globalBanIP(data.ip, data.note); - user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans()); + db.setGlobalIPBan(data.ip, data.note, function (err, res) { + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); + }); + }); }); user.socket.on("acp-global-unban", function(ip) { ActionLog.record(user.ip, user.name, "acp-global-unban", ip); - Server.db.globalUnbanIP(ip); - user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans()); + db.clearGlobalIPBan(ip, function (err, res) { + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); + }); + }); }); - user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans()); + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); + }); user.socket.on("acp-lookup-user", function(name) { - var db = Server.db.getConnection(); - if(!db) { - return; - } - - var query = Server.db.createQuery( - "SELECT id,uname,global_rank,profile_image,profile_text,email FROM registrations WHERE uname LIKE ?", - ["%"+name+"%"] - ); - - var res = db.querySync(query); - if(!res) - return; - - var rows = res.fetchAllSync(); - user.socket.emit("acp-userdata", rows); + db.searchUser(name, function (err, res) { + res = res || []; + user.socket.emit("acp-userdata", res); + }); }); user.socket.on("acp-lookup-channel", function (data) { - var db = Server.db.getConnection(); - if(!db) { - return; - } - - var query; - if(data.field === "owner") { - query = Server.db.createQuery( - "SELECT * FROM channels WHERE owner LIKE ?", - ["%" + data.value + "%"] - ); - } else if (data.field === "name") { - query = Server.db.createQuery( - "SELECT * FROM channels WHERE name LIKE ?", - ["%" + data.value + "%"] - ); - } else { - return; - } - - var results = db.querySync(query); - if(!results) - return; - - var rows = results.fetchAllSync(); - user.socket.emit("acp-channeldata", rows); + db.searchChannel(data.field, data.value, function (e, res) { + res = res || []; + user.socket.emit("acp-channeldata", res); + }); }); user.socket.on("acp-reset-password", function(data) { diff --git a/database.js b/database.js index 72f60b76..b9e0bd5a 100644 --- a/database.js +++ b/database.js @@ -234,7 +234,7 @@ Database.prototype.isGlobalIPBanned = function (ip, callback) { callback(null, banned); }; -Database.prototype.getGlobalIPBans = function (callback) { +Database.prototype.listGlobalIPBans = function (callback) { var self = this; if(typeof callback !== "function") callback = blackHole; @@ -292,6 +292,21 @@ Database.prototype.clearGlobalIPBan = function (ip, callback) { /* END REGION */ /* REGION channels */ + +Database.prototype.searchChannel = function (field, value, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT * FROM channels WHERE "; + if(field === "owner") + query += "owner LIKE %?%"; + else if(field === "name") + query += "name LIKE %?%"; + + self.query(query, [value], callback); +}; + Database.prototype.channelExists = function (name, callback) { var self = this; if(typeof callback !== "function") @@ -638,6 +653,20 @@ Database.prototype.clearChannelNameBan = function (channame, name, /* REGION users */ +Database.prototype.searchUser = function (name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + // NOTE: No SELECT * here because I don't want to risk exposing + // the user's password hash + var query = "SELECT id, uname, global_rank, profile_image, " + + "profile_text, email FROM registrations WHERE " + + "uname LIKE %?%"; + + self.query(query, [name], callback); +}; + /* email and profile */ Database.prototype.getUserProfile = function (name, callback) { @@ -953,4 +982,5 @@ Database.prototype.listIPsForName = function (name, callback) { }); }; +/* END REGION */ module.exports = Database; From c4d5b1cd1d4ae41a9606eeeb9e8f06223950bf7c Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 14:43:35 -0500 Subject: [PATCH 20/60] finish refactoring acp (kind of -- depends on auth --- acp.js | 70 ++++++++++++++++++++--------------------------------- database.js | 25 +++++++++++++++++++ 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/acp.js b/acp.js index 7115e413..124a0099 100644 --- a/acp.js +++ b/acp.js @@ -70,30 +70,23 @@ module.exports = function (Server) { user.socket.on("acp-reset-password", function(data) { if(Auth.getGlobalRank(data.name) >= user.global_rank) return; - try { - var hash = Server.db.generatePasswordReset(user.ip, data.name, data.email); - ActionLog.record(user.ip, user.name, "acp-reset-password", data.name); - } - catch(e) { - user.socket.emit("acp-reset-password", { - success: false, - error: e - }); - return; - } - if(hash) { - user.socket.emit("acp-reset-password", { - success: true, - hash: hash - }); - } - else { - user.socket.emit("acp-reset-password", { - success: false, - error: "Reset failed" - }); - } + db.genPasswordReset(user.ip, data.name, data.email, + function (err, hash) { + var pkt = { + success: !err + }; + + if(err) { + pkt.error = err; + } else { + pkt.hash = hash; + } + + user.socket.emit("acp-reset-password", pkt); + ActionLog.record(user.ip, user.name, + "acp-reset-password", data.name); + }); }); user.socket.on("acp-set-rank", function(data) { @@ -103,21 +96,13 @@ module.exports = function (Server) { if(Auth.getGlobalRank(data.name) >= user.global_rank) return; - var db = Server.db.getConnection(); - if(!db) - return; + db.setGlobalRank(data.name, data.rank, function (err, res) { - ActionLog.record(user.ip, user.name, "acp-set-rank", data); - var query = Server.db.createQuery( - "UPDATE registrations SET global_rank=? WHERE uname=?", - [data.rank, data.name] - ); - - var res = db.querySync(query); - if(!res) - return; - - user.socket.emit("acp-set-rank", data); + ActionLog.record(user.ip, user.name, "acp-set-rank", + data); + if(!err) + user.socket.emit("acp-set-rank", data); + }); }); user.socket.on("acp-list-loaded", function() { @@ -174,13 +159,10 @@ module.exports = function (Server) { }); user.socket.on("acp-view-stats", function () { - var db = Server.db.getConnection(); - if(!db) - return; - var query = "SELECT * FROM stats ORDER BY time ASC"; - var results = db.querySync(query); - if(results) - user.socket.emit("acp-view-stats", results.fetchAllSync()); + db.listStats(function (err, res) { + if(!err) + user.socket.emit("acp-view-stats", res); + }); }); } } diff --git a/database.js b/database.js index b9e0bd5a..2b739e08 100644 --- a/database.js +++ b/database.js @@ -38,6 +38,9 @@ Database.prototype.query = function (query, sub, callback) { sub = false; } + if(typeof callback !== "function") + callback = blackHole; + var self = this; self.pool.getConnection(function (err, conn) { if(err) { @@ -667,6 +670,17 @@ Database.prototype.searchUser = function (name, callback) { self.query(query, [name], callback); }; +/* rank */ + +Database.prototype.setGlobalRank = function (name, rank, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "UPDATE registrations SET global_rank=? WHERE uname=?"; + self.query(query, [rank, name], callback); +}; + /* email and profile */ Database.prototype.getUserProfile = function (name, callback) { @@ -983,4 +997,15 @@ Database.prototype.listIPsForName = function (name, callback) { }; /* END REGION */ + +/* REGION stats */ + +Database.prototype.listStats = function (callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT * FROM stats ORDER BY time ASC"; + self.query(query, callback); +}; module.exports = Database; From b342be50a37fdfe50d61cdfc3685b6d7559d14fa Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 17:44:22 -0500 Subject: [PATCH 21/60] Refactor actionlog queries --- actionlog.js | 181 +++++++++++++-------------------------------------- database.js | 96 +++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 137 deletions(-) diff --git a/actionlog.js b/actionlog.js index 312a2f74..4fb864b6 100644 --- a/actionlog.js +++ b/actionlog.js @@ -9,143 +9,50 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var Database = require("./database"); var Logger = require("./logger"); -exports.record = function(ip, name, action, args) { - if(typeof args === "undefined" || args === null) { - args = ""; - } else { - try { - args = JSON.stringify(args); - } catch(e) { - args = ""; +module.exports = function (Server) { + var db = Server.db; + return { + record: function (ip, name, action, args) { + if(!args) + args = ""; + else { + try { + args = JSON.stringify(args); + } catch(e) { + args = ""; + } + } + + db.recordAction(ip, name, action, args); + }, + + clear: function (actions) { + db.clearActions(actions); + }, + + clearOne: function (item) { + db.clearSingleAction(item); + }, + + throttleRegistrations: function (ip, callback) { + db.recentRegistrationCount(ip, function (err, count) { + if(err) { + callback(err, null); + return; + } + + callback(null, count > 4); + }); + }, + + listActionTypes: function (callback) { + db.listActionTypes(callback); + }, + + listActions: function (types, callback) { + db.listActions(types, callback); } - } - - var db = Database.getConnection(); - if(!db) - return false; - - var query = Database.createQuery( - "INSERT INTO actionlog (ip, name, action, args, time) "+ - "VALUES (?, ?, ?, ?, ?)", - [ip, name, action, args, Date.now()] - ); - - var result = db.querySync(query); - if(!result) { - Logger.errlog.log("! Failed to record action"); - } - - return result; -} - -exports.clear = function(actions) { - var db = Database.getConnection(); - if(!db) - return false; - - var list = new Array(actions.length); - for(var i = 0; i < actions.length; i++) - list[i] = "?"; - - var query = Database.createQuery( - "DELETE FROM actionlog WHERE action IN ("+ - list.join(",")+ - ")", - actions - ); - - var result = db.querySync(query); - if(!result) { - Logger.errlog.log("! Failed to clear action log"); - } - - return result; -} - -exports.clearOne = function(e) { - var db = Database.getConnection(); - if(!db) - return false; - - var query = Database.createQuery( - "DELETE FROM actionlog WHERE ip=? AND time=?", - [e.ip, e.time] - ); - - var result = db.querySync(query); - if(!result) { - Logger.errlog.log("! Failed to clear action log"); - } - - return result; -} - -exports.tooManyRegistrations = function (ip) { - var db = Database.getConnection(); - if(!db) - return true; - - var query = Database.createQuery( - "SELECT * FROM actionlog WHERE ip=? AND action='register-success'"+ - "AND time > ?", - [ip, Date.now() - 48 * 3600 * 1000] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to check tooManyRegistrations"); - return true; - } - - var rows = results.fetchAllSync(); - // TODO Config value for this - return rows.length > 4; -} - -exports.getLogTypes = function () { - var db = Database.getConnection(); - if(!db) - return false; - - var query = "SELECT DISTINCT action FROM actionlog"; - var result = db.querySync(query); - if(!result) { - Logger.errlog.log("! Failed to read action log"); - return []; - } - - result = result.fetchAllSync(); - var actions = []; - for(var i in result) - actions.push(result[i].action); - - return actions; -} - -exports.readLog = function (actions) { - var db = Database.getConnection(); - if(!db) - return false; - - var query = "SELECT * FROM actionlog"; - if(actions !== undefined) { - var list = new Array(actions.length); - for(var i in actions) - list[i] = "?"; - list = list.join(","); - query += Database.createQuery( - " WHERE action IN ("+list+")", - actions - ); - } - var result = db.querySync(query); - if(!result) { - Logger.errlog.log("! Failed to read action log"); - return []; - } - - return result.fetchAllSync(); -} + }; +}; diff --git a/database.js b/database.js index 2b739e08..607c4c19 100644 --- a/database.js +++ b/database.js @@ -998,6 +998,100 @@ Database.prototype.listIPsForName = function (name, callback) { /* END REGION */ +/* REGION action log */ + +Database.prototype.recordAction = function (ip, name, action, args, + callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "INSERT INTO actionlog (ip, name, action, args, time) " + + "VALUES (?, ?, ?, ?, ?)"; + + self.query(query, [ip, name, action, args, Date.now()], callback); +}; + +Database.prototype.clearActions = function (actions, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var list = []; + for(var i in actions) + list.push("?"); + + var actionlist = "(" + list.join(",") + ")"; + + var query = "DELETE FROM actionlog WHERE action IN " + actionlist; + self.query(query, actions, callback); +}; + +Database.prototype.clearSingleAction = function (item, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "DELETE FROM actionlog WHERE ip=? AND time=?"; + self.query(query, [item.ip, item.time], callback); +}; + + +Database.prototype.recentRegistrationCount = function (ip, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT * FROM actionlog WHERE ip=? " + + "AND action='register-success' AND time > ?"; + + self.query(query, [ip, Date.now() - 48 * 3600 * 1000], + function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, res.length); + }); +}; + +Database.prototype.listActionTypes = function (callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT DISTINCT action FROM actionlog"; + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + var types = []; + res.forEach(function (row) { + types.push(row.action); + }); + callback(null, types); + }); +}; + +Database.prototype.listActions = function (types, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var list = []; + for(var i in types) + list.push("?"); + + var actionlist = "(" + list.join(",") + ")"; + var query = "SELECT * FROM actionlog WHERE action IN " + actiontypes; + self.query(query, types, callback); +}; + +/* END REGION */ + /* REGION stats */ Database.prototype.listStats = function (callback) { @@ -1008,4 +1102,6 @@ Database.prototype.listStats = function (callback) { var query = "SELECT * FROM stats ORDER BY time ASC"; self.query(query, callback); }; + +/* END REGION */ module.exports = Database; From e7b22997c79cd1a4a8c62219df92d718bdf5b2f3 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 19:39:05 -0500 Subject: [PATCH 22/60] Update acp.js to be compatible with new actionlog --- acp.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/acp.js b/acp.js index 124a0099..0f10c827 100644 --- a/acp.js +++ b/acp.js @@ -10,10 +10,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var Auth = require("./auth"); -var ActionLog = require("./actionlog"); module.exports = function (Server) { var db = Server.db; + var ActionLog = require("./actionlog")(Server); return { init: function(user) { ActionLog.record(user.ip, user.name, "acp-init"); @@ -29,7 +29,7 @@ module.exports = function (Server) { }); user.socket.on("acp-global-ban", function(data) { - ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip); + ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip); db.setGlobalIPBan(data.ip, data.note, function (err, res) { db.listGlobalIPBans(function (err, res) { res = res || []; @@ -143,9 +143,10 @@ module.exports = function (Server) { }); user.socket.on("acp-actionlog-list", function () { - user.socket.emit("acp-actionlog-list", - ActionLog.getLogTypes() - ); + ActionLog.listActionTypes(function (err, types) { + if(!err) + user.socket.emit("acp-actionlog-list", types); + }); }); user.socket.on("acp-actionlog-clear", function(data) { From 59695583208f3ca86a75b3e69207eeaa3c3fd482 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 19:51:03 -0500 Subject: [PATCH 23/60] Start fucking api.js --- api.js | 233 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 115 insertions(+), 118 deletions(-) diff --git a/api.js b/api.js index 0ff1c544..659b0390 100644 --- a/api.js +++ b/api.js @@ -14,7 +14,6 @@ var Logger = require("./logger"); var ActionLog = require("./actionlog"); var fs = require("fs"); - module.exports = function (Server) { function getIP(req) { var raw = req.connection.remoteAddress; @@ -52,6 +51,7 @@ module.exports = function (Server) { } var app = Server.app; + var db = Server.db; /* */ app.get("/api/coffee", function (req, res) { @@ -283,62 +283,63 @@ module.exports = function (Server) { var ip = getIP(req); var hash = false; - try { - hash = Server.db.generatePasswordReset(ip, name, email); - ActionLog.record(ip, name, "password-reset-generate", email); - } catch(e) { - res.jsonp({ - success: false, - error: e - }); - return; - } - - if(!Server.cfg["enable-mail"]) { - res.jsonp({ - success: false, - error: "This server does not have email recovery enabled."+ - " Contact an administrator for assistance." - }); - return; - } - - if(!email) { - res.jsonp({ - success: false, - error: "You don't have a recovery email address set. "+ - "Contact an administrator for assistance." - }); - return; - } - - var msg = "A password reset request was issued for your account '"+ - name + "' on " + Server.cfg["domain"] + ". This request"+ - " is valid for 24 hours. If you did not initiate this, "+ - "there is no need to take action. To reset your "+ - "password, copy and paste the following link into your "+ - "browser: " + Server.cfg["domain"] + "/reset.html?"+hash; - - var mail = { - from: "CyTube Services <" + Server.cfg["mail-from"] + ">", - to: email, - subject: "Password reset request", - text: msg - }; - - Server.cfg["nodemailer"].sendMail(mail, function (err, response) { + db.genPasswordReset(ip, name, email, function (err, hash) { if(err) { - Logger.errlog.log("mail fail: " + err); res.jsonp({ success: false, - error: "Email send failed. Contact an administrator "+ - "if this persists" - }); - } else { - res.jsonp({ - success: true + error: err }); + return; } + ActionLog.record(ip, name, "password-reset-generate", email); + if(!Server.cfg["enable-mail"]) { + res.jsonp({ + success: false, + error: "This server does not have email recovery " + + "enabled. Contact an administrator for " + + "assistance." + }); + return; + } + + if(!email) { + res.jsonp({ + success: false, + error: "You don't have a recovery email address set. "+ + "Contact an administrator for assistance." + }); + return; + } + + var msg = "A password reset request was issued for your " + + "account '"+ name + "' on " + Server.cfg["domain"] + + ". This request is valid for 24 hours. If you did "+ + "not initiate this, there is no need to take action."+ + " To reset your password, copy and paste the " + + "following link into your browser: " + + Server.cfg["domain"] + "/reset.html?"+hash; + + var mail = { + from: "CyTube Services <" + Server.cfg["mail-from"] + ">", + to: email, + subject: "Password reset request", + text: msg + }; + + Server.cfg["nodemailer"].sendMail(mail, function (err, response) { + if(err) { + Logger.errlog.log("mail fail: " + err); + res.jsonp({ + success: false, + error: "Email send failed. Contact an administrator "+ + "if this persists" + }); + } else { + res.jsonp({ + success: true + }); + } + }); }); }); @@ -348,21 +349,22 @@ module.exports = function (Server) { var hash = req.query.hash; var ip = getIP(req); - try { - var info = Server.db.recoverPassword(hash); + db.recoverUserPassword(hash, function (err, auth) { + if(err) { + ActionLog.record(ip, "", "password-recover-failure", hash); + res.jsonp({ + success: false, + error: err + }); + return; + } + ActionLog.record(ip, info[0], "password-recover-success"); res.jsonp({ success: true, - name: info[0], - pw: info[1] + name: auth.name, + pw: auth.pw }); - ActionLog.record(ip, info[0], "password-recover-success"); - } catch(e) { - ActionLog.record(ip, "", "password-recover-failure", hash); - res.jsonp({ - success: false, - error: e - }); - } + }); }); /* profile retrieval */ @@ -370,19 +372,21 @@ module.exports = function (Server) { res.type("application/jsonp"); var name = req.params.user; - try { - var prof = Server.db.getProfile(name); + db.getUserProfile(name, function (err, profile) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } + res.jsonp({ success: true, - profile_image: prof.profile_image, - profile_text: prof.profile_text + profile_image: profile.profile_image, + profile_text: profile.profile_text }); - } catch(e) { - res.jsonp({ - success: false, - error: e - }); - } + }); }); /* profile change */ @@ -402,40 +406,33 @@ module.exports = function (Server) { }); return; } + + db.setUserProfile(name, { image: img, text: text }, + function (err, dbres) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - var result = Server.db.setProfile(name, { - image: img, - text: text - }); - - if(!result) { - res.jsonp({ - success: false, - error: "Server error. Contact an administrator for assistance" - }); - return; - } - - res.jsonp({ - success: true - }); - - // Update profile on all channels the user is connected to - name = name.toLowerCase(); - for(var i in Server.channels) { - var chan = Server.channels[i]; - for(var j in chan.users) { - var user = chan.users[j]; - if(user.name.toLowerCase() == name) { - user.profile = { - image: img, - text: text - }; - chan.broadcastUserUpdate(user); + res.jsonp({ success: true }); + name = name.toLowerCase(); + for(var i in Server.channels) { + var chan = Server.channels[i]; + for(var j in chan.users) { + var user = chan.users[j]; + if(user.name.toLowerCase() == name) { + user.profile = { + image: img, + text: text + }; + chan.broadcastUserUpdate(user); + } } } - } - + }); }); /* set email */ @@ -470,20 +467,20 @@ module.exports = function (Server) { return; } - var success = Server.db.setUserEmail(name, email); - if(!success) { - res.jsonp({ - success: false, - error: "Email update failed. Contact an administrator "+ - "for assistance." - }); - return false; - } + db.setUserEmail(name, email, function (err, dbres) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - ActionLog.record(getIP(req), name, "email-update", email); - res.jsonp({ - success: true, - session: row.session_hash + ActionLog.record(getIP(req), name, "email-update", email); + res.jsonp({ + success: true, + session: row.session_hash + }); }); }); From a20df0751562419680d835c63214fe1174b0c3ff Mon Sep 17 00:00:00 2001 From: calzoneman Date: Thu, 15 Aug 2013 22:18:10 -0500 Subject: [PATCH 24/60] Start deprecating auth.js --- database.js | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/database.js b/database.js index 607c4c19..fe3477dc 100644 --- a/database.js +++ b/database.js @@ -654,6 +654,196 @@ Database.prototype.clearChannelNameBan = function (channame, name, /* END REGION */ +/* REGION Auth */ + +Database.prototype.isUsernameTaken = function (name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT id FROM registrations WHERE uname=?"; + self.query(query, [name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, res.length > 0); + }); +}; + +Database.prototype.registerUser = function (name, pw, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + if(!$util.isValidUserName(name)) { + callback("Invalid username", null); + return; + } + + self.isUsernameTaken(name, function (err, taken) { + if(err) { + callback(err, null); + return; + } + + if(taken) { + callback("Username already taken", null); + return; + } + + bcrypt.hash(pw, 10, function (err, hash) { + if(err) { + callback(err, null); + return; + } + + var query = "INSERT INTO registrations VALUES " + + "(NULL, ?, ?, 1, '', 0, '', '', '')"; + + self.query(query, [name, hash], function (err, res) { + callback(err, res); + }); + }); + }); +}; + +Database.prototype.userLogin = function (name, pw, session, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var postLogin = function (err, row) { + if(err) { + callback(err, null); + return; + } + + self.createLoginSession(name, function (err, hash) { + if(err) { + callback(err, null); + return; + } + + row.session_hash = hash; + callback(null, row); + }); + }; + + if(session) { + self.userLoginSession(name, session, postLogin); + } else if(pw) { + self.userLoginPassword(name, pw, postLogin); + } else { + callback("Invalid login", null); + } +}; + +Database.prototype.userLoginPassword = function (name, pw, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "SELECT * FROM registrations WHERE uname=?"; + self.query(query, [name], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("User does not exist", null); + return; + } + + var row = res[0]; + + bcrypt.compare(pw, row.pw, function (err, valid) { + if(valid) { + // For security, erase the password field before returning + delete row["pw"]; + callback(null, row); + return; + } + + // Possibly could be a SHA256 hash from an *ancient* version + // of CyTube + + var sha = hashlib.sha256(pw); + if(sha == row.pw) { + // Replace it + bcrypt.hash(pw, 10, function (err, hash) { + if(!err) { + self.query("UPDATE registrations SET pw=? "+ + "WHERE uname=?", [hash, name]); + } + }); + + // Remove password field before returning + delete row["pw"]; + callback(null, row); + } else { + callback("Invalid username/password combination", null); + } + }); + }); +}; + +Database.prototype.userLoginSession = function (name, session, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "SELECT * FROM registrations WHERE uname=? AND " + + "session_hash=?"; + + self.query(query, [name, session], function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("Invalid session", null); + return; + } + + var row = res[0]; + + if(row.expire < Date.now()) { + callback("Session expired", null); + return; + } + + callback(null, row); + }); +}; + +Database.prototype.createLoginSession = function (name, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var salt = $util.randomSalt(32); + var hash = hashlib.sha256(salt + name); + + var query = "UPDATE registrations SET session_hash=?, expire=? " + + "WHERE uname=?"; + + self.query(query, [hash, Date.now() + 604800000, name], + function (err, res) { + if(err) { + callback(err, null); + return; + } + + callback(null, hash); + }); +}; + +/* END REGION */ + /* REGION users */ Database.prototype.searchUser = function (name, callback) { From f523649f5410438a770e1b9512511cc0be721d40 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Aug 2013 10:37:26 -0500 Subject: [PATCH 25/60] Finish refactoring api --- api.js | 17 +++++++++++++---- utilities.js | 6 +++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/api.js b/api.js index 659b0390..88f8ab01 100644 --- a/api.js +++ b/api.js @@ -499,10 +499,19 @@ module.exports = function (Server) { return; } - var channels = Server.db.listUserChannels(name); - res.jsonp({ - success: true, - channels: channels + db.listUserChannels(name, function (err, res) { + if(err) { + res.jsonp({ + success: false, + channels: [] + }); + return; + } + + res.jsonp({ + success: true, + channels: res + }); }); }); diff --git a/utilities.js b/utilities.js index d0c08596..42d04df5 100644 --- a/utilities.js +++ b/utilities.js @@ -1,6 +1,10 @@ module.exports = { isValidChannelName: function (name) { - return name.match(/^[\w-_]+$/); + return name.match(/^[\w-_]{1,30}$/); + }, + + isValidUserName: function (name) { + return name.match(/^[\w-_]{1,20}$/); }, randomSalt: function (length) { From f46169fbe33f3ba3c998fb25de57c9edca985c73 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Aug 2013 11:01:31 -0500 Subject: [PATCH 26/60] Start updating auth dependencies --- acp.js | 52 +++++++++--------- api.js | 153 ++++++++++++++++++++++++++-------------------------- database.js | 59 ++++++++++++++++++-- 3 files changed, 161 insertions(+), 103 deletions(-) diff --git a/acp.js b/acp.js index 0f10c827..7446fe44 100644 --- a/acp.js +++ b/acp.js @@ -9,8 +9,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var Auth = require("./auth"); - module.exports = function (Server) { var db = Server.db; var ActionLog = require("./actionlog")(Server); @@ -68,24 +66,26 @@ module.exports = function (Server) { }); user.socket.on("acp-reset-password", function(data) { - if(Auth.getGlobalRank(data.name) >= user.global_rank) - return; + db.getGlobalRank(data.name, function (err, rank) { + if(err || rank >= user.global_rank) + return; - db.genPasswordReset(user.ip, data.name, data.email, - function (err, hash) { - var pkt = { - success: !err - }; + db.genPasswordReset(user.ip, data.name, data.email, + function (err, hash) { + var pkt = { + success: !err + }; - if(err) { - pkt.error = err; - } else { - pkt.hash = hash; - } + if(err) { + pkt.error = err; + } else { + pkt.hash = hash; + } - user.socket.emit("acp-reset-password", pkt); - ActionLog.record(user.ip, user.name, - "acp-reset-password", data.name); + user.socket.emit("acp-reset-password", pkt); + ActionLog.record(user.ip, user.name, + "acp-reset-password", data.name); + }); }); }); @@ -93,15 +93,17 @@ module.exports = function (Server) { if(data.rank < 1 || data.rank >= user.global_rank) return; - if(Auth.getGlobalRank(data.name) >= user.global_rank) - return; + db.getGlobalRank(data.name, function (err, rank) { + if(err || rank >= user.global_rank) + return; - db.setGlobalRank(data.name, data.rank, function (err, res) { - - ActionLog.record(user.ip, user.name, "acp-set-rank", - data); - if(!err) - user.socket.emit("acp-set-rank", data); + db.setGlobalRank(data.name, data.rank, + function (err, res) { + ActionLog.record(user.ip, user.name, "acp-set-rank", + data); + if(!err) + user.socket.emit("acp-set-rank", data); + }); }); }); diff --git a/api.js b/api.js index 88f8ab01..76098a65 100644 --- a/api.js +++ b/api.js @@ -9,7 +9,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var Auth = require("./auth"); var Logger = require("./logger"); var ActionLog = require("./actionlog"); var fs = require("fs"); @@ -94,21 +93,40 @@ module.exports = function (Server) { if(filter !== "public") { var name = query.name || ""; var session = query.session || ""; - var row = Auth.login(name, "", session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } + db.userLoginSession(name, session, function (err, row) { + if(err) { + if(err !== "Invalid session" && + err !== "Session expired") { + res.send(500); + } else { + res.send(403); + } + return; + } + + if(row.global_rank < 255) { + res.send(403); + return; + } + + var channels = []; + for(var key in Server.channels) { + var channel = Server.channels[key]; + channels.push(getChannelData(channel)); + } + + res.type("application/jsonp"); + res.jsonp(channels); + }); } + + // If we get here, the filter is public channels var channels = []; for(var key in Server.channels) { var channel = Server.channels[key]; - if(channel.opts.show_public) { + if(channel.opts.show_public) channels.push(getChannelData(channel)); - } else if(filter !== "public") { - channels.push(getChannelData(channel)); - } } res.type("application/jsonp"); @@ -136,33 +154,26 @@ module.exports = function (Server) { return; } - var row = Auth.login(name, pw, session); - if(!row) { - if(session && !pw) { + db.userLogin(name, pw, session, function (err, row) { + if(err) { + if(err !== "Session expired") + ActionLog.record(getIP(req), name, "login-failure"); res.jsonp({ success: false, - error: "Session expired" - }); - return; - } else { - ActionLog.record(getIP(req), name, "login-failure", - "invalid_password"); - res.jsonp({ - success: false, - error: "Provided username/password pair is invalid" + error: err }); return; } - } - - // record the login if the user is an administrator - if(row.global_rank >= 255) - ActionLog.record(getIP(req), name, "login-success"); - res.jsonp({ - success: true, - name: name, - session: row.session_hash + // Only record login-success for admins + if(row.global_rank >= 255) + ActionLog.record(getIP(req), name, "login-success"); + + res.jsonp({ + success: true, + name: name, + session: row.session_hash + }); }); }); @@ -195,7 +206,8 @@ module.exports = function (Server) { return; } - if(!Auth.validateName(name)) { + + if(!$util.isValidUserName(name)) { ActionLog.record(ip, name, "register-failure", "Invalid name"); res.jsonp({ success: false, @@ -206,29 +218,21 @@ module.exports = function (Server) { return; } - if(Auth.isRegistered(name)) { - ActionLog.record(ip, name, "register-failure", "Name taken"); - res.jsonp({ - success: false, - error: "That username is already taken" - }); - return; - } + // db.registerUser checks if the name is taken already + db.registerUser(name, pw, function (err, session) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - var session = Auth.register(name, pw); - if(!session) { + ActionLog.record(ip, name, "register-success"); res.jsonp({ - success: false, - error: "Registration error. Contact an administrator "+ - "for assistance." + success: true, + session: session }); - return; - } - - ActionLog.record(ip, name, "register-success"); - res.jsonp({ - success: true, - session: session }); }); @@ -248,30 +252,29 @@ module.exports = function (Server) { return; } - var row = Auth.login(name, oldpw, ""); - if(!row) { - res.jsonp({ - success: false, - error: "Invalid username/password combination" - }); - return; - } + db.userLoginPassword(name, oldpw, function (err, row) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - ActionLog.record(getIP(req), name, "password-change"); - var success = Auth.setUserPassword(name, newpw); - - if(!success) { - res.jsonp({ - success: false, - error: "Server error. Please try again or ask an "+ - "administrator for assistance." - }); - return; - } + db.setUserPassword(name, newpw, function (err, row) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - res.jsonp({ - success: true, - session: row.session_hash + ActionLog.record(getIP(req), name, "password-change"); + res.jsonp({ + success: true + }); + }); }); }); @@ -393,11 +396,11 @@ module.exports = function (Server) { app.post("/api/account/profile", function (req, res) { res.type("application/jsonp"); var name = req.body.name; - var pw = req.body.pw; var session = req.body.session; var img = req.body.profile_image; var text = req.body.profile_text; + db.userLoginSession(name, session, function (err, row) { var row = Auth.login(name, pw, session); if(!row) { res.jsonp({ diff --git a/database.js b/database.js index fe3477dc..7628fc5c 100644 --- a/database.js +++ b/database.js @@ -682,6 +682,24 @@ Database.prototype.registerUser = function (name, pw, callback) { return; } + var postRegister = function (err, res) { + if(err) { + callback(err, null); + return; + } + + self.createLoginSession(name, function (err, hash) { + if(err) { + // Don't confuse people into thinking the registration + // failed when it was the session that failed + callback(null, ""); + return; + } + + callback(null, hash); + }); + }; + self.isUsernameTaken(name, function (err, taken) { if(err) { callback(err, null); @@ -702,9 +720,7 @@ Database.prototype.registerUser = function (name, pw, callback) { var query = "INSERT INTO registrations VALUES " + "(NULL, ?, ?, 1, '', 0, '', '', '')"; - self.query(query, [name, hash], function (err, res) { - callback(err, res); - }); + self.query(query, [name, hash], postRegister); }); }); }; @@ -842,6 +858,43 @@ Database.prototype.createLoginSession = function (name, callback) { }); }; +Database.prototype.setUserPassword = function (name, pw, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + bcrypt.hash(pw, 10, function (err, hash) { + if(err) { + callback(err, null); + return; + } + + var query = "UPDATE registrations SET pw=? WHERE uname=?"; + self.query(query, [hash, name], callback); + }); +}; + +Database.prototype.getGlobalRank = function (name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + var query = "SELECT global_rank FROM registrations WHERE uname=?"; + self.query(query, function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("User does not exist", null); + return; + } + + callback(null, res[0].global_rank); + }); +}; + /* END REGION */ /* REGION users */ From 3f26fc80e0eddb2fecaf678a043b17b6a70d2eb6 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Aug 2013 13:19:00 -0500 Subject: [PATCH 27/60] Finish fixing api.js --- api.js | 225 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 136 insertions(+), 89 deletions(-) diff --git a/api.js b/api.js index 76098a65..0e207ad7 100644 --- a/api.js +++ b/api.js @@ -401,17 +401,6 @@ module.exports = function (Server) { var text = req.body.profile_text; db.userLoginSession(name, session, function (err, row) { - var row = Auth.login(name, pw, session); - if(!row) { - res.jsonp({ - success: false, - error: "Invalid login" - }); - return; - } - - db.setUserProfile(name, { image: img, text: text }, - function (err, dbres) { if(err) { res.jsonp({ success: false, @@ -419,22 +408,33 @@ module.exports = function (Server) { }); return; } + + db.setUserProfile(name, { image: img, text: text }, + function (err, dbres) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } - res.jsonp({ success: true }); - name = name.toLowerCase(); - for(var i in Server.channels) { - var chan = Server.channels[i]; - for(var j in chan.users) { - var user = chan.users[j]; - if(user.name.toLowerCase() == name) { - user.profile = { - image: img, - text: text - }; - chan.broadcastUserUpdate(user); + res.jsonp({ success: true }); + name = name.toLowerCase(); + for(var i in Server.channels) { + var chan = Server.channels[i]; + for(var j in chan.users) { + var user = chan.users[j]; + if(user.name.toLowerCase() == name) { + user.profile = { + image: img, + text: text + }; + chan.broadcastUserUpdate(user); + } } } - } + }); }); }); @@ -461,16 +461,7 @@ module.exports = function (Server) { return; } - var row = Auth.login(name, pw, ""); - if(!row) { - res.jsonp({ - success: false, - error: "Invalid login credentials" - }); - return; - } - - db.setUserEmail(name, email, function (err, dbres) { + db.userLoginPassword(name, pw, function (err, row) { if(err) { res.jsonp({ success: false, @@ -479,10 +470,20 @@ module.exports = function (Server) { return; } - ActionLog.record(getIP(req), name, "email-update", email); - res.jsonp({ - success: true, - session: row.session_hash + db.setUserEmail(name, email, function (err, dbres) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } + + ActionLog.record(getIP(req), name, "email-update", email); + res.jsonp({ + success: true, + session: row.session_hash + }); }); }); }); @@ -493,29 +494,31 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; - var row = Auth.login(name, "", session); - if(!row) { - res.jsonp({ - success: false, - error: "Invalid login" - }); - return; - } - - db.listUserChannels(name, function (err, res) { + db.userLoginSession(name, session, function (err, row) { if(err) { res.jsonp({ success: false, - channels: [] + error: err }); return; } - res.jsonp({ - success: true, - channels: res + db.listUserChannels(name, function (err, res) { + if(err) { + res.jsonp({ + success: false, + channels: [] + }); + return; + } + + res.jsonp({ + success: true, + channels: res + }); }); }); + }); /* END REGION */ @@ -529,15 +532,26 @@ module.exports = function (Server) { var session = req.query.session; var types = req.query.actions; - var row = Auth.login(name, "", session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } + db.userLoginSession(name, session, function (err, row) { + if(err) { + if(err !== "Invalid session" && + err !== "Session expired") { + res.send(500); + } else { + res.send(403); + } + return; + } - types = types.split(","); - var actions = ActionLog.readLog(types); - res.jsonp(actions); + if(row.global_rank < 255) { + res.send(403); + return; + } + + types = types.split(","); + var actions = ActionLog.readLog(types); + res.jsonp(actions); + }); }); /* helper function to pipe the last N bytes of a file */ @@ -564,13 +578,24 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; - var row = Auth.login(name, "", session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } + db.userLoginSession(name, session, function (err, row) { + if(err) { + if(err !== "Invalid session" && + err !== "Session expired") { + res.send(500); + } else { + res.send(403); + } + return; + } - pipeLast(res, "sys.log", 1048576); + if(row.global_rank < 255) { + res.send(403); + return; + } + + pipeLast(res, "sys.log", 1048576); + }); }); app.get("/api/logging/errorlog", function (req, res) { @@ -580,13 +605,24 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; - var row = Auth.login(name, "", session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } + db.userLoginSession(name, session, function (err, row) { + if(err) { + if(err !== "Invalid session" && + err !== "Session expired") { + res.send(500); + } else { + res.send(403); + } + return; + } - pipeLast(res, "error.log", 1048576); + if(row.global_rank < 255) { + res.send(403); + return; + } + + pipeLast(res, "error.log", 1048576); + }); }); app.get("/api/logging/channels/:channel", function (req, res) { @@ -596,24 +632,35 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; - var row = Auth.login(name, "", session); - if(!row || row.global_rank < 255) { - res.send(403); - return; - } - - var chan = req.params.channel || ""; - if(!chan.match(/^[\w-_]+$/)) { - res.send(400); - return; - } - - fs.exists("chanlogs/" + chan + ".log", function(exists) { - if(exists) { - pipeLast(res, "chanlogs/" + chan + ".log", 1048576); - } else { - res.send(404); + db.userLoginSession(name, session, function (err, row) { + if(err) { + if(err !== "Invalid session" && + err !== "Session expired") { + res.send(500); + } else { + res.send(403); + } + return; } + + if(row.global_rank < 255) { + res.send(403); + return; + } + + var chan = req.params.channel || ""; + if(!$util.isValidChannelName(chan)) { + res.send(400); + return; + } + + fs.exists("chanlogs/" + chan + ".log", function(exists) { + if(exists) { + pipeLast(res, "chanlogs/" + chan + ".log", 1048576); + } else { + res.send(404); + } + }); }); }); From 063b179ab31197afd65155b328d263bd65102bab Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Aug 2013 13:25:36 -0500 Subject: [PATCH 28/60] Fix ActionLog calls for api --- api.js | 95 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/api.js b/api.js index 0e207ad7..9bb1676c 100644 --- a/api.js +++ b/api.js @@ -185,41 +185,7 @@ module.exports = function (Server) { var ip = getIP(req); // Limit registrations per IP within a certain time period - if(ActionLog.tooManyRegistrations(ip)) { - ActionLog.record(ip, name, "register-failure", - "Too many recent registrations"); - res.jsonp({ - success: false, - error: "Your IP address has registered too many accounts "+ - "in the past 48 hours. Please wait a while before"+ - " registering another." - }); - return; - } - - if(!pw) { - // costanza.jpg - res.jsonp({ - success: false, - error: "You must provide a password" - }); - return; - } - - - if(!$util.isValidUserName(name)) { - ActionLog.record(ip, name, "register-failure", "Invalid name"); - res.jsonp({ - success: false, - error: "Invalid username. Valid usernames must be 1-20 "+ - "characters long and consist only of alphanumeric "+ - "characters and underscores (_)" - }); - return; - } - - // db.registerUser checks if the name is taken already - db.registerUser(name, pw, function (err, session) { + ActionLog.throttleRegistrations(ip, function (err, toomany) { if(err) { res.jsonp({ success: false, @@ -227,11 +193,56 @@ module.exports = function (Server) { }); return; } + + if(toomany) { + ActionLog.record(ip, name, "register-failure", + "Too many recent registrations"); + res.jsonp({ + success: false, + error: "Your IP address has registered too many " + + "accounts in the past 48 hours. Please wait " + + "a while before registering another." + }); + return; + } - ActionLog.record(ip, name, "register-success"); - res.jsonp({ - success: true, - session: session + if(!pw) { + // costanza.jpg + res.jsonp({ + success: false, + error: "You must provide a password" + }); + return; + } + + + if(!$util.isValidUserName(name)) { + ActionLog.record(ip, name, "register-failure", + "Invalid name"); + res.jsonp({ + success: false, + error: "Invalid username. Valid usernames must be " + + "1-20 characters long and consist only of " + + "alphanumeric characters and underscores (_)" + }); + return; + } + + // db.registerUser checks if the name is taken already + db.registerUser(name, pw, function (err, session) { + if(err) { + res.jsonp({ + success: false, + error: err + }); + return; + } + + ActionLog.record(ip, name, "register-success"); + res.jsonp({ + success: true, + session: session + }); }); }); }); @@ -549,6 +560,12 @@ module.exports = function (Server) { } types = types.split(","); + ActionLog.listActions(types, function (err, actions) { + if(err) + actions = []; + + res.jsonp(actions); + }); var actions = ActionLog.readLog(types); res.jsonp(actions); }); From 448d50e796e429e42e4699300984fc8a6dd52bf2 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 16 Aug 2013 13:26:31 -0500 Subject: [PATCH 29/60] Amend last commit --- api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.js b/api.js index 9bb1676c..76e5e38c 100644 --- a/api.js +++ b/api.js @@ -10,10 +10,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var Logger = require("./logger"); -var ActionLog = require("./actionlog"); var fs = require("fs"); module.exports = function (Server) { + var ActionLog = require("./actionlog")(Server); function getIP(req) { var raw = req.connection.remoteAddress; var forward = req.header("x-forwarded-for"); From b7c02334eda1b4b7ed344541fd242f802c0dcd5e Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 12:30:52 -0500 Subject: [PATCH 30/60] Start on channel.js --- channel.js | 496 +++++++++++++++++++++++++++-------------------------- user.js | 24 +-- 2 files changed, 256 insertions(+), 264 deletions(-) diff --git a/channel.js b/channel.js index 3460e139..4195cd53 100644 --- a/channel.js +++ b/channel.js @@ -24,26 +24,27 @@ var Playlist = require("./playlist"); var sanitize = require("validator").sanitize; var Channel = function(name, Server) { + var self = this; Logger.syslog.log("Opening channel " + name); - this.initialized = false; - this.server = Server; + self.initialized = false; + self.server = Server; - this.name = name; - this.canonical_name = name.toLowerCase(); + self.name = name; + self.canonical_name = name.toLowerCase(); // Initialize defaults - this.registered = false; - this.users = []; - this.afkers = []; - this.playlist = new Playlist(this); - this.library = {}; - this.position = -1; - this.drinks = 0; - this.leader = null; - this.chatbuffer = []; - this.openqueue = false; - this.poll = false; - this.voteskip = false; - this.permissions = { + self.registered = false; + self.users = []; + self.afkers = []; + self.playlist = new Playlist(self); + self.library = {}; + self.position = -1; + self.drinks = 0; + self.leader = null; + self.chatbuffer = []; + self.openqueue = false; + self.poll = false; + self.voteskip = false; + self.permissions = { oplaylistadd: -1, oplaylistnext: 1.5, oplaylistmove: 1.5, @@ -74,11 +75,11 @@ var Channel = function(name, Server) { drink: 1.5, chat: 0 }; - this.opts = { + self.opts = { allow_voteskip: true, voteskip_ratio: 0.5, afk_timeout: 180, - pagetitle: this.name, + pagetitle: self.name, maxlength: 0, externalcss: "", externaljs: "", @@ -86,42 +87,43 @@ var Channel = function(name, Server) { show_public: false, enable_link_regex: true }; - this.filters = [ + self.filters = [ new Filter("monospace", "`([^`]+)`", "g", "$1"), new Filter("bold", "(^|\\s)\\*([^\\*]+)\\*", "g", "$1$2"), new Filter("italic", "(^| )_([^_]+)_", "g", "$1$2"), new Filter("strikethrough", "~~([^~]+)~~", "g", "$1"), new Filter("inline spoiler", "\\[spoiler\\](.*)\\[\\/spoiler\\]", "ig", "$1"), ]; - this.motd = { + self.motd = { motd: "", html: "" }; - this.ipbans = {}; - this.namebans = {}; - this.ip_alias = {}; - this.name_alias = {}; - this.login_hist = []; - this.logger = new Logger.Logger("chanlogs/" + this.canonical_name + ".log"); - this.i = 0; - this.time = new Date().getTime(); - this.plmeta = { + self.ipbans = {}; + self.namebans = {}; + self.ip_alias = {}; + self.name_alias = {}; + self.login_hist = []; + self.logger = new Logger.Logger("chanlogs/" + self.canonical_name + ".log"); + self.i = 0; + self.time = new Date().getTime(); + self.plmeta = { count: 0, time: "00:00" }; - this.css = ""; - this.js = ""; + self.css = ""; + self.js = ""; - this.ipkey = ""; + self.ipkey = ""; for(var i = 0; i < 15; i++) { - this.ipkey += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[parseInt(Math.random() * 65)] + self.ipkey += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[parseInt(Math.random() * 65)] } - Server.db.loadChannel(this); - if(this.registered) { - this.loadDump(); - } + Server.db.loadChannelData(self, function () { + if(self.registered) { + self.loadDump(); + } + }); } /* REGION Permissions */ @@ -338,18 +340,19 @@ Channel.prototype.tryReadLog = function (user) { }); } -Channel.prototype.tryRegister = function(user) { - if(this.registered) { - ActionLog.record(user.ip, user.name, "channel-register-failure", [ - this.name, "Channel already registered"]); +Channel.prototype.tryRegister = function (user) { + var self = this; + if(self.registered) { + ActionLog.record(user.ip, user.name, "channel-register-failure", + [self.name, "Channel already registered"]); user.socket.emit("registerChannel", { success: false, error: "This channel is already registered" }); } else if(!user.loggedIn) { - ActionLog.record(user.ip, user.name, "channel-register-failure", [ - this.name, "Not logged in"]); + ActionLog.record(user.ip, user.name, "channel-register-failure", + [self.name, "Not logged in"]); user.socket.emit("registerChannel", { success: false, error: "You must log in to register a channel" @@ -357,70 +360,135 @@ Channel.prototype.tryRegister = function(user) { } else if(!Rank.hasPermission(user, "registerChannel")) { - ActionLog.record(user.ip, user.name, "channel-register-failure", [ - this.name, "Insufficient permissions"]); + ActionLog.record(user.ip, user.name, "channel-register-failure", + [self.name, "Insufficient permissions"]); user.socket.emit("registerChannel", { success: false, - error: "You don't have permission to register this channel" + error: "You don't have permission to register self channel" }); } else { - if(this.server.db.registerChannel(this.name, user.name)) { - ActionLog.record(user.ip, user.name, "channel-register-success", this.name); - this.registered = true; - this.initialized = true; - this.saveDump(); - this.saveRank(user); + self.server.db.registerChannel(self.name, user.name, + function (err, res) { + if(err) { + user.socket.emit("registerChannel", { + success: false, + error: "Unable to register channel: " + err + }); + return; + } + + ActionLog.record(user.ip, user.name, + "channel-register-success", self.name); + self.registered = true; + self.initialized = true; + self.saveDump(); + self.saveRank(user); user.socket.emit("registerChannel", { - success: true, + success: true }); - this.logger.log("*** " + user.name + " registered the channel"); - Logger.syslog.log("Channel " + this.name + " was registered by " + user.name); - } - else { - user.socket.emit("registerChannel", { + self.logger.log("*** " + user.name + " registered the channel"); + }); + } +} + +Channel.prototype.unregister = function (user) { + var self = this; + + if(!self.registered) { + user.socket.emit("unregisterChannel", { + success: false, + error: "This channel is already unregistered" + }); + return; + } + + if(user.rank < 10) { + user.socket.emit("unregisterChannel", { + success: false, + error: "You must be the channel owner to unregister it" + }); + return; + } + self.server.db.dropChannel(self.name, function (err, res) { + if(err) { + user.socket.emit("unregisterChannel", { success: false, - error: "Unable to register channel, see an admin" + error: "Unregistration failed: " + err }); + return; } - } -} - -Channel.prototype.unregister = function() { - if(this.server.db.deleteChannel(this.name)) { - this.registered = false; - return true; - } - return false; -} - -Channel.prototype.getRank = function(name) { - var global = Auth.getGlobalRank(name); - if(!this.registered) { - return global; - } - var local = this.server.db.getChannelRank(this.name, name); - return local > global ? local : global; -} - -Channel.prototype.saveRank = function(user) { - return this.server.db.setChannelRank(this.name, user.name, user.rank); -} - -Channel.prototype.getIPRank = function(ip) { - var names = []; - if(!(ip in this.ip_alias)) - this.ip_alias[ip] = this.server.db.getAliases(ip); - this.ip_alias[ip].forEach(function(name) { - names.push(name); + + self.registered = false; + user.socket.emit("unregisterChannel", { success: true }); }); +} - var ranks = this.server.db.getChannelRank(this.name, names); - var rank = 0; - for(var i = 0; i < ranks.length; i++) { - rank = (ranks[i] > rank) ? ranks[i] : rank; +Channel.prototype.getRank = function (name, callback) { + var self = this; + self.server.db.getGlobalRank(name, function (err, global) { + if(err) { + callback(err, null); + return; + } + + if(!self.registered) { + callback(null, global); + return; + } + + self.server.db.getChannelRank(self.name, name, + function (err, rank) { + if(err) { + callback(err, null); + return; + } + + callback(null, rank > global ? rank : global); + }); + }); +} + +Channel.prototype.saveRank = function (user) { + this.server.db.setChannelRank(this.name, user.name, user.rank); +} + +Channel.prototype.getIPRank = function (ip, callback) { + var self = this; + var names = []; + var next = function (names) { + self.server.db.getChannelRank(self.name, names, + function (err, res) { + if(err) { + callback(err, null); + return; + } + + var rank = 0; + for(var i in res) { + rank = (res[i].rank > rank) ? res[i].rank : rank; + } + callback(null, rank); + }); + }; + + if(ip in self.ip_alias) { + names = self.ip_alias[ip]; + next(names); + } else if(ip.match(/^(\d+)\.(\d+)\.(\d+)$/)) { + // Range + for(var ip2 in self.ip_alias) { + if(ip2.indexOf(ip) == 0) { + for(var i in self.ip_aliases[ip2]) + names.push(self.ip_aliases[ip2][i]); + } + next(names); + } else{ + self.server.db.listAliases(ip, function (err, names) { + self.ip_alias[ip] = names; + next(names); + }); } - return rank; } Channel.prototype.cacheMedia = function(media) { @@ -437,35 +505,46 @@ Channel.prototype.cacheMedia = function(media) { } Channel.prototype.tryNameBan = function(actor, name) { - if(!this.hasPermission(actor, "ban")) { + var self = this; + if(!self.hasPermission(actor, "ban")) { return false; } name = name.toLowerCase(); - var rank = this.getRank(name); - if(rank >= actor.rank) { - actor.socket.emit("errorMsg", {msg: "You don't have permission to ban this person."}); - return false; - } - - this.namebans[name] = actor.name; - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].name.toLowerCase() == name) { - this.kick(this.users[i], "You're banned!"); - break; + self.getRank(name, function (err, rank) { + if(err) { + actor.socket.emit("errorMsg", { + msg: "Internal error" + }); + return; } - } - this.logger.log("*** " + actor.name + " namebanned " + name); - var chan = this; - this.users.forEach(function(u) { - chan.sendBanlist(u); - }); - if(!this.registered) { - return false; - } - return this.server.db.channelBan(this.name, "*", name, actor.name); + if(rank >= actor.rank) { + actor.socket.emit("errorMsg", { + msg: "You don't have permission to ban this person." + }); + return; + } + + self.namebans[name] = actor.name; + for(var i = 0; i < self.users.length; i++) { + if(self.users[i].name.toLowerCase() == name) { + self.kick(self.users[i], "You're banned!"); + break; + } + } + self.logger.log("*** " + actor.name + " namebanned " + name); + self.users.forEach(function(u) { + self.sendBanlist(u); + }); + + if(!self.registered) { + return; + } + + self.server.db.addChannelBan(self.name, "*", name, actor.name); + }); } Channel.prototype.unbanName = function(actor, name) { @@ -485,50 +564,54 @@ Channel.prototype.unbanName = function(actor, name) { } Channel.prototype.tryIPBan = function(actor, name, range) { - if(!this.hasPermission(actor, "ban")) { - return false; + var self = this; + if(!self.hasPermission(actor, "ban")) { + return; } if(typeof name != "string") { - return false; + return; } - var ips = this.server.db.ipForName(name); - var chan = this; - ips.forEach(function(ip) { - if(chan.getIPRank(ip) >= actor.rank) { - actor.socket.emit("errorMsg", {msg: "You don't have permission to ban IP: x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1")}); - return false; - } - - if(range) { + var ips = self.server.db.ipForName(name); + ips.forEach(function (ip) { + if(range) ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); - for(var ip2 in chan.ip_alias) { - if(ip2.indexOf(ip) == 0 && chan.getIPRank(ip2) >= actor.rank) { - actor.socket.emit("errorMsg", {msg: "You don't have permission to ban IP: x.x." + ip2.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1")}); - return false; + self.getIPRank(ip, function (err, rank) { + if(err) { + actor.socket.emit("errorMsg", { + msg: "Internal error" + }); + return; + } + + if(rank >= actor.rank) { + actor.socket.emit("errorMsg", { + msg: "You don't have permission to ban IP: x.x." + + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1") + }); + return; + } + + self.ipbans[ip] = [name, actor.name]; + self.logger.log("*** " + actor.name + " banned " + ip + + " (" + name + ")"); + + for(var i = 0; i < self.users.length; i++) { + if(self.users[i].ip.indexOf(ip) == 0) { + self.kick(self.users[i], "Your IP is banned!"); + i--; } } - } - chan.ipbans[ip] = [name, actor.name]; - //chan.broadcastBanlist(); - chan.logger.log("*** " + actor.name + " banned " + ip + " (" + name + ")"); - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].ip.indexOf(ip) == 0) { - chan.kick(chan.users[i], "Your IP is banned!"); - i--; - } - } + if(!self.registered) + return; - if(!chan.registered) - return false; - - // Update database ban table - return chan.server.db.channelBan(chan.name, ip, name, actor.name); - }); - - var chan = this; - this.users.forEach(function(u) { - chan.sendBanlist(u); + self.server.db.addChannelBan(chan.name, ip, name, actor.name + function (err, res) { + self.users.forEach(function(u) { + self.sendBanlist(u); + }); + }); + }); }); } @@ -1858,6 +1941,7 @@ Channel.prototype.sendMessage = function(username, msg, msgclass, data) { /* REGION Rank stuff */ Channel.prototype.trySetRank = function(user, data) { + var self = this; if(!Rank.hasPermission(user, "promote")) return; @@ -1871,9 +1955,9 @@ Channel.prototype.trySetRank = function(user, data) { return; var receiver; - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].name == data.user) { - receiver = this.users[i]; + for(var i = 0; i < self.users.length; i++) { + if(self.users[i].name == data.user) { + receiver = self.users[i]; break; } } @@ -1883,94 +1967,24 @@ Channel.prototype.trySetRank = function(user, data) { return; receiver.rank = data.rank; if(receiver.loggedIn) { - this.saveRank(receiver); + self.saveRank(receiver); } - this.broadcastUserUpdate(receiver); + self.broadcastUserUpdate(receiver); } else { - var rrank = this.getRank(data.user); - if(rrank >= user.rank) - return; - this.server.db.setChannelRank(this.name, data.user, data.rank); - } - - this.logger.log("*** " + user.name + " set " + data.user + "'s rank to " + - data.rank); - this.sendAllWithPermission("acl", "setChannelRank", data); -} - -Channel.prototype.tryPromoteUser = function(actor, data) { - if(!Rank.hasPermission(actor, "promote")) { - return; - } - - if(data.name == undefined) { - return; - } - - var name = data.name; - - var receiver; - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].name == name) { - receiver = this.users[i]; - break; - } - } - - var rank = receiver ? receiver.rank : this.getRank(data.name); - - if(actor.rank > rank + 1) { - rank++; - if(receiver) { - receiver.rank++; - if(receiver.loggedIn) { - this.saveRank(receiver); - } - this.broadcastUserUpdate(receiver); - } - else { - this.server.db.setChannelRank(this.name, data.name, rank); - } - this.logger.log("*** " + actor.name + " promoted " + data.name + " from " + (rank - 1) + " to " + rank); - //this.broadcastRankTable(); - } -} - -Channel.prototype.tryDemoteUser = function(actor, data) { - if(!Rank.hasPermission(actor, "promote")) { - return; - } - - if(data.name == undefined) { - return; - } - - var name = data.name; - var receiver; - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].name == name) { - receiver = this.users[i]; - break; - } - } - - var rank = receiver ? receiver.rank : this.getRank(data.name); - - if(actor.rank > rank) { - rank--; - if(receiver) { - receiver.rank--; - if(receiver.loggedIn) { - this.saveRank(receiver); - } - this.broadcastUserUpdate(receiver); - } - else { - this.server.db.setChannelRank(this.name, data.name, rank); - } - this.logger.log("*** " + actor.name + " demoted " + data.name + " from " + (rank + 1) + " to " + rank); - //this.broadcastRankTable(); + self.getRank(data.user, function (err, rrank) { + if(err) + return; + if(rrank >= user.rank) + return; + self.server.db.setChannelRank(this.name, data.user, + data.rank, function (err, res) { + + self.logger.log("*** " + user.name + " set " + + data.user + "'s rank to " + data.rank); + self.sendAllWithPermission("acl", "setChannelRank", data); + }); + }); } } diff --git a/user.js b/user.js index 6395c6c8..dfee73a0 100644 --- a/user.js +++ b/user.js @@ -343,29 +343,7 @@ User.prototype.initCallbacks = function() { if(this.channel == null) { return; } - if(!this.channel.registered) { - this.socket.emit("unregisterChannel", { - success: false, - error: "This channel is already unregistered" - }); - } - else if(this.rank < 10) { - this.socket.emit("unregisterChannel", { - success: false, - error: "You don't have permission to unregister" - }); - } - else if(this.channel.unregister()) { - this.socket.emit("unregisterChannel", { - success: true - }); - } - else { - this.socket.emit("unregisterChannel", { - success: false, - error: "Unregistration failed. Please see a site admin if this continues." - }); - } + this.channel.unregister(this); }.bind(this)); this.socket.on("setOptions", function(data) { From e155a30a1756fd6f022dc08e42fdf228975e57b7 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 12:37:35 -0500 Subject: [PATCH 31/60] Do a bit more work --- channel.js | 140 ++++++++++++++++++++++++----------------------------- 1 file changed, 64 insertions(+), 76 deletions(-) diff --git a/channel.js b/channel.js index 4195cd53..f6b624e0 100644 --- a/channel.js +++ b/channel.js @@ -492,14 +492,14 @@ Channel.prototype.getIPRank = function (ip, callback) { } Channel.prototype.cacheMedia = function(media) { + var self = this; // Prevent the copy in the playlist from messing with this one media = media.dup(); if(media.temp) { return; } - this.library[media.id] = media; - if(this.registered) { - return this.server.db.addToLibrary(this.name, media); + if(self.registered) { + self.server.db.addToLibrary(self.name, media); } return false; } @@ -548,19 +548,21 @@ Channel.prototype.tryNameBan = function(actor, name) { } Channel.prototype.unbanName = function(actor, name) { - if(!this.hasPermission(actor, "ban")) { + var self = this; + if(!self.hasPermission(actor, "ban")) { return false; } - this.namebans[name] = null; - delete this.namebans[name]; - this.logger.log("*** " + actor.name + " un-namebanned " + name); + self.namebans[name] = null; + delete self.namebans[name]; + self.logger.log("*** " + actor.name + " un-namebanned " + name); - var chan = this; - this.users.forEach(function(u) { - chan.sendBanlist(u); + self.server.db.clearChannelNameBan(self.name, name, function (err, res) { + + self.users.forEach(function(u) { + self.sendBanlist(u); + }); }); - return this.server.db.channelUnbanName(this.name, name); } Channel.prototype.tryIPBan = function(actor, name, range) { @@ -571,89 +573,75 @@ Channel.prototype.tryIPBan = function(actor, name, range) { if(typeof name != "string") { return; } - var ips = self.server.db.ipForName(name); - ips.forEach(function (ip) { - if(range) - ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); - self.getIPRank(ip, function (err, rank) { - if(err) { - actor.socket.emit("errorMsg", { - msg: "Internal error" - }); - return; - } - - if(rank >= actor.rank) { - actor.socket.emit("errorMsg", { - msg: "You don't have permission to ban IP: x.x." + - ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1") - }); - return; - } - - self.ipbans[ip] = [name, actor.name]; - self.logger.log("*** " + actor.name + " banned " + ip + - " (" + name + ")"); - - for(var i = 0; i < self.users.length; i++) { - if(self.users[i].ip.indexOf(ip) == 0) { - self.kick(self.users[i], "Your IP is banned!"); - i--; + self.server.db.listIPsForName(name, function (err, ips) { + if(err) { + actor.socket.emit("errorMsg", { + msg: "Internal error" + }); + return; + } + ips.forEach(function (ip) { + if(range) + ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); + self.getIPRank(ip, function (err, rank) { + if(err) { + actor.socket.emit("errorMsg", { + msg: "Internal error" + }); + return; } - } - if(!self.registered) - return; + if(rank >= actor.rank) { + actor.socket.emit("errorMsg", { + msg: "You don't have permission to ban IP: x.x." + + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1") + }); + return; + } - self.server.db.addChannelBan(chan.name, ip, name, actor.name - function (err, res) { - self.users.forEach(function(u) { - self.sendBanlist(u); + self.ipbans[ip] = [name, actor.name]; + self.logger.log("*** " + actor.name + " banned " + ip + + " (" + name + ")"); + + for(var i = 0; i < self.users.length; i++) { + if(self.users[i].ip.indexOf(ip) == 0) { + self.kick(self.users[i], "Your IP is banned!"); + i--; + } + } + + if(!self.registered) + return; + + self.server.db.addChannelBan(chan.name, ip, name, + actor.name, + function (err, res) { + self.users.forEach(function(u) { + self.sendBanlist(u); + }); }); }); }); }); } -Channel.prototype.banIP = function(actor, receiver) { - if(!this.hasPermission(actor, "ban")) - return false; - - this.ipbans[receiver.ip] = [receiver.name, actor.name]; - try { - receiver.socket.disconnect(true); - } - catch(e) { - // Socket already disconnected - } - //this.broadcastBanlist(); - this.logger.log(receiver.ip + " (" + receiver.name + ") was banned by " + actor.name); - - if(!this.registered) - return false; - - // Update database ban table - return this.server.db.channelBanIP(this.name, receiver.ip, receiver.name, actor.name); -} - Channel.prototype.unbanIP = function(actor, ip) { - if(!this.hasPermission(actor, "ban")) + var self = this; + if(!self.hasPermission(actor, "ban")) return false; - this.ipbans[ip] = null; - var chan = this; - this.users.forEach(function(u) { - chan.sendBanlist(u); + self.ipbans[ip] = null; + self.users.forEach(function(u) { + self.sendBanlist(u); }); - this.logger.log("*** " + actor.name + " unbanned " + ip); + self.logger.log("*** " + actor.name + " unbanned " + ip); - if(!this.registered) + if(!self.registered) return false; - //this.broadcastBanlist(); // Update database ban table - return this.server.db.channelUnbanIP(this.name, ip); + self.server.db.clearChannelIPBan(self.name, ip); } Channel.prototype.tryUnban = function(actor, data) { From 4204ea8ddf2bdacada5269120f478052dfb02fdf Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 15:47:11 -0500 Subject: [PATCH 32/60] Finish channel.js --- channel.js | 298 +++++++++++++++++++++++++++------------------------- database.js | 16 +++ user.js | 22 ++-- 3 files changed, 186 insertions(+), 150 deletions(-) diff --git a/channel.js b/channel.js index f6b624e0..daa31081 100644 --- a/channel.js +++ b/channel.js @@ -36,7 +36,6 @@ var Channel = function(name, Server) { self.users = []; self.afkers = []; self.playlist = new Playlist(self); - self.library = {}; self.position = -1; self.drinks = 0; self.leader = null; @@ -501,7 +500,6 @@ Channel.prototype.cacheMedia = function(media) { if(self.registered) { self.server.db.addToLibrary(self.name, media); } - return false; } Channel.prototype.tryNameBan = function(actor, name) { @@ -655,34 +653,20 @@ Channel.prototype.tryUnban = function(actor, data) { } Channel.prototype.search = function(query, callback) { - // Search youtube - if(callback) { - if(query.trim() == "") { - return; + var self = this; + self.server.db.searchLibrary(self.name, query, function (err, res) { + if(err) { + res = []; } - this.server.infogetter.Getters["ytSearch"](query.split(" "), function(err, vids) { - if(!err) { - callback(vids); - } + + results.sort(function(a, b) { + var x = a.title.toLowerCase(); + var y = b.title.toLowerCase(); + + return (x == y) ? 0 : (x < y ? -1 : 1); }); - return; - } - - query = query.toLowerCase(); - var results = []; - for(var id in this.library) { - if(this.library[id].title.toLowerCase().indexOf(query) != -1) { - results.push(this.library[id]); - } - } - results.sort(function(a, b) { - var x = a.title.toLowerCase(); - var y = b.title.toLowerCase(); - - return (x == y) ? 0 : (x < y ? -1 : 1); + callback(results); }); - - return results; } /* REGION User interaction */ @@ -870,7 +854,15 @@ Channel.prototype.sendRankStuff = function(user) { Channel.prototype.sendChannelRanks = function(user) { if(Rank.hasPermission(user, "acl")) { - user.socket.emit("channelRanks", this.server.db.listChannelRanks(this.name)); + this.server.db.listChannelRanks(this.name, function (err, res) { + if(err) { + user.socket.emit("errorMsg", { + msg: "Internal error: " + err + }); + return; + } + user.socket.emit("channelRanks", res); + }); } } @@ -949,50 +941,56 @@ Channel.prototype.broadcastUsercount = function() { } Channel.prototype.broadcastNewUser = function(user) { - var aliases = this.server.db.getAliases(user.ip); - var chan = this; - this.ip_alias[user.ip] = aliases; - aliases.forEach(function(alias) { - chan.name_alias[alias] = aliases; - }); - - this.login_hist.unshift({ - name: user.name, - aliases: this.ip_alias[user.ip], - time: Date.now() - }); - if(this.login_hist.length > 20) - this.login_hist.pop(); - - if(user.name.toLowerCase() in this.namebans && - this.namebans[user.name.toLowerCase()] != null) { - this.kick(user, "You're banned!"); - return; - } - this.sendAll("addUser", { - name: user.name, - rank: user.rank, - leader: this.leader == user, - meta: user.meta, - profile: user.profile - }); - //this.sendRankStuff(user); - if(user.rank > Rank.Guest) { - this.saveRank(user); - } - - var msg = user.name + " joined (aliases: "; - msg += this.ip_alias[user.ip].join(", ") + ")"; - var pkt = { - username: "[server]", - msg: msg, - msgclass: "server-whisper", - time: Date.now() - }; - this.users.forEach(function(u) { - if(u.rank >= 2) { - u.socket.emit("joinMessage", pkt); + var self = this; + self.server.db.listAliases(user.ip, function (err, aliases) { + if(err) { + aliases = []; } + + self.ip_alias[user.ip] = aliases; + aliases.forEach(function (alias) { + chan.name_alias[alias] = aliases; + }); + + self.login_hist.unshift({ + name: user.name, + aliases: self.ip_alias[user.ip], + time: Date.now() + }); + + if(self.login_hist.length > 20) + self.login_hist.pop(); + + if(user.name.toLowerCase() in self.namebans && + self.namebans[user.name.toLowerCase()] !== null) { + self.kick(user, "You're banned!"); + return; + } + self.sendAll("addUser", { + name: user.name, + rank: user.rank, + leader: self.leader == user, + meta: user.meta, + profile: user.profile + }); + + if(user.rank > Rank.Guest) { + self.saveRank(user); + } + + var msg = user.name + " joined (aliases: "; + msg += self.ip_alias[user.ip].join(", ") + ")"; + var pkt = { + username: "[server]", + msg: msg, + msgclass: "server-whisper", + time: Date.now() + }; + self.users.forEach(function(u) { + if(u.rank >= 2) { + u.socket.emit("joinMessage", pkt); + } + }); }); } @@ -1076,13 +1074,6 @@ Channel.prototype.broadcastBanlist = function() { } } -Channel.prototype.broadcastRankTable = function() { - var ranks = this.server.db.listChannelRanks(this.name); - for(var i = 0; i < this.users.length; i++) { - this.sendACL(this.users[i]); - } -} - Channel.prototype.broadcastChatFilters = function() { var filts = new Array(this.filters.length); for(var i = 0; i < this.filters.length; i++) { @@ -1200,9 +1191,6 @@ Channel.prototype.tryQueue = function(user, data) { if(typeof data.id !== "string" && data.id !== false) { return; } - if(typeof data.type !== "string" && !(data.id in this.library)) { - return; - } if(data.pos == "next" && !this.hasPermission(user, "playlistnext")) { return; @@ -1224,69 +1212,87 @@ Channel.prototype.tryQueue = function(user, data) { } Channel.prototype.addMedia = function(data, user) { - if(data.type === "yp" && !this.hasPermission(user, "playlistaddlist")) { - user.socket.emit("queueFail", "You don't have permission to add playlists"); + var self = this; + if(data.type === "yp" && + !self.hasPermission(user, "playlistaddlist")) { + user.socket.emit("queueFail", "You don't have permission to add " + + "playlists"); return; } - if(data.type === "cu" && !this.hasPermission(user, "playlistaddcustom")) { - user.socket.emit("queueFail", "You don't have permission to add cusstom embeds"); + if(data.type === "cu" && + !self.hasPermission(user, "playlistaddcustom")) { + user.socket.emit("queueFail", "You don't have permission to add " + + "custom embeds"); return; } data.temp = data.temp || isLive(data.type); data.queueby = user ? user.name : ""; - data.maxlength = this.hasPermission(user, "exceedmaxlength") ? 0 : this.opts.maxlength; - var chan = this; - if(data.id in this.library) { - var m = this.library[data.id].dup(); - if(data.maxlength && m.seconds > data.maxlength) { - user.socket.emit("queueFail", "Media is too long!"); + data.maxlength = self.hasPermission(user, "exceedmaxlength") + ? 0 + : this.opts.maxlength; + self.server.db.getLibraryItem(self.name, data.id, + function (err, item) { + if(err) { + user.socket.emit("queueFail", "Internal error: " + err); return; } - data.media = m; - this.playlist.addCachedMedia(data, function (err, item) { - if(err) { - if(err === true) - err = false; - if(user) - user.socket.emit("queueFail", err); + if(item !== null) { + var m = new Media(item.id, item.title, item.seconds, item.type); + if(data.maxlength && m.seconds > data.maxlength) { + user.socket.emit("queueFail", "Media is too long!"); return; } - else { - chan.logger.log("### " + user.name + " queued " + item.media.title); - chan.sendAll("queue", { - item: item.pack(), - after: item.prev ? item.prev.uid : "prepend" - }); - chan.broadcastPlaylistMeta(); - } - }); - return; - } - if(isLive(data.type) && !this.hasPermission(user, "playlistaddlive")) { - user.socket.emit("queueFail", "You don't have permission to queue livestreams"); - return; - } - this.playlist.addMedia(data, function(err, item) { - if(err) { - if(err === true) - err = false; - if(user) - user.socket.emit("queueFail", err); - return; - } - else { - chan.logger.log("### " + user.name + " queued " + item.media.title); - chan.sendAll("queue", { - item: item.pack(), - after: item.prev ? item.prev.uid : "prepend" + data.media = m; + self.playlist.addCachedMedia(data, function (err, item) { + if(err) { + if(err === true) + err = false; + if(user) + user.socket.emit("queueFail", err); + return; + } + else { + chan.logger.log("### " + user.name + " queued " + item.media.title); + chan.sendAll("queue", { + item: item.pack(), + after: item.prev ? item.prev.uid : "prepend" + }); + chan.broadcastPlaylistMeta(); + } + }); + return; + } else { + if(isLive(data.type) && + !self.hasPermission(user, "playlistaddlive")) { + user.socket.emit("queueFail", "You don't have " + + "permission to add livestreams"); + return; + } + self.playlist.addMedia(data, function(err, item) { + if(err) { + if(err === true) + err = false; + if(user) + user.socket.emit("queueFail", err); + return; + } + else { + chan.logger.log("### " + user.name + " queued " + item.media.title); + chan.sendAll("queue", { + item: item.pack(), + after: item.prev ? item.prev.uid : "prepend" + }); + chan.broadcastPlaylistMeta(); + if(!item.temp) + chan.cacheMedia(item.media); + } }); - chan.broadcastPlaylistMeta(); - if(!item.temp) - chan.cacheMedia(item.media); } + }); + } Channel.prototype.addMediaList = function(data, user) { @@ -1315,6 +1321,7 @@ Channel.prototype.addMediaList = function(data, user) { } Channel.prototype.tryQueuePlaylist = function(user, data) { + var self = this; if(!this.hasPermission(user, "playlistaddlist")) { return; } @@ -1328,11 +1335,19 @@ Channel.prototype.tryQueuePlaylist = function(user, data) { return; } - var pl = this.server.db.loadUserPlaylist(user.name, data.name); - data.list = pl; - data.queueby = user.name; - data.temp = !this.hasPermission(user, "addnontemp"); - this.addMediaList(data, user); + self.server.db.getUserPlaylist(user.name, data.name, + function (err, pl) { + if(err) { + user.socket.emit("errorMsg", { + msg: "Playlist load failed: " + err + }); + return; + } + data.list = pl; + data.queueby = user.name; + data.temp = !self.hasPermission(user, "addnontemp"); + self.addMediaList(data, user); + }); } Channel.prototype.setTemp = function(uid, temp) { @@ -1394,13 +1409,14 @@ Channel.prototype.tryUncache = function(user, data) { if(typeof data.id != "string") { return; } - if(this.server.db.removeFromLibrary(this.name, data.id)) { - var msg = data.id; - if(data.id in this.library) - msg = this.library[data.id]; - this.logger.log("*** " + user.name + " deleted " + msg + " from library"); - delete this.library[data.id]; - } + self.server.db.removeFromLibrary(self.name, data.id, + function (err, res) { + if(err) + return; + + self.logger.log("*** " + user.name + " deleted " + data.id + + " from library"); + }); } Channel.prototype.playNext = function() { diff --git a/database.js b/database.js index 7628fc5c..6d0fa265 100644 --- a/database.js +++ b/database.js @@ -607,6 +607,22 @@ Database.prototype.getLibraryItem = function (channame, id, callback) { }); }; +Database.prototype.searchLibrary = function (channame, term, callback) { + var self = this; + if(typeof callback !== "function") + return; + + if(!$util.isValidChannelName(channame)) { + callback("Invalid channel name", null); + return; + } + + var query = "SELECT id, title, seconds, type FROM " + + "`chan_" + channame + "_library` WHERE title LIKE %?%"; + + self.query(query, [term], callback); +}; + Database.prototype.addChannelBan = function (channame, ip, name, banBy, callback) { var self = this; diff --git a/user.js b/user.js index dfee73a0..063ed3a9 100644 --- a/user.js +++ b/user.js @@ -298,19 +298,23 @@ User.prototype.initCallbacks = function() { }.bind(this)); this.socket.on("searchMedia", function(data) { + var self = this; if(this.channel != null) { if(data.source == "yt") { - var callback = function(vids) { - this.socket.emit("searchResults", { + var searchfn = self.server.infogetter.Getters["ytSearch"]; + searchfn(data.query.split(" "), function (e, vids) { + if(!e) { + self.socket.emit("searchResults", { + results: vids + }); + } + }); + } else { + self.channel.search(data.query, function (vids) { + self.socket.emit("searchResults", { results: vids }); - }.bind(this); - this.channel.search(data.query, callback); - } - else { - this.socket.emit("searchResults", { - results: this.channel.search(data.query) - }); + } } } }.bind(this)); From ebd55b484682b7829546bca5c84717a5ab1979f0 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 15:47:24 -0500 Subject: [PATCH 33/60] Remove unnecessary import --- channel.js | 1 - 1 file changed, 1 deletion(-) diff --git a/channel.js b/channel.js index daa31081..7978452f 100644 --- a/channel.js +++ b/channel.js @@ -16,7 +16,6 @@ var Media = require("./media.js").Media; var formatTime = require("./media.js").formatTime; var Logger = require("./logger.js"); var Rank = require("./rank.js"); -var Auth = require("./auth.js"); var ChatCommand = require("./chatcommand.js"); var Filter = require("./filter.js").Filter; var ActionLog = require("./actionlog"); From 65d7a8a4557e9293b5605c800dd7be54a1f973cc Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 15:54:23 -0500 Subject: [PATCH 34/60] Fix user.js for new auth --- user.js | 241 +++++++++++++++++++++++--------------------------------- 1 file changed, 97 insertions(+), 144 deletions(-) diff --git a/user.js b/user.js index 063ed3a9..3111a313 100644 --- a/user.js +++ b/user.js @@ -533,15 +533,16 @@ User.prototype.initCallbacks = function() { var lastguestlogin = {}; // Attempt to login User.prototype.login = function(name, pw, session) { + var self = this; // No password => try guest login if(pw == "" && session == "") { - if(this.ip in lastguestlogin) { - var diff = (Date.now() - lastguestlogin[this.ip])/1000; - if(diff < this.server.cfg["guest-login-delay"]) { - this.socket.emit("login", { + if(self.ip in lastguestlogin) { + var diff = (Date.now() - lastguestlogin[self.ip])/1000; + if(diff < self.server.cfg["guest-login-delay"]) { + self.socket.emit("login", { success: false, error: ["Guest logins are restricted to one per ", - this.server.cfg["guest-login-delay"] + self.server.cfg["guest-login-delay"] + " seconds per IP. ", "This restriction does not apply to registered users." ].join("") @@ -549,149 +550,101 @@ User.prototype.login = function(name, pw, session) { return false; } } - try { - // Sorry bud, can't take that name - if(Auth.isRegistered(name)) { - this.socket.emit("login", { - success: false, - error: "That username is already taken" - }); - return false; - } - // YOUR ARGUMENT IS INVALID - else if(!Auth.validateName(name)) { - this.socket.emit("login", { - success: false, - error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores" - }); - } - else { - if(this.channel != null) { - for(var i = 0; i < this.channel.users.length; i++) { - if(this.channel.users[i].name == name) { - this.socket.emit("login", { - success: false, - error: "That name is already taken on this channel" - }); - return; - } - } - } - lastguestlogin[this.ip] = Date.now(); - this.rank = Rank.Guest; - Logger.syslog.log(this.ip + " signed in as " + name); - this.server.db.recordVisit(this.ip, name); - this.name = name; - this.loggedIn = false; - this.socket.emit("login", { - success: true, - name: name - }); - this.socket.emit("rank", this.rank); - if(this.channel != null) { - this.channel.logger.log(this.ip + " signed in as " + name); - this.channel.broadcastNewUser(this); - } - } - } - catch(e) { - this.socket.emit("login", { + if(!$util.isValidUserName(name)) { + self.socket.emit("login", { success: false, - error: e + error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores" }); + return; } - } - else { - try { - var row; - if((row = Auth.login(name, pw, session))) { - if(this.channel != null) { - for(var i = 0; i < this.channel.users.length; i++) { - if(this.channel.users[i].name == name) { - this.channel.kick(this.channel.users[i], "Duplicate login"); - } - } - } - if(this.global_rank >= 255) - ActionLog.record(this.ip, name, "login-success"); - this.loggedIn = true; - this.socket.emit("login", { - success: true, - session: row.session_hash, - name: name - }); - Logger.syslog.log(this.ip + " logged in as " + name); - this.server.db.recordVisit(this.ip, name); - this.profile = { - image: row.profile_image, - text: row.profile_text - }; - var chanrank = (this.channel != null) ? this.channel.getRank(name) - : Rank.Guest; - var rank = (chanrank > row.global_rank) ? chanrank - : row.global_rank; - this.rank = (this.rank > rank) ? this.rank : rank; - this.global_rank = row.global_rank; - this.socket.emit("rank", this.rank); - this.name = name; - if(this.channel != null) { - this.channel.logger.log(this.ip + " logged in as " + name); - this.channel.broadcastNewUser(this); - } - } - // Wrong password - else { - ActionLog.record(this.ip, this.name, "login-failure"); - this.socket.emit("login", { - success: false, - error: "Invalid session" - }); - return false; - } - } - catch(e) { - this.socket.emit("login", { - success: false, - error: e - }); - } - } -} -// Attempt to register a user account -User.prototype.register = function(name, pw) { - if(pw == "") { - // Sorry bud, password required - this.socket.emit("register", { - success: false, - error: "You must provide a password" - }); - return false; - } - else if(Auth.isRegistered(name)) { - this.socket.emit("register", { - success: false, - error: "That username is already taken" - }); - return false; - } - else if(!Auth.validateName(name)) { - this.socket.emit("register", { - success: false, - error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores" - }); - } - else if(Auth.register(name, pw)) { - console.log(this.ip + " registered " + name); - this.socket.emit("register", { - success: true - }); - this.login(name, pw); - } - else { - this.socket.emit("register", { - success: false, - error: "[](/ppshrug) Registration Failed." + self.server.db.isUsernameTaken(name, function (err, taken) { + if(err) { + self.socket.emit("login", { + success: false, + error: "Internal error: " + err + }); + return; + } + + if(taken) { + self.socket.emit("login", { + success: false, + error: "That username is taken" + }); + return; + } + + if(self.channel != null) { + for(var i = 0; i < self.channel.users.length; i++) { + if(self.channel.users[i].name == name) { + self.socket.emit("login", { + success: false, + error: "That name is already taken on self channel" + }); + return; + } + } + } + lastguestlogin[self.ip] = Date.now(); + self.rank = Rank.Guest; + Logger.syslog.log(self.ip + " signed in as " + name); + self.server.db.recordVisit(self.ip, name); + self.name = name; + self.loggedIn = false; + self.socket.emit("login", { + success: true, + name: name + }); + self.socket.emit("rank", self.rank); + if(self.channel != null) { + self.channel.logger.log(self.ip + " signed in as " + name); + self.channel.broadcastNewUser(self); + } + } + } else { + self.server.db.userLogin(name, pw, session, function (err, row) { + if(err) { + ActionLog.record(self.ip, self.name, "login-failure"); + self.socket.emit("login", { + success: false, + error: err + }); + return; + } + if(self.channel != null) { + for(var i = 0; i < self.channel.users.length; i++) { + if(self.channel.users[i].name == name) { + self.channel.kick(self.channel.users[i], "Duplicate login"); + } + } + } + if(self.global_rank >= 255) + ActionLog.record(self.ip, name, "login-success"); + self.loggedIn = true; + self.socket.emit("login", { + success: true, + session: row.session_hash, + name: name + }); + Logger.syslog.log(self.ip + " logged in as " + name); + self.server.db.recordVisit(self.ip, name); + self.profile = { + image: row.profile_image, + text: row.profile_text + }; + var chanrank = (self.channel != null) ? self.channel.getRank(name) + : Rank.Guest; + var rank = (chanrank > row.global_rank) ? chanrank + : row.global_rank; + self.rank = (self.rank > rank) ? self.rank : rank; + self.global_rank = row.global_rank; + self.socket.emit("rank", self.rank); + self.name = name; + if(self.channel != null) { + self.channel.logger.log(self.ip + " logged in as " + name); + self.channel.broadcastNewUser(self); + } }); } } From b686deb16f28146d699b468e24314fb562b78384 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 18:44:48 -0500 Subject: [PATCH 35/60] Refactoring --- acp.js | 2 +- api.js | 2 +- channel.js | 9 ++++----- database.js | 19 +++++++++++++++++++ server.js | 23 +++++++++++++---------- stats.js | 28 ++++------------------------ user.js | 7 +++---- 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/acp.js b/acp.js index 7446fe44..d0ade27b 100644 --- a/acp.js +++ b/acp.js @@ -11,7 +11,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI module.exports = function (Server) { var db = Server.db; - var ActionLog = require("./actionlog")(Server); + var ActionLog = Server.actionlog; return { init: function(user) { ActionLog.record(user.ip, user.name, "acp-init"); diff --git a/api.js b/api.js index 76e5e38c..96654562 100644 --- a/api.js +++ b/api.js @@ -13,7 +13,7 @@ var Logger = require("./logger"); var fs = require("fs"); module.exports = function (Server) { - var ActionLog = require("./actionlog")(Server); + var ActionLog = Server.actionlog; function getIP(req) { var raw = req.connection.remoteAddress; var forward = req.header("x-forwarded-for"); diff --git a/channel.js b/channel.js index 7978452f..ee9feab3 100644 --- a/channel.js +++ b/channel.js @@ -18,7 +18,6 @@ var Logger = require("./logger.js"); var Rank = require("./rank.js"); var ChatCommand = require("./chatcommand.js"); var Filter = require("./filter.js").Filter; -var ActionLog = require("./actionlog"); var Playlist = require("./playlist"); var sanitize = require("validator").sanitize; @@ -341,7 +340,7 @@ Channel.prototype.tryReadLog = function (user) { Channel.prototype.tryRegister = function (user) { var self = this; if(self.registered) { - ActionLog.record(user.ip, user.name, "channel-register-failure", + self.server.actionlog.record(user.ip, user.name, "channel-register-failure", [self.name, "Channel already registered"]); user.socket.emit("registerChannel", { success: false, @@ -349,7 +348,7 @@ Channel.prototype.tryRegister = function (user) { }); } else if(!user.loggedIn) { - ActionLog.record(user.ip, user.name, "channel-register-failure", + self.server.actionlog.record(user.ip, user.name, "channel-register-failure", [self.name, "Not logged in"]); user.socket.emit("registerChannel", { success: false, @@ -358,7 +357,7 @@ Channel.prototype.tryRegister = function (user) { } else if(!Rank.hasPermission(user, "registerChannel")) { - ActionLog.record(user.ip, user.name, "channel-register-failure", + self.server.actionlog.record(user.ip, user.name, "channel-register-failure", [self.name, "Insufficient permissions"]); user.socket.emit("registerChannel", { success: false, @@ -376,7 +375,7 @@ Channel.prototype.tryRegister = function (user) { return; } - ActionLog.record(user.ip, user.name, + self.server.actionlog.record(user.ip, user.name, "channel-register-success", self.name); self.registered = true; self.initialized = true; diff --git a/database.js b/database.js index 6d0fa265..4186e056 100644 --- a/database.js +++ b/database.js @@ -1353,6 +1353,25 @@ Database.prototype.listActions = function (types, callback) { /* REGION stats */ +Database.prototype.addStatPoint = function (time, ucount, ccount, mem, + callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "INSERT INTO stats VALUES (?, ?, ?, ?)"; + self.query(query, [time, ucount, ccount, mem], callback); +}; + +Database.prototype.pruneStats = function (before, callback) { + var self = this; + if(typeof callback !== "function") + callback = blackHole; + + var query = "DELETE FROM stats WHERE time < ?"; + self.query(query, [before], callback); +}; + Database.prototype.listStats = function (callback) { var self = this; if(typeof callback !== "function") diff --git a/server.js b/server.js index 70c2fb81..5cecd78a 100644 --- a/server.js +++ b/server.js @@ -73,6 +73,7 @@ var Server = { ips: {}, acp: null, httpaccess: null, + actionlog: null, logHTTP: function (req, status) { if(status === undefined) status = 200; @@ -88,6 +89,7 @@ var Server = { this.httpaccess.log([ipstr, req.method, url, status, req.headers["user-agent"]].join(" ")); }, init: function () { + this.actionlog = require("./actionlog")(this); this.httpaccess = new Logger.Logger("httpaccess.log"); this.app = express(); this.app.use(express.bodyParser()); @@ -169,14 +171,15 @@ var Server = { this.io.sockets.on("connection", function (socket) { var ip = getSocketIP(socket); socket._ip = ip; - if(this.db.checkGlobalBan(ip)) { - Logger.syslog.log("Disconnecting " + ip + " - gbanned"); - socket.emit("kick", { - reason: "You're globally banned." - }); - socket.disconnect(true); - return; - } + this.db.isGlobalIPBanned(ip, function (err, bant) { + if(bant) { + Logger.syslog.log("Disconnecting " + ip + " - gbanned"); + socket.emit("kick", { + reason: "You're globally banned." + }); + socket.disconnect(true); + } + }); socket.on("disconnect", function () { this.ips[ip]--; @@ -200,8 +203,8 @@ var Server = { }.bind(this)); // init database - this.db = require("./database"); - this.db.setup(Server.cfg); + var Database = require("./database"); + this.db = new Database(this.cfg); this.db.init(); // init ACP diff --git a/stats.js b/stats.js index 04349990..c8a061af 100644 --- a/stats.js +++ b/stats.js @@ -15,6 +15,7 @@ const STAT_INTERVAL = 60 * 60 * 1000; const STAT_EXPIRE = 24 * STAT_INTERVAL; module.exports = function (Server) { + var db = Server.db; setInterval(function () { var chancount = Server.channels.length; var usercount = 0; @@ -24,29 +25,8 @@ module.exports = function (Server) { var mem = process.memoryUsage().rss; - var db = Server.db.getConnection(); - if(!db) - return; - - var query = Server.db.createQuery( - "INSERT INTO stats VALUES (?, ?, ?, ?)", - [Date.now(), usercount, chancount, mem] - ); - - if(!db.querySync(query)) { - Logger.errlog.log("! Failed to record stats"); - Logger.errlog.log(query); - } - - query = Server.db.createQuery( - "DELETE FROM stats WHERE time= 255) - ActionLog.record(self.ip, name, "login-success"); + self.server.actionlog.record(self.ip, name, "login-success"); self.loggedIn = true; self.socket.emit("login", { success: true, From f4b32ad3adade8151837394c2130fdcbf1e5f1e4 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 18:45:21 -0500 Subject: [PATCH 36/60] Make config saving synchronous --- config.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/config.js b/config.js index b8c5b69a..4d21e58c 100644 --- a/config.js +++ b/config.js @@ -49,12 +49,7 @@ function save(cfg, file) { if(k !== "nodemailer" && k !== "loaded") x[k] = cfg[k]; } - fs.writeFile(file, JSON.stringify(x, null, 4), function (err) { - if(err) { - Logger.errlog.log("Failed to save config"); - Logger.errlog.log(err); - } - }); + fs.writeFileSync(file, JSON.stringify(x, null, 4)); } exports.load = function (Server, file, callback) { From 492a50fb963e2138fef7fb53315e2883b9b27320 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 18:51:10 -0500 Subject: [PATCH 37/60] Well the process starts, that's a start --- api.js | 4 ++-- channel.js | 3 ++- database.js | 4 ++-- server.js | 8 ++++---- user.js | 5 ++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api.js b/api.js index 96654562..58735f83 100644 --- a/api.js +++ b/api.js @@ -514,7 +514,7 @@ module.exports = function (Server) { return; } - db.listUserChannels(name, function (err, res) { + db.listUserChannels(name, function (err, dbres) { if(err) { res.jsonp({ success: false, @@ -525,7 +525,7 @@ module.exports = function (Server) { res.jsonp({ success: true, - channels: res + channels: dbres }); }); }); diff --git a/channel.js b/channel.js index ee9feab3..7bf92123 100644 --- a/channel.js +++ b/channel.js @@ -478,9 +478,10 @@ Channel.prototype.getIPRank = function (ip, callback) { if(ip2.indexOf(ip) == 0) { for(var i in self.ip_aliases[ip2]) names.push(self.ip_aliases[ip2][i]); + } } next(names); - } else{ + } else { self.server.db.listAliases(ip, function (err, names) { self.ip_alias[ip] = names; next(names); diff --git a/database.js b/database.js index 4186e056..2104dc1e 100644 --- a/database.js +++ b/database.js @@ -24,6 +24,7 @@ var Database = function (cfg) { this.pool.getConnection(function (err, conn) { if(err) { Logger.errlog.log("! DB connection failed"); + return; } conn.end(); }); @@ -45,7 +46,6 @@ Database.prototype.query = function (query, sub, callback) { self.pool.getConnection(function (err, conn) { if(err) { callback("Database failure", null); - conn.end(); } else { function cback(err, res) { if(err) { @@ -217,7 +217,7 @@ Database.prototype.init = function () { }); // Refresh global IP bans - self.getGlobalIPBans(); + self.listGlobalIPBans(); }; /* REGION global bans */ diff --git a/server.js b/server.js index 5cecd78a..2bc42bf1 100644 --- a/server.js +++ b/server.js @@ -89,6 +89,10 @@ var Server = { this.httpaccess.log([ipstr, req.method, url, status, req.headers["user-agent"]].join(" ")); }, init: function () { + // init database + var Database = require("./database"); + this.db = new Database(this.cfg); + this.db.init(); this.actionlog = require("./actionlog")(this); this.httpaccess = new Logger.Logger("httpaccess.log"); this.app = express(); @@ -202,10 +206,6 @@ var Server = { new User(socket, this); }.bind(this)); - // init database - var Database = require("./database"); - this.db = new Database(this.cfg); - this.db.init(); // init ACP this.acp = require("./acp")(this); diff --git a/user.js b/user.js index a078e2e7..1ef9130a 100644 --- a/user.js +++ b/user.js @@ -13,7 +13,6 @@ var Rank = require("./rank.js"); var Channel = require("./channel.js").Channel; var formatTime = require("./media.js").formatTime; var Logger = require("./logger.js"); -var self.server.actionlog = require("./actionlog"); // Represents a client connected via socket.io var User = function(socket, Server) { @@ -313,7 +312,7 @@ User.prototype.initCallbacks = function() { self.socket.emit("searchResults", { results: vids }); - } + }); } } }.bind(this)); @@ -600,7 +599,7 @@ User.prototype.login = function(name, pw, session) { self.channel.logger.log(self.ip + " signed in as " + name); self.channel.broadcastNewUser(self); } - } + }); } else { self.server.db.userLogin(name, pw, session, function (err, row) { if(err) { From 26e654cf5ab4a2fa9081d703420485102fd9def1 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 18:52:13 -0500 Subject: [PATCH 38/60] Oh yeah, use the actual logger --- auth.js | 226 ---------- database-old.js | 1084 ----------------------------------------------- 2 files changed, 1310 deletions(-) delete mode 100644 auth.js delete mode 100644 database-old.js diff --git a/auth.js b/auth.js deleted file mode 100644 index 89fb8efc..00000000 --- a/auth.js +++ /dev/null @@ -1,226 +0,0 @@ -/* -The MIT License (MIT) -Copyright (c) 2013 Calvin Montgomery - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -var mysql = require("mysql-libmysqlclient"); -var Database = require("./database.js"); -var bcrypt = require("bcrypt"); -var hashlib = require("node_hash"); -var Logger = require("./logger.js"); - -// Check if a name is taken -exports.isRegistered = function(name) { - var db = Database.getConnection(); - if(!db) { - throw "Database failure"; - } - var query = Database.createQuery( - "SELECT * FROM `registrations` WHERE uname=?", - [name] - ); - var results = db.querySync(query); - if(!results) { - return true; - } - - var rows = results.fetchAllSync(); - return rows.length > 0; -} - -// Check if a name is valid -// Valid names are 1-20 characters, alphanumeric and underscores -exports.validateName = function(name) { - if(name.length > 20) - return false; - const VALID_REGEX = /^[a-zA-Z0-9_]+$/; - return name.match(VALID_REGEX) != null; -} - -// Try to register a new account -exports.register = function(name, pw) { - if(!exports.validateName(name)) - return false; - if(exports.isRegistered(name)) - return false; - var db = Database.getConnection(); - if(!db) { - return false; - } - - var hash = bcrypt.hashSync(pw, 10); - var query = Database.createQuery( - ["INSERT INTO `registrations` VALUES ", - "(NULL, ?, ?, 1, '', 0, '', '', '')"].join(""), - [name, hash] - ); - var results = db.querySync(query); - if(results) { - return exports.createSession(name); - } - return false; -} - -exports.login = function(name, pw, session) { - if(session) { - var res = exports.loginSession(name, session); - if(res) { - return res; - } - else if(!pw) { - return false; - } - } - var row = exports.loginPassword(name, pw); - if(row) { - var hash = exports.createSession(name); - row.session_hash = hash; - return row; - } -} - -// Try to login -exports.loginPassword = function(name, pw) { - var db = Database.getConnection(); - if(!db) { - throw "Database failure"; - } - var query = Database.createQuery( - "SELECT * FROM `registrations` WHERE uname=?", - [name] - ); - var results = db.querySync(query); - if(!results) { - return false; - } - var rows = results.fetchAllSync(); - if(rows.length > 0) { - try { - if(bcrypt.compareSync(pw, rows[0].pw)) { - return rows[0]; - } - else { - // Check if the sha256 is in the database - // If so, migrate to bcrypt - var sha256 = hashlib.sha256(pw); - if(sha256 == rows[0].pw) { - var newhash = bcrypt.hashSync(pw, 10); - var query = Database.createQuery( - ["UPDATE `registrations` SET pw=?", - "WHERE uname=?"].join(""), - [newhash, name] - ); - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("Failed to migrate password! user=" + name); - return false; - } - return rows[0]; - } - return false; - } - } - catch(e) { - Logger.errlog.log("Auth.login fail"); - Logger.errlog.log(e); - } - } - return false; -} - -exports.createSession = function(name) { - var salt = sessionSalt(); - var hash = hashlib.sha256(salt + name); - var db = Database.getConnection(); - if(!db) { - throw "Database failure"; - } - var query = Database.createQuery( - ["UPDATE `registrations` SET ", - "`session_hash`=?,", - "`expire`=? ", - "WHERE uname=?"].join(""), - [hash, Date.now() + 604800000, name] - ); - var results = db.querySync(query); - return results ? hash : false; -} - -exports.loginSession = function(name, hash) { - var db = Database.getConnection(); - if(!db) { - throw "Database failure"; - } - var query = Database.createQuery( - "SELECT * FROM `registrations` WHERE `uname`=?", - [name] - ); - var results = db.querySync(query); - if(!results) { - return false; - } - var rows = results.fetchAllSync(); - if(rows.length != 1) { - return false; - } - - var dbhash = rows[0].session_hash; - if(hash != dbhash) { - return false; - } - var timeout = rows[0].expire; - if(timeout < new Date().getTime()) { - return false; - } - return rows[0]; -} - -function sessionSalt() { - var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - + "0123456789!@#$%^&*_+=~"; - var salt = []; - for(var i = 0; i < 32; i++) { - salt.push(chars[parseInt(Math.random()*chars.length)]); - } - return salt.join(''); -} - -exports.setUserPassword = function(name, pw) { - var db = Database.getConnection(); - if(!db) { - return false; - } - var hash = bcrypt.hashSync(pw, 10); - var query = Database.createQuery( - "UPDATE `registrations` SET `pw`=? WHERE `uname`=?", - [hash, name] - ); - var result = db.querySync(query); - return result; -} - -exports.getGlobalRank = function(name) { - var db = Database.getConnection(); - if(!db) { - return false; - } - var query = Database.createQuery( - "SELECT * FROM `registrations` WHERE `uname`=?", - [name] - ); - var results = db.querySync(query); - if(!results) { - return 0; - } - var rows = results.fetchAllSync(); - if(rows.length > 0) { - return rows[0].global_rank; - } - return 0; -} diff --git a/database-old.js b/database-old.js deleted file mode 100644 index 9da571c3..00000000 --- a/database-old.js +++ /dev/null @@ -1,1084 +0,0 @@ -/* -The MIT License (MIT) -Copyright (c) 2013 Calvin Montgomery - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -var mysql = require("mysql-libmysqlclient"); -var Logger = require("./logger"); -var Media = require("./media").Media; -var bcrypt = require("bcrypt"); -var hashlib = require("node_hash"); - -var db = false; -var SERVER = ""; -var USER = ""; -var DATABASE = ""; -var PASSWORD = ""; -var CONFIG = {}; -var global_bans = {}; - -function setup(cfg) { - SERVER = cfg["mysql-server"]; - USER = cfg["mysql-user"]; - DATABASE = cfg["mysql-db"]; - PASSWORD = cfg["mysql-pw"]; - CONFIG = cfg; -} - -function getConnection() { - if(db && db.connectedSync()) { - return db; - } - db = mysql.createConnectionSync(); - db.connectSync(SERVER, USER, PASSWORD, DATABASE); - if(!db.connectedSync()) { - Logger.errlog.log("DB connection failed"); - return false; - } - if(CONFIG["debug"]) { - db._querySync = db.querySync; - db.querySync = function(q) { - Logger.syslog.log("DEBUG: " + q); - return this._querySync(q); - } - } - return db; -} - -function sqlEscape(obj) { - if(obj === undefined || obj === null) - return "NULL"; - - if(typeof obj === "boolean") - return obj ? "true" : "false"; - - if(typeof obj === "number") - return obj + ""; - - if(typeof obj === "object") - return "'object'"; - - if(typeof obj === "string") { - obj = obj.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { - switch(s) { - case "\0": return "\\0"; - case "\n": return "\\n"; - case "\r": return "\\r"; - case "\b": return "\\b"; - case "\t": return "\\t"; - case "\x1a": return "\\Z"; - default: return "\\" + s; - } - }); - return "'" + obj + "'"; - } -} - -function createQuery(template, args) { - var last = -1; - while(template.indexOf("?", last) >= 0) { - var idx = template.indexOf("?", last); - var arg = args.shift(); - arg = sqlEscape(arg); - // Stupid workaround because even if I call replace() with a string - // and not a regex, '$' still holds special meaning - // this actually replaces '$' with '$$' - // What the hell, Javascript? - arg = arg.replace(/\$/g, "$$$$"); - var first = template.substring(0, idx); - template = first + template.substring(idx).replace("?", arg); - last = idx + arg.length; - } - template = template.replace(/`'/g, "`"); - template = template.replace(/'`/g, "`"); - return template; -} - -function init() { - var db = getConnection(); - if(!db) { - return false; - } - - // Create channel table - var query = ["CREATE TABLE IF NOT EXISTS `channels` (", - "`id` INT NOT NULL AUTO_INCREMENT,", - "`name` VARCHAR(255) NOT NULL,", - "`owner` VARCHAR(20) NOT NULL,", - "PRIMARY KEY(`id`))", - "ENGINE = MyISAM;"].join(""); - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create channels table"); - } - - // Create registration table - query = ["CREATE TABLE IF NOT EXISTS `registrations` (", - "`id` INT NOT NULL AUTO_INCREMENT,", - "`uname` VARCHAR(20) NOT NULL,", - "`pw` VARCHAR(64) NOT NULL,", - "`global_rank` INT NOT NULL,", - "`session_hash` VARCHAR(64) NOT NULL,", - "`expire` BIGINT NOT NULL,", - "`profile_image` VARCHAR(255) NOT NULL,", - "`profile_text` TEXT NOT NULL,", - "`email` VARCHAR(255) NOT NULL,", - "PRIMARY KEY (`id`))", - "ENGINE = MyISAM;"].join(""); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create registrations table"); - } - - // Create global bans table - query = ["CREATE TABLE IF NOT EXISTS `global_bans` (", - "`ip` VARCHAR(15) NOT NULL,", - "`note` VARCHAR(255) NOT NULL,", - "PRIMARY KEY (`ip`))", - "ENGINE = MyISAM;"].join(""); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create global ban table"); - } - - refreshGlobalBans(); - - // Create password reset table - query = ["CREATE TABLE IF NOT EXISTS `password_reset` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`hash` VARCHAR(64) NOT NULL,", - "`email` VARCHAR(255) NOT NULL,", - "`expire` BIGINT NOT NULL,", - "PRIMARY KEY (`name`))", - "ENGINE = MyISAM;"].join(""); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create password reset table"); - } - - // Create user playlist table - query = ["CREATE TABLE IF NOT EXISTS `user_playlists` (", - "`user` VARCHAR(20) NOT NULL,", - "`name` VARCHAR(255) NOT NULL,", - "`contents` MEDIUMTEXT NOT NULL,", - "`count` INT NOT NULL,", - "`time` INT NOT NULL,", - "PRIMARY KEY (`user`, `name`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create playlist table"); - } - - // Create user aliases table - query = ["CREATE TABLE IF NOT EXISTS `aliases` (", - "`visit_id` INT NOT NULL AUTO_INCREMENT,", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`visit_id`), INDEX (`ip`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create aliases table"); - } - - // Create action log table - query = ["CREATE TABLE IF NOT EXISTS `actionlog` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`action` VARCHAR(255) NOT NULL,", - "`args` TEXT NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`ip`, `time`), INDEX (`action`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create actionlog table"); - } - - // Create stats table - query = ["CREATE TABLE IF NOT EXISTS `stats` (", - "`time` BIGINT NOT NULL,", - "`usercount` INT NOT NULL,", - "`chancount` INT NOT NULL,", - "`mem` INT NOT NULL,", - "PRIMARY KEY (`time`))", - "ENGINE = MyISAM;"].join(""); - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create stats table"); - } -} - -/* REGION Global Bans */ - -function checkGlobalBan(ip) { - const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/; - var s16 = ip.replace(re, "$1.$2"); - var s24 = ip.replace(re, "$1.$2.$3"); - return (ip in global_bans || - s16 in global_bans || - s24 in global_bans); -} - -function refreshGlobalBans() { - var db = getConnection(); - if(!db) { - return; - } - - var query = "SELECT * FROM `global_bans` WHERE 1"; - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load global bans"); - } - else { - var rows = results.fetchAllSync(); - global_bans = {}; - for(var i = 0; i < rows.length; i++) { - global_bans[rows[i].ip] = rows[i].note; - } - } - return global_bans; -} - -function globalBanIP(ip, reason) { - var db = getConnection(); - if(!db) { - return; - } - - var query = createQuery( - "INSERT INTO `global_bans` VALUES (?, ?)", - [ip, reason] - ); - return db.querySync(query); -} - -function globalUnbanIP(ip) { - var db = getConnection(); - if(!db) { - return; - } - - var query = createQuery( - "DELETE FROM `global_bans` WHERE ip=?", - [ip] - ); - - return db.querySync(query); -} - -/* REGION Channel Registration/Loading */ - -function registerChannel(name, owner) { - if(!name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - // Library table - var query = ["CREATE TABLE `?` (", - "`id` VARCHAR(255) NOT NULL,", - "`title` VARCHAR(255) NOT NULL,", - "`seconds` INT NOT NULL,", - "`type` VARCHAR(2) NOT NULL,", - "PRIMARY KEY (`id`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_library"]); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_library"); - return false; - } - - // Rank table - query = ["CREATE TABLE `?` (", - "`name` VARCHAR(32) NOT NULL,", - "`rank` INT NOT NULL,", - "UNIQUE (`name`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_ranks"]); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_ranks"); - return false; - } - - // Ban table - query = ["CREATE TABLE `?` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(32) NOT NULL,", - "`banner` VARCHAR(32) NOT NULL,", - "PRIMARY KEY (`ip`))", - "ENGINE = MyISAM;"].join(""); - query = createQuery(query, ["chan_" + name + "_bans"]); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to create table: chan_"+name+"_bans"); - return false; - } - - // Insert into channel table - query = createQuery( - "INSERT INTO `channels` VALUES (NULL, ?, ?)", - [name, owner] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert into channel table: " + name); - return false; - } - - return true; -} - -function loadChannel(chan) { - if(!chan.name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT * FROM `channels` WHERE name=?", - [chan.name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query channel table"); - } - else { - var rows = results.fetchAllSync(); - if(rows.length == 0) { - // Unregistered - Logger.syslog.log("Channel " + chan.name + " is unregistered"); - return; - } - // Database is case insensitive - else if(rows[0].name != chan.name) { - chan.name = rows[0].name; - } - chan.registered = true; - } - - // Load channel library - query = createQuery( - "SELECT * FROM `?`", - ["chan_" + chan.name + "_library"] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load channel library: " + chan.name); - } - else { - var rows = results.fetchAllSync(); - for(var i = 0; i < rows.length; i++) { - var r = rows[i]; - var m = new Media(r.id, r.title, r.seconds, r.type); - chan.library[r.id] = m; - } - } - - // Load channel bans - query = createQuery( - "SELECT * FROM `?`", - ["chan_" + chan.name + "_bans"] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to load channel bans: " + chan.name); - } - else { - var rows = results.fetchAllSync(); - for(var i = 0; i < rows.length; i++) { - var r = rows[i]; - if(r.ip == "*") { - chan.namebans[r.name] = r.banner; - } - else { - chan.ipbans[r.ip] = [r.name, r.banner]; - } - } - } - - chan.logger.log("*** Loaded channel from database"); - Logger.syslog.log("Loaded channel " + chan.name + " from database"); -} - -function deleteChannel(name) { - if(!name.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - - var db = getConnection(); - if(!db) { - return false; - } - - var query = "DROP TABLE `chan_?_bans`, `chan_?_ranks`, `chan_?_library`" - .replace(/\?/g, name); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete channel tables for " + name); - return false; - } - - query = createQuery( - "DELETE FROM `channels` WHERE name=?", - [name] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete row from channel table: " + name); - return false; - } - - return true; -} - -/* REGION Channel data */ - -function getChannelRank(chan, name) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return 0; - } - - var query; - if(typeof name == "object") { - var n = "(?"; - for(var i = 1; i < name.length; i++) { - n += ",?"; - } - n += ")" - name.unshift("chan_" + chan + "_ranks"); - query = createQuery( - "SELECT * FROM `?` WHERE name IN " + n, - name - ); - } - else { - query = createQuery( - "SELECT * FROM `?` WHERE name=?", - ["chan_"+chan+"_ranks", name] - ); - } - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to lookup chan_"+chan+"_ranks"); - return 0; - } - - var rows = results.fetchAllSync(); - if(typeof name == "object") { - var ranks = []; - for(var i = 0; i < rows.length; i++) { - ranks.push(rows[i].rank); - } - while(ranks.length < rows.length) { - ranks.push(0); - } - return ranks; - } - if(rows.length == 0) { - return 0; - } - - return rows[0].rank; -} - -function setChannelRank(chan, name, rank) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`name`, `rank`) ", - "VALUES ", - "(?, ?) ", - "ON DUPLICATE KEY UPDATE ", - "`rank`=?"].join(""), - ["chan_"+chan+"_ranks", name, rank, rank] - ); - - return db.querySync(query); -} - -function listChannelRanks(chan) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return []; - } - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT * FROM `?` WHERE 1", - ["chan_"+chan+"_ranks"] - ); - - var results = db.querySync(query); - if(!results) { - return []; - } - - return results.fetchAllSync(); -} - -function addToLibrary(chan, media) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`id`, `title`, `seconds`, `type`) ", - "VALUES ", - "(?, ?, ?, ?)"].join(""), - ["chan_"+chan+"_library", media.id, media.title, media.seconds, media.type] - ); - - return db.querySync(query); -} - -function removeFromLibrary(chan, id) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE id=?", - ["chan_"+chan+"_library", id] - ); - - return db.querySync(query); -} - -function channelBan(chan, ip, name, banby) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["INSERT INTO `?` ", - "(`ip`, `name`, `banner`) ", - "VALUES ", - "(?, ?, ?)"].join(""), - ["chan_"+chan+"_bans", ip, name, banby] - ); - - return db.querySync(query); -} - -function channelUnbanIP(chan, ip) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE `ip`=?", - ["chan_"+chan+"_bans", ip] - ); - - return db.querySync(query); -} - -function channelUnbanName(chan, name) { - if(!chan.match(/^[a-zA-Z0-9-_]+$/)) { - return false; - } - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM `?` WHERE `ip`='*' AND `name`=?", - ["chan_"+chan+"_bans", name] - ); - - return db.querySync(query); -} - -/* REGION Users */ - -function getProfile(name) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT profile_image,profile_text FROM registrations WHERE uname=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve user profile"); - throw "Database failure. Contact an administrator."; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "User not found"; - } - - return { - profile_image: rows[0].profile_image, - profile_text: rows[0].profile_text - }; -} - -function setProfile(name, data) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - ["UPDATE `registrations` SET ", - "`profile_image`=?,", - "`profile_text`=? ", - "WHERE uname=?"].join(""), - [data.image, data.text, name] - ); - - return db.querySync(query); -} - -function setUserEmail(name, email) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "UPDATE `registrations` SET `email`=? WHERE `uname`=?", - [email, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to set user email"); - return false; - } - return true; -} - -function genSalt() { - var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - + "0123456789!@#$%^&*_+=~"; - var salt = []; - for(var i = 0; i < 32; i++) { - salt.push(chars[parseInt(Math.random()*chars.length)]); - } - return salt.join(''); -} - -function generatePasswordReset(ip, name, email) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT `email` FROM `registrations` WHERE `uname`=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve user email"); - return false; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "Provided username does not exist"; - } - if(rows[0].email != email) { - throw "Provided email does not match user's email"; - } - - // Validation complete, now time to reset it - var hash = hashlib.sha256(genSalt() + name); - var exp = Date.now() + 24*60*60*1000; - query = createQuery( - ["INSERT INTO `password_reset` (", - "`ip`, `name`, `hash`, `email`, `expire`", - ") VALUES (", - "?, ?, ?, ?, ?", - ") ON DUPLICATE KEY UPDATE `hash`=?,`expire`=?"].join(""), - [ip, name, hash, email, exp, hash, exp] - ); - - results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert password reset"); - return false; - } - - return hash; -} - -function recoverPassword(hash) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "SELECT * FROM password_reset WHERE hash=?", - [hash] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve from password_reset"); - throw "Database error. Contact an administrator"; - } - - var rows = results.fetchAllSync(); - if(rows.length == 0) { - throw "Invalid password reset link"; - } - - db.querySync(createQuery( - "DELETE FROM password_reset WHERE hash=?", - [hash] - )); - - if(Date.now() > rows[0].expire) { - throw "Link expired. Password resets are valid for 24 hours"; - } - - var pw; - if(!(pw = resetPassword(rows[0].name))) { - throw "Operation failed. Contact an administrator."; - } - - return [rows[0].name, pw]; -} - -function resetPassword(name) { - var db = getConnection(); - if(!db) { - return false; - } - - var pw = ""; - for(var i = 0; i < 10; i++) { - pw += "abcdefghijklmnopqrstuvwxyz"[parseInt(Math.random() * 25)]; - } - var hash = bcrypt.hashSync(pw, 10); - var query = createQuery( - "UPDATE `registrations` SET `pw`=? WHERE `uname`=?", - [hash, name] - ); - - var results = db.querySync(query); - if(!results) { - return false; - } - - return pw; -} - -/* REGION User Playlists */ -function getUserPlaylists(user) { - var db = getConnection(); - if(!db) { - []; - } - - var query = createQuery( - "SELECT name,count,time FROM user_playlists WHERE user=?", - [user] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query user playlists"); - return []; - } - - return results.fetchAllSync(); -} - -function loadUserPlaylist(user, name) { - var db = getConnection(); - if(!db) { - []; - } - - var query = createQuery( - "SELECT contents FROM user_playlists WHERE user=? AND name=?", - [user, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to query playlists"); - return []; - } - - var row = results.fetchAllSync()[0]; - var pl; - try { - pl = JSON.parse(row.contents); - } - catch(e) { - Logger.errlog.log("! Failed to load playlist "+user+"."+name); - return []; - } - - return pl; -} - -function saveUserPlaylist(pl, user, name) { - var db = getConnection(); - if(!db) { - return false; - } - - // Strip out unnecessary data - var pl2 = []; - var time = 0; - for(var i = 0; i < pl.length; i++) { - var e = { - id: pl[i].media.id, - title: pl[i].media.title, - seconds: pl[i].media.seconds, - type: pl[i].media.type - }; - time += pl[i].media.seconds; - pl2.push(e); - } - var count = pl2.length; - var plstr = JSON.stringify(pl2); - - var query = createQuery( - "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?)" + - "ON DUPLICATE KEY UPDATE contents=?,count=?,time=?", - [user, name, plstr, count, time, plstr, count, time] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to insert into playlists"); - return false; - } - - return true; -} - -function deleteUserPlaylist(user, name) { - var db = getConnection(); - if(!db) { - return false; - } - - var query = createQuery( - "DELETE FROM user_playlists WHERE user=? AND name=?", - [user, name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to delete from user_playlists"); - } - return results; -} - -function listUserChannels(user) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT * FROM channels WHERE owner=? ORDER BY id ASC", - [user] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to list user channels"); - return []; - } - - return results.fetchAllSync(); -} - -/* User Aliases */ - -function recordVisit(ip, name) { - var db = getConnection(); - if(!db) { - return false; - } - - var time = Date.now(); - db.querySync(createQuery( - "DELETE FROM aliases WHERE ip=? AND name=?", - [ip, name] - )); - var query = createQuery( - "INSERT INTO aliases VALUES (NULL, ?, ?, ?)", - [ip, name, time] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to record visit"); - } - - // Keep most recent 5 records per IP - results = db.querySync(createQuery( - ["DELETE FROM aliases WHERE ip=? AND visit_id NOT IN (", - "SELECT visit_id FROM (", - "SELECT visit_id,time FROM aliases WHERE ip=? ORDER BY time DESC LIMIT 5", - ") foo", - ");"].join(""), - [ip, ip] - )); - - return results; -} - -function getAliases(ip) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT name FROM aliases WHERE ip=?", - [ip] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve aliases"); - return []; - } - - var names = []; - results.fetchAllSync().forEach(function(row) { - names.push(row.name); - }); - - return names; -} - -function ipForName(name) { - var db = getConnection(); - if(!db) { - return []; - } - - var query = createQuery( - "SELECT ip FROM aliases WHERE name=?", - [name] - ); - - var results = db.querySync(query); - if(!results) { - Logger.errlog.log("! Failed to retrieve IP for name"); - return []; - } - - var ips = []; - results.fetchAllSync().forEach(function(row) { - ips.push(row.ip); - }); - - return ips; -} - -exports.setup = setup; -exports.getConnection = getConnection; -exports.createQuery = createQuery; -exports.init = init; -exports.checkGlobalBan = checkGlobalBan; -exports.refreshGlobalBans = refreshGlobalBans; -exports.globalBanIP = globalBanIP; -exports.globalUnbanIP = globalUnbanIP; -exports.registerChannel = registerChannel; -exports.loadChannel = loadChannel; -exports.deleteChannel = deleteChannel; -exports.getChannelRank = getChannelRank; -exports.setChannelRank = setChannelRank; -exports.listChannelRanks = listChannelRanks; -exports.addToLibrary = addToLibrary; -exports.removeFromLibrary = removeFromLibrary; -exports.channelBan = channelBan; -exports.channelUnbanIP = channelUnbanIP; -exports.channelUnbanName = channelUnbanName; -exports.setProfile = setProfile; -exports.getProfile = getProfile; -exports.setUserEmail = setUserEmail; -exports.generatePasswordReset = generatePasswordReset; -exports.recoverPassword = recoverPassword; -exports.resetPassword = resetPassword; -exports.getUserPlaylists = getUserPlaylists; -exports.loadUserPlaylist = loadUserPlaylist; -exports.saveUserPlaylist = saveUserPlaylist; -exports.deleteUserPlaylist = deleteUserPlaylist; -exports.listUserChannels = listUserChannels; -exports.recordVisit = recordVisit; -exports.getAliases = getAliases; -exports.ipForName = ipForName; From 9f52ca7eec1ae191e6b8f0b74eeeea250ce0bd94 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 20:22:34 -0500 Subject: [PATCH 39/60] Forgot to add to commit --- database.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/database.js b/database.js index 2104dc1e..ca0f967f 100644 --- a/database.js +++ b/database.js @@ -2,14 +2,7 @@ var mysql = require("mysql"); var hashlib = require("node_hash"); var bcrypt = require("bcrypt"); var $util = require("./utilities"); - -var Logger = { - errlog: { - log: function () { - console.log(arguments[0]); - } - } -}; +var Logger = require("./logger"); var Database = function (cfg) { this.cfg = cfg; From 73dbc4ffab39838f6a072825e48a5b3cac4581d2 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 21:00:50 -0500 Subject: [PATCH 40/60] Minor fix for API --- api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api.js b/api.js index 58735f83..c955af31 100644 --- a/api.js +++ b/api.js @@ -11,6 +11,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var Logger = require("./logger"); var fs = require("fs"); +var $util = require("./utilities"); module.exports = function (Server) { var ActionLog = Server.actionlog; From 42e89dc5571d6bbf7c81c15b17ad4c743443826c Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 17 Aug 2013 22:41:54 -0500 Subject: [PATCH 41/60] Hack around cross-domain POSTing --- www/assets/js/account.js | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/www/assets/js/account.js b/www/assets/js/account.js index 49bc7d4a..5ad2d614 100644 --- a/www/assets/js/account.js +++ b/www/assets/js/account.js @@ -9,6 +9,31 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* + So, it turns out that $.post causes Firefox to use a GET request + on cross-site requests. What the hell? I'd understand if they just + made it error instead, but why give me chicken tenders if I ordered a + cheeseburger and act like everything's peachy? +*/ +function postJSON(url, data, callback) { + $.ajax(url, { + method: "POST", + crossDomain: true, + data: data, + success: function (data) { + try { + data = data.substring(data.indexOf("{")); + data = data.substring(0, data.lastIndexOf("}") + 1); + data = JSON.parse(data); + callback(data); + } catch(e) { + return; + } + }, + dataType: "text" + }); +} + var uname = readCookie("cytube_uname") || ""; var session = readCookie("cytube_session") || ""; var loggedin = false; @@ -18,7 +43,7 @@ if(uname && session) { name: uname, session: session }; - $.post(WEB_URL + "/api/login?callback=?", data, function (data) { + postJSON(WEB_URL + "/api/login?callback=?", data, function (data) { if(data.success) onLogin(); }, "jsonp"); @@ -141,7 +166,7 @@ $("#registerbtn").click(function() { pw: pw }; - $.post(WEB_URL + "/api/register?callback=?", data, function (data) { + postJSON(WEB_URL + "/api/register?callback=?", data, function (data) { if(data.success) { uname = name; session = data.session; @@ -176,7 +201,8 @@ $("#loginbtn").click(function() { name: uname, pw: $("#loginpw").val() }; - $.post(WEB_URL+"/api/login?callback=?", data, function(data) { + + postJSON(WEB_URL+"/api/login?callback=?", data, function(data) { if(data.success) { session = data.session; onLogin(); @@ -238,7 +264,7 @@ $("#cpwbtn").click(function() { oldpw: oldpw, newpw: newpw }; - $.post(WEB_URL + "/api/account/passwordchange?callback=?", data, + postJSON(WEB_URL + "/api/account/passwordchange?callback=?", data, function (data) { if(data.success) { $("
").addClass("alert alert-success") @@ -291,7 +317,7 @@ $("#cebtn").click(function() { pw: pw, email: email }; - $.post(WEB_URL + "/api/account/email?callback=?", data, + postJSON(WEB_URL + "/api/account/email?callback=?", data, function (data) { if(data.success) { $("
").addClass("alert alert-success") @@ -321,7 +347,7 @@ $("#rpbtn").click(function() { name: name, email: email }; - $.post(WEB_URL + "/api/account/passwordreset?callback=?", data, + postJSON(WEB_URL + "/api/account/passwordreset?callback=?", data, function (data) { $("#rpbtn").text("Send Reset"); if(data.success) { @@ -350,7 +376,7 @@ $("#profilesave").click(function() { profile_text: text }; - $.post(WEB_URL+"/api/account/profile?callback=?", data, + postJSON(WEB_URL+"/api/account/profile?callback=?", data, function (data) { if(data.success) { $("
").addClass("alert alert-success") From 2f4621dab9d0415e6d892cd54a7ec9bb76e97d7a Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 12:21:34 -0500 Subject: [PATCH 42/60] Start fixing things --- acp.js | 2 + api.js | 3 +- channel.js | 10 +- database.js | 28 +-- user.js | 542 ++++++++++++++++++++++++++++------------------------ 5 files changed, 312 insertions(+), 273 deletions(-) diff --git a/acp.js b/acp.js index d0ade27b..0a332608 100644 --- a/acp.js +++ b/acp.js @@ -9,6 +9,8 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +var Logger = require("./logger"); + module.exports = function (Server) { var db = Server.db; var ActionLog = Server.actionlog; diff --git a/api.js b/api.js index c955af31..8c8f0fdf 100644 --- a/api.js +++ b/api.js @@ -141,6 +141,7 @@ module.exports = function (Server) { /* login */ app.post("/api/login", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var pw = req.body.pw; var session = req.body.session; @@ -567,8 +568,6 @@ module.exports = function (Server) { res.jsonp(actions); }); - var actions = ActionLog.readLog(types); - res.jsonp(actions); }); }); diff --git a/channel.js b/channel.js index 7bf92123..7a4bbc94 100644 --- a/channel.js +++ b/channel.js @@ -25,6 +25,7 @@ var Channel = function(name, Server) { var self = this; Logger.syslog.log("Opening channel " + name); self.initialized = false; + self.dbloaded = false; self.server = Server; self.name = name; @@ -117,6 +118,7 @@ var Channel = function(name, Server) { } Server.db.loadChannelData(self, function () { + self.dbloaded = true; if(self.registered) { self.loadDump(); } @@ -658,13 +660,13 @@ Channel.prototype.search = function(query, callback) { res = []; } - results.sort(function(a, b) { + res.sort(function(a, b) { var x = a.title.toLowerCase(); var y = b.title.toLowerCase(); return (x == y) ? 0 : (x < y ? -1 : 1); }); - callback(results); + callback(res); }); } @@ -700,7 +702,7 @@ Channel.prototype.userJoin = function(user) { // If the channel is empty and isn't registered, the first person // gets ownership of the channel (temporarily) - if(this.users.length == 0 && !this.registered) { + if(this.dbloaded && this.users.length == 0 && !this.registered) { user.rank = (user.rank < Rank.Owner) ? 10 : user.rank; user.socket.emit("channelNotRegistered"); } @@ -948,7 +950,7 @@ Channel.prototype.broadcastNewUser = function(user) { self.ip_alias[user.ip] = aliases; aliases.forEach(function (alias) { - chan.name_alias[alias] = aliases; + self.name_alias[alias] = aliases; }); self.login_hist.unshift({ diff --git a/database.js b/database.js index ca0f967f..61afafef 100644 --- a/database.js +++ b/database.js @@ -10,7 +10,8 @@ var Database = function (cfg) { host: cfg["mysql-server"], user: cfg["mysql-user"], password: cfg["mysql-pw"], - database: cfg["mysql-db"] + database: cfg["mysql-db"], + multipleStatements: true }); // Test the connection @@ -43,6 +44,7 @@ Database.prototype.query = function (query, sub, callback) { function cback(err, res) { if(err) { if(self.cfg["debug"]) { + console.log(query); console.log(err); } callback("Database failure", null); @@ -52,9 +54,9 @@ Database.prototype.query = function (query, sub, callback) { conn.end(); } - if(sub) + if(sub) { conn.query(query, sub, cback); - else { + } else { conn.query(query, cback); } } @@ -296,11 +298,11 @@ Database.prototype.searchChannel = function (field, value, callback) { var query = "SELECT * FROM channels WHERE "; if(field === "owner") - query += "owner LIKE %?%"; + query += "owner LIKE ?"; else if(field === "name") - query += "name LIKE %?%"; + query += "name LIKE ?"; - self.query(query, [value], callback); + self.query(query, ["%" + value + "%"], callback); }; Database.prototype.channelExists = function (name, callback) { @@ -611,9 +613,9 @@ Database.prototype.searchLibrary = function (channame, term, callback) { } var query = "SELECT id, title, seconds, type FROM " + - "`chan_" + channame + "_library` WHERE title LIKE %?%"; + "`chan_" + channame + "_library` WHERE title LIKE ?"; - self.query(query, [term], callback); + self.query(query, ["%" + term + "%"], callback); }; Database.prototype.addChannelBan = function (channame, ip, name, banBy, @@ -889,7 +891,7 @@ Database.prototype.getGlobalRank = function (name, callback) { return; var query = "SELECT global_rank FROM registrations WHERE uname=?"; - self.query(query, function (err, res) { + self.query(query, [name], function (err, res) { if(err) { callback(err, null); return; @@ -917,9 +919,9 @@ Database.prototype.searchUser = function (name, callback) { // the user's password hash var query = "SELECT id, uname, global_rank, profile_image, " + "profile_text, email FROM registrations WHERE " + - "uname LIKE %?%"; + "uname LIKE ?"; - self.query(query, [name], callback); + self.query(query, ["%" + name + "%"], callback); }; /* rank */ @@ -1221,7 +1223,7 @@ Database.prototype.listAliases = function (ip, callback) { if(!err) { names = []; res.forEach(function (row) { - names.append(row.name); + names.push(row.name); }); } @@ -1338,7 +1340,7 @@ Database.prototype.listActions = function (types, callback) { list.push("?"); var actionlist = "(" + list.join(",") + ")"; - var query = "SELECT * FROM actionlog WHERE action IN " + actiontypes; + var query = "SELECT * FROM actionlog WHERE action IN " + actionlist; self.query(query, types, callback); }; diff --git a/user.js b/user.js index 1ef9130a..51422abd 100644 --- a/user.js +++ b/user.js @@ -116,188 +116,188 @@ User.prototype.autoAFK = function () { } User.prototype.initCallbacks = function() { - this.socket.on("disconnect", function() { - this.awaytimer && clearTimeout(this.awaytimer); - if(this.channel != null) - this.channel.userLeave(this); - }.bind(this)); + var self = this; + self.socket.on("disconnect", function() { + self.awaytimer && clearTimeout(self.awaytimer); + if(self.channel != null) + self.channel.userLeave(self); + }); - this.socket.on("joinChannel", function(data) { - if(this.channel != null) + self.socket.on("joinChannel", function(data) { + if(self.channel != null) return; if(typeof data.name != "string") return; if(!data.name.match(/^[\w-_]{1,30}$/)) { - this.socket.emit("errorMsg", { + self.socket.emit("errorMsg", { msg: "Invalid channel name. Channel names may consist of"+ " 1-30 characters in the set a-z, A-Z, 0-9, -, and _" }); - this.socket.emit("kick", { + self.socket.emit("kick", { reason: "Bad channel name" }); return; } data.name = data.name.toLowerCase(); - this.channel = this.server.getChannel(data.name); - if(this.loggedIn) { - var chanrank = this.channel.getRank(this.name); - if(chanrank > this.rank) { - this.rank = chanrank; - } + self.channel = self.server.getChannel(data.name); + if(self.loggedIn) { + self.channel.getRank(self.name, function (err, rank) { + if(!err && rank > self.rank) + self.rank = rank; + }); } - this.channel.userJoin(this); - }.bind(this)); + self.channel.userJoin(self); + }); - this.socket.on("login", function(data) { + self.socket.on("login", function(data) { var name = data.name || ""; var pw = data.pw || ""; var session = data.session || ""; if(pw.length > 100) pw = pw.substring(0, 100); - if(this.name == "") - this.login(name, pw, session); - }.bind(this)); + if(self.name == "") + self.login(name, pw, session); + }); - this.socket.on("assignLeader", function(data) { - if(this.channel != null) { - this.channel.tryChangeLeader(this, data); + self.socket.on("assignLeader", function(data) { + if(self.channel != null) { + self.channel.tryChangeLeader(self, data); } - }.bind(this)); + }); - this.socket.on("promote", function(data) { - if(this.channel != null) { - this.channel.tryPromoteUser(this, data); + self.socket.on("promote", function(data) { + if(self.channel != null) { + self.channel.tryPromoteUser(self, data); } - }.bind(this)); + }); - this.socket.on("demote", function(data) { - if(this.channel != null) { - this.channel.tryDemoteUser(this, data); + self.socket.on("demote", function(data) { + if(self.channel != null) { + self.channel.tryDemoteUser(self, data); } - }.bind(this)); + }); - this.socket.on("setChannelRank", function(data) { - if(this.channel != null) { - this.channel.trySetRank(this, data); + self.socket.on("setChannelRank", function(data) { + if(self.channel != null) { + self.channel.trySetRank(self, data); } - }.bind(this)); + }); - this.socket.on("banName", function(data) { - if(this.channel != null) { - this.channel.banName(this, data.name || ""); + self.socket.on("banName", function(data) { + if(self.channel != null) { + self.channel.banName(self, data.name || ""); } - }.bind(this)); + }); - this.socket.on("banIP", function(data) { - if(this.channel != null) { - this.channel.tryIPBan(this, data); + self.socket.on("banIP", function(data) { + if(self.channel != null) { + self.channel.tryIPBan(self, data); } - }.bind(this)); + }); - this.socket.on("unban", function(data) { - if(this.channel != null) { - this.channel.tryUnban(this, data); + self.socket.on("unban", function(data) { + if(self.channel != null) { + self.channel.tryUnban(self, data); } - }.bind(this)); + }); - this.socket.on("chatMsg", function(data) { - if(this.channel != null) { + self.socket.on("chatMsg", function(data) { + if(self.channel != null) { if(data.msg.indexOf("/afk") != 0) { - this.setAFK(false); - this.autoAFK(); + self.setAFK(false); + self.autoAFK(); } - this.channel.tryChat(this, data); + self.channel.tryChat(self, data); } - }.bind(this)); + }); - this.socket.on("newPoll", function(data) { - if(this.channel != null) { - this.channel.tryOpenPoll(this, data); + self.socket.on("newPoll", function(data) { + if(self.channel != null) { + self.channel.tryOpenPoll(self, data); } - }.bind(this)); + }); - this.socket.on("playerReady", function() { - if(this.channel != null) { - this.channel.sendMediaUpdate(this); + self.socket.on("playerReady", function() { + if(self.channel != null) { + self.channel.sendMediaUpdate(self); } - }.bind(this)); + }); - this.socket.on("requestPlaylist", function() { - if(this.channel != null) { - this.channel.sendPlaylist(this); + self.socket.on("requestPlaylist", function() { + if(self.channel != null) { + self.channel.sendPlaylist(self); } - }.bind(this)); + }); - this.socket.on("queue", function(data) { - if(this.channel != null) { - this.channel.tryQueue(this, data); + self.socket.on("queue", function(data) { + if(self.channel != null) { + self.channel.tryQueue(self, data); } - }.bind(this)); + }); - this.socket.on("setTemp", function(data) { - if(this.channel != null) { - this.channel.trySetTemp(this, data); + self.socket.on("setTemp", function(data) { + if(self.channel != null) { + self.channel.trySetTemp(self, data); } - }.bind(this)); + }); - this.socket.on("delete", function(data) { - if(this.channel != null) { - this.channel.tryDequeue(this, data); + self.socket.on("delete", function(data) { + if(self.channel != null) { + self.channel.tryDequeue(self, data); } - }.bind(this)); + }); - this.socket.on("uncache", function(data) { - if(this.channel != null) { - this.channel.tryUncache(this, data); + self.socket.on("uncache", function(data) { + if(self.channel != null) { + self.channel.tryUncache(self, data); } - }.bind(this)); + }); - this.socket.on("moveMedia", function(data) { - if(this.channel != null) { - this.channel.tryMove(this, data); + self.socket.on("moveMedia", function(data) { + if(self.channel != null) { + self.channel.tryMove(self, data); } - }.bind(this)); + }); - this.socket.on("jumpTo", function(data) { - if(this.channel != null) { - this.channel.tryJumpTo(this, data); + self.socket.on("jumpTo", function(data) { + if(self.channel != null) { + self.channel.tryJumpTo(self, data); } - }.bind(this)); + }); - this.socket.on("playNext", function() { - if(this.channel != null) { - this.channel.tryPlayNext(this); + self.socket.on("playNext", function() { + if(self.channel != null) { + self.channel.tryPlayNext(self); } - }.bind(this)); + }); - this.socket.on("clearPlaylist", function() { - if(this.channel != null) { - this.channel.tryClearqueue(this); + self.socket.on("clearPlaylist", function() { + if(self.channel != null) { + self.channel.tryClearqueue(self); } - }.bind(this)); + }); - this.socket.on("shufflePlaylist", function() { - if(this.channel != null) { - this.channel.tryShufflequeue(this); + self.socket.on("shufflePlaylist", function() { + if(self.channel != null) { + self.channel.tryShufflequeue(self); } - }.bind(this)); + }); - this.socket.on("togglePlaylistLock", function() { - if(this.channel != null) { - this.channel.tryToggleLock(this); + self.socket.on("togglePlaylistLock", function() { + if(self.channel != null) { + self.channel.tryToggleLock(self); } - }.bind(this)); + }); - this.socket.on("mediaUpdate", function(data) { - if(this.channel != null) { - this.channel.tryUpdate(this, data); + self.socket.on("mediaUpdate", function(data) { + if(self.channel != null) { + self.channel.tryUpdate(self, data); } - }.bind(this)); + }); - this.socket.on("searchMedia", function(data) { - var self = this; - if(this.channel != null) { + self.socket.on("searchMedia", function(data) { + if(self.channel != null) { if(data.source == "yt") { var searchfn = self.server.infogetter.Getters["ytSearch"]; searchfn(data.query.split(" "), function (e, vids) { @@ -315,148 +315,151 @@ User.prototype.initCallbacks = function() { }); } } - }.bind(this)); + }); - this.socket.on("closePoll", function() { - if(this.channel != null) { - this.channel.tryClosePoll(this); + self.socket.on("closePoll", function() { + if(self.channel != null) { + self.channel.tryClosePoll(self); } - }.bind(this)); + }); - this.socket.on("vote", function(data) { - if(this.channel != null) { - this.channel.tryVote(this, data); + self.socket.on("vote", function(data) { + if(self.channel != null) { + self.channel.tryVote(self, data); } - }.bind(this)); + }); - this.socket.on("registerChannel", function(data) { - if(this.channel == null) { - this.socket.emit("channelRegistration", { + self.socket.on("registerChannel", function(data) { + if(self.channel == null) { + self.socket.emit("channelRegistration", { success: false, error: "You're not in any channel!" }); } else { - this.channel.tryRegister(this); + self.channel.tryRegister(self); } - }.bind(this)); + }); - this.socket.on("unregisterChannel", function() { - if(this.channel == null) { + self.socket.on("unregisterChannel", function() { + if(self.channel == null) { return; } - this.channel.unregister(this); - }.bind(this)); + self.channel.unregister(self); + }); - this.socket.on("setOptions", function(data) { - if(this.channel != null) { - this.channel.tryUpdateOptions(this, data); + self.socket.on("setOptions", function(data) { + if(self.channel != null) { + self.channel.tryUpdateOptions(self, data); } - }.bind(this)); + }); - this.socket.on("setPermissions", function(data) { - if(this.channel != null) { - this.channel.tryUpdatePermissions(this, data); + self.socket.on("setPermissions", function(data) { + if(self.channel != null) { + self.channel.tryUpdatePermissions(self, data); } - }.bind(this)); + }); - this.socket.on("setChannelCSS", function(data) { - if(this.channel != null) { - this.channel.trySetCSS(this, data); + self.socket.on("setChannelCSS", function(data) { + if(self.channel != null) { + self.channel.trySetCSS(self, data); } - }.bind(this)); + }); - this.socket.on("setChannelJS", function(data) { - if(this.channel != null) { - this.channel.trySetJS(this, data); + self.socket.on("setChannelJS", function(data) { + if(self.channel != null) { + self.channel.trySetJS(self, data); } - }.bind(this)); + }); - this.socket.on("updateFilter", function(data) { - if(this.channel != null) { - this.channel.tryUpdateFilter(this, data); + self.socket.on("updateFilter", function(data) { + if(self.channel != null) { + self.channel.tryUpdateFilter(self, data); } - }.bind(this)); + }); - this.socket.on("removeFilter", function(data) { - if(this.channel != null) { - this.channel.tryRemoveFilter(this, data); + self.socket.on("removeFilter", function(data) { + if(self.channel != null) { + self.channel.tryRemoveFilter(self, data); } - }.bind(this)); + }); - this.socket.on("moveFilter", function(data) { - if(this.channel != null) { - this.channel.tryMoveFilter(this, data); + self.socket.on("moveFilter", function(data) { + if(self.channel != null) { + self.channel.tryMoveFilter(self, data); } - }.bind(this)); + }); - this.socket.on("setMotd", function(data) { - if(this.channel != null) { - this.channel.tryUpdateMotd(this, data); + self.socket.on("setMotd", function(data) { + if(self.channel != null) { + self.channel.tryUpdateMotd(self, data); } - }.bind(this)); + }); - this.socket.on("requestLoginHistory", function() { - if(this.channel != null) { - this.channel.sendLoginHistory(this); + self.socket.on("requestLoginHistory", function() { + if(self.channel != null) { + self.channel.sendLoginHistory(self); } - }.bind(this)); + }); - this.socket.on("requestBanlist", function() { - if(this.channel != null) { - this.channel.sendBanlist(this); + self.socket.on("requestBanlist", function() { + if(self.channel != null) { + self.channel.sendBanlist(self); } - }.bind(this)); + }); - this.socket.on("requestChatFilters", function() { - if(this.channel != null) { - this.channel.sendChatFilters(this); + self.socket.on("requestChatFilters", function() { + if(self.channel != null) { + self.channel.sendChatFilters(self); } - }.bind(this)); + }); - this.socket.on("requestChannelRanks", function() { - if(this.channel != null) { - if(this.noflood("requestChannelRanks", 0.25)) + self.socket.on("requestChannelRanks", function() { + if(self.channel != null) { + if(self.noflood("requestChannelRanks", 0.25)) return; - this.channel.sendChannelRanks(this); + self.channel.sendChannelRanks(self); } - }.bind(this)); + }); - this.socket.on("voteskip", function(data) { - if(this.channel != null) { - this.channel.tryVoteskip(this); + self.socket.on("voteskip", function(data) { + if(self.channel != null) { + self.channel.tryVoteskip(self); } - }.bind(this)); + }); - this.socket.on("listPlaylists", function(data) { - if(this.name == "" || this.rank < 1) { - this.socket.emit("listPlaylists", { + self.socket.on("listPlaylists", function(data) { + if(self.name == "" || self.rank < 1) { + self.socket.emit("listPlaylists", { pllist: [], error: "You must be logged in to manage playlists" }); return; } - var list = this.server.db.getUserPlaylists(this.name); - for(var i = 0; i < list.length; i++) { - list[i].time = formatTime(list[i].time); - } - this.socket.emit("listPlaylists", { - pllist: list, + self.server.db.listUserPlaylists(self.name, function (err, list) { + if(err) + list = []; + for(var i = 0; i < list.length; i++) { + list[i].time = formatTime(list[i].time); + } + self.socket.emit("listPlaylists", { + pllist: list, + }); }); - }.bind(this)); + }); - this.socket.on("savePlaylist", function(data) { - if(this.rank < 1) { - this.socket.emit("savePlaylist", { + self.socket.on("savePlaylist", function(data) { + if(self.rank < 1) { + self.socket.emit("savePlaylist", { success: false, error: "You must be logged in to manage playlists" }); return; } - if(this.channel == null) { - this.socket.emit("savePlaylist", { + if(self.channel == null) { + self.socket.emit("savePlaylist", { success: false, error: "Not in a channel" }); @@ -467,65 +470,86 @@ User.prototype.initCallbacks = function() { return; } - var pl = this.channel.playlist.items.toArray(); - var result = this.server.db.saveUserPlaylist(pl, this.name, data.name); - this.socket.emit("savePlaylist", { - success: result, - error: result ? false : "Unknown" - }); - var list = this.server.db.getUserPlaylists(this.name); - for(var i = 0; i < list.length; i++) { - list[i].time = formatTime(list[i].time); - } - this.socket.emit("listPlaylists", { - pllist: list, - }); - }.bind(this)); + var pl = self.channel.playlist.items.toArray(); + self.server.db.saveUserPlaylist(pl, self.name, data.name, + function (err, res) { + if(err) { + console.log(typeof err); + self.socket.emit("savePlaylist", { + success: false, + error: err + }); + return; + } + + self.socket.emit("savePlaylist", { + success: true + }); - this.socket.on("queuePlaylist", function(data) { - if(this.channel != null) { - this.channel.tryQueuePlaylist(this, data); - } - }.bind(this)); + self.server.db.listUserPlaylists(self.name, + function (err, list) { + if(err) + list = []; + for(var i = 0; i < list.length; i++) { + list[i].time = formatTime(list[i].time); + } + self.socket.emit("listPlaylists", { + pllist: list, + }); + }); + }); + }); - this.socket.on("deletePlaylist", function(data) { + self.socket.on("queuePlaylist", function(data) { + if(self.channel != null) { + self.channel.tryQueuePlaylist(self, data); + } + }); + + self.socket.on("deletePlaylist", function(data) { if(typeof data.name != "string") { return; } - this.server.db.deleteUserPlaylist(this.name, data.name); - var list = this.server.db.getUserPlaylists(this.name); - for(var i = 0; i < list.length; i++) { - list[i].time = formatTime(list[i].time); - } - this.socket.emit("listPlaylists", { - pllist: list, + self.server.db.deleteUserPlaylist(self.name, data.name, + function () { + self.server.db.listUserPlaylists(self.name, + function (err, list) { + if(err) + list = []; + for(var i = 0; i < list.length; i++) { + list[i].time = formatTime(list[i].time); + } + self.socket.emit("listPlaylists", { + pllist: list, + }); + }); }); - }.bind(this)); + }); - this.socket.on("readChanLog", function () { - if(this.channel !== null) { - this.channel.tryReadLog(this); + self.socket.on("readChanLog", function () { + if(self.channel !== null) { + self.channel.tryReadLog(self); } - }.bind(this)); + }); - this.socket.on("acp-init", function() { - if(this.global_rank >= Rank.Siteadmin) - this.server.acp.init(this); - }.bind(this)); + self.socket.on("acp-init", function() { + if(self.global_rank >= Rank.Siteadmin) + self.server.acp.init(self); + }); - this.socket.on("borrow-rank", function(rank) { - if(this.global_rank < 255) + self.socket.on("borrow-rank", function(rank) { + if(self.global_rank < 255) return; - if(rank > this.global_rank) + if(rank > self.global_rank) return; - this.rank = rank; - this.socket.emit("rank", rank); - if(this.channel != null) - this.channel.broadcastUserUpdate(this); + self.rank = rank; + self.socket.emit("rank", rank); + if(self.channel != null) + self.channel.broadcastUserUpdate(self); - }.bind(this)); + }); } var lastguestlogin = {}; @@ -631,17 +655,27 @@ User.prototype.login = function(name, pw, session) { image: row.profile_image, text: row.profile_text }; - var chanrank = (self.channel != null) ? self.channel.getRank(name) - : Rank.Guest; - var rank = (chanrank > row.global_rank) ? chanrank - : row.global_rank; - self.rank = (self.rank > rank) ? self.rank : rank; self.global_rank = row.global_rank; - self.socket.emit("rank", self.rank); - self.name = name; - if(self.channel != null) { - self.channel.logger.log(self.ip + " logged in as " + name); - self.channel.broadcastNewUser(self); + var afterRankLookup = function () { + self.socket.emit("rank", self.rank); + self.name = name; + if(self.channel != null) { + self.channel.logger.log(self.ip + " logged in as " + + name); + self.channel.broadcastNewUser(self); + } + }; + if(self.channel !== null) { + self.channel.getRank(self.name, function (err, rank) { + if(!err && rank > self.global_rank) + self.rank = rank; + else + self.rank = self.global_rank + afterRankLookup(); + }); + } else { + self.rank = self.global_rank; + afterRankLookup(); } }); } From 4253d3c84be6433e844d128f0c3191ca798c9c2b Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 12:24:32 -0500 Subject: [PATCH 43/60] Fix the other POST api calls besides just /api/login --- api.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api.js b/api.js index 8c8f0fdf..2c92e8f0 100644 --- a/api.js +++ b/api.js @@ -182,6 +182,7 @@ module.exports = function (Server) { /* register an account */ app.post("/api/register", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var pw = req.body.pw; var ip = getIP(req); @@ -252,6 +253,7 @@ module.exports = function (Server) { /* password change */ app.post("/api/account/passwordchange", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var oldpw = req.body.oldpw; @@ -294,6 +296,7 @@ module.exports = function (Server) { /* password reset */ app.post("/api/account/passwordreset", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var email = req.body.email; var ip = getIP(req); @@ -408,6 +411,7 @@ module.exports = function (Server) { /* profile change */ app.post("/api/account/profile", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var session = req.body.session; var img = req.body.profile_image; @@ -454,6 +458,7 @@ module.exports = function (Server) { /* set email */ app.post("/api/account/email", function (req, res) { res.type("application/jsonp"); + res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var pw = req.body.pw; var email = req.body.email; From e4d0d21667aa7215d636583b33141ed8a398c46b Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 12:48:49 -0500 Subject: [PATCH 44/60] Don't display session expired --- database.js | 2 +- www/assets/js/callbacks.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database.js b/database.js index 61afafef..c72dd4e5 100644 --- a/database.js +++ b/database.js @@ -832,7 +832,7 @@ Database.prototype.userLoginSession = function (name, session, callback) { } if(res.length == 0) { - callback("Invalid session", null); + callback("Session expired", null); return; } diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 74bd8aab..a12994e6 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -572,7 +572,7 @@ Callbacks = { login: function(data) { if(!data.success) { - if(data.error != "Invalid session") { + if(data.error != "Session expired") { alert(data.error); } } From dfcdee7637c1a8858e719206e77e1bb0dd99b2b7 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 12:49:54 -0500 Subject: [PATCH 45/60] Fix user.js crash --- user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/user.js b/user.js index 51422abd..a634064a 100644 --- a/user.js +++ b/user.js @@ -13,6 +13,7 @@ var Rank = require("./rank.js"); var Channel = require("./channel.js").Channel; var formatTime = require("./media.js").formatTime; var Logger = require("./logger.js"); +var $util = require("./utilities"); // Represents a client connected via socket.io var User = function(socket, Server) { From 7254512e7d3884e02e8faed054204e460fceea65 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 13:05:12 -0500 Subject: [PATCH 46/60] Fix a couple bugs --- channel.js | 2 +- database.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/channel.js b/channel.js index 7a4bbc94..8433c0f4 100644 --- a/channel.js +++ b/channel.js @@ -612,7 +612,7 @@ Channel.prototype.tryIPBan = function(actor, name, range) { if(!self.registered) return; - self.server.db.addChannelBan(chan.name, ip, name, + self.server.db.addChannelBan(self.name, ip, name, actor.name, function (err, res) { self.users.forEach(function(u) { diff --git a/database.js b/database.js index c72dd4e5..ec14fae2 100644 --- a/database.js +++ b/database.js @@ -553,7 +553,7 @@ Database.prototype.addToLibrary = function (channame, media, callback) { return; } - var query = "INSERT INTO `chan_" + channame + "_ranks`" + + var query = "INSERT INTO `chan_" + channame + "_library` " + "(id, title, seconds, type) " + "VALUES (?, ?, ?, ?)"; var params = [ From 806cbb3336cd90cf4407343eef8e6d091fca37da Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 13:08:31 -0500 Subject: [PATCH 47/60] Fix channel rank change --- channel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/channel.js b/channel.js index 8433c0f4..02e387fb 100644 --- a/channel.js +++ b/channel.js @@ -1982,7 +1982,7 @@ Channel.prototype.trySetRank = function(user, data) { return; if(rrank >= user.rank) return; - self.server.db.setChannelRank(this.name, data.user, + self.server.db.setChannelRank(self.name, data.user, data.rank, function (err, res) { self.logger.log("*** " + user.name + " set " + From bfc420336ae38aefa69e70e2a4650c031940e4da Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 13:09:51 -0500 Subject: [PATCH 48/60] Fix global ban typo --- database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database.js b/database.js index ec14fae2..a6558058 100644 --- a/database.js +++ b/database.js @@ -265,7 +265,7 @@ Database.prototype.setGlobalIPBan = function (ip, reason, callback) { return; } - self.getGlobalIPBans(); + self.listGlobalIPBans(); callback(null, res); }); }; From aa13cc95ece136a84bb3794fb8839af812f3f5e9 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 13:16:46 -0500 Subject: [PATCH 49/60] Add a few checks --- channel.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/channel.js b/channel.js index 02e387fb..bd1b2924 100644 --- a/channel.js +++ b/channel.js @@ -119,6 +119,14 @@ var Channel = function(name, Server) { Server.db.loadChannelData(self, function () { self.dbloaded = true; + // If the channel is empty and isn't registered, the first person + // gets ownership of the channel (temporarily) + if(self.users.length == 1 && !self.registered) { + var user = self.users[0]; + user.rank = (user.rank < Rank.Owner) ? 10 : user.rank; + self.broadcastUserUpdate(user); + user.socket.emit("channelNotRegistered"); + } if(self.registered) { self.loadDump(); } @@ -449,6 +457,8 @@ Channel.prototype.getRank = function (name, callback) { } Channel.prototype.saveRank = function (user) { + if(!this.registered) + return; this.server.db.setChannelRank(this.name, user.name, user.rank); } @@ -854,7 +864,7 @@ Channel.prototype.sendRankStuff = function(user) { } Channel.prototype.sendChannelRanks = function(user) { - if(Rank.hasPermission(user, "acl")) { + if(Rank.hasPermission(user, "acl") && this.registered) { this.server.db.listChannelRanks(this.name, function (err, res) { if(err) { user.socket.emit("errorMsg", { @@ -1975,8 +1985,7 @@ Channel.prototype.trySetRank = function(user, data) { self.saveRank(receiver); } self.broadcastUserUpdate(receiver); - } - else { + } else if(self.registered) { self.getRank(data.user, function (err, rrank) { if(err) return; From c9b5254f24f55e7e3131a6d45b5965dfb3bf4f8d Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 13:35:57 -0500 Subject: [PATCH 50/60] Fix rank change for person in channel --- channel.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/channel.js b/channel.js index bd1b2924..0b476c65 100644 --- a/channel.js +++ b/channel.js @@ -456,10 +456,10 @@ Channel.prototype.getRank = function (name, callback) { }); } -Channel.prototype.saveRank = function (user) { +Channel.prototype.saveRank = function (user, callback) { if(!this.registered) return; - this.server.db.setChannelRank(this.name, user.name, user.rank); + this.server.db.setChannelRank(this.name, user.name, user.rank, callback); } Channel.prototype.getIPRank = function (ip, callback) { @@ -1982,7 +1982,11 @@ Channel.prototype.trySetRank = function(user, data) { return; receiver.rank = data.rank; if(receiver.loggedIn) { - self.saveRank(receiver); + self.saveRank(receiver, function (err, res) { + self.logger.log("*** " + user.name + " set " + + data.user + "'s rank to " + data.rank); + self.sendAllWithPermission("acl", "setChannelRank", data); + }); } self.broadcastUserUpdate(receiver); } else if(self.registered) { From 08a46f5e006f57c38a0fe97b98400941d359e95e Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 14:21:42 -0500 Subject: [PATCH 51/60] Fixes --- channel.js | 20 ++++++-------------- database.js | 7 +++++++ user.js | 11 +++++------ www/assets/js/account.js | 15 ++++++++------- www/login.html | 28 ++++++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 29 deletions(-) diff --git a/channel.js b/channel.js index 0b476c65..1d53a34b 100644 --- a/channel.js +++ b/channel.js @@ -119,14 +119,6 @@ var Channel = function(name, Server) { Server.db.loadChannelData(self, function () { self.dbloaded = true; - // If the channel is empty and isn't registered, the first person - // gets ownership of the channel (temporarily) - if(self.users.length == 1 && !self.registered) { - var user = self.users[0]; - user.rank = (user.rank < Rank.Owner) ? 10 : user.rank; - self.broadcastUserUpdate(user); - user.socket.emit("channelNotRegistered"); - } if(self.registered) { self.loadDump(); } @@ -710,12 +702,6 @@ Channel.prototype.userJoin = function(user) { } } - // If the channel is empty and isn't registered, the first person - // gets ownership of the channel (temporarily) - if(this.dbloaded && this.users.length == 0 && !this.registered) { - user.rank = (user.rank < Rank.Owner) ? 10 : user.rank; - user.socket.emit("channelNotRegistered"); - } this.users.push(user); this.broadcastVoteskipUpdate(); if(user.name != "") { @@ -953,6 +939,12 @@ Channel.prototype.broadcastUsercount = function() { Channel.prototype.broadcastNewUser = function(user) { var self = this; + // If the channel is empty and isn't registered, the first person + // gets ownership of the channel (temporarily) + if(self.dbloaded && self.users.length == 1 && !self.registered) { + user.rank = (user.rank < Rank.Owner) ? 10 : user.rank; + user.socket.emit("channelNotRegistered"); + } self.server.db.listAliases(user.ip, function (err, aliases) { if(err) { aliases = []; diff --git a/database.js b/database.js index a6558058..74af84c0 100644 --- a/database.js +++ b/database.js @@ -747,6 +747,11 @@ Database.prototype.userLogin = function (name, pw, session, callback) { return; } + if(row.session_hash) { + callback(null, row); + return; + } + self.createLoginSession(name, function (err, hash) { if(err) { callback(err, null); @@ -790,6 +795,7 @@ Database.prototype.userLoginPassword = function (name, pw, callback) { if(valid) { // For security, erase the password field before returning delete row["pw"]; + row.session_hash = ""; callback(null, row); return; } @@ -809,6 +815,7 @@ Database.prototype.userLoginPassword = function (name, pw, callback) { // Remove password field before returning delete row["pw"]; + row.session_hash = ""; callback(null, row); } else { callback("Invalid username/password combination", null); diff --git a/user.js b/user.js index a634064a..cc6086d2 100644 --- a/user.js +++ b/user.js @@ -475,7 +475,6 @@ User.prototype.initCallbacks = function() { self.server.db.saveUserPlaylist(pl, self.name, data.name, function (err, res) { if(err) { - console.log(typeof err); self.socket.emit("savePlaylist", { success: false, error: err @@ -628,7 +627,7 @@ User.prototype.login = function(name, pw, session) { } else { self.server.db.userLogin(name, pw, session, function (err, row) { if(err) { - self.server.actionlog.record(self.ip, self.name, "login-failure"); + self.server.actionlog.record(self.ip, name, "login-failure"); self.socket.emit("login", { success: false, error: err @@ -637,7 +636,7 @@ User.prototype.login = function(name, pw, session) { } if(self.channel != null) { for(var i = 0; i < self.channel.users.length; i++) { - if(self.channel.users[i].name == name) { + if(self.channel.users[i].name.toLowerCase() == name.toLowerCase()) { self.channel.kick(self.channel.users[i], "Duplicate login"); } } @@ -667,11 +666,11 @@ User.prototype.login = function(name, pw, session) { } }; if(self.channel !== null) { - self.channel.getRank(self.name, function (err, rank) { - if(!err && rank > self.global_rank) + self.channel.getRank(name, function (err, rank) { + if(!err) self.rank = rank; else - self.rank = self.global_rank + self.rank = self.global_rank; afterRankLookup(); }); } else { diff --git a/www/assets/js/account.js b/www/assets/js/account.js index 5ad2d614..c65cf7f9 100644 --- a/www/assets/js/account.js +++ b/www/assets/js/account.js @@ -44,9 +44,10 @@ if(uname && session) { session: session }; postJSON(WEB_URL + "/api/login?callback=?", data, function (data) { + console.log(data); if(data.success) onLogin(); - }, "jsonp"); + }); } function onLogin() { @@ -182,7 +183,7 @@ $("#registerbtn").click(function() { .text(data.error) .insertBefore($("#registerpane form")); } - }, "jsonp"); + }); }); $("#loginbtn").click(function() { @@ -217,7 +218,7 @@ $("#loginbtn").click(function() { .text(data.error) .insertBefore($("#loginpane form")); } - }, "jsonp"); + }); }); $("#cpwbtn").click(function() { @@ -279,7 +280,7 @@ $("#cpwbtn").click(function() { .text(data.error) .insertBefore($("#changepwpane form")); } - }, "jsonp"); + }); }); $("#cebtn").click(function() { @@ -332,7 +333,7 @@ $("#cebtn").click(function() { .text(data.error) .insertBefore($("#changeemailpane form")); } - }, "jsonp"); + }); }); @@ -360,7 +361,7 @@ $("#rpbtn").click(function() { .text(data.error) .insertBefore($("#pwresetpane form")); } - }, "jsonp"); + }); }); @@ -388,7 +389,7 @@ $("#profilesave").click(function() { .text(data.error) .insertBefore($("#profilepane form")); } - }, "jsonp"); + }); }); $("#login").click(function() { diff --git a/www/login.html b/www/login.html index 2ace9b2d..0e48e3c8 100644 --- a/www/login.html +++ b/www/login.html @@ -37,6 +37,30 @@ From b3526b5ee282728e6a438ddd79d5222d23554252 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 17:58:16 -0500 Subject: [PATCH 52/60] Revisit some banning issues --- channel.js | 62 +++++++++++++++++------------------- database.js | 89 ++++++++++++++++++++++++++++++++++++++++++++-------- utilities.js | 10 ++++++ 3 files changed, 115 insertions(+), 46 deletions(-) diff --git a/channel.js b/channel.js index 1d53a34b..2639c6fc 100644 --- a/channel.js +++ b/channel.js @@ -20,6 +20,7 @@ var ChatCommand = require("./chatcommand.js"); var Filter = require("./filter.js").Filter; var Playlist = require("./playlist"); var sanitize = require("validator").sanitize; +var $util = require("./utilities"); var Channel = function(name, Server) { var self = this; @@ -307,8 +308,11 @@ Channel.prototype.readLog = function (filterIp, callback) { rs.on("end", function () { if(filterIp) { buffer = buffer.replace( - /(\d{1,3}\.){2}(\d{1,3})\.(\d{1,3})/g, - "x.x.$2.$3" + /\d+\.\d+\.(\d+\.\d+)/, + "x.x.$1" + ).replace( + /\d+\.\d+\.(\d+)/, + "x.x.$1.*" ); } @@ -456,10 +460,9 @@ Channel.prototype.saveRank = function (user, callback) { Channel.prototype.getIPRank = function (ip, callback) { var self = this; - var names = []; - var next = function (names) { - self.server.db.getChannelRank(self.name, names, - function (err, res) { + self.server.db.listAliases(ip, function (err, names) { + self.server.db.listChannelUserRanks(self.name, names, + function (err, res) { if(err) { callback(err, null); return; @@ -467,30 +470,23 @@ Channel.prototype.getIPRank = function (ip, callback) { var rank = 0; for(var i in res) { - rank = (res[i].rank > rank) ? res[i].rank : rank; + rank = (res[i] > rank) ? res[i] : rank; } - callback(null, rank); - }); - }; - if(ip in self.ip_alias) { - names = self.ip_alias[ip]; - next(names); - } else if(ip.match(/^(\d+)\.(\d+)\.(\d+)$/)) { - // Range - for(var ip2 in self.ip_alias) { - if(ip2.indexOf(ip) == 0) { - for(var i in self.ip_aliases[ip2]) - names.push(self.ip_aliases[ip2][i]); - } - } - next(names); - } else { - self.server.db.listAliases(ip, function (err, names) { - self.ip_alias[ip] = names; - next(names); + self.server.db.listGlobalRanks(names, function (err, res) { + if(err) { + callback(err, null); + return; + } + + for(var i in res) { + rank = (res[i] > rank) ? res[i] : rank; + } + + callback(null, rank); + }); }); - } + }); } Channel.prototype.cacheMedia = function(media) { @@ -516,7 +512,7 @@ Channel.prototype.tryNameBan = function(actor, name) { self.getRank(name, function (err, rank) { if(err) { actor.socket.emit("errorMsg", { - msg: "Internal error" + msg: "Internal error " + err }); return; } @@ -577,7 +573,7 @@ Channel.prototype.tryIPBan = function(actor, name, range) { self.server.db.listIPsForName(name, function (err, ips) { if(err) { actor.socket.emit("errorMsg", { - msg: "Internal error" + msg: "Internal error: " + err }); return; } @@ -594,8 +590,8 @@ Channel.prototype.tryIPBan = function(actor, name, range) { if(rank >= actor.rank) { actor.socket.emit("errorMsg", { - msg: "You don't have permission to ban IP: x.x." + - ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1") + msg: "You don't have permission to ban IP: " + + $util.maskIP(ip) }); return; } @@ -807,7 +803,7 @@ Channel.prototype.sendBanlist = function(user) { var ip_hidden = this.hideIP(ip); var disp = ip; if(user.rank < Rank.Siteadmin) { - disp = "x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1"); + disp = $util.maskIP(ip); } ents.push({ ip_displayed: disp, @@ -1032,7 +1028,7 @@ Channel.prototype.broadcastBanlist = function() { var name = this.ipbans[ip][0]; var ip_hidden = this.hideIP(ip); ents.push({ - ip_displayed: "x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1"), + ip_displayed: $util.maskIP(ip), ip_hidden: ip_hidden, name: name, aliases: this.ip_alias[ip] || [], diff --git a/database.js b/database.js index 74af84c0..c965d4e4 100644 --- a/database.js +++ b/database.js @@ -468,7 +468,35 @@ Database.prototype.dropChannel = function (name, callback) { }); }; -Database.prototype.getChannelRank = function (channame, names, callback) { +Database.prototype.getChannelRank = function (channame, name, callback) { + var self = this; + if(typeof callback !== "function") + return; + + if(!$util.isValidChannelName(channame)) { + callback("Invalid channel name", null); + return; + } + + var query = "SELECT name, rank FROM `chan_" + channame + "_ranks`" + + "WHERE name=?"; + + self.query(query, [name], function (err, res) { + if(err) { + Logger.errlog.log("! Failed to lookup " + channame + " ranks"); + callback(err, null); + return; + } + + if(res.length == 0) + callback(null, 0); + else + callback(null, res[0].rank); + }); +}; + +Database.prototype.listChannelUserRanks = function (channame, names, + callback) { var self = this; if(typeof callback !== "function") return; @@ -493,20 +521,12 @@ Database.prototype.getChannelRank = function (channame, names, callback) { self.query(query, names, function (err, res) { if(err) { Logger.errlog.log("! Failed to lookup " + channame + " ranks"); - if(names.length == 1) - callback(err, 0); - else - callback(err, []); + callback(err, null); return; } - if(names.length == 1) { - if(res.length == 0) - callback(null, 0); - else - callback(null, res[0].rank); - return; - } + for(var i in res) + res[i] = res[i].rank; callback(null, res); }); @@ -898,6 +918,7 @@ Database.prototype.getGlobalRank = function (name, callback) { return; var query = "SELECT global_rank FROM registrations WHERE uname=?"; + self.query(query, [name], function (err, res) { if(err) { callback(err, null); @@ -913,6 +934,40 @@ Database.prototype.getGlobalRank = function (name, callback) { }); }; +Database.prototype.listGlobalRanks = function (names, callback) { + var self = this; + if(typeof callback !== "function") + return; + + if(typeof names === "string") + names = [names]; + + // Build the query template (?, ?, ?, ?, ...) + var nlist = []; + for(var i in names) + nlist.push("?"); + nlist = "(" + nlist.join(",") + ")"; + + var query = "SELECT global_rank FROM registrations WHERE uname IN " + + nlist; + self.query(query, names, function (err, res) { + if(err) { + callback(err, null); + return; + } + + if(res.length == 0) { + callback("User does not exist", null); + return; + } + + for(var i in res) + res[i] = res[i].global_rank; + + callback(null, res); + }); +}; + /* END REGION */ /* REGION users */ @@ -1224,7 +1279,15 @@ Database.prototype.listAliases = function (ip, callback) { if(typeof callback !== "function") return; - var query = "SELECT name FROM aliases WHERE ip=?"; + var query = "SELECT name FROM aliases WHERE ip"; + // Range + if(ip.match(/^\d+\.\d+\.\d+$/)) { + query += " LIKE ?"; + ip += ".%"; + } else { + query += "=?"; + } + self.query(query, [ip], function (err, res) { var names = null; if(!err) { diff --git a/utilities.js b/utilities.js index 42d04df5..0e401cb4 100644 --- a/utilities.js +++ b/utilities.js @@ -15,5 +15,15 @@ module.exports = { salt.push(chars[parseInt(Math.random()*chars.length)]); } return salt.join(''); + }, + + maskIP: function (ip) { + if(ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { + // standard 32 bit IP + return ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "x.x.$1"); + } else if(ip.match(/^\d+\.\d+\.\d+/)) { + // /24 range + return ip.replace(/\d+\.\d+\.(\d+)/, "x.x.$1.*"); + } } }; From bab2b887f4f7a06198d07c1a29d715c62046fe22 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 18:01:57 -0500 Subject: [PATCH 53/60] Fix channel.js bug, add defer to APIs for faster page loads --- channel.js | 40 +++++++--------------------------------- www/channel.html | 12 ++++++------ 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/channel.js b/channel.js index 2639c6fc..88467f5a 100644 --- a/channel.js +++ b/channel.js @@ -1117,32 +1117,6 @@ Channel.prototype.onVideoChange = function () { this.broadcastDrinks(); } -// The server autolead function -function mediaUpdate(chan, id) { - // Bail cases - video changed, someone's leader, no video playing - if(chan.media == null || - id != chan.media.id || - chan.leader != null || - chan.users.length == 0) { - return; - } - - chan.media.currentTime += (new Date().getTime() - chan.time) / 1000.0; - chan.time = new Date().getTime(); - - // Show's over, move on to the next thing - if(chan.media.currentTime > chan.media.seconds + 1) { - chan.playNext(); - } - // Send updates about every 5 seconds - else if(chan.i % 5 == 0) { - chan.sendAll("mediaUpdate", chan.media.timeupdate()); - } - chan.i++; - - setTimeout(function() { mediaUpdate(chan, id); }, 1000); -} - function isLive(type) { return type == "li" // Livestream.com || type == "tw" // Twitch.tv @@ -1253,12 +1227,12 @@ Channel.prototype.addMedia = function(data, user) { return; } else { - chan.logger.log("### " + user.name + " queued " + item.media.title); - chan.sendAll("queue", { + self.logger.log("### " + user.name + " queued " + item.media.title); + self.sendAll("queue", { item: item.pack(), after: item.prev ? item.prev.uid : "prepend" }); - chan.broadcastPlaylistMeta(); + self.broadcastPlaylistMeta(); } }); return; @@ -1278,14 +1252,14 @@ Channel.prototype.addMedia = function(data, user) { return; } else { - chan.logger.log("### " + user.name + " queued " + item.media.title); - chan.sendAll("queue", { + self.logger.log("### " + user.name + " queued " + item.media.title); + self.sendAll("queue", { item: item.pack(), after: item.prev ? item.prev.uid : "prepend" }); - chan.broadcastPlaylistMeta(); + self.broadcastPlaylistMeta(); if(!item.temp) - chan.cacheMedia(item.media); + self.cacheMedia(item.media); } }); } diff --git a/www/channel.html b/www/channel.html index e87bd8b8..9cee56ee 100644 --- a/www/channel.html +++ b/www/channel.html @@ -246,12 +246,12 @@ - - - - - - + + + + + + From 1f7a53a90b94c4079488c38b9a7169241a9c136a Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 18:03:49 -0500 Subject: [PATCH 54/60] Fix a crash condition --- channel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/channel.js b/channel.js index 88467f5a..a19d40d3 100644 --- a/channel.js +++ b/channel.js @@ -1376,6 +1376,7 @@ Channel.prototype.tryDequeue = function(user, data) { } Channel.prototype.tryUncache = function(user, data) { + var self = this; if(!Rank.hasPermission(user, "uncache")) { return; } From e3ef9e7896a92b86484c7cfa5bd68031b4b5aa9a Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 18 Aug 2013 18:35:49 -0500 Subject: [PATCH 55/60] Add a special notice --- channel.js | 13 +++++++++++++ www/assets/js/callbacks.js | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/channel.js b/channel.js index a19d40d3..de601cc0 100644 --- a/channel.js +++ b/channel.js @@ -508,6 +508,12 @@ Channel.prototype.tryNameBan = function(actor, name) { } name = name.toLowerCase(); + if(name == actor.name.toLowerCase()) { + actor.socket.emit("costanza", { + msg: "Trying to ban yourself?" + }); + return; + } self.getRank(name, function (err, rank) { if(err) { @@ -570,6 +576,13 @@ Channel.prototype.tryIPBan = function(actor, name, range) { if(typeof name != "string") { return; } + name = name.toLowerCase(); + if(name == actor.name.toLowerCase()) { + actor.socket.emit("costanza", { + msg: "Trying to ban yourself?" + }); + return; + } self.server.db.listIPsForName(name, function (err, ips) { if(err) { actor.socket.emit("errorMsg", { diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index a12994e6..6ac940bf 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -74,6 +74,33 @@ Callbacks = { alert(data.msg); }, + costanza: function (data) { + hidePlayer(); + $("#costanza-modal").modal("hide"); + var modal = $("
").addClass("modal hide fade") + .attr("id", "costanza-modal") + .appendTo($("body")); + + + var body = $("
").addClass("modal-body").appendTo(modal); + $("