187 lines
6.2 KiB
TypeScript
187 lines
6.2 KiB
TypeScript
import { MeetRoom, UserRole } from '@typings-ce';
|
|
import { NextFunction, Request, Response } from 'express';
|
|
import { container } from '../config/index.js';
|
|
import { RecordingHelper } from '../helpers/index.js';
|
|
import {
|
|
errorInsufficientPermissions,
|
|
errorInvalidRecordingSecret,
|
|
errorRecordingDisabled,
|
|
errorRecordingNotFound,
|
|
handleError,
|
|
rejectRequestFromMeetError
|
|
} from '../models/error.model.js';
|
|
import { LoggerService, MeetStorageService, ParticipantService, RoomService } from '../services/index.js';
|
|
import {
|
|
allowAnonymous,
|
|
apiKeyValidator,
|
|
recordingTokenValidator,
|
|
tokenAndRoleValidator,
|
|
withAuth
|
|
} from './auth.middleware.js';
|
|
|
|
export const withRecordingEnabled = async (req: Request, res: Response, next: NextFunction) => {
|
|
const logger = container.get(LoggerService);
|
|
const roomService = container.get(RoomService);
|
|
|
|
try {
|
|
const roomId = extractRoomIdFromRequest(req);
|
|
const room: MeetRoom = await roomService.getMeetRoom(roomId!);
|
|
|
|
if (!room.preferences?.recordingPreferences?.enabled) {
|
|
logger.debug(`Recording is disabled for room '${roomId}'`);
|
|
const error = errorRecordingDisabled(roomId!);
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
return next();
|
|
} catch (error) {
|
|
handleError(res, error, 'checking recording preferences');
|
|
}
|
|
};
|
|
|
|
export const withCanRecordPermission = async (req: Request, res: Response, next: NextFunction) => {
|
|
const roomId = extractRoomIdFromRequest(req);
|
|
const payload = req.session?.tokenClaims;
|
|
const role = req.session?.participantRole;
|
|
|
|
if (!payload || !role) {
|
|
const error = errorInsufficientPermissions();
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
const participantService = container.get(ParticipantService);
|
|
const metadata = participantService.parseMetadata(payload.metadata || '{}');
|
|
|
|
const sameRoom = payload.video?.room === roomId;
|
|
const permissions = metadata.roles.find((r) => r.role === role)?.permissions;
|
|
const canRecord = permissions?.canRecord;
|
|
|
|
if (!sameRoom || !canRecord) {
|
|
const error = errorInsufficientPermissions();
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
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, 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 is using the public access secret.
|
|
* - The user is using the private access secret and is authenticated.
|
|
*/
|
|
if (!payload) {
|
|
return next();
|
|
}
|
|
|
|
const roomService = container.get(RoomService);
|
|
const metadata = roomService.parseRecordingTokenMetadata(payload.metadata || '{}');
|
|
|
|
const sameRoom = roomId ? payload.video?.room === roomId : true;
|
|
const canRetrieveRecordings = metadata.recordingPermissions.canRetrieveRecordings;
|
|
|
|
if (!sameRoom || !canRetrieveRecordings) {
|
|
const error = errorInsufficientPermissions();
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
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 roomService = container.get(RoomService);
|
|
const metadata = roomService.parseRecordingTokenMetadata(payload.metadata || '{}');
|
|
|
|
const sameRoom = roomId ? payload.video?.room === roomId : true;
|
|
const canDeleteRecordings = metadata.recordingPermissions.canDeleteRecordings;
|
|
|
|
if (!sameRoom || !canDeleteRecordings) {
|
|
const error = errorInsufficientPermissions();
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
return next();
|
|
};
|
|
|
|
/**
|
|
* Middleware to configure authentication for retrieving recording based on the provided secret.
|
|
*
|
|
* - If a valid secret is provided in the query, access is granted according to the secret type.
|
|
* - If no secret is provided, the default authentication logic is applied, i.e., API key, admin and recording token access.
|
|
*/
|
|
export const configureRecordingAuth = async (req: Request, res: Response, next: NextFunction) => {
|
|
const storageService = container.get(MeetStorageService);
|
|
|
|
const secret = req.query.secret as string;
|
|
|
|
// If a secret is provided, validate it against the stored secrets
|
|
// and apply the appropriate authentication logic.
|
|
if (secret) {
|
|
try {
|
|
const recordingId = req.params.recordingId as string;
|
|
const recordingSecrets = await storageService.getAccessRecordingSecrets(recordingId);
|
|
|
|
if (!recordingSecrets) {
|
|
const error = errorRecordingNotFound(recordingId);
|
|
return rejectRequestFromMeetError(res, error);
|
|
}
|
|
|
|
const authValidators = [];
|
|
|
|
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:
|
|
// Invalid secret provided
|
|
return rejectRequestFromMeetError(res, errorInvalidRecordingSecret(recordingId, secret));
|
|
}
|
|
|
|
return withAuth(...authValidators)(req, res, next);
|
|
} catch (error) {
|
|
return handleError(res, error, 'retrieving recording secrets');
|
|
}
|
|
}
|
|
|
|
// 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];
|
|
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;
|
|
};
|