backend: Enhance recording middleware and routes with new permission checks and authentication for media access
This commit is contained in:
parent
fd878cd3ea
commit
f1c59526e0
@ -1,21 +1,10 @@
|
||||
import { MeetRoom, OpenViduMeetPermissions } from '@typings-ce';
|
||||
import { MeetRecordingAccess, 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';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { LoggerService, RoomService } from '../services/index.js';
|
||||
|
||||
const extractRoomIdFromRequest = (req: Request): string => {
|
||||
if (req.body.roomId) {
|
||||
return req.body.roomId as string;
|
||||
}
|
||||
|
||||
// If roomId is not in the body, check if it's in the params
|
||||
const recordingId = req.params.recordingId as string;
|
||||
|
||||
const { roomId } = RecordingHelper.extractInfoFromRecordingId(recordingId);
|
||||
return roomId;
|
||||
};
|
||||
import { LoggerService, MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { allowAnonymous, recordingTokenValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
|
||||
|
||||
export const withRecordingEnabled = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
@ -23,8 +12,7 @@ export const withRecordingEnabled = async (req: Request, res: Response, next: Ne
|
||||
|
||||
try {
|
||||
const roomId = extractRoomIdFromRequest(req);
|
||||
|
||||
const room: MeetRoom = await roomService.getMeetRoom(roomId);
|
||||
const room: MeetRoom = await roomService.getMeetRoom(roomId!);
|
||||
|
||||
if (!room.preferences?.recordingPreferences?.enabled) {
|
||||
logger.debug(`Recording is disabled for room ${roomId}`);
|
||||
@ -42,7 +30,7 @@ export const withRecordingEnabled = async (req: Request, res: Response, next: Ne
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
message: 'Unexpected error checking recording permissions'
|
||||
message: 'Unexpected error checking recording preferences'
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -58,7 +46,7 @@ export const withCanRecordPermission = async (req: Request, res: Response, next:
|
||||
const sameRoom = payload.video?.room === roomId;
|
||||
const metadata = JSON.parse(payload.metadata || '{}');
|
||||
const permissions = metadata.permissions as OpenViduMeetPermissions | undefined;
|
||||
const canRecord = permissions?.canRecord === true;
|
||||
const canRecord = permissions?.canRecord;
|
||||
|
||||
if (!sameRoom || !canRecord) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
@ -66,3 +54,103 @@ export const withCanRecordPermission = async (req: Request, res: Response, next:
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
export const withCanRetrieveRecordingsPermission = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const roomId = extractRoomIdFromRequest(req);
|
||||
const payload = req.session?.tokenClaims;
|
||||
|
||||
// If there is no token, it is invoked using the API key, the user is admin or
|
||||
// the user is anonymous and recording access is public.
|
||||
// In this case, the user is allowed to access the resource
|
||||
if (!payload) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const sameRoom = roomId ? payload.video?.room === roomId : true;
|
||||
const metadata = JSON.parse(payload.metadata || '{}');
|
||||
const permissions = metadata.permissions as RecordingPermissions | undefined;
|
||||
const canRetrieveRecordings = permissions?.canRetrieveRecordings;
|
||||
|
||||
if (!sameRoom || !canRetrieveRecordings) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
export const withCanDeleteRecordingsPermission = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const roomId = extractRoomIdFromRequest(req);
|
||||
const payload = req.session?.tokenClaims;
|
||||
|
||||
// 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
|
||||
if (!payload) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const sameRoom = payload.video?.room === roomId;
|
||||
const metadata = JSON.parse(payload.metadata || '{}');
|
||||
const permissions = metadata.permissions as RecordingPermissions | undefined;
|
||||
const canDeleteRecordings = permissions?.canDeleteRecordings;
|
||||
|
||||
if (!sameRoom || !canDeleteRecordings) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware to configure authentication for retrieving recording media based on recording access.
|
||||
*
|
||||
* - Admin and recording token are always allowed
|
||||
* - If recording access is public, anonymous users are allowed
|
||||
*/
|
||||
export const configureRecordingMediaAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const storageService = container.get(MeetStorageService);
|
||||
|
||||
let recordingAccess: MeetRecordingAccess;
|
||||
|
||||
try {
|
||||
const roomId = extractRoomIdFromRequest(req);
|
||||
const room = await storageService.getArchivedRoomMetadata(roomId!);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({
|
||||
message: 'Room metadata associated with the recording not found'
|
||||
});
|
||||
}
|
||||
|
||||
recordingAccess = room.preferences!.recordingPreferences.allowAccessTo;
|
||||
} catch (error) {
|
||||
logger.error(`Error checking recording permissions: ${error}`);
|
||||
return res.status(500).json({
|
||||
message: 'Unexpected error checking recording permissions'
|
||||
});
|
||||
}
|
||||
|
||||
const authValidators = [tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator];
|
||||
|
||||
if (recordingAccess === MeetRecordingAccess.PUBLIC) {
|
||||
authValidators.push(allowAnonymous);
|
||||
}
|
||||
|
||||
return withAuth(...authValidators)(req, res, next);
|
||||
};
|
||||
|
||||
const extractRoomIdFromRequest = (req: Request): string | undefined => {
|
||||
if (req.body.roomId) {
|
||||
return req.body.roomId as string;
|
||||
}
|
||||
|
||||
// If roomId is not in the body, check if it's in the params
|
||||
const recordingId = req.params.recordingId as string;
|
||||
|
||||
if (!recordingId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { roomId } = RecordingHelper.extractInfoFromRecordingId(recordingId);
|
||||
return roomId;
|
||||
};
|
||||
|
||||
@ -4,10 +4,14 @@ import { Router } from 'express';
|
||||
import * as recordingCtrl from '../controllers/recording.controller.js';
|
||||
import {
|
||||
apiKeyValidator,
|
||||
configureRecordingMediaAuth,
|
||||
participantTokenValidator,
|
||||
recordingTokenValidator,
|
||||
tokenAndRoleValidator,
|
||||
withAuth,
|
||||
withCanDeleteRecordingsPermission,
|
||||
withCanRecordPermission,
|
||||
withCanRetrieveRecordingsPermission,
|
||||
withRecordingEnabled,
|
||||
withValidRecordingBulkDeleteRequest,
|
||||
withValidRecordingFiltersRequest,
|
||||
@ -22,19 +26,22 @@ recordingRouter.use(bodyParser.json());
|
||||
// Recording Routes
|
||||
recordingRouter.delete(
|
||||
'/:recordingId',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||
withValidRecordingId,
|
||||
withCanDeleteRecordingsPermission,
|
||||
recordingCtrl.deleteRecording
|
||||
);
|
||||
recordingRouter.get(
|
||||
'/:recordingId',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||
withValidRecordingId,
|
||||
withCanRetrieveRecordingsPermission,
|
||||
recordingCtrl.getRecording
|
||||
);
|
||||
recordingRouter.get(
|
||||
'/',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||
withCanRetrieveRecordingsPermission,
|
||||
withValidRecordingFiltersRequest,
|
||||
recordingCtrl.getRecordings
|
||||
);
|
||||
@ -44,11 +51,11 @@ recordingRouter.delete(
|
||||
withValidRecordingBulkDeleteRequest,
|
||||
recordingCtrl.bulkDeleteRecordings
|
||||
);
|
||||
|
||||
recordingRouter.get(
|
||||
'/:recordingId/media',
|
||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
configureRecordingMediaAuth,
|
||||
withValidRecordingId,
|
||||
withCanRetrieveRecordingsPermission,
|
||||
recordingCtrl.getRecordingMedia
|
||||
);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user