From d72149c97dff1a1cd2b2e20bf3d0a5881d650374 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Tue, 11 Nov 2025 13:10:13 +0100 Subject: [PATCH] Prevents editing rooms with active meetings Enhances room management by preventing modifications to rooms with active meetings. Adds validation to backend to prevent updates to room configuration during an active meeting. Improves frontend user experience by disabling the room editing option and adding a guard to redirect users away from the edit page. --- .../responses/error-room-active-meeting.yaml | 8 ++++ meet-ce/backend/openapi/paths/rooms.yaml | 10 +++-- meet-ce/backend/src/models/error.model.ts | 4 ++ meet-ce/backend/src/services/room.service.ts | 7 ++++ .../api/rooms/update-room-config.test.ts | 38 +++++++++++++++++++ .../rooms-lists/rooms-lists.component.ts | 4 +- .../src/lib/guards/check-room-edit.guard.ts | 37 ++++++++++++++++++ .../src/lib/guards/index.ts | 1 + .../src/lib/routes/base-routes.ts | 4 +- 9 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 meet-ce/backend/openapi/components/responses/error-room-active-meeting.yaml create mode 100644 meet-ce/frontend/projects/shared-meet-components/src/lib/guards/check-room-edit.guard.ts diff --git a/meet-ce/backend/openapi/components/responses/error-room-active-meeting.yaml b/meet-ce/backend/openapi/components/responses/error-room-active-meeting.yaml new file mode 100644 index 00000000..a9591d1e --- /dev/null +++ b/meet-ce/backend/openapi/components/responses/error-room-active-meeting.yaml @@ -0,0 +1,8 @@ +description: Room not found +content: + application/json: + schema: + $ref: '../schemas/error.yaml' + example: + error: 'Room Error' + message: 'Room "room_123" has an active meeting' diff --git a/meet-ce/backend/openapi/paths/rooms.yaml b/meet-ce/backend/openapi/paths/rooms.yaml index efeea64e..5f6f3a2b 100644 --- a/meet-ce/backend/openapi/paths/rooms.yaml +++ b/meet-ce/backend/openapi/paths/rooms.yaml @@ -56,10 +56,10 @@ description: | Delete multiple OpenVidu Meet rooms at once with the specified room IDs. - If any of the rooms have active meetings or recordings, + If any of the rooms have active meetings or recordings, deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies. - Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end, + Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end, or the operation may fail if deletion is not permitted. tags: - OpenVidu Meet - Rooms @@ -120,10 +120,10 @@ description: | Deletes the specified OpenVidu Meet room by its room ID. - If the room has an active meeting or existing recordings, + If the room has an active meeting or existing recordings, deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies. - Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends, + Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends, or the operation may fail if deletion is not permitted. tags: - OpenVidu Meet - Rooms @@ -204,6 +204,8 @@ $ref: '../components/responses/forbidden-error.yaml' '404': $ref: '../components/responses/error-room-not-found.yaml' + '409': + $ref: '../components/responses/error-room-active-meeting.yaml' '422': $ref: '../components/responses/validation-error.yaml' '500': diff --git a/meet-ce/backend/src/models/error.model.ts b/meet-ce/backend/src/models/error.model.ts index d8b72bee..b34a9de0 100644 --- a/meet-ce/backend/src/models/error.model.ts +++ b/meet-ce/backend/src/models/error.model.ts @@ -205,6 +205,10 @@ export const errorRoomClosed = (roomId: string): OpenViduMeetError => { return new OpenViduMeetError('Room Error', `Room '${roomId}' is closed and cannot be joined`, 409); }; +export const errorRoomActiveMeeting = (roomId: string): OpenViduMeetError => { + return new OpenViduMeetError('Room Error', `Room '${roomId}' has an active meeting`, 409); +}; + export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => { return new OpenViduMeetError('Room Error', `Secret '${secret}' is not recognized for room '${roomId}'`, 400); }; diff --git a/meet-ce/backend/src/services/room.service.ts b/meet-ce/backend/src/services/room.service.ts index 513500a3..28fcbf12 100644 --- a/meet-ce/backend/src/services/room.service.ts +++ b/meet-ce/backend/src/services/room.service.ts @@ -25,6 +25,7 @@ import { validateRecordingTokenMetadata } from '../middlewares/index.js'; import { errorDeletingRoom, errorInvalidRoomSecret, + errorRoomActiveMeeting, errorRoomNotFound, internalError, OpenViduMeetError @@ -139,6 +140,12 @@ export class RoomService { */ async updateMeetRoomConfig(roomId: string, config: MeetRoomConfig): Promise { const room = await this.getMeetRoom(roomId); + + if (room.status === MeetRoomStatus.ACTIVE_MEETING) { + // Reject config updates during active meetings + throw errorRoomActiveMeeting(roomId); + } + room.config = config; await this.roomRepository.update(room); diff --git a/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts b/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts index c016f9ba..379b0d13 100644 --- a/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts +++ b/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts @@ -5,10 +5,12 @@ import { FrontendEventService } from '../../../../src/services/index.js'; import { createRoom, deleteAllRooms, + disconnectFakeParticipants, getRoom, startTestServer, updateRoomConfig } from '../../../helpers/request-helpers.js'; +import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js'; describe('Room API Tests', () => { beforeAll(async () => { @@ -133,6 +135,42 @@ describe('Room API Tests', () => { }); }); + describe('Update Room Config with Active Meeting Tests', () => { + let roomData: RoomData; + + afterEach(async () => { + await disconnectFakeParticipants(); + }); + + it('should reject room config update when there is an active meeting', async () => { + // Create a room and start a meeting + roomData = await setupSingleRoom(true); + + // Try to update room config + const newConfig = { + recording: { + enabled: false + }, + chat: { + enabled: false + }, + virtualBackground: { + enabled: false + }, + e2ee: { + enabled: false + } + }; + + const response = await updateRoomConfig(roomData.room.roomId, newConfig); + + // Should return 409 Conflict + expect(response.status).toBe(409); + expect(response.body.error).toBe('Room Error'); + expect(response.body.message).toContain(`Room '${roomData.room.roomId}' has an active meeting`); + }); + }); + describe('Update Room Config Validation failures', () => { it('should fail when config has incorrect structure', async () => { const { roomId } = await createRoom({ diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/components/rooms-lists/rooms-lists.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/components/rooms-lists/rooms-lists.component.ts index c3752a8c..fe301737 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/components/rooms-lists/rooms-lists.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/components/rooms-lists/rooms-lists.component.ts @@ -323,8 +323,8 @@ export class RoomsListsComponent implements OnInit, OnChanges { return room.status !== MeetRoomStatus.CLOSED; } - canEditRoom(_room: MeetRoom): boolean { - return true; + canEditRoom(room: MeetRoom): boolean { + return room.status !== MeetRoomStatus.ACTIVE_MEETING; } // ===== UI HELPER METHODS ===== diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/check-room-edit.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/check-room-edit.guard.ts new file mode 100644 index 00000000..ae785548 --- /dev/null +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/check-room-edit.guard.ts @@ -0,0 +1,37 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { MeetRoomStatus } from '@openvidu-meet/typings'; +import { RoomService } from '../services/room.service'; + +/** + * Guard that prevents editing a room when there's an active meeting. + * Redirects to /rooms if the room has an active meeting. + */ +export const checkRoomEditGuard: CanActivateFn = async (route) => { + const roomService = inject(RoomService); + const router = inject(Router); + + const roomId = route.paramMap.get('roomId'); + + if (!roomId) { + console.warn('No roomId provided in route params'); + router.navigate(['/rooms']); + return false; + } + + try { + const room = await roomService.getRoom(roomId); + + if (room.status === MeetRoomStatus.ACTIVE_MEETING) { + console.warn(`Cannot edit room ${roomId} - active meeting in progress`); + router.navigate(['/rooms']); + return false; + } + + return true; + } catch (error) { + console.error(`Error checking room status for ${roomId}:`, error); + router.navigate(['/rooms']); + return false; + } +}; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/index.ts index 435d7e29..c5214044 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/guards/index.ts @@ -1,4 +1,5 @@ export * from './auth.guard'; +export * from './check-room-edit.guard'; export * from './extract-query-params.guard'; export * from './remove-query-params.guard'; export * from './run-serially.guard'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts index 386d4d01..cca40eb8 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { checkParticipantRoleAndAuthGuard, checkRecordingAuthGuard, + checkRoomEditGuard, checkUserAuthenticatedGuard, checkUserNotAuthenticatedGuard, extractRecordingQueryParamsGuard, @@ -90,7 +91,8 @@ export const baseRoutes: Routes = [ }, { path: 'rooms/:roomId/edit', - component: RoomWizardComponent + component: RoomWizardComponent, + canActivate: [checkRoomEditGuard] }, { path: 'recordings',