mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Move the spam prevention into its own file.
issue #5286 - Moved the spam prevention functions into their own file - Added unit tests for the functions
This commit is contained in:
parent
e7a078a541
commit
766bf99de9
6 changed files with 395 additions and 155 deletions
|
@ -232,7 +232,7 @@ setupMiddleware = function (blogAppInstance, adminApp) {
|
|||
blogApp = blogAppInstance;
|
||||
middleware.cacheBlogApp(blogApp);
|
||||
middleware.cacheOauthServer(oauthServer);
|
||||
oauth.init(oauthServer, middleware.resetSpamCounter);
|
||||
oauth.init(oauthServer, middleware.spamPrevention.resetCounter);
|
||||
|
||||
// Make sure 'req.secure' is valid for proxied requests
|
||||
// (X-Forwarded-Proto header will be checked, if present)
|
||||
|
|
|
@ -18,13 +18,11 @@ var _ = require('lodash'),
|
|||
|
||||
busboy = require('./ghost-busboy'),
|
||||
cacheControl = require('./cache-control'),
|
||||
spamPrevention = require('./spam-prevention'),
|
||||
|
||||
middleware,
|
||||
blogApp,
|
||||
oauthServer,
|
||||
loginSecurity = [],
|
||||
forgottenSecurity = [],
|
||||
protectedSecurity = [];
|
||||
oauthServer;
|
||||
|
||||
function isBlackListedFileType(file) {
|
||||
var blackListedFileTypes = ['.hbs', '.md', '.json'],
|
||||
|
@ -174,112 +172,6 @@ middleware = {
|
|||
express['static'](path.join(config.paths.themePath, activeTheme.value), {maxAge: utils.ONE_YEAR_MS})(req, res, next);
|
||||
});
|
||||
},
|
||||
// ### Spam prevention Middleware
|
||||
// limit signin requests to ten failed requests per IP per hour
|
||||
spamSigninPrevention: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
deniedRateLimit = '',
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
rateSigninPeriod = config.rateSigninPeriod || 3600,
|
||||
rateSigninAttempts = config.rateSigninAttempts || 10;
|
||||
|
||||
if (req.body.username && req.body.grant_type === 'password') {
|
||||
loginSecurity.push({ip: remoteAddress, time: currentTime, email: req.body.username});
|
||||
} else if (req.body.grant_type === 'refresh_token') {
|
||||
return next();
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No username.'));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateSigninPeriod
|
||||
loginSecurity = _.filter(loginSecurity, function (logTime) {
|
||||
return (logTime.time + rateSigninPeriod > currentTime);
|
||||
});
|
||||
|
||||
// check number of tries per IP address
|
||||
ipCount = _.chain(loginSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateSigninAttempts);
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateSigninAttempts + ' tries per IP address every ' + rateSigninPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
);
|
||||
message += rateSigninPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
return next(new errors.UnauthorizedError(message));
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
// ### Spam prevention Middleware
|
||||
// limit forgotten password requests to five requests per IP per hour for different email addresses
|
||||
// limit forgotten password requests to five requests per email address
|
||||
spamForgottenPrevention: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
rateForgottenPeriod = config.rateForgottenPeriod || 3600,
|
||||
rateForgottenAttempts = config.rateForgottenAttempts || 5,
|
||||
email = req.body.passwordreset[0].email,
|
||||
ipCount = '',
|
||||
deniedRateLimit = '',
|
||||
deniedEmailRateLimit = '',
|
||||
message = 'Too many attempts.',
|
||||
index = _.findIndex(forgottenSecurity, function (logTime) {
|
||||
return (logTime.ip === remoteAddress && logTime.email === email);
|
||||
});
|
||||
|
||||
if (email) {
|
||||
if (index !== -1) {
|
||||
forgottenSecurity[index].count = forgottenSecurity[index].count + 1;
|
||||
} else {
|
||||
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
|
||||
}
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No email.'));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateForgottenPeriod
|
||||
forgottenSecurity = _.filter(forgottenSecurity, function (logTime) {
|
||||
return (logTime.time + rateForgottenPeriod > currentTime);
|
||||
});
|
||||
|
||||
// check number of tries with different email addresses per IP
|
||||
ipCount = _.chain(forgottenSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateForgottenAttempts);
|
||||
|
||||
if (index !== -1) {
|
||||
deniedEmailRateLimit = (forgottenSecurity[index].count > rateForgottenAttempts);
|
||||
}
|
||||
|
||||
if (deniedEmailRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' forgotten password attempts per email every ' +
|
||||
rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' tries per IP address every ' + rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedEmailRateLimit || deniedRateLimit) {
|
||||
message += rateForgottenPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
return next(new errors.UnauthorizedError(message));
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
resetSpamCounter: function (email) {
|
||||
loginSecurity = _.filter(loginSecurity, function (logTime) {
|
||||
return (logTime.email !== email);
|
||||
});
|
||||
},
|
||||
|
||||
// work around to handle missing client_secret
|
||||
// oauth2orize needs it, but untrusted clients don't have it
|
||||
|
@ -402,46 +294,6 @@ middleware = {
|
|||
});
|
||||
},
|
||||
|
||||
spamProtectedPrevention: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
rateProtectedPeriod = config.rateProtectedPeriod || 3600,
|
||||
rateProtectedAttempts = config.rateProtectedAttempts || 10,
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
deniedRateLimit = '',
|
||||
password = req.body.password;
|
||||
|
||||
if (password) {
|
||||
protectedSecurity.push({ip: remoteAddress, time: currentTime});
|
||||
} else {
|
||||
res.error = {
|
||||
message: 'No password entered'
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
// filter entries that are older than rateProtectedPeriod
|
||||
protectedSecurity = _.filter(protectedSecurity, function (logTime) {
|
||||
return (logTime.time + rateProtectedPeriod > currentTime);
|
||||
});
|
||||
|
||||
ipCount = _.chain(protectedSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts);
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateProtectedAttempts + ' tries per IP address every ' + rateProtectedPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
);
|
||||
message += rateProtectedPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
res.error = {
|
||||
message: message
|
||||
};
|
||||
}
|
||||
return next();
|
||||
},
|
||||
|
||||
authenticateProtection: function (req, res, next) {
|
||||
// if errors have been generated from the previous call
|
||||
if (res.error) {
|
||||
|
@ -472,7 +324,8 @@ middleware = {
|
|||
},
|
||||
|
||||
busboy: busboy,
|
||||
cacheControl: cacheControl
|
||||
cacheControl: cacheControl,
|
||||
spamPrevention: spamPrevention
|
||||
};
|
||||
|
||||
module.exports = middleware;
|
||||
|
|
166
core/server/middleware/spam-prevention.js
Normal file
166
core/server/middleware/spam-prevention.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
// # SpamPrevention Middleware
|
||||
// Usage: spamPrevention
|
||||
// After:
|
||||
// Before:
|
||||
// App: Admin|Blog|API
|
||||
//
|
||||
// Helpers to handle spam detection on signin, forgot password, and protected pages.
|
||||
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
loginSecurity = [],
|
||||
forgottenSecurity = [],
|
||||
protectedSecurity = [],
|
||||
spamPrevention;
|
||||
|
||||
spamPrevention = {
|
||||
/*jslint unparam:true*/
|
||||
// limit signin requests to ten failed requests per IP per hour
|
||||
signin: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
deniedRateLimit = '',
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
rateSigninPeriod = config.rateSigninPeriod || 3600,
|
||||
rateSigninAttempts = config.rateSigninAttempts || 10;
|
||||
|
||||
if (req.body.username && req.body.grant_type === 'password') {
|
||||
loginSecurity.push({ip: remoteAddress, time: currentTime, email: req.body.username});
|
||||
} else if (req.body.grant_type === 'refresh_token') {
|
||||
return next();
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No username.'));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateSigninPeriod
|
||||
loginSecurity = _.filter(loginSecurity, function (logTime) {
|
||||
return (logTime.time + rateSigninPeriod > currentTime);
|
||||
});
|
||||
|
||||
// check number of tries per IP address
|
||||
ipCount = _.chain(loginSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateSigninAttempts);
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateSigninAttempts + ' tries per IP address every ' + rateSigninPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
);
|
||||
message += rateSigninPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
return next(new errors.UnauthorizedError(message));
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
// limit forgotten password requests to five requests per IP per hour for different email addresses
|
||||
// limit forgotten password requests to five requests per email address
|
||||
forgotten: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
rateForgottenPeriod = config.rateForgottenPeriod || 3600,
|
||||
rateForgottenAttempts = config.rateForgottenAttempts || 5,
|
||||
email = req.body.passwordreset[0].email,
|
||||
ipCount = '',
|
||||
deniedRateLimit = '',
|
||||
deniedEmailRateLimit = '',
|
||||
message = 'Too many attempts.',
|
||||
index = _.findIndex(forgottenSecurity, function (logTime) {
|
||||
return (logTime.ip === remoteAddress && logTime.email === email);
|
||||
});
|
||||
|
||||
if (email) {
|
||||
if (index !== -1) {
|
||||
forgottenSecurity[index].count = forgottenSecurity[index].count + 1;
|
||||
} else {
|
||||
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
|
||||
}
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No email.'));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateForgottenPeriod
|
||||
forgottenSecurity = _.filter(forgottenSecurity, function (logTime) {
|
||||
return (logTime.time + rateForgottenPeriod > currentTime);
|
||||
});
|
||||
|
||||
// check number of tries with different email addresses per IP
|
||||
ipCount = _.chain(forgottenSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateForgottenAttempts);
|
||||
|
||||
if (index !== -1) {
|
||||
deniedEmailRateLimit = (forgottenSecurity[index].count > rateForgottenAttempts);
|
||||
}
|
||||
|
||||
if (deniedEmailRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' forgotten password attempts per email every ' +
|
||||
rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' tries per IP address every ' + rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedEmailRateLimit || deniedRateLimit) {
|
||||
message += rateForgottenPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
return next(new errors.UnauthorizedError(message));
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
protected: function (req, res, next) {
|
||||
var currentTime = process.hrtime()[0],
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
rateProtectedPeriod = config.rateProtectedPeriod || 3600,
|
||||
rateProtectedAttempts = config.rateProtectedAttempts || 10,
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
deniedRateLimit = '',
|
||||
password = req.body.password;
|
||||
|
||||
if (password) {
|
||||
protectedSecurity.push({ip: remoteAddress, time: currentTime});
|
||||
} else {
|
||||
res.error = {
|
||||
message: 'No password entered'
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
// filter entries that are older than rateProtectedPeriod
|
||||
protectedSecurity = _.filter(protectedSecurity, function (logTime) {
|
||||
return (logTime.time + rateProtectedPeriod > currentTime);
|
||||
});
|
||||
|
||||
ipCount = _.chain(protectedSecurity).countBy('ip').value();
|
||||
deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts);
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateProtectedAttempts + ' tries per IP address every ' + rateProtectedPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
);
|
||||
message += rateProtectedPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
res.error = {
|
||||
message: message
|
||||
};
|
||||
}
|
||||
return next();
|
||||
},
|
||||
|
||||
resetCounter: function (email) {
|
||||
loginSecurity = _.filter(loginSecurity, function (logTime) {
|
||||
return (logTime.email !== email);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = spamPrevention;
|
|
@ -69,7 +69,7 @@ apiRoutes = function (middleware) {
|
|||
|
||||
// ## Authentication
|
||||
router.post('/authentication/passwordreset',
|
||||
middleware.spamForgottenPrevention,
|
||||
middleware.spamPrevention.forgotten,
|
||||
api.http(api.authentication.generateResetToken)
|
||||
);
|
||||
router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
|
||||
|
@ -78,7 +78,7 @@ apiRoutes = function (middleware) {
|
|||
router.post('/authentication/setup', api.http(api.authentication.setup));
|
||||
router.get('/authentication/setup', api.http(api.authentication.isSetup));
|
||||
router.post('/authentication/token',
|
||||
middleware.spamSigninPrevention,
|
||||
middleware.spamPrevention.signin,
|
||||
middleware.addClientSecret,
|
||||
middleware.authenticateClient,
|
||||
middleware.generateAccessToken
|
||||
|
|
|
@ -41,7 +41,7 @@ frontendRoutes = function (middleware) {
|
|||
)
|
||||
.post(
|
||||
middleware.isPrivateSessionAuth,
|
||||
middleware.spamProtectedPrevention,
|
||||
middleware.spamPrevention.protected,
|
||||
middleware.authenticateProtection,
|
||||
frontend.private
|
||||
);
|
||||
|
|
221
core/test/unit/middleware/spam-prevention_spec.js
Normal file
221
core/test/unit/middleware/spam-prevention_spec.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*globals describe, beforeEach, afterEach, it*/
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
middleware = require('../../../server/middleware').middleware;
|
||||
|
||||
describe('Middleware: spamPrevention', function () {
|
||||
var sandbox,
|
||||
req,
|
||||
next,
|
||||
error,
|
||||
spyNext;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
error = null;
|
||||
|
||||
next = sinon.spy();
|
||||
|
||||
spyNext = sinon.spy(function (param) {
|
||||
error = param;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('signin', function () {
|
||||
beforeEach(function () {
|
||||
req = {
|
||||
connection: {
|
||||
remoteAddress: '10.0.0.0'
|
||||
},
|
||||
body: {
|
||||
username: 'tester',
|
||||
grant_type: 'password'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('calls next if refreshing the token', function (done) {
|
||||
req.body.grant_type = 'refresh_token';
|
||||
middleware.spamPrevention.signin(req, null, next);
|
||||
|
||||
next.calledOnce.should.be.true;
|
||||
done();
|
||||
});
|
||||
|
||||
it ('creates a BadRequestError when there\'s no username', function (done) {
|
||||
req.body = {};
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
|
||||
should.exist(error);
|
||||
error.should.be.a.BadRequestError;
|
||||
done();
|
||||
});
|
||||
|
||||
it ('rate limits after 10 attempts', function (done) {
|
||||
for (var ndx = 0; ndx < 10; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
should.exist(error);
|
||||
error.should.be.a.UnauthorizedError;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it ('allows more attempts after an hour', function (done) {
|
||||
var ndx,
|
||||
stub = sinon.stub(process, 'hrtime', function () {
|
||||
return [10, 10];
|
||||
});
|
||||
|
||||
for (ndx = 0; ndx < 10; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
error.should.be.a.UnauthorizedError;
|
||||
error = null;
|
||||
|
||||
// fast forward 1 hour
|
||||
process.hrtime.restore();
|
||||
stub = sinon.stub(process, 'hrtime', function () {
|
||||
return [3700000, 10];
|
||||
});
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
should(error).equal(undefined);
|
||||
spyNext.should.be.calledOnce;
|
||||
|
||||
process.hrtime.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('forgotten', function () {
|
||||
beforeEach(function () {
|
||||
req = {
|
||||
connection: {
|
||||
remoteAddress: '10.0.0.0'
|
||||
},
|
||||
body: {
|
||||
passwordreset: [
|
||||
{email:'test@ghost.org'}
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it ('send a bad request if no email is specified', function (done) {
|
||||
req.body = {
|
||||
passwordreset: [{}]
|
||||
};
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
error.should.be.a.BadRequestError;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it ('creates an unauthorized error after 5 attempts with same email', function (done) {
|
||||
for (var ndx = 0; ndx < 6; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
error.should.be.a.UnauthorizedError;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it ('creates an unauthorized error after 5 attempts from the same ip', function (done) {
|
||||
var ndx, email;
|
||||
|
||||
for (ndx = 0; ndx < 6; ndx = ndx + 1) {
|
||||
email = 'test' + String(ndx) + '@ghost.org';
|
||||
req.body.passwordreset = [
|
||||
{email: email}
|
||||
];
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
error.should.be.a.UnauthorizedError;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('protected', function () {
|
||||
var res;
|
||||
|
||||
beforeEach(function () {
|
||||
res = sinon.spy();
|
||||
req = {
|
||||
connection: {
|
||||
remoteAddress: '10.0.0.0'
|
||||
},
|
||||
body: {
|
||||
password: 'password'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it ('sets an error when there is no password', function (done) {
|
||||
req.body = {};
|
||||
|
||||
middleware.spamPrevention.protected(req, res, spyNext);
|
||||
res.error.message.should.equal('No password entered');
|
||||
spyNext.should.be.calledOnce;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it ('sets and error message after 10 tries', function (done) {
|
||||
var ndx;
|
||||
|
||||
for (ndx = 0; ndx < 10; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.protected(req, res, spyNext);
|
||||
}
|
||||
|
||||
should.not.exist(res.error);
|
||||
middleware.spamPrevention.protected(req, res, spyNext);
|
||||
should.exist(res.error);
|
||||
should.exist(res.error.message);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it ('allows more tries after an hour', function (done) {
|
||||
var ndx,
|
||||
stub = sinon.stub(process, 'hrtime', function () {
|
||||
return [10, 10];
|
||||
});
|
||||
|
||||
for (ndx = 0; ndx < 11; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.protected(req, res, spyNext);
|
||||
}
|
||||
|
||||
should.exist(res.error);
|
||||
process.hrtime.restore();
|
||||
stub = sinon.stub(process, 'hrtime', function () {
|
||||
return [3610000, 10];
|
||||
});
|
||||
|
||||
res = sinon.spy();
|
||||
|
||||
middleware.spamPrevention.protected(req, res, spyNext);
|
||||
should.not.exist(res.error);
|
||||
|
||||
process.hrtime.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue