[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:
Antoine Clausse
2026-01-16 11:58:30 +01:00
committed by Copybot
parent 9619ad52ea
commit 379788a8a6
3 changed files with 323 additions and 694 deletions

View File

@@ -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)
})
})
})

View File

@@ -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)
})
})

View File

@@ -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