diff --git a/api.js b/api.js index eb0fb3f9..9d3f9a3b 100644 --- a/api.js +++ b/api.js @@ -26,10 +26,12 @@ var jsonHandlers = { "login" : handleLogin, "register" : handleRegister, "changepass" : handlePasswordChange, + "resetpass" : handlePasswordReset, + "recoverpw" : handlePasswordRecover, "setemail" : handleEmailChange, "globalbans" : handleGlobalBans, "admreports" : handleAdmReports, - "pwreset" : handlePasswordReset + "acppwreset" : handleAcpPasswordReset }; function handle(path, req, res) { @@ -229,6 +231,50 @@ function handlePasswordChange(params, req, res) { } } +function handlePasswordReset(params, req, res) { + var name = params.name || ""; + var email = unescape(params.email || ""); + var ip = req.socket.address().address; + + try { + Database.generatePasswordReset(ip, name, email); + } + catch(e) { + sendJSON(res, { + success: false, + error: e + }); + return; + } + + sendJSON(res, { + success: true + }); +} + +function handlePasswordRecover(params, req, res) { + var hash = params.hash || ""; + var ip = req.socket.address().address; + + try { + var info = Database.recoverPassword(hash); + sendJSON(res, { + success: true, + name: info[0], + pw: info[1] + }); + Logger.syslog.log(ip + " recovered password for " + name); + return; + } + catch(e) { + sendJSON(res, { + success: false, + error: e + }); + } + +} + function handleEmailChange(params, req, res) { var name = params.name || ""; var pw = params.pw || ""; @@ -309,7 +355,7 @@ function handleRegister(params, req, res) { else { sendJSON(res, { success: false, - error: "I dunno what went wrong" + error: "Registration error. Contact an admin for assistance." }); } } @@ -373,7 +419,7 @@ function handleAdmReports(params, req, res) { }); } -function handlePasswordReset(params, req, res) { +function handleAcpPasswordReset(params, req, res) { var name = params.name || ""; var pw = params.pw || ""; var session = params.session || ""; diff --git a/auth.js b/auth.js index 03a87703..51cedbe1 100644 --- a/auth.js +++ b/auth.js @@ -58,7 +58,7 @@ exports.register = function(name, pw) { var hash = bcrypt.hashSync(pw, 10); var query = Database.createQuery( ["INSERT INTO `registrations` VALUES ", - "(NULL, ?, ?, 1, '', 0, '', '')"].join(""), + "(NULL, ?, ?, 1, '', 0, '', '', '')"].join(""), [name, hash] ); var results = db.querySync(query); diff --git a/database.js b/database.js index 9eafcb52..fc4454d7 100644 --- a/database.js +++ b/database.js @@ -2,6 +2,7 @@ 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 = ""; @@ -620,13 +621,14 @@ function generatePasswordReset(ip, name, email) { // Validation complete, now time to reset it var hash = hashlib.sha256(Date.now() + name); + var exp = Date.now() + 24*60*60*1000; query = createQuery( ["INSERT INTO `password_reset` (", - "`ip`, `name`, `hash`, `email`, `expire", + "`ip`, `name`, `hash`, `email`, `expire`", ") VALUES (", "?, ?, ?, ?, ?", - ")"].join(""), - [ip, name, hash, email, Date.now() + 24*60*60] + ") ON DUPLICATE KEY UPDATE `expire`=?"].join(""), + [ip, name, hash, email, exp, exp] ); results = db.querySync(query); @@ -638,6 +640,45 @@ function generatePasswordReset(ip, name, email) { return true; } +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) { @@ -684,4 +725,5 @@ exports.channelUnbanName = channelUnbanName; exports.setProfile = setProfile; exports.setUserEmail = setUserEmail; exports.generatePasswordReset = generatePasswordReset; +exports.recoverPassword = recoverPassword; exports.resetPassword = resetPassword; diff --git a/www/account.html b/www/account.html index c170d69a..fcee1ffa 100644 --- a/www/account.html +++ b/www/account.html @@ -160,6 +160,28 @@ +
diff --git a/www/assets/js/account.js b/www/assets/js/account.js index e56f13d3..1148b6d1 100644 --- a/www/assets/js/account.js +++ b/www/assets/js/account.js @@ -40,17 +40,18 @@ function makeTabCallback(tabid, paneid) { $("#register").click(makeTabCallback("#register", "#registerpane")); $("#pwchange").click(makeTabCallback("#pwchange", "#changepwpane")); +$("#pwreset").click(makeTabCallback("#pwreset", "#pwresetpane")); $("#email").click(makeTabCallback("#email", "#changeemailpane")); $("#registerbtn").click(function() { $("#registerpane").find(".alert-error").remove(); $("#registerpane").find(".error").removeClass("error"); - var uname = $("#regusername").val(); + var name = $("#regusername").val(); var pw = $("#regpw").val(); var pwc = $("#regpwconfirm").val(); var err = false; - if(!uname.match(/^[a-z0-9_]{1,20}$/i)) { + if(!name.match(/^[a-z0-9_]{1,20}$/i)) { $("
").addClass("alert alert-error") .text("Usernames must be 1-20 characters long and contain only a-z, 0-9, and underscores") .insertAfter($("#regusername").parent().parent()); @@ -78,6 +79,28 @@ $("#registerbtn").click(function() { } // Input valid, try registering + var url = api + "register?" + [ + "name=" + name, + "pw=" + pw + ].join("&") + "&callback=?"; + + $.getJSON(url, function(data) { + if(data.success) { + uname = name; + session = data.session; + onLogin(); + $("
").addClass("alert alert-success") + .text("Registration successful") + .insertBefore($("#registerpane form")); + $("#regpw").val(""); + $("#regusername").val(""); + } + else { + $("
").addClass("alert alert-error") + .text(data.error) + .insertBefore($("#registerpane form")); + } + }); }); $("#loginbtn").click(function() { @@ -227,6 +250,32 @@ $("#cebtn").click(function() { }); +$("#rpbtn").click(function() { + $("#pwresetpane").find(".alert-error").remove(); + $("#pwresetpane").find(".alert-success").remove(); + var name = $("#rpusername").val(); + var email = $("#rpemail").val(); + + email = escape(email); + var url = api + "resetpass?" + [ + "name=" + name, + "email=" + email + ].join("&") + "&callback=?"; + $.getJSON(url, function(data) { + if(data.success) { + $("
").addClass("alert alert-success") + .text("Password reset link issued. Check your email.") + .insertBefore($("#pwresetpane form")); + } + else { + $("
").addClass("alert alert-error") + .text(data.error) + .insertBefore($("#pwresetpane form")); + } + }); + +}); + $("#login").click(function() { if(!loggedin) { makeTabCallback("#login", "#loginpane")();