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.
This commit is contained in:
juancarmore 2026-03-02 17:37:25 +01:00
parent c563860758
commit 63d72c994b
46 changed files with 948 additions and 485 deletions

View File

@ -9,5 +9,5 @@ schema:
type: string
examples:
basic:
value: 'roomId,roomName,accessUrl'
value: 'roomId,roomName,status'
summary: Only return basic room information

View File

@ -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'

View File

@ -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'

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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).
<br/><br/>
@ -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:

View File

@ -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:

View File

@ -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':

View File

@ -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}'`);
}
};

View File

@ -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 };
}
/**

View File

@ -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);

View File

@ -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<MeetRoomDocument> = (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;

View File

@ -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<MeetRoomDocument>(
type: MeetRoomRolesSchema,
required: true
},
anonymous: {
type: MeetRoomAnonymousSchema,
required: true
},
accessUrl: {
type: String,
access: {
type: MeetRoomAccessSchema,
required: true
},
status: {

View File

@ -11,7 +11,7 @@ import {
MeetRecordingEncodingPreset,
MeetRecordingLayout,
MeetRecordingVideoCodec,
MeetRoomAnonymousConfig,
MeetRoomAccessConfig,
MeetRoomAutoDeletionPolicy,
MeetRoomCaptionsConfig,
MeetRoomConfig,
@ -296,13 +296,27 @@ const RoomRolesConfigSchema: z.ZodType<MeetRoomRolesConfig> = z.object({
.optional()
});
const RoomAnonymousConfigSchema: z.ZodType<MeetRoomAnonymousConfig> = z.object({
moderator: z
const RoomAccessConfigSchema: z.ZodType<MeetRoomAccessConfig> = 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<MeetRoomOptions> = 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({

View File

@ -261,19 +261,25 @@ export class RoomRepository extends BaseRepository<MeetRoom, MeetRoomDocument> {
* @returns Normalized partial room data
*/
private normalizeRoomForStorage(room: Partial<MeetRoom>): Partial<MeetRoom> {
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<MeetRoom, MeetRoomDocument> {
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;

View File

@ -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

View File

@ -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<MeetRoomMemberRole> {
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<void> {
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,

View File

@ -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<MeetRoom> {
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<MeetRoom> {
const room = await this.getMeetRoom(roomId, ['anonymous', 'status']);
async updateMeetRoomAccess(roomId: string, access: MeetRoomAccessConfig): Promise<MeetRoom> {
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<boolean> {
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);
}
/**

View File

@ -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);

View File

@ -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 (

View File

@ -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 })

View File

@ -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 }

View File

@ -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 }
}
});
});

View File

@ -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);

View File

@ -100,17 +100,26 @@ const expectMigratedRoomToCurrentVersion = (migratedRoom: Record<string, unknown
permissions: expect.any(Object)
}
},
anonymous: {
moderator: {
enabled: true,
accessUrl: `/room/${roomId}?secret=123456`
access: {
anonymous: {
moderator: {
enabled: true,
url: `/room/${roomId}?secret=123456`
},
speaker: {
enabled: true,
url: `/room/${roomId}?secret=abcdef`
},
recording: {
enabled: false,
url: expect.stringContaining(`/room/${roomId}/recordings`)
}
},
speaker: {
registered: {
enabled: true,
accessUrl: `/room/${roomId}?secret=abcdef`
url: `/room/${roomId}`
}
},
accessUrl: `/room/${roomId}`,
rolesUpdatedAt: expect.any(Number),
status: MeetRoomStatus.OPEN,
meetingEndAction: MeetingEndAction.NONE
@ -259,17 +268,26 @@ describe('Room Schema Migrations', () => {
}
}
},
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

View File

@ -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);
});
});

View File

@ -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);
}
/**

View File

@ -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(),

View File

@ -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');
}

View File

@ -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');
}

View File

@ -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);
}

View File

@ -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 }
}
}
};

View File

@ -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');
}

View File

@ -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<void> {
const path = `${this.ROOMS_API}/${roomId}/anonymous`;
return this.httpService.putRequest(path, { anonymous: anonymousConfig });
async updateRoomAccess(roomId: string, accessConfig: MeetRoomAccessConfig): Promise<void> {
const path = `${this.ROOMS_API}/${roomId}/access`;
return this.httpService.putRequest(path, { access: accessConfig });
}
/**

View File

@ -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>
): MeetRoomOptions {
private mergeRoomDetailsData(currentOptions: MeetRoomOptions, stepData: Partial<MeetRoomOptions>): MeetRoomOptions {
return {
...currentOptions,
...('roomName' in stepData ? { roomName: stepData.roomName } : {}),
@ -310,10 +307,7 @@ export class RoomWizardStateService {
};
}
private mergeRecordingData(
currentOptions: MeetRoomOptions,
stepData: Partial<MeetRoomOptions>
): MeetRoomOptions {
private mergeRecordingData(currentOptions: MeetRoomOptions, stepData: Partial<MeetRoomOptions>): 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
}
}
};

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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<Record<keyof MeetRoomMemberPermissions, MeetRoomField[]>> = {
canShareAccessLinks: ['anonymous']
canShareAccessLinks: ['access']
};
export const SENSITIVE_ROOM_FIELDS_ENTRIES = Object.entries(SENSITIVE_ROOM_FIELDS_BY_PERMISSION) as ReadonlyArray<