From 63d72c994b0f46939c915232a62d2b640e8e871e Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 2 Mar 2026 17:37:25 +0100 Subject: [PATCH] refactor: rename anonymous room access to access across all codebase - Updated the MeetRoom interface to replace anonymous access configuration with a unified access configuration. - Refactored RoomService to handle access configuration for both anonymous and registered users. - Modified tests to reflect changes in access configuration structure. - Updated frontend components to use the new access configuration for meeting URLs and permissions. - Ensured backward compatibility by adjusting API endpoints and request/response types. --- .../parameters/room-x-fields-header.yaml | 2 +- .../update-room-access-request.yaml | 9 ++ .../update-room-anonymous-request.yaml | 9 -- .../responses/success-bulk-delete-rooms.yaml | 110 +++++++++------ .../responses/success-create-room.yaml | 26 ++-- .../success-delete-room-processed.yaml | 44 +++--- .../success-delete-room-scheduled.yaml | 66 +++++---- .../responses/success-get-room.yaml | 131 ++++++++++++++---- .../responses/success-get-rooms.yaml | 86 +++++++----- ...s.yaml => success-update-room-access.yaml} | 4 +- .../schemas/meet-room-access-config.yaml | 46 ++++++ .../schemas/meet-room-anonymous-config.yaml | 24 ---- .../components/schemas/meet-room-options.yaml | 15 +- .../openapi/components/schemas/meet-room.yaml | 88 +++++++----- .../backend/openapi/openvidu-meet-api.yaml | 4 +- meet-ce/backend/openapi/paths/rooms.yaml | 15 +- .../src/controllers/room.controller.ts | 12 +- meet-ce/backend/src/helpers/room.helper.ts | 42 ++++-- .../room-validator.middleware.ts | 6 +- .../backend/src/migrations/room-migrations.ts | 24 +++- .../models/mongoose-schemas/room.schema.ts | 52 ++++--- .../src/models/zod-schemas/room.schema.ts | 38 +++-- .../src/repositories/room.repository.ts | 40 ++++-- meet-ce/backend/src/routes/room.routes.ts | 8 +- .../src/services/room-member.service.ts | 12 +- meet-ce/backend/src/services/room.service.ts | 54 +++++--- .../tests/helpers/assertion-helpers.ts | 29 ++-- .../backend/tests/helpers/request-helpers.ts | 8 +- .../backend/tests/helpers/test-scenarios.ts | 2 +- .../api/auth/token-validation.test.ts | 28 ++-- .../generate-room-member-token.test.ts | 38 +++-- .../api/rooms/expired-rooms-gc.test.ts | 2 +- .../api/rooms/room-migrations.test.ts | 46 ++++-- .../api/security/room-security.test.ts | 36 ++--- .../services/meeting-context.service.ts | 2 +- .../meeting/services/meeting-lobby.service.ts | 2 +- .../meeting/services/meeting.service.ts | 2 +- .../room-detail/room-detail.component.ts | 4 +- .../room-wizard/room-wizard.component.ts | 8 +- .../role-permissions.component.ts | 99 ++++++++++--- .../rooms/pages/rooms/rooms.component.ts | 12 +- .../domains/rooms/services/room.service.ts | 14 +- .../rooms/services/wizard-state.service.ts | 45 +++--- meet-ce/typings/src/database/room.entity.ts | 62 +++++++-- meet-ce/typings/src/request/room-request.ts | 22 ++- meet-ce/typings/src/response/room-response.ts | 5 +- 46 files changed, 948 insertions(+), 485 deletions(-) create mode 100644 meet-ce/backend/openapi/components/requestBodies/update-room-access-request.yaml delete mode 100644 meet-ce/backend/openapi/components/requestBodies/update-room-anonymous-request.yaml rename meet-ce/backend/openapi/components/responses/{success-update-room-anonymous.yaml => success-update-room-access.yaml} (50%) create mode 100644 meet-ce/backend/openapi/components/schemas/meet-room-access-config.yaml delete mode 100644 meet-ce/backend/openapi/components/schemas/meet-room-anonymous-config.yaml diff --git a/meet-ce/backend/openapi/components/parameters/room-x-fields-header.yaml b/meet-ce/backend/openapi/components/parameters/room-x-fields-header.yaml index ff3cefcb..68738127 100644 --- a/meet-ce/backend/openapi/components/parameters/room-x-fields-header.yaml +++ b/meet-ce/backend/openapi/components/parameters/room-x-fields-header.yaml @@ -9,5 +9,5 @@ schema: type: string examples: basic: - value: 'roomId,roomName,accessUrl' + value: 'roomId,roomName,status' summary: Only return basic room information diff --git a/meet-ce/backend/openapi/components/requestBodies/update-room-access-request.yaml b/meet-ce/backend/openapi/components/requestBodies/update-room-access-request.yaml new file mode 100644 index 00000000..4d05b3d5 --- /dev/null +++ b/meet-ce/backend/openapi/components/requestBodies/update-room-access-request.yaml @@ -0,0 +1,9 @@ +description: Room access configuration update options +required: true +content: + application/json: + schema: + type: object + properties: + access: + $ref: '../schemas/meet-room-access-config.yaml' diff --git a/meet-ce/backend/openapi/components/requestBodies/update-room-anonymous-request.yaml b/meet-ce/backend/openapi/components/requestBodies/update-room-anonymous-request.yaml deleted file mode 100644 index 03ff3151..00000000 --- a/meet-ce/backend/openapi/components/requestBodies/update-room-anonymous-request.yaml +++ /dev/null @@ -1,9 +0,0 @@ -description: Room anonymous access configuration update options -required: true -content: - application/json: - schema: - type: object - properties: - config: - $ref: '../schemas/meet-room-anonymous-config.yaml' diff --git a/meet-ce/backend/openapi/components/responses/success-bulk-delete-rooms.yaml b/meet-ce/backend/openapi/components/responses/success-bulk-delete-rooms.yaml index 9d95f610..795cef37 100644 --- a/meet-ce/backend/openapi/components/responses/success-bulk-delete-rooms.yaml +++ b/meet-ce/backend/openapi/components/responses/success-bulk-delete-rooms.yaml @@ -70,14 +70,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: closed meetingEndAction: none _extraFields: @@ -94,14 +100,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: delete _extraFields: @@ -118,14 +130,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: close _extraFields: @@ -142,14 +160,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: delete _extraFields: @@ -166,14 +190,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: close _extraFields: diff --git a/meet-ce/backend/openapi/components/responses/success-create-room.yaml b/meet-ce/backend/openapi/components/responses/success-create-room.yaml index ed804b7c..44956e92 100644 --- a/meet-ce/backend/openapi/components/responses/success-create-room.yaml +++ b/meet-ce/backend/openapi/components/responses/success-create-room.yaml @@ -9,7 +9,7 @@ content: _extraFields: type: array description: > - List of extra fields that can be included in the response based on the `X-ExtraFields` header. + List of extra fields that can be included in the response based on the `X-ExtraFields` header. items: type: string example: config @@ -58,17 +58,23 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: open meetingEndAction: none - _extraFields: ["config"] + _extraFields: ['config'] headers: Location: description: URL of the newly created room diff --git a/meet-ce/backend/openapi/components/responses/success-delete-room-processed.yaml b/meet-ce/backend/openapi/components/responses/success-delete-room-processed.yaml index 44f2a4cc..58c73a6a 100644 --- a/meet-ce/backend/openapi/components/responses/success-delete-room-processed.yaml +++ b/meet-ce/backend/openapi/components/responses/success-delete-room-processed.yaml @@ -61,14 +61,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: closed meetingEndAction: none _extraFields: @@ -86,14 +92,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: close _extraFields: diff --git a/meet-ce/backend/openapi/components/responses/success-delete-room-scheduled.yaml b/meet-ce/backend/openapi/components/responses/success-delete-room-scheduled.yaml index dfae9ae1..bb031d00 100644 --- a/meet-ce/backend/openapi/components/responses/success-delete-room-scheduled.yaml +++ b/meet-ce/backend/openapi/components/responses/success-delete-room-scheduled.yaml @@ -36,14 +36,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: delete _extraFields: @@ -57,14 +63,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: delete _extraFields: @@ -78,14 +90,20 @@ content: roomName: room owner: 'admin' creationDate: 1620000000000 - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: active_meeting meetingEndAction: close _extraFields: diff --git a/meet-ce/backend/openapi/components/responses/success-get-room.yaml b/meet-ce/backend/openapi/components/responses/success-get-room.yaml index 9bd359a4..ea892dab 100644 --- a/meet-ce/backend/openapi/components/responses/success-get-room.yaml +++ b/meet-ce/backend/openapi/components/responses/success-get-room.yaml @@ -15,7 +15,7 @@ content: example: config examples: default_room_details: - summary: Full room details response + summary: Full room details response without extra fields value: roomId: 'room-123' roomName: 'room' @@ -58,14 +58,20 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: open meetingEndAction: none _extraFields: @@ -73,16 +79,6 @@ content: fields=roomId,roomName,creationDate,autoDeletionDate,config: summary: Room details with roomId, roomName, creationDate, autoDeletionDate, and config - value: - roomId: 'room-123' - roomName: 'room' - creationDate: 1620000000000 - autoDeletionDate: 1900000000000 - _extraFields: - - config - - extraFields=config: - summary: Room details with expanded config value: roomId: 'room-123' roomName: 'room' @@ -103,15 +99,98 @@ content: enabled: true _extraFields: - config - fields=anonymous: - summary: Response containing only anonymous access configuration + + extraFields=config: + summary: Room details with expanded config value: - anonymous: + roomId: 'room-123' + roomName: 'room' + owner: 'admin' + creationDate: 1620000000000 + autoDeletionDate: 1900000000000 + autoDeletionPolicy: + withMeeting: when_meeting_ends + withRecordings: close + config: + chat: + enabled: true + recording: + enabled: true + layout: grid + encoding: H264_720P_30 + virtualBackground: + enabled: true + e2ee: + enabled: false + captions: + enabled: true + roles: moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' + permissions: + canRecord: true + canRetrieveRecordings: true + canDeleteRecordings: true + canJoinMeeting: true + canShareAccessLinks: true + canMakeModerator: true + canKickParticipants: true + canEndMeeting: true + canPublishVideo: true + canPublishAudio: true + canShareScreen: true + canReadChat: true + canWriteChat: true + canChangeVirtualBackground: true speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' + permissions: + canRecord: false + canRetrieveRecordings: true + canDeleteRecordings: false + canJoinMeeting: true + canShareAccessLinks: false + canMakeModerator: false + canKickParticipants: false + canEndMeeting: false + canPublishVideo: true + canPublishAudio: true + canShareScreen: true + canReadChat: true + canWriteChat: true + canChangeVirtualBackground: true + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' + status: open + meetingEndAction: none + _extraFields: + - config + fields=access: + summary: Response containing only access configuration + value: + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' _extraFields: - config diff --git a/meet-ce/backend/openapi/components/responses/success-get-rooms.yaml b/meet-ce/backend/openapi/components/responses/success-get-rooms.yaml index e7cd3758..0aeec474 100644 --- a/meet-ce/backend/openapi/components/responses/success-get-rooms.yaml +++ b/meet-ce/backend/openapi/components/responses/success-get-rooms.yaml @@ -20,7 +20,7 @@ content: examples: default_room_details: - summary: Full room details response with multiple rooms + summary: Full room details without extra fields for multiple rooms value: rooms: - roomId: 'room-123' @@ -64,14 +64,20 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: open meetingEndAction: none - roomId: 'room-456' @@ -115,14 +121,20 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: false - accessUrl: 'https://example.com/room/room-456?secret=789012' - speaker: + access: + anonymous: + moderator: + enabled: false + url: 'https://example.com/room/room-456?secret=789012' + speaker: + enabled: true + url: 'https://example.com/room/room-456?secret=210987' + recording: + enabled: true + url: 'https://example.com/room/room-456/recordings?secret=345678' + registered: enabled: true - accessUrl: 'https://example.com/room/room-456?secret=210987' - accessUrl: 'https://example.com/room/room-456' + url: 'https://example.com/room/room-456' status: open meetingEndAction: none _extraFields: @@ -199,14 +211,20 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=123456' - speaker: - enabled: true - accessUrl: 'https://example.com/room/room-123?secret=654321' - accessUrl: 'https://example.com/room/room-123' + access: + anonymous: + moderator: + enabled: true + url: 'https://example.com/room/room-123?secret=123456' + speaker: + enabled: true + url: 'https://example.com/room/room-123?secret=654321' + recording: + enabled: true + url: 'https://example.com/room/room-123/recordings?secret=987654' + registered: + enabled: false + url: 'https://example.com/room/room-123' status: open meetingEndAction: none - roomId: 'room-456' @@ -273,14 +291,20 @@ content: canReadChat: true canWriteChat: true canChangeVirtualBackground: true - anonymous: - moderator: - enabled: false - accessUrl: 'https://example.com/room/room-456?secret=789012' - speaker: + access: + anonymous: + moderator: + enabled: false + url: 'https://example.com/room/room-456?secret=789012' + speaker: + enabled: true + url: 'https://example.com/room/room-456?secret=210987' + recording: + enabled: true + url: 'https://example.com/room/room-456/recordings?secret=345678' + registered: enabled: true - accessUrl: 'https://example.com/room/room-456?secret=210987' - accessUrl: 'https://example.com/room/room-456' + url: 'https://example.com/room/room-456' status: open meetingEndAction: none _extraFields: diff --git a/meet-ce/backend/openapi/components/responses/success-update-room-anonymous.yaml b/meet-ce/backend/openapi/components/responses/success-update-room-access.yaml similarity index 50% rename from meet-ce/backend/openapi/components/responses/success-update-room-anonymous.yaml rename to meet-ce/backend/openapi/components/responses/success-update-room-access.yaml index d739418f..0a80aef8 100644 --- a/meet-ce/backend/openapi/components/responses/success-update-room-anonymous.yaml +++ b/meet-ce/backend/openapi/components/responses/success-update-room-access.yaml @@ -1,4 +1,4 @@ -description: Success response for updating room anonymous access configuration +description: Success response for updating room access configuration content: application/json: schema: @@ -7,4 +7,4 @@ content: message: type: string example: - message: Anonymous access config for room 'room-123' updated successfully + message: Access config for room 'room-123' updated successfully diff --git a/meet-ce/backend/openapi/components/schemas/meet-room-access-config.yaml b/meet-ce/backend/openapi/components/schemas/meet-room-access-config.yaml new file mode 100644 index 00000000..ea3feffb --- /dev/null +++ b/meet-ce/backend/openapi/components/schemas/meet-room-access-config.yaml @@ -0,0 +1,46 @@ +type: object +properties: + anonymous: + type: object + properties: + moderator: + type: object + properties: + enabled: + type: boolean + default: true + example: true + description: | + Enables or disables anonymous access for the moderator role. + speaker: + type: object + properties: + enabled: + type: boolean + default: true + example: true + description: | + Enables or disables anonymous access for the speaker role. + recording: + type: object + properties: + enabled: + type: boolean + default: true + example: true + description: | + Enables or disables anonymous access for recordings in the room. This also controls whether individual anonymous recording URLs can be generated. + registered: + type: object + properties: + enabled: + type: boolean + default: true + example: true + description: | + Enables or disables access for registered users. + **Note**: admins, owner and members of the room will always have access regardless of this setting. +description: | + Configuration for room access. + + All fields are optional. If not specified, current configuration will be maintained. diff --git a/meet-ce/backend/openapi/components/schemas/meet-room-anonymous-config.yaml b/meet-ce/backend/openapi/components/schemas/meet-room-anonymous-config.yaml deleted file mode 100644 index e824804f..00000000 --- a/meet-ce/backend/openapi/components/schemas/meet-room-anonymous-config.yaml +++ /dev/null @@ -1,24 +0,0 @@ -type: object -properties: - moderator: - type: object - properties: - enabled: - type: boolean - default: true - example: true - description: | - Enables or disables anonymous access for the moderator role. - speaker: - type: object - properties: - enabled: - type: boolean - default: true - example: true - description: | - Enables or disables anonymous access for the speaker role. -description: | - Configuration for anonymous access. - - Both moderator and speaker fields are optional. If not specified, current configuration will be maintained. diff --git a/meet-ce/backend/openapi/components/schemas/meet-room-options.yaml b/meet-ce/backend/openapi/components/schemas/meet-room-options.yaml index 2b6651a5..9b93808b 100644 --- a/meet-ce/backend/openapi/components/schemas/meet-room-options.yaml +++ b/meet-ce/backend/openapi/components/schemas/meet-room-options.yaml @@ -68,12 +68,15 @@ properties: - Speaker: Permissions to publish audio and video streams. You can customize this by providing partial permissions for each role (only specify the permissions you want to override). - anonymous: - $ref: meet-room-anonymous-config.yaml + access: + $ref: meet-room-access-config.yaml description: | - Configuration for anonymous access to the room. + Configuration for room access. - By default (if not specified), anonymous access is enabled for both moderators and speakers. - You can customize this behavior by disabling anonymous access for specific roles (moderator/speaker) with per-role `enabled: false` + By default (if not specified), anonymous access is enabled for both moderator and speaker roles, and for recording access. + However, registered access is disabled by default. + You can customize this behavior by disabling access for specific scopes and roles + (`registered`, `anonymous.moderator`, `anonymous.speaker`, `anonymous.recording`) using `enabled: false`. - Permissions for anonymous users are determined by the room's role permissions. + Permissions for anonymous users are determined by the room's role permissions. For registered users (who are not admins or members of the room), + permissions will be the same as the speaker role. diff --git a/meet-ce/backend/openapi/components/schemas/meet-room.yaml b/meet-ce/backend/openapi/components/schemas/meet-room.yaml index 6b0f87ff..2a9f01df 100644 --- a/meet-ce/backend/openapi/components/schemas/meet-room.yaml +++ b/meet-ce/backend/openapi/components/schemas/meet-room.yaml @@ -62,7 +62,7 @@ properties: Policy for automatic deletion when the room has recordings. Options are: - force: The room and its recordings will be deleted. - close: The room will be closed instead of deleted, maintaining its recordings. - config (excluded by default): + config: description: | Room configuration (chat, recording, virtual background, e2ee, captions).

