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 { 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';
|
||||||
import { OpenViduMeetError } from '../models/error.model.js';
|
import { OpenViduMeetError } from '../models/error.model.js';
|
||||||
import { LoggerService, RoomService } from '../services/index.js';
|
import { LoggerService, MeetStorageService, RoomService } from '../services/index.js';
|
||||||
|
import { allowAnonymous, recordingTokenValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const withRecordingEnabled = async (req: Request, res: Response, next: NextFunction) => {
|
export const withRecordingEnabled = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
@ -23,8 +12,7 @@ export const withRecordingEnabled = async (req: Request, res: Response, next: Ne
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const roomId = extractRoomIdFromRequest(req);
|
const roomId = extractRoomIdFromRequest(req);
|
||||||
|
const room: MeetRoom = await roomService.getMeetRoom(roomId!);
|
||||||
const room: MeetRoom = await roomService.getMeetRoom(roomId);
|
|
||||||
|
|
||||||
if (!room.preferences?.recordingPreferences?.enabled) {
|
if (!room.preferences?.recordingPreferences?.enabled) {
|
||||||
logger.debug(`Recording is disabled for room ${roomId}`);
|
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({
|
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 sameRoom = payload.video?.room === roomId;
|
||||||
const metadata = JSON.parse(payload.metadata || '{}');
|
const metadata = JSON.parse(payload.metadata || '{}');
|
||||||
const permissions = metadata.permissions as OpenViduMeetPermissions | undefined;
|
const permissions = metadata.permissions as OpenViduMeetPermissions | undefined;
|
||||||
const canRecord = permissions?.canRecord === true;
|
const canRecord = permissions?.canRecord;
|
||||||
|
|
||||||
if (!sameRoom || !canRecord) {
|
if (!sameRoom || !canRecord) {
|
||||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
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();
|
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 * as recordingCtrl from '../controllers/recording.controller.js';
|
||||||
import {
|
import {
|
||||||
apiKeyValidator,
|
apiKeyValidator,
|
||||||
|
configureRecordingMediaAuth,
|
||||||
participantTokenValidator,
|
participantTokenValidator,
|
||||||
|
recordingTokenValidator,
|
||||||
tokenAndRoleValidator,
|
tokenAndRoleValidator,
|
||||||
withAuth,
|
withAuth,
|
||||||
|
withCanDeleteRecordingsPermission,
|
||||||
withCanRecordPermission,
|
withCanRecordPermission,
|
||||||
|
withCanRetrieveRecordingsPermission,
|
||||||
withRecordingEnabled,
|
withRecordingEnabled,
|
||||||
withValidRecordingBulkDeleteRequest,
|
withValidRecordingBulkDeleteRequest,
|
||||||
withValidRecordingFiltersRequest,
|
withValidRecordingFiltersRequest,
|
||||||
@ -22,19 +26,22 @@ recordingRouter.use(bodyParser.json());
|
|||||||
// Recording Routes
|
// Recording Routes
|
||||||
recordingRouter.delete(
|
recordingRouter.delete(
|
||||||
'/:recordingId',
|
'/:recordingId',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||||
withValidRecordingId,
|
withValidRecordingId,
|
||||||
|
withCanDeleteRecordingsPermission,
|
||||||
recordingCtrl.deleteRecording
|
recordingCtrl.deleteRecording
|
||||||
);
|
);
|
||||||
recordingRouter.get(
|
recordingRouter.get(
|
||||||
'/:recordingId',
|
'/:recordingId',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||||
withValidRecordingId,
|
withValidRecordingId,
|
||||||
|
withCanRetrieveRecordingsPermission,
|
||||||
recordingCtrl.getRecording
|
recordingCtrl.getRecording
|
||||||
);
|
);
|
||||||
recordingRouter.get(
|
recordingRouter.get(
|
||||||
'/',
|
'/',
|
||||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator),
|
||||||
|
withCanRetrieveRecordingsPermission,
|
||||||
withValidRecordingFiltersRequest,
|
withValidRecordingFiltersRequest,
|
||||||
recordingCtrl.getRecordings
|
recordingCtrl.getRecordings
|
||||||
);
|
);
|
||||||
@ -44,11 +51,11 @@ recordingRouter.delete(
|
|||||||
withValidRecordingBulkDeleteRequest,
|
withValidRecordingBulkDeleteRequest,
|
||||||
recordingCtrl.bulkDeleteRecordings
|
recordingCtrl.bulkDeleteRecordings
|
||||||
);
|
);
|
||||||
|
|
||||||
recordingRouter.get(
|
recordingRouter.get(
|
||||||
'/:recordingId/media',
|
'/:recordingId/media',
|
||||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
|
configureRecordingMediaAuth,
|
||||||
withValidRecordingId,
|
withValidRecordingId,
|
||||||
|
withCanRetrieveRecordingsPermission,
|
||||||
recordingCtrl.getRecordingMedia
|
recordingCtrl.getRecordingMedia
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user