mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Merge pull request #3307 from TryGhost/revert-3282-issue#3128
Revert "Restore spam prevention"
This commit is contained in:
commit
d77f61b556
6 changed files with 112 additions and 174 deletions
|
@ -245,15 +245,7 @@ errors = {
|
||||||
}
|
}
|
||||||
errors.renderErrorPage(err.status || 500, err, req, res, next);
|
errors.renderErrorPage(err.status || 500, err, req, res, next);
|
||||||
} else {
|
} else {
|
||||||
// generate a valid JSON response
|
res.send(err.status || 500, err);
|
||||||
var statusCode = 500,
|
|
||||||
errorContent = {};
|
|
||||||
|
|
||||||
statusCode = err.code || 500;
|
|
||||||
|
|
||||||
errorContent.message = _.isString(err) ? err : (_.isObject(err) ? err.message : 'Unknown Error');
|
|
||||||
errorContent.type = err.type || 'InternalServerError';
|
|
||||||
res.json(statusCode, errorContent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,13 +9,11 @@ var _ = require('lodash'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
api = require('../api'),
|
api = require('../api'),
|
||||||
passport = require('passport'),
|
passport = require('passport'),
|
||||||
errors = require('../errors'),
|
|
||||||
|
|
||||||
expressServer,
|
expressServer,
|
||||||
oauthServer,
|
oauthServer,
|
||||||
ONE_HOUR_MS = 60 * 60 * 1000,
|
ONE_HOUR_MS = 60 * 60 * 1000,
|
||||||
ONE_YEAR_MS = 365 * 24 * ONE_HOUR_MS,
|
ONE_YEAR_MS = 365 * 24 * ONE_HOUR_MS;
|
||||||
loginSecurity = [];
|
|
||||||
|
|
||||||
function isBlackListedFileType(file) {
|
function isBlackListedFileType(file) {
|
||||||
var blackListedFileTypes = ['.hbs', '.md', '.json'],
|
var blackListedFileTypes = ['.hbs', '.md', '.json'],
|
||||||
|
@ -151,31 +149,6 @@ var middleware = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### Spam prevention Middleware
|
|
||||||
// limit signin requests to one every two seconds
|
|
||||||
spamPrevention: function (req, res, next) {
|
|
||||||
var currentTime = process.hrtime()[0],
|
|
||||||
remoteAddress = req.connection.remoteAddress,
|
|
||||||
denied = '';
|
|
||||||
|
|
||||||
// filter for IPs that tried to login in the last 2 sec
|
|
||||||
loginSecurity = _.filter(loginSecurity, function (logTime) {
|
|
||||||
return (logTime.time + 2 > currentTime);
|
|
||||||
});
|
|
||||||
|
|
||||||
// check if IP tried to login in the last 2 sec
|
|
||||||
denied = _.find(loginSecurity, function (logTime) {
|
|
||||||
return (logTime.ip === remoteAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!denied) {
|
|
||||||
loginSecurity.push({ip: remoteAddress, time: currentTime});
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
return next(new errors.UnauthorizedError('Slow down, there are way too many login attempts!'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// work around to handle missing client_secret
|
// work around to handle missing client_secret
|
||||||
// oauth2orize needs it, but untrusted clients don't have it
|
// oauth2orize needs it, but untrusted clients don't have it
|
||||||
addClientSecret: function (req, res, next) {
|
addClientSecret: function (req, res, next) {
|
||||||
|
@ -184,15 +157,9 @@ var middleware = {
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### Authenticate Client Middleware
|
|
||||||
// authenticate client that is asking for an access token
|
|
||||||
authenticateClient: function (req, res, next) {
|
authenticateClient: function (req, res, next) {
|
||||||
return passport.authenticate(['oauth2-client-password'], { session: false })(req, res, next);
|
return passport.authenticate(['oauth2-client-password'], { session: false })(req, res, next);
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### Generate access token Middleware
|
|
||||||
// register the oauth2orize middleware for password and refresh token grants
|
|
||||||
generateAccessToken: function (req, res, next) {
|
generateAccessToken: function (req, res, next) {
|
||||||
return oauthServer.token()(req, res, next);
|
return oauthServer.token()(req, res, next);
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,8 +43,8 @@ oauth = {
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
return done(null, false);
|
return done(null, false);
|
||||||
});
|
});
|
||||||
}).catch(function (error) {
|
}).catch(function () {
|
||||||
return done(error);
|
return done(null, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -270,16 +270,15 @@ User = ghostBookshelf.Model.extend({
|
||||||
var self = this,
|
var self = this,
|
||||||
s;
|
s;
|
||||||
return this.getByEmail(object.email).then(function (user) {
|
return this.getByEmail(object.email).then(function (user) {
|
||||||
if (!user || user.get('status') === 'invited' || user.get('status') === 'invited-pending'
|
if (!user || user.get('status') === 'invited' || user.get('status') === 'inactive') {
|
||||||
|| user.get('status') === 'inactive') {
|
return when.reject(new Error('NotFound'));
|
||||||
return when.reject(new errors.NotFoundError('NotFound'));
|
|
||||||
}
|
}
|
||||||
if (user.get('status') !== 'locked') {
|
if (user.get('status') !== 'locked') {
|
||||||
return nodefn.call(bcrypt.compare, object.password, user.get('password')).then(function (matched) {
|
return nodefn.call(bcrypt.compare, object.password, user.get('password')).then(function (matched) {
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
return when(self.setWarning(user)).then(function (remaining) {
|
return when(self.setWarning(user)).then(function (remaining) {
|
||||||
s = (remaining > 1) ? 's' : '';
|
s = (remaining > 1) ? 's' : '';
|
||||||
return when.reject(new errors.UnauthorizedError('Your password is incorrect.<br>' +
|
return when.reject(new Error('Your password is incorrect.<br>' +
|
||||||
remaining + ' attempt' + s + ' remaining!'));
|
remaining + ' attempt' + s + ' remaining!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -289,7 +288,7 @@ User = ghostBookshelf.Model.extend({
|
||||||
});
|
});
|
||||||
}, errors.logAndThrowError);
|
}, errors.logAndThrowError);
|
||||||
}
|
}
|
||||||
return when.reject(new errors.NoPermissionError('Your account is locked due to too many ' +
|
return when.reject(new Error('Your account is locked due to too many ' +
|
||||||
'login attempts. Please reset your password to log in again by clicking ' +
|
'login attempts. Please reset your password to log in again by clicking ' +
|
||||||
'the "Forgotten password?" link!'));
|
'the "Forgotten password?" link!'));
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ apiRoutes = function (middleware) {
|
||||||
router.post('/authentication/setup', api.http(api.authentication.setup));
|
router.post('/authentication/setup', api.http(api.authentication.setup));
|
||||||
router.get('/authentication/setup', api.http(api.authentication.isSetup));
|
router.get('/authentication/setup', api.http(api.authentication.isSetup));
|
||||||
router.post('/authentication/token',
|
router.post('/authentication/token',
|
||||||
middleware.spamPrevention,
|
|
||||||
middleware.addClientSecret,
|
middleware.addClientSecret,
|
||||||
middleware.authenticateClient,
|
middleware.authenticateClient,
|
||||||
middleware.generateAccessToken
|
middleware.generateAccessToken
|
||||||
|
|
|
@ -4,35 +4,35 @@
|
||||||
/*globals CasperTest, casper, url, newUser, user, falseUser */
|
/*globals CasperTest, casper, url, newUser, user, falseUser */
|
||||||
|
|
||||||
// TODO fix signup vs setup testing
|
// TODO fix signup vs setup testing
|
||||||
CasperTest.begin('Ensure a User is Registered', 3, function suite(test) {
|
//CasperTest.begin('Ensure a User is Registered', 3, function suite(test) {
|
||||||
casper.thenOpenAndWaitForPageLoad('setup', function checkUrl() {
|
// casper.thenOpenAndWaitForPageLoad('signup', function checkUrl() {
|
||||||
test.assertUrlMatch(/ghost\/setup\/$/, 'Landed on the correct URL');
|
// test.assertUrlMatch(/ghost\/signup\/$/, 'Landed on the correct URL');
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
casper.waitForOpaque('.setup-box',
|
// casper.waitForOpaque('.signup-box',
|
||||||
function then() {
|
// function then() {
|
||||||
this.fillAndAdd('#setup', newSetup);
|
// this.fillAndSave('#signup', newUser);
|
||||||
},
|
// },
|
||||||
function onTimeout() {
|
// function onTimeout() {
|
||||||
test.fail('Set up form didn\'t fade in.');
|
// test.fail('Sign up form didn\'t fade in.');
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
casper.captureScreenshot('login_register_test.png');
|
// casper.captureScreenshot('login_register_test.png');
|
||||||
|
//
|
||||||
casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||||
test.assertSelectorHasText('.notification-error', 'already registered');
|
// test.assertSelectorHasText('.notification-error', 'already registered');
|
||||||
// If the previous assert succeeds, then we should skip the next check and just pass.
|
// // If the previous assert succeeds, then we should skip the next check and just pass.
|
||||||
casper.echoConcise('Already registered!');
|
// casper.echoConcise('Already registered!');
|
||||||
casper.captureScreenshot('already_registered.png');
|
// casper.captureScreenshot('already_registered.png');
|
||||||
}, function onTimeout() {
|
// }, function onTimeout() {
|
||||||
test.assertUrlMatch(/ghost\/\d+\/$/, 'If we\'re not already registered, we should be logged in.');
|
// test.assertUrlMatch(/ghost\/\d+\/$/, 'If we\'re not already registered, we should be logged in.');
|
||||||
casper.echoConcise('Successfully registered.');
|
// casper.echoConcise('Successfully registered.');
|
||||||
}, 2000);
|
// }, 2000);
|
||||||
|
//
|
||||||
casper.thenOpenAndWaitForPageLoad('signout', function then() {
|
// casper.thenOpenAndWaitForPageLoad('signout', function then() {
|
||||||
test.assertUrlMatch(/ghost\/signin/, 'We got redirected to signin page.');
|
// test.assertUrlMatch(/ghost\/signin/, 'We got redirected to signin page.');
|
||||||
});
|
// });
|
||||||
}, true);
|
//}, true);
|
||||||
|
|
||||||
CasperTest.begin('Ghost admin will load login page', 3, function suite(test) {
|
CasperTest.begin('Ghost admin will load login page', 3, function suite(test) {
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
||||||
|
@ -59,84 +59,73 @@ CasperTest.begin('Redirects login to signin', 2, function suite(test) {
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// TODO: please uncomment when the spam prevention bug is fixed (https://github.com/TryGhost/Ghost/issues/3128)
|
||||||
|
// CasperTest.begin('Can\'t spam it', 4, function suite(test) {
|
||||||
|
// casper.thenOpenAndWaitForPageLoad('signin', function testTitle() {
|
||||||
|
// test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
||||||
|
// test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
||||||
|
// });
|
||||||
|
|
||||||
CasperTest.begin('Can\'t spam it', 4, function suite(test) {
|
// casper.waitForOpaque('.login-box',
|
||||||
// init user to prevent redirect to setup
|
// function then() {
|
||||||
CasperTest.Routines.setup.run(test);
|
// this.fillAndSave('#login', falseUser);
|
||||||
CasperTest.Routines.signout.run(test);
|
// },
|
||||||
|
// function onTimeout() {
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitle() {
|
// test.fail('Sign in form didn\'t fade in.');
|
||||||
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
// });
|
||||||
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
|
||||||
});
|
|
||||||
|
|
||||||
casper.waitForOpaque('.login-box',
|
|
||||||
function then() {
|
|
||||||
this.fillAndSave('#login', falseUser);
|
|
||||||
},
|
|
||||||
function onTimeout() {
|
|
||||||
test.fail('Sign in form didn\'t fade in.');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
casper.captureScreenshot('login_spam_test.png');
|
// casper.captureScreenshot('login_spam_test.png');
|
||||||
|
|
||||||
casper.waitForText('attempts remaining!', function then() {
|
// casper.waitForText('attempts remaining!', function then() {
|
||||||
this.fillAndSave('#login', falseUser);
|
// this.fillAndSave('#login', falseUser);
|
||||||
});
|
// });
|
||||||
|
|
||||||
casper.captureScreenshot('login_spam_test2.png');
|
// casper.captureScreenshot('login_spam_test2.png');
|
||||||
|
|
||||||
casper.waitForText('Slow down, there are way too many login attempts!', function onSuccess() {
|
// casper.waitForText('Slow down, there are way too many login attempts!', function onSuccess() {
|
||||||
test.assert(true, 'Spamming the login did result in an error notification');
|
// test.assert(true, 'Spamming the login did result in an error notification');
|
||||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
// test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||||
}, function onTimeout() {
|
// }, function onTimeout() {
|
||||||
test.assert(false, 'Spamming the login did not result in an error notification');
|
// test.assert(false, 'Spamming the login did not result in an error notification');
|
||||||
});
|
// });
|
||||||
|
|
||||||
// This test causes the spam notification
|
// // This test causes the spam notification
|
||||||
// add a wait to ensure future tests don't get tripped up by this.
|
// // add a wait to ensure future tests don't get tripped up by this.
|
||||||
casper.wait(2000);
|
// casper.wait(2000);
|
||||||
}, true);
|
// }, true);
|
||||||
|
|
||||||
CasperTest.begin('Login limit is in place', 4, function suite(test) {
|
// TODO: please uncomment when the spam prevention bug is fixed (https://github.com/TryGhost/Ghost/issues/3128)
|
||||||
// init user to prevent redirect to setup
|
// CasperTest.begin('Login limit is in place', 4, function suite(test) {
|
||||||
CasperTest.Routines.setup.run(test);
|
// casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
||||||
CasperTest.Routines.signout.run(test);
|
// test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
||||||
|
// test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
||||||
|
// });
|
||||||
|
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
// casper.waitForOpaque('.login-box',
|
||||||
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
// function then() {
|
||||||
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
// this.fillAndSave('#login', falseUser);
|
||||||
});
|
// },
|
||||||
|
// function onTimeout() {
|
||||||
|
// test.fail('Sign in form didn\'t fade in.');
|
||||||
|
// });
|
||||||
|
|
||||||
casper.waitForOpaque('.login-box',
|
// casper.wait(2100, function doneWait() {
|
||||||
function then() {
|
// this.fillAndSave('#login', falseUser);
|
||||||
this.fillAndSave('#login', falseUser);
|
// });
|
||||||
},
|
|
||||||
function onTimeout() {
|
|
||||||
test.fail('Sign in form didn\'t fade in.');
|
|
||||||
});
|
|
||||||
|
|
||||||
casper.wait(2100, function doneWait() {
|
// casper.waitForText('remaining', function onSuccess() {
|
||||||
this.fillAndSave('#login', falseUser);
|
// test.assert(true, 'The login limit is in place.');
|
||||||
});
|
// test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||||
|
// }, function onTimeout() {
|
||||||
casper.waitForText('remaining', function onSuccess() {
|
// test.assert(false, 'We did not trip the login limit.');
|
||||||
test.assert(true, 'The login limit is in place.');
|
// });
|
||||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
// // This test used login, add a wait to
|
||||||
}, function onTimeout() {
|
// // ensure future tests don't get tripped up by this.
|
||||||
test.assert(false, 'We did not trip the login limit.');
|
// casper.wait(2000);
|
||||||
});
|
// }, true);
|
||||||
// This test used login, add a wait to
|
|
||||||
// ensure future tests don't get tripped up by this.
|
|
||||||
casper.wait(2000);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
CasperTest.begin('Can login to Ghost', 5, function suite(test) {
|
CasperTest.begin('Can login to Ghost', 5, function suite(test) {
|
||||||
// init user
|
|
||||||
CasperTest.Routines.setup.run(test);
|
|
||||||
CasperTest.Routines.signout.run(test);
|
|
||||||
|
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
||||||
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
||||||
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
||||||
|
@ -158,10 +147,6 @@ CasperTest.begin('Can login to Ghost', 5, function suite(test) {
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
CasperTest.begin('Authenticated user is redirected', 8, function suite(test) {
|
CasperTest.begin('Authenticated user is redirected', 8, function suite(test) {
|
||||||
// init user
|
|
||||||
CasperTest.Routines.setup.run(test);
|
|
||||||
CasperTest.Routines.signout.run(test);
|
|
||||||
|
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
||||||
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
||||||
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
||||||
|
@ -190,31 +175,27 @@ CasperTest.begin('Authenticated user is redirected', 8, function suite(test) {
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// TODO: please uncomment when the validation problem is fixed (https://github.com/TryGhost/Ghost/issues/3120)
|
||||||
|
// CasperTest.begin('Ensure email field form validation', 3, function suite(test) {
|
||||||
|
// casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
||||||
|
// test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
||||||
|
// test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
||||||
|
// });
|
||||||
|
|
||||||
CasperTest.begin('Ensure email field form validation', 3, function suite(test) {
|
// casper.waitForOpaque('.js-login-box',
|
||||||
// init user to prevent redirect to setup
|
// function then() {
|
||||||
CasperTest.Routines.setup.run(test);
|
// this.fillAndSave('form.login-form', {
|
||||||
CasperTest.Routines.signout.run(test);
|
// 'email': 'notanemail'
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// function onTimeout() {
|
||||||
|
// test.fail('Login form didn\'t fade in.');
|
||||||
|
// });
|
||||||
|
|
||||||
casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() {
|
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||||
test.assertTitle('Ghost Admin', 'Ghost admin has no title');
|
// test.assertSelectorHasText('.notification-error', 'Invalid Email');
|
||||||
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
|
// }, function onTimeout() {
|
||||||
});
|
// test.fail('Email validation error did not appear');
|
||||||
|
// }, 2000);
|
||||||
|
|
||||||
casper.waitForOpaque('.js-login-box',
|
// }, true);
|
||||||
function then() {
|
|
||||||
this.fillAndSave('form.login-form', {
|
|
||||||
'identification': 'notanemail'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function onTimeout() {
|
|
||||||
test.fail('Login form didn\'t fade in.');
|
|
||||||
});
|
|
||||||
|
|
||||||
casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
|
||||||
test.assertSelectorHasText('.notification-error', 'Invalid Email');
|
|
||||||
}, function onTimeout() {
|
|
||||||
test.fail('Email validation error did not appear');
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
}, true);
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue