diff --git a/backend/src/middlewares/room.middleware.ts b/backend/src/middlewares/room.middleware.ts index c2ecbe8..dec9e89 100644 --- a/backend/src/middlewares/room.middleware.ts +++ b/backend/src/middlewares/room.middleware.ts @@ -1,7 +1,12 @@ -import { AuthMode, ParticipantRole, UserRole } from '@typings-ce'; +import { AuthMode, MeetRecordingAccess, MeetRoom, ParticipantRole, UserRole } from '@typings-ce'; import { NextFunction, Request, Response } from 'express'; import { container } from '../config/index.js'; -import { LoggerService, MeetStorageService } from '../services/index.js'; +import { + errorInsufficientPermissions, + errorRoomNotFoundOrEmptyRecordings, + OpenViduMeetError +} from '../models/error.model.js'; +import { LoggerService, MeetStorageService, RoomService } from '../services/index.js'; import { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js'; /** @@ -93,3 +98,77 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne // If the user is not a moderator, it is not allowed to access the resource return res.status(403).json({ message: 'Insufficient permissions to access this resource' }); }; + +/** + * Middleware to configure authentication based on participant role and authentication mode + * for generating a token for retrieving/deleting recordings. + * + * - If the authentication mode is MODERATORS_ONLY and the participant role is MODERATOR, configure user authentication. + * - If the authentication mode is ALL_USERS, configure user authentication. + * - Otherwise, allow anonymous access. + */ +export const configureRecordingTokenAuth = async (req: Request, res: Response, next: NextFunction) => { + const logger = container.get(LoggerService); + const storageService = container.get(MeetStorageService); + const roomService = container.get(RoomService); + + let role: ParticipantRole; + + try { + const roomId = req.params.roomId; + const { secret } = req.body; + const room = await storageService.getArchivedRoomMetadata(roomId); + + if (!room) { + // If the room is not found, it means that there are no recordings for that room or the room doesn't exist + throw errorRoomNotFoundOrEmptyRecordings(roomId); + } + + const recordingAccess = room.preferences!.recordingPreferences.allowAccessTo; + + if (recordingAccess === MeetRecordingAccess.ADMIN) { + // Deny request if the room is configured to allow access to recordings only for admins + throw errorInsufficientPermissions(); + } + + role = roomService.getRoomRoleBySecretFromRoom(room as MeetRoom, secret); + } catch (error) { + logger.error('Error getting room role by secret', error); + + if (error instanceof OpenViduMeetError) { + return res.status(error.statusCode).json({ name: error.name, message: error.message }); + } else { + return res.status(500).json({ + name: 'Room Error', + message: 'Internal server error. Room operation failed' + }); + } + } + + let authMode: AuthMode; + + try { + const { securityPreferences } = await storageService.getGlobalPreferences(); + authMode = securityPreferences.authentication.authMode; + } catch (error) { + logger.error('Error checking authentication preferences', error); + return res.status(500).json({ message: 'Internal server error' }); + } + + const authValidators = []; + + if (authMode === AuthMode.NONE) { + authValidators.push(allowAnonymous); + } else { + const isModeratorsOnlyMode = authMode === AuthMode.MODERATORS_ONLY && role === ParticipantRole.MODERATOR; + const isAllUsersMode = authMode === AuthMode.ALL_USERS; + + if (isModeratorsOnlyMode || isAllUsersMode) { + authValidators.push(tokenAndRoleValidator(UserRole.USER)); + } else { + authValidators.push(allowAnonymous); + } + } + + return withAuth(...authValidators)(req, res, next); +}; diff --git a/backend/src/routes/room.routes.ts b/backend/src/routes/room.routes.ts index d0048bd..8aa9f31 100644 --- a/backend/src/routes/room.routes.ts +++ b/backend/src/routes/room.routes.ts @@ -5,6 +5,7 @@ import * as roomCtrl from '../controllers/room.controller.js'; import { apiKeyValidator, configureCreateRoomAuth, + configureRecordingTokenAuth, configureRoomAuthorization, participantTokenValidator, tokenAndRoleValidator, @@ -66,7 +67,7 @@ internalRoomRouter.put( internalRoomRouter.post( '/:roomId/recording-token', - configureCreateRoomAuth, + configureRecordingTokenAuth, withValidRoomSecret, roomCtrl.generateRecordingToken ); diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index af8e548..3a70bf8 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -249,7 +249,7 @@ export class RoomService { return this.getRoomRoleBySecretFromRoom(room, secret); } - protected getRoomRoleBySecretFromRoom(room: MeetRoom, secret: string): ParticipantRole { + getRoomRoleBySecretFromRoom(room: MeetRoom, secret: string): ParticipantRole { const { moderatorSecret, publisherSecret } = MeetRoomHelper.extractSecretsFromRoom(room); switch (secret) {