mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] Promisify RegistrationTests and SessionTests (#30646)
* Promisify RegistrationTests.mjs
* Re-add `await this.user.request.get('/login')` and remove comment
* Promisify SessionTests.mjs
* Remove callback versions of RedisHelper
* Re-add `await this.user.request.get('/login')`
GitOrigin-RevId: 9d0e21336f348c6aff99cb262738163e611f5712
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import async from 'async'
|
||||
import metrics from './helpers/metrics.mjs'
|
||||
import User from './helpers/User.mjs'
|
||||
import redis from './helpers/redis.mjs'
|
||||
@@ -8,43 +7,28 @@ import Features from '../../../app/src/infrastructure/Features.mjs'
|
||||
const UserPromises = User.promises
|
||||
|
||||
// Expectations
|
||||
const expectProjectAccess = function (user, projectId, callback) {
|
||||
async function expectProjectAccess(user, projectId) {
|
||||
// should have access to project
|
||||
user.openProject(projectId, err => {
|
||||
expect(err).to.be.oneOf([null, undefined])
|
||||
return callback()
|
||||
})
|
||||
await user.openProject(projectId)
|
||||
}
|
||||
|
||||
const expectNoProjectAccess = function (user, projectId, callback) {
|
||||
async function expectNoProjectAccess(user, projectId) {
|
||||
// should not have access to project page
|
||||
user.openProject(projectId, err => {
|
||||
expect(err).to.be.instanceof(Error)
|
||||
return callback()
|
||||
})
|
||||
let error = null
|
||||
try {
|
||||
await user.openProject(projectId)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error).to.be.instanceof(Error)
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
async function tryLoginThroughRegistrationForm(user, email, password) {
|
||||
await user.getCsrfToken()
|
||||
return await user.doRequest('POST', {
|
||||
url: '/register',
|
||||
json: { email, password },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -204,95 +188,63 @@ describe('Registration', function () {
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
this.user = new User()
|
||||
this.user = new UserPromises()
|
||||
this.email = `test+${Math.random()}@example.com`
|
||||
this.password = 'password11'
|
||||
})
|
||||
|
||||
afterEach(function (done) {
|
||||
this.user.fullDeleteUser(this.email, done)
|
||||
afterEach(async function () {
|
||||
await this.user.fullDeleteUser(this.email)
|
||||
})
|
||||
|
||||
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 register with the csrf token', async function () {
|
||||
await this.user.request.get('/login')
|
||||
await this.user.getCsrfToken()
|
||||
const { response } = await this.user.doRequest('POST', {
|
||||
url: '/register',
|
||||
json: {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
},
|
||||
headers: {
|
||||
'x-csrf-token': this.user.csrfToken,
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(200)
|
||||
})
|
||||
|
||||
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 no csrf token', async function () {
|
||||
await this.user.request.get('/login')
|
||||
await this.user.getCsrfToken()
|
||||
const { response } = await this.user.doRequest('POST', {
|
||||
url: '/register',
|
||||
json: {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
},
|
||||
headers: {
|
||||
'x-csrf-token': '',
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(403)
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
it('should fail with a stale csrf token', async function () {
|
||||
await this.user.request.get('/login')
|
||||
await this.user.getCsrfToken()
|
||||
const oldCsrfToken = this.user.csrfToken
|
||||
await this.user.logout()
|
||||
const { response } = await this.user.doRequest('POST', {
|
||||
url: '/register',
|
||||
json: {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
},
|
||||
headers: {
|
||||
'x-csrf-token': oldCsrfToken,
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(403)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -304,38 +256,32 @@ describe('Registration', function () {
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
this.user = new User()
|
||||
this.user = new UserPromises()
|
||||
})
|
||||
|
||||
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()
|
||||
})
|
||||
it('Set emails attribute', async function () {
|
||||
const user = await this.user.register()
|
||||
expect(user.email).to.equal(this.user.email)
|
||||
expect(user.emails).to.exist
|
||||
expect(user.emails).to.be.a('array')
|
||||
expect(user.emails.length).to.equal(1)
|
||||
expect(user.emails[0].email).to.equal(this.user.email)
|
||||
})
|
||||
})
|
||||
|
||||
describe('LoginViaRegistration', function () {
|
||||
beforeEach(function (done) {
|
||||
beforeEach(async function () {
|
||||
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.user1 = new UserPromises()
|
||||
this.user2 = new UserPromises()
|
||||
|
||||
await this.user1.login()
|
||||
await this.user1.logout()
|
||||
await redis.clearUserSessions(this.user1)
|
||||
await this.user2.login()
|
||||
await this.user2.logout()
|
||||
await redis.clearUserSessions(this.user2)
|
||||
|
||||
this.project_id = null
|
||||
})
|
||||
|
||||
@@ -346,68 +292,46 @@ describe('Registration', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('should not allow sign in with secondary email', function (done) {
|
||||
it('should not allow sign in with secondary email', async function () {
|
||||
const secondaryEmail = 'acceptance-test-secondary@example.com'
|
||||
this.user1.addEmail(secondaryEmail, err => {
|
||||
expect(err).to.not.exist
|
||||
this.user1.loginWith(secondaryEmail, err => {
|
||||
expect(err).to.match(/login failed: status=401/)
|
||||
expect(err.info.body).to.deep.equal({
|
||||
message: {
|
||||
type: 'error',
|
||||
key: 'invalid-password-retry-or-reset',
|
||||
},
|
||||
})
|
||||
this.user1.isLoggedIn((err, isLoggedIn) => {
|
||||
expect(err).to.not.exist
|
||||
expect(isLoggedIn).to.equal(false)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
await this.user1.addEmail(secondaryEmail)
|
||||
let error = null
|
||||
try {
|
||||
await this.user1.loginWith(secondaryEmail)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error).to.be.instanceOf(Error)
|
||||
expect(error.message).to.match(/login failed: status=401/)
|
||||
expect(error.info.body).to.deep.equal({
|
||||
message: {
|
||||
type: 'error',
|
||||
key: 'invalid-password-retry-or-reset',
|
||||
},
|
||||
})
|
||||
|
||||
const isLoggedIn = await this.user1.isLoggedIn()
|
||||
expect(isLoggedIn).to.equal(false)
|
||||
})
|
||||
|
||||
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
|
||||
it('should have user1 login and create a project, which user2 cannot access', async function () {
|
||||
// user1 logs in and creates a project which only they can access
|
||||
await this.user1.login()
|
||||
const projectId = await this.user1.createProject('Private Project')
|
||||
await expectProjectAccess(this.user1, projectId)
|
||||
await expectNoProjectAccess(this.user2, projectId)
|
||||
// should prevent user2 from login/register with user1 email address
|
||||
const { body } = await tryLoginThroughRegistrationForm(
|
||||
this.user2,
|
||||
this.user1.email,
|
||||
'totally_not_the_right_password'
|
||||
)
|
||||
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')
|
||||
// check user still can't access the project
|
||||
await expectNoProjectAccess(this.user2, projectId)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,558 +1,286 @@
|
||||
import { expect } from 'chai'
|
||||
import async from 'async'
|
||||
import { setTimeout } from 'node:timers/promises'
|
||||
import UserHelper from './helpers/User.mjs'
|
||||
import redis from './helpers/redis.mjs'
|
||||
import UserSessionsRedis from '../../../app/src/Features/User/UserSessionsRedis.mjs'
|
||||
|
||||
const rclient = UserSessionsRedis.client()
|
||||
|
||||
const UserPromises = UserHelper.promises
|
||||
|
||||
describe('Sessions', function () {
|
||||
beforeEach(function (done) {
|
||||
beforeEach(async function () {
|
||||
this.timeout(20000)
|
||||
this.user1 = new UserHelper()
|
||||
this.site_admin = new UserHelper({ email: 'admin@example.com' })
|
||||
async.series(
|
||||
[cb => this.user1.login(cb), cb => this.user1.logout(cb)],
|
||||
done
|
||||
)
|
||||
this.user1 = new UserPromises()
|
||||
this.site_admin = new UserPromises({ email: 'admin@example.com' })
|
||||
await this.user1.login()
|
||||
await this.user1.logout()
|
||||
})
|
||||
|
||||
describe('one session', function () {
|
||||
it('should have one session in UserSessions set', function (done) {
|
||||
async.series(
|
||||
[
|
||||
next => {
|
||||
redis.clearUserSessions(this.user1, next)
|
||||
},
|
||||
it('should have one session in UserSessions set', async function () {
|
||||
await redis.clearUserSessions(this.user1)
|
||||
|
||||
// login, should add session to set
|
||||
next => {
|
||||
this.user1.login(err => next(err))
|
||||
},
|
||||
// login, should add session to set
|
||||
await this.user1.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions = await redis.getUserSessions(this.user1)
|
||||
expect(sessions.length).to.equal(1)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// should be able to access project list page
|
||||
next => {
|
||||
this.user1.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// should be able to access project list page
|
||||
const statusCode = await this.user1.getProjectListPage()
|
||||
expect(statusCode).to.equal(200)
|
||||
|
||||
// logout, should remove session from set
|
||||
next => {
|
||||
this.user1.logout(err => next(err))
|
||||
},
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(0)
|
||||
next()
|
||||
})
|
||||
},
|
||||
],
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
done()
|
||||
}
|
||||
)
|
||||
// logout, should remove session from set
|
||||
await this.user1.logout()
|
||||
const sessionsAfterLogout = await redis.getUserSessions(this.user1)
|
||||
expect(sessionsAfterLogout.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('should update audit log on logout', function (done) {
|
||||
async.series(
|
||||
[
|
||||
next => {
|
||||
redis.clearUserSessions(this.user1, next)
|
||||
},
|
||||
it('should update audit log on logout', async function () {
|
||||
await redis.clearUserSessions(this.user1)
|
||||
|
||||
// login
|
||||
next => {
|
||||
this.user1.login(err => next(err))
|
||||
},
|
||||
// login
|
||||
await this.user1.login()
|
||||
|
||||
// logout, should add logout audit log entry (happens in background)
|
||||
next => {
|
||||
this.user1.logout(err => next(err))
|
||||
},
|
||||
// logout, should add logout audit log entry (happens in background)
|
||||
await this.user1.logout()
|
||||
|
||||
// poll for audit log entry since it's written in the background
|
||||
next => {
|
||||
let attempts = 0
|
||||
const checkAuditLog = () => {
|
||||
this.user1.getAuditLogWithoutNoise((error, auditLog) => {
|
||||
if (error) return next(error)
|
||||
// poll for audit log entry since it's written in the background
|
||||
const findAuditLogEntry = async () => {
|
||||
for (let attempts = 0; attempts < 10; attempts++) {
|
||||
const auditLog = await this.user1.getAuditLogWithoutNoise()
|
||||
|
||||
const logoutEntries = auditLog.filter(
|
||||
entry => entry.operation === 'logout'
|
||||
)
|
||||
const logoutEntries = auditLog.filter(
|
||||
entry => entry.operation === 'logout'
|
||||
)
|
||||
|
||||
// If we found the logout entry, we're done
|
||||
if (logoutEntries.length > 0) {
|
||||
expect(logoutEntries.length).to.be.greaterThan(0)
|
||||
const lastLogout = logoutEntries[logoutEntries.length - 1]
|
||||
expect(lastLogout.operation).to.equal('logout')
|
||||
expect(lastLogout.ipAddress).to.exist
|
||||
expect(lastLogout.initiatorId).to.exist
|
||||
expect(lastLogout.timestamp).to.exist
|
||||
return next()
|
||||
}
|
||||
|
||||
// Otherwise retry up to 10 times
|
||||
attempts++
|
||||
if (attempts >= 10) {
|
||||
return next(
|
||||
new Error(
|
||||
'Logout audit log entry not found after 10 attempts'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
setTimeout(checkAuditLog, 25)
|
||||
})
|
||||
}
|
||||
checkAuditLog()
|
||||
},
|
||||
],
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
// If we found the logout entry, we're done
|
||||
if (logoutEntries.length > 0) {
|
||||
return logoutEntries
|
||||
}
|
||||
done()
|
||||
|
||||
await setTimeout(25)
|
||||
}
|
||||
)
|
||||
|
||||
throw new Error('Logout audit log entry not found after 10 attempts')
|
||||
}
|
||||
|
||||
const logoutEntries = await findAuditLogEntry()
|
||||
expect(logoutEntries.length).to.be.greaterThan(0)
|
||||
const lastLogout = logoutEntries[logoutEntries.length - 1]
|
||||
expect(lastLogout.operation).to.equal('logout')
|
||||
expect(lastLogout.ipAddress).to.exist
|
||||
expect(lastLogout.initiatorId).to.exist
|
||||
expect(lastLogout.timestamp).to.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('two sessions', function () {
|
||||
beforeEach(function () {
|
||||
// set up second session for this user
|
||||
this.user2 = new UserHelper()
|
||||
this.user2 = new UserPromises()
|
||||
this.user2.email = this.user1.email
|
||||
this.user2.emails = this.user1.emails
|
||||
this.user2.password = this.user1.password
|
||||
})
|
||||
|
||||
it('should have two sessions in UserSessions set', function (done) {
|
||||
async.series(
|
||||
[
|
||||
next => {
|
||||
redis.clearUserSessions(this.user1, next)
|
||||
},
|
||||
it('should have two sessions in UserSessions set', async function () {
|
||||
await redis.clearUserSessions(this.user1)
|
||||
|
||||
// login, should add session to set
|
||||
next => {
|
||||
this.user1.login(err => next(err))
|
||||
},
|
||||
// login, should add session to set
|
||||
await this.user1.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions1 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions1.length).to.equal(1)
|
||||
expect(sessions1[0].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// login again, should add the second session to set
|
||||
next => {
|
||||
this.user2.login(err => next(err))
|
||||
},
|
||||
// login again, should add the second session to set
|
||||
await this.user2.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(2)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions[1].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions2 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions2.length).to.equal(2)
|
||||
expect(sessions2[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions2[1].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// both should be able to access project list page
|
||||
next => {
|
||||
this.user1.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// both should be able to access project list page
|
||||
const statusCode1 = await this.user1.getProjectListPage()
|
||||
expect(statusCode1).to.equal(200)
|
||||
const statusCode2 = await this.user2.getProjectListPage()
|
||||
expect(statusCode2).to.equal(200)
|
||||
|
||||
next => {
|
||||
this.user2.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// logout first session, should remove session from set
|
||||
await this.user1.logout()
|
||||
|
||||
// logout first session, should remove session from set
|
||||
next => {
|
||||
this.user1.logout(err => next(err))
|
||||
},
|
||||
const sessions3 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions3.length).to.equal(1)
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// first session should not have access to project list page
|
||||
const statusCode3 = await this.user1.getProjectListPage()
|
||||
expect(statusCode3).to.equal(302)
|
||||
|
||||
// first session should not have access to project list page
|
||||
next => {
|
||||
this.user1.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// second session should still have access to settings
|
||||
const statusCode4 = await this.user2.getProjectListPage()
|
||||
expect(statusCode4).to.equal(200)
|
||||
|
||||
// second session should still have access to settings
|
||||
next => {
|
||||
this.user2.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// logout second session, should remove last session from set
|
||||
await this.user2.logout()
|
||||
|
||||
// logout second session, should remove last session from set
|
||||
next => {
|
||||
this.user2.logout(err => next(err))
|
||||
},
|
||||
const sessions4 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions4.length).to.equal(0)
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(0)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
// second session should not have access to project list page
|
||||
next => {
|
||||
this.user2.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
],
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
done()
|
||||
}
|
||||
)
|
||||
// second session should not have access to project list page
|
||||
const statusCode5 = await this.user2.getProjectListPage()
|
||||
expect(statusCode5).to.equal(302)
|
||||
})
|
||||
})
|
||||
|
||||
describe('three sessions, password reset', function () {
|
||||
beforeEach(function () {
|
||||
// set up second session for this user
|
||||
this.user2 = new UserHelper()
|
||||
this.user2 = new UserPromises()
|
||||
this.user2.email = this.user1.email
|
||||
this.user2.emails = this.user1.emails
|
||||
this.user2.password = this.user1.password
|
||||
this.user3 = new UserHelper()
|
||||
this.user3 = new UserPromises()
|
||||
this.user3.email = this.user1.email
|
||||
this.user3.emails = this.user1.emails
|
||||
this.user3.password = this.user1.password
|
||||
})
|
||||
|
||||
it('should erase both sessions when password is reset', function (done) {
|
||||
async.series(
|
||||
[
|
||||
next => {
|
||||
redis.clearUserSessions(this.user1, next)
|
||||
},
|
||||
it('should erase both sessions when password is reset', async function () {
|
||||
await redis.clearUserSessions(this.user1)
|
||||
|
||||
// login, should add session to set
|
||||
next => {
|
||||
this.user1.login(err => next(err))
|
||||
},
|
||||
// login, should add session to set
|
||||
await this.user1.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions1 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions1.length).to.equal(1)
|
||||
expect(sessions1[0].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// login again, should add the second session to set
|
||||
next => {
|
||||
this.user2.login(err => next(err))
|
||||
},
|
||||
// login again, should add the second session to set
|
||||
await this.user2.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(2)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions[1].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions2 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions2.length).to.equal(2)
|
||||
expect(sessions2[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions2[1].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// login third session, should add the second session to set
|
||||
next => {
|
||||
this.user3.login(err => next(err))
|
||||
},
|
||||
// login third session, should add the second session to set
|
||||
await this.user3.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(3)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions[1].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions3 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions3.length).to.equal(3)
|
||||
expect(sessions3[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions3[1].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// password reset from second session, should erase two of the three sessions
|
||||
next => {
|
||||
this.user2.changePassword(`password${Date.now()}`, err => next(err))
|
||||
},
|
||||
// password reset from second session, should erase two of the three sessions
|
||||
await this.user2.changePassword(`password${Date.now()}`)
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user2, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions4 = await redis.getUserSessions(this.user2)
|
||||
expect(sessions4.length).to.equal(1)
|
||||
|
||||
// users one and three should not be able to access project list page
|
||||
next => {
|
||||
this.user1.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// users one and three should not be able to access project list page
|
||||
const statusCode1 = await this.user1.getProjectListPage()
|
||||
expect(statusCode1).to.equal(302)
|
||||
const statusCode3 = await this.user3.getProjectListPage()
|
||||
expect(statusCode3).to.equal(302)
|
||||
|
||||
next => {
|
||||
this.user3.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// user two should still be logged in, and able to access project list page
|
||||
const statusCode2 = await this.user2.getProjectListPage()
|
||||
expect(statusCode2).to.equal(200)
|
||||
|
||||
// user two should still be logged in, and able to access project list page
|
||||
next => {
|
||||
this.user2.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
// logout second session, should remove last session from set
|
||||
await this.user2.logout()
|
||||
|
||||
// logout second session, should remove last session from set
|
||||
next => {
|
||||
this.user2.logout(err => next(err))
|
||||
},
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(0)
|
||||
next()
|
||||
})
|
||||
},
|
||||
],
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
done()
|
||||
}
|
||||
)
|
||||
const sessions5 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions5.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('three sessions, sessions page', function () {
|
||||
beforeEach(function (done) {
|
||||
beforeEach(async function () {
|
||||
// set up second session for this user
|
||||
this.user2 = new UserHelper()
|
||||
this.user2 = new UserPromises()
|
||||
this.user2.email = this.user1.email
|
||||
this.user2.emails = this.user1.emails
|
||||
this.user2.password = this.user1.password
|
||||
this.user3 = new UserHelper()
|
||||
this.user3 = new UserPromises()
|
||||
this.user3.email = this.user1.email
|
||||
this.user3.emails = this.user1.emails
|
||||
this.user3.password = this.user1.password
|
||||
async.series([this.user2.login.bind(this.user2)], done)
|
||||
await this.user2.login()
|
||||
})
|
||||
|
||||
it('should allow the user to erase the other two sessions', function (done) {
|
||||
async.series(
|
||||
[
|
||||
next => {
|
||||
redis.clearUserSessions(this.user1, next)
|
||||
},
|
||||
it('should allow the user to erase the other two sessions', async function () {
|
||||
await redis.clearUserSessions(this.user1)
|
||||
|
||||
// login, should add session to set
|
||||
next => {
|
||||
this.user1.login(err => next(err))
|
||||
},
|
||||
// login, should add session to set
|
||||
await this.user1.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions1 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions1.length).to.equal(1)
|
||||
expect(sessions1[0].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// login again, should add the second session to set
|
||||
next => {
|
||||
this.user2.login(err => next(err))
|
||||
},
|
||||
// login again, should add the second session to set
|
||||
await this.user2.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(2)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions[1].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions2 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions2.length).to.equal(2)
|
||||
expect(sessions2[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions2[1].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// login third session, should add the second session to set
|
||||
next => {
|
||||
this.user3.login(err => next(err))
|
||||
},
|
||||
// login third session, should add the second session to set
|
||||
await this.user3.login()
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(3)
|
||||
expect(sessions[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions[1].slice(0, 5)).to.equal('sess:')
|
||||
next()
|
||||
})
|
||||
},
|
||||
const sessions3 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions3.length).to.equal(3)
|
||||
expect(sessions3[0].slice(0, 5)).to.equal('sess:')
|
||||
expect(sessions3[1].slice(0, 5)).to.equal('sess:')
|
||||
|
||||
// check the sessions page
|
||||
next => {
|
||||
this.user2.request.get(
|
||||
{
|
||||
uri: '/user/sessions',
|
||||
},
|
||||
(err, response, body) => {
|
||||
expect(err).to.be.oneOf([null, undefined])
|
||||
expect(response.statusCode).to.equal(200)
|
||||
next()
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// clear sessions from second session, should erase two of the three sessions
|
||||
next => {
|
||||
this.user2.getCsrfToken(err => {
|
||||
expect(err).to.be.oneOf([null, undefined])
|
||||
this.user2.request.post(
|
||||
{
|
||||
uri: '/user/sessions/clear',
|
||||
},
|
||||
err => next(err)
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user2, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(1)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
// users one and three should not be able to access project list page
|
||||
next => {
|
||||
this.user1.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
next => {
|
||||
this.user3.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(302)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
// user two should still be logged in, and able to access project list page
|
||||
next => {
|
||||
this.user2.getProjectListPage((err, statusCode) => {
|
||||
expect(err).to.equal(null)
|
||||
expect(statusCode).to.equal(200)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
// logout second session, should remove last session from set
|
||||
next => {
|
||||
this.user2.logout(err => next(err))
|
||||
},
|
||||
|
||||
next => {
|
||||
redis.getUserSessions(this.user1, (err, sessions) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sessions.length).to.equal(0)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
// the user audit log should have been updated
|
||||
next => {
|
||||
this.user1.getAuditLogWithoutNoise((error, auditLog) => {
|
||||
expect(error).not.to.exist
|
||||
expect(auditLog).to.exist
|
||||
|
||||
// find the clear-sessions entry
|
||||
const clearSessionsEntries = auditLog.filter(
|
||||
entry => entry.operation === 'clear-sessions'
|
||||
)
|
||||
expect(clearSessionsEntries.length).to.equal(1)
|
||||
expect(clearSessionsEntries[0].operation).to.equal(
|
||||
'clear-sessions'
|
||||
)
|
||||
expect(clearSessionsEntries[0].ipAddress).to.exist
|
||||
expect(clearSessionsEntries[0].initiatorId).to.exist
|
||||
expect(clearSessionsEntries[0].timestamp).to.exist
|
||||
expect(clearSessionsEntries[0].info.sessions.length).to.equal(2)
|
||||
next()
|
||||
})
|
||||
},
|
||||
],
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
done()
|
||||
}
|
||||
// check the sessions page
|
||||
const { response: sessionsPageResponse } = await this.user2.doRequest(
|
||||
'GET',
|
||||
'/user/sessions'
|
||||
)
|
||||
expect(sessionsPageResponse.statusCode).to.equal(200)
|
||||
|
||||
// clear sessions from second session, should erase two of the three sessions
|
||||
await this.user2.getCsrfToken()
|
||||
await this.user2.doRequest('POST', '/user/sessions/clear')
|
||||
const sessions4 = await redis.getUserSessions(this.user2)
|
||||
expect(sessions4.length).to.equal(1)
|
||||
|
||||
// users one and three should not be able to access project list page
|
||||
const statusCode1 = await this.user1.getProjectListPage()
|
||||
expect(statusCode1).to.equal(302)
|
||||
const statusCode3 = await this.user3.getProjectListPage()
|
||||
expect(statusCode3).to.equal(302)
|
||||
|
||||
// user two should still be logged in, and able to access project list page
|
||||
const statusCode2 = await this.user2.getProjectListPage()
|
||||
expect(statusCode2).to.equal(200)
|
||||
|
||||
// logout second session, should remove last session from set
|
||||
await this.user2.logout()
|
||||
const sessions5 = await redis.getUserSessions(this.user1)
|
||||
expect(sessions5.length).to.equal(0)
|
||||
|
||||
// the user audit log should have been updated
|
||||
const auditLog = await this.user1.getAuditLogWithoutNoise()
|
||||
expect(auditLog).to.exist
|
||||
|
||||
// find the clear-sessions entry
|
||||
const clearSessionsEntries = auditLog.filter(
|
||||
entry => entry.operation === 'clear-sessions'
|
||||
)
|
||||
expect(clearSessionsEntries.length).to.equal(1)
|
||||
expect(clearSessionsEntries[0].operation).to.equal('clear-sessions')
|
||||
expect(clearSessionsEntries[0].ipAddress).to.exist
|
||||
expect(clearSessionsEntries[0].initiatorId).to.exist
|
||||
expect(clearSessionsEntries[0].timestamp).to.exist
|
||||
expect(clearSessionsEntries[0].info.sessions.length).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,49 +1,26 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import Async from 'async'
|
||||
import UserSessionsRedis from '../../../../app/src/Features/User/UserSessionsRedis.mjs'
|
||||
|
||||
// rclient = redis.createClient(Settings.redis.web)
|
||||
const rclient = UserSessionsRedis.client()
|
||||
|
||||
export default {
|
||||
getUserSessions(user, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return rclient.smembers(
|
||||
UserSessionsRedis.sessionSetKey(user),
|
||||
(err, result) => callback(err, result)
|
||||
)
|
||||
},
|
||||
|
||||
clearUserSessions(user, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
const sessionSetKey = UserSessionsRedis.sessionSetKey(user)
|
||||
return rclient.smembers(sessionSetKey, (err, sessionKeys) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (sessionKeys.length === 0) {
|
||||
return callback(null)
|
||||
}
|
||||
const actions = sessionKeys.map(k => cb => rclient.del(k, err => cb(err)))
|
||||
return Async.series(actions, () =>
|
||||
rclient.srem(sessionSetKey, sessionKeys, err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
return callback(null)
|
||||
})
|
||||
)
|
||||
})
|
||||
},
|
||||
async function getUserSessions(user) {
|
||||
return await rclient.smembers(UserSessionsRedis.sessionSetKey(user))
|
||||
}
|
||||
|
||||
async function clearUserSessions(user) {
|
||||
const sessionSetKey = UserSessionsRedis.sessionSetKey(user)
|
||||
const sessionKeys = await rclient.smembers(sessionSetKey)
|
||||
if (sessionKeys.length === 0) {
|
||||
return
|
||||
}
|
||||
for (const k of sessionKeys) {
|
||||
await rclient.del(k)
|
||||
}
|
||||
await rclient.srem(sessionSetKey, sessionKeys)
|
||||
}
|
||||
|
||||
const RedisHelper = {
|
||||
getUserSessions,
|
||||
clearUserSessions,
|
||||
}
|
||||
|
||||
export default RedisHelper
|
||||
|
||||
Reference in New Issue
Block a user