mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Added brute force protection to login
Closes half of #468 * adds a 2 second limit until you can retry logging in, otherwise sends you a 401. * bounce: 2ms, checks the pw: 254ms on my machine * added a test to the casper suite
This commit is contained in:
parent
2b94364842
commit
368eb7a352
3 changed files with 65 additions and 9 deletions
|
@ -13,7 +13,8 @@ var Ghost = require('../../ghost'),
|
||||||
ghost = new Ghost(),
|
ghost = new Ghost(),
|
||||||
dataProvider = ghost.dataProvider,
|
dataProvider = ghost.dataProvider,
|
||||||
adminNavbar,
|
adminNavbar,
|
||||||
adminControllers;
|
adminControllers,
|
||||||
|
loginSecurity = [];
|
||||||
|
|
||||||
// TODO: combine path/navClass to single "slug(?)" variable with no prefix
|
// TODO: combine path/navClass to single "slug(?)" variable with no prefix
|
||||||
adminNavbar = {
|
adminNavbar = {
|
||||||
|
@ -43,6 +44,7 @@ adminNavbar = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// TODO: make this a util or helper
|
// TODO: make this a util or helper
|
||||||
function setSelected(list, name) {
|
function setSelected(list, name) {
|
||||||
_.each(list, function (item, key) {
|
_.each(list, function (item, key) {
|
||||||
|
@ -93,6 +95,17 @@ adminControllers = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'auth': function (req, res) {
|
'auth': function (req, res) {
|
||||||
|
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) {
|
api.users.check({email: req.body.email, pw: req.body.password}).then(function (user) {
|
||||||
req.session.user = user.id;
|
req.session.user = user.id;
|
||||||
res.json(200, {redirect: req.body.redirect ? '/ghost/'
|
res.json(200, {redirect: req.body.redirect ? '/ghost/'
|
||||||
|
@ -100,6 +113,10 @@ adminControllers = {
|
||||||
}, function (error) {
|
}, function (error) {
|
||||||
res.json(401, {error: error.message});
|
res.json(401, {error: error.message});
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
res.json(401, {error: 'Slow down, there are way too many login attempts!'});
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
changepw: function (req, res) {
|
changepw: function (req, res) {
|
||||||
api.users.changePassword({
|
api.users.changePassword({
|
||||||
|
|
|
@ -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.begin("Ghost admin will load login page", 2, function suite(test) {
|
||||||
|
|
||||||
casper.test.filename = "admin_test.png";
|
casper.test.filename = "admin_test.png";
|
||||||
|
@ -45,3 +45,38 @@ casper.test.begin("Can login to Ghost", 3, function suite(test) {
|
||||||
test.done();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@ var host = casper.cli.options.host || 'localhost',
|
||||||
email: email,
|
email: email,
|
||||||
password: password
|
password: password
|
||||||
},
|
},
|
||||||
|
falseUser = {
|
||||||
|
email: email,
|
||||||
|
password: 'letmethrough'
|
||||||
|
},
|
||||||
testPost = {
|
testPost = {
|
||||||
title: "Bacon ipsum dolor sit amet",
|
title: "Bacon ipsum dolor sit amet",
|
||||||
content: "I am a test post.\n#I have some small content"
|
content: "I am a test post.\n#I have some small content"
|
||||||
|
|
Loading…
Reference in a new issue