| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- 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 Features = require('../../../app/src/infrastructure/Features')
- // Expectations
- const expectProjectAccess = function (user, projectId, callback) {
- // should have access to project
- user.openProject(projectId, err => {
- expect(err).to.be.oneOf([null, undefined])
- return callback()
- })
- }
- const expectNoProjectAccess = function (user, projectId, callback) {
- // should not have access to project page
- user.openProject(projectId, err => {
- expect(err).to.be.instanceof(Error)
- return callback()
- })
- }
- // Actions
- const tryLoginThroughRegistrationForm = function (
- user,
- email,
- password,
- callback
- ) {
- user.getCsrfToken(err => {
- if (err != null) {
- return callback(err)
- }
- user.request.post(
- {
- url: '/register',
- json: {
- email,
- password,
- },
- },
- callback
- )
- })
- }
- describe('Registration', function () {
- describe('LoginRateLimit', function () {
- let userA
- beforeEach(function () {
- 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()
- })
- 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'
- )
- )
- })
- })
- })
- })
- })
- describe('CSRF protection', function () {
- before(function () {
- if (!Features.hasFeature('registration')) {
- this.skip()
- }
- })
- beforeEach(function () {
- this.user = new User()
- this.email = `test+${Math.random()}@example.com`
- this.password = 'password11'
- })
- afterEach(function (done) {
- this.user.fullDeleteUser(this.email, done)
- })
- it('should register with the csrf token', function (done) {
- this.user.request.get('/login', (err, res, body) => {
- expect(err).to.not.exist
- this.user.getCsrfToken(error => {
- expect(error).to.not.exist
- this.user.request.post(
- {
- url: '/register',
- json: {
- email: this.email,
- password: this.password,
- },
- headers: {
- 'x-csrf-token': this.user.csrfToken,
- },
- },
- (error, response, body) => {
- expect(error).to.not.exist
- expect(response.statusCode).to.equal(200)
- return done()
- }
- )
- })
- })
- })
- it('should fail with no csrf token', function (done) {
- this.user.request.get('/login', (err, res, body) => {
- expect(err).to.not.exist
- this.user.getCsrfToken(error => {
- expect(error).to.not.exist
- this.user.request.post(
- {
- url: '/register',
- json: {
- email: this.email,
- password: this.password,
- },
- headers: {
- 'x-csrf-token': '',
- },
- },
- (error, response, body) => {
- expect(error).to.not.exist
- expect(response.statusCode).to.equal(403)
- return done()
- }
- )
- })
- })
- })
- it('should fail with a stale csrf token', function (done) {
- this.user.request.get('/login', (err, res, body) => {
- expect(err).to.not.exist
- this.user.getCsrfToken(error => {
- expect(error).to.not.exist
- const oldCsrfToken = this.user.csrfToken
- this.user.logout(err => {
- expect(err).to.not.exist
- this.user.request.post(
- {
- url: '/register',
- json: {
- email: this.email,
- password: this.password,
- },
- headers: {
- 'x-csrf-token': oldCsrfToken,
- },
- },
- (error, response, body) => {
- expect(error).to.not.exist
- expect(response.statusCode).to.equal(403)
- return done()
- }
- )
- })
- })
- })
- })
- })
- describe('Register', function () {
- before(function () {
- if (!Features.hasFeature('registration')) {
- this.skip()
- }
- })
- beforeEach(function () {
- this.user = new User()
- })
- it('Set emails attribute', function (done) {
- this.user.register((error, user) => {
- expect(error).to.not.exist
- user.email.should.equal(this.user.email)
- user.emails.should.exist
- user.emails.should.be.a('array')
- user.emails.length.should.equal(1)
- user.emails[0].email.should.equal(this.user.email)
- return done()
- })
- })
- })
- describe('LoginViaRegistration', function () {
- beforeEach(function (done) {
- this.timeout(60000)
- this.user1 = new User()
- this.user2 = new User()
- async.series(
- [
- cb => this.user1.login(cb),
- cb => this.user1.logout(cb),
- cb => redis.clearUserSessions(this.user1, cb),
- cb => this.user2.login(cb),
- cb => this.user2.logout(cb),
- cb => redis.clearUserSessions(this.user2, cb),
- ],
- done
- )
- this.project_id = null
- })
- describe('[Security] Trying to register/login as another user', function () {
- before(function () {
- if (!Features.hasFeature('registration')) {
- this.skip()
- }
- })
- it('should not allow sign in with secondary email', function (done) {
- const secondaryEmail = 'acceptance-test-secondary@example.com'
- this.user1.addEmail(secondaryEmail, err => {
- expect(err).to.not.exist
- this.user1.loginWith(secondaryEmail, err => {
- expect(err != null).to.equal(false)
- this.user1.isLoggedIn((err, isLoggedIn) => {
- expect(err).to.not.exist
- expect(isLoggedIn).to.equal(false)
- return done()
- })
- })
- })
- })
- it('should have user1 login and create a project, which user2 cannot access', function (done) {
- let projectId
- async.series(
- [
- // user1 logs in and creates a project which only they can access
- cb => {
- this.user1.login(err => {
- expect(err).not.to.exist
- cb()
- })
- },
- cb => {
- this.user1.createProject('Private Project', (err, id) => {
- expect(err).not.to.exist
- projectId = id
- cb()
- })
- },
- cb => expectProjectAccess(this.user1, projectId, cb),
- cb => expectNoProjectAccess(this.user2, projectId, cb),
- // should prevent user2 from login/register with user1 email address
- cb => {
- tryLoginThroughRegistrationForm(
- this.user2,
- this.user1.email,
- 'totally_not_the_right_password',
- (err, response, body) => {
- expect(err).to.not.exist
- expect(body.redir != null).to.equal(false)
- expect(body.message != null).to.equal(true)
- expect(body.message).to.have.all.keys('type', 'text')
- expect(body.message.type).to.equal('error')
- cb()
- }
- )
- },
- // check user still can't access the project
- cb => expectNoProjectAccess(this.user2, projectId, done),
- ],
- done
- )
- })
- })
- })
- })
|