Rename global preferences to global config

This commit is contained in:
juancarmore 2025-09-18 13:02:56 +02:00
parent 465a68295f
commit fa1582bee0
82 changed files with 1239 additions and 1261 deletions

File diff suppressed because it is too large Load Diff

View File

@ -25,14 +25,15 @@ npm run build:prod
## Storage Structure ## Storage Structure
The OpenVidu Meet backend uses an S3 bucket to store all application data, including rooms, recordings, user information, and system preferences. The bucket follows a hierarchical structure organized as follows: The OpenVidu Meet backend uses an S3 bucket to store all application data, including rooms, recordings, user information, and system config. The bucket follows a hierarchical structure organized as follows:
### Bucket Structure ### Bucket Structure
```plaintext ```plaintext
openvidu-appdata/ openvidu-appdata/
├── openvidu-meet/ ├── openvidu-meet/
│ ├── global-preferences.json │ ├── api-keys.json
│ ├── global-config.json
│ ├── users/ │ ├── users/
│ │ └── admin.json │ │ └── admin.json
│ ├── rooms/ │ ├── rooms/
@ -56,13 +57,17 @@ openvidu-appdata/
### Directory Descriptions ### Directory Descriptions
#### **Global Preferences** (`global-preferences.json`) #### **API Keys** (`api-keys.json`)
Contains system-wide settings and configurations for the OpenVidu Meet application, such as default recording settings, UI preferences, and feature toggles. Stores API keys used for authenticating requests to the OpenVidu Meet API. This file contains a list of valid API keys along with their creation dates.
#### **Global Config** (`global-config.json`)
Contains system-wide settings and configurations for the OpenVidu Meet application, such as security config, webhook config and global rooms appearance.
#### **Users** (`users/`) #### **Users** (`users/`)
Stores user account information in individual JSON files. Each file is named using the username (e.g., `admin.json`) and contains user-specific data including authentication details, permissions, and preferences. Stores user account information in individual JSON files. Each file is named using the username (e.g., `admin.json`) and contains user-specific data including authentication details and roles.
#### **Rooms** (`rooms/`) #### **Rooms** (`rooms/`)

View File

@ -0,0 +1,6 @@
description: New security config
required: true
content:
application/json:
schema:
$ref: '../../schemas/internal/global-security-config.yaml'

View File

@ -1,6 +0,0 @@
description: New security preferences
required: true
content:
application/json:
schema:
$ref: '../../schemas/internal/security-preferences.yaml'

View File

@ -0,0 +1,6 @@
description: New webhooks config
required: true
content:
application/json:
schema:
$ref: '../../schemas/internal/webhooks-config.yaml'

View File

@ -1,6 +0,0 @@
description: New webhooks preferences
required: true
content:
application/json:
schema:
$ref: '../../schemas/internal/webhooks-preferences.yaml'

View File

@ -0,0 +1,5 @@
description: Successfully retrieved security config
content:
application/json:
schema:
$ref: '../../schemas/internal/global-security-config.yaml'

View File

@ -1,5 +0,0 @@
description: Successfully retrieved security preferences
content:
application/json:
schema:
$ref: '../../schemas/internal/security-preferences.yaml'

View File

@ -0,0 +1,5 @@
description: Successfully retrieved webhooks config
content:
application/json:
schema:
$ref: '../../schemas/internal/webhooks-config.yaml'

View File

@ -1,5 +0,0 @@
description: Successfully retrieved webhooks preferences
content:
application/json:
schema:
$ref: '../../schemas/internal/webhooks-preferences.yaml'

View File

@ -1,4 +1,4 @@
description: Successfully updated security preferences description: Successfully updated security config
content: content:
application/json: application/json:
schema: schema:
@ -6,4 +6,4 @@ content:
properties: properties:
message: message:
type: string type: string
example: 'Security preferences updated successfully' example: 'Security config updated successfully'

View File

@ -1,4 +1,4 @@
description: Successfully updated webhooks preferences description: Successfully updated webhooks config
content: content:
application/json: application/json:
schema: schema:
@ -6,4 +6,4 @@ content:
properties: properties:
message: message:
type: string type: string
example: 'Webhooks preferences updated successfully' example: 'Webhooks config updated successfully'

View File

@ -1,10 +1,10 @@
type: object type: object
properties: properties:
authentication: authentication:
$ref: '#/AuthenticationPreferences' $ref: '#/AuthenticationConfig'
description: Preferences for authentication. description: Config for authentication.
AuthenticationPreferences: AuthenticationConfig:
type: object type: object
properties: properties:
authMethod: authMethod:

View File

@ -20,14 +20,14 @@ paths:
$ref: './paths/internal/users.yaml#/~1users~1profile' $ref: './paths/internal/users.yaml#/~1users~1profile'
/users/change-password: /users/change-password:
$ref: './paths/internal/users.yaml#/~1users~1change-password' $ref: './paths/internal/users.yaml#/~1users~1change-password'
/preferences/webhooks: /config/webhooks:
$ref: './paths/internal/global-preferences.yaml#/~1preferences~1webhooks' $ref: './paths/internal/meet-global-config.yaml#/~1config~1webhooks'
/preferences/webhooks/test: /config/webhooks/test:
$ref: './paths/internal/global-preferences.yaml#/~1preferences~1webhooks~1test' $ref: './paths/internal/meet-global-config.yaml#/~1config~1webhooks~1test'
/preferences/security: /config/security:
$ref: './paths/internal/global-preferences.yaml#/~1preferences~1security' $ref: './paths/internal/meet-global-config.yaml#/~1config~1security'
/preferences/appearance: /config/appearance:
$ref: './paths/internal/global-preferences.yaml#/~1preferences~1appearance' $ref: './paths/internal/meet-global-config.yaml#/~1config~1appearance'
/rooms/{roomId}/recording-token: /rooms/{roomId}/recording-token:
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1recording-token' $ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1recording-token'
/rooms/{roomId}/roles: /rooms/{roomId}/roles:
@ -55,10 +55,10 @@ components:
schemas: schemas:
User: User:
$ref: components/schemas/internal/user.yaml $ref: components/schemas/internal/user.yaml
WebhooksPreferences: WebhooksConfig:
$ref: components/schemas/internal/webhooks-preferences.yaml $ref: components/schemas/internal/webhooks-config.yaml
SecurityPreferences: SecurityConfig:
$ref: components/schemas/internal/security-preferences.yaml $ref: components/schemas/internal/global-security-config.yaml
MeetRoom: MeetRoom:
$ref: components/schemas/meet-room.yaml $ref: components/schemas/meet-room.yaml
MeetRoomOptions: MeetRoomOptions:

View File

@ -1,16 +1,16 @@
/preferences/webhooks: /config/webhooks:
get: get:
operationId: getWebhooksPreferences operationId: getWebhooksConfig
summary: Get webhooks preferences summary: Get webhooks config
description: > description: >
Retrieves the webhooks preferences for the OpenVidu Meet application. Retrieves the webhooks config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
security: security:
- accessTokenCookie: [] - accessTokenCookie: []
responses: responses:
'200': '200':
$ref: '../../components/responses/internal/success-get-webhooks-preferences.yaml' $ref: '../../components/responses/internal/success-get-webhooks-config.yaml'
'401': '401':
$ref: '../../components/responses/unauthorized-error.yaml' $ref: '../../components/responses/unauthorized-error.yaml'
'403': '403':
@ -18,19 +18,19 @@
'500': '500':
$ref: '../../components/responses/internal-server-error.yaml' $ref: '../../components/responses/internal-server-error.yaml'
put: put:
operationId: updateWebhooksPreferences operationId: updateWebhooksConfig
summary: Update webhooks preferences summary: Update webhooks config
description: > description: >
Updates the webhooks preferences for the OpenVidu Meet application. Updates the webhooks config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
security: security:
- accessTokenCookie: [] - accessTokenCookie: []
requestBody: requestBody:
$ref: '../../components/requestBodies/internal/update-webhooks-preferences.yaml' $ref: '../../components/requestBodies/internal/update-webhooks-config.yaml'
responses: responses:
'200': '200':
$ref: '../../components/responses/internal/success-update-webhooks-preferences.yaml' $ref: '../../components/responses/internal/success-update-webhooks-config.yaml'
'401': '401':
$ref: '../../components/responses/unauthorized-error.yaml' $ref: '../../components/responses/unauthorized-error.yaml'
'403': '403':
@ -40,14 +40,14 @@
'500': '500':
$ref: '../../components/responses/internal-server-error.yaml' $ref: '../../components/responses/internal-server-error.yaml'
/preferences/webhooks/test: /config/webhooks/test:
post: post:
operationId: testWebhookUrl operationId: testWebhookUrl
summary: Test webhook URL summary: Test webhook URL
description: > description: >
Tests the provided webhook URL to ensure it is valid and reachable. Tests the provided webhook URL to ensure it is valid and reachable.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
requestBody: requestBody:
$ref: '../../components/requestBodies/internal/test-webhook-url-request.yaml' $ref: '../../components/requestBodies/internal/test-webhook-url-request.yaml'
responses: responses:
@ -60,33 +60,33 @@
'500': '500':
$ref: '../../components/responses/internal-server-error.yaml' $ref: '../../components/responses/internal-server-error.yaml'
/preferences/security: /config/security:
get: get:
operationId: getSecurityPreferences operationId: getSecurityConfig
summary: Get security preferences summary: Get security config
description: > description: >
Retrieves the security preferences for the OpenVidu Meet application. Retrieves the security config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
responses: responses:
'200': '200':
$ref: '../../components/responses/internal/success-get-security-preferences.yaml' $ref: '../../components/responses/internal/success-get-security-config.yaml'
'500': '500':
$ref: '../../components/responses/internal-server-error.yaml' $ref: '../../components/responses/internal-server-error.yaml'
put: put:
operationId: updateSecurityPreferences operationId: updateSecurityConfig
summary: Update security preferences summary: Update security config
description: > description: >
Updates the security preferences for the OpenVidu Meet application. Updates the security config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
security: security:
- accessTokenCookie: [] - accessTokenCookie: []
requestBody: requestBody:
$ref: '../../components/requestBodies/internal/update-security-preferences.yaml' $ref: '../../components/requestBodies/internal/update-security-config.yaml'
responses: responses:
'200': '200':
$ref: '../../components/responses/internal/success-update-security-preferences.yaml' $ref: '../../components/responses/internal/success-update-security-config.yaml'
'401': '401':
$ref: '../../components/responses/unauthorized-error.yaml' $ref: '../../components/responses/unauthorized-error.yaml'
'403': '403':
@ -96,26 +96,26 @@
'500': '500':
$ref: '../../components/responses/internal-server-error.yaml' $ref: '../../components/responses/internal-server-error.yaml'
/preferences/appearance: /config/appearance:
get: get:
operationId: getAppearancePreferences operationId: getAppearanceConfig
summary: Get appearance preferences summary: Get appearance config
description: > description: >
Retrieves the appearance preferences for the OpenVidu Meet application. Retrieves the appearance config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
security: security:
- accessTokenCookie: [] - accessTokenCookie: []
responses: responses:
'202': '202':
$ref: '../../components/responses/pro-feature-error.yaml' $ref: '../../components/responses/pro-feature-error.yaml'
put: put:
operationId: updateAppearancePreferences operationId: updateAppearanceConfig
summary: Update appearance preferences summary: Update appearance config
description: > description: >
Updates the appearance preferences for the OpenVidu Meet application. Updates the appearance config for the OpenVidu Meet application.
tags: tags:
- Internal API - Global Preferences - Internal API - Global Config
security: security:
- accessTokenCookie: [] - accessTokenCookie: []
responses: responses:

View File

@ -6,8 +6,8 @@
description: Authentication operations description: Authentication operations
- name: Internal API - Users - name: Internal API - Users
description: Operations related to managing users in OpenVidu Meet description: Operations related to managing users in OpenVidu Meet
- name: Internal API - Global Preferences - name: Internal API - Global Config
description: Operations related to managing global preferences in OpenVidu Meet description: Operations related to managing global config in OpenVidu Meet
- name: Internal API - Rooms - name: Internal API - Rooms
description: Operations related to managing OpenVidu Meet rooms description: Operations related to managing OpenVidu Meet rooms
- name: Internal API - Participant - name: Internal API - Participant

View File

@ -42,7 +42,7 @@
"test:integration-recordings": "node --experimental-vm-modules node_modules/.bin/jest --maxWorkers=1 --maxConcurrency=1 --forceExit --testPathPattern \"tests/integration/api/recordings\" --ci --reporters=default --reporters=jest-junit", "test:integration-recordings": "node --experimental-vm-modules node_modules/.bin/jest --maxWorkers=1 --maxConcurrency=1 --forceExit --testPathPattern \"tests/integration/api/recordings\" --ci --reporters=default --reporters=jest-junit",
"test:integration-webhooks": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/webhooks\" --ci --reporters=default --reporters=jest-junit", "test:integration-webhooks": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/webhooks\" --ci --reporters=default --reporters=jest-junit",
"test:integration-security": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/security\" --ci --reporters=default --reporters=jest-junit", "test:integration-security": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/security\" --ci --reporters=default --reporters=jest-junit",
"test:integration-global-preferences": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/global-preferences\" --ci --reporters=default --reporters=jest-junit", "test:integration-global-config": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/global-config\" --ci --reporters=default --reporters=jest-junit",
"test:integration-participants": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/participants\" --ci --reporters=default --reporters=jest-junit", "test:integration-participants": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/participants\" --ci --reporters=default --reporters=jest-junit",
"test:integration-meetings": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/meetings\" --ci --reporters=default --reporters=jest-junit", "test:integration-meetings": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/meetings\" --ci --reporters=default --reporters=jest-junit",
"test:integration-users": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/users\" --ci --reporters=default --reporters=jest-junit", "test:integration-users": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/users\" --ci --reporters=default --reporters=jest-junit",

