backend: allow publisher participants to retrieve room information except moderatorRoomUrl field
This commit is contained in:
parent
c22c00b602
commit
aef2a2484b
@ -44,12 +44,13 @@ export const getRoom = async (req: Request, res: Response) => {
|
||||
|
||||
const { roomId } = req.params;
|
||||
const fields = req.query.fields as string | undefined;
|
||||
const role = req.session?.participantRole;
|
||||
|
||||
try {
|
||||
logger.verbose(`Getting room '${roomId}'`);
|
||||
|
||||
const roomService = container.get(RoomService);
|
||||
const room = await roomService.getMeetRoom(roomId, fields);
|
||||
const room = await roomService.getMeetRoom(roomId, fields, role);
|
||||
|
||||
return res.status(200).json(room);
|
||||
} catch (error) {
|
||||
|
||||
@ -94,6 +94,33 @@ export const tokenAndRoleValidator = (role: UserRole) => {
|
||||
// Configure token validator for participant access
|
||||
export const participantTokenValidator = async (req: Request) => {
|
||||
await validateTokenAndSetSession(req, INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME);
|
||||
|
||||
// Check if the participant role is provided in the request headers
|
||||
// This is required to distinguish roles when multiple are present in the token
|
||||
const participantRole = req.headers[INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER];
|
||||
const allRoles = [ParticipantRole.MODERATOR, ParticipantRole.PUBLISHER];
|
||||
|
||||
if (!participantRole || !allRoles.includes(participantRole as ParticipantRole)) {
|
||||
throw errorWithControl(errorInvalidParticipantRole(), true);
|
||||
}
|
||||
|
||||
if (!participantRole || !allRoles.includes(participantRole as ParticipantRole)) {
|
||||
throw errorWithControl(errorInvalidParticipantRole(), true);
|
||||
}
|
||||
|
||||
// Check that the specified role is present in the token claims
|
||||
const metadata = JSON.parse(req.session?.tokenClaims?.metadata || '{}');
|
||||
const roles = metadata.roles || [];
|
||||
const hasRole = roles.some(
|
||||
(r: { role: ParticipantRole; permissions: OpenViduMeetPermissions }) => r.role === participantRole
|
||||
);
|
||||
|
||||
if (!hasRole) {
|
||||
throw errorWithControl(errorInsufficientPermissions(), true);
|
||||
}
|
||||
|
||||
// Set the participant role in the session
|
||||
req.session!.participantRole = participantRole as ParticipantRole;
|
||||
};
|
||||
|
||||
// Configure token validator for recording access
|
||||
@ -121,31 +148,6 @@ const validateTokenAndSetSession = async (req: Request, cookieName: string) => {
|
||||
} catch (error) {
|
||||
throw errorWithControl(errorInvalidToken(), true);
|
||||
}
|
||||
|
||||
// If the token is a participant token, set the participant role in the session
|
||||
if (cookieName === INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME) {
|
||||
const participantRole = req.headers[INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER];
|
||||
const allRoles = [ParticipantRole.MODERATOR, ParticipantRole.PUBLISHER];
|
||||
|
||||
// Ensure the participant role is provided and valid
|
||||
// This is required to distinguish roles when multiple are present in the token
|
||||
if (!participantRole || !allRoles.includes(participantRole as ParticipantRole)) {
|
||||
throw errorWithControl(errorInvalidParticipantRole(), true);
|
||||
}
|
||||
|
||||
// Check that the specified role is present in the token claims
|
||||
const metadata = JSON.parse(payload.metadata || '{}');
|
||||
const roles = metadata.roles || [];
|
||||
const hasRole = roles.some(
|
||||
(r: { role: ParticipantRole; permissions: OpenViduMeetPermissions }) => r.role === participantRole
|
||||
);
|
||||
|
||||
if (!hasRole) {
|
||||
throw errorWithControl(errorInsufficientPermissions(), true);
|
||||
}
|
||||
|
||||
req.session.participantRole = participantRole as ParticipantRole;
|
||||
}
|
||||
};
|
||||
|
||||
// Configure API key validatior
|
||||
|
||||
@ -15,13 +15,11 @@ import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middlewa
|
||||
*
|
||||
* - If there is no token in the session, the user is granted access (admin or API key).
|
||||
* - If the user does not belong to the requested room, access is denied.
|
||||
* - If the user is not a moderator, access is denied.
|
||||
* - If the user is a moderator and belongs to the room, access is granted.
|
||||
* - Otherwise, the user is allowed to access the room.
|
||||
*/
|
||||
export const configureRoomAuthorization = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const roomId = req.params.roomId as string;
|
||||
const payload = req.session?.tokenClaims;
|
||||
const role = req.session?.participantRole;
|
||||
|
||||
// If there is no token, the user is admin or it is invoked using the API key
|
||||
// In this case, the user is allowed to access the resource
|
||||
@ -31,9 +29,8 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
||||
|
||||
const sameRoom = payload.video?.room === roomId;
|
||||
|
||||
// If the user does not belong to the requested room,
|
||||
// or the user is not a moderator, access is denied
|
||||
if (!sameRoom || role !== ParticipantRole.MODERATOR) {
|
||||
// If the user does not belong to the requested room, access is denied
|
||||
if (!sameRoom) {
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
@ -185,7 +185,7 @@ export class RoomService {
|
||||
* @param roomId - The name of the room to retrieve.
|
||||
* @returns A promise that resolves to an {@link MeetRoom} object.
|
||||
*/
|
||||
async getMeetRoom(roomId: string, fields?: string): Promise<MeetRoom> {
|
||||
async getMeetRoom(roomId: string, fields?: string, participantRole?: ParticipantRole): Promise<MeetRoom> {
|
||||
const meetRoom = await this.storageService.getMeetRoom(roomId);
|
||||
|
||||
if (!meetRoom) {
|
||||
@ -193,7 +193,14 @@ export class RoomService {
|
||||
throw errorRoomNotFound(roomId);
|
||||
}
|
||||
|
||||
return UtilsHelper.filterObjectFields(meetRoom, fields) as MeetRoom;
|
||||
const filteredRoom = UtilsHelper.filterObjectFields(meetRoom, fields);
|
||||
|
||||
// Remove moderatorRoomUrl if the participant is a publisher to prevent access to moderator links
|
||||
if (participantRole === ParticipantRole.PUBLISHER) {
|
||||
delete filteredRoom.moderatorRoomUrl;
|
||||
}
|
||||
|
||||
return filteredRoom as MeetRoom;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from '@jest/globals';
|
||||
import ms from 'ms';
|
||||
import { MeetRecordingAccess } from '../../../../src/typings/ce/index.js';
|
||||
import { MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js';
|
||||
import {
|
||||
expectSuccessRoomResponse,
|
||||
expectValidationError,
|
||||
@ -8,6 +8,7 @@ import {
|
||||
expectValidRoomWithFields
|
||||
} from '../../../helpers/assertion-helpers.js';
|
||||
import { createRoom, deleteAllRooms, getRoom, startTestServer } from '../../../helpers/request-helpers.js';
|
||||
import { setupSingleRoom } from '../../../helpers/test-scenarios.js';
|
||||
|
||||
describe('Room API Tests', () => {
|
||||
beforeAll(() => {
|
||||
@ -95,6 +96,25 @@ describe('Room API Tests', () => {
|
||||
|
||||
expectSuccessRoomResponse(response, 'deletion-date', validAutoDeletionDate);
|
||||
});
|
||||
|
||||
it('should retrieve a room without moderatorRoomUrl when participant is publisher', async () => {
|
||||
const roomData = await setupSingleRoom();
|
||||
const response = await getRoom(
|
||||
roomData.room.roomId,
|
||||
undefined,
|
||||
roomData.publisherCookie,
|
||||
ParticipantRole.PUBLISHER
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.moderatorRoomUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return 404 for a non-existent room', async () => {
|
||||
const fakeRoomId = 'non-existent-room-id';
|
||||
const response = await getRoom(fakeRoomId);
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body.message).toBe(`Room '${fakeRoomId}' does not exist`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get Room Validation failures', () => {
|
||||
|
||||
@ -142,12 +142,12 @@ describe('Room API Security Tests', () => {
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
||||
it('should fail when participant is publisher', async () => {
|
||||
it('should succeed when participant is publisher', async () => {
|
||||
const response = await request(app)
|
||||
.get(`${ROOMS_PATH}/${roomData.room.roomId}`)
|
||||
.set('Cookie', roomData.publisherCookie)
|
||||
.set(INTERNAL_CONFIG.PARTICIPANT_ROLE_HEADER, ParticipantRole.PUBLISHER);
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user