Finish password recovery

This commit is contained in:
calzoneman 2014-02-01 13:03:08 -06:00
parent 9562bc3757
commit 0603a02d2e
6 changed files with 84 additions and 12 deletions

View file

@ -19,6 +19,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
var Logger = require("./logger"); var Logger = require("./logger");
var Config = require("./config"); var Config = require("./config");
var db = require("./database");
var init = null; var init = null;
@ -28,7 +29,6 @@ function initStats(Server) {
var STAT_EXPIRE = Config.get("stats.max-age"); var STAT_EXPIRE = Config.get("stats.max-age");
setInterval(function () { setInterval(function () {
var db = Server.db;
var chancount = Server.channels.length; var chancount = Server.channels.length;
var usercount = 0; var usercount = 0;
Server.channels.forEach(function (chan) { Server.channels.forEach(function (chan) {
@ -49,7 +49,7 @@ function initAliasCleanup(Server) {
var CLEAN_EXPIRE = Config.get("aliases.max-age"); var CLEAN_EXPIRE = Config.get("aliases.max-age");
setInterval(function () { setInterval(function () {
Server.db.cleanOldAliases(CLEAN_EXPIRE, function (err) { db.cleanOldAliases(CLEAN_EXPIRE, function (err) {
Logger.syslog.log("Cleaned old aliases"); Logger.syslog.log("Cleaned old aliases");
if (err) if (err)
Logger.errlog.log(err); Logger.errlog.log(err);
@ -57,6 +57,18 @@ function initAliasCleanup(Server) {
}, CLEAN_INTERVAL); }, 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 */ /* Clean out old rate limiters */
function initIpThrottleCleanup(Server) { function initIpThrottleCleanup(Server) {
setInterval(function () { setInterval(function () {
@ -91,4 +103,5 @@ module.exports = function (Server) {
initAliasCleanup(Server); initAliasCleanup(Server);
initIpThrottleCleanup(Server); initIpThrottleCleanup(Server);
initChannelDumper(Server); initChannelDumper(Server);
initPasswordResetCleanup(Server);
}; };

View file

@ -249,6 +249,18 @@ module.exports.globalUnbanIP = function (ip, callback) {
/* password recovery */ /* 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) { module.exports.addPasswordReset = function (data, cb) {
if (typeof cb !== "function") { if (typeof cb !== "function") {
cb = blackHole; cb = blackHole;
@ -270,6 +282,23 @@ module.exports.addPasswordReset = function (data, cb) {
[ip, name, email, hash, expire, ip, hash, email, expire], 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.deletePasswordReset = function (hash) {
module.exports.query("DELETE FROM `password_reset` WHERE hash=?", [hash]); module.exports.query("DELETE FROM `password_reset` WHERE hash=?", [hash]);
}; };

View file

@ -69,4 +69,4 @@ var eventlog = makeConsoleLogger(path.join(__dirname, "..", "events.log"));
exports.Logger = Logger; exports.Logger = Logger;
exports.errlog = errlog; exports.errlog = errlog;
exports.syslog = syslog; exports.syslog = syslog;
exports.eventlog = syslog; exports.eventlog = eventlog;

View file

@ -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) { function handlePasswordReset(req, res) {
logRequest(req); logRequest(req);
@ -548,7 +548,7 @@ function handlePasswordReset(req, res) {
"not initiate this, there is no need to take action."+ "not initiate this, there is no need to take action."+
" To reset your password, copy and paste the " + " To reset your password, copy and paste the " +
"following link into your browser: " + "following link into your browser: " +
Config.get("http.domain") + "/passwordrecover/"+hash; Config.get("http.domain") + "/account/passwordrecover/"+hash;
var mail = { var mail = {
from: "CyTube Services <" + Config.get("mail.from") + ">", from: "CyTube Services <" + Config.get("mail.from") + ">",
@ -579,10 +579,10 @@ function handlePasswordReset(req, res) {
} }
/** /**
* Handles a request for /passwordreceover/<hash> * Handles a request for /account/passwordrecover/<hash>
*/ */
function handlePasswordRecover(req, res) { function handlePasswordRecover(req, res) {
var hash = req.query.hash; var hash = req.params.hash;
if (typeof hash !== "string") { if (typeof hash !== "string") {
res.send(400); res.send(400);
return; return;
@ -592,7 +592,7 @@ function handlePasswordRecover(req, res) {
db.lookupPasswordReset(hash, function (err, row) { db.lookupPasswordReset(hash, function (err, row) {
if (err) { if (err) {
sendJade(req, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: err, recoverErr: err,
loginName: false loginName: false
@ -601,7 +601,7 @@ function handlePasswordRecover(req, res) {
} }
if (row.ip && row.ip !== ip) { if (row.ip && row.ip !== ip) {
sendJade(req, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: "Your IP address does not match the address " + recoverErr: "Your IP address does not match the address " +
"used to submit the reset request. For your " + "used to submit the reset request. For your " +
@ -613,7 +613,7 @@ function handlePasswordRecover(req, res) {
} }
if (Date.now() >= row.expire) { if (Date.now() >= row.expire) {
sendJade(req, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: "This password recovery link has expired. Password " + recoverErr: "This password recovery link has expired. Password " +
"recovery links are valid only for 24 hours after " + "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) { db.users.setPassword(row.name, newpw, function (err) {
if (err) { if (err) {
sendJade(req, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: "Database error. Please contact an administrator if " + recoverErr: "Database error. Please contact an administrator if " +
"this persists.", "this persists.",
@ -641,7 +641,7 @@ function handlePasswordRecover(req, res) {
db.deletePasswordReset(hash); db.deletePasswordReset(hash);
sendJade(req, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: true, recovered: true,
recoverPw: newpw, recoverPw: newpw,
loginName: false loginName: false
@ -663,5 +663,6 @@ module.exports = {
app.post("/account/profile", handleAccountProfile); app.post("/account/profile", handleAccountProfile);
app.get("/account/passwordreset", handlePasswordResetPage); app.get("/account/passwordreset", handlePasswordResetPage);
app.post("/account/passwordreset", handlePasswordReset); app.post("/account/passwordreset", handlePasswordReset);
app.get("/account/passwordrecover/:hash", handlePasswordRecover);
} }
}; };

View file

@ -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 <code>#{recoverPw}</code>. You may now use this password to log in and choose a new password by visiting the <a href="/account/edit">change password/email</a> page.
else
.alert.alert-danger.center.messagebox
strong Password recovery failed
p= recoverErr
include footer
mixin footer()

View file

@ -34,6 +34,7 @@ html(lang="en")
.form-group .form-group
label(for="password") Password label(for="password") Password
input#password.form-control(type="password", name="password") input#password.form-control(type="password", name="password")
a(href="/account/passwordreset") Forgot password?
button.btn.btn-success.btn-block(type="submit") Login button.btn.btn-success.btn-block(type="submit") Login
else else
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3