|
|
@@ -2,11 +2,12 @@
|
|
|
handle-callback-err
|
|
|
*/
|
|
|
|
|
|
-const { assert, expect } = require('chai')
|
|
|
+const { expect } = require('chai')
|
|
|
const async = require('async')
|
|
|
+const metrics = require('./helpers/metrics')
|
|
|
const User = require('./helpers/User')
|
|
|
+const UserPromises = require('./helpers/User').promises
|
|
|
const redis = require('./helpers/redis')
|
|
|
-const _ = require('lodash')
|
|
|
const Features = require('../../../app/src/infrastructure/Features')
|
|
|
|
|
|
// Currently this is testing registration via the 'public-registration' module,
|
|
|
@@ -64,55 +65,155 @@ const tryLoginThroughRegistrationForm = function (
|
|
|
|
|
|
describe('Registration', function () {
|
|
|
describe('LoginRateLimit', function () {
|
|
|
+ let userA
|
|
|
beforeEach(function () {
|
|
|
- this.user = new User()
|
|
|
- this.badEmail = 'bademail@example.com'
|
|
|
- this.badPassword = 'badpassword'
|
|
|
+ userA = new UserPromises()
|
|
|
+ })
|
|
|
+ function loginRateLimited(line) {
|
|
|
+ return line.includes('rate_limit_hit') && line.includes('login')
|
|
|
+ }
|
|
|
+ async function getLoginRateLimitHitMetricValue() {
|
|
|
+ return await metrics.promises.getMetric(loginRateLimited)
|
|
|
+ }
|
|
|
+ let beforeCount
|
|
|
+ beforeEach('get baseline metric value', async function () {
|
|
|
+ beforeCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ })
|
|
|
+ beforeEach('setup csrf token', async function () {
|
|
|
+ await userA.getCsrfToken()
|
|
|
})
|
|
|
|
|
|
- it('should rate limit login attempts after 10 within two minutes', function (done) {
|
|
|
- this.user.request.get('/login', (err, res, body) => {
|
|
|
- async.timesSeries(
|
|
|
- 15,
|
|
|
- (n, cb) => {
|
|
|
- this.user.getCsrfToken(error => {
|
|
|
- if (error != null) {
|
|
|
- return cb(error)
|
|
|
- }
|
|
|
- this.user.request.post(
|
|
|
- {
|
|
|
- url: '/login',
|
|
|
- json: {
|
|
|
- email: this.badEmail,
|
|
|
- password: this.badPassword,
|
|
|
- },
|
|
|
- },
|
|
|
- (err, response, body) => {
|
|
|
- const message = body && body.message && body.message.text
|
|
|
- return cb(null, message)
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
- },
|
|
|
- (err, results) => {
|
|
|
- // ten incorrect-credentials messages, then five rate-limit messages
|
|
|
- expect(results.length).to.equal(15)
|
|
|
- assert.deepEqual(
|
|
|
- results,
|
|
|
- _.concat(
|
|
|
- _.fill(
|
|
|
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
|
- 'Your email or password is incorrect. Please try again'
|
|
|
- ),
|
|
|
- _.fill(
|
|
|
- [1, 2, 3, 4, 5],
|
|
|
+ describe('pushing an account just below the rate limit', function () {
|
|
|
+ async function doLoginAttempts(user, n, pushInto) {
|
|
|
+ while (n--) {
|
|
|
+ const { body } = await user.doRequest('POST', {
|
|
|
+ url: '/login',
|
|
|
+ json: {
|
|
|
+ email: user.email,
|
|
|
+ password: 'invalid-password',
|
|
|
+ },
|
|
|
+ })
|
|
|
+ const message = body && body.message && body.message.text
|
|
|
+ pushInto.push(message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let results = []
|
|
|
+ beforeEach('do 9 login attempts', async function () {
|
|
|
+ results = []
|
|
|
+ await doLoginAttempts(userA, 9, results)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not record any rate limited requests', async function () {
|
|
|
+ const afterCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ expect(afterCount).to.equal(beforeCount)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should produce the correct responses so far', function () {
|
|
|
+ expect(results.length).to.equal(9)
|
|
|
+ expect(results).to.deep.equal(
|
|
|
+ Array(9).fill('Your email or password is incorrect. Please try again')
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('pushing the account past the limit', function () {
|
|
|
+ beforeEach('do 6 login attempts', async function () {
|
|
|
+ await doLoginAttempts(userA, 6, results)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should record 5 rate limited requests', async function () {
|
|
|
+ const afterCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ expect(afterCount).to.equal(beforeCount + 5)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should produce the correct responses', function () {
|
|
|
+ expect(results.length).to.equal(15)
|
|
|
+ expect(results).to.deep.equal(
|
|
|
+ Array(10)
|
|
|
+ .fill('Your email or password is incorrect. Please try again')
|
|
|
+ .concat(
|
|
|
+ Array(5).fill(
|
|
|
'This account has had too many login requests. Please wait 2 minutes before trying to log in again'
|
|
|
)
|
|
|
)
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('logging in with another user', function () {
|
|
|
+ let userB
|
|
|
+ beforeEach(function () {
|
|
|
+ userB = new UserPromises()
|
|
|
+ })
|
|
|
+
|
|
|
+ beforeEach('update baseline metric value', async function () {
|
|
|
+ beforeCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ })
|
|
|
+ beforeEach('setup csrf token', async function () {
|
|
|
+ await userB.getCsrfToken()
|
|
|
+ })
|
|
|
+
|
|
|
+ let messages = []
|
|
|
+ beforeEach('do bad login', async function () {
|
|
|
+ messages = []
|
|
|
+ await doLoginAttempts(userB, 1, messages)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not rate limit their request', function () {
|
|
|
+ expect(messages).to.deep.equal([
|
|
|
+ 'Your email or password is incorrect. Please try again',
|
|
|
+ ])
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not record any further rate limited requests', async function () {
|
|
|
+ const afterCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ expect(afterCount).to.equal(beforeCount)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('performing a valid login for clearing the limit', function () {
|
|
|
+ beforeEach('do login', async function () {
|
|
|
+ await userA.login()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should log the user in', async function () {
|
|
|
+ const { response } = await userA.doRequest('GET', '/project')
|
|
|
+ expect(response.statusCode).to.equal(200)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not record any rate limited requests', async function () {
|
|
|
+ const afterCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ expect(afterCount).to.equal(beforeCount)
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('logging out and performing more invalid login requests', function () {
|
|
|
+ beforeEach('logout', async function () {
|
|
|
+ await userA.logout()
|
|
|
+ })
|
|
|
+ beforeEach('fetch new csrf token', async function () {
|
|
|
+ await userA.getCsrfToken()
|
|
|
+ })
|
|
|
+
|
|
|
+ let results = []
|
|
|
+ beforeEach('do 9 login attempts', async function () {
|
|
|
+ results = []
|
|
|
+ await doLoginAttempts(userA, 9, results)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not record any rate limited requests yet', async function () {
|
|
|
+ const afterCount = await getLoginRateLimitHitMetricValue()
|
|
|
+ expect(afterCount).to.equal(beforeCount)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should not emit any rate limited responses yet', function () {
|
|
|
+ expect(results.length).to.equal(9)
|
|
|
+ expect(results).to.deep.equal(
|
|
|
+ Array(9).fill(
|
|
|
+ 'Your email or password is incorrect. Please try again'
|
|
|
+ )
|
|
|
)
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
+ })
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
})
|