From 3135db0e7258a22f6e0ddf310e6498862ccd8454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= <mrsimpson@users.noreply.github.com> Date: Sat, 21 Nov 2020 03:18:45 +0100 Subject: [PATCH] [NEW] REST endpoint to log user out from other clients #19246 --- app/api/server/v1/users.js | 10 ++++ tests/end-to-end/api/01-users.js | 88 ++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index b182c363305..47338e67d2e 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -779,6 +779,16 @@ API.v1.addRoute('users.requestDataDownload', { authRequired: true }, { }, }); +API.v1.addRoute('users.logoutOtherClients', { authRequired: true }, { + post() { + try { + Meteor.runAsUser(this.userId, () => API.v1.success(Meteor.call('logoutOtherClients'))); + } catch (error) { + return API.v1.failure(error); + } + }, +}); + API.v1.addRoute('users.autocomplete', { authRequired: true }, { get() { const { selector } = this.queryParams; diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index 9607852e2a9..2f19bd900ea 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -20,6 +20,47 @@ import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createUser, login, deleteUser, getUserStatus } from '../../data/users.helper.js'; import { createRoom } from '../../data/rooms.helper'; +function createTestUser() { + return new Promise((resolve) => { + const username = `user.test.${ Date.now() }`; + const email = `${ username }@rocket.chat`; + request.post(api('users.create')) + .set(credentials) + .send({ email, name: username, username, password }) + .end((err, res) => resolve(res.body.user)); + }); +} + +function loginTestUser(user) { + return new Promise((resolve, reject) => { + request.post(api('login')) + .send({ + user: user.username, + password, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + const userCredentials = {}; + userCredentials['X-Auth-Token'] = res.body.data.authToken; + userCredentials['X-User-Id'] = res.body.data.userId; + resolve(userCredentials); + }) + .end((err) => (err ? reject(err) : resolve())); + }); +} + +function deleteTestUser(user) { + return new Promise((resolve) => { + request.post(api('users.delete')) + .set(credentials) + .send({ + userId: user._id, + }) + .end(resolve); + }); +} + describe('[Users]', function() { this.retries(0); @@ -2288,6 +2329,53 @@ describe('[Users]', function() { }); }); + describe('[/users.logoutOtherClients]', function() { + let user; + let userCredentials; + let newCredentials; + + this.timeout(20000); + + before(async () => { + user = await createTestUser(); + userCredentials = await loginTestUser(user); + newCredentials = await loginTestUser(user); + }); + after(async () => { + await deleteTestUser(user); + user = undefined; + }); + + it('should invalidate all active sesions', (done) => { + /* We want to validate that the login with the "old" credentials fails + However, the removal of the tokens is done asynchronously. + Thus, we check that within the next seconds, at least one try to + access an authentication requiring route fails */ + let counter = 0; + + async function checkAuthenticationFails() { + const result = await request.get(api('me')) + .set(userCredentials); + return result.statusCode === 401; + } + + async function tryAuthentication() { + if (await checkAuthenticationFails()) { + done(); + } else if (++counter < 20) { + setTimeout(tryAuthentication, 1000); + } else { + done('Session did not invalidate in time'); + } + } + + request.post(api('users.logoutOtherClients')) + .set(newCredentials) + .expect(200) + .then(tryAuthentication); + }); + }); + describe('[/users.autocomplete]', () => { it('should return an empty list when the user does not have the necessary permission', (done) => { updatePermission('view-outside-room', []).then(() => { -- GitLab