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
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
```plaintext
openvidu-appdata/
├── openvidu-meet/
│ ├── global-preferences.json
│ ├── api-keys.json
│ ├── global-config.json
│ ├── users/
│ │ └── admin.json
│ ├── rooms/
@ -56,13 +57,17 @@ openvidu-appdata/
### 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/`)
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/`)

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:
application/json:
schema:
@ -6,4 +6,4 @@ content:
properties:
message:
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:
application/json:
schema:
@ -6,4 +6,4 @@ content:
properties:
message:
type: string
example: 'Webhooks preferences updated successfully'
example: 'Webhooks config updated successfully'

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
description: Authentication operations
- name: Internal API - Users
description: Operations related to managing users in OpenVidu Meet
- name: Internal API - Global Preferences
description: Operations related to managing global preferences in OpenVidu Meet
- name: Internal API - Global Config
description: Operations related to managing global config in OpenVidu Meet
- name: Internal API - Rooms
description: Operations related to managing OpenVidu Meet rooms
- 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-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-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-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",

View File

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

View File

@ -50,7 +50,7 @@ export const {
LIVEKIT_API_KEY = 'devkey',
LIVEKIT_API_SECRET = 'secret',
MEET_PREFERENCES_STORAGE_MODE = 's3', // Options: 's3', 'abs'
MEET_BLOB_STORAGE_MODE = 's3', // Options: 's3', 'abs'
// S3 configuration
MEET_S3_BUCKET = 'openvidu-appdata',
@ -108,12 +108,12 @@ export const logEnvVars = () => {
console.log('SERVICE NAME ID: ', text(MEET_NAME_ID));
console.log('CORS ORIGIN:', text(SERVER_CORS_ORIGIN));
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 PASSWORD: ', credential('****' + MEET_INITIAL_ADMIN_PASSWORD.slice(-3)));
if (!MEET_INITIAL_API_KEY) {
console.log(chalk.red('MEET INITIAL_API_KEY: none'));
console.log(chalk.red('MEET INITIAL API KEY: none'));
} else {
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('---------------------------------------------------------');
if (MEET_PREFERENCES_STORAGE_MODE === 's3') {
if (MEET_BLOB_STORAGE_MODE === 's3') {
console.log('S3 Configuration');
console.log('---------------------------------------------------------');
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 S3 WITH PATH STYLE ACCESS:', text(MEET_S3_WITH_PATH_STYLE_ACCESS));
console.log('---------------------------------------------------------');
} else if (MEET_PREFERENCES_STORAGE_MODE === 'abs') {
} else if (MEET_BLOB_STORAGE_MODE === 'abs') {
console.log('Azure Blob Storage Configuration');
console.log('---------------------------------------------------------');
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/participant-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.
*/
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);
let role: ParticipantRole;
@ -28,10 +28,10 @@ export const configureParticipantTokenAuth = async (req: Request, res: Response,
let authModeToAccessRoom: AuthMode;
try {
const { securityPreferences } = await globalPrefService.getGlobalPreferences();
authModeToAccessRoom = securityPreferences.authentication.authModeToAccessRoom;
const { securityConfig } = await storageService.getGlobalConfig();
authModeToAccessRoom = securityConfig.authentication.authModeToAccessRoom;
} catch (error) {
return handleError(res, error, 'checking authentication preferences');
return handleError(res, error, 'checking authentication config');
}
const authValidators = [];

View File

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

View File

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

View File

@ -3,7 +3,7 @@ export const enum RedisKeyPrefix {
}
export const enum RedisKeyName {
GLOBAL_PREFERENCES = `${RedisKeyPrefix.BASE}global_preferences`,
GLOBAL_CONFIG = `${RedisKeyPrefix.BASE}global_config`,
ROOM = `${RedisKeyPrefix.BASE}room:`,
RECORDING = `${RedisKeyPrefix.BASE}recording:`,
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 './user.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 {
authRouter,
configRouter,
internalMeetingRouter,
internalParticipantRouter,
internalRecordingRouter,
internalRoomRouter,
livekitWebhookRouter,
preferencesRouter,
recordingRouter,
roomRouter,
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}/participants`, internalParticipantRouter);
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'));

View File

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

View File

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

View File

@ -1,14 +1,14 @@
import { inject, injectable } from 'inversify';
import { container, STORAGE_TYPES } from '../../config/dependency-injector.config.js';
import { LoggerService } from '../index.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
* based on configuration.
*
* 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.
*
* Domain-specific logic should be handled in the MeetStorageService layer.