View File

@ -1,15 +1,18 @@
import { Container } from 'inversify'; import { Container } from 'inversify';
import { MEET_PREFERENCES_STORAGE_MODE } from '../environment.js'; import { MEET_BLOB_STORAGE_MODE } from '../environment.js';
import { import {
ABSService, ABSService,
ABSStorageProvider, ABSStorageProvider,
AuthService, AuthService,
DistributedEventService,
FrontendEventService,
LiveKitService, LiveKitService,
LivekitWebhookService, LivekitWebhookService,
LoggerService, LoggerService,
MeetStorageService, MeetStorageService,
MutexService, MutexService,
OpenViduWebhookService, OpenViduWebhookService,
ParticipantNameService,
ParticipantService, ParticipantService,
RecordingService, RecordingService,
RedisService, RedisService,
@ -20,12 +23,9 @@ import {
StorageFactory, StorageFactory,
StorageKeyBuilder, StorageKeyBuilder,
StorageProvider, StorageProvider,
DistributedEventService,
TaskSchedulerService, TaskSchedulerService,
TokenService, TokenService,
UserService, UserService
FrontendEventService,
ParticipantNameService
} from '../services/index.js'; } from '../services/index.js';
export const container: Container = new Container(); export const container: Container = new Container();
@ -51,7 +51,7 @@ export const registerDependencies = () => {
container.bind(MutexService).toSelf().inSingletonScope(); container.bind(MutexService).toSelf().inSingletonScope();
container.bind(TaskSchedulerService).toSelf().inSingletonScope(); container.bind(TaskSchedulerService).toSelf().inSingletonScope();
configureStorage(MEET_PREFERENCES_STORAGE_MODE); configureStorage(MEET_BLOB_STORAGE_MODE);
container.bind(StorageFactory).toSelf().inSingletonScope(); container.bind(StorageFactory).toSelf().inSingletonScope();
container.bind(MeetStorageService).toSelf().inSingletonScope(); container.bind(MeetStorageService).toSelf().inSingletonScope();

View File

@ -0,0 +1,12 @@
import { Request, Response } from 'express';
import { errorProFeature, rejectRequestFromMeetError } from '../../models/error.model.js';
export const updateAppearanceConfig = async (_req: Request, res: Response) => {
const error = errorProFeature('update appearance config');
rejectRequestFromMeetError(res, error);
};
export const getAppearanceConfig = async (_req: Request, res: Response) => {
const error = errorProFeature('get appearance config');
rejectRequestFromMeetError(res, error);
};

View File

@ -0,0 +1,42 @@
import { SecurityConfig } from '@typings-ce';
import { Request, Response } from 'express';
import { container } from '../../config/index.js';
import { handleError } from '../../models/error.model.js';
import { LoggerService, MeetStorageService } from '../../services/index.js';
export const updateSecurityConfig = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const storageService = container.get(MeetStorageService);
logger.verbose(`Updating security config: ${JSON.stringify(req.body)}`);
const securityConfig = req.body as SecurityConfig;
try {
const globalConfig = await storageService.getGlobalConfig();
const currentAuth = globalConfig.securityConfig.authentication;
const newAuth = securityConfig.authentication;
currentAuth.authMethod = newAuth.authMethod;
currentAuth.authModeToAccessRoom = newAuth.authModeToAccessRoom;
await storageService.saveGlobalConfig(globalConfig);
return res.status(200).json({ message: 'Security config updated successfully' });
} catch (error) {
handleError(res, error, 'updating security config');
}
};
export const getSecurityConfig = async (_req: Request, res: Response) => {
const logger = container.get(LoggerService);
const storageService = container.get(MeetStorageService);
logger.verbose('Getting security config');
try {
const config = await storageService.getGlobalConfig();
const securityConfig = config.securityConfig;
return res.status(200).json(securityConfig);
} catch (error) {
handleError(res, error, 'getting security config');
}
};

View File

@ -0,0 +1,59 @@
import { WebhookConfig } from '@typings-ce';
import { Request, Response } from 'express';
import { container } from '../../config/index.js';
import { handleError } from '../../models/error.model.js';
import { LoggerService, MeetStorageService, OpenViduWebhookService } from '../../services/index.js';
export const updateWebhookConfig = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const storageService = container.get(MeetStorageService);
logger.info(`Updating webhooks config: ${JSON.stringify(req.body)}`);
const webhookConfig = req.body as WebhookConfig;
try {
const globalConfig = await storageService.getGlobalConfig();
// TODO: Validate the URL if webhooks are enabled by making a test request
globalConfig.webhooksConfig = {
enabled: webhookConfig.enabled,
url: webhookConfig.url === undefined ? globalConfig.webhooksConfig.url : webhookConfig.url
};
await storageService.saveGlobalConfig(globalConfig);
return res.status(200).json({ message: 'Webhooks config updated successfully' });
} catch (error) {
handleError(res, error, 'updating webhooks config');
}
};
export const getWebhookConfig = async (_req: Request, res: Response) => {
const logger = container.get(LoggerService);
const storageService = container.get(MeetStorageService);
logger.verbose('Getting webhooks config');
try {
const config = await storageService.getGlobalConfig();
return res.status(200).json(config.webhooksConfig);
} catch (error) {
handleError(res, error, 'getting webhooks config');
}
};
export const testWebhook = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const webhookService = container.get(OpenViduWebhookService);
logger.verbose(`Testing webhook URL: ${req.body.url}`);
const url = req.body.url;
try {
await webhookService.testWebhookUrl(url);
logger.info(`Webhook URL '${url}' is valid`);
return res.status(200).json({ message: 'Webhook URL is valid' });
} catch (error) {
handleError(res, error, 'testing webhook URL');
}
};

View File

@ -1,12 +0,0 @@
import { Request, Response } from 'express';
import { errorProFeature, rejectRequestFromMeetError } from '../../models/error.model.js';
export const updateAppearancePreferences = async (_req: Request, res: Response) => {
const error = errorProFeature('update appearance preferences');
rejectRequestFromMeetError(res, error);
};
export const getAppearancePreferences = async (_req: Request, res: Response) => {
const error = errorProFeature('get appearance preferences');
rejectRequestFromMeetError(res, error);
};

View File