@@ -70,7 +70,7 @@ properties: **Excluded from responses by default to reduce payload size.** To include it in the response: - **POST requests:** use the `X-ExtraFields: config` header - - **Other requests:** use either the `extraFields=config` query parameter or the `X-ExtraFields: config` header $ref: meet-room-config.yaml#/MeetRoomConfig + - **Other requests:** use either the `extraFields=config` query parameter or the `X-ExtraFields: config` header $ref: meet-room-config.yaml#/MeetRoomConfig # maxParticipants: # type: integer @@ -97,49 +97,71 @@ properties: $ref: meet-permissions.yaml description: > The complete set of permissions for the speaker role. These define what speakers can do in the meeting. - anonymous: + access: description: > - Configuration for anonymous access to the room. Defines which roles have anonymous access enabled and their access URLs. + Access configuration and generated access URLs for the room. type: object properties: - moderator: + anonymous: + type: object + properties: + moderator: + type: object + properties: + enabled: + type: boolean + example: true + description: > + Whether anonymous access for the moderator role is enabled. + url: + type: string + format: uri + example: 'http://localhost:6080/room/room-123?secret=123456' + description: > + The URL for anonymous moderators to access the room. + speaker: + type: object + properties: + enabled: + type: boolean + example: true + description: > + Whether anonymous access for the speaker role is enabled. + url: + type: string + format: uri + example: 'http://localhost:6080/room/room-123?secret=654321' + description: > + The URL for anonymous speakers to access the room. + recording: + type: object + properties: + enabled: + type: boolean + example: true + description: > + Whether anonymous access for recordings in the room is enabled. + url: + type: string + format: uri + example: 'http://localhost:6080/room/room-123?secret=987654' + description: > + The URL for anonymous access to the room's recordings. + registered: type: object properties: enabled: type: boolean example: true description: > - Whether anonymous access with moderator role is enabled. - accessUrl: + Whether access for registered users is enabled. + **Note**: admins, owner and members of the room will always have access regardless of this setting. + url: type: string format: uri - example: 'http://localhost:6080/room/room-123?secret=123456' + example: 'http://localhost:6080/room/room-123' description: > - The URL for anonymous moderators to access the room. - speaker: - type: object - properties: - enabled: - type: boolean - example: true - description: > - Whether anonymous access with speaker role is enabled. - accessUrl: - type: string - format: uri - example: 'http://localhost:6080/room/room-123?secret=654321' - description: > - The URL for anonymous speakers to access the room. - accessUrl: - type: string - format: uri - example: 'http://localhost:6080/room/room-123' - description: | - The general access URL for authenticated users to join the room. - - This URL should be used by: - - The room owner (registered Meet user who created the room) - - Registered Meet users who are members of the room + The URL for registered users to access the room. status: type: string enum: diff --git a/meet-ce/backend/openapi/openvidu-meet-api.yaml b/meet-ce/backend/openapi/openvidu-meet-api.yaml index 795406f4..2859118e 100644 --- a/meet-ce/backend/openapi/openvidu-meet-api.yaml +++ b/meet-ce/backend/openapi/openvidu-meet-api.yaml @@ -17,8 +17,8 @@ paths: $ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1config' /rooms/{roomId}/roles: $ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1roles' - /rooms/{roomId}/anonymous: - $ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1anonymous' + /rooms/{roomId}/access: + $ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1access' /rooms/{roomId}/status: $ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1status' /rooms/{roomId}/members: diff --git a/meet-ce/backend/openapi/paths/rooms.yaml b/meet-ce/backend/openapi/paths/rooms.yaml index 22eb6001..e29e29e6 100644 --- a/meet-ce/backend/openapi/paths/rooms.yaml +++ b/meet-ce/backend/openapi/paths/rooms.yaml @@ -290,14 +290,15 @@ $ref: '../components/responses/validation-error.yaml' '500': $ref: '../components/responses/internal-server-error.yaml' -/rooms/{roomId}/anonymous: +/rooms/{roomId}/access: put: - operationId: updateRoomAnonymous - summary: Update anonymous access config for a room + operationId: updateRoomAccess + summary: Update access config for a room description: | - Updates the anonymous access configuration for the specified room. + Updates the access configuration for the specified room. - This allows you to enable or disable anonymous access for specific roles (moderator/speaker). + This allows you to enable or disable access for registered users and anonymous roles + (moderator/speaker/recording). tags: - OpenVidu Meet - Rooms security: @@ -306,10 +307,10 @@ parameters: - $ref: '../components/parameters/room-id-path.yaml' requestBody: - $ref: '../components/requestBodies/update-room-anonymous-request.yaml' + $ref: '../components/requestBodies/update-room-access-request.yaml' responses: '200': - $ref: '../components/responses/success-update-room-anonymous.yaml' + $ref: '../components/responses/success-update-room-access.yaml' '401': $ref: '../components/responses/unauthorized-error.yaml' '403': diff --git a/meet-ce/backend/src/controllers/room.controller.ts b/meet-ce/backend/src/controllers/room.controller.ts index bccc213c..55b7bd2c 100644 --- a/meet-ce/backend/src/controllers/room.controller.ts +++ b/meet-ce/backend/src/controllers/room.controller.ts @@ -257,18 +257,18 @@ export const updateRoomRoles = async (req: Request, res: Response) => { } }; -export const updateRoomAnonymous = async (req: Request, res: Response) => { +export const updateRoomAccess = async (req: Request, res: Response) => { const logger = container.get(LoggerService); const roomService = container.get(RoomService); - const { anonymous } = req.body; + const { access } = req.body; const { roomId } = req.params; - logger.verbose(`Updating anonymous access config for room '${roomId}'`); + logger.verbose(`Updating access config for room '${roomId}'`); try { - await roomService.updateMeetRoomAnonymous(roomId, anonymous); - return res.status(200).json({ message: `Anonymous access config for room '${roomId}' updated successfully` }); + await roomService.updateMeetRoomAccess(roomId, access); + return res.status(200).json({ message: `Access config for room '${roomId}' updated successfully` }); } catch (error) { - handleError(res, error, `updating anonymous access config for room '${roomId}'`); + handleError(res, error, `updating access config for room '${roomId}'`); } }; diff --git a/meet-ce/backend/src/helpers/room.helper.ts b/meet-ce/backend/src/helpers/room.helper.ts index b4a52e4f..a777ad81 100644 --- a/meet-ce/backend/src/helpers/room.helper.ts +++ b/meet-ce/backend/src/helpers/room.helper.ts @@ -2,7 +2,7 @@ import { MEET_ROOM_EXTRA_FIELDS, MEET_ROOM_FIELDS, MeetRoom, - MeetRoomAnonymous, + MeetRoomAccess, MeetRoomExtraField, MeetRoomField, MeetRoomMemberPermissions, @@ -77,12 +77,20 @@ export class MeetRoomHelper { autoDeletionPolicy: room.autoDeletionPolicy, config: room.config, roles: room.roles, - anonymous: { - moderator: { - enabled: room.anonymous.moderator.enabled + access: { + anonymous: { + moderator: { + enabled: room.access.anonymous.moderator.enabled + }, + speaker: { + enabled: room.access.anonymous.speaker.enabled + }, + recording: { + enabled: room.access.anonymous.recording.enabled + } }, - speaker: { - enabled: room.anonymous.speaker.enabled + registered: { + enabled: room.access.registered.enabled } } // maxParticipants: room.maxParticipants @@ -90,25 +98,33 @@ export class MeetRoomHelper { } /** - * Extracts speaker and moderator secrets from MeetRoom anonymous access URLs. + * Extracts speaker, moderator, and recording secrets from MeetRoom anonymous access URLs. * - * This method parses the 'secret' query parameter from both speaker and moderator + * This method parses the 'secret' query parameter from speaker, moderator, and recording * anonymous access URLs associated with the meeting room. * - * @param room - The anonymous access configuration of the MeetRoom from which to extract secrets. + * @param roomAccess - The access configuration of the MeetRoom from which to extract secrets. * @returns An object containing the extracted secrets with the following properties: * - speakerSecret: The secret extracted from the speaker anonymous access URL * - moderatorSecret: The secret extracted from the moderator anonymous access URL + * - recordingSecret: The secret extracted from the recording anonymous access URL */ - static extractSecretsFromRoom(anonymous: MeetRoomAnonymous): { speakerSecret: string; moderatorSecret: string } { - const speakerUrl = anonymous.speaker.accessUrl; - const moderatorUrl = anonymous.moderator.accessUrl; + static extractSecretsFromRoom(roomAccess: MeetRoomAccess): { + speakerSecret: string; + moderatorSecret: string; + recordingSecret: string; + } { + const speakerUrl = roomAccess.anonymous.speaker.url; + const moderatorUrl = roomAccess.anonymous.moderator.url; + const recordingUrl = roomAccess.anonymous.recording.url; const parsedSpeakerUrl = new URL(speakerUrl); const speakerSecret = parsedSpeakerUrl.searchParams.get('secret') || ''; const parsedModeratorUrl = new URL(moderatorUrl); const moderatorSecret = parsedModeratorUrl.searchParams.get('secret') || ''; - return { speakerSecret, moderatorSecret }; + const parsedRecordingUrl = new URL(recordingUrl); + const recordingSecret = parsedRecordingUrl.searchParams.get('secret') || ''; + return { speakerSecret, moderatorSecret, recordingSecret }; } /** diff --git a/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts b/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts index c6837434..81328e15 100644 --- a/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts +++ b/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts @@ -8,7 +8,7 @@ import { RoomFiltersSchema, RoomOptionsSchema, RoomQueryFieldsSchema, - UpdateRoomAnonymousReqSchema, + UpdateRoomAccessReqSchema, UpdateRoomConfigReqSchema, UpdateRoomRolesReqSchema, UpdateRoomStatusReqSchema @@ -152,8 +152,8 @@ export const validateUpdateRoomRolesReq = (req: Request, res: Response, next: Ne next(); }; -export const validateUpdateRoomAnonymousReq = (req: Request, res: Response, next: NextFunction) => { - const { success, error, data } = UpdateRoomAnonymousReqSchema.safeParse(req.body); +export const validateUpdateRoomAccessReq = (req: Request, res: Response, next: NextFunction) => { + const { success, error, data } = UpdateRoomAccessReqSchema.safeParse(req.body); if (!success) { return rejectUnprocessableRequest(res, error); diff --git a/meet-ce/backend/src/migrations/room-migrations.ts b/meet-ce/backend/src/migrations/room-migrations.ts index 4f6096b4..b267989b 100644 --- a/meet-ce/backend/src/migrations/room-migrations.ts +++ b/meet-ce/backend/src/migrations/room-migrations.ts @@ -1,4 +1,5 @@ import { MeetRecordingEncodingPreset, MeetRecordingLayout } from '@openvidu-meet/typings'; +import { uid as secureUid } from 'uid/secure'; import { MEET_ENV } from '../environment.js'; import { generateSchemaMigrationName, SchemaMigrationMap, SchemaTransform } from '../models/migration.model.js'; import { meetRoomCollectionName, MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; @@ -63,17 +64,26 @@ const roomMigrationV2ToV3Transform: SchemaTransform = (room) = } } }; - room.anonymous = { - moderator: { - enabled: true, - accessUrl: legacyRoom.moderatorUrl! + room.access = { + anonymous: { + moderator: { + enabled: true, + url: legacyRoom.moderatorUrl! + }, + speaker: { + enabled: true, + url: legacyRoom.speakerUrl! + }, + recording: { + enabled: false, + url: `/room/${room.roomId}/recordings?secret=${secureUid(10)}` + } }, - speaker: { + registered: { enabled: true, - accessUrl: legacyRoom.speakerUrl! + url: `/room/${room.roomId}` } }; - room.accessUrl = `/room/${room.roomId}`; room.rolesUpdatedAt = Date.now(); delete legacyRoom.moderatorUrl; diff --git a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts index 5e226f34..c4392f59 100644 --- a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts +++ b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts @@ -200,26 +200,48 @@ const MeetRoomRolesSchema = new Schema( ); /** - * Sub-schema for anonymous access configuration. + * Sub-schema for access configuration. */ -const MeetRoomAnonymousSchema = new Schema( +const MeetRoomAccessSchema = new Schema( { - moderator: { - enabled: { - type: Boolean, - required: true + anonymous: { + moderator: { + enabled: { + type: Boolean, + required: true + }, + url: { + type: String, + required: true + } }, - accessUrl: { - type: String, - required: true + speaker: { + enabled: { + type: Boolean, + required: true + }, + url: { + type: String, + required: true + } + }, + recording: { + enabled: { + type: Boolean, + required: true + }, + url: { + type: String, + required: true + } } }, - speaker: { + registered: { enabled: { type: Boolean, required: true }, - accessUrl: { + url: { type: String, required: true } @@ -300,12 +322,8 @@ const MeetRoomSchema = new Schema( type: MeetRoomRolesSchema, required: true }, - anonymous: { - type: MeetRoomAnonymousSchema, - required: true - }, - accessUrl: { - type: String, + access: { + type: MeetRoomAccessSchema, required: true }, status: { diff --git a/meet-ce/backend/src/models/zod-schemas/room.schema.ts b/meet-ce/backend/src/models/zod-schemas/room.schema.ts index 9e1dd2e2..2c1ac1af 100644 --- a/meet-ce/backend/src/models/zod-schemas/room.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/room.schema.ts @@ -11,7 +11,7 @@ import { MeetRecordingEncodingPreset, MeetRecordingLayout, MeetRecordingVideoCodec, - MeetRoomAnonymousConfig, + MeetRoomAccessConfig, MeetRoomAutoDeletionPolicy, MeetRoomCaptionsConfig, MeetRoomConfig, @@ -296,13 +296,27 @@ const RoomRolesConfigSchema: z.ZodType = z.object({ .optional() }); -const RoomAnonymousConfigSchema: z.ZodType = z.object({ - moderator: z +const RoomAccessConfigSchema: z.ZodType = z.object({ + anonymous: z .object({ - enabled: z.boolean() + moderator: z + .object({ + enabled: z.boolean() + }) + .optional(), + speaker: z + .object({ + enabled: z.boolean() + }) + .optional(), + recording: z + .object({ + enabled: z.boolean() + }) + .optional() }) .optional(), - speaker: z + registered: z .object({ enabled: z.boolean() }) @@ -359,9 +373,13 @@ export const RoomOptionsSchema: z.ZodType = z.object({ captions: { enabled: true } }), roles: RoomRolesConfigSchema.optional(), - anonymous: RoomAnonymousConfigSchema.optional().default({ - moderator: { enabled: true }, - speaker: { enabled: true } + access: RoomAccessConfigSchema.optional().default({ + anonymous: { + moderator: { enabled: true }, + speaker: { enabled: true }, + recording: { enabled: true } + }, + registered: { enabled: false } }) // maxParticipants: z // .number() @@ -556,8 +574,8 @@ export const UpdateRoomRolesReqSchema = z.object({ roles: RoomRolesConfigSchema }); -export const UpdateRoomAnonymousReqSchema = z.object({ - anonymous: RoomAnonymousConfigSchema +export const UpdateRoomAccessReqSchema = z.object({ + access: RoomAccessConfigSchema }); export const UpdateRoomStatusReqSchema = z.object({ diff --git a/meet-ce/backend/src/repositories/room.repository.ts b/meet-ce/backend/src/repositories/room.repository.ts index 8cb9d22f..0a56faa1 100644 --- a/meet-ce/backend/src/repositories/room.repository.ts +++ b/meet-ce/backend/src/repositories/room.repository.ts @@ -261,19 +261,25 @@ export class RoomRepository extends BaseRepository { * @returns Normalized partial room data */ private normalizeRoomForStorage(room: Partial): Partial { - if (room.accessUrl) { - room.accessUrl = this.extractPathFromUrl(room.accessUrl); + const registeredUrl = room.access?.registered.url; + const moderatorUrl = room.access?.anonymous.moderator.url; + const speakerUrl = room.access?.anonymous.speaker.url; + const recordingUrl = room.access?.anonymous.recording.url; + + if (registeredUrl) { + room.access!.registered.url = this.extractPathFromUrl(registeredUrl); } - const moderatorUrl = room.anonymous?.moderator.accessUrl; - const speakerUrl = room.anonymous?.speaker.accessUrl; - if (moderatorUrl) { - room.anonymous!.moderator.accessUrl = this.extractPathFromUrl(moderatorUrl); + room.access!.anonymous.moderator.url = this.extractPathFromUrl(moderatorUrl); } if (speakerUrl) { - room.anonymous!.speaker.accessUrl = this.extractPathFromUrl(speakerUrl); + room.access!.anonymous.speaker.url = this.extractPathFromUrl(speakerUrl); + } + + if (recordingUrl) { + room.access!.anonymous.recording.url = this.extractPathFromUrl(recordingUrl); } return room; @@ -330,19 +336,25 @@ export class RoomRepository extends BaseRepository { private enrichRoomWithBaseUrls(room: MeetRoom): MeetRoom { const baseUrl = getBaseUrl(); - if (room.accessUrl) { - room.accessUrl = `${baseUrl}${room.accessUrl}`; + const registeredUrl = room.access?.registered.url; + const moderatorUrl = room.access?.anonymous.moderator.url; + const speakerUrl = room.access?.anonymous.speaker.url; + const recordingUrl = room.access?.anonymous.recording.url; + + if (registeredUrl) { + room.access!.registered.url = `${baseUrl}${registeredUrl}`; } - const moderatorUrl = room.anonymous?.moderator.accessUrl; - const speakerUrl = room.anonymous?.speaker.accessUrl; - if (moderatorUrl) { - room.anonymous!.moderator.accessUrl = `${baseUrl}${moderatorUrl}`; + room.access!.anonymous.moderator.url = `${baseUrl}${moderatorUrl}`; } if (speakerUrl) { - room.anonymous!.speaker.accessUrl = `${baseUrl}${speakerUrl}`; + room.access!.anonymous.speaker.url = `${baseUrl}${speakerUrl}`; + } + + if (recordingUrl) { + room.access!.anonymous.recording.url = `${baseUrl}${recordingUrl}`; } return room; diff --git a/meet-ce/backend/src/routes/room.routes.ts b/meet-ce/backend/src/routes/room.routes.ts index 1994166e..9ebaf417 100644 --- a/meet-ce/backend/src/routes/room.routes.ts +++ b/meet-ce/backend/src/routes/room.routes.ts @@ -22,7 +22,7 @@ import { validateDeleteRoomReq, validateGetRoomReq, validateGetRoomsReq, - validateUpdateRoomAnonymousReq, + validateUpdateRoomAccessReq, validateUpdateRoomConfigReq, validateUpdateRoomRolesReq, validateUpdateRoomStatusReq, @@ -116,12 +116,12 @@ roomRouter.put( roomCtrl.updateRoomRoles ); roomRouter.put( - '/:roomId/anonymous', + '/:roomId/access', withAuth(apiKeyValidator, accessTokenValidator(MeetUserRole.ADMIN, MeetUserRole.USER)), withValidRoomId, - validateUpdateRoomAnonymousReq, + validateUpdateRoomAccessReq, authorizeRoomManagement, - roomCtrl.updateRoomAnonymous + roomCtrl.updateRoomAccess ); // Room Member Routes diff --git a/meet-ce/backend/src/services/room-member.service.ts b/meet-ce/backend/src/services/room-member.service.ts index 816dcd60..70885037 100644 --- a/meet-ce/backend/src/services/room-member.service.ts +++ b/meet-ce/backend/src/services/room-member.service.ts @@ -418,10 +418,10 @@ export class RoomMemberService { } else { // If secret matches anonymous access URL secret, assign role and permissions based on it baseRole = await this.getRoomMemberRoleBySecret(roomId, secret); - const { roles, anonymous } = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); + const { roles, access } = await this.roomService.getMeetRoom(roomId, ['roles', 'access']); // Check that anonymous access is enabled for the role - if (!anonymous[baseRole].enabled) { + if (!access.anonymous[baseRole].enabled) { throw errorAnonymousAccessDisabled(roomId, baseRole); } @@ -609,8 +609,8 @@ export class RoomMemberService { * @throws Error if the provided secret doesn't match any of the room's secrets (unauthorized) */ protected async getRoomMemberRoleBySecret(roomId: string, secret: string): Promise { - const { anonymous } = await this.roomService.getMeetRoom(roomId, ['anonymous']); - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); + const { access } = await this.roomService.getMeetRoom(roomId, ['access']); + const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(access); switch (secret) { case moderatorSecret: @@ -778,7 +778,7 @@ export class RoomMemberService { newRole: MeetRoomMemberRole ): Promise { try { - const { roles, anonymous } = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); + const { roles, access } = await this.roomService.getMeetRoom(roomId, ['roles', 'access']); const participant = await this.getParticipantFromMeeting(roomId, participantIdentity); const metadata: MeetRoomMemberTokenMetadata = this.tokenService.parseRoomMemberTokenMetadata( participant.metadata @@ -791,7 +791,7 @@ export class RoomMemberService { await this.livekitService.updateParticipantMetadata(roomId, participantIdentity, JSON.stringify(metadata)); - const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); + const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(access); const secret = newRole === MeetRoomMemberRole.MODERATOR ? moderatorSecret : speakerSecret; await this.frontendEventService.sendParticipantRoleUpdatedSignal( roomId, diff --git a/meet-ce/backend/src/services/room.service.ts b/meet-ce/backend/src/services/room.service.ts index d34eb3d6..74da80f2 100644 --- a/meet-ce/backend/src/services/room.service.ts +++ b/meet-ce/backend/src/services/room.service.ts @@ -1,8 +1,8 @@ import { MeetingEndAction, MeetRoom, - MeetRoomAnonymous, - MeetRoomAnonymousConfig, + MeetRoomAccess, + MeetRoomAccessConfig, MeetRoomConfig, MeetRoomDeletionErrorCode, MeetRoomDeletionPolicyWithMeeting, @@ -81,7 +81,7 @@ export class RoomService { * */ async createMeetRoom(roomOptions: MeetRoomOptions): Promise { - const { roomName, autoDeletionDate, autoDeletionPolicy, config, roles, anonymous } = roomOptions; + const { roomName, autoDeletionDate, autoDeletionPolicy, config, roles, access } = roomOptions; // Generate a unique room ID based on the room name const roomIdPrefix = MeetRoomHelper.createRoomIdPrefixFromRoomName(roomName!) || 'room'; @@ -135,14 +135,24 @@ export class RoomService { } }; - const anonymousConfig: MeetRoomAnonymous = { - moderator: { - enabled: anonymous?.moderator?.enabled ?? true, - accessUrl: `/room/${roomId}?secret=${secureUid(10)}` + const accessConfig: MeetRoomAccess = { + anonymous: { + moderator: { + enabled: access?.anonymous?.moderator?.enabled ?? true, + url: `/room/${roomId}?secret=${secureUid(10)}` + }, + speaker: { + enabled: access?.anonymous?.speaker?.enabled ?? true, + url: `/room/${roomId}?secret=${secureUid(10)}` + }, + recording: { + enabled: access?.anonymous?.recording?.enabled ?? true, + url: `/room/${roomId}/recordings?secret=${secureUid(10)}` + } }, - speaker: { - enabled: anonymous?.speaker?.enabled ?? true, - accessUrl: `/room/${roomId}?secret=${secureUid(10)}` + registered: { + enabled: access?.registered?.enabled ?? false, + url: `/room/${roomId}` } }; @@ -157,8 +167,7 @@ export class RoomService { autoDeletionPolicy: autoDeletionDate ? autoDeletionPolicy : undefined, config: config as MeetRoomConfig, roles: roomRoles, - anonymous: anonymousConfig, - accessUrl: `/room/${roomId}`, + access: accessConfig, status: MeetRoomStatus.OPEN, rolesUpdatedAt: now, meetingEndAction: MeetingEndAction.NONE @@ -284,24 +293,24 @@ export class RoomService { } /** - * Updates the anonymous access configuration of a specific meeting room. + * Updates the access configuration of a specific meeting room. * * @param roomId - The unique identifier of the meeting room to update - * @param anonymous - Partial anonymous config with the fields to update + * @param access - Partial access config with the fields to update * @returns A Promise that resolves to the updated MeetRoom object */ - async updateMeetRoomAnonymous(roomId: string, anonymous: MeetRoomAnonymousConfig): Promise { - const room = await this.getMeetRoom(roomId, ['anonymous', 'status']); + async updateMeetRoomAccess(roomId: string, access: MeetRoomAccessConfig): Promise { + const room = await this.getMeetRoom(roomId, ['access', 'status']); if (room.status === MeetRoomStatus.ACTIVE_MEETING) { throw errorRoomActiveMeeting(roomId); } - // Merge existing anonymous config with new anonymous config (partial update) - const updatedAnonymous = merge({}, room.anonymous, anonymous); + // Merge existing access config with new access config (partial update) + const updatedAccess = merge({}, room.access, access); return this.roomRepository.updatePartial(roomId, { - anonymous: updatedAnonymous, + access: updatedAccess, rolesUpdatedAt: Date.now() }); } @@ -870,9 +879,10 @@ export class RoomService { * @throws Error if room not found */ async isValidRoomSecret(roomId: string, secret: string): Promise { - const { anonymous } = await this.getMeetRoom(roomId, ['anonymous']); - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); - return secret === moderatorSecret || secret === speakerSecret; + const { access } = await this.getMeetRoom(roomId, ['access']); + const { moderatorSecret, speakerSecret, recordingSecret } = MeetRoomHelper.extractSecretsFromRoom(access); + const allSecrets = [moderatorSecret, speakerSecret, recordingSecret]; + return allSecrets.includes(secret); } /** diff --git a/meet-ce/backend/tests/helpers/assertion-helpers.ts b/meet-ce/backend/tests/helpers/assertion-helpers.ts index 80b37c2b..8cd3df8d 100644 --- a/meet-ce/backend/tests/helpers/assertion-helpers.ts +++ b/meet-ce/backend/tests/helpers/assertion-helpers.ts @@ -169,18 +169,23 @@ export const expectValidRoom = ( expect(room.owner).toBeDefined(); expect(room.roles).toBeDefined(); - expect(room.anonymous).toBeDefined(); - expect(room.anonymous.moderator).toBeDefined(); - expect(room.anonymous.speaker).toBeDefined(); - expect(room.anonymous.moderator.enabled).toBeDefined(); - expect(room.anonymous.speaker.enabled).toBeDefined(); - expect(room.anonymous.moderator.accessUrl).toBeDefined(); - expect(room.anonymous.speaker.accessUrl).toBeDefined(); - expect(room.anonymous.moderator.accessUrl).toContain(room.roomId); - expect(room.anonymous.speaker.accessUrl).toContain(room.roomId); - - expect(room.accessUrl).toBeDefined(); - expect(room.accessUrl).toContain(room.roomId); + expect(room.access).toBeDefined(); + expect(room.access.anonymous.moderator).toBeDefined(); + expect(room.access.anonymous.speaker).toBeDefined(); + expect(room.access.anonymous.recording).toBeDefined(); + expect(room.access.registered).toBeDefined(); + expect(room.access.anonymous.moderator.enabled).toBeDefined(); + expect(room.access.anonymous.speaker.enabled).toBeDefined(); + expect(room.access.anonymous.recording.enabled).toBeDefined(); + expect(room.access.registered.enabled).toBeDefined(); + expect(room.access.anonymous.moderator.url).toBeDefined(); + expect(room.access.anonymous.speaker.url).toBeDefined(); + expect(room.access.anonymous.recording.url).toBeDefined(); + expect(room.access.registered.url).toBeDefined(); + expect(room.access.anonymous.moderator.url).toContain(room.roomId); + expect(room.access.anonymous.speaker.url).toContain(room.roomId); + expect(room.access.anonymous.recording.url).toContain(room.roomId); + expect(room.access.registered.url).toContain(room.roomId); expect(room.status).toBeDefined(); expect(room.status).toEqual(status || MeetRoomStatus.OPEN); diff --git a/meet-ce/backend/tests/helpers/request-helpers.ts b/meet-ce/backend/tests/helpers/request-helpers.ts index c97f0f83..39ab7f69 100644 --- a/meet-ce/backend/tests/helpers/request-helpers.ts +++ b/meet-ce/backend/tests/helpers/request-helpers.ts @@ -6,7 +6,7 @@ import { MeetRecordingInfo, MeetRecordingStatus, MeetRoom, - MeetRoomAnonymousConfig, + MeetRoomAccessConfig, MeetRoomConfig, MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings, @@ -547,13 +547,13 @@ export const updateRoomRoles = async (roomId: string, rolesConfig: MeetRoomRoles .send({ roles: rolesConfig }); }; -export const updateRoomAnonymousConfig = async (roomId: string, anonymousConfig: MeetRoomAnonymousConfig) => { +export const updateRoomAccessConfig = async (roomId: string, accessConfig: MeetRoomAccessConfig) => { checkAppIsRunning(); return await request(app) - .put(getFullPath(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${roomId}/anonymous`)) + .put(getFullPath(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${roomId}/access`)) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) - .send({ anonymous: anonymousConfig }); + .send({ access: accessConfig }); }; export const deleteRoom = async ( diff --git a/meet-ce/backend/tests/helpers/test-scenarios.ts b/meet-ce/backend/tests/helpers/test-scenarios.ts index a16c1d44..a0f14e4d 100644 --- a/meet-ce/backend/tests/helpers/test-scenarios.ts +++ b/meet-ce/backend/tests/helpers/test-scenarios.ts @@ -51,7 +51,7 @@ export const setupSingleRoom = async ( }); // Extract the room secrets and generate room member tokens - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room.anonymous); + const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room.access); const [moderatorToken, speakerToken] = await Promise.all([ generateRoomMemberToken(room.roomId, { secret: moderatorSecret, joinMeeting: false }), generateRoomMemberToken(room.roomId, { secret: speakerSecret, joinMeeting: false }) diff --git a/meet-ce/backend/tests/integration/api/auth/token-validation.test.ts b/meet-ce/backend/tests/integration/api/auth/token-validation.test.ts index fa5ef44a..0bf574ab 100644 --- a/meet-ce/backend/tests/integration/api/auth/token-validation.test.ts +++ b/meet-ce/backend/tests/integration/api/auth/token-validation.test.ts @@ -15,7 +15,7 @@ import { resetUserPassword, sleep, startTestServer, - updateRoomAnonymousConfig, + updateRoomAccessConfig, updateRoomConfig, updateRoomMember, updateRoomRoles @@ -478,7 +478,7 @@ describe('Token Validation Tests', () => { expect(response.body.message).toContain('Invalid token'); }); - it('should succeed when room anonymous config is updated after room member token issuance', async () => { + it('should succeed when room access config is updated after room member token issuance', async () => { // Create a room with an external member const roomData = await setupSingleRoom(); const roomMember = await setupRoomMember(roomData.room.roomId, { @@ -492,11 +492,13 @@ describe('Token Validation Tests', () => { .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomMember.memberToken); expect(initialResponse.status).toBe(200); - // Update the room anonymous configuration + // Update the room access configuration await sleep('100ms'); // Small delay to ensure timestamp difference - await updateRoomAnonymousConfig(roomData.room.roomId, { - moderator: { - enabled: false + await updateRoomAccessConfig(roomData.room.roomId, { + anonymous: { + moderator: { + enabled: false + } } }); @@ -507,7 +509,7 @@ describe('Token Validation Tests', () => { expect(response.status).toBe(200); }); - it('should fail when room anonymous config is updated after anonymous room member token issuance', async () => { + it('should fail when room access config is updated after anonymous room member token issuance', async () => { // Create a room and generate an anonymous room member token const roomData = await setupSingleRoom(); @@ -517,11 +519,13 @@ describe('Token Validation Tests', () => { .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken); expect(initialResponse.status).toBe(200); - // Update the room's anonymous configuration + // Update the room's access configuration await sleep('100ms'); // Small delay to ensure timestamp difference - await updateRoomAnonymousConfig(roomData.room.roomId, { - moderator: { - enabled: false + await updateRoomAccessConfig(roomData.room.roomId, { + anonymous: { + moderator: { + enabled: false + } } }); @@ -538,7 +542,7 @@ describe('Token Validation Tests', () => { // Create a room and generate an anonymous room member token const roomData = await setupSingleRoom(); - // Update room config (not roles/permissions/anonymous) + // Update room config (not roles/access) await sleep('100ms'); await updateRoomConfig(roomData.room.roomId, { chat: { enabled: false } diff --git a/meet-ce/backend/tests/integration/api/room-members/generate-room-member-token.test.ts b/meet-ce/backend/tests/integration/api/room-members/generate-room-member-token.test.ts index 2152a23e..af7dc333 100644 --- a/meet-ce/backend/tests/integration/api/room-members/generate-room-member-token.test.ts +++ b/meet-ce/backend/tests/integration/api/room-members/generate-room-member-token.test.ts @@ -18,7 +18,7 @@ import { generateRoomMemberToken, generateRoomMemberTokenRequest, startTestServer, - updateRoomAnonymousConfig, + updateRoomAccessConfig, updateRoomRoles, updateRoomStatus } from '../../../helpers/request-helpers.js'; @@ -63,8 +63,10 @@ describe('Room Members API Tests', () => { describe('Generate Room Member Token Tests', () => { it('should generate anonymous moderator token when anonymous.moderator.enabled is true', async () => { // Enable anonymous moderator access - await updateRoomAnonymousConfig(roomId, { - moderator: { enabled: true } + await updateRoomAccessConfig(roomId, { + anonymous: { + moderator: { enabled: true } + } }); const response = await generateRoomMemberTokenRequest(roomId, { secret: roomData.moderatorSecret }); @@ -77,23 +79,29 @@ describe('Room Members API Tests', () => { it('should fail to generate anonymous moderator token when anonymous.moderator.enabled is false', async () => { // Disable anonymous moderator access - await updateRoomAnonymousConfig(roomId, { - moderator: { enabled: false } + await updateRoomAccessConfig(roomId, { + anonymous: { + moderator: { enabled: false } + } }); const response = await generateRoomMemberTokenRequest(roomId, { secret: roomData.moderatorSecret }); expect(response.status).toBe(403); // Enable anonymous moderator access for further tests - await updateRoomAnonymousConfig(roomId, { - moderator: { enabled: true } + await updateRoomAccessConfig(roomId, { + anonymous: { + moderator: { enabled: true } + } }); }); it('should generate anonymous speaker token when anonymous.speaker.enabled is true', async () => { // Enable anonymous speaker access - await updateRoomAnonymousConfig(roomId, { - speaker: { enabled: true } + await updateRoomAccessConfig(roomId, { + anonymous: { + speaker: { enabled: true } + } }); const response = await generateRoomMemberTokenRequest(roomId, { secret: roomData.speakerSecret }); @@ -106,16 +114,20 @@ describe('Room Members API Tests', () => { it('should fail to generate anonymous speaker token when anonymous.speaker.enabled is false', async () => { // Disable anonymous speaker access - await updateRoomAnonymousConfig(roomId, { - speaker: { enabled: false } + await updateRoomAccessConfig(roomId, { + anonymous: { + speaker: { enabled: false } + } }); const response = await generateRoomMemberTokenRequest(roomId, { secret: roomData.speakerSecret }); expect(response.status).toBe(403); // Enable anonymous speaker access for further tests - await updateRoomAnonymousConfig(roomId, { - speaker: { enabled: true } + await updateRoomAccessConfig(roomId, { + anonymous: { + speaker: { enabled: true } + } }); }); diff --git a/meet-ce/backend/tests/integration/api/rooms/expired-rooms-gc.test.ts b/meet-ce/backend/tests/integration/api/rooms/expired-rooms-gc.test.ts index 54c8d28f..86c98e18 100644 --- a/meet-ce/backend/tests/integration/api/rooms/expired-rooms-gc.test.ts +++ b/meet-ce/backend/tests/integration/api/rooms/expired-rooms-gc.test.ts @@ -98,7 +98,7 @@ describe('Expired Rooms GC Tests', () => { expect(response.body).toHaveProperty('meetingEndAction', 'delete'); // End the meeting - const { moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room.anonymous); + const { moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room.access); const moderatorToken = await generateRoomMemberToken(room.roomId, { secret: moderatorSecret }); await endMeeting(room.roomId, moderatorToken); diff --git a/meet-ce/backend/tests/integration/api/rooms/room-migrations.test.ts b/meet-ce/backend/tests/integration/api/rooms/room-migrations.test.ts index 2151e125..f2037d7c 100644 --- a/meet-ce/backend/tests/integration/api/rooms/room-migrations.test.ts +++ b/meet-ce/backend/tests/integration/api/rooms/room-migrations.test.ts @@ -100,17 +100,26 @@ const expectMigratedRoomToCurrentVersion = (migratedRoom: Record { } } }, - anonymous: { - moderator: { - enabled: true, - accessUrl: '/room/room-v2?secret=123456' + access: { + anonymous: { + moderator: { + enabled: true, + url: '/room/room-v2?secret=123456' + }, + speaker: { + enabled: true, + url: '/room/room-v2?secret=abcdef' + }, + recording: { + enabled: false, + url: expect.stringContaining('/room/room-v2/recordings') + } }, - speaker: { + registered: { enabled: true, - accessUrl: '/room/room-v2?secret=abcdef' + url: '/room/room-v2' } }, - accessUrl: '/room/room-v2', rolesUpdatedAt: expect.any(Number), status: MeetRoomStatus.OPEN, meetingEndAction: MeetingEndAction.NONE diff --git a/meet-ce/backend/tests/integration/api/security/room-security.test.ts b/meet-ce/backend/tests/integration/api/security/room-security.test.ts index 0831acc3..d9d98c92 100644 --- a/meet-ce/backend/tests/integration/api/security/room-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/room-security.test.ts @@ -757,7 +757,7 @@ describe('Room API Security Tests', () => { }); }); - describe('Update Room Anonymous Config Tests', () => { + describe('Update Room Access Config Tests', () => { let roomData: RoomData; let roomId: string; let roomUsers: RoomTestUsers; @@ -771,70 +771,70 @@ describe('Room API Security Tests', () => { it('should succeed when request includes API key', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(200); }); it('should succeed when user is authenticated as ADMIN', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(200); }); it('should succeed when user is authenticated as USER and is room owner', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.userOwner.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(200); }); it('should fail when user is authenticated as USER and is room member', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.userMember.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(403); }); it('should fail when user is authenticated as USER without access to the room', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(403); }); it('should fail when user is authenticated as ROOM_MEMBER and is room member', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.roomMember.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(403); }); it('should fail when user is authenticated as ROOM_MEMBER without access to the room', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(403); }); it('should fail when user is not authenticated', async () => { - const response = await request(app).put(`${ROOMS_PATH}/${roomId}/anonymous`).send({ anonymous: {} }); + const response = await request(app).put(`${ROOMS_PATH}/${roomId}/access`).send({ access: {} }); expect(response.status).toBe(401); }); it('should fail when using room member token', async () => { const response = await request(app) - .put(`${ROOMS_PATH}/${roomId}/anonymous`) + .put(`${ROOMS_PATH}/${roomId}/access`) .set(INTERNAL_CONFIG.ROOM_MEMBER_TOKEN_HEADER, roomData.moderatorToken) - .send({ anonymous: {} }); + .send({ access: {} }); expect(response.status).toBe(401); }); }); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts index 308193df..8aaad552 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts @@ -93,7 +93,7 @@ export class MeetingContextService { setMeetRoom(room: MeetRoom): void { this._meetRoom.set(room); this.setRoomId(room.roomId); - this.setMeetingUrl(room.accessUrl); + this.setMeetingUrl(room.access.registered.url); } /** diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts index f5e4d8b9..79a1f589 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts @@ -156,7 +156,7 @@ export class MeetingLobbyService { const [room] = await Promise.all([ this.roomService.getRoom(roomId, { - fields: ['roomId', 'roomName', 'status', 'config', 'accessUrl', 'anonymous'], + fields: ['roomId', 'roomName', 'status', 'config', 'access'], extraFields: ['config'] }), this.setBackButtonText(), diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts index b5845768..5344b85a 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts @@ -21,7 +21,7 @@ export class MeetingService { * Copies the meeting speaker link to the clipboard. */ copyMeetingSpeakerLink(room: MeetRoom): void { - const speakerLink = room.anonymous.speaker.accessUrl; + const speakerLink = room.access.anonymous.speaker.url; this.clipboard.copy(speakerLink); this.notificationService.showSnackbar('Speaker link copied to clipboard'); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-detail/room-detail.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-detail/room-detail.component.ts index 6153a0e7..e7a54846 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-detail/room-detail.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-detail/room-detail.component.ts @@ -201,14 +201,14 @@ export class RoomDetailComponent implements OnInit { const room = this.room(); if (!room) return; - window.open(room.accessUrl, '_blank'); + window.open(room.access.registered.url, '_blank'); } copyAccessLink() { const room = this.room(); if (!room) return; - this.clipboard.copy(room.accessUrl); + this.clipboard.copy(room.access.registered.url); this.notificationService.showSnackbar('Access link copied to clipboard'); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/room-wizard.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/room-wizard.component.ts index 23fd1274..a59bcef1 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/room-wizard.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/room-wizard.component.ts @@ -137,10 +137,10 @@ export class RoomWizardComponent implements OnInit { async createRoomBasic(roomName?: string) { try { // Create room with basic config including e2ee: false (default settings) - const { accessUrl } = await this.roomService.createRoom({ roomName }, { fields: ['accessUrl'] }); + const { access } = await this.roomService.createRoom({ roomName }, { fields: ['access'] }); // Extract the path from the access URL and navigate to it - const url = new URL(accessUrl); + const url = new URL(access.registered.url); const path = url.pathname; await this.navigationService.redirectTo(path); } catch (error) { @@ -168,10 +168,10 @@ export class RoomWizardComponent implements OnInit { this.notificationService.showSnackbar('Room updated successfully'); } else { // Create new room - const { accessUrl } = await this.roomService.createRoom(roomOptions, { fields: ['accessUrl'] }); + const { access } = await this.roomService.createRoom(roomOptions, { fields: ['access'] }); // Extract the path from the access URL and navigate to it - const url = new URL(accessUrl); + const url = new URL(access.registered.url); const path = url.pathname; await this.navigationService.redirectTo(path); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/role-permissions/role-permissions.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/role-permissions/role-permissions.component.ts index b9fd11de..49c7a562 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/role-permissions/role-permissions.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/role-permissions/role-permissions.component.ts @@ -26,37 +26,102 @@ export const PERMISSION_GROUPS: PermissionGroup[] = [ label: 'Meeting', icon: 'groups', permissions: [ - { key: 'canJoinMeeting', label: 'Can join meeting', description: 'Allow joining the meeting', icon: 'login' }, - { key: 'canEndMeeting', label: 'Can end meeting', description: 'Allow ending the meeting for all participants', icon: 'meeting_room' }, - { key: 'canMakeModerator', label: 'Can make moderator', description: 'Allow promoting participants to moderator role', icon: 'manage_accounts' }, - { key: 'canKickParticipants', label: 'Can kick participants', description: 'Allow removing participants from the meeting', icon: 'person_remove' }, - { key: 'canShareAccessLinks', label: 'Can share access links', description: 'Allow sharing invite links with others', icon: 'link' } + { + key: 'canJoinMeeting', + label: 'Can join meeting', + description: 'Allow joining the meeting', + icon: 'login' + }, + { + key: 'canEndMeeting', + label: 'Can end meeting', + description: 'Allow ending the meeting for all participants', + icon: 'meeting_room' + }, + { + key: 'canMakeModerator', + label: 'Can make moderator', + description: 'Allow promoting participants to moderator role', + icon: 'manage_accounts' + }, + { + key: 'canKickParticipants', + label: 'Can kick participants', + description: 'Allow removing participants from the meeting', + icon: 'person_remove' + }, + { + key: 'canShareAccessLinks', + label: 'Can share access links', + description: 'Allow sharing invite links with others', + icon: 'link' + } ] }, { label: 'Media', icon: 'perm_media', permissions: [ - { key: 'canPublishVideo', label: 'Can publish video', description: 'Allow sharing camera video', icon: 'videocam' }, - { key: 'canPublishAudio', label: 'Can publish audio', description: 'Allow sharing microphone audio', icon: 'mic' }, - { key: 'canShareScreen', label: 'Can share screen', description: 'Allow sharing desktop or browser tabs', icon: 'screen_share' }, - { key: 'canChangeVirtualBackground', label: 'Can change virtual background', description: 'Allow changing the virtual background', icon: 'background_replace' } + { + key: 'canPublishVideo', + label: 'Can publish video', + description: 'Allow sharing camera video', + icon: 'videocam' + }, + { + key: 'canPublishAudio', + label: 'Can publish audio', + description: 'Allow sharing microphone audio', + icon: 'mic' + }, + { + key: 'canShareScreen', + label: 'Can share screen', + description: 'Allow sharing desktop or browser tabs', + icon: 'screen_share' + }, + { + key: 'canChangeVirtualBackground', + label: 'Can change virtual background', + description: 'Allow changing the virtual background', + icon: 'background_replace' + } ] }, { label: 'Recordings', icon: 'video_library', permissions: [ - { key: 'canRecord', label: 'Can record', description: 'Allow starting and stopping recordings', icon: 'fiber_manual_record' }, - { key: 'canRetrieveRecordings', label: 'Can retrieve recordings', description: 'Allow listing and playing recordings', icon: 'play_circle' }, - { key: 'canDeleteRecordings', label: 'Can delete recordings', description: 'Allow deleting recordings', icon: 'delete' } + { + key: 'canRecord', + label: 'Can record', + description: 'Allow starting and stopping recordings', + icon: 'fiber_manual_record' + }, + { + key: 'canRetrieveRecordings', + label: 'Can retrieve recordings', + description: 'Allow listing and playing recordings', + icon: 'play_circle' + }, + { + key: 'canDeleteRecordings', + label: 'Can delete recordings', + description: 'Allow deleting recordings', + icon: 'delete' + } ] }, { label: 'Chat', icon: 'chat', permissions: [ - { key: 'canReadChat', label: 'Can read chat', description: 'Allow reading chat messages', icon: 'visibility' }, + { + key: 'canReadChat', + label: 'Can read chat', + description: 'Allow reading chat messages', + icon: 'visibility' + }, { key: 'canWriteChat', label: 'Can write chat', description: 'Allow sending chat messages', icon: 'edit' } ] } @@ -107,9 +172,11 @@ export class RolePermissionsComponent implements OnDestroy { moderator: { permissions: buildPermissions(formValue.moderator) }, speaker: { permissions: buildPermissions(formValue.speaker) } }, - anonymous: { - moderator: { enabled: formValue.moderator.anonymousEnabled ?? false }, - speaker: { enabled: formValue.speaker.anonymousEnabled ?? false } + access: { + anonymous: { + moderator: { enabled: formValue.moderator.anonymousEnabled ?? false }, + speaker: { enabled: formValue.speaker.anonymousEnabled ?? false } + } } }; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/rooms/rooms.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/rooms/rooms.component.ts index c40d3ef7..079e0963 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/rooms/rooms.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/rooms/rooms.component.ts @@ -206,8 +206,8 @@ export class RoomsComponent implements OnInit { } } - private openRoom({ accessUrl }: MeetRoom) { - window.open(accessUrl, '_blank'); + private openRoom({ access }: MeetRoom) { + window.open(access.registered.url, '_blank'); } private async editRoomConfig(room: MeetRoom) { @@ -219,13 +219,13 @@ export class RoomsComponent implements OnInit { } } - private copyModeratorLink({ anonymous }: MeetRoom) { - this.clipboard.copy(anonymous.moderator.accessUrl); + private copyModeratorLink({ access }: MeetRoom) { + this.clipboard.copy(access.anonymous.moderator.url); this.notificationService.showSnackbar('Moderator link copied to clipboard'); } - private copySpeakerLink({ anonymous }: MeetRoom) { - this.clipboard.copy(anonymous.speaker.accessUrl); + private copySpeakerLink({ access }: MeetRoom) { + this.clipboard.copy(access.anonymous.speaker.url); this.notificationService.showSnackbar('Speaker link copied to clipboard'); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts index 95e2b926..f37cf894 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from '@angular/core'; import { MeetRoom, - MeetRoomAnonymousConfig, + MeetRoomAccessConfig, MeetRoomConfig, MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings, @@ -167,15 +167,15 @@ export class RoomService { } /** - * Updates the anonymous access configuration of a room. + * Updates the access configuration of a room. * * @param roomId - The unique identifier of the room - * @param anonymousConfig - The new anonymous access configuration to be set - * @returns A promise that resolves when the anonymous access configuration has been updated + * @param accessConfig - The new access configuration to be set + * @returns A promise that resolves when the access configuration has been updated */ - async updateRoomAnonymous(roomId: string, anonymousConfig: MeetRoomAnonymousConfig): Promise { - const path = `${this.ROOMS_API}/${roomId}/anonymous`; - return this.httpService.putRequest(path, { anonymous: anonymousConfig }); + async updateRoomAccess(roomId: string, accessConfig: MeetRoomAccessConfig): Promise { + const path = `${this.ROOMS_API}/${roomId}/access`; + return this.httpService.putRequest(path, { access: accessConfig }); } /** diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts index ca68d291..f4c08615 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts @@ -238,13 +238,13 @@ export class RoomWizardStateService { isVisible: true, formGroup: this.formBuilder.group({ moderator: this.formBuilder.group({ - anonymousEnabled: initialRoomOptions.anonymous?.moderator?.enabled ?? false, + anonymousEnabled: initialRoomOptions.access?.anonymous?.moderator?.enabled ?? false, ...this.buildPermissionsFormConfig( initialRoomOptions.roles?.moderator?.permissions ?? DEFAULT_MODERATOR_PERMISSIONS ) }), speaker: this.formBuilder.group({ - anonymousEnabled: initialRoomOptions.anonymous?.speaker?.enabled ?? false, + anonymousEnabled: initialRoomOptions.access?.anonymous?.speaker?.enabled ?? false, ...this.buildPermissionsFormConfig( initialRoomOptions.roles?.speaker?.permissions ?? DEFAULT_SPEAKER_PERMISSIONS ) @@ -298,10 +298,7 @@ export class RoomWizardStateService { } } - private mergeRoomDetailsData( - currentOptions: MeetRoomOptions, - stepData: Partial - ): MeetRoomOptions { + private mergeRoomDetailsData(currentOptions: MeetRoomOptions, stepData: Partial): MeetRoomOptions { return { ...currentOptions, ...('roomName' in stepData ? { roomName: stepData.roomName } : {}), @@ -310,10 +307,7 @@ export class RoomWizardStateService { }; } - private mergeRecordingData( - currentOptions: MeetRoomOptions, - stepData: Partial - ): MeetRoomOptions { + private mergeRecordingData(currentOptions: MeetRoomOptions, stepData: Partial): MeetRoomOptions { return { ...currentOptions, config: this.buildMergedConfig(currentOptions.config, { @@ -353,16 +347,29 @@ export class RoomWizardStateService { } } }, - anonymous: { - moderator: { - enabled: - stepData.anonymous?.moderator?.enabled ?? - currentOptions.anonymous?.moderator?.enabled ?? - false + access: { + anonymous: { + moderator: { + enabled: + stepData.access?.anonymous?.moderator?.enabled ?? + currentOptions.access?.anonymous?.moderator?.enabled ?? + false + }, + speaker: { + enabled: + stepData.access?.anonymous?.speaker?.enabled ?? + currentOptions.access?.anonymous?.speaker?.enabled ?? + false + }, + recording: { + enabled: + stepData.access?.anonymous?.recording?.enabled ?? + currentOptions.access?.anonymous?.recording?.enabled ?? + false + } }, - speaker: { - enabled: - stepData.anonymous?.speaker?.enabled ?? currentOptions.anonymous?.speaker?.enabled ?? false + registered: { + enabled: stepData.access?.registered?.enabled ?? currentOptions.access?.registered?.enabled ?? true } } }; diff --git a/meet-ce/typings/src/database/room.entity.ts b/meet-ce/typings/src/database/room.entity.ts index c5586551..52b879dd 100644 --- a/meet-ce/typings/src/database/room.entity.ts +++ b/meet-ce/typings/src/database/room.entity.ts @@ -38,13 +38,9 @@ export interface MeetRoom { */ roles: MeetRoomRoles; /** - * Anonymous access configuration for the room. See {@link MeetRoomAnonymous} for details. + * Access configuration for the room. See {@link MeetRoomAccess} for details. */ - anonymous: MeetRoomAnonymous; - /** - * General access URL for registered users with access to the room. - */ - accessUrl: string; + access: MeetRoomAccess; /** * Status of the room. See {@link MeetRoomStatus} for details. */ @@ -73,17 +69,63 @@ export interface MeetRoomRoles { } /** - * Anonymous access configuration for a room. + * Access configuration for a room. + */ +export interface MeetRoomAccess { + /** + * Anonymous users access configuration for the room. See {@link MeetRoomAnonymousUsers} for details. + */ + anonymous: MeetRoomAnonymousUsers; + /** + * Registered users access configuration for the room. See {@link MeetRoomRegisteredUsers} for details. + */ + registered: MeetRoomRegisteredUsers; +} + +/** + * Access configuration for registered users in a room. + */ +export interface MeetRoomRegisteredUsers { + /** + * Indicates if all registered users can access the room. + * When enabled, any registered user can join, including non-members. + * Room owners and administrators always have access regardless of this setting. + */ + enabled: boolean; + /** + * General access URL for registered users with access to the room. + */ + url: string; +} + +/** + * Access configuration for anonymous users in a room. * Defines which roles have anonymous access enabled and their access URLs. */ -export interface MeetRoomAnonymous { +export interface MeetRoomAnonymousUsers { + /** + * Indicates if anonymous access is enabled for the moderator role in the room. + * If true, anonymous users with moderator permissions can access the room using the provided URL. + */ moderator: { enabled: boolean; - accessUrl: string; + url: string; }; + /** + * Indicates if anonymous access is enabled for the speaker role in the room. + * If true, anonymous users with speaker permissions can access the room using the provided URL. + */ speaker: { enabled: boolean; - accessUrl: string; + url: string; + }; + /** + * Indicates if anonymous access is enabled for recordings of the room. + * If true, anonymous users can access the recordings using the provided URL. + */ + recording: { + enabled: boolean; + url: string; }; } diff --git a/meet-ce/typings/src/request/room-request.ts b/meet-ce/typings/src/request/room-request.ts index fc912b41..f02fc1b1 100644 --- a/meet-ce/typings/src/request/room-request.ts +++ b/meet-ce/typings/src/request/room-request.ts @@ -27,9 +27,9 @@ export interface MeetRoomOptions { */ roles?: MeetRoomRolesConfig; /** - * Anonymous access configuration for the room. See {@link MeetRoomAnonymousConfig} for details. + * Access configuration for the room. See {@link MeetRoomAccessConfig} for details. */ - anonymous?: MeetRoomAnonymousConfig; + access?: MeetRoomAccessConfig; } /** @@ -46,14 +46,22 @@ export interface MeetRoomRolesConfig { } /** - * Anonymous access configuration for creating/updating a room. + * Access configuration for creating/updating a room. * Only includes enabled flags. */ -export interface MeetRoomAnonymousConfig { - moderator?: { - enabled: boolean; +export interface MeetRoomAccessConfig { + anonymous?: { + moderator?: { + enabled: boolean; + }; + speaker?: { + enabled: boolean; + }; + recording?: { + enabled: boolean; + }; }; - speaker?: { + registered?: { enabled: boolean; }; } diff --git a/meet-ce/typings/src/response/room-response.ts b/meet-ce/typings/src/response/room-response.ts index 6eef8f12..d1435574 100644 --- a/meet-ce/typings/src/response/room-response.ts +++ b/meet-ce/typings/src/response/room-response.ts @@ -14,8 +14,7 @@ export const MEET_ROOM_FIELDS = [ 'creationDate', 'config', 'roles', - 'anonymous', - 'accessUrl', + 'access', 'status', 'rolesUpdatedAt', 'meetingEndAction', @@ -57,7 +56,7 @@ export type MeetRoomSortField = (typeof MEET_ROOM_SORT_FIELDS)[number]; * Sensitive fields of a MeetRoom that require specific permissions to be viewed. */ export const SENSITIVE_ROOM_FIELDS_BY_PERMISSION: Partial> = { - canShareAccessLinks: ['anonymous'] + canShareAccessLinks: ['access'] }; export const SENSITIVE_ROOM_FIELDS_ENTRIES = Object.entries(SENSITIVE_ROOM_FIELDS_BY_PERMISSION) as ReadonlyArray<