From c635c9f17c12e785db7547583ef8bdb69c7cba63 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Fri, 17 Oct 2025 19:37:02 -0300 Subject: [PATCH] fix: API method `rooms.delete` deleting main team room (#36790) Co-authored-by: Abhinav Kumar <15830206+abhinavkrin@users.noreply.github.com> Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com> --- .changeset/slimy-apples-complain.md | 5 +++ apps/meteor/app/api/server/v1/rooms.ts | 18 +++++++++-- apps/meteor/server/lib/eraseRoom.ts | 7 +++-- apps/meteor/tests/end-to-end/api/rooms.ts | 37 ++++++++++++++++++++--- 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 .changeset/slimy-apples-complain.md diff --git a/.changeset/slimy-apples-complain.md b/.changeset/slimy-apples-complain.md new file mode 100644 index 00000000000..3315ec9e138 --- /dev/null +++ b/.changeset/slimy-apples-complain.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where the API method `rooms.delete` inadvertently deletes the main room of a team diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 7206cfb5ce2..88558dc9a62 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,4 +1,4 @@ -import { Media, Team } from '@rocket.chat/core-services'; +import { Media, MeteorError, Team } from '@rocket.chat/core-services'; import type { IRoom, IUpload } from '@rocket.chat/core-typings'; import { isPrivateRoom, isPublicRoom } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; @@ -130,7 +130,21 @@ API.v1.addRoute( return API.v1.failure("The 'roomId' param is required"); } - await eraseRoom(roomId, this.userId); + const room = await Rooms.findOneById(roomId); + + if (!room) { + throw new MeteorError('error-invalid-room', 'Invalid room', { + method: 'eraseRoom', + }); + } + + if (room.teamMain) { + throw new Meteor.Error('error-cannot-delete-team-channel', 'Cannot delete a team channel', { + method: 'eraseRoom', + }); + } + + await eraseRoom(room, this.userId); return API.v1.success(); }, diff --git a/apps/meteor/server/lib/eraseRoom.ts b/apps/meteor/server/lib/eraseRoom.ts index 9852daaf3c5..4b2d618643f 100644 --- a/apps/meteor/server/lib/eraseRoom.ts +++ b/apps/meteor/server/lib/eraseRoom.ts @@ -1,5 +1,6 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message, Team } from '@rocket.chat/core-services'; +import type { IRoom } from '@rocket.chat/core-typings'; import { Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -7,8 +8,8 @@ import { roomCoordinator } from './rooms/roomCoordinator'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { deleteRoom } from '../../app/lib/server/functions/deleteRoom'; -export async function eraseRoom(rid: string, uid: string): Promise { - const room = await Rooms.findOneById(rid); +export async function eraseRoom(roomOrId: string | IRoom, uid: string): Promise { + const room = typeof roomOrId === 'string' ? await Rooms.findOneById(roomOrId) : roomOrId; if (!room) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { @@ -46,7 +47,7 @@ export async function eraseRoom(rid: string, uid: string): Promise { } } - await deleteRoom(rid); + await deleteRoom(room._id); if (team) { const user = await Meteor.userAsync(); diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index a4818aa3f71..2e9b97b9f67 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -2598,13 +2598,30 @@ describe('[Rooms]', () => { describe('/rooms.delete', () => { let testChannel: IRoom; + let testTeam: ITeam; + let testUser: IUser; + let testUser2: IUser; + let userCredentials: Credentials; - before('create an channel', async () => { - const result = await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` }); - testChannel = result.body.channel; + before('create channel and team', async () => { + testUser = await createUser(); + testUser2 = await createUser(); + userCredentials = await login(testUser.username, password); + + const { + body: { channel }, + } = await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` }); + testChannel = channel; + testTeam = await createTeam(userCredentials, `team.test.${Date.now()}-${Math.random()}`, TEAM_TYPE.PUBLIC, [ + testUser.username as string, + testUser2.username as string, + ]); }); - after(() => deleteRoom({ type: 'c', roomId: testChannel._id })); + after('delete channel and team', async () => { + await deleteTeam(userCredentials, testTeam.name); + await deleteRoom({ type: 'c', roomId: testChannel._id }); + }); it('should throw an error when roomId is not provided', (done) => { void request @@ -2643,6 +2660,18 @@ describe('[Rooms]', () => { }) .end(done); }); + it('should throw an error when room is a main team room', (done) => { + void request + .post(api('rooms.delete')) + .set(credentials) + .send({ roomId: testTeam.roomId }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }) + .end(done); + }); }); describe('rooms.saveRoomSettings', () => {