@ -1,42 +0,0 @@
import { SecurityPreferences } from '@typings-ce';
import { Request, Response } from 'express';
import { container } from '../../config/index.js';
import { handleError } from '../../models/error.model.js';
import { LoggerService, MeetStorageService } from '../../services/index.js';
export const updateSecurityPreferences = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const globalPrefService = container.get(MeetStorageService);
logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`);
const securityPreferences = req.body as SecurityPreferences;
try {
const globalPreferences = await globalPrefService.getGlobalPreferences();
const currentAuth = globalPreferences.securityPreferences.authentication;
const newAuth = securityPreferences.authentication;
currentAuth.authMethod = newAuth.authMethod;
currentAuth.authModeToAccessRoom = newAuth.authModeToAccessRoom;
await globalPrefService.saveGlobalPreferences(globalPreferences);
return res.status(200).json({ message: 'Security preferences updated successfully' });
} catch (error) {
handleError(res, error, 'updating security preferences');
}
};
export const getSecurityPreferences = async (_req: Request, res: Response) => {
const logger = container.get(LoggerService);
const preferenceService = container.get(MeetStorageService);
logger.verbose('Getting security preferences');
try {
const preferences = await preferenceService.getGlobalPreferences();
const securityPreferences = preferences.securityPreferences;
return res.status(200).json(securityPreferences);
} catch (error) {
handleError(res, error, 'getting security preferences');
}
};

View File

@ -1,62 +0,0 @@
import { WebhookPreferences } from '@typings-ce';
import { Request, Response } from 'express';
import { container } from '../../config/index.js';
import { handleError } from '../../models/error.model.js';
import { LoggerService, MeetStorageService, OpenViduWebhookService } from '../../services/index.js';
export const updateWebhookPreferences = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const globalPrefService = container.get(MeetStorageService);
logger.info(`Updating webhooks preferences: ${JSON.stringify(req.body)}`);
const webhookPreferences = req.body as WebhookPreferences;
try {
const globalPreferences = await globalPrefService.getGlobalPreferences();
// TODO: Validate the URL if webhooks are enabled by making a test request
globalPreferences.webhooksPreferences = {
enabled: webhookPreferences.enabled,
url:
webhookPreferences.url === undefined
? globalPreferences.webhooksPreferences.url
: webhookPreferences.url
};
await globalPrefService.saveGlobalPreferences(globalPreferences);
return res.status(200).json({ message: 'Webhooks preferences updated successfully' });
} catch (error) {
handleError(res, error, 'updating webhooks preferences');
}
};
export const getWebhookPreferences = async (_req: Request, res: Response) => {
const logger = container.get(LoggerService);
const preferenceService = container.get(MeetStorageService);
logger.verbose('Getting webhooks preferences');
try {
const preferences = await preferenceService.getGlobalPreferences();
return res.status(200).json(preferences.webhooksPreferences);
} catch (error) {
handleError(res, error, 'getting webhooks preferences');
}
};
export const testWebhook = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const webhookService = container.get(OpenViduWebhookService);
logger.verbose(`Testing webhook URL: ${req.body.url}`);
const url = req.body.url;
try {
await webhookService.testWebhookUrl(url);
logger.info(`Webhook URL '${url}' is valid`);
return res.status(200).json({ message: 'Webhook URL is valid' });
} catch (error) {
handleError(res, error, 'testing webhook URL');
}
};

View File

@ -6,6 +6,6 @@ export * from './participant.controller.js';
export * from './recording.controller.js'; export * from './recording.controller.js';
export * from './livekit-webhook.controller.js'; export * from './livekit-webhook.controller.js';
export * from './global-preferences/appearance-preferences.controller.js'; export * from './global-config/appearance-config.controller.js';
export * from './global-preferences/webhook-preferences.controller.js'; export * from './global-config/webhook-config.controller.js';
export * from './global-preferences/security-preferences.controller.js'; export * from './global-config/security-config.controller.js';

View File

@ -50,7 +50,7 @@ export const {
LIVEKIT_API_KEY = 'devkey', LIVEKIT_API_KEY = 'devkey',
LIVEKIT_API_SECRET = 'secret', LIVEKIT_API_SECRET = 'secret',
MEET_PREFERENCES_STORAGE_MODE = 's3', // Options: 's3', 'abs' MEET_BLOB_STORAGE_MODE = 's3', // Options: 's3', 'abs'
// S3 configuration // S3 configuration
MEET_S3_BUCKET = 'openvidu-appdata', MEET_S3_BUCKET = 'openvidu-appdata',
@ -108,12 +108,12 @@ export const logEnvVars = () => {
console.log('SERVICE NAME ID: ', text(MEET_NAME_ID)); console.log('SERVICE NAME ID: ', text(MEET_NAME_ID));
console.log('CORS ORIGIN:', text(SERVER_CORS_ORIGIN)); console.log('CORS ORIGIN:', text(SERVER_CORS_ORIGIN));
console.log('MEET LOG LEVEL: ', text(MEET_LOG_LEVEL)); console.log('MEET LOG LEVEL: ', text(MEET_LOG_LEVEL));
console.log('MEET PREFERENCES STORAGE:', text(MEET_PREFERENCES_STORAGE_MODE)); console.log('MEET BLOB STORAGE MODE:', text(MEET_BLOB_STORAGE_MODE));
console.log('MEET INITIAL ADMIN USER: ', credential('****' + MEET_INITIAL_ADMIN_USER.slice(-3))); console.log('MEET INITIAL ADMIN USER: ', credential('****' + MEET_INITIAL_ADMIN_USER.slice(-3)));
console.log('MEET INITIAL ADMIN PASSWORD: ', credential('****' + MEET_INITIAL_ADMIN_PASSWORD.slice(-3))); console.log('MEET INITIAL ADMIN PASSWORD: ', credential('****' + MEET_INITIAL_ADMIN_PASSWORD.slice(-3)));
if (!MEET_INITIAL_API_KEY) { if (!MEET_INITIAL_API_KEY) {
console.log(chalk.red('MEET INITIAL_API_KEY: none')); console.log(chalk.red('MEET INITIAL API KEY: none'));
} else { } else {
console.log('MEET INITIAL API KEY: ', credential('****' + MEET_INITIAL_API_KEY.slice(-3))); console.log('MEET INITIAL API KEY: ', credential('****' + MEET_INITIAL_API_KEY.slice(-3)));
} }
@ -133,7 +133,7 @@ export const logEnvVars = () => {
console.log('LIVEKIT API KEY: ', credential('****' + LIVEKIT_API_KEY.slice(-3))); console.log('LIVEKIT API KEY: ', credential('****' + LIVEKIT_API_KEY.slice(-3)));
console.log('---------------------------------------------------------'); console.log('---------------------------------------------------------');
if (MEET_PREFERENCES_STORAGE_MODE === 's3') { if (MEET_BLOB_STORAGE_MODE === 's3') {
console.log('S3 Configuration'); console.log('S3 Configuration');
console.log('---------------------------------------------------------'); console.log('---------------------------------------------------------');
console.log('MEET S3 BUCKET:', text(MEET_S3_BUCKET)); console.log('MEET S3 BUCKET:', text(MEET_S3_BUCKET));
@ -143,7 +143,7 @@ export const logEnvVars = () => {
console.log('MEET AWS REGION:', text(MEET_AWS_REGION)); console.log('MEET AWS REGION:', text(MEET_AWS_REGION));
console.log('MEET S3 WITH PATH STYLE ACCESS:', text(MEET_S3_WITH_PATH_STYLE_ACCESS)); console.log('MEET S3 WITH PATH STYLE ACCESS:', text(MEET_S3_WITH_PATH_STYLE_ACCESS));
console.log('---------------------------------------------------------'); console.log('---------------------------------------------------------');
} else if (MEET_PREFERENCES_STORAGE_MODE === 'abs') { } else if (MEET_BLOB_STORAGE_MODE === 'abs') {
console.log('Azure Blob Storage Configuration'); console.log('Azure Blob Storage Configuration');
console.log('---------------------------------------------------------'); console.log('---------------------------------------------------------');
console.log('MEET AZURE ACCOUNT NAME:', text(MEET_AZURE_ACCOUNT_NAME)); console.log('MEET AZURE ACCOUNT NAME:', text(MEET_AZURE_ACCOUNT_NAME));

View File

@ -9,4 +9,4 @@ export * from './request-validators/user-validator.middleware.js';
export * from './request-validators/room-validator.middleware.js'; export * from './request-validators/room-validator.middleware.js';
export * from './request-validators/participant-validator.middleware.js'; export * from './request-validators/participant-validator.middleware.js';
export * from './request-validators/recording-validator.middleware.js'; export * from './request-validators/recording-validator.middleware.js';
export * from './request-validators/preferences-validator.middleware.js'; export * from './request-validators/config-validator.middleware.js';

View File

@ -13,7 +13,7 @@ import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middlewa
* - Otherwise, allow anonymous access. * - Otherwise, allow anonymous access.
*/ */
export const configureParticipantTokenAuth = async (req: Request, res: Response, next: NextFunction) => { export const configureParticipantTokenAuth = async (req: Request, res: Response, next: NextFunction) => {
const globalPrefService = container.get(MeetStorageService); const storageService = container.get(MeetStorageService);
const roomService = container.get(RoomService); const roomService = container.get(RoomService);
let role: ParticipantRole; let role: ParticipantRole;
@ -28,10 +28,10 @@ export const configureParticipantTokenAuth = async (req: Request, res: Response,
let authModeToAccessRoom: AuthMode; let authModeToAccessRoom: AuthMode;
try { try {
const { securityPreferences } = await globalPrefService.getGlobalPreferences(); const { securityConfig } = await storageService.getGlobalConfig();
authModeToAccessRoom = securityPreferences.authentication.authModeToAccessRoom; authModeToAccessRoom = securityConfig.authentication.authModeToAccessRoom;
} catch (error) { } catch (error) {
return handleError(res, error, 'checking authentication preferences'); return handleError(res, error, 'checking authentication config');
} }
const authValidators = []; const authValidators = [];

View File

@ -1,17 +1,17 @@
import { import {
AuthenticationPreferences, AuthenticationConfig,
AuthMode, AuthMode,
AuthType, AuthType,
SecurityPreferences, SecurityConfig,
SingleUserAuth, SingleUserAuth,
ValidAuthMethod, ValidAuthMethod,
WebhookPreferences WebhookConfig
} from '@typings-ce'; } from '@typings-ce';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import { z } from 'zod'; import { z } from 'zod';
import { rejectUnprocessableRequest } from '../../models/error.model.js'; import { rejectUnprocessableRequest } from '../../models/error.model.js';
const WebhookPreferencesSchema: z.ZodType<WebhookPreferences> = z const WebhookConfigSchema: z.ZodType<WebhookConfig> = z
.object({ .object({
enabled: z.boolean(), enabled: z.boolean(),
url: z url: z
@ -48,17 +48,17 @@ const SingleUserAuthSchema: z.ZodType<SingleUserAuth> = z.object({
const ValidAuthMethodSchema: z.ZodType<ValidAuthMethod> = SingleUserAuthSchema; const ValidAuthMethodSchema: z.ZodType<ValidAuthMethod> = SingleUserAuthSchema;
const AuthenticationPreferencesSchema: z.ZodType<AuthenticationPreferences> = z.object({ const AuthenticationConfigSchema: z.ZodType<AuthenticationConfig> = z.object({
authMethod: ValidAuthMethodSchema, authMethod: ValidAuthMethodSchema,
authModeToAccessRoom: AuthModeSchema authModeToAccessRoom: AuthModeSchema
}); });
const SecurityPreferencesSchema: z.ZodType<SecurityPreferences> = z.object({ const SecurityConfigSchema: z.ZodType<SecurityConfig> = z.object({
authentication: AuthenticationPreferencesSchema authentication: AuthenticationConfigSchema
}); });
export const validateWebhookPreferences = (req: Request, res: Response, next: NextFunction) => { export const validateWebhookConfig = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = WebhookPreferencesSchema.safeParse(req.body); const { success, error, data } = WebhookConfigSchema.safeParse(req.body);
if (!success) { if (!success) {
return rejectUnprocessableRequest(res, error); return rejectUnprocessableRequest(res, error);
@ -79,8 +79,8 @@ export const withValidWebhookTestRequest = (req: Request, res: Response, next: N
next(); next();
}; };
export const validateSecurityPreferences = (req: Request, res: Response, next: NextFunction) => { export const validateSecurityConfig = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = SecurityPreferencesSchema.safeParse(req.body); const { success, error, data } = SecurityConfigSchema.safeParse(req.body);
if (!success) { if (!success) {
return rejectUnprocessableRequest(res, error); return rejectUnprocessableRequest(res, error);

View File

@ -77,10 +77,10 @@ export const configureRecordingTokenAuth = async (req: Request, res: Response, n
let authModeToAccessRoom: AuthMode; let authModeToAccessRoom: AuthMode;
try { try {
const { securityPreferences } = await storageService.getGlobalPreferences(); const { securityConfig } = await storageService.getGlobalConfig();
authModeToAccessRoom = securityPreferences.authentication.authModeToAccessRoom; authModeToAccessRoom = securityConfig.authentication.authModeToAccessRoom;
} catch (error) { } catch (error) {
return handleError(res, error, 'checking authentication preferences'); return handleError(res, error, 'checking authentication config');
} }
const authValidators = []; const authValidators = [];

View File

@ -3,7 +3,7 @@ export const enum RedisKeyPrefix {
} }
export const enum RedisKeyName { export const enum RedisKeyName {
GLOBAL_PREFERENCES = `${RedisKeyPrefix.BASE}global_preferences`, GLOBAL_CONFIG = `${RedisKeyPrefix.BASE}global_config`,
ROOM = `${RedisKeyPrefix.BASE}room:`, ROOM = `${RedisKeyPrefix.BASE}room:`,
RECORDING = `${RedisKeyPrefix.BASE}recording:`, RECORDING = `${RedisKeyPrefix.BASE}recording:`,
RECORDING_SECRETS = `${RedisKeyPrefix.BASE}recording_secrets:`, RECORDING_SECRETS = `${RedisKeyPrefix.BASE}recording_secrets:`,

View File

@ -0,0 +1,48 @@
import { UserRole } from '@typings-ce';
import bodyParser from 'body-parser';
import { Router } from 'express';
import * as appearanceConfigCtrl from '../controllers/global-config/appearance-config.controller.js';
import * as securityConfigCtrl from '../controllers/global-config/security-config.controller.js';
import * as webhookConfigCtrl from '../controllers/global-config/webhook-config.controller.js';
import {
tokenAndRoleValidator,
validateSecurityConfig,
validateWebhookConfig,
withAuth,
withValidWebhookTestRequest
} from '../middlewares/index.js';
export const configRouter = Router();
configRouter.use(bodyParser.urlencoded({ extended: true }));
configRouter.use(bodyParser.json());
// Webhook config
configRouter.put(
'/webhooks',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
validateWebhookConfig,
webhookConfigCtrl.updateWebhookConfig
);
configRouter.get('/webhooks', withAuth(tokenAndRoleValidator(UserRole.ADMIN)), webhookConfigCtrl.getWebhookConfig);
configRouter.post('/webhooks/test', withValidWebhookTestRequest, webhookConfigCtrl.testWebhook);
// Security config
configRouter.put(
'/security',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
validateSecurityConfig,
securityConfigCtrl.updateSecurityConfig
);
configRouter.get('/security', securityConfigCtrl.getSecurityConfig);
// Appearance config
configRouter.put(
'/appearance',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
appearanceConfigCtrl.updateAppearanceConfig
);
configRouter.get(
'/appearance',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
appearanceConfigCtrl.getAppearanceConfig
);

View File

@ -1,52 +0,0 @@
import { UserRole } from '@typings-ce';
import bodyParser from 'body-parser';
import { Router } from 'express';
import * as appearancePrefCtrl from '../controllers/global-preferences/appearance-preferences.controller.js';
import * as securityPrefCtrl from '../controllers/global-preferences/security-preferences.controller.js';
import * as webhookPrefCtrl from '../controllers/global-preferences/webhook-preferences.controller.js';
import {
tokenAndRoleValidator,
validateSecurityPreferences,
validateWebhookPreferences,
withAuth,
withValidWebhookTestRequest
} from '../middlewares/index.js';
export const preferencesRouter = Router();
preferencesRouter.use(bodyParser.urlencoded({ extended: true }));
preferencesRouter.use(bodyParser.json());
// Webhook preferences
preferencesRouter.put(
'/webhooks',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
validateWebhookPreferences,
webhookPrefCtrl.updateWebhookPreferences
);
preferencesRouter.get(
'/webhooks',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
webhookPrefCtrl.getWebhookPreferences
);
preferencesRouter.post('/webhooks/test', withValidWebhookTestRequest, webhookPrefCtrl.testWebhook);
// Security preferences
preferencesRouter.put(
'/security',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
validateSecurityPreferences,
securityPrefCtrl.updateSecurityPreferences
);
preferencesRouter.get('/security', securityPrefCtrl.getSecurityPreferences);
// Appearance preferences
preferencesRouter.put(
'/appearance',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
appearancePrefCtrl.updateAppearancePreferences
);
preferencesRouter.get(
'/appearance',
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
appearancePrefCtrl.getAppearancePreferences
);

View File

@ -1,4 +1,4 @@
export * from './global-preferences.routes.js'; export * from './global-config.routes.js';
export * from './auth.routes.js'; export * from './auth.routes.js';
export * from './user.routes.js'; export * from './user.routes.js';
export * from './room.routes.js'; export * from './room.routes.js';

View File

@ -8,12 +8,12 @@ import { SERVER_CORS_ORIGIN, SERVER_PORT, logEnvVars } from './environment.js';
import { jsonSyntaxErrorHandler } from './middlewares/index.js'; import { jsonSyntaxErrorHandler } from './middlewares/index.js';
import { import {
authRouter, authRouter,
configRouter,
internalMeetingRouter, internalMeetingRouter,
internalParticipantRouter, internalParticipantRouter,
internalRecordingRouter, internalRecordingRouter,
internalRoomRouter, internalRoomRouter,
livekitWebhookRouter, livekitWebhookRouter,
preferencesRouter,
recordingRouter, recordingRouter,
roomRouter, roomRouter,
userRouter userRouter
@ -62,7 +62,7 @@ const createApp = () => {
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter); app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantRouter); app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantRouter);
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter); app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter);
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences`, preferencesRouter); app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`, configRouter);
app.use('/health', (_req: Request, res: Response) => res.status(200).send('OK')); app.use('/health', (_req: Request, res: Response) => res.status(200).send('OK'));

View File

@ -5,7 +5,7 @@ import {
MeetWebhookEvent, MeetWebhookEvent,
MeetWebhookEventType, MeetWebhookEventType,
MeetWebhookPayload, MeetWebhookPayload,
WebhookPreferences WebhookConfig
} from '@typings-ce'; } from '@typings-ce';
import crypto from 'crypto'; import crypto from 'crypto';
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
@ -20,7 +20,7 @@ import { AuthService, LoggerService, MeetStorageService } from './index.js';
export class OpenViduWebhookService { export class OpenViduWebhookService {
constructor( constructor(
@inject(LoggerService) protected logger: LoggerService, @inject(LoggerService) protected logger: LoggerService,
@inject(MeetStorageService) protected globalPrefService: MeetStorageService, @inject(MeetStorageService) protected storageService: MeetStorageService,
@inject(AuthService) protected authService: AuthService @inject(AuthService) protected authService: AuthService
) {} ) {}
@ -153,9 +153,9 @@ export class OpenViduWebhookService {
} }
protected async sendWebhookEvent(event: MeetWebhookEventType, payload: MeetWebhookPayload) { protected async sendWebhookEvent(event: MeetWebhookEventType, payload: MeetWebhookPayload) {
const webhookPreferences = await this.getWebhookPreferences(); const webhookConfig = await this.getWebhookConfig();
if (!webhookPreferences.enabled) return; if (!webhookConfig.enabled) return;
const creationDate = Date.now(); const creationDate = Date.now();
const data: MeetWebhookEvent = { const data: MeetWebhookEvent = {
@ -169,7 +169,7 @@ export class OpenViduWebhookService {
try { try {
const signature = await this.generateWebhookSignature(creationDate, data); const signature = await this.generateWebhookSignature(creationDate, data);
await this.fetchWithRetry(webhookPreferences.url!, { await this.fetchWithRetry(webhookConfig.url!, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -179,7 +179,7 @@ export class OpenViduWebhookService {
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
} catch (error) { } catch (error) {
this.logger.error(`Error sending webhook event ${data.event} to '${webhookPreferences.url}':`, error); this.logger.error(`Error sending webhook event ${data.event} to '${webhookConfig.url}':`, error);
throw error; throw error;
} }
} }
@ -309,12 +309,12 @@ export class OpenViduWebhookService {
} }
} }
protected async getWebhookPreferences(): Promise<WebhookPreferences> { protected async getWebhookConfig(): Promise<WebhookConfig> {
try { try {
const { webhooksPreferences } = await this.globalPrefService.getGlobalPreferences(); const { webhooksConfig } = await this.storageService.getGlobalConfig();
return webhooksPreferences; return webhooksConfig;
} catch (error) { } catch (error) {
this.logger.error('Error getting webhook preferences:', error); this.logger.error('Error getting webhook config:', error);
throw error; throw error;
} }
} }

View File

@ -3,8 +3,8 @@ import { RecordingHelper } from '../../../../helpers/recording.helper.js';
import { StorageKeyBuilder } from '../../storage.interface.js'; import { StorageKeyBuilder } from '../../storage.interface.js';
export class S3KeyBuilder implements StorageKeyBuilder { export class S3KeyBuilder implements StorageKeyBuilder {
buildGlobalPreferencesKey(): string { buildGlobalConfigKey(): string {
return `global-preferences.json`; return `global-config.json`;
} }
buildMeetRoomKey(roomId: string): string { buildMeetRoomKey(roomId: string): string {

View File

@ -1,14 +1,14 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import { container, STORAGE_TYPES } from '../../config/dependency-injector.config.js';
import { LoggerService } from '../index.js'; import { LoggerService } from '../index.js';
import { StorageKeyBuilder, StorageProvider } from './storage.interface.js'; import { StorageKeyBuilder, StorageProvider } from './storage.interface.js';
import { container, STORAGE_TYPES } from '../../config/dependency-injector.config.js';
/** /**
* Factory class responsible for creating the appropriate basic storage provider * Factory class responsible for creating the appropriate basic storage provider
* based on configuration. * based on configuration.
* *
* This factory determines which basic storage implementation to use based on the * This factory determines which basic storage implementation to use based on the
* `MEET_PREFERENCES_STORAGE_MODE` environment variable. It creates providers that * `MEET_BLOB_STORAGE_MODE` environment variable. It creates providers that
* handle only basic CRUD operations, following the Single Responsibility Principle. * handle only basic CRUD operations, following the Single Responsibility Principle.
* *
* Domain-specific logic should be handled in the MeetStorageService layer. * Domain-specific logic should be handled in the MeetStorageService layer.

View File

@ -109,9 +109,9 @@ export interface StorageProvider {
*/ */
export interface StorageKeyBuilder { export interface StorageKeyBuilder {
/** /**
* Builds the key for global preferences storage. * Builds the key for global config storage.
*/ */
buildGlobalPreferencesKey(): string; buildGlobalConfigKey(): string;
/** /**
* Builds the key for a specific room. * Builds the key for a specific room.

View File

@ -1,13 +1,4 @@
import { import { AuthMode, AuthType, GlobalConfig, MeetApiKey, MeetRecordingInfo, MeetRoom, User, UserRole } from '@typings-ce';
AuthMode,
AuthType,
GlobalPreferences,
MeetApiKey,
MeetRecordingInfo,
MeetRoom,
User,
UserRole
} from '@typings-ce';
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import ms from 'ms'; import ms from 'ms';
import { Readable } from 'stream'; import { Readable } from 'stream';
@ -35,21 +26,21 @@ import { StorageKeyBuilder, StorageProvider } from './storage.interface.js';
/** /**
* Domain-specific storage service for OpenVidu Meet. * Domain-specific storage service for OpenVidu Meet.
* *
* This service handles all domain-specific logic for rooms, recordings, and preferences, * This service handles all domain-specific logic for rooms, recordings, and global config,
* while delegating basic storage operations to the StorageProvider. * while delegating basic storage operations to the StorageProvider.
* *
* This architecture follows the Single Responsibility Principle: * This architecture follows the Single Responsibility Principle:
* - StorageProvider: Handles only basic CRUD operations * - StorageProvider: Handles only basic CRUD operations
* - MeetStorageService: Handles domain-specific business logic * - MeetStorageService: Handles domain-specific business logic
* *
* @template GPrefs - Type for global preferences, extends GlobalPreferences * @template GConfig - Type for global config, extends GlobalConfig
* @template MRoom - Type for room data, extends MeetRoom * @template MRoom - Type for room data, extends MeetRoom
* @template MRec - Type for recording data, extends MeetRecordingInfo * @template MRec - Type for recording data, extends MeetRecordingInfo
* @template MUser - Type for user data, extends User * @template MUser - Type for user data, extends User
*/ */
@injectable() @injectable()
export class MeetStorageService< export class MeetStorageService<
GPrefs extends GlobalPreferences = GlobalPreferences, GConfig extends GlobalConfig = GlobalConfig,
MRoom extends MeetRoom = MeetRoom, MRoom extends MeetRoom = MeetRoom,
MRec extends MeetRecordingInfo = MeetRecordingInfo, MRec extends MeetRecordingInfo = MeetRecordingInfo,
MUser extends User = User MUser extends User = User
@ -104,7 +95,7 @@ export class MeetStorageService<
/** /**
* Initializes the storage with default data and initial environment variables if not already initialized. * Initializes the storage with default data and initial environment variables if not already initialized.
* This includes global preferences, admin user and API key. * This includes global config, admin user and API key.
*/ */
async initializeStorage(): Promise<void> { async initializeStorage(): Promise<void> {
try { try {
@ -127,7 +118,7 @@ export class MeetStorageService<
this.logger.info('Storage not initialized or different project detected, proceeding with initialization'); this.logger.info('Storage not initialized or different project detected, proceeding with initialization');
await this.initializeGlobalPreferences(); await this.initializeGlobalConfig();
await this.initializeAdminUser(); await this.initializeAdminUser();
await this.initializeApiKey(); await this.initializeApiKey();
@ -139,32 +130,32 @@ export class MeetStorageService<
} }
// ========================================== // ==========================================
// GLOBAL PREFERENCES DOMAIN LOGIC // GLOBAL CONFIG DOMAIN LOGIC
// ========================================== // ==========================================
async getGlobalPreferences(): Promise<GPrefs> { async getGlobalConfig(): Promise<GConfig> {
const redisKey = RedisKeyName.GLOBAL_PREFERENCES; const redisKey = RedisKeyName.GLOBAL_CONFIG;
const storageKey = this.keyBuilder.buildGlobalPreferencesKey(); const storageKey = this.keyBuilder.buildGlobalConfigKey();
const preferences = await this.getFromCacheAndStorage<GPrefs>(redisKey, storageKey); const config = await this.getFromCacheAndStorage<GConfig>(redisKey, storageKey);
if (preferences) return preferences; if (config) return config;
// Build and save default preferences if not found in cache or storage // Build and save default config if not found in cache or storage
await this.initializeGlobalPreferences(); await this.initializeGlobalConfig();
return this.getDefaultPreferences(); return this.getDefaultConfig();
} }
/** /**
* Saves global preferences to the storage provider. * Saves global config to the storage provider.
* @param {GPrefs} preferences - The global preferences to save. * @param {GConfig} config - The global config to save.
* @returns {Promise<GPrefs>} The saved global preferences. * @returns {Promise<GConfig>} The saved global config.
*/ */
async saveGlobalPreferences(preferences: GPrefs): Promise<GPrefs> { async saveGlobalConfig(config: GConfig): Promise<GConfig> {
this.logger.info('Saving global preferences'); this.logger.info('Saving global config');
const redisKey = RedisKeyName.GLOBAL_PREFERENCES; const redisKey = RedisKeyName.GLOBAL_CONFIG;
const storageKey = this.keyBuilder.buildGlobalPreferencesKey(); const storageKey = this.keyBuilder.buildGlobalConfigKey();
return await this.saveCacheAndStorage<GPrefs>(redisKey, storageKey, preferences); return await this.saveCacheAndStorage<GConfig>(redisKey, storageKey, config);
} }
// ========================================== // ==========================================
@ -643,24 +634,24 @@ export class MeetStorageService<
// ========================================== // ==========================================
/** /**
* Checks if storage is already initialized by verifying that global preferences exist * Checks if storage is already initialized by verifying that global config exist
* and belong to the current project. * and belong to the current project.
* @returns {Promise<boolean>} True if storage is already initialized for this project * @returns {Promise<boolean>} True if storage is already initialized for this project
*/ */
protected async checkStorageInitialization(): Promise<boolean> { protected async checkStorageInitialization(): Promise<boolean> {
try { try {
const redisKey = RedisKeyName.GLOBAL_PREFERENCES; const redisKey = RedisKeyName.GLOBAL_CONFIG;
const storageKey = this.keyBuilder.buildGlobalPreferencesKey(); const storageKey = this.keyBuilder.buildGlobalConfigKey();
const existing = await this.getFromCacheAndStorage<GPrefs>(redisKey, storageKey); const existing = await this.getFromCacheAndStorage<GConfig>(redisKey, storageKey);
if (!existing) { if (!existing) {
this.logger.verbose('No global preferences found, storage needs initialization'); this.logger.verbose('No global config found, storage needs initialization');
return false; return false;
} }
// Check if it's from the same project // Check if it's from the same project
const existingProjectId = (existing as GlobalPreferences)?.projectId; const existingProjectId = (existing as GlobalConfig)?.projectId;
const currentProjectId = MEET_NAME_ID; const currentProjectId = MEET_NAME_ID;
if (existingProjectId !== currentProjectId) { if (existingProjectId !== currentProjectId) {
@ -679,26 +670,26 @@ export class MeetStorageService<
} }
/** /**
* Initializes default global preferences if not already present. * Initializes default global config if not already present.
*/ */
protected async initializeGlobalPreferences(): Promise<void> { protected async initializeGlobalConfig(): Promise<void> {
const preferences = this.getDefaultPreferences(); const config = this.getDefaultConfig();
await this.saveGlobalPreferences(preferences); await this.saveGlobalConfig(config);
this.logger.info('Global preferences initialized with default values'); this.logger.info('Global config initialized with default values');
} }
/** /**
* Returns the default global preferences. * Returns the default global config.
* @returns {GPrefs} * @returns {GConfig}
*/ */
protected getDefaultPreferences(): GPrefs { protected getDefaultConfig(): GConfig {
return { return {
projectId: MEET_NAME_ID, projectId: MEET_NAME_ID,
webhooksPreferences: { webhooksConfig: {
enabled: MEET_INITIAL_WEBHOOK_ENABLED === 'true' && MEET_INITIAL_API_KEY, enabled: MEET_INITIAL_WEBHOOK_ENABLED === 'true' && MEET_INITIAL_API_KEY,
url: MEET_INITIAL_WEBHOOK_URL url: MEET_INITIAL_WEBHOOK_URL
}, },
securityPreferences: { securityConfig: {
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -706,7 +697,7 @@ export class MeetStorageService<
authModeToAccessRoom: AuthMode.NONE authModeToAccessRoom: AuthMode.NONE
} }
} }
} as GPrefs; } as GConfig;
} }
/** /**

View File

@ -26,7 +26,7 @@ import {
MeetRoomDeletionPolicyWithRecordings, MeetRoomDeletionPolicyWithRecordings,
MeetRoomOptions, MeetRoomOptions,
ParticipantRole, ParticipantRole,
WebhookPreferences WebhookConfig
} from '../../src/typings/ce/index.js'; } from '../../src/typings/ce/index.js';
const CREDENTIALS = { const CREDENTIALS = {
@ -77,47 +77,47 @@ export const getApiKeys = async () => {
return response; return response;
}; };
export const getAppearancePreferences = async () => { export const getAppearanceConfig = async () => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/appearance`) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/appearance`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(); .send();
return response; return response;
}; };
export const updateAppearancePreferences = async (preferences: any) => { export const updateAppearanceConfig = async (config: any) => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/appearance`) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/appearance`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(preferences); .send(config);
return response; return response;
}; };
export const getWebbhookPreferences = async () => { export const getWebbhookConfig = async () => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/webhooks`) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(); .send();
return response; return response;
}; };
export const updateWebbhookPreferences = async (preferences: WebhookPreferences) => { export const updateWebbhookConfig = async (config: WebhookConfig) => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/webhooks`) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(preferences); .send(config);
return response; return response;
}; };
@ -126,35 +126,35 @@ export const testWebhookUrl = async (url: string) => {
checkAppIsRunning(); checkAppIsRunning();
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/webhooks/test`) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/webhooks/test`)
.send({ url }); .send({ url });
return response; return response;
}; };
export const getSecurityPreferences = async () => { export const getSecurityConfig = async () => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/security`) .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(); .send();
return response; return response;
}; };
export const updateSecurityPreferences = async (preferences: any) => { export const updateSecurityConfig = async (config: any) => {
checkAppIsRunning(); checkAppIsRunning();
const adminCookie = await loginUser(); const adminCookie = await loginUser();
const response = await request(app) const response = await request(app)
.put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/security`) .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/security`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(preferences); .send(config);
return response; return response;
}; };
export const changeSecurityPreferences = async (authMode: AuthMode) => { export const changeSecurityConfig = async (authMode: AuthMode) => {
const response = await updateSecurityPreferences({ const response = await updateSecurityConfig({
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -376,7 +376,7 @@ export const generateParticipantToken = async (participantOptions: any, cookie?:
checkAppIsRunning(); checkAppIsRunning();
// Disable authentication to generate the token // Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
// Generate the participant token // Generate the participant token
const response = await request(app) const response = await request(app)
@ -418,7 +418,7 @@ export const refreshParticipantToken = async (participantOptions: any, cookie: s
checkAppIsRunning(); checkAppIsRunning();
// Disable authentication to generate the token // Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token/refresh`) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token/refresh`)
@ -580,7 +580,7 @@ export const generateRecordingToken = async (roomId: string, secret: string) =>
checkAppIsRunning(); checkAppIsRunning();
// Disable authentication to generate the token // Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms/${roomId}/recording-token`) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms/${roomId}/recording-token`)

View File

@ -1,25 +1,21 @@
import { beforeAll, describe, expect, it } from '@jest/globals'; import { beforeAll, describe, expect, it } from '@jest/globals';
import { import { getAppearanceConfig, startTestServer, updateAppearanceConfig } from '../../../helpers/request-helpers.js';
getAppearancePreferences,
startTestServer,
updateAppearancePreferences
} from '../../../helpers/request-helpers.js';
describe('Appearance API Tests', () => { describe('Appearance API Tests', () => {
beforeAll(() => { beforeAll(() => {
startTestServer(); startTestServer();
}); });
describe('Get Appearance Preferences', () => { describe('Get Appearance Config', () => {
it('should return 402 status code as it is a PRO feature', async () => { it('should return 402 status code as it is a PRO feature', async () => {
const response = await getAppearancePreferences(); const response = await getAppearanceConfig();
expect(response.status).toBe(402); expect(response.status).toBe(402);
}); });
}); });
describe('Update Appearance Preferences', () => { describe('Update Appearance Config', () => {
it('should return 402 status code as it is a PRO feature', async () => { it('should return 402 status code as it is a PRO feature', async () => {
const response = await updateAppearancePreferences({}); const response = await updateAppearanceConfig({});
expect(response.status).toBe(402); expect(response.status).toBe(402);
}); });
}); });

View File

@ -3,13 +3,9 @@ import { container } from '../../../../src/config/dependency-injector.config.js'
import { MeetStorageService } from '../../../../src/services/index.js'; import { MeetStorageService } from '../../../../src/services/index.js';
import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js'; import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js';
import { expectValidationError } from '../../../helpers/assertion-helpers.js'; import { expectValidationError } from '../../../helpers/assertion-helpers.js';
import { import { getSecurityConfig, startTestServer, updateSecurityConfig } from '../../../helpers/request-helpers.js';
getSecurityPreferences,
startTestServer,
updateSecurityPreferences
} from '../../../helpers/request-helpers.js';
const defaultPreferences = { const defaultConfig = {
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -18,23 +14,23 @@ const defaultPreferences = {
} }
}; };
const restoreDefaultGlobalPreferences = async () => { const restoreDefaultGlobalConfig = async () => {
const defaultPref = await container.get(MeetStorageService)['getDefaultPreferences'](); const defaultGlobalConfig = await container.get(MeetStorageService)['getDefaultConfig']();
await container.get(MeetStorageService).saveGlobalPreferences(defaultPref); await container.get(MeetStorageService).saveGlobalConfig(defaultGlobalConfig);
}; };
describe('Security Preferences API Tests', () => { describe('Security Config API Tests', () => {
beforeAll(async () => { beforeAll(async () => {
startTestServer(); startTestServer();
}); });
afterEach(async () => { afterEach(async () => {
await restoreDefaultGlobalPreferences(); await restoreDefaultGlobalConfig();
}); });
describe('Update security preferences', () => { describe('Update security config', () => {
it('should update security preferences with valid complete data', async () => { it('should update security config with valid complete data', async () => {
const validPreferences = { const validConfig = {
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -42,20 +38,20 @@ describe('Security Preferences API Tests', () => {
authModeToAccessRoom: AuthMode.ALL_USERS authModeToAccessRoom: AuthMode.ALL_USERS
} }
}; };
let response = await updateSecurityPreferences(validPreferences); let response = await updateSecurityConfig(validConfig);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.message).toBe('Security preferences updated successfully'); expect(response.body.message).toBe('Security config updated successfully');
response = await getSecurityPreferences(); response = await getSecurityConfig();
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual(validPreferences); expect(response.body).toEqual(validConfig);
}); });
}); });
describe('Update security preferences validation', () => { describe('Update security config validation', () => {
it('should reject when authModeToAccessRoom is not a valid enum value', async () => { it('should reject when authModeToAccessRoom is not a valid enum value', async () => {
const response = await updateSecurityPreferences({ const response = await updateSecurityConfig({
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -72,7 +68,7 @@ describe('Security Preferences API Tests', () => {
}); });
it('should reject when authType is not a valid enum value', async () => { it('should reject when authType is not a valid enum value', async () => {
const response = await updateSecurityPreferences({ const response = await updateSecurityConfig({
authentication: { authentication: {
authMethod: { authMethod: {
type: 'invalid' type: 'invalid'
@ -89,14 +85,14 @@ describe('Security Preferences API Tests', () => {
}); });
it('should reject when authModeToAccessRoom or authMethod are not provided', async () => { it('should reject when authModeToAccessRoom or authMethod are not provided', async () => {
let response = await updateSecurityPreferences({ let response = await updateSecurityConfig({
authentication: { authentication: {
authMode: AuthMode.NONE authMode: AuthMode.NONE
} }
}); });
expectValidationError(response, 'authentication.authMethod', 'Required'); expectValidationError(response, 'authentication.authMethod', 'Required');
response = await updateSecurityPreferences({ response = await updateSecurityConfig({
authentication: { authentication: {
method: { method: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -107,7 +103,7 @@ describe('Security Preferences API Tests', () => {
}); });
it('should reject when authentication is not an object', async () => { it('should reject when authentication is not an object', async () => {
const response = await updateSecurityPreferences({ const response = await updateSecurityConfig({
authentication: 'invalid' authentication: 'invalid'
}); });
@ -115,12 +111,12 @@ describe('Security Preferences API Tests', () => {
}); });
}); });
describe('Get security preferences', () => { describe('Get security config', () => {
it('should return security preferences when authenticated as admin', async () => { it('should return security config when authenticated as admin', async () => {
const response = await getSecurityPreferences(); const response = await getSecurityConfig();
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual(defaultPreferences); expect(response.body).toEqual(defaultConfig);
}); });
}); });
}); });

View File

@ -5,78 +5,78 @@ import { MEET_INITIAL_WEBHOOK_ENABLED, MEET_INITIAL_WEBHOOK_URL } from '../../..
import { MeetStorageService } from '../../../../src/services/index.js'; import { MeetStorageService } from '../../../../src/services/index.js';
import { expectValidationError } from '../../../helpers/assertion-helpers.js'; import { expectValidationError } from '../../../helpers/assertion-helpers.js';
import { import {
getWebbhookPreferences, getWebbhookConfig,
startTestServer, startTestServer,
testWebhookUrl, testWebhookUrl,
updateWebbhookPreferences updateWebbhookConfig
} from '../../../helpers/request-helpers.js'; } from '../../../helpers/request-helpers.js';
import { startWebhookServer, stopWebhookServer } from '../../../helpers/test-scenarios.js'; import { startWebhookServer, stopWebhookServer } from '../../../helpers/test-scenarios.js';
describe('Webhook Preferences API Tests', () => { describe('Webhook Config API Tests', () => {
beforeAll(() => { beforeAll(() => {
startTestServer(); startTestServer();
}); });
afterEach(async () => { afterEach(async () => {
const storageService = container.get(MeetStorageService); const storageService = container.get(MeetStorageService);
await storageService['initializeGlobalPreferences'](); await storageService['initializeGlobalConfig']();
}); });
describe('Update webhook preferences', () => { describe('Update webhook config', () => {
it('should update webhook preferences with valid data', async () => { it('should update webhook config with valid data', async () => {
const validPreferences = { const validConfig = {
enabled: true, enabled: true,
url: 'https://example.com/webhook' url: 'https://example.com/webhook'
}; };
let response = await updateWebbhookPreferences(validPreferences); let response = await updateWebbhookConfig(validConfig);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.message).toBe('Webhooks preferences updated successfully'); expect(response.body.message).toBe('Webhooks config updated successfully');
response = await getWebbhookPreferences(); response = await getWebbhookConfig();
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.enabled).toBe(true); expect(response.body.enabled).toBe(true);
expect(response.body.url).toBe(validPreferences.url); expect(response.body.url).toBe(validConfig.url);
expect(response.body).toEqual(validPreferences); expect(response.body).toEqual(validConfig);
}); });
it('should allow disabling webhooks', async () => { it('should allow disabling webhooks', async () => {
const oldWebhookPreferences = await getWebbhookPreferences(); const oldWebhookConfig = await getWebbhookConfig();
expect(oldWebhookPreferences.status).toBe(200); expect(oldWebhookConfig.status).toBe(200);
let response = await updateWebbhookPreferences({ let response = await updateWebbhookConfig({
enabled: false enabled: false
}); });
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.message).toBe('Webhooks preferences updated successfully'); expect(response.body.message).toBe('Webhooks config updated successfully');
response = await getWebbhookPreferences(); response = await getWebbhookConfig();
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.enabled).toBe(false); expect(response.body.enabled).toBe(false);
expect(response.body.url).toBe(oldWebhookPreferences.body.url); expect(response.body.url).toBe(oldWebhookConfig.body.url);
}); });
it('should update URL even when disabling webhooks', async () => { it('should update URL even when disabling webhooks', async () => {
const preference = { const config = {
enabled: false, enabled: false,
url: 'https://newurl.com/webhook' url: 'https://newurl.com/webhook'
}; };
const response = await updateWebbhookPreferences(preference); const response = await updateWebbhookConfig(config);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.message).toBe('Webhooks preferences updated successfully'); expect(response.body.message).toBe('Webhooks config updated successfully');
const preferencesResponse = await getWebbhookPreferences(); const configResponse = await getWebbhookConfig();
expect(preferencesResponse.status).toBe(200); expect(configResponse.status).toBe(200);
expect(preferencesResponse.body.enabled).toBe(preference.enabled); expect(configResponse.body.enabled).toBe(config.enabled);
expect(preferencesResponse.body.url).toBe(preference.url); expect(configResponse.body.url).toBe(config.url);
}); });
}); });
describe('Update webhook preferences validation', () => { describe('Update webhook config validation', () => {
it('should reject invalid webhook URL', async () => { it('should reject invalid webhook URL', async () => {
const response = await updateWebbhookPreferences({ const response = await updateWebbhookConfig({
enabled: true, enabled: true,
url: 'invalid-url' url: 'invalid-url'
}); });
@ -86,14 +86,14 @@ describe('Webhook Preferences API Tests', () => {
}); });
it('should reject missing URL when webhooks are enabled', async () => { it('should reject missing URL when webhooks are enabled', async () => {
const response = await updateWebbhookPreferences({ enabled: true }); const response = await updateWebbhookConfig({ enabled: true });
expect(response.status).toBe(422); expect(response.status).toBe(422);
expectValidationError(response, 'url', 'URL is required when webhooks are enabled'); expectValidationError(response, 'url', 'URL is required when webhooks are enabled');
}); });
it('should reject non-http(s) URLs', async () => { it('should reject non-http(s) URLs', async () => {
const response = await updateWebbhookPreferences({ const response = await updateWebbhookConfig({
enabled: true, enabled: true,
url: 'ftp://example.com/webhook' url: 'ftp://example.com/webhook'
}); });
@ -103,9 +103,9 @@ describe('Webhook Preferences API Tests', () => {
}); });
}); });
describe('Get webhook preferences', () => { describe('Get webhook config', () => {
it('should return webhook preferences when authenticated as admin', async () => { it('should return webhook config when authenticated as admin', async () => {
const response = await getWebbhookPreferences(); const response = await getWebbhookConfig();
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual({ expect(response.body).toEqual({

View File

@ -34,7 +34,7 @@ describe('Room API Tests', () => {
it('should retrieve custom room config', async () => { it('should retrieve custom room config', async () => {
const payload = { const payload = {
roomName: 'custom-prefs', roomName: 'custom-config',
config: { config: {
recording: { recording: {
enabled: true, enabled: true,

View File

@ -34,7 +34,7 @@ describe('Room API Tests', () => {
it('should retrieve a room with custom config', async () => { it('should retrieve a room with custom config', async () => {
const payload = { const payload = {
roomName: 'custom-prefs', roomName: 'custom-config',
config: { config: {
recording: { recording: {
enabled: true, enabled: true,
@ -50,7 +50,7 @@ describe('Room API Tests', () => {
// Retrieve the room by its ID // Retrieve the room by its ID
const response = await getRoom(roomId); const response = await getRoom(roomId);
expectSuccessRoomResponse(response, 'custom-prefs', undefined, payload.config); expectSuccessRoomResponse(response, 'custom-config', undefined, payload.config);
}); });
it('should retrieve only specified fields when using fields parameter', async () => { it('should retrieve only specified fields when using fields parameter', async () => {

View File

@ -77,7 +77,7 @@ describe('Room API Tests', () => {
expect(getResponse.body.config).toEqual(updatedConfig); expect(getResponse.body.config).toEqual(updatedConfig);
}); });
it('should allow partial preference updates', async () => { it('should allow partial config updates', async () => {
// Create a room first with all config enabled // Create a room first with all config enabled
const createdRoom = await createRoom({ const createdRoom = await createRoom({
roomName: 'partial-update', roomName: 'partial-update',
@ -91,7 +91,7 @@ describe('Room API Tests', () => {
} }
}); });
// Update only one preference // Update only one config field
const partialConfig = { const partialConfig = {
recording: { recording: {
enabled: false, enabled: false,

View File

@ -4,7 +4,7 @@ import request from 'supertest';
import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js';
import { AuthMode } from '../../../../src/typings/ce/index.js'; import { AuthMode } from '../../../../src/typings/ce/index.js';
import { import {
changeSecurityPreferences, changeSecurityConfig,
deleteAllRooms, deleteAllRooms,
disconnectFakeParticipants, disconnectFakeParticipants,
loginUser, loginUser,
@ -39,7 +39,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is speaker', async () => { it('should succeed when no authentication is required and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -50,7 +50,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is moderator', async () => { it('should succeed when no authentication is required and participant is moderator', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -61,7 +61,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator and participant is speaker', async () => { it('should succeed when authentication is required for moderator and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -72,7 +72,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -83,7 +83,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -94,7 +94,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => { it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -105,7 +105,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => { it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -116,7 +116,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -127,7 +127,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({ const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
roomId: roomData.room.roomId, roomId: roomData.room.roomId,
@ -154,7 +154,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is speaker', async () => { it('should succeed when no authentication is required and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -169,7 +169,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is moderator', async () => { it('should succeed when no authentication is required and participant is moderator', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -184,7 +184,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator and participant is speaker', async () => { it('should succeed when authentication is required for moderator and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -199,7 +199,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -214,7 +214,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -229,7 +229,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => { it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -244,7 +244,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => { it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -259,7 +259,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)
@ -274,7 +274,7 @@ describe('Participant API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${PARTICIPANTS_PATH}/token/refresh`) .post(`${PARTICIPANTS_PATH}/token/refresh`)

View File

@ -6,9 +6,9 @@ import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js';
import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js'; import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js';
import { loginUser, startTestServer } from '../../../helpers/request-helpers.js'; import { loginUser, startTestServer } from '../../../helpers/request-helpers.js';
const PREFERENCES_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences`; const CONFIG_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`;
describe('Global Preferences API Security Tests', () => { describe('Global Config API Security Tests', () => {
let app: Express; let app: Express;
let adminCookie: string; let adminCookie: string;
@ -17,55 +17,55 @@ describe('Global Preferences API Security Tests', () => {
adminCookie = await loginUser(); adminCookie = await loginUser();
}); });
describe('Update Webhook Preferences Tests', () => { describe('Update Webhook Config Tests', () => {
const webhookPreferences = { const webhookConfig = {
enabled: true, enabled: true,
url: 'https://example.com/webhook' url: 'https://example.com/webhook'
}; };
it('should fail when request includes API key', async () => { it('should fail when request includes API key', async () => {
const response = await request(app) const response = await request(app)
.put(`${PREFERENCES_PATH}/webhooks`) .put(`${CONFIG_PATH}/webhooks`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send(webhookPreferences); .send(webhookConfig);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should succeed when user is authenticated as admin', async () => { it('should succeed when user is authenticated as admin', async () => {
const response = await request(app) const response = await request(app)
.put(`${PREFERENCES_PATH}/webhooks`) .put(`${CONFIG_PATH}/webhooks`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(webhookPreferences); .send(webhookConfig);
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
it('should fail when user is not authenticated', async () => { it('should fail when user is not authenticated', async () => {
const response = await request(app).put(`${PREFERENCES_PATH}/webhooks`).send(webhookPreferences); const response = await request(app).put(`${CONFIG_PATH}/webhooks`).send(webhookConfig);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
}); });
describe('Get Webhook Preferences Tests', () => { describe('Get Webhook Config Tests', () => {
it('should fail when request includes API key', async () => { it('should fail when request includes API key', async () => {
const response = await request(app) const response = await request(app)
.get(`${PREFERENCES_PATH}/webhooks`) .get(`${CONFIG_PATH}/webhooks`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should succeed when user is authenticated as admin', async () => { it('should succeed when user is authenticated as admin', async () => {
const response = await request(app).get(`${PREFERENCES_PATH}/webhooks`).set('Cookie', adminCookie); const response = await request(app).get(`${CONFIG_PATH}/webhooks`).set('Cookie', adminCookie);
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
it('should fail when user is not authenticated', async () => { it('should fail when user is not authenticated', async () => {
const response = await request(app).get(`${PREFERENCES_PATH}/webhooks`); const response = await request(app).get(`${CONFIG_PATH}/webhooks`);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
}); });
describe('Update Security Preferences Tests', () => { describe('Update Security Config Tests', () => {
const securityPreferences = { const securityConfig = {
authentication: { authentication: {
authMethod: { authMethod: {
type: AuthType.SINGLE_USER type: AuthType.SINGLE_USER
@ -76,71 +76,68 @@ describe('Global Preferences API Security Tests', () => {
it('should fail when request includes API key', async () => { it('should fail when request includes API key', async () => {
const response = await request(app) const response = await request(app)
.put(`${PREFERENCES_PATH}/security`) .put(`${CONFIG_PATH}/security`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send(securityPreferences); .send(securityConfig);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should succeed when user is authenticated as admin', async () => { it('should succeed when user is authenticated as admin', async () => {
const response = await request(app) const response = await request(app)
.put(`${PREFERENCES_PATH}/security`) .put(`${CONFIG_PATH}/security`)
.set('Cookie', adminCookie) .set('Cookie', adminCookie)
.send(securityPreferences); .send(securityConfig);
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
it('should fail when user is not authenticated', async () => { it('should fail when user is not authenticated', async () => {
const response = await request(app).put(`${PREFERENCES_PATH}/security`).send(securityPreferences); const response = await request(app).put(`${CONFIG_PATH}/security`).send(securityConfig);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
}); });
describe('Get Security Preferences Tests', () => { describe('Get Security Config Tests', () => {
it('should succeed when user is not authenticated', async () => { it('should succeed when user is not authenticated', async () => {
const response = await request(app).get(`${PREFERENCES_PATH}/security`); const response = await request(app).get(`${CONFIG_PATH}/security`);
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
}); });
describe('Update Appearance Preferences Tests', () => { describe('Update Appearance Config Tests', () => {
it('should fail when request includes API key', async () => { it('should fail when request includes API key', async () => {
const response = await request(app) const response = await request(app)
.put(`${PREFERENCES_PATH}/appearance`) .put(`${CONFIG_PATH}/appearance`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send({}); .send({});
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should succeed when user is authenticated as admin', async () => { it('should succeed when user is authenticated as admin', async () => {
const response = await request(app) const response = await request(app).put(`${CONFIG_PATH}/appearance`).set('Cookie', adminCookie).send({});
.put(`${PREFERENCES_PATH}/appearance`)
.set('Cookie', adminCookie)
.send({});
expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case
}); });
it('should fail when user is not authenticated', async () => { it('should fail when user is not authenticated', async () => {
const response = await request(app).put(`${PREFERENCES_PATH}/appearance`).send({}); const response = await request(app).put(`${CONFIG_PATH}/appearance`).send({});
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
}); });
describe('Get Appearance Preferences Tests', () => { describe('Get Appearance Config Tests', () => {
it('should fail when request includes API key', async () => { it('should fail when request includes API key', async () => {
const response = await request(app) const response = await request(app)
.get(`${PREFERENCES_PATH}/appearance`) .get(`${CONFIG_PATH}/appearance`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should succeed when user is authenticated as admin', async () => { it('should succeed when user is authenticated as admin', async () => {
const response = await request(app).get(`${PREFERENCES_PATH}/appearance`).set('Cookie', adminCookie); const response = await request(app).get(`${CONFIG_PATH}/appearance`).set('Cookie', adminCookie);
expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case
}); });
it('should fail when user is not authenticated', async () => { it('should fail when user is not authenticated', async () => {
const response = await request(app).get(`${PREFERENCES_PATH}/appearance`); const response = await request(app).get(`${CONFIG_PATH}/appearance`);
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
}); });

View File

@ -5,7 +5,7 @@ import INTERNAL_CONFIG from '../../../../src/config/internal-config.js';
import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js';
import { AuthMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js'; import { AuthMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js';
import { import {
changeSecurityPreferences, changeSecurityConfig,
createRoom, createRoom,
deleteAllRecordings, deleteAllRecordings,
deleteAllRooms, deleteAllRooms,
@ -323,7 +323,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is speaker', async () => { it('should succeed when no authentication is required and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -332,7 +332,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when no authentication is required and participant is moderator', async () => { it('should succeed when no authentication is required and participant is moderator', async () => {
await changeSecurityPreferences(AuthMode.NONE); await changeSecurityConfig(AuthMode.NONE);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -341,7 +341,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator and participant is speaker', async () => { it('should succeed when authentication is required for moderator and participant is speaker', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -350,7 +350,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for moderator, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -360,7 +360,7 @@ describe('Room API Security Tests', () => {
}); });
it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for moderator and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY); await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -369,7 +369,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => { it('should succeed when authentication is required for all users, participant is speaker and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -379,7 +379,7 @@ describe('Room API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => { it('should fail when authentication is required for all users and participant is speaker but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -388,7 +388,7 @@ describe('Room API Security Tests', () => {
}); });
it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => { it('should succeed when authentication is required for all users, participant is moderator and authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)
@ -398,7 +398,7 @@ describe('Room API Security Tests', () => {
}); });
it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => { it('should fail when authentication is required for all users and participant is moderator but not authenticated', async () => {
await changeSecurityPreferences(AuthMode.ALL_USERS); await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app) const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`) .post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)

View File

@ -13,7 +13,7 @@ import {
endMeeting, endMeeting,
sleep, sleep,
startTestServer, startTestServer,
updateWebbhookPreferences updateWebbhookConfig
} from '../../helpers/request-helpers.js'; } from '../../helpers/request-helpers.js';
import { import {
setupSingleRoom, setupSingleRoom,
@ -41,8 +41,8 @@ describe('Webhook Integration Tests', () => {
beforeEach(async () => { beforeEach(async () => {
receivedWebhooks = []; receivedWebhooks = [];
// Enable webhooks in global preferences // Enable webhooks in global config
await updateWebbhookPreferences({ await updateWebbhookConfig({
enabled: true, enabled: true,
url: `http://localhost:5080/webhook` url: `http://localhost:5080/webhook`
}); });
@ -50,14 +50,14 @@ describe('Webhook Integration Tests', () => {
afterAll(async () => { afterAll(async () => {
await stopWebhookServer(); await stopWebhookServer();
await storageService['initializeGlobalPreferences'](); await storageService['initializeGlobalConfig']();
await disconnectFakeParticipants(); await disconnectFakeParticipants();
await Promise.all([deleteAllRooms(), deleteAllRecordings()]); await Promise.all([deleteAllRooms(), deleteAllRecordings()]);
}); });
it('should not send webhooks when disabled', async () => { it('should not send webhooks when disabled', async () => {
await updateWebbhookPreferences({ await updateWebbhookConfig({
enabled: false enabled: false
}); });

View File

@ -14,7 +14,7 @@ import type { DeleteRoomDialogOptions } from '@lib/models';
import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce'; import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce';
@Component({ @Component({
selector: 'ov-dialog', selector: 'ov-delete-room-dialog',
standalone: true, standalone: true,
imports: [ imports: [
FormsModule, FormsModule,

View File

@ -14,9 +14,9 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { ViewportService } from '@lib/services';
import { MeetRecordingInfo, MeetRecordingStatus } from '@lib/typings/ce'; import { MeetRecordingInfo, MeetRecordingStatus } from '@lib/typings/ce';
import { formatBytes, formatDurationToHMS } from '@lib/utils'; import { formatBytes, formatDurationToHMS } from '@lib/utils';
import { ViewportService } from '@lib/services';
export interface RecordingTableAction { export interface RecordingTableAction {
recordings: MeetRecordingInfo[]; recordings: MeetRecordingInfo[];

View File

@ -1,20 +1,20 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatButtonModule, MatIconButton } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@Component({ @Component({
selector: 'ov-share-meeting-link', selector: 'ov-share-meeting-link',
standalone: true, standalone: true,
imports: [MatButtonModule, MatIconModule, MatIconButton], imports: [MatButtonModule, MatIconModule],
templateUrl: './share-meeting-link.component.html', templateUrl: './share-meeting-link.component.html',
styleUrl: './share-meeting-link.component.scss' styleUrl: './share-meeting-link.component.scss'
}) })
export class ShareMeetingLinkComponent { export class ShareMeetingLinkComponent {
@Input() meetingUrl!: string; @Input() meetingUrl!: string;
@Input() title: string = 'Invite others with this meeting link'; @Input() title: string = 'Invite others with this meeting link';
@Input() titleSize: 'sm' | 'md' | 'lg' | 'xl' = 'sm'; @Input() titleSize: 'sm' | 'md' | 'lg' | 'xl' = 'sm';
@Input() titleWeight: 'light' | 'semibold' | 'bold' | 'normal' = 'normal'; @Input() titleWeight: 'light' | 'semibold' | 'bold' | 'normal' = 'normal';
@Input() subtitle?: string; @Input() subtitle?: string;
@Input() additionalInfo?: string; @Input() additionalInfo?: string;
@Output() copyClicked = new EventEmitter<void>(); @Output() copyClicked = new EventEmitter<void>();
} }

View File

@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@ang
import { ErrorReason } from '@lib/models'; import { ErrorReason } from '@lib/models';
import { import {
AuthService, AuthService,
GlobalPreferencesService, GlobalConfigService,
NavigationService, NavigationService,
ParticipantService, ParticipantService,
RecordingService, RecordingService,
@ -53,7 +53,7 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
) => { ) => {
const navigationService = inject(NavigationService); const navigationService = inject(NavigationService);
const authService = inject(AuthService); const authService = inject(AuthService);
const preferencesService = inject(GlobalPreferencesService); const configService = inject(GlobalConfigService);
const roomService = inject(RoomService); const roomService = inject(RoomService);
const participantService = inject(ParticipantService); const participantService = inject(ParticipantService);
@ -81,7 +81,7 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
} }
} }
const authMode = await preferencesService.getAuthModeToAccessRoom(); const authMode = await configService.getAuthModeToAccessRoom();
// If the user is a moderator and the room requires authentication for moderators only, // If the user is a moderator and the room requires authentication for moderators only,
// or if the room requires authentication for all users, // or if the room requires authentication for all users,

View File

@ -4,7 +4,7 @@ import { ConsoleNavLink } from '@lib/models';
import { AuthService } from '@lib/services'; import { AuthService } from '@lib/services';
@Component({ @Component({
selector: 'app-console', selector: 'ov-console',
standalone: true, standalone: true,
imports: [ConsoleNavComponent], imports: [ConsoleNavComponent],
templateUrl: './console.component.html', templateUrl: './console.component.html',

View File

@ -9,11 +9,11 @@ import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { AuthService, GlobalPreferencesService, NotificationService } from '@lib/services'; import { AuthService, GlobalConfigService, NotificationService } from '@lib/services';
import { MeetApiKey } from '@lib/typings/ce'; import { MeetApiKey } from '@lib/typings/ce';
@Component({ @Component({
selector: 'ov-developers-settings', selector: 'ov-embedded',
standalone: true, standalone: true,
imports: [ imports: [
MatCardModule, MatCardModule,
@ -26,10 +26,10 @@ import { MeetApiKey } from '@lib/typings/ce';
ReactiveFormsModule, ReactiveFormsModule,
MatProgressSpinnerModule MatProgressSpinnerModule
], ],
templateUrl: './developers.component.html', templateUrl: './embedded.component.html',
styleUrl: './developers.component.scss' styleUrl: './embedded.component.scss'
}) })
export class DevelopersSettingsComponent implements OnInit { export class EmbeddedComponent implements OnInit {
isLoading = signal(true); isLoading = signal(true);
hasWebhookChanges = signal(false); hasWebhookChanges = signal(false);
@ -51,7 +51,7 @@ export class DevelopersSettingsComponent implements OnInit {
constructor( constructor(
protected authService: AuthService, protected authService: AuthService,
protected preferencesService: GlobalPreferencesService, protected configService: GlobalConfigService,
protected notificationService: NotificationService, protected notificationService: NotificationService,
protected clipboard: Clipboard protected clipboard: Clipboard
) { ) {
@ -164,16 +164,16 @@ export class DevelopersSettingsComponent implements OnInit {
private async loadWebhookConfig() { private async loadWebhookConfig() {
try { try {
const webhookPreferences = await this.preferencesService.getWebhookPreferences(); const webhookConfig = await this.configService.getWebhookConfig();
this.webhookForm.patchValue({ this.webhookForm.patchValue({
isEnabled: webhookPreferences.enabled, isEnabled: webhookConfig.enabled,
url: webhookPreferences.url url: webhookConfig.url
// roomCreated: webhookPreferences.events.roomCreated, // roomCreated: webhookConfig.events.roomCreated,
// roomDeleted: webhookPreferences.events.roomDeleted, // roomDeleted: webhookConfig.events.roomDeleted,
// participantJoined: webhookPreferences.events.participantJoined, // participantJoined: webhookConfig.events.participantJoined,
// participantLeft: webhookPreferences.events.participantLeft, // participantLeft: webhookConfig.events.participantLeft,
// recordingStarted: webhookPreferences.events.recordingStarted, // recordingStarted: webhookConfig.events.recordingStarted,
// recordingFinished: webhookPreferences.events.recordingFinished // recordingFinished: webhookConfig.events.recordingFinished
}); });
// Store initial values after loading // Store initial values after loading
@ -199,7 +199,7 @@ export class DevelopersSettingsComponent implements OnInit {
if (!this.webhookForm.valid) return; if (!this.webhookForm.valid) return;
const formValue = this.webhookForm.value; const formValue = this.webhookForm.value;
const webhookPreferences = { const webhookConfig = {
enabled: formValue.isEnabled!, enabled: formValue.isEnabled!,
url: formValue.url ?? undefined url: formValue.url ?? undefined
// events: { // events: {
@ -213,7 +213,7 @@ export class DevelopersSettingsComponent implements OnInit {
}; };
try { try {
await this.preferencesService.saveWebhookPreferences(webhookPreferences); await this.configService.saveWebhookConfig(webhookConfig);
this.notificationService.showSnackbar('Webhook configuration saved successfully'); this.notificationService.showSnackbar('Webhook configuration saved successfully');
// Update initial values after successful save // Update initial values after successful save
@ -229,7 +229,7 @@ export class DevelopersSettingsComponent implements OnInit {
const url = this.webhookForm.get('url')?.value; const url = this.webhookForm.get('url')?.value;
if (url) { if (url) {
try { try {
await this.preferencesService.testWebhookUrl(url); await this.configService.testWebhookUrl(url);
this.notificationService.showSnackbar('Test webhook sent successfully. Your URL is reachable.'); this.notificationService.showSnackbar('Test webhook sent successfully. Your URL is reachable.');
} catch (error: any) { } catch (error: any) {
const errorMessage = error.error?.message || error.message || 'Unknown error'; const errorMessage = error.error?.message || error.message || 'Unknown error';

View File

@ -53,7 +53,7 @@
<!-- Room Wizard Steps --> <!-- Room Wizard Steps -->
@switch (currentStep()?.id) { @switch (currentStep()?.id) {
@case ('roomDetails') { @case ('roomDetails') {
<ov-room-wizard-room-details></ov-room-wizard-room-details> <ov-room-details></ov-room-details>
} }
@case ('recording') { @case ('recording') {
<ov-recording-config></ov-recording-config> <ov-recording-config></ov-recording-config>

View File

@ -39,7 +39,7 @@ export class RecordingLayoutComponent implements OnDestroy {
description: 'Highlight the active speaker with other participants below', description: 'Highlight the active speaker with other participants below',
imageUrl: './assets/layouts/speaker.png', imageUrl: './assets/layouts/speaker.png',
isPro: true, isPro: true,
disabled: true, disabled: true
// recommended: true // recommended: true
}, },
{ {

View File

@ -31,7 +31,7 @@ export class RecordingTriggerComponent implements OnDestroy {
id: 'manual', id: 'manual',
title: 'Manual Recording', title: 'Manual Recording',
description: 'Start recording manually when needed', description: 'Start recording manually when needed',
icon: 'touch_app', icon: 'touch_app'
// recommended: true // recommended: true
}, },
{ {

View File

@ -14,7 +14,7 @@
<!-- Config Cards Grid --> <!-- Config Cards Grid -->
<div class="config-grid"> <div class="config-grid">
<!-- Chat Settings Card --> <!-- Chat Settings Card -->
<mat-card class="preference-card"> <mat-card class="config-card">
<mat-card-content> <mat-card-content>
<div class="card-header"> <div class="card-header">
<div class="icon-title-group"> <div class="icon-title-group">
@ -38,7 +38,7 @@
</mat-card> </mat-card>
<!-- Virtual Backgrounds Card --> <!-- Virtual Backgrounds Card -->
<mat-card class="preference-card"> <mat-card class="config-card">
<mat-card-content> <mat-card-content>
<div class="card-header"> <div class="card-header">
<div class="icon-title-group"> <div class="icon-title-group">

View File

@ -59,7 +59,7 @@
} }
} }
.preference-card { .config-card {
@include ov-card; @include ov-card;
border: 2px solid var(--ov-meet-border-secondary); border: 2px solid var(--ov-meet-border-secondary);
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
@ -155,7 +155,7 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.preference-card { .config-card {
.card-header { .card-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@ -171,7 +171,7 @@
@media (max-width: 480px) { @media (max-width: 480px) {
.room-config-step { .room-config-step {
.preference-card { .config-card {
.card-header { .card-header {
.icon-title-group { .icon-title-group {
.title-group { .title-group {
@ -187,4 +187,4 @@
} }
} }
} }
} }

View File

@ -18,7 +18,7 @@ import {
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'ov-room-wizard-room-details', selector: 'ov-room-details',
standalone: true, standalone: true,
imports: [ imports: [
ReactiveFormsModule, ReactiveFormsModule,

View File

@ -31,7 +31,7 @@ import {
import { ILogger, LoggerService } from 'openvidu-components-angular'; import { ILogger, LoggerService } from 'openvidu-components-angular';
@Component({ @Component({
selector: 'ov-room-config', selector: 'ov-rooms',
standalone: true, standalone: true,
imports: [ imports: [
MatListModule, MatListModule,

View File

@ -18,11 +18,11 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { ProFeatureBadgeComponent } from '@lib/components'; import { ProFeatureBadgeComponent } from '@lib/components';
import { AuthService, GlobalPreferencesService, NotificationService } from '@lib/services'; import { AuthService, GlobalConfigService, NotificationService } from '@lib/services';
import { AuthMode } from '@lib/typings/ce'; import { AuthMode } from '@lib/typings/ce';
@Component({ @Component({
selector: 'ov-preferences', selector: 'ov-users-permissions',
standalone: true, standalone: true,
imports: [ imports: [
MatCardModule, MatCardModule,
@ -68,7 +68,7 @@ export class UsersPermissionsComponent implements OnInit {
private initialAccessSettingsFormValue: any = null; private initialAccessSettingsFormValue: any = null;
constructor( constructor(
private preferencesService: GlobalPreferencesService, private configService: GlobalConfigService,
private authService: AuthService, private authService: AuthService,
private notificationService: NotificationService private notificationService: NotificationService
) { ) {
@ -129,15 +129,15 @@ export class UsersPermissionsComponent implements OnInit {
private async loadAccessSettings() { private async loadAccessSettings() {
try { try {
const authMode = await this.preferencesService.getAuthModeToAccessRoom(); const authMode = await this.configService.getAuthModeToAccessRoom();
this.accessSettingsForm.get('authModeToAccessRoom')?.setValue(authMode); this.accessSettingsForm.get('authModeToAccessRoom')?.setValue(authMode);
// Store initial values after loading // Store initial values after loading
this.initialAccessSettingsFormValue = this.accessSettingsForm.value; this.initialAccessSettingsFormValue = this.accessSettingsForm.value;
this.hasAccessSettingsChanges.set(false); this.hasAccessSettingsChanges.set(false);
} catch (error) { } catch (error) {
console.error('Error loading security preferences:', error); console.error('Error loading security config:', error);
this.notificationService.showSnackbar('Failed to load security preferences'); this.notificationService.showSnackbar('Failed to load security config');
} }
} }
@ -220,10 +220,10 @@ export class UsersPermissionsComponent implements OnInit {
const formData = this.accessSettingsForm.value; const formData = this.accessSettingsForm.value;
try { try {
const securityPrefs = await this.preferencesService.getSecurityPreferences(); const securityConfig = await this.configService.getSecurityConfig();
securityPrefs.authentication.authModeToAccessRoom = formData.authModeToAccessRoom!; securityConfig.authentication.authModeToAccessRoom = formData.authModeToAccessRoom!;
await this.preferencesService.saveSecurityPreferences(securityPrefs); await this.configService.saveSecurityConfig(securityConfig);
this.notificationService.showSnackbar('Access & Permissions settings saved successfully'); this.notificationService.showSnackbar('Access & Permissions settings saved successfully');
// Update initial values after successful save // Update initial values after successful save

View File

@ -1,6 +1,6 @@
export * from './console/console.component'; export * from './console/console.component';
export * from './console/about/about.component'; export * from './console/about/about.component';
export * from './console/developers/developers.component'; export * from './console/embedded/embedded.component';
export * from './console/overview/overview.component'; export * from './console/overview/overview.component';
export * from './console/recordings/recordings.component'; export * from './console/recordings/recordings.component';
export * from './console/rooms/rooms.component'; export * from './console/rooms/rooms.component';

View File

@ -61,7 +61,7 @@ import {
import { combineLatest, Subject, takeUntil } from 'rxjs'; import { combineLatest, Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'app-meeting', selector: 'ov-meeting',
templateUrl: './meeting.component.html', templateUrl: './meeting.component.html',
styleUrls: ['./meeting.component.scss'], styleUrls: ['./meeting.component.scss'],
standalone: true, standalone: true,

View File

@ -10,7 +10,7 @@ import { MeetRecordingFilters, MeetRecordingInfo } from '@lib/typings/ce';
import { ILogger, LoggerService } from 'openvidu-components-angular'; import { ILogger, LoggerService } from 'openvidu-components-angular';
@Component({ @Component({
selector: 'app-room-recordings', selector: 'ov-room-recordings',
templateUrl: './room-recordings.component.html', templateUrl: './room-recordings.component.html',
styleUrls: ['./room-recordings.component.scss'], styleUrls: ['./room-recordings.component.scss'],
standalone: true, standalone: true,

View File

@ -13,7 +13,7 @@ import {
} from '@lib/guards'; } from '@lib/guards';
import { import {
ConsoleComponent, ConsoleComponent,
DevelopersSettingsComponent, EmbeddedComponent,
EndMeetingComponent, EndMeetingComponent,
ErrorComponent, ErrorComponent,
LoginComponent, LoginComponent,
@ -96,7 +96,7 @@ export const baseRoutes: Routes = [
}, },
{ {
path: 'embedded', path: 'embedded',
component: DevelopersSettingsComponent component: EmbeddedComponent
}, },
{ {
path: 'users-permissions', path: 'users-permissions',

View File

@ -128,7 +128,7 @@ export class FeatureConfigurationService {
* Core logic to calculate features based on all configurations * Core logic to calculate features based on all configurations
*/ */
protected calculateFeatures( protected calculateFeatures(
roomPrefs?: MeetRoomConfig, roomConfig?: MeetRoomConfig,
participantPerms?: ParticipantPermissions, participantPerms?: ParticipantPermissions,
role?: ParticipantRole, role?: ParticipantRole,
recordingPerms?: RecordingPermissions recordingPerms?: RecordingPermissions
@ -137,10 +137,10 @@ export class FeatureConfigurationService {
const features: ApplicationFeatures = { ...DEFAULT_FEATURES }; const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
// Apply room configurations // Apply room configurations
if (roomPrefs) { if (roomConfig) {
features.showRecordingPanel = roomPrefs.recording.enabled; features.showRecordingPanel = roomConfig.recording.enabled;
features.showChat = roomPrefs.chat.enabled; features.showChat = roomConfig.chat.enabled;
features.showBackgrounds = roomPrefs.virtualBackground.enabled; features.showBackgrounds = roomConfig.virtualBackground.enabled;
} }
// Apply participant permissions (these can restrict enabled features) // Apply participant permissions (these can restrict enabled features)

View File

@ -0,0 +1,68 @@
import { Injectable } from '@angular/core';
import { HttpService } from '@lib/services';
import { AuthMode, SecurityConfig, WebhookConfig } from '@lib/typings/ce';
import { LoggerService } from 'openvidu-components-angular';
@Injectable({
providedIn: 'root'
})
export class GlobalConfigService {
protected readonly GLOBAL_CONFIG_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/config`;
protected securityConfig?: SecurityConfig;
protected log;
constructor(
protected loggerService: LoggerService,
protected httpService: HttpService
) {
this.log = this.loggerService.get('OpenVidu Meet - GlobalConfigService');
}
async getSecurityConfig(forceRefresh = false): Promise<SecurityConfig> {
if (this.securityConfig && !forceRefresh) {
return this.securityConfig;
}
try {
const path = `${this.GLOBAL_CONFIG_API}/security`;
this.securityConfig = await this.httpService.getRequest<SecurityConfig>(path);
return this.securityConfig;
} catch (error) {
this.log.e('Error fetching security config:', error);
throw error;
}
}
async getAuthModeToAccessRoom(): Promise<AuthMode> {
await this.getSecurityConfig();
return this.securityConfig!.authentication.authModeToAccessRoom;
}
async saveSecurityConfig(config: SecurityConfig) {
const path = `${this.GLOBAL_CONFIG_API}/security`;
await this.httpService.putRequest(path, config);
this.securityConfig = config;
}
async getWebhookConfig(): Promise<WebhookConfig> {
try {
const path = `${this.GLOBAL_CONFIG_API}/webhooks`;
return await this.httpService.getRequest<WebhookConfig>(path);
} catch (error) {
this.log.e('Error fetching webhook config:', error);
throw error;
}
}
async saveWebhookConfig(config: WebhookConfig) {
const path = `${this.GLOBAL_CONFIG_API}/webhooks`;
await this.httpService.putRequest(path, config);
}
async testWebhookUrl(url: string): Promise<void> {
const path = `${this.GLOBAL_CONFIG_API}/webhooks/test`;
await this.httpService.postRequest(path, { url });
}
}

View File

@ -1,68 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpService } from '@lib/services';
import { AuthMode, SecurityPreferences, WebhookPreferences } from '@lib/typings/ce';
import { LoggerService } from 'openvidu-components-angular';
@Injectable({
providedIn: 'root'
})
export class GlobalPreferencesService {
protected readonly PREFERENCES_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/preferences`;
protected securityPreferences?: SecurityPreferences;
protected log;
constructor(
protected loggerService: LoggerService,
protected httpService: HttpService
) {
this.log = this.loggerService.get('OpenVidu Meet - GlobalPreferencesService');
}
async getSecurityPreferences(forceRefresh = false): Promise<SecurityPreferences> {
if (this.securityPreferences && !forceRefresh) {
return this.securityPreferences;
}
try {
const path = `${this.PREFERENCES_API}/security`;
this.securityPreferences = await this.httpService.getRequest<SecurityPreferences>(path);
return this.securityPreferences;
} catch (error) {
this.log.e('Error fetching security preferences:', error);
throw error;
}
}
async getAuthModeToAccessRoom(): Promise<AuthMode> {
await this.getSecurityPreferences();
return this.securityPreferences!.authentication.authModeToAccessRoom;
}
async saveSecurityPreferences(preferences: SecurityPreferences) {
const path = `${this.PREFERENCES_API}/security`;
await this.httpService.putRequest(path, preferences);
this.securityPreferences = preferences;
}
async getWebhookPreferences(): Promise<WebhookPreferences> {
try {
const path = `${this.PREFERENCES_API}/webhooks`;
return await this.httpService.getRequest<WebhookPreferences>(path);
} catch (error) {
this.log.e('Error fetching webhook preferences:', error);
throw error;
}
}
async saveWebhookPreferences(preferences: WebhookPreferences) {
const path = `${this.PREFERENCES_API}/webhooks`;
await this.httpService.putRequest(path, preferences);
}
async testWebhookUrl(url: string): Promise<void> {
const path = `${this.PREFERENCES_API}/webhooks/test`;
await this.httpService.postRequest(path, { url });
}
}

View File

@ -1,7 +1,7 @@
export * from './app-data.service'; export * from './app-data.service';
export * from './http.service'; export * from './http.service';
export * from './auth.service'; export * from './auth.service';
export * from './global-preferences.service'; export * from './global-config.service';
export * from './room.service'; export * from './room.service';
export * from './participant.service'; export * from './participant.service';
export * from './meeting.service'; export * from './meeting.service';

View File

@ -1,5 +1,5 @@
import { Injectable, signal, computed, OnDestroy } from '@angular/core'; import { computed, Injectable, OnDestroy, signal } from '@angular/core';
import { fromEvent, Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { debounceTime, distinctUntilChanged, fromEvent, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
/** /**
@ -256,4 +256,4 @@ export class ViewportService implements OnDestroy {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
} }
} }

View File

@ -131,7 +131,7 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId); await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
await viewRecordingsAs('moderator', page); await viewRecordingsAs('moderator', page);
await waitForElementInIframe(page, 'app-room-recordings', { state: 'visible' }); await waitForElementInIframe(page, 'ov-room-recordings', { state: 'visible' });
}); });
test('should speaker not be able to access recording when access level is set to moderator', async ({ page }) => { test('should speaker not be able to access recording when access level is set to moderator', async ({ page }) => {
@ -155,7 +155,7 @@ test.describe('Recording Access Tests', () => {
await waitForElementInIframe(page, '#view-recordings-btn', { state: 'hidden' }); await waitForElementInIframe(page, '#view-recordings-btn', { state: 'hidden' });
}); });
test('should allow moderators to access recording when access level is set to speaker', async ({ page }) => { test('should allow moderator to access recording when access level is set to speaker', async ({ page }) => {
await updateRoomConfig( await updateRoomConfig(
roomId, roomId,
{ {
@ -173,7 +173,7 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId); await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
await viewRecordingsAs('moderator', page); await viewRecordingsAs('moderator', page);
await waitForElementInIframe(page, 'app-room-recordings', { state: 'visible' }); await waitForElementInIframe(page, 'ov-room-recordings', { state: 'visible' });
}); });
test('should allow speaker to access recording when access level is set to speaker', async ({ page }) => { test('should allow speaker to access recording when access level is set to speaker', async ({ page }) => {
@ -194,6 +194,6 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId); await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
await viewRecordingsAs('speaker', page); await viewRecordingsAs('speaker', page);
await waitForElementInIframe(page, 'app-room-recordings', { state: 'visible' }); await waitForElementInIframe(page, 'ov-room-recordings', { state: 'visible' });
}); });
}); });

View File

@ -1,4 +1,4 @@
export interface AuthenticationPreferences { export interface AuthenticationConfig {
authMethod: ValidAuthMethod; authMethod: ValidAuthMethod;
authModeToAccessRoom: AuthMode; authModeToAccessRoom: AuthMode;
} }

View File

@ -0,0 +1,21 @@
import { AuthenticationConfig } from './auth-config.js';
/**
* Represents global config for OpenVidu Meet.
*/
export interface GlobalConfig {
projectId: string;
securityConfig: SecurityConfig;
webhooksConfig: WebhookConfig;
// roomsConfig: MeetRoomConfig;
}
export interface WebhookConfig {
enabled: boolean;
url?: string;
// events: WebhookEvent[];
}
export interface SecurityConfig {
authentication: AuthenticationConfig;
}

View File

@ -1,21 +0,0 @@
import { AuthenticationPreferences } from './auth-preferences.js';
/**
* Represents global preferences for OpenVidu Meet.
*/
export interface GlobalPreferences {
projectId: string;
// roomFeaturesPreferences: RoomFeaturesPreferences;
webhooksPreferences: WebhookPreferences;
securityPreferences: SecurityPreferences;
}
export interface WebhookPreferences {
enabled: boolean;
url?: string;
// events: WebhookEvent[];
}
export interface SecurityPreferences {
authentication: AuthenticationPreferences;
}

View File

@ -1,6 +1,6 @@
export * from './api-key.js'; export * from './api-key.js';
export * from './auth-preferences.js'; export * from './auth-config.js';
export * from './global-preferences.js'; export * from './global-config.js';
export * from './permissions/livekit-permissions.js'; export * from './permissions/livekit-permissions.js';
export * from './permissions/openvidu-permissions.js'; export * from './permissions/openvidu-permissions.js';