Rename global preferences to global config
This commit is contained in:
parent
465a68295f
commit
fa1582bee0
38
.github/workflows/backend-integration-test.yaml
vendored
38
.github/workflows/backend-integration-test.yaml
vendored
@ -84,7 +84,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-rooms'
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-rooms
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-rooms'
|
||||
@ -154,7 +154,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-recordings'
|
||||
@ -163,7 +163,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-recordings
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-recordings'
|
||||
@ -220,7 +220,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-webhooks'
|
||||
@ -229,7 +229,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-webhooks
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-webhooks'
|
||||
@ -286,7 +286,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-security'
|
||||
@ -295,7 +295,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-security
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-security'
|
||||
@ -311,8 +311,8 @@ jobs:
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
test-global-preferences:
|
||||
name: Global Preferences API Tests
|
||||
test-global-config:
|
||||
name: Global Config API Tests
|
||||
runs-on: ov-actions-runner
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -352,16 +352,16 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-global-preferences'
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd backend
|
||||
npm run test:integration-global-preferences
|
||||
npm run test:integration-global-config
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-global-preferences'
|
||||
@ -418,7 +418,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-participants'
|
||||
@ -427,7 +427,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-participants
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-participants'
|
||||
@ -484,7 +484,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-meetings'
|
||||
@ -493,7 +493,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-meetings
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-meetings'
|
||||
@ -550,7 +550,7 @@ jobs:
|
||||
- name: Setup OpenVidu Meet
|
||||
uses: OpenVidu/actions/start-openvidu-meet@main
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-users'
|
||||
@ -559,7 +559,7 @@ jobs:
|
||||
cd backend
|
||||
npm run test:integration-users
|
||||
env:
|
||||
MEET_PREFERENCES_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ vars.MEET_AZURE_ACCOUNT_NAME }}
|
||||
MEET_AZURE_ACCOUNT_KEY: ${{ secrets.MEET_AZURE_ACCOUNT_KEY }}
|
||||
MEET_AZURE_CONTAINER_NAME: 'openvidu-appdata-users'
|
||||
|
||||
@ -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/`)
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
description: New security config
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/global-security-config.yaml'
|
||||
@ -1,6 +0,0 @@
|
||||
description: New security preferences
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/security-preferences.yaml'
|
||||
@ -0,0 +1,6 @@
|
||||
description: New webhooks config
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/webhooks-config.yaml'
|
||||
@ -1,6 +0,0 @@
|
||||
description: New webhooks preferences
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/webhooks-preferences.yaml'
|
||||
@ -0,0 +1,5 @@
|
||||
description: Successfully retrieved security config
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/global-security-config.yaml'
|
||||
@ -1,5 +0,0 @@
|
||||
description: Successfully retrieved security preferences
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/security-preferences.yaml'
|
||||
@ -0,0 +1,5 @@
|
||||
description: Successfully retrieved webhooks config
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/webhooks-config.yaml'
|
||||
@ -1,5 +0,0 @@
|
||||
description: Successfully retrieved webhooks preferences
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/webhooks-preferences.yaml'
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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:
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
@ -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';
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
@ -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);
|
||||
@ -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 = [];
|
||||
|
||||
@ -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:`,
|
||||
|
||||
48
backend/src/routes/global-config.routes.ts
Normal file
48
backend/src/routes/global-config.routes.ts
Normal 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
|
||||
);
|
||||
@ -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
|
||||
);
|
||||
@ -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';
|
||||
|
||||
@ -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'));
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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({
|
||||
@ -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,
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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'
|
||||
})
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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';
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
},
|
||||
{
|
||||
|
||||
@ -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
|
||||
},
|
||||
{
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -31,7 +31,7 @@ import {
|
||||
import { ILogger, LoggerService } from 'openvidu-components-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-room-config',
|
||||
selector: 'ov-rooms',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatListModule,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@ -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' });
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export interface AuthenticationPreferences {
|
||||
export interface AuthenticationConfig {
|
||||
authMethod: ValidAuthMethod;
|
||||
authModeToAccessRoom: AuthMode;
|
||||
}
|
||||
21
typings/src/global-config.ts
Normal file
21
typings/src/global-config.ts
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user