diff --git a/api.js b/api.js index e13f96e7..b4317e18 100644 --- a/api.js +++ b/api.js @@ -16,7 +16,9 @@ var apilog = new Logger.Logger("api.log"); var jsonHandlers = { "channeldata": handleChannelData, - "listloaded" : handleChannelList + "listloaded" : handleChannelList, + "login" : handleLogin, + "register" : handleRegister }; function handle(path, req, res) { @@ -59,6 +61,15 @@ function handle(path, req, res) { } exports.handle = handle; +function sendJSON(res, obj) { + var response = JSON.stringify(obj, null, 4); + var len = unescape(encodeURIComponent(response)).length; + + res.setHeader("Content-Type", "application/json"); + res.setHeader("Content-Length", len); + res.end(response); +} + function handleChannelData(params, req, res) { var clist = params.channel || ""; clist = clist.split(","); @@ -91,18 +102,15 @@ function handleChannelData(params, req, res) { data.push(d); } - var response = JSON.stringify(data, null, 4); - var len = unescape(encodeURIComponent(response)).length; - - res.setHeader("Content-Type", "application/json"); - res.setHeader("Content-Length", len); - res.end(response); + sendJSON(res, data); } function handleChannelList(params, req, res) { + var session = params.session || ""; var name = params.name || ""; var pw = params.pw || ""; - if(!Auth.login(name, pw)) { + var row = Auth.login(name, pw, session); + if(!row || row.global_rank < 255) { res.send(403); return; } @@ -112,3 +120,64 @@ function handleChannelList(params, req, res) { } handleChannelData({channel: clist.join(",")}, req, res); } + +function handleLogin(params, req, res) { + var session = params.session || ""; + var name = params.name || ""; + var pw = params.pw || ""; + + var row = Auth.login(name, pw, session); + if(row) { + sendJSON(res, { + success: true, + session: row.session_hash + }); + } + else { + sendJSON(res, { + success: false + }); + } +} + +function handleRegister(params, req, res) { + var name = params.name || ""; + var pw = params.pw || ""; + + if(pw == "") { + sendJSON(res, { + success: false, + error: "You must provide a password" + }); + return; + } + else if(Auth.isRegistered(name)) { + sendJSON(res, { + success: false, + error: "That username is already taken" + }); + return false; + } + else if(!Auth.validateName(name)) { + 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) { + Logger.syslog.log(this.ip + " registered " + name); + sendJSON(res, { + success: true, + session: session + }); + } + else { + sendJSON(res, { + success: false, + error: "I dunno what went wrong" + }); + } + } +} diff --git a/auth.js b/auth.js index c096352c..dd7dc8e0 100644 --- a/auth.js +++ b/auth.js @@ -55,16 +55,36 @@ exports.register = function(name, pw) { return false; } var hash = bcrypt.hashSync(pw, 10); - var query = "INSERT INTO registrations VALUES (NULL, '{1}', '{2}', 1)" + var query = "INSERT INTO registrations VALUES (NULL, '{1}', '{2}', 1, '', 0)" .replace(/\{1\}/, name) .replace(/\{2\}/, hash); var results = db.querySync(query); db.closeSync(); - return results; + 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.login = function(name, pw) { +exports.loginPassword = function(name, pw) { var db = mysql.createConnectionSync(); db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER, Config.MYSQL_PASSWORD, Config.MYSQL_DB); @@ -110,6 +130,62 @@ exports.login = function(name, pw) { return false; } +exports.createSession = function(name) { + var salt = sessionSalt(); + var hash = hashlib.sha256(salt + name); + var db = mysql.createConnectionSync(); + db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER, + Config.MYSQL_PASSWORD, Config.MYSQL_DB); + if(!db.connectedSync()) { + return false; + } + var query = ["UPDATE registrations SET ", + "session_hash='{1}',", + "expire={2} ", + "WHERE uname='{3}'"].join("") + .replace(/\{1\}/, hash) + .replace(/\{2\}/, new Date().getTime() + 604800000) + .replace(/\{3\}/, name) + var results = db.querySync(query); + return results ? hash : false; +} + +exports.loginSession = function(name, hash) { + var db = mysql.createConnectionSync(); + db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER, + Config.MYSQL_PASSWORD, Config.MYSQL_DB); + if(!db.connectedSync()) { + return false; + } + var query = "SELECT * FROM registrations WHERE uname='{1}'" + .replace(/\{1\}/, name) + var results = db.querySync(query); + 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.getGlobalRank = function(name) { var db = mysql.createConnectionSync(); db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER, diff --git a/package.json b/package.json index 0bf54acf..3d2fa18b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "1.2.7", + "version": "1.3.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/server.js b/server.js index 147dd089..27568552 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ 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. */ -const VERSION = "1.2.7"; +const VERSION = "1.3.0"; var fs = require("fs"); var Logger = require("./logger.js"); diff --git a/user.js b/user.js index 354ccf18..177cf219 100644 --- a/user.js +++ b/user.js @@ -98,12 +98,13 @@ User.prototype.initCallbacks = function() { }.bind(this)); this.socket.on("login", function(data) { - if(data.name == undefined || data.pw == undefined) - return; - if(data.pw.length > 100) - data.pw = data.pw.substring(0, 100); + 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(data.name, data.pw); + this.login(name, pw, session); }.bind(this)); this.socket.on("register", function(data) { @@ -325,7 +326,7 @@ User.prototype.handleAdm = function(data) { }; // Attempt to login -User.prototype.login = function(name, pw) { +User.prototype.login = function(name, pw, session) { if(this.channel != null && name != "") { for(var i = 0; i < this.channel.users.length; i++) { if(this.channel.users[i].name == name) { @@ -338,7 +339,7 @@ User.prototype.login = function(name, pw) { } } // No password => try guest login - if(pw == "") { + if(pw == "" && session == "") { // Sorry bud, can't take that name if(Auth.isRegistered(name)) { this.socket.emit("login", { @@ -372,10 +373,11 @@ User.prototype.login = function(name, pw) { } else { var row; - if((row = Auth.login(name, pw))) { + if((row = Auth.login(name, pw, session))) { this.loggedIn = true; this.socket.emit("login", { - success: true + success: true, + session: row.session_hash }); Logger.syslog.log(this.ip + " logged in as " + name); var chanrank = (this.channel != null) ? this.channel.getRank(name) diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 6a7b0cb0..c2f045e2 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -143,10 +143,9 @@ function initCallbacks() { $("#loginform").css("display", "none"); $("#logoutform").css("display", ""); $("#loggedin").css("display", ""); - if(pw != "") { - createCookie("sync_uname", uname, 1); - createCookie("sync_pw", pw, 1); - } + session = data.session; + createCookie("sync_uname", uname, 7); + createCookie("sync_session", session, 7); } }); diff --git a/www/assets/js/client.js b/www/assets/js/client.js index 4bb49f97..7b42bbf7 100644 --- a/www/assets/js/client.js +++ b/www/assets/js/client.js @@ -30,7 +30,7 @@ var VHEIGHT = "377"; var IGNORED = []; var KICKED = false; var uname = readCookie("sync_uname"); -var pw = readCookie("sync_pw"); +var session = readCookie("sync_session"); var Rank = { Guest: 0, @@ -116,10 +116,10 @@ socket.on("connect", function() { socket.emit("joinChannel", { name: params["channel"] }); - if(uname != null && pw != null && pw != "false") { + if(uname && session) { socket.emit("login", { name: uname, - pw: pw + session: session }); } $("
").addClass("server-msg-reconnect") @@ -194,40 +194,14 @@ $("#qlockbtn").click(function() { }); }); -function loginClick() { - uname = $("#username").val(); - pw = $("#password").val(); - socket.emit("login", { - name: uname, - pw: pw - }); -}; - -$("#login").click(loginClick); -$("#username").keydown(function(ev) { - if(ev.keyCode == 13) - loginClick(); -}); -$("#password").keydown(function(ev) { - if(ev.keyCode == 13) - loginClick(); -}); +$("#login").click(showLoginFrame); $("#logout").click(function() { eraseCookie("sync_uname"); - eraseCookie("sync_pw"); + eraseCookie("sync_session"); document.location.reload(true); }); -$("#register").click(function() { - uname = $("#username").val(); - pw = $("#password").val(); - socket.emit("register", { - name: uname, - pw: pw - }); -}); - $("#chatline").keydown(function(ev) { if(ev.keyCode == 13 && $("#chatline").val() != "") { if($("#chatline").val().trim() == "/poll") { diff --git a/www/assets/js/functions.js b/www/assets/js/functions.js index f175df6e..63f4bcfa 100644 --- a/www/assets/js/functions.js +++ b/www/assets/js/functions.js @@ -843,3 +843,69 @@ function newPollMenu() { }); modal.modal(); } + +function showLoginFrame() { + var modal = $("
").addClass("modal hide fade") + .appendTo($("body")); + var head = $("
").addClass("modal-header") + .appendTo(modal); + $("