login
Version:
Dead simple login processor for express.js
244 lines (231 loc) • 9.93 kB
JavaScript
var bcrypt = require("bcrypt");
var helpers = require("./helpers");
function h (string) {
return string.replace(/&/g, "&").replace(/\\/g, "\\\\").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
}
module.exports = (function (app, postgresql, postmark, options) {
options = (options || {});
options["app_name"] = options["app_name"] || "Development App";
options["base_url"] = options["base_url"] || "http://127.0.0.1:3000";
options["from"] = options["from"]||"test@example.com";
options["email_key"] = options["email_key"] || "email";
var actions = {
authenticate: function (req, res) {
req.session.failed = false;
req.session.email = null;
res.sendfile(__dirname+"/static/login.html");
},
login_js: function (req, res) {
var str= "$(function () {";
if (req.session.failed) {
str += "$('h2').after('<div class=\"alert-message error\"><p>The username or password you entered is incorrect.</p></div>');"
}
if (req.session.email) {
str += "$('#email').val(\""+h(req.session.email)+"\");"
}
str +=" });"
res.send(str);
},
authentication: function (req, res) {
var failed = function () {
req.session.failed = true;
req.session.email = req.body.email;
res.sendfile(__dirname+"/static/login.html");
}
if (req.body.email && req.body.password) {
postgresql.query("select * from users where LOWER("+options["email_key"]+") = $1 and disabled_at is null and deleted_at is null", [req.body.email.toLowerCase()], function (err, result) {
if (result.rows.length == 1) {
var user = result.rows[0];
if (bcrypt.compare_sync(req.body.password, user.crypted_password)) {
var pt = helpers.persistence_token();
var now = new Date();
var ipAddr = req.connection.remoteAddress;
postgresql.query("update users set persistence_token = $1, current_login_at = $2, last_login_at = $3, last_login_ip = $4, current_login_ip = $5, login_count = $6 where id = $7;", [pt, now, user.current_login_at, user.current_login_ip, ipAddr, (user.login_count || 0) + 1, user.id ], function (err, update_res) {
if (!err) {
req.session.pt = pt;
if (req.session.previous_url) {
var purl = req.session.previous_url;
delete req.session.previous_url;
res.redirect(purl);
} else {
res.redirect("/");
}
} else {
failed();
}
});
} else {
failed();
}
} else {
failed();
}
})
} else {
failed();
}
},
forgot_password: function (req, res) {
req.session.failed = false;
res.sendfile(__dirname+"/static/forgot.html");
},
forgot_js: function (req, res) {
var str= "$(function () {";
if (req.session.failed) {
str += "$('h2').after('<div class=\"notice\">Could not find account with that email address.<br>Please try again.</div>');"
}
if (req.session.email) {
str += "$('#email').val(\""+h(req.session.email)+"\");"
}
str +=" });"
res.send(str);
},
send_password: function (req, res) {
req.session.failed = false;
req.session.email = null;
if (req.body.email) {
req.session.email = req.body.email;
postgresql.query("select * from users where "+options["email_key"]+" = $1", [req.body.email], function (err, result) {
if (result.rows.length == 1) {
var user = result.rows[0];
var tempkey = helpers.tempkey();
postgresql.query("update users set perishable_token = $1, last_request_at = NOW() where id = $2;", [tempkey, user.id], function (err, update_res) {
postmark.send({
"From": options.from,
"To": req.body.email,
"Subject": options.app_name + " Password Reset Request",
"TextBody": "Hey, we heard you lost your "+options.app_name+" password. Say it ain't so!\n\nUse the following link within the next 24 hours to reset your password:\n\n" + options.base_url + "/login/reset/"+tempkey+"\n\nThanks,\nThe "+options.app_name+" Team"
});
res.sendfile(__dirname+"/static/sent.html");
})
} else {
req.session.failed = true;
res.redirect("/forgot_password");
}
});
} else {
req.session.failed = true;
res.redirect("/forgot_password");
}
},
sent_js: function (req, res) {
var str= "$(function () {";
if (req.session.email) {
str += "$('#email').text(\""+h(req.session.email)+"\");"
}
str +=" });"
res.send(str);
},
reset_password: function (req, res) {
req.session.psk = null;
if (req.params.psk) {
postgresql.query("select * from users where perishable_token = $1 and last_request_at >= $2", [req.params.psk, helpers.yesterday()], function (err, result) {
if (result.rows && result.rows.length ==1) {
req.session.email = result.rows[0][options["email_key"]];
req.session.psk = req.params.psk;
res.sendfile(__dirname+"/static/reset.html");
} else {
res.redirect("/forgot_password");
}
});
} else {
res.redirect("/forgot_password");
}
},
reset_js: function (req, res) {
var str= "$(function () {";
str += "$('#email').val(\""+h(req.session.email)+"\");"
str += "$('#psk').val(\""+h(req.session.psk)+"\");"
str +=" });"
res.send(str);
},
update_password: function (req, res) {
if (req.body.psk) {
if (req.body.password && req.body.password.length > 6 && req.body.password.length < 200 && req.body.password == req.body.password_confirmation) {
var salt = bcrypt.gen_salt_sync(10);
var hash = bcrypt.encrypt_sync(req.body.password, salt);
var pt = helpers.persistence_token()
postgresql.query("update users set crypted_password = $1, persistence_token = $4 where perishable_token = $2 and last_request_at >= $3;", [hash, req.body.psk, helpers.yesterday(), pt], function (err, update_res) {
req.session.pt = pt;
res.sendfile(__dirname+"/static/updated.html");
});
} else {
res.redirect("/login/reset/"+h(req.body.psk))
}
} else {
res.redirect("/forgot_password");
}
},
logout: function (req, res) {
req.session.pt = null;
delete req.session.pt;
res.redirect("/");
}
};
app.get("/login/buttons.png", function (req, res) { res.sendfile(__dirname+"/static/buttons.png"); });
app.get("/login/login.css", function (req,res) { res.sendfile(__dirname + "/static/login.css"); });
app.get("/login/login.js", actions.login_js);
app.get("/login/forgot.js", actions.forgot_js);
app.get("/login/reset.js", actions.reset_js);
app.get("/login/sent.js", actions.sent_js);
// Basic account maintenance
app.get("/authenticate", actions.authenticate);
app.post("/authenticate", actions.authentication);
app.get("/forgot_password", actions.forgot_password);
app.post("/forgot_password", actions.send_password);
app.get("/login/reset/:psk", actions.reset_password);
app.post("/reset_password", actions.update_password);
app.get("/logout", actions.logout);
var fubt = (function (pt, cb) {
postgresql.query("select * from users where persistence_token = $1 and current_login_at >= $2", [pt, helpers.session_timeout()], function (err, result) {
if (result.rows && result.rows[0])
cb(result.rows[0]);
else
cb(null);
})
});
return {
find_user_by_token: fubt,
load_user: function (req, res, next) {
if (req.session.pt) {
fubt(req.session.pt, function (user) {
if (user) { req.user = user }
next();
});
} else {
next();
}
},
require_user: function (req, res, next) {
req.session.previous_url = req.url;
if (req.session.pt) {
fubt(req.session.pt, function (user) {
if (user) {
req.user = user;
next();
} else {
res.redirect("/authenticate");
}
});
} else {
res.redirect("/authenticate");
}
},
bootstrap: function (email, password, extra_fields) {
if (extra_fields) {
if (!(/$,/.test(extra_fields))) {
extra_fields = ", "+extra_fields;
}
} else {
extra_fields = "";
}
postgresql.query('CREATE TABLE IF NOT EXISTS "users" ( "id" int4 NOT NULL DEFAULT nextval(\'users_id_seq\'::regclass), "email" varchar(255) NOT NULL DEFAULT NULL, "crypted_password" varchar(255) NOT NULL DEFAULT NULL, "persistence_token" varchar(255) NOT NULL DEFAULT NULL, "perishable_token" varchar(255) NOT NULL DEFAULT NULL, "login_count" int4 NOT NULL DEFAULT 0, "last_request_at" timestamp(6) NULL DEFAULT NULL, "current_login_at" timestamp(6) NULL DEFAULT NULL, "last_login_at" timestamp(6) NULL DEFAULT NULL, "current_login_ip" varchar(255) DEFAULT NULL, "last_login_ip" varchar(255) DEFAULT NULL'+extra_fields+') WITH (OIDS=FALSE);', function (err, result) {
var salt = bcrypt.gen_salt_sync(10);
var hash = bcrypt.encrypt_sync(password, salt);
postgresql.query("insert into users (email, crypted_password) VALUES ($1, $2)", [email, hash], function (err1, res2) {
console.log("Bootstrapped user authentication system");
});
});
}
};
});