backend: update security preferences structure to remove unused attributes and refactor associated code

This commit is contained in:
juancarmore 2025-05-31 00:03:18 +02:00
parent dd3a2939e4
commit 55bc8726d0
7 changed files with 54 additions and 156 deletions

View File

@ -1,4 +1,4 @@
import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce'; import { SecurityPreferences } from '@typings-ce';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { container } from '../../config/index.js'; import { container } from '../../config/index.js';
import { handleError } from '../../models/error.model.js'; import { handleError } from '../../models/error.model.js';
@ -9,29 +9,15 @@ export const updateSecurityPreferences = async (req: Request, res: Response) =>
const globalPrefService = container.get(MeetStorageService); const globalPrefService = container.get(MeetStorageService);
logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`); logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`);
const securityPreferences = req.body as UpdateSecurityPreferencesDTO; const securityPreferences = req.body as SecurityPreferences;
try { try {
const globalPreferences = await globalPrefService.getGlobalPreferences(); const globalPreferences = await globalPrefService.getGlobalPreferences();
if (securityPreferences.roomCreationPolicy) {
globalPreferences.securityPreferences.roomCreationPolicy = {
allowRoomCreation: securityPreferences.roomCreationPolicy.allowRoomCreation,
requireAuthentication:
securityPreferences.roomCreationPolicy.requireAuthentication === undefined
? globalPreferences.securityPreferences.roomCreationPolicy.requireAuthentication
: securityPreferences.roomCreationPolicy.requireAuthentication
};
}
if (securityPreferences.authentication) {
const currentAuth = globalPreferences.securityPreferences.authentication; const currentAuth = globalPreferences.securityPreferences.authentication;
const newAuth = securityPreferences.authentication; const newAuth = securityPreferences.authentication;
currentAuth.authMode = newAuth.authMode; currentAuth.authMethod = newAuth.authMethod;
currentAuth.method.type = newAuth.method.type; currentAuth.authModeToAccessRoom = newAuth.authModeToAccessRoom;
}
await globalPrefService.saveGlobalPreferences(globalPreferences); await globalPrefService.saveGlobalPreferences(globalPreferences);
return res.status(200).json({ message: 'Security preferences updated successfully' }); return res.status(200).json({ message: 'Security preferences updated successfully' });
@ -48,19 +34,8 @@ export const getSecurityPreferences = async (_req: Request, res: Response) => {
try { try {
const preferences = await preferenceService.getGlobalPreferences(); const preferences = await preferenceService.getGlobalPreferences();
// Convert the preferences to the DTO format by removing credentials
const securityPreferences = preferences.securityPreferences; const securityPreferences = preferences.securityPreferences;
const securityPreferencesDTO: SecurityPreferencesDTO = { return res.status(200).json(securityPreferences);
roomCreationPolicy: securityPreferences.roomCreationPolicy,
authentication: {
authMode: securityPreferences.authentication.authMode,
method: {
type: securityPreferences.authentication.method.type
}
}
};
return res.status(200).json(securityPreferencesDTO);
} catch (error) { } catch (error) {
handleError(res, error, 'getting security preferences'); handleError(res, error, 'getting security preferences');
} }

View File

@ -25,22 +25,22 @@ export const configureParticipantTokenAuth = async (req: Request, res: Response,
return handleError(res, error, 'getting room role by secret'); return handleError(res, error, 'getting room role by secret');
} }
let authMode: AuthMode; let authModeToAccessRoom: AuthMode;
try { try {
const { securityPreferences } = await globalPrefService.getGlobalPreferences(); const { securityPreferences } = await globalPrefService.getGlobalPreferences();
authMode = securityPreferences.authentication.authMode; authModeToAccessRoom = securityPreferences.authentication.authModeToAccessRoom;
} catch (error) { } catch (error) {
return handleError(res, error, 'checking authentication preferences'); return handleError(res, error, 'checking authentication preferences');
} }
const authValidators = []; const authValidators = [];
if (authMode === AuthMode.NONE) { if (authModeToAccessRoom === AuthMode.NONE) {
authValidators.push(allowAnonymous); authValidators.push(allowAnonymous);
} else { } else {
const isModeratorsOnlyMode = authMode === AuthMode.MODERATORS_ONLY && role === ParticipantRole.MODERATOR; const isModeratorsOnlyMode = authModeToAccessRoom === AuthMode.MODERATORS_ONLY && role === ParticipantRole.MODERATOR;
const isAllUsersMode = authMode === AuthMode.ALL_USERS; const isAllUsersMode = authModeToAccessRoom === AuthMode.ALL_USERS;
if (isModeratorsOnlyMode || isAllUsersMode) { if (isModeratorsOnlyMode || isAllUsersMode) {
authValidators.push(tokenAndRoleValidator(UserRole.USER)); authValidators.push(tokenAndRoleValidator(UserRole.USER));

View File

@ -1,11 +1,10 @@
import { import {
AuthenticationPreferencesDTO, AuthenticationPreferences,
AuthMode, AuthMode,
AuthType, AuthType,
RoomCreationPolicy, SecurityPreferences,
SingleUserAuthDTO, SingleUserAuth,
UpdateSecurityPreferencesDTO, ValidAuthMethod,
ValidAuthMethodDTO,
WebhookPreferences WebhookPreferences
} from '@typings-ce'; } from '@typings-ce';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
@ -36,40 +35,19 @@ const AuthModeSchema: z.ZodType<AuthMode> = z.enum([AuthMode.NONE, AuthMode.MODE
const AuthTypeSchema: z.ZodType<AuthType> = z.enum([AuthType.SINGLE_USER]); const AuthTypeSchema: z.ZodType<AuthType> = z.enum([AuthType.SINGLE_USER]);
const SingleUserAuthDTOSchema: z.ZodType<SingleUserAuthDTO> = z.object({ const SingleUserAuthSchema: z.ZodType<SingleUserAuth> = z.object({
type: AuthTypeSchema type: AuthTypeSchema
}); });
const ValidAuthMethodDTOSchema: z.ZodType<ValidAuthMethodDTO> = SingleUserAuthDTOSchema; const ValidAuthMethodSchema: z.ZodType<ValidAuthMethod> = SingleUserAuthSchema;
const AuthenticationPreferencesDTOSchema: z.ZodType<AuthenticationPreferencesDTO> = z.object({ const AuthenticationPreferencesSchema: z.ZodType<AuthenticationPreferences> = z.object({
authMode: AuthModeSchema, authMethod: ValidAuthMethodSchema,
method: ValidAuthMethodDTOSchema authModeToAccessRoom: AuthModeSchema
}); });
const RoomCreationPolicySchema: z.ZodType<RoomCreationPolicy> = z const SecurityPreferencesSchema: z.ZodType<SecurityPreferences> = z.object({
.object({ authentication: AuthenticationPreferencesSchema
allowRoomCreation: z.boolean(),
requireAuthentication: z.boolean().optional()
})
.refine(
(data) => {
// If allowRoomCreation is true, requireAuthentication must be provided
return !data.allowRoomCreation || data.requireAuthentication !== undefined;
},
{
message: 'requireAuthentication is required when allowRoomCreation is true',
path: ['requireAuthentication']
}
);
const UpdateSecurityPreferencesDTOSchema: z.ZodType<UpdateSecurityPreferencesDTO> = z
.object({
authentication: AuthenticationPreferencesDTOSchema.optional(),
roomCreationPolicy: RoomCreationPolicySchema.optional()
})
.refine((data) => Object.keys(data).length > 0, {
message: 'At least one field must be provided for the update'
}); });
export const validateWebhookPreferences = (req: Request, res: Response, next: NextFunction) => { export const validateWebhookPreferences = (req: Request, res: Response, next: NextFunction) => {
@ -84,7 +62,7 @@ export const validateWebhookPreferences = (req: Request, res: Response, next: Ne
}; };
export const validateSecurityPreferences = (req: Request, res: Response, next: NextFunction) => { export const validateSecurityPreferences = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = UpdateSecurityPreferencesDTOSchema.safeParse(req.body); const { success, error, data } = SecurityPreferencesSchema.safeParse(req.body);
if (!success) { if (!success) {
return rejectUnprocessableRequest(res, error); return rejectUnprocessableRequest(res, error);

View File

@ -8,39 +8,7 @@ import {
rejectRequestFromMeetError rejectRequestFromMeetError
} from '../models/error.model.js'; } from '../models/error.model.js';
import { MeetStorageService, RoomService } from '../services/index.js'; import { MeetStorageService, RoomService } from '../services/index.js';
import { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js'; import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
/**
* Middleware that configures authentication for creating a room based on global settings.
*
* - Admin role and API key authentication methods are always allowed.
* - If room creation is allowed and requires authentication, the user must have a valid token.
* - If room creation is allowed and does not require authentication, anonymous users are allowed.
*/
export const configureCreateRoomAuth = async (req: Request, res: Response, next: NextFunction) => {
const globalPrefService = container.get(MeetStorageService);
let allowRoomCreation: boolean;
let requireAuthentication: boolean | undefined;
try {
const { securityPreferences } = await globalPrefService.getGlobalPreferences();
({ allowRoomCreation, requireAuthentication } = securityPreferences.roomCreationPolicy);
} catch (error) {
return handleError(res, error, 'checking room creation policy');
}
const authValidators = [apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)];
if (allowRoomCreation) {
if (requireAuthentication) {
authValidators.push(tokenAndRoleValidator(UserRole.USER));
} else {
authValidators.push(allowAnonymous);
}
}
return withAuth(...authValidators)(req, res, next);
};
/** /**
* Middleware that configures authorization for accessing a specific room. * Middleware that configures authorization for accessing a specific room.
@ -79,7 +47,7 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
}; };
/** /**
* Middleware to configure authentication based on participant role and authentication mode * Middleware to configure authentication based on participant role and authentication mode to access a room
* for generating a token for retrieving/deleting recordings. * for generating a token for retrieving/deleting recordings.
* *
* - If the authentication mode is MODERATORS_ONLY and the participant role is MODERATOR, configure user authentication. * - If the authentication mode is MODERATORS_ONLY and the participant role is MODERATOR, configure user authentication.
@ -114,22 +82,22 @@ export const configureRecordingTokenAuth = async (req: Request, res: Response, n
return handleError(res, error, 'getting room role by secret'); return handleError(res, error, 'getting room role by secret');
} }
let authMode: AuthMode; let authModeToAccessRoom: AuthMode;
try { try {
const { securityPreferences } = await storageService.getGlobalPreferences(); const { securityPreferences } = await storageService.getGlobalPreferences();
authMode = securityPreferences.authentication.authMode; authModeToAccessRoom = securityPreferences.authentication.authModeToAccessRoom;
} catch (error) { } catch (error) {
return handleError(res, error, 'checking authentication preferences'); return handleError(res, error, 'checking authentication preferences');
} }
const authValidators = []; const authValidators = [];
if (authMode === AuthMode.NONE) { if (authModeToAccessRoom === AuthMode.NONE) {
authValidators.push(allowAnonymous); authValidators.push(allowAnonymous);
} else { } else {
const isModeratorsOnlyMode = authMode === AuthMode.MODERATORS_ONLY && role === ParticipantRole.MODERATOR; const isModeratorsOnlyMode = authModeToAccessRoom === AuthMode.MODERATORS_ONLY && role === ParticipantRole.MODERATOR;
const isAllUsersMode = authMode === AuthMode.ALL_USERS; const isAllUsersMode = authModeToAccessRoom === AuthMode.ALL_USERS;
if (isModeratorsOnlyMode || isAllUsersMode) { if (isModeratorsOnlyMode || isAllUsersMode) {
authValidators.push(tokenAndRoleValidator(UserRole.USER)); authValidators.push(tokenAndRoleValidator(UserRole.USER));

View File

@ -3,8 +3,8 @@ import bodyParser from 'body-parser';
import { Router } from 'express'; import { Router } from 'express';
import * as roomCtrl from '../controllers/room.controller.js'; import * as roomCtrl from '../controllers/room.controller.js';
import { import {
allowAnonymous,
apiKeyValidator, apiKeyValidator,
configureCreateRoomAuth,
configureRecordingTokenAuth, configureRecordingTokenAuth,
configureRoomAuthorization, configureRoomAuthorization,
participantTokenValidator, participantTokenValidator,
@ -24,7 +24,12 @@ roomRouter.use(bodyParser.urlencoded({ extended: true }));
roomRouter.use(bodyParser.json()); roomRouter.use(bodyParser.json());
// Room Routes // Room Routes
roomRouter.post('/', configureCreateRoomAuth, withValidRoomOptions, roomCtrl.createRoom); roomRouter.post(
'/',
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
withValidRoomOptions,
roomCtrl.createRoom
);
roomRouter.get( roomRouter.get(
'/', '/',
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)), withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
@ -56,7 +61,6 @@ export const internalRoomRouter = Router();
internalRoomRouter.use(bodyParser.urlencoded({ extended: true })); internalRoomRouter.use(bodyParser.urlencoded({ extended: true }));
internalRoomRouter.use(bodyParser.json()); internalRoomRouter.use(bodyParser.json());
// Room preferences
internalRoomRouter.put( internalRoomRouter.put(
'/:roomId', '/:roomId',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)), withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
@ -64,7 +68,6 @@ internalRoomRouter.put(
withValidRoomPreferences, withValidRoomPreferences,
roomCtrl.updateRoomPreferences roomCtrl.updateRoomPreferences
); );
internalRoomRouter.post( internalRoomRouter.post(
'/:roomId/recording-token', '/:roomId/recording-token',
configureRecordingTokenAuth, configureRecordingTokenAuth,
@ -72,7 +75,15 @@ internalRoomRouter.post(
withValidRoomSecret, withValidRoomSecret,
roomCtrl.generateRecordingToken roomCtrl.generateRecordingToken
); );
internalRoomRouter.get(
// Roles and permissions '/:roomId/roles',
internalRoomRouter.get('/:roomId/roles', withValidRoomId, roomCtrl.getRoomRolesAndPermissions); withAuth(allowAnonymous),
internalRoomRouter.get('/:roomId/roles/:secret', withValidRoomId, roomCtrl.getRoomRoleAndPermissions); withValidRoomId,
roomCtrl.getRoomRolesAndPermissions
);
internalRoomRouter.get(
'/:roomId/roles/:secret',
withAuth(allowAnonymous),
withValidRoomId,
roomCtrl.getRoomRoleAndPermissions
);

View File

@ -1,6 +1,6 @@
export interface AuthenticationPreferences { export interface AuthenticationPreferences {
authMode: AuthMode; authMethod: ValidAuthMethod;
method: ValidAuthMethod; authModeToAccessRoom: AuthMode;
} }
/** /**
@ -33,7 +33,6 @@ export const enum AuthType {
*/ */
export interface SingleUserAuth extends AuthMethod { export interface SingleUserAuth extends AuthMethod {
type: AuthType.SINGLE_USER; type: AuthType.SINGLE_USER;
credentials: SingleUserCredentials;
} }
/** /**
@ -57,14 +56,6 @@ export interface SingleUserAuth extends AuthMethod {
*/ */
export type ValidAuthMethod = SingleUserAuth /* | MultiUserAuth | OAuthOnlyAuth */; export type ValidAuthMethod = SingleUserAuth /* | MultiUserAuth | OAuthOnlyAuth */;
/**
* Configuration for a single user login method.
*/
export interface SingleUserCredentials {
username: string;
passwordHash: string;
}
/** /**
* Configuration for OAuth authentication. * Configuration for OAuth authentication.
*/ */
@ -82,13 +73,3 @@ export interface SingleUserCredentials {
// GOOGLE = 'google', // GOOGLE = 'google',
// GITHUB = 'github' // GITHUB = 'github'
// } // }
// DTOs
export interface AuthenticationPreferencesDTO {
authMode: AuthMode;
method: ValidAuthMethodDTO;
}
export type ValidAuthMethodDTO = SingleUserAuthDTO;
export type SingleUserAuthDTO = Omit<SingleUserAuth, 'credentials'>;

View File

@ -1,4 +1,4 @@
import { AuthenticationPreferences, AuthenticationPreferencesDTO } from './auth-preferences.js'; import { AuthenticationPreferences } from './auth-preferences.js';
/** /**
* Represents global preferences for OpenVidu Meet. * Represents global preferences for OpenVidu Meet.
@ -18,19 +18,4 @@ export interface WebhookPreferences {
export interface SecurityPreferences { export interface SecurityPreferences {
authentication: AuthenticationPreferences; authentication: AuthenticationPreferences;
roomCreationPolicy: RoomCreationPolicy;
// e2eEncryption: {};
} }
export interface RoomCreationPolicy {
allowRoomCreation: boolean;
requireAuthentication?: boolean;
}
// DTOs
export interface SecurityPreferencesDTO {
authentication: AuthenticationPreferencesDTO;
roomCreationPolicy: RoomCreationPolicy;
}
export type UpdateSecurityPreferencesDTO = Partial<SecurityPreferencesDTO>;