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 { NextFunction, Request, Response } from 'express';
|
||||||
import { container } from '../config/index.js';
|
import { container } from '../config/index.js';
|
||||||
import { RecordingHelper } from '../helpers/index.js';
|
import { RecordingHelper } from '../helpers/index.js';
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
errorInvalidRecordingSecret,
|
errorInvalidRecordingSecret,
|
||||||
errorRecordingDisabled,
|
errorRecordingDisabled,
|
||||||
errorRecordingNotFound,
|
errorRecordingNotFound,
|
||||||
errorRoomMetadataNotFound,
|
|
||||||
handleError,
|
handleError,
|
||||||
rejectRequestFromMeetError
|
rejectRequestFromMeetError
|
||||||
} from '../models/error.model.js';
|
} from '../models/error.model.js';
|
||||||
@ -71,7 +70,6 @@ export const withCanRetrieveRecordingsPermission = async (req: Request, res: Res
|
|||||||
*
|
*
|
||||||
* - The request is invoked using the API key.
|
* - The request is invoked using the API key.
|
||||||
* - The user is admin.
|
* - 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 anonymous and is using the public access secret.
|
||||||
* - The user is using the private access secret and is authenticated.
|
* - 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 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 storageService = container.get(MeetStorageService);
|
||||||
|
|
||||||
const secret = req.query.secret as string;
|
const secret = req.query.secret as string;
|
||||||
@ -143,9 +140,11 @@ export const configureRecordingMediaAuth = async (req: Request, res: Response, n
|
|||||||
|
|
||||||
switch (secret) {
|
switch (secret) {
|
||||||
case recordingSecrets.publicAccessSecret:
|
case recordingSecrets.publicAccessSecret:
|
||||||
|
// Public access secret allows anonymous access
|
||||||
authValidators.push(allowAnonymous);
|
authValidators.push(allowAnonymous);
|
||||||
break;
|
break;
|
||||||
case recordingSecrets.privateAccessSecret:
|
case recordingSecrets.privateAccessSecret:
|
||||||
|
// Private access secret requires authentication with user role
|
||||||
authValidators.push(tokenAndRoleValidator(UserRole.USER));
|
authValidators.push(tokenAndRoleValidator(UserRole.USER));
|
||||||
break;
|
break;
|
||||||
default:
|
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
|
// If no secret is provided, we proceed with the default authentication logic.
|
||||||
let recordingAccess: MeetRecordingAccess | undefined;
|
// This will allow API key, admin and recording token access.
|
||||||
|
|
||||||
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
|
|
||||||
const authValidators = [apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator];
|
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);
|
return withAuth(...authValidators)(req, res, next);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,12 @@ const StartRecordingRequestSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const GetRecordingSchema = 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({
|
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) => {
|
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) {
|
if (!success) {
|
||||||
return rejectUnprocessableRequest(res, error);
|
return rejectUnprocessableRequest(res, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.params.recordingId = data.recordingId;
|
req.params.recordingId = data.params.recordingId;
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -60,8 +60,7 @@ const validForceQueryParam = () =>
|
|||||||
const RecordingAccessSchema: z.ZodType<MeetRecordingAccess> = z.enum([
|
const RecordingAccessSchema: z.ZodType<MeetRecordingAccess> = z.enum([
|
||||||
MeetRecordingAccess.ADMIN,
|
MeetRecordingAccess.ADMIN,
|
||||||
MeetRecordingAccess.ADMIN_MODERATOR,
|
MeetRecordingAccess.ADMIN_MODERATOR,
|
||||||
MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER,
|
MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||||
MeetRecordingAccess.PUBLIC
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z
|
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Router } from 'express';
|
|||||||
import * as recordingCtrl from '../controllers/recording.controller.js';
|
import * as recordingCtrl from '../controllers/recording.controller.js';
|
||||||
import {
|
import {
|
||||||
apiKeyValidator,
|
apiKeyValidator,
|
||||||
configureRecordingMediaAuth,
|
configureRecordingAuth,
|
||||||
participantTokenValidator,
|
participantTokenValidator,
|
||||||
recordingTokenValidator,
|
recordingTokenValidator,
|
||||||
tokenAndRoleValidator,
|
tokenAndRoleValidator,
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
withCanRetrieveRecordingsPermission,
|
withCanRetrieveRecordingsPermission,
|
||||||
withRecordingEnabled,
|
withRecordingEnabled,
|
||||||
withValidGetRecordingMediaRequest,
|
withValidGetRecordingMediaRequest,
|
||||||
|
withValidGetRecordingRequest,
|
||||||
withValidGetRecordingUrlRequest,
|
withValidGetRecordingUrlRequest,
|
||||||
withValidRecordingBulkDeleteRequest,
|
withValidRecordingBulkDeleteRequest,
|
||||||
withValidRecordingFiltersRequest,
|
withValidRecordingFiltersRequest,
|
||||||
@ -41,8 +42,8 @@ recordingRouter.delete(
|
|||||||
);
|
);
|
||||||
recordingRouter.get(
|
recordingRouter.get(
|
||||||
'/:recordingId',
|
'/:recordingId',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
withValidGetRecordingRequest,
|
||||||
withValidRecordingId,
|
configureRecordingAuth,
|
||||||
withCanRetrieveRecordingsPermission,
|
withCanRetrieveRecordingsPermission,
|
||||||
recordingCtrl.getRecording
|
recordingCtrl.getRecording
|
||||||
);
|
);
|
||||||
@ -56,7 +57,7 @@ recordingRouter.delete(
|
|||||||
recordingRouter.get(
|
recordingRouter.get(
|
||||||
'/:recordingId/media',
|
'/:recordingId/media',
|
||||||
withValidGetRecordingMediaRequest,
|
withValidGetRecordingMediaRequest,
|
||||||
configureRecordingMediaAuth,
|
configureRecordingAuth,
|
||||||
withCanRetrieveRecordingsPermission,
|
withCanRetrieveRecordingsPermission,
|
||||||
recordingCtrl.getRecordingMedia
|
recordingCtrl.getRecordingMedia
|
||||||
);
|
);
|
||||||
|
|||||||
@ -46,8 +46,8 @@ roomRouter.delete(
|
|||||||
roomRouter.get(
|
roomRouter.get(
|
||||||
'/:roomId',
|
'/:roomId',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), participantTokenValidator),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), participantTokenValidator),
|
||||||
configureRoomAuthorization,
|
|
||||||
withValidRoomId,
|
withValidRoomId,
|
||||||
|
configureRoomAuthorization,
|
||||||
roomCtrl.getRoom
|
roomCtrl.getRoom
|
||||||
);
|
);
|
||||||
roomRouter.delete(
|
roomRouter.delete(
|
||||||
|
|||||||
@ -305,12 +305,10 @@ export class RoomService {
|
|||||||
|
|
||||||
/* A participant can retrieve recordings if
|
/* A participant can retrieve recordings if
|
||||||
- they can delete recordings
|
- they can delete recordings
|
||||||
- the recording access is public
|
|
||||||
- they are a publisher and the recording access includes publishers
|
- they are a publisher and the recording access includes publishers
|
||||||
*/
|
*/
|
||||||
const canRetrieveRecordings =
|
const canRetrieveRecordings =
|
||||||
canDeleteRecordings ||
|
canDeleteRecordings ||
|
||||||
recordingAccess === MeetRecordingAccess.PUBLIC ||
|
|
||||||
(role === ParticipantRole.PUBLISHER && recordingAccess === MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER);
|
(role === ParticipantRole.PUBLISHER && recordingAccess === MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -450,7 +448,7 @@ export class RoomService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Prepare rooms for batch update
|
// Prepare rooms for batch update
|
||||||
const roomsToUpdate: {roomId: string; room: MeetRoom}[] = [];
|
const roomsToUpdate: { roomId: string; room: MeetRoom }[] = [];
|
||||||
const successfulRoomIds: string[] = [];
|
const successfulRoomIds: string[] = [];
|
||||||
|
|
||||||
roomResults.forEach((result, index) => {
|
roomResults.forEach((result, index) => {
|
||||||
@ -459,7 +457,7 @@ export class RoomService {
|
|||||||
if (result.status === 'fulfilled' && result.value) {
|
if (result.status === 'fulfilled' && result.value) {
|
||||||
const room = result.value;
|
const room = result.value;
|
||||||
room.markedForDeletion = true;
|
room.markedForDeletion = true;
|
||||||
roomsToUpdate.push({roomId, room});
|
roomsToUpdate.push({ roomId, room });
|
||||||
successfulRoomIds.push(roomId);
|
successfulRoomIds.push(roomId);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
@ -470,7 +468,7 @@ export class RoomService {
|
|||||||
|
|
||||||
// Batch save all updated rooms
|
// Batch save all updated rooms
|
||||||
if (roomsToUpdate.length > 0) {
|
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`);
|
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 = 'admin', // Only admins can access the recording
|
||||||
ADMIN_MODERATOR = 'admin-moderator', // Admins and moderators can access
|
ADMIN_MODERATOR = 'admin-moderator', // Admins and moderators can access
|
||||||
ADMIN_MODERATOR_PUBLISHER = 'admin-moderator-publisher', // Admins, moderators and publishers can access
|
ADMIN_MODERATOR_PUBLISHER = 'admin-moderator-publisher', // Admins, moderators and publishers can access
|
||||||
PUBLIC = 'public', // Everyone can access
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeetChatPreferences {
|
export interface MeetChatPreferences {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user