backend: remove public option from MeetRecordingAccess and refactor related code
This commit is contained in:
parent
0acf064976
commit
acd9a4c880
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
|
||||
@ -60,8 +60,7 @@ const validForceQueryParam = () =>
|
||||
const RecordingAccessSchema: z.ZodType<MeetRecordingAccess> = z.enum([
|
||||
MeetRecordingAccess.ADMIN,
|
||||
MeetRecordingAccess.ADMIN_MODERATOR,
|
||||
MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER,
|
||||
MeetRecordingAccess.PUBLIC
|
||||
MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
]);
|
||||
|
||||
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -46,8 +46,8 @@ roomRouter.delete(
|
||||
roomRouter.get(
|
||||
'/:roomId',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), participantTokenValidator),
|
||||
configureRoomAuthorization,
|
||||
withValidRoomId,
|
||||
configureRoomAuthorization,
|
||||
roomCtrl.getRoom
|
||||
);
|
||||
roomRouter.delete(
|
||||
|
||||
@ -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`);
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user