From acd9a4c88088e13e9fda6cc9221029dbee78ab76 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 13 Jun 2025 17:18:18 +0200 Subject: [PATCH] backend: remove public option from MeetRecordingAccess and refactor related code --- .../src/middlewares/recording.middleware.ts | 41 ++++--------------- .../recording-validator.middleware.ts | 26 ++++++++++-- .../room-validator.middleware.ts | 3 +- backend/src/routes/recording.routes.ts | 9 ++-- backend/src/routes/room.routes.ts | 2 +- backend/src/services/room.service.ts | 8 ++-- typings/src/room-preferences.ts | 1 - 7 files changed, 42 insertions(+), 48 deletions(-) diff --git a/backend/src/middlewares/recording.middleware.ts b/backend/src/middlewares/recording.middleware.ts index d52beb0..1b0a141 100644 --- a/backend/src/middlewares/recording.middleware.ts +++ b/backend/src/middlewares/recording.middleware.ts @@ -1,4 +1,4 @@ -import { MeetRecordingAccess, MeetRoom, OpenViduMeetPermissions, RecordingPermissions, UserRole } from '@typings-ce'; +import { MeetRoom, OpenViduMeetPermissions, RecordingPermissions, UserRole } from '@typings-ce'; import { NextFunction, Request, Response } from 'express'; import { container } from '../config/index.js'; import { RecordingHelper } from '../helpers/index.js'; @@ -7,7 +7,6 @@ import { errorInvalidRecordingSecret, errorRecordingDisabled, errorRecordingNotFound, - errorRoomMetadataNotFound, handleError, rejectRequestFromMeetError } from '../models/error.model.js'; @@ -68,10 +67,9 @@ export const withCanRetrieveRecordingsPermission = async (req: Request, res: Res /** * If there is no token, the user is allowed to access the resource because one of the following reasons: - * + * * - The request is invoked using the API key. * - The user is admin. - * - The user is anonymous and recording access is public. * - The user is anonymous and is using the public access secret. * - The user is using the private access secret and is authenticated. */ @@ -116,13 +114,12 @@ export const withCanDeleteRecordingsPermission = async (req: Request, res: Respo }; /** - * Middleware to configure authentication for retrieving recording media based on recording access. + * Middleware to configure authentication for retrieving recording based on the provided secret. * - * - API key, admin and recording token are always allowed. * - If a valid secret is provided in the query, access is granted according to the secret type. - * - If recording access is public, anonymous users are allowed. + * - If no secret is provided, the default authentication logic is applied, i.e., API key, admin and recording token access. */ -export const configureRecordingMediaAuth = async (req: Request, res: Response, next: NextFunction) => { +export const configureRecordingAuth = async (req: Request, res: Response, next: NextFunction) => { const storageService = container.get(MeetStorageService); const secret = req.query.secret as string; @@ -143,9 +140,11 @@ export const configureRecordingMediaAuth = async (req: Request, res: Response, n switch (secret) { case recordingSecrets.publicAccessSecret: + // Public access secret allows anonymous access authValidators.push(allowAnonymous); break; case recordingSecrets.privateAccessSecret: + // Private access secret requires authentication with user role authValidators.push(tokenAndRoleValidator(UserRole.USER)); break; default: @@ -159,31 +158,9 @@ export const configureRecordingMediaAuth = async (req: Request, res: Response, n } } - // If no secret is provided, determine access based on room's recording preferences - let recordingAccess: MeetRecordingAccess | undefined; - - try { - const roomId = extractRoomIdFromRequest(req); - const room = await storageService.getArchivedRoomMetadata(roomId!); - - if (!room) { - const error = errorRoomMetadataNotFound(roomId!); - return rejectRequestFromMeetError(res, error); - } - - recordingAccess = room.preferences!.recordingPreferences.allowAccessTo; - } catch (error) { - return handleError(res, error, 'checking recording permissions'); - } - - // Always allow API key, admin, and recording token + // If no secret is provided, we proceed with the default authentication logic. + // This will allow API key, admin and recording token access. const authValidators = [apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator]; - - // If access is public, allow anonymous users as well - if (recordingAccess === MeetRecordingAccess.PUBLIC) { - authValidators.push(allowAnonymous); - } - return withAuth(...authValidators)(req, res, next); }; diff --git a/backend/src/middlewares/request-validators/recording-validator.middleware.ts b/backend/src/middlewares/request-validators/recording-validator.middleware.ts index 84c1a2c..4db2215 100644 --- a/backend/src/middlewares/request-validators/recording-validator.middleware.ts +++ b/backend/src/middlewares/request-validators/recording-validator.middleware.ts @@ -56,7 +56,12 @@ const StartRecordingRequestSchema = z.object({ }); const GetRecordingSchema = z.object({ - recordingId: nonEmptySanitizedRecordingId('recordingId') + params: z.object({ + recordingId: nonEmptySanitizedRecordingId('recordingId') + }), + query: z.object({ + secret: z.string().optional() + }) }); const BulkDeleteRecordingsSchema = z.object({ @@ -143,13 +148,28 @@ export const withValidStartRecordingRequest = (req: Request, res: Response, next }; export const withValidRecordingId = (req: Request, res: Response, next: NextFunction) => { - const { success, error, data } = GetRecordingSchema.safeParse({ recordingId: req.params.recordingId }); + const { success, error, data } = nonEmptySanitizedRecordingId('recordingId').safeParse(req.params.recordingId); + + if (!success) { + error.errors[0].path = ['recordingId']; + return rejectUnprocessableRequest(res, error); + } + + req.params.recordingId = data; + next(); +}; + +export const withValidGetRecordingRequest = (req: Request, res: Response, next: NextFunction) => { + const { success, error, data } = GetRecordingSchema.safeParse({ + params: req.params, + query: req.query + }); if (!success) { return rejectUnprocessableRequest(res, error); } - req.params.recordingId = data.recordingId; + req.params.recordingId = data.params.recordingId; next(); }; diff --git a/backend/src/middlewares/request-validators/room-validator.middleware.ts b/backend/src/middlewares/request-validators/room-validator.middleware.ts index 9d06653..0e9578a 100644 --- a/backend/src/middlewares/request-validators/room-validator.middleware.ts +++ b/backend/src/middlewares/request-validators/room-validator.middleware.ts @@ -60,8 +60,7 @@ const validForceQueryParam = () => const RecordingAccessSchema: z.ZodType = z.enum([ MeetRecordingAccess.ADMIN, MeetRecordingAccess.ADMIN_MODERATOR, - MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER, - MeetRecordingAccess.PUBLIC + MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER ]); const RecordingPreferencesSchema: z.ZodType = z diff --git a/backend/src/routes/recording.routes.ts b/backend/src/routes/recording.routes.ts index b27c96a..f64ed8b 100644 --- a/backend/src/routes/recording.routes.ts +++ b/backend/src/routes/recording.routes.ts @@ -4,7 +4,7 @@ import { Router } from 'express'; import * as recordingCtrl from '../controllers/recording.controller.js'; import { apiKeyValidator, - configureRecordingMediaAuth, + configureRecordingAuth, participantTokenValidator, recordingTokenValidator, tokenAndRoleValidator, @@ -14,6 +14,7 @@ import { withCanRetrieveRecordingsPermission, withRecordingEnabled, withValidGetRecordingMediaRequest, + withValidGetRecordingRequest, withValidGetRecordingUrlRequest, withValidRecordingBulkDeleteRequest, withValidRecordingFiltersRequest, @@ -41,8 +42,8 @@ recordingRouter.delete( ); recordingRouter.get( '/:recordingId', - withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator), - withValidRecordingId, + withValidGetRecordingRequest, + configureRecordingAuth, withCanRetrieveRecordingsPermission, recordingCtrl.getRecording ); @@ -56,7 +57,7 @@ recordingRouter.delete( recordingRouter.get( '/:recordingId/media', withValidGetRecordingMediaRequest, - configureRecordingMediaAuth, + configureRecordingAuth, withCanRetrieveRecordingsPermission, recordingCtrl.getRecordingMedia ); diff --git a/backend/src/routes/room.routes.ts b/backend/src/routes/room.routes.ts index 709e06a..9d90607 100644 --- a/backend/src/routes/room.routes.ts +++ b/backend/src/routes/room.routes.ts @@ -46,8 +46,8 @@ roomRouter.delete( roomRouter.get( '/:roomId', withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), participantTokenValidator), - configureRoomAuthorization, withValidRoomId, + configureRoomAuthorization, roomCtrl.getRoom ); roomRouter.delete( diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index c9fe9a9..02c981f 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -305,12 +305,10 @@ export class RoomService { /* A participant can retrieve recordings if - they can delete recordings - - the recording access is public - they are a publisher and the recording access includes publishers */ const canRetrieveRecordings = canDeleteRecordings || - recordingAccess === MeetRecordingAccess.PUBLIC || (role === ParticipantRole.PUBLISHER && recordingAccess === MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER); return { @@ -450,7 +448,7 @@ export class RoomService { ); // Prepare rooms for batch update - const roomsToUpdate: {roomId: string; room: MeetRoom}[] = []; + const roomsToUpdate: { roomId: string; room: MeetRoom }[] = []; const successfulRoomIds: string[] = []; roomResults.forEach((result, index) => { @@ -459,7 +457,7 @@ export class RoomService { if (result.status === 'fulfilled' && result.value) { const room = result.value; room.markedForDeletion = true; - roomsToUpdate.push({roomId, room}); + roomsToUpdate.push({ roomId, room }); successfulRoomIds.push(roomId); } else { this.logger.warn( @@ -470,7 +468,7 @@ export class RoomService { // Batch save all updated rooms if (roomsToUpdate.length > 0) { - await Promise.allSettled(roomsToUpdate.map(({room}) => this.storageService.saveMeetRoom(room))); + await Promise.allSettled(roomsToUpdate.map(({ room }) => this.storageService.saveMeetRoom(room))); } this.logger.info(`Successfully marked ${successfulRoomIds.length} rooms for deletion`); diff --git a/typings/src/room-preferences.ts b/typings/src/room-preferences.ts index 9bbcf9b..7218c91 100644 --- a/typings/src/room-preferences.ts +++ b/typings/src/room-preferences.ts @@ -19,7 +19,6 @@ export const enum MeetRecordingAccess { ADMIN = 'admin', // Only admins can access the recording ADMIN_MODERATOR = 'admin-moderator', // Admins and moderators can access ADMIN_MODERATOR_PUBLISHER = 'admin-moderator-publisher', // Admins, moderators and publishers can access - PUBLIC = 'public', // Everyone can access } export interface MeetChatPreferences {