login
Version:
Dead simple login processor for express.js
294 lines (263 loc) • 10.5 kB
JavaScript
var bcrypt = require("bcrypt");
var http = require("http");
var url = require("url");
var helpers = require("./helpers");
// Pass in the base couchdb url (assumes database exists)
module.exports = (function (app, couchdb_url, 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";
// CouchDB parsing...
if (!(/\/$/.test(couchdb_url))) {
couchdb_url += "/";
}
var parsed_url = url.parse(couchdb_url);
var port = parsed_url.port || 80;
if (parsed_url.protocol == "https:" && port == 80) { port = 443; }
var http_options = {
port: parsed_url.port,
host: parsed_url.hostname,
headers: []
};
http_options.headers['Content-Type'] = 'application/json';
if (parsed_url.auth) {
http_options.headers["Authorization"] = 'Basic ' + new Buffer(parsed_url.auth).toString('base64');
}
var base_path = parsed_url.pathname;
function CouchDBRequest(path, method) {
this.path = base_path+path;
this.method = (typeof method == 'undefined' ? 'GET' : method);
}
CouchDBRequest.prototype = http_options;
function saveUser(user, onComplete, onError) {
var update = http.request(new CouchDBRequest(user._id, 'PUT'), function (post_response) {
var data = "";
post_response.on("data", function (d) { data += d; });
if (post_response.statusCode == 201) {
onComplete();
} else {
post_response.on("end", function () {
onError(JSON.parse(data));
})
}
});
update.write(JSON.stringify(user));
update.end();
}
function getUser(email, onComplete) {
http.get(new CouchDBRequest(email), function (response) {
if (response.statusCode == 200) {
var data = "";
response.on("data", function (d) { data += d; });
response.on("end", function () {
var user = JSON.parse(data);
onComplete(user);
});
} else {
onComplete(null);
}
});
}
var actions = {
authenticate: function (req, res) {
res.render(__dirname+"/views/authenticate", {layout: __dirname+"/views/layout"})
},
authentication: function (req, res) {
var failed_authentication = function () {
res.render(__dirname+"/views/authenticate", {layout: __dirname+"/views/layout", failed: true, email: req.body.email});
};
if (req.body.email && req.body.password) {
getUser(req.body.email, function (user) {
if (user && bcrypt.compare_sync(req.body.password, user.encrypted_password)) {
var pt = helpers.persistence_token();
user.persistence_token = pt;
user.last_login_at = user.current_login_at;
user.last_login_ip = user.current_login_ip;
user.current_login_at = +(new Date());
user.current_login_ip = req.connection.remoteAddress;
saveUser(user, function () {
req.session.pt = pt;
res.redirect("/");
}, failed_authentication);
} else {
failed_authentication();
}
});
} else {
res.render(__dirname+"/views/authenticate", {layout: __dirname+"/views/layout", failed: true, email: req.body.email})
}
},
forgot_password: function (req, res) {
res.render(__dirname+"/views/forgot_password", {layout: __dirname+"/views/layout"});
},
send_password: function (req, res) {
var failed_reset = function () {
res.render(__dirname+"/views/forgot_password", {layout: __dirname+"/views/layout", error: true});
};
if (req.body.email) {
getUser(req.body.email, function (user) {
var tempkey = helpers.tempkey();
user.perishable_token = tempkey;
user.perishable_token_at = +(new Date());
saveUser(user, function () {
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/"+req.body.email+"/"+tempkey+"\n\nThanks,\nThe OurParents Team"
});
res.render(__dirname+"/views/reset_password", {layout: __dirname+"/views/layout", email: req.body.email})
}, failed_reset);
});
} else {
failed_reset();
}
},
reset_password: function (req, res) {
var redir = function () {
res.redirect("/forgot_password");
};
if (req.params.email && req.params.psk) {
getUser(req.params.email, function (user) {
if (user) {
if (user.last_request_at < (+helpers.yesterday()) || req.params.psk != user.perishable_token) {
redir();
} else {
res.render(__dirname+"/views/update_password", {layout: __dirname+"/views/layout", psk: req.params.psk, email: req.params.email});
}
} else {
redir()
}
});
} else {
redir();
}
},
update_password: function (req, res) {
var redir = function () {
res.redirect("/forgot_password");
};
if (req.body.psk && req.body.email) {
getUser(req.body.email, function (user) {
if (user) {
if (user.last_request_at < (+helpers.yesterday()) || req.body.psk != user.perishable_token) {
redir();
} else 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()
user.encrypted_password = hash;
user.persistence_token = pt;
user.last_login_at = user.current_login_at;
user.last_login_ip = user.current_login_ip;
user.current_login_at = +(new Date());
user.current_login_ip = req.connection.remoteAddress;
saveUser(user, function () {
req.session.pt = pt;
res.render(__dirname+"/views/updated_password", {layout: __dirname+"/views/layout"});
}, redir);
} else {
res.render(__dirname+"/views/update_password", {
layout: __dirname+"/views/layout",
psk: req.params.psk,
email: req.params.email
});
}
} else redir();
});
} else {
redir();
}
},
logout: function (req, res) {
req.session.pt = null;
delete req.session.pt;
res.redirect("/");
}
};
// Basic account maintenance
app.get("/login/login.css", function (req,res) { res.sendfile(__dirname + "/static/login.css"); });
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/:email/:psk", actions.reset_password);
app.post("/reset_password", actions.update_password);
app.get("/logout", actions.logout);
var fubt = (function (pt, cb) {
http.get(new CouchDBRequest("_design/find_user/_view/persistence_token/?key=\""+pt+"\""), function (response) {
data = "";
response.on("data", function (d) { data += d; });
response.on("end", function () {
var user = JSON.parse(data)["rows"][0];
if (user) { user = user.value; }
if (user && user.current_login_at >= (+helpers.session_timeout())) {
cb(user);
} 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) {
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) {
http.get(new CouchDBRequest("_design/find_user"), function (response) {
if (response.statusCode == 404) {
var pt_view = http.request(new CouchDBRequest("_design/find_user", "PUT"), function (response) {
if (response.statusCode == 201) {
console.log("[login.js] Bootstrapped authentication system.");
var default_user = http.request(new CouchDBRequest(email, "PUT"), function (resp) {
if (resp.statusCode == 201)
console.log("[login.js] Bootstrapped default user - "+email);
});
var salt = bcrypt.gen_salt_sync(10);
var hash = bcrypt.encrypt_sync(password, salt);
var user = {
"_id": email,
email: email,
type: 'user',
encrypted_password: hash
};
if (extra_fields) {
for (var field in extra_fields) {
if (extra_fields.hasOwnProperty(field))
user[field] = extra_fields[field];
}
}
default_user.write(JSON.stringify(user));
default_user.end();
}
});
pt_view.write('{"_id":"_design/find_user","language":"javascript","views":{"persistence_token":{"map":"function(doc) { if (doc.type == \'user\') { emit(doc.persistence_token, doc); } }"},"email":{"map":"function(doc) { if (doc.type == \'user\') { emit(doc.email, doc); } }"}}}');
pt_view.end();
}
});
}
};
});