From 0603a02d2e3e7a3266cbfef416039a8ac59cfdf5 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 1 Feb 2014 13:03:08 -0600 Subject: [PATCH] Finish password recovery --- lib/bgtask.js | 17 +++++++++++++-- lib/database.js | 29 ++++++++++++++++++++++++++ lib/logger.js | 2 +- lib/web/account.js | 19 +++++++++-------- templates/account-passwordrecover.jade | 28 +++++++++++++++++++++++++ templates/login.jade | 1 + 6 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 templates/account-passwordrecover.jade diff --git a/lib/bgtask.js b/lib/bgtask.js index 8b8d86c3..509fbd0f 100644 --- a/lib/bgtask.js +++ b/lib/bgtask.js @@ -19,6 +19,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var Logger = require("./logger"); var Config = require("./config"); +var db = require("./database"); var init = null; @@ -28,7 +29,6 @@ function initStats(Server) { var STAT_EXPIRE = Config.get("stats.max-age"); setInterval(function () { - var db = Server.db; var chancount = Server.channels.length; var usercount = 0; Server.channels.forEach(function (chan) { @@ -49,7 +49,7 @@ function initAliasCleanup(Server) { var CLEAN_EXPIRE = Config.get("aliases.max-age"); setInterval(function () { - Server.db.cleanOldAliases(CLEAN_EXPIRE, function (err) { + db.cleanOldAliases(CLEAN_EXPIRE, function (err) { Logger.syslog.log("Cleaned old aliases"); if (err) Logger.errlog.log(err); @@ -57,6 +57,18 @@ function initAliasCleanup(Server) { }, CLEAN_INTERVAL); } +/* Password reset cleanup */ +function initPasswordResetCleanup(Server) { + var CLEAN_INTERVAL = 8*60*60*1000; + + setInterval(function () { + db.cleanOldPasswordResets(function (err) { + if (err) + Logger.errlog.log(err); + }); + }, CLEAN_INTERVAL); +} + /* Clean out old rate limiters */ function initIpThrottleCleanup(Server) { setInterval(function () { @@ -91,4 +103,5 @@ module.exports = function (Server) { initAliasCleanup(Server); initIpThrottleCleanup(Server); initChannelDumper(Server); + initPasswordResetCleanup(Server); }; diff --git a/lib/database.js b/lib/database.js index cf5bbaba..0b0872d1 100644 --- a/lib/database.js +++ b/lib/database.js @@ -249,6 +249,18 @@ module.exports.globalUnbanIP = function (ip, callback) { /* password recovery */ +/** + * Deletes recovery rows older than the given time + */ +module.exports.cleanOldPasswordResets = function (callback) { + if (typeof callback === "undefined") { + callback = blackHole; + } + + var query = "DELETE FROM aliases WHERE time < ?"; + module.exports.query(query, [Date.now() - 24*60*60*1000], callback); +}; + module.exports.addPasswordReset = function (data, cb) { if (typeof cb !== "function") { cb = blackHole; @@ -270,6 +282,23 @@ module.exports.addPasswordReset = function (data, cb) { [ip, name, email, hash, expire, ip, hash, email, expire], cb); }; +module.exports.lookupPasswordReset = function (hash, cb) { + if (typeof cb !== "function") { + return; + } + + module.exports.query("SELECT * FROM `password_reset` WHERE hash=?", [hash], + function (err, rows) { + if (err) { + cb(err, null); + } else if (rows.length === 0) { + cb("Invalid password reset link", null); + } else { + cb(null, rows[0]); + } + }); +}; + module.exports.deletePasswordReset = function (hash) { module.exports.query("DELETE FROM `password_reset` WHERE hash=?", [hash]); }; diff --git a/lib/logger.js b/lib/logger.js index 2b60ba88..2b3851c1 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -69,4 +69,4 @@ var eventlog = makeConsoleLogger(path.join(__dirname, "..", "events.log")); exports.Logger = Logger; exports.errlog = errlog; exports.syslog = syslog; -exports.eventlog = syslog; +exports.eventlog = eventlog; diff --git a/lib/web/account.js b/lib/web/account.js index e42acc34..67289acd 100644 --- a/lib/web/account.js +++ b/lib/web/account.js @@ -459,7 +459,7 @@ function handlePasswordResetPage(req, res) { } /** - * Handles a POST request to reset a user"s password + * Handles a POST request to reset a user's password */ function handlePasswordReset(req, res) { logRequest(req); @@ -548,7 +548,7 @@ function handlePasswordReset(req, res) { "not initiate this, there is no need to take action."+ " To reset your password, copy and paste the " + "following link into your browser: " + - Config.get("http.domain") + "/passwordrecover/"+hash; + Config.get("http.domain") + "/account/passwordrecover/"+hash; var mail = { from: "CyTube Services <" + Config.get("mail.from") + ">", @@ -579,10 +579,10 @@ function handlePasswordReset(req, res) { } /** - * Handles a request for /passwordreceover/ + * Handles a request for /account/passwordrecover/ */ function handlePasswordRecover(req, res) { - var hash = req.query.hash; + var hash = req.params.hash; if (typeof hash !== "string") { res.send(400); return; @@ -592,7 +592,7 @@ function handlePasswordRecover(req, res) { db.lookupPasswordReset(hash, function (err, row) { if (err) { - sendJade(req, "account-passwordrecover", { + sendJade(res, "account-passwordrecover", { recovered: false, recoverErr: err, loginName: false @@ -601,7 +601,7 @@ function handlePasswordRecover(req, res) { } if (row.ip && row.ip !== ip) { - sendJade(req, "account-passwordrecover", { + sendJade(res, "account-passwordrecover", { recovered: false, recoverErr: "Your IP address does not match the address " + "used to submit the reset request. For your " + @@ -613,7 +613,7 @@ function handlePasswordRecover(req, res) { } if (Date.now() >= row.expire) { - sendJade(req, "account-passwordrecover", { + sendJade(res, "account-passwordrecover", { recovered: false, recoverErr: "This password recovery link has expired. Password " + "recovery links are valid only for 24 hours after " + @@ -630,7 +630,7 @@ function handlePasswordRecover(req, res) { } db.users.setPassword(row.name, newpw, function (err) { if (err) { - sendJade(req, "account-passwordrecover", { + sendJade(res, "account-passwordrecover", { recovered: false, recoverErr: "Database error. Please contact an administrator if " + "this persists.", @@ -641,7 +641,7 @@ function handlePasswordRecover(req, res) { db.deletePasswordReset(hash); - sendJade(req, "account-passwordrecover", { + sendJade(res, "account-passwordrecover", { recovered: true, recoverPw: newpw, loginName: false @@ -663,5 +663,6 @@ module.exports = { app.post("/account/profile", handleAccountProfile); app.get("/account/passwordreset", handlePasswordResetPage); app.post("/account/passwordreset", handlePasswordReset); + app.get("/account/passwordrecover/:hash", handlePasswordRecover); } }; diff --git a/templates/account-passwordrecover.jade b/templates/account-passwordrecover.jade new file mode 100644 index 00000000..16b2d1b2 --- /dev/null +++ b/templates/account-passwordrecover.jade @@ -0,0 +1,28 @@ +doctype html +html(lang="en") + head + include head + mixin head() + body + #wrap + nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation") + include nav + mixin navheader() + #nav-collapsible.collapse.navbar-collapse + ul.nav.navbar-nav + mixin navdefaultlinks("/account/passwordrecover/") + mixin navloginlogout("/account/passwordrecover/") + section#mainpage + .container + .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 + h3 Recover Password + if recovered + .alert.alert-success.center.messagebox + strong Your password has been changed + p Your account has been assigned the temporary password #{recoverPw}. You may now use this password to log in and choose a new password by visiting the change password/email page. + else + .alert.alert-danger.center.messagebox + strong Password recovery failed + p= recoverErr + include footer + mixin footer() diff --git a/templates/login.jade b/templates/login.jade index ac047c94..50cb1041 100644 --- a/templates/login.jade +++ b/templates/login.jade @@ -34,6 +34,7 @@ html(lang="en") .form-group label(for="password") Password input#password.form-control(type="password", name="password") + a(href="/account/passwordreset") Forgot password? button.btn.btn-success.btn-block(type="submit") Login else .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3