View File

@ -109,9 +109,9 @@ export interface StorageProvider {
*/
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.

View File

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

View File

@ -26,7 +26,7 @@ import {
MeetRoomDeletionPolicyWithRecordings,
MeetRoomOptions,
ParticipantRole,
WebhookPreferences
WebhookConfig
} from '../../src/typings/ce/index.js';
const CREDENTIALS = {
@ -77,47 +77,47 @@ export const getApiKeys = async () => {
return response;
};
export const getAppearancePreferences = async () => {
export const getAppearanceConfig = async () => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send();
return response;
};
export const updateAppearancePreferences = async (preferences: any) => {
export const updateAppearanceConfig = async (config: any) => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send(preferences);
.send(config);
return response;
};
export const getWebbhookPreferences = async () => {
export const getWebbhookConfig = async () => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send();
return response;
};
export const updateWebbhookPreferences = async (preferences: WebhookPreferences) => {
export const updateWebbhookConfig = async (config: WebhookConfig) => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send(preferences);
.send(config);
return response;
};
@ -126,35 +126,35 @@ export const testWebhookUrl = async (url: string) => {
checkAppIsRunning();
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 });
return response;
};
export const getSecurityPreferences = async () => {
export const getSecurityConfig = async () => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send();
return response;
};
export const updateSecurityPreferences = async (preferences: any) => {
export const updateSecurityConfig = async (config: any) => {
checkAppIsRunning();
const adminCookie = await loginUser();
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)
.send(preferences);
.send(config);
return response;
};
export const changeSecurityPreferences = async (authMode: AuthMode) => {
const response = await updateSecurityPreferences({
export const changeSecurityConfig = async (authMode: AuthMode) => {
const response = await updateSecurityConfig({
authentication: {
authMethod: {
type: AuthType.SINGLE_USER
@ -376,7 +376,7 @@ export const generateParticipantToken = async (participantOptions: any, cookie?:
checkAppIsRunning();
// Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
// Generate the participant token
const response = await request(app)
@ -418,7 +418,7 @@ export const refreshParticipantToken = async (participantOptions: any, cookie: s
checkAppIsRunning();
// Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app)
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token/refresh`)
@ -580,7 +580,7 @@ export const generateRecordingToken = async (roomId: string, secret: string) =>
checkAppIsRunning();
// Disable authentication to generate the token
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app)
.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 {
getAppearancePreferences,
startTestServer,
updateAppearancePreferences
} from '../../../helpers/request-helpers.js';
import { getAppearanceConfig, startTestServer, updateAppearanceConfig } from '../../../helpers/request-helpers.js';
describe('Appearance API Tests', () => {
beforeAll(() => {
startTestServer();
});
describe('Get Appearance Preferences', () => {
describe('Get Appearance Config', () => {
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);
});
});
describe('Update Appearance Preferences', () => {
describe('Update Appearance Config', () => {
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);
});
});

View File

@ -3,13 +3,9 @@ import { container } from '../../../../src/config/dependency-injector.config.js'
import { MeetStorageService } from '../../../../src/services/index.js';
import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js';
import { expectValidationError } from '../../../helpers/assertion-helpers.js';
import {
getSecurityPreferences,
startTestServer,
updateSecurityPreferences
} from '../../../helpers/request-helpers.js';
import { getSecurityConfig, startTestServer, updateSecurityConfig } from '../../../helpers/request-helpers.js';
const defaultPreferences = {
const defaultConfig = {
authentication: {
authMethod: {
type: AuthType.SINGLE_USER
@ -18,23 +14,23 @@ const defaultPreferences = {
}
};
const restoreDefaultGlobalPreferences = async () => {
const defaultPref = await container.get(MeetStorageService)['getDefaultPreferences']();
await container.get(MeetStorageService).saveGlobalPreferences(defaultPref);
const restoreDefaultGlobalConfig = async () => {
const defaultGlobalConfig = await container.get(MeetStorageService)['getDefaultConfig']();
await container.get(MeetStorageService).saveGlobalConfig(defaultGlobalConfig);
};
describe('Security Preferences API Tests', () => {
describe('Security Config API Tests', () => {
beforeAll(async () => {
startTestServer();
});
afterEach(async () => {
await restoreDefaultGlobalPreferences();
await restoreDefaultGlobalConfig();
});
describe('Update security preferences', () => {
it('should update security preferences with valid complete data', async () => {
const validPreferences = {
describe('Update security config', () => {
it('should update security config with valid complete data', async () => {
const validConfig = {
authentication: {
authMethod: {
type: AuthType.SINGLE_USER
@ -42,20 +38,20 @@ describe('Security Preferences API Tests', () => {
authModeToAccessRoom: AuthMode.ALL_USERS
}
};
let response = await updateSecurityPreferences(validPreferences);
let response = await updateSecurityConfig(validConfig);
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.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 () => {
const response = await updateSecurityPreferences({
const response = await updateSecurityConfig({
authentication: {
authMethod: {
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 () => {
const response = await updateSecurityPreferences({
const response = await updateSecurityConfig({
authentication: {
authMethod: {
type: 'invalid'
@ -89,14 +85,14 @@ describe('Security Preferences API Tests', () => {
});
it('should reject when authModeToAccessRoom or authMethod are not provided', async () => {
let response = await updateSecurityPreferences({
let response = await updateSecurityConfig({
authentication: {
authMode: AuthMode.NONE
}
});
expectValidationError(response, 'authentication.authMethod', 'Required');
response = await updateSecurityPreferences({
response = await updateSecurityConfig({
authentication: {
method: {
type: AuthType.SINGLE_USER
@ -107,7 +103,7 @@ describe('Security Preferences API Tests', () => {
});
it('should reject when authentication is not an object', async () => {
const response = await updateSecurityPreferences({
const response = await updateSecurityConfig({
authentication: 'invalid'
});
@ -115,12 +111,12 @@ describe('Security Preferences API Tests', () => {
});
});
describe('Get security preferences', () => {
it('should return security preferences when authenticated as admin', async () => {
const response = await getSecurityPreferences();
describe('Get security config', () => {
it('should return security config when authenticated as admin', async () => {
const response = await getSecurityConfig();
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 { expectValidationError } from '../../../helpers/assertion-helpers.js';
import {
getWebbhookPreferences,
getWebbhookConfig,
startTestServer,
testWebhookUrl,
updateWebbhookPreferences
updateWebbhookConfig
} from '../../../helpers/request-helpers.js';
import { startWebhookServer, stopWebhookServer } from '../../../helpers/test-scenarios.js';
describe('Webhook Preferences API Tests', () => {
describe('Webhook Config API Tests', () => {
beforeAll(() => {
startTestServer();
});
afterEach(async () => {
const storageService = container.get(MeetStorageService);
await storageService['initializeGlobalPreferences']();
await storageService['initializeGlobalConfig']();
});
describe('Update webhook preferences', () => {
it('should update webhook preferences with valid data', async () => {
const validPreferences = {
describe('Update webhook config', () => {
it('should update webhook config with valid data', async () => {
const validConfig = {
enabled: true,
url: 'https://example.com/webhook'
};
let response = await updateWebbhookPreferences(validPreferences);
let response = await updateWebbhookConfig(validConfig);
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.body.enabled).toBe(true);
expect(response.body.url).toBe(validPreferences.url);
expect(response.body).toEqual(validPreferences);
expect(response.body.url).toBe(validConfig.url);
expect(response.body).toEqual(validConfig);
});
it('should allow disabling webhooks', async () => {
const oldWebhookPreferences = await getWebbhookPreferences();
expect(oldWebhookPreferences.status).toBe(200);
const oldWebhookConfig = await getWebbhookConfig();
expect(oldWebhookConfig.status).toBe(200);
let response = await updateWebbhookPreferences({
let response = await updateWebbhookConfig({
enabled: false
});
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.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 () => {
const preference = {
const config = {
enabled: false,
url: 'https://newurl.com/webhook'
};
const response = await updateWebbhookPreferences(preference);
const response = await updateWebbhookConfig(config);
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();
expect(preferencesResponse.status).toBe(200);
expect(preferencesResponse.body.enabled).toBe(preference.enabled);
expect(preferencesResponse.body.url).toBe(preference.url);
const configResponse = await getWebbhookConfig();
expect(configResponse.status).toBe(200);
expect(configResponse.body.enabled).toBe(config.enabled);
expect(configResponse.body.url).toBe(config.url);
});
});
describe('Update webhook preferences validation', () => {
describe('Update webhook config validation', () => {
it('should reject invalid webhook URL', async () => {
const response = await updateWebbhookPreferences({
const response = await updateWebbhookConfig({
enabled: true,
url: 'invalid-url'
});
@ -86,14 +86,14 @@ describe('Webhook Preferences API Tests', () => {
});
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);
expectValidationError(response, 'url', 'URL is required when webhooks are enabled');
});
it('should reject non-http(s) URLs', async () => {
const response = await updateWebbhookPreferences({
const response = await updateWebbhookConfig({
enabled: true,
url: 'ftp://example.com/webhook'
});
@ -103,9 +103,9 @@ describe('Webhook Preferences API Tests', () => {
});
});
describe('Get webhook preferences', () => {
it('should return webhook preferences when authenticated as admin', async () => {
const response = await getWebbhookPreferences();
describe('Get webhook config', () => {
it('should return webhook config when authenticated as admin', async () => {
const response = await getWebbhookConfig();
expect(response.status).toBe(200);
expect(response.body).toEqual({

View File

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

View File

@ -34,7 +34,7 @@ describe('Room API Tests', () => {
it('should retrieve a room with custom config', async () => {
const payload = {
roomName: 'custom-prefs',
roomName: 'custom-config',
config: {
recording: {
enabled: true,
@ -50,7 +50,7 @@ describe('Room API Tests', () => {
// Retrieve the room by its ID
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 () => {

View File

@ -77,7 +77,7 @@ describe('Room API Tests', () => {
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
const createdRoom = await createRoom({
roomName: 'partial-update',
@ -91,7 +91,7 @@ describe('Room API Tests', () => {
}
});
// Update only one preference
// Update only one config field
const partialConfig = {
recording: {
enabled: false,

View File

@ -4,7 +4,7 @@ import request from 'supertest';
import INTERNAL_CONFIG from '../../../../src/config/internal-config.js';
import { AuthMode } from '../../../../src/typings/ce/index.js';
import {
changeSecurityPreferences,
changeSecurityConfig,
deleteAllRooms,
disconnectFakeParticipants,
loginUser,
@ -39,7 +39,7 @@ describe('Participant API Security Tests', () => {
});
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({
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 () => {
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).set('Cookie', adminCookie).send({
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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app).post(`${PARTICIPANTS_PATH}/token`).send({
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 () => {
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 { 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 adminCookie: string;
@ -17,55 +17,55 @@ describe('Global Preferences API Security Tests', () => {
adminCookie = await loginUser();
});
describe('Update Webhook Preferences Tests', () => {
const webhookPreferences = {
describe('Update Webhook Config Tests', () => {
const webhookConfig = {
enabled: true,
url: 'https://example.com/webhook'
};
it('should fail when request includes API key', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/webhooks`)
.put(`${CONFIG_PATH}/webhooks`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send(webhookPreferences);
.send(webhookConfig);
expect(response.status).toBe(401);
});
it('should succeed when user is authenticated as admin', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/webhooks`)
.put(`${CONFIG_PATH}/webhooks`)
.set('Cookie', adminCookie)
.send(webhookPreferences);
.send(webhookConfig);
expect(response.status).toBe(200);
});
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);
});
});
describe('Get Webhook Preferences Tests', () => {
describe('Get Webhook Config Tests', () => {
it('should fail when request includes API key', async () => {
const response = await request(app)
.get(`${PREFERENCES_PATH}/webhooks`)
.get(`${CONFIG_PATH}/webhooks`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY);
expect(response.status).toBe(401);
});
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);
});
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);
});
});
describe('Update Security Preferences Tests', () => {
const securityPreferences = {
describe('Update Security Config Tests', () => {
const securityConfig = {
authentication: {
authMethod: {
type: AuthType.SINGLE_USER
@ -76,71 +76,68 @@ describe('Global Preferences API Security Tests', () => {
it('should fail when request includes API key', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/security`)
.put(`${CONFIG_PATH}/security`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send(securityPreferences);
.send(securityConfig);
expect(response.status).toBe(401);
});
it('should succeed when user is authenticated as admin', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/security`)
.put(`${CONFIG_PATH}/security`)
.set('Cookie', adminCookie)
.send(securityPreferences);
.send(securityConfig);
expect(response.status).toBe(200);
});
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);
});
});
describe('Get Security Preferences Tests', () => {
describe('Get Security Config Tests', () => {
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);
});
});
describe('Update Appearance Preferences Tests', () => {
describe('Update Appearance Config Tests', () => {
it('should fail when request includes API key', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/appearance`)
.put(`${CONFIG_PATH}/appearance`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY)
.send({});
expect(response.status).toBe(401);
});
it('should succeed when user is authenticated as admin', async () => {
const response = await request(app)
.put(`${PREFERENCES_PATH}/appearance`)
.set('Cookie', adminCookie)
.send({});
const response = await request(app).put(`${CONFIG_PATH}/appearance`).set('Cookie', adminCookie).send({});
expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case
});
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);
});
});
describe('Get Appearance Preferences Tests', () => {
describe('Get Appearance Config Tests', () => {
it('should fail when request includes API key', async () => {
const response = await request(app)
.get(`${PREFERENCES_PATH}/appearance`)
.get(`${CONFIG_PATH}/appearance`)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY);
expect(response.status).toBe(401);
});
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
});
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);
});
});

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 { AuthMode, MeetRecordingAccess, ParticipantRole } from '../../../../src/typings/ce/index.js';
import {
changeSecurityPreferences,
changeSecurityConfig,
createRoom,
deleteAllRecordings,
deleteAllRooms,
@ -323,7 +323,7 @@ describe('Room API Security Tests', () => {
});
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(`${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 () => {
await changeSecurityPreferences(AuthMode.NONE);
await changeSecurityConfig(AuthMode.NONE);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.MODERATORS_ONLY);
await changeSecurityConfig(AuthMode.MODERATORS_ONLY);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.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 () => {
await changeSecurityPreferences(AuthMode.ALL_USERS);
await changeSecurityConfig(AuthMode.ALL_USERS);
const response = await request(app)
.post(`${INTERNAL_ROOMS_PATH}/${roomData.room.roomId}/recording-token`)

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@ang
import { ErrorReason } from '@lib/models';
import {
AuthService,
GlobalPreferencesService,
GlobalConfigService,
NavigationService,
ParticipantService,
RecordingService,
@ -53,7 +53,7 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
) => {
const navigationService = inject(NavigationService);
const authService = inject(AuthService);
const preferencesService = inject(GlobalPreferencesService);
const configService = inject(GlobalConfigService);
const roomService = inject(RoomService);
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,
// 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';
@Component({
selector: 'app-console',
selector: 'ov-console',
standalone: true,
imports: [ConsoleNavComponent],
templateUrl: './console.component.html',

View File

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

View File

@ -53,7 +53,7 @@
<!-- Room Wizard Steps -->
@switch (currentStep()?.id) {
@case ('roomDetails') {
<ov-room-wizard-room-details></ov-room-wizard-room-details>
<ov-room-details></ov-room-details>
}
@case ('recording') {
<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',
imageUrl: './assets/layouts/speaker.png',
isPro: true,
disabled: true,
disabled: true
// recommended: true
},
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
export * from './console/console.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/recordings/recordings.component';
export * from './console/rooms/rooms.component';

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ export class FeatureConfigurationService {
* Core logic to calculate features based on all configurations
*/
protected calculateFeatures(
roomPrefs?: MeetRoomConfig,
roomConfig?: MeetRoomConfig,
participantPerms?: ParticipantPermissions,
role?: ParticipantRole,
recordingPerms?: RecordingPermissions
@ -137,10 +137,10 @@ export class FeatureConfigurationService {
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
// Apply room configurations
if (roomPrefs) {
features.showRecordingPanel = roomPrefs.recording.enabled;
features.showChat = roomPrefs.chat.enabled;
features.showBackgrounds = roomPrefs.virtualBackground.enabled;
if (roomConfig) {
features.showRecordingPanel = roomConfig.recording.enabled;
features.showChat = roomConfig.chat.enabled;
features.showBackgrounds = roomConfig.virtualBackground.enabled;
}
// 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 './http.service';
export * from './auth.service';
export * from './global-preferences.service';
export * from './global-config.service';
export * from './room.service';
export * from './participant.service';
export * from './meeting.service';

View File

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

View File

@ -131,7 +131,7 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
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 }) => {
@ -155,7 +155,7 @@ test.describe('Recording Access Tests', () => {
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(
roomId,
{
@ -173,7 +173,7 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
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 }) => {
@ -194,6 +194,6 @@ test.describe('Recording Access Tests', () => {
await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
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;
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 './auth-preferences.js';
export * from './global-preferences.js';
export * from './auth-config.js';
export * from './global-config.js';
export * from './permissions/livekit-permissions.js';
export * from './permissions/openvidu-permissions.js';