diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index f08891e7535b..789848739e5b 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -98,8 +98,10 @@ API.v1.addRoute('users.deleteOwnAccount', { authRequired: true }, { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } + const { confirmRelinquish = false } = this.requestParams(); + Meteor.runAsUser(this.userId, () => { - Meteor.call('deleteUserOwnAccount', password); + Meteor.call('deleteUserOwnAccount', password, confirmRelinquish); }); return API.v1.success(); @@ -133,7 +135,8 @@ API.v1.addRoute('users.setActiveStatus', { authRequired: true }, { } Meteor.runAsUser(this.userId, () => { - Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.activeStatus, this.bodyParams.confirmRelinquish); + const { userId, activeStatus, confirmRelinquish = false } = this.bodyParams; + Meteor.call('setUserActiveStatus', userId, activeStatus, confirmRelinquish); }); return API.v1.success({ user: Users.findOneById(this.bodyParams.userId, { fields: { active: 1 } }) }); }, @@ -463,8 +466,10 @@ API.v1.addRoute('users.update', { authRequired: true, twoFactorRequired: true }, } if (typeof this.bodyParams.data.active !== 'undefined') { + const { userId, data: { active }, confirmRelinquish = false } = this.bodyParams; + Meteor.runAsUser(this.userId, () => { - Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.data.active); + Meteor.call('setUserActiveStatus', userId, active, confirmRelinquish); }); } const { fields } = this.parseJsonQuery(); diff --git a/tests/data/rooms.helper.js b/tests/data/rooms.helper.js index 9c995969fbf7..6611f348f31d 100644 --- a/tests/data/rooms.helper.js +++ b/tests/data/rooms.helper.js @@ -1,6 +1,6 @@ import { api, credentials, request } from './api-data'; -export const createRoom = ({ name, type, username }) => { +export const createRoom = ({ name, type, username, members = [] }) => { if (!type) { throw new Error('"type" is required in "createRoom" test helper'); } @@ -18,7 +18,10 @@ export const createRoom = ({ name, type, username }) => { return request.post(api(endpoints[type])) .set(credentials) - .send(params); + .send({ + ...params, + members, + }); }; export const closeRoom = ({ type, roomId }) => { diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index f066d9ef1c0d..9607852e2a91 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -18,6 +18,7 @@ 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, deleteUser, getUserStatus } from '../../data/users.helper.js'; +import { createRoom } from '../../data/rooms.helper'; describe('[Users]', function() { this.retries(0); @@ -1355,6 +1356,165 @@ describe('[Users]', function() { }); }); }); + + it('should return an error when trying to delete user own account if user is the last room owner', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); + }); + }); + + it('should delete user own account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should assign a new owner to the room if the last room owner is deleted', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(1); + expect(res.body.roles[0].roles).to.eql(['owner']); + expect(res.body.roles[0].u).to.have.property('_id', credentials['X-User-Id']); + }); + }); }); describe('[/users.delete]', () => { @@ -1371,7 +1531,7 @@ describe('[Users]', function() { }); const testUsername = `testuserdelete${ +new Date() }`; let targetUser; - it('register a new user...', (done) => { + beforeEach((done) => { request.post(api('users.register')) .set(credentials) .send({ @@ -1384,40 +1544,196 @@ describe('[Users]', function() { .expect(200) .expect((res) => { targetUser = res.body.user; + }).end(done); + }); + + afterEach((done) => { + updatePermission('delete-user', ['admin']).then(() => { + request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }).end(done); + }); + }); + + it('should return an error when trying delete user account without "delete-user" permission', async () => { + await updatePermission('delete-user', ['user']); + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, }) - .end(done); + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'unauthorized'); + }); }); - it('should return an error when trying delete user account without "delete-user" permission', (done) => { - updatePermission('delete-user', ['user']) - .then(() => { - request.post(api('users.delete')) - .set(credentials) - .send({ - userId: targetUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + + it('should return an error when trying to delete user account if the user is the last room owner', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); }); }); - it('should delete user account when logged user has "delete-user" permission', (done) => { - updatePermission('delete-user', ['admin']) - .then(() => { - request.post(api('users.delete')) - .set(credentials) - .send({ - userId: targetUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); + + it('should delete user account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should delete user account when logged user has "delete-user" permission', async () => { + await updatePermission('delete-user', ['admin']); + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should assign a new owner to the room if the last room owner is deleted', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(1); + expect(res.body.roles[0].roles).to.eql(['owner']); + expect(res.body.roles[0].u).to.have.property('_id', credentials['X-User-Id']); }); }); }); @@ -1681,6 +1997,207 @@ describe('[Users]', function() { }) .end(done); }); + + it('should return an error when trying to set other user status to inactive and the user is the last owner of a room', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: targetUser.username, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); + }); + }); + + it('should set other user status to inactive if the user is the last owner of a room and `confirmRelinquish` is set to `true`', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: targetUser.username, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should set other user as room owner if the last owner of a room is deactivated and `confirmRelinquish` is set to `true`', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: true, + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(2); + expect(res.body.roles[1].roles).to.eql(['owner']); + expect(res.body.roles[1].u).to.have.property('_id', credentials['X-User-Id']); + }); + }); + it('should return an error when trying to set other user active status and has not the necessary permission(edit-other-user-active-status)', (done) => { updatePermission('edit-other-user-active-status', []).then(() => { request.post(api('users.setActiveStatus'))