Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] API users.logoutOtherClient to logout from other locations #16193

10 changes: 10 additions & 0 deletions app/api/server/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -710,3 +710,13 @@ API.v1.addRoute('users.autocomplete', { 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);
}
},
});
jschirrmacher marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions tests/data/users.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export const login = (username, password) => new Promise((resolve) => {
});
});

export const deleteUser = (user) => new Promise((resolve) => {
request.post(api('users.delete'))
.set(credentials)
.send({
userId: user._id,
})
.end(resolve);
});

export const getUserByUsername = (username) => new Promise((resolve) => {
request.get(api(`users.info?username=${ username }`))
.set(credentials)
Expand Down
81 changes: 52 additions & 29 deletions tests/end-to-end/api/01-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { adminEmail, preferences, password, adminUsername } from '../../data/use
import { imgURL } from '../../data/interactions.js';
import { customFieldText, clearCustomFields, setCustomFields } from '../../data/custom-fields.js';
import { updatePermission, updateSetting } from '../../data/permissions.helper';
import { createUser, login } from '../../data/users.helper.js';
import { createUser, login, deleteUser } from '../../data/users.helper.js';

describe('[Users]', function() {
this.retries(0);
Expand Down Expand Up @@ -419,41 +419,19 @@ describe('[Users]', function() {

describe('[/users.setAvatar]', () => {
let user;
before((done) => {
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) => {
user = res.body.user;
done();
});
before(async () => {
user = await createUser();
});

let userCredentials;
before((done) => {
request.post(api('login'))
.send({
user: user.username,
password,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
userCredentials = {};
userCredentials['X-Auth-Token'] = res.body.data.authToken;
userCredentials['X-User-Id'] = res.body.data.userId;
})
.end(done);
before(async () => {
userCredentials = await login(user.username, password);
});
before((done) => {
updatePermission('edit-other-user-info', ['admin', 'user']).then(done);
});
after((done) => {
request.post(api('users.delete')).set(credentials).send({
userId: user._id,
}).end(() => updatePermission('edit-other-user-info', ['admin']).then(done));
after(async () => {
await deleteUser(user);
jschirrmacher marked this conversation as resolved.
Show resolved Hide resolved
user = undefined;
});
it('should set the avatar of the logged user by a local image', (done) => {
Expand Down Expand Up @@ -1758,4 +1736,49 @@ describe('[Users]', function() {
.end(done);
});
});

describe('[/users.logoutOtherClients]', () => {
jschirrmacher marked this conversation as resolved.
Show resolved Hide resolved
let user;
let userCredentials;
let newCredentials;

before(async () => {
user = await createUser();
userCredentials = await login(user.username, password);
newCredentials = await login(user.username, password);
});
after(async () => {
await deleteUser(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);
jschirrmacher marked this conversation as resolved.
Show resolved Hide resolved
} else {
done('Session did not invalidate in time');
}
}

request.post(api('users.logoutOtherClients'))
jschirrmacher marked this conversation as resolved.
Show resolved Hide resolved
.set(newCredentials)
.expect(200)
.then(tryAuthentication);
});
});
});