Merge pull request #496 from javorszky/iss468

Added brute force protection to login
This commit is contained in:
Hannah Wolfe 2013-08-22 14:19:05 -07:00
commit ba8bdf7dd7
3 changed files with 65 additions and 9 deletions

View File

@ -13,7 +13,8 @@ var Ghost = require('../../ghost'),
ghost = new Ghost(),
dataProvider = ghost.dataProvider,
adminNavbar,
adminControllers;
adminControllers,
loginSecurity = [];
// TODO: combine path/navClass to single "slug(?)" variable with no prefix
adminNavbar = {
@ -43,6 +44,7 @@ adminNavbar = {
}
};
// TODO: make this a util or helper
function setSelected(list, name) {
_.each(list, function (item, key) {
@ -93,13 +95,28 @@ adminControllers = {
});
},
'auth': function (req, res) {
api.users.check({email: req.body.email, pw: req.body.password}).then(function (user) {
req.session.user = user.id;
res.json(200, {redirect: req.body.redirect ? '/ghost/'
+ decodeURIComponent(req.body.redirect) : '/ghost/'});
}, function (error) {
res.json(401, {error: error.message});
var currentTime = process.hrtime()[0],
denied = '';
loginSecurity = _.filter(loginSecurity, function (ipTime) {
return (ipTime.time + 2 > currentTime);
});
denied = _.find(loginSecurity, function (ipTime) {
return (ipTime.ip === req.connection.remoteAddress);
});
if (!denied) {
loginSecurity.push({ip: req.connection.remoteAddress, time: process.hrtime()[0]});
api.users.check({email: req.body.email, pw: req.body.password}).then(function (user) {
req.session.user = user.id;
res.json(200, {redirect: req.body.redirect ? '/ghost/'
+ decodeURIComponent(req.body.redirect) : '/ghost/'});
}, function (error) {
res.json(401, {error: error.message});
});
} else {
res.json(401, {error: 'Slow down, there are way too many login attempts!'});
}
},
changepw: function (req, res) {
api.users.changePassword({

View File

@ -1,4 +1,4 @@
/*globals casper, __utils__, url, user */
/*globals casper, __utils__, url, user, falseUser */
casper.test.begin("Ghost admin will load login page", 2, function suite(test) {
casper.test.filename = "admin_test.png";
@ -44,4 +44,39 @@ casper.test.begin("Can login to Ghost", 3, function suite(test) {
casper.run(function () {
test.done();
});
});
});
casper.test.begin("Can't spam it", 2, function suite(test) {
casper.test.filename = "login_test.png";
casper.start(url + "ghost/login/", function testTitle() {
test.assertTitle("", "Ghost admin has no title");
}).viewport(1280, 1024);
casper.waitFor(function checkOpaque() {
return this.evaluate(function () {
var loginBox = document.querySelector('.login-box');
return window.getComputedStyle(loginBox).getPropertyValue('display') === "block"
&& window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1";
});
}, function then() {
this.fill("#login", falseUser, true);
casper.wait(200, function doneWait() {
this.fill("#login", falseUser, true);
});
});
casper.wait(200, function doneWait() {
this.echo("I've waited for 1 seconds.");
});
casper.then(function testForErrorMessage() {
test.assertSelectorHasText('.notification-error', 'Slow down, there are way too many login attempts!');
});
casper.run(function () {
test.done();
});
});

View File

@ -31,6 +31,10 @@ var host = casper.cli.options.host || 'localhost',
email: email,
password: password
},
falseUser = {
email: email,
password: 'letmethrough'
},
testPost = {
title: "Bacon ipsum dolor sit amet",
content: "I am a test post.\n#I have some small content"