diff --git a/services/web/app/src/Features/User/UserSessionsManager.js b/services/web/app/src/Features/User/UserSessionsManager.js index eb62d4b369..d4f7eb7ce5 100644 --- a/services/web/app/src/Features/User/UserSessionsManager.js +++ b/services/web/app/src/Features/User/UserSessionsManager.js @@ -133,7 +133,9 @@ const UserSessionsManager = { */ removeSessionsFromRedis(user, retainSessionID, callback) { if (!user) { - return callback(null) + return callback( + new Error('bug: user not passed to removeSessionsFromRedis') + ) } const sessionSetKey = UserSessionsRedis.sessionSetKey(user) rclient.smembers(sessionSetKey, function (err, sessionKeys) { @@ -155,7 +157,7 @@ const UserSessionsManager = { { userId: user._id }, 'no sessions in UserSessions set to delete, returning' ) - return callback(null) + return callback(null, 0) } logger.debug( { userId: user._id, count: keysToDelete.length }, @@ -180,7 +182,7 @@ const UserSessionsManager = { }) return callback(err) } - callback(null) + callback(null, keysToDelete.length) }) }) }) diff --git a/services/web/scripts/clear_admin_sessions.js b/services/web/scripts/clear_admin_sessions.js new file mode 100644 index 0000000000..2f88cb194d --- /dev/null +++ b/services/web/scripts/clear_admin_sessions.js @@ -0,0 +1,70 @@ +const { + db, + waitForDb, + READ_PREFERENCE_SECONDARY, +} = require('../app/src/infrastructure/mongodb') +const UserSessionsManager = require('../app/src/Features/User/UserSessionsManager') + +const COMMIT = process.argv.includes('--commit') +const LOG_SESSIONS = !process.argv.includes('--log-sessions=false') + +async function main() { + await waitForDb() + const adminUsers = await db.users + .find( + { isAdmin: true }, + { + projection: { + _id: 1, + email: 1, + }, + readPreference: READ_PREFERENCE_SECONDARY, + } + ) + .toArray() + + if (LOG_SESSIONS) { + for (const user of adminUsers) { + user.sessions = JSON.stringify( + await UserSessionsManager.promises.getAllUserSessions(user, []) + ) + } + } + console.log('All Admin users before clearing:') + console.table(adminUsers) + + if (COMMIT) { + let anyFailed = false + for (const user of adminUsers) { + console.error( + `Clearing sessions for ${user.email} (${user._id.toString()})` + ) + user.clearedSessions = 0 + try { + user.clearedSessions = + await UserSessionsManager.promises.removeSessionsFromRedis(user) + } catch (err) { + anyFailed = true + console.error(err) + } + } + console.log('All Admin users after clearing:') + console.table(adminUsers) + + if (anyFailed) { + throw new Error('failed to clear some sessions, see above for details') + } + } else { + console.warn('Use --commit to clear sessions.') + } +} + +main() + .then(() => { + console.error('Done.') + process.exit(0) + }) + .catch(error => { + console.error({ error }) + process.exit(1) + }) diff --git a/services/web/test/unit/src/User/UserSessionsManagerTests.js b/services/web/test/unit/src/User/UserSessionsManagerTests.js index f300676bf4..dc6a0c52e3 100644 --- a/services/web/test/unit/src/User/UserSessionsManagerTests.js +++ b/services/web/test/unit/src/User/UserSessionsManagerTests.js @@ -363,6 +363,14 @@ describe('UserSessionsManager', function () { }) }) + it('should yield the number of purged sessions', function (done) { + return this.call((err, n) => { + expect(err).to.not.exist + expect(n).to.equal(this.sessionKeys.length) + return done() + }) + }) + it('should call the appropriate redis methods', function (done) { return this.call(err => { this.rclient.smembers.callCount.should.equal(1) @@ -465,9 +473,11 @@ describe('UserSessionsManager', function () { }) }) - it('should not produce an error', function (done) { + it('should produce an error', function (done) { return this.call(err => { - expect(err).to.not.exist + expect(err).to.match( + /bug: user not passed to removeSessionsFromRedis/ + ) return done() }) })