From 815c44f1e1a1932080e17d3df17b560cf86e911d Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Mon, 16 Mar 2026 00:02:58 +0100 Subject: [PATCH] [mirotalk] - #290 refactor: centralize environment config in config.template.js --- README.md | 6 +- app/src/api.js | 5 +- app/src/config.template.js | 248 +++++++++++++++++++++++++++++++++++++ app/src/lib/nodemailer.js | 20 +-- app/src/server.js | 214 +++++++++----------------------- package.json | 1 + tests/test-api.js | 5 +- 7 files changed, 329 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index a0c7f2fe..2eeef743 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,9 @@ $ git clone https://github.com/miroslavpejic85/mirotalk.git $ cd mirotalk # copy .env.template to .env (edit it according to your needs) $ cp .env.template .env -# Copy app/src/config.template.js in app/src/config.js (edit it according to your needs) +# Setup configuration: copy the template and edit config.js to match your environment. +# config.js is the central configuration file — it reads environment variables +# and also contains branding, button visibility, and webhook settings. $ cp app/src/config.template.js app/src/config.js # install dependencies $ npm install @@ -202,7 +204,7 @@ $ git clone https://github.com/miroslavpejic85/mirotalk.git $ cd mirotalk # copy .env.template to .env (edit it according to your needs) $ cp .env.template .env -# Copy app/src/config.template.js in app/src/config.js (edit it according to your needs) +# Copy app/src/config.template.js in app/src/config.js (central configuration file) $ cp app/src/config.template.js app/src/config.js # Copy docker-compose.template.yml in docker-compose.yml (edit it according to your needs) $ cp docker-compose.template.yml docker-compose.yml diff --git a/app/src/api.js b/app/src/api.js index 35aa8324..a76f7275 100644 --- a/app/src/api.js +++ b/app/src/api.js @@ -5,8 +5,9 @@ const CryptoJS = require('crypto-js'); const { v4: uuidV4 } = require('uuid'); -const JWT_KEY = process.env.JWT_KEY || 'mirotalk_jwt_secret'; -const JWT_EXP = process.env.JWT_EXP || '1h'; +const config = require('./config'); +const JWT_KEY = config.jwt.key; +const JWT_EXP = config.jwt.exp; module.exports = class ServerApi { constructor(host = null, authorization = null, api_key_secret = null) { this._host = host; diff --git a/app/src/config.template.js b/app/src/config.template.js index eec7aa46..1670f67c 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -5,13 +5,258 @@ * MiroTalk P2P v.1.7.42 - Configuration File * ============================================== * + * This file is the central configuration source. + * All environment variables are read here so the + * rest of the codebase imports config values + * instead of reading process.env directly. + * + * Setup: + * cp app/src/config.template.js app/src/config.js + * Then edit config.js to match your environment. + * + * Docker/container environments inject values via + * environment variables which are read at startup. + * * Branding and customizations require a license: * https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 */ +require('dotenv').config(); + const packageJson = require('../../package.json'); +// Helper: parse env string to boolean +function getEnvBoolean(key, force_true_if_undefined = false) { + if (key == undefined && force_true_if_undefined) return true; + return key == 'true' ? true : false; +} + +// Helper: safely parse JSON env vars with a fallback +function parseJsonEnv(envValue, fallback) { + if (!envValue) return fallback; + try { + return JSON.parse(envValue); + } catch (e) { + return fallback; + } +} + +const port = process.env.PORT || 3000; + module.exports = { + // ========================================== + // Server + // ========================================== + server: { + port: port, + host: process.env.HOST || `http://localhost:${port}`, + environment: process.env.NODE_ENV || 'development', + trustProxy: !!getEnvBoolean(process.env.TRUST_PROXY), + }, + + // ========================================== + // CORS + // ========================================== + cors: { + origin: parseJsonEnv(process.env.CORS_ORIGIN, '*'), + methods: parseJsonEnv(process.env.CORS_METHODS, ['GET', 'POST']), + }, + + // ========================================== + // Host Protection + // ========================================== + host: { + protected: getEnvBoolean(process.env.HOST_PROTECTED), + userAuth: getEnvBoolean(process.env.HOST_USER_AUTH), + users: parseJsonEnv(process.env.HOST_USERS, [{ username: 'MiroTalk', password: 'P2P' }]), + maxLoginAttempts: process.env.HOST_MAX_LOGIN_ATTEMPTS || 5, + minLoginBlockTime: process.env.HOST_MIN_LOGIN_BLOCK_TIME || 15, // in minutes + maxRoomParticipants: parseInt(process.env.ROOM_MAX_PARTICIPANTS) || 1000, + showActiveRooms: getEnvBoolean(process.env.SHOW_ACTIVE_ROOMS) || false, + }, + + // ========================================== + // JWT + // ========================================== + jwt: { + key: process.env.JWT_KEY || 'mirotalk_jwt_secret', + exp: process.env.JWT_EXP || '1h', + }, + + // ========================================== + // Presenters + // ========================================== + presenters: parseJsonEnv(process.env.PRESENTERS, ['MiroTalk P2P']), + + // ========================================== + // API + // ========================================== + api: { + keySecret: process.env.API_KEY_SECRET || 'mirotalkp2p_default_secret', + disabled: parseJsonEnv(process.env.API_DISABLED, ['token', 'meetings']), + }, + + // ========================================== + // Ngrok + // ========================================== + ngrok: { + enabled: getEnvBoolean(process.env.NGROK_ENABLED), + authToken: process.env.NGROK_AUTH_TOKEN, + }, + + // ========================================== + // WebRTC ICE Servers + // ========================================== + webrtc: { + stun: { + enabled: getEnvBoolean(process.env.STUN_SERVER_ENABLED), + url: process.env.STUN_SERVER_URL, + }, + turn: { + enabled: getEnvBoolean(process.env.TURN_SERVER_ENABLED), + url: process.env.TURN_SERVER_URL, + username: process.env.TURN_SERVER_USERNAME, + credential: process.env.TURN_SERVER_CREDENTIAL, + }, + }, + + // ========================================== + // IP Lookup + // ========================================== + ipLookup: { + enabled: getEnvBoolean(process.env.IP_LOOKUP_ENABLED), + }, + + // ========================================== + // Survey + // ========================================== + survey: { + enabled: getEnvBoolean(process.env.SURVEY_ENABLED), + url: process.env.SURVEY_URL || 'https://www.questionpro.com/t/AUs7VZq00L', + }, + + // ========================================== + // Redirect + // ========================================== + redirect: { + enabled: getEnvBoolean(process.env.REDIRECT_ENABLED), + url: process.env.REDIRECT_URL || '/newcall', + }, + + // ========================================== + // Sentry + // ========================================== + sentry: { + enabled: getEnvBoolean(process.env.SENTRY_ENABLED), + dsn: process.env.SENTRY_DSN, + tracesSampleRate: parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE || '0.0'), + logLevels: process.env.SENTRY_LOG_LEVELS + ? process.env.SENTRY_LOG_LEVELS.split(',').map((level) => level.trim()) + : ['error'], + }, + + // ========================================== + // Slack + // ========================================== + slack: { + enabled: getEnvBoolean(process.env.SLACK_ENABLED), + signingSecret: process.env.SLACK_SIGNING_SECRET, + }, + + // ========================================== + // ChatGPT / OpenAI + // ========================================== + chatGPT: { + enabled: getEnvBoolean(process.env.CHATGPT_ENABLED), + basePath: process.env.CHATGPT_BASE_PATH, + apiKey: process.env.CHATGPT_APIKEY, + model: process.env.CHATGPT_MODEL, + max_tokens: parseInt(process.env.CHATGPT_MAX_TOKENS), + temperature: parseInt(process.env.CHATGPT_TEMPERATURE), + }, + + // ========================================== + // IP Whitelist + // ========================================== + ipWhitelist: { + enabled: getEnvBoolean(process.env.IP_WHITELIST_ENABLED), + allowed: parseJsonEnv(process.env.IP_WHITELIST_ALLOWED, []), + }, + + // ========================================== + // OIDC - OpenID Connect + // ========================================== + oidc: { + enabled: process.env.OIDC_ENABLED ? getEnvBoolean(process.env.OIDC_ENABLED) : false, + allowRoomCreationForAuthUsers: process.env.OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS + ? getEnvBoolean(process.env.OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS) + : false, + baseUrlDynamic: process.env.OIDC_BASE_URL_DYNAMIC + ? getEnvBoolean(process.env.OIDC_BASE_URL_DYNAMIC) + : false, + config: { + issuerBaseURL: process.env.OIDC_ISSUER_BASE_URL, + clientID: process.env.OIDC_CLIENT_ID, + clientSecret: process.env.OIDC_CLIENT_SECRET, + baseURL: process.env.OIDC_BASE_URL, + secret: process.env.SESSION_SECRET, + authorizationParams: { + response_type: 'code', + scope: 'openid profile email', + }, + authRequired: process.env.OIDC_AUTH_REQUIRED + ? getEnvBoolean(process.env.OIDC_AUTH_REQUIRED) + : false, + auth0Logout: process.env.OIDC_AUTH_LOGOUT + ? getEnvBoolean(process.env.OIDC_AUTH_LOGOUT) + : true, + routes: { + callback: '/auth/callback', + login: false, + logout: '/logout', + }, + }, + }, + + // ========================================== + // Mattermost + // ========================================== + mattermost: { + enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED), + serverUrl: process.env.MATTERMOST_SERVER_URL, + username: process.env.MATTERMOST_USERNAME, + password: process.env.MATTERMOST_PASSWORD, + token: process.env.MATTERMOST_TOKEN, + roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE, + }, + + // ========================================== + // Stats / Analytics + // ========================================== + stats: { + enabled: process.env.STATS_ENABLED ? getEnvBoolean(process.env.STATS_ENABLED) : true, + src: process.env.STATS_SCR || 'https://stats.mirotalk.com/script.js', + id: process.env.STATS_ID || 'c7615aa7-ceec-464a-baba-54cb605d7261', + }, + + // ========================================== + // Email + // ========================================== + email: { + alert: process.env.EMAIL_ALERT === 'true' || false, + host: process.env.EMAIL_HOST, + port: Number(process.env.EMAIL_PORT), + username: process.env.EMAIL_USERNAME, + password: process.env.EMAIL_PASSWORD, + from: process.env.EMAIL_FROM || process.env.EMAIL_USERNAME, + sendTo: process.env.EMAIL_SEND_TO, + https: process.env.HTTPS === 'true' || false, + serverPort: process.env.PORT || 3000, + }, + + // ========================================== + // Branding (UI customizations) + // ========================================== brand: { htmlInjection: true, app: { @@ -206,6 +451,9 @@ module.exports = { whiteboardLockBtn: false, }, }, + // ========================================== + // Webhook + // ========================================== webhook: { enabled: false, // Enable webhook functionality url: 'http://localhost:8888/webhook-endpoint', // Webhook server URL diff --git a/app/src/lib/nodemailer.js b/app/src/lib/nodemailer.js index 80b817fb..7c529fb7 100644 --- a/app/src/lib/nodemailer.js +++ b/app/src/lib/nodemailer.js @@ -4,18 +4,20 @@ const nodemailer = require('nodemailer'); const Logs = require('../logs'); const log = new Logs('NodeMailer'); +const config = require('../config'); + // Email config const emailCfg = { - alert: process.env.EMAIL_ALERT === 'true' || false, - host: process.env.EMAIL_HOST, - port: Number(process.env.EMAIL_PORT), - username: process.env.EMAIL_USERNAME, - password: process.env.EMAIL_PASSWORD, - from: process.env.EMAIL_FROM || process.env.EMAIL_USERNAME, - send_to: process.env.EMAIL_SEND_TO, + alert: config.email.alert, + host: config.email.host, + port: config.email.port, + username: config.email.username, + password: config.email.password, + from: config.email.from, + send_to: config.email.sendTo, // Room join params - https: process.env.HTTPS === 'true' || false, - server_port: process.env.PORT || 3000, + https: config.email.https, + server_port: config.email.serverPort, }; const isTLSPort = emailCfg.port === 465; // 465 is the default TLS/SSL port diff --git a/app/src/server.js b/app/src/server.js index b5e06f47..81075767 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -51,8 +51,6 @@ dependencies: { 'use strict'; // https://www.w3schools.com/js/js_strict.asp -require('dotenv').config(); - const { auth, requiresAuth } = require('express-openid-connect'); const { Server } = require('socket.io'); const httpolyglot = require('httpolyglot'); @@ -74,8 +72,8 @@ const Host = require('./host'); const Logs = require('./logs'); const log = new Logs('server'); -// Custom Brand and buttons -const config = safeRequire('./config'); +// Central configuration (reads .env via dotenv internally) +const config = require('./config'); // Email alerts and notifications const nodemailer = require('./lib/nodemailer'); @@ -84,8 +82,8 @@ const packageJson = require('../../package.json'); // Login attempts limit const rateLimit = require('express-rate-limit'); -const maxAttempts = process.env.HOST_MAX_LOGIN_ATTEMPTS || 5; -const minBlockTime = process.env.HOST_MIN_LOGIN_BLOCK_TIME || 15; // in minutes +const maxAttempts = config.host.maxLoginAttempts; +const minBlockTime = config.host.minLoginBlockTime; // in minutes const loginLimiter = rateLimit({ windowMs: minBlockTime * 60 * 1000, // 15 minutes default max: maxAttempts, @@ -93,8 +91,8 @@ const loginLimiter = rateLimit({ keyGenerator: (req) => req.body?.username || getIP(req), }); -const port = process.env.PORT || 3000; -const host = process.env.HOST || `http://localhost:${port}`; +const port = config.server.port; +const host = config.server.host; const authHost = new Host(); // Authenticated IP by Login @@ -122,34 +120,12 @@ server.on('clientError', (err, socket) => { }); // Trust Proxy -const trustProxy = !!getEnvBoolean(process.env.TRUST_PROXY); +const trustProxy = config.server.trustProxy; // Cors -const cors_origin = process.env.CORS_ORIGIN; -const cors_methods = process.env.CORS_METHODS; - -let corsOrigin = '*'; -let corsMethods = ['GET', 'POST']; - -if (cors_origin && cors_origin !== '*') { - try { - corsOrigin = JSON.parse(cors_origin); - } catch (error) { - log.error('Error parsing CORS_ORIGIN', error.message); - } -} - -if (cors_methods && cors_methods !== '') { - try { - corsMethods = JSON.parse(cors_methods); - } catch (error) { - log.error('Error parsing CORS_METHODS', error.message); - } -} - const corsOptions = { - origin: corsOrigin, - methods: corsMethods, + origin: config.cors.origin, + methods: config.cors.methods, }; /* @@ -164,28 +140,23 @@ const io = new Server({ // console.log(io); // Host protection (disabled by default) -const hostProtected = getEnvBoolean(process.env.HOST_PROTECTED); -const userAuth = getEnvBoolean(process.env.HOST_USER_AUTH); -const hostUsersString = process.env.HOST_USERS || '[{"username": "MiroTalk", "password": "P2P"}]'; -const hostUsers = JSON.parse(hostUsersString); const hostCfg = { - protected: hostProtected, - user_auth: userAuth, - users: hostUsers, - authenticated: !hostProtected, - maxRoomParticipants: parseInt(process.env.ROOM_MAX_PARTICIPANTS) || 1000, - showActiveRooms: getEnvBoolean(process.env.SHOW_ACTIVE_ROOMS) || false, + protected: config.host.protected, + user_auth: config.host.userAuth, + users: config.host.users, + authenticated: !config.host.protected, + maxRoomParticipants: config.host.maxRoomParticipants, + showActiveRooms: config.host.showActiveRooms, }; // JWT config const jwtCfg = { - JWT_KEY: process.env.JWT_KEY || 'mirotalk_jwt_secret', - JWT_EXP: process.env.JWT_EXP || '1h', + JWT_KEY: config.jwt.key, + JWT_EXP: config.jwt.exp, }; // Room presenters -const roomPresentersString = process.env.PRESENTERS || '["MiroTalk P2P"]'; -const roomPresenters = JSON.parse(roomPresentersString); +const roomPresenters = config.presenters; // Swagger config const yaml = require('js-yaml'); @@ -196,30 +167,29 @@ const swaggerDocument = yaml.load(fs.readFileSync(path.join(__dirname, '/../api/ const { v4: uuidV4 } = require('uuid'); const apiBasePath = '/api/v1'; // api endpoint path const api_docs = host + apiBasePath + '/docs'; // api docs -const api_key_secret = process.env.API_KEY_SECRET || 'mirotalkp2p_default_secret'; -const apiDisabledString = process.env.API_DISABLED || '["token", "meetings"]'; -const api_disabled = JSON.parse(apiDisabledString); +const api_key_secret = config.api.keySecret; +const api_disabled = config.api.disabled; // Ngrok config const ngrok = require('@ngrok/ngrok'); -const ngrokEnabled = getEnvBoolean(process.env.NGROK_ENABLED); -const ngrokAuthToken = process.env.NGROK_AUTH_TOKEN; +const ngrokEnabled = config.ngrok.enabled; +const ngrokAuthToken = config.ngrok.authToken; // Handle WebHook const webhook = { - enabled: config?.webhook?.enabled || false, - url: config?.webhook?.url || 'http://localhost:8888/webhook-endpoint', + enabled: config.webhook?.enabled || false, + url: config.webhook?.url || 'http://localhost:8888/webhook-endpoint', }; // Stun (https://bloggeek.me/webrtcglossary/stun/) // Turn (https://bloggeek.me/webrtcglossary/turn/) const iceServers = []; -const stunServerUrl = process.env.STUN_SERVER_URL; -const turnServerUrl = process.env.TURN_SERVER_URL; -const turnServerUsername = process.env.TURN_SERVER_USERNAME; -const turnServerCredential = process.env.TURN_SERVER_CREDENTIAL; -const stunServerEnabled = getEnvBoolean(process.env.STUN_SERVER_ENABLED); -const turnServerEnabled = getEnvBoolean(process.env.TURN_SERVER_ENABLED); +const stunServerUrl = config.webrtc.stun.url; +const turnServerUrl = config.webrtc.turn.url; +const turnServerUsername = config.webrtc.turn.username; +const turnServerCredential = config.webrtc.turn.credential; +const stunServerEnabled = config.webrtc.stun.enabled; +const turnServerEnabled = config.webrtc.turn.enabled; // Stun is mandatory for not internal network if (stunServerEnabled && stunServerUrl) iceServers.push({ urls: stunServerUrl }); // Turn is recommended if direct peer to peer connection is not possible @@ -232,27 +202,27 @@ if (turnServerEnabled && turnServerUrl && turnServerUsername && turnServerCreden const testStunTurn = host + '/icetest'; // IP Lookup -const IPLookupEnabled = getEnvBoolean(process.env.IP_LOOKUP_ENABLED); +const IPLookupEnabled = config.ipLookup.enabled; // Survey URL -const surveyEnabled = getEnvBoolean(process.env.SURVEY_ENABLED); -const surveyURL = process.env.SURVEY_URL || 'https://www.questionpro.com/t/AUs7VZq00L'; +const surveyEnabled = config.survey.enabled; +const surveyURL = config.survey.url; // Redirect URL -const redirectEnabled = getEnvBoolean(process.env.REDIRECT_ENABLED); -const redirectURL = process.env.REDIRECT_URL || '/newcall'; +const redirectEnabled = config.redirect.enabled; +const redirectURL = config.redirect.url; // Sentry config const Sentry = require('@sentry/node'); -const sentryEnabled = getEnvBoolean(process.env.SENTRY_ENABLED); -const sentryDSN = process.env.SENTRY_DSN; -const sentryTracesSampleRate = parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE || '0.0'); +const sentryEnabled = config.sentry.enabled; +const sentryDSN = config.sentry.dsn; +const sentryTracesSampleRate = config.sentry.tracesSampleRate; // Slack API const CryptoJS = require('crypto-js'); const qS = require('qs'); -const slackEnabled = getEnvBoolean(process.env.SLACK_ENABLED); -const slackSigningSecret = process.env.SLACK_SIGNING_SECRET; +const slackEnabled = config.slack.enabled; +const slackSigningSecret = config.slack.signingSecret; // Setup sentry client if (sentryEnabled && typeof sentryDSN === 'string' && sentryDSN.trim()) { @@ -263,9 +233,7 @@ if (sentryEnabled && typeof sentryDSN === 'string' && sentryDSN.trim()) { tracesSampleRate: sentryTracesSampleRate, }); - const logLevels = process.env.SENTRY_LOG_LEVELS - ? process.env.SENTRY_LOG_LEVELS.split(',').map((level) => level.trim()) - : ['error']; + const logLevels = config.sentry.logLevels; const stripAnsi = (str) => (typeof str === 'string' ? str.replace(/\u001b\[[0-9;]*m/g, '') : str); @@ -294,14 +262,7 @@ if (sentryEnabled && typeof sentryDSN === 'string' && sentryDSN.trim()) { // OpenAI/ChatGPT let chatGPT; -const configChatGPT = { - enabled: getEnvBoolean(process.env.CHATGPT_ENABLED), - basePath: process.env.CHATGPT_BASE_PATH, - apiKey: process.env.CHATGPT_APIKEY, - model: process.env.CHATGPT_MODEL, - max_tokens: parseInt(process.env.CHATGPT_MAX_TOKENS), - temperature: parseInt(process.env.CHATGPT_TEMPERATURE), -}; +const configChatGPT = config.chatGPT; if (configChatGPT.enabled) { if (configChatGPT.apiKey) { const { OpenAI } = require('openai'); @@ -316,37 +277,10 @@ if (configChatGPT.enabled) { } // IP Whitelist -const ipWhitelist = { - enabled: getEnvBoolean(process.env.IP_WHITELIST_ENABLED), - allowed: process.env.IP_WHITELIST_ALLOWED ? JSON.parse(process.env.IP_WHITELIST_ALLOWED) : [], -}; +const ipWhitelist = config.ipWhitelist; // OIDC - Open ID Connect -const OIDC = { - enabled: process.env.OIDC_ENABLED ? getEnvBoolean(process.env.OIDC_ENABLED) : false, - allowRoomCreationForAuthUsers: process.env.OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS - ? getEnvBoolean(process.env.OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS) - : false, - baseUrlDynamic: process.env.OIDC_BASE_URL_DYNAMIC ? getEnvBoolean(process.env.OIDC_BASE_URL_DYNAMIC) : false, - config: { - issuerBaseURL: process.env.OIDC_ISSUER_BASE_URL, - clientID: process.env.OIDC_CLIENT_ID, - clientSecret: process.env.OIDC_CLIENT_SECRET, - baseURL: process.env.OIDC_BASE_URL, - secret: process.env.SESSION_SECRET, - authorizationParams: { - response_type: 'code', - scope: 'openid profile email', - }, - authRequired: process.env.OIDC_AUTH_REQUIRED ? getEnvBoolean(process.env.OIDC_AUTH_REQUIRED) : false, // Set to true if authentication is required for all routes - auth0Logout: process.env.OIDC_AUTH_LOGOUT ? getEnvBoolean(process.env.OIDC_AUTH_LOGOUT) : true, // Set to true to enable logout with Auth0 - routes: { - callback: '/auth/callback', // Indicating the endpoint where your application will handle the callback from the authentication provider after a user has been authenticated. - login: false, // Dedicated route in your application for user login. - logout: '/logout', // Indicating the endpoint where your application will handle user logout requests. - }, - }, -}; +const OIDC = config.oidc; // Custom middleware function for OIDC authentication function OIDCAuth(req, res, next) { @@ -390,23 +324,19 @@ function OIDCAuth(req, res, next) { // Mattermost config const mattermostCfg = { - enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED), - server_url: process.env.MATTERMOST_SERVER_URL, - username: process.env.MATTERMOST_USERNAME, - password: process.env.MATTERMOST_PASSWORD, - token: process.env.MATTERMOST_TOKEN, - roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE, - encryptionKey: process.env.JWT_KEY, + enabled: config.mattermost.enabled, + server_url: config.mattermost.serverUrl, + username: config.mattermost.username, + password: config.mattermost.password, + token: config.mattermost.token, + roomTokenExpire: config.mattermost.roomTokenExpire, + encryptionKey: config.jwt.key, security: hostCfg.protected || OIDC.enabled, api_disabled: api_disabled, }; // stats configuration -const statsData = { - enabled: process.env.STATS_ENABLED ? getEnvBoolean(process.env.STATS_ENABLED) : true, - src: process.env.STATS_SCR || 'https://stats.mirotalk.com/script.js', - id: process.env.STATS_ID || 'c7615aa7-ceec-464a-baba-54cb605d7261', -}; +const statsData = config.stats; // directory const dir = { @@ -427,11 +357,11 @@ const views = { }; // Branding configuration -const brandHtmlInjection = config?.brand?.htmlInjection ?? true; +const brandHtmlInjection = config.brand?.htmlInjection ?? true; // File to cache and inject custom HTML data like OG tags and any other elements. const filesPath = [views.landing, views.newCall, views.client, views.login, views.activeRooms, views.customizeRoom]; -const htmlInjector = new HtmlInjector(filesPath, config?.brand || null); +const htmlInjector = new HtmlInjector(filesPath, config.brand || null); const channels = {}; // collect channels const sockets = {}; // collect sockets @@ -644,7 +574,7 @@ app.post('/isRoomActive', (req, res) => { app.post('/isWidgetRoomActive', (req, res) => { const { roomId } = checkXSS(req.body); const roomWidgetActive = - roomId && roomId === config?.brand?.widget?.roomId && Object.prototype.hasOwnProperty.call(peers, roomId); + roomId && roomId === config.brand?.widget?.roomId && Object.prototype.hasOwnProperty.call(peers, roomId); log.debug('isWidgetRoomActive', { roomId, roomWidgetActive }); res.status(200).json({ message: roomWidgetActive }); }); @@ -829,12 +759,12 @@ app.post('/login', loginLimiter, (req, res) => { // UI buttons configuration app.get('/buttons', (req, res) => { - res.status(200).json({ message: config && config.buttons ? config.buttons : false }); + res.status(200).json({ message: config.buttons ? config.buttons : false }); }); // UI brand configuration app.get('/brand', (req, res) => { - res.status(200).json({ message: config && config.brand && brandHtmlInjection ? config.brand : false }); + res.status(200).json({ message: config.brand && brandHtmlInjection ? config.brand : false }); }); // Join roomId redirect to /join?room=roomId @@ -1154,10 +1084,10 @@ function getServerConfig(tunnel = false) { redirect: redirectEnabled ? redirectURL : false, // Widget Configuration - widget: config?.brand?.widget?.enabled ? config.brand.widget : false, + widget: config.brand?.widget?.enabled ? config.brand.widget : false, // Versions and environment information - environment: process.env.NODE_ENV || 'development', + environment: config.server.environment, app_version: packageJson.version, node_version: process.versions.node, }; @@ -2133,17 +2063,6 @@ io.sockets.on('connect', async (socket) => { } }); // end [sockets.on-connect] -/** - * Get Env as boolean - * @param {string} key - * @param {boolean} force_true_if_undefined - * @returns boolean - */ -function getEnvBoolean(key, force_true_if_undefined = false) { - if (key == undefined && force_true_if_undefined) return true; - return key == 'true' ? true : false; -} - /** * Check if valid filename * @param {string} fileName @@ -2427,21 +2346,6 @@ function removeIP(socket) { } } -/** - * Load modules if exists - * @param {string} filePath - * @returns - */ -function safeRequire(filePath) { - let data = null; - try { - data = require(filePath); - } catch (error) { - log.error(error); - } - return data; -} - /** * Cleanup HTML injector when the application is shutting down */ diff --git a/package.json b/package.json index b8d30fcf..16bdc937 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "A free WebRTC browser-based video call", "main": "server.js", "scripts": { + "prestart": "test -f app/src/config.js || cp app/src/config.template.js app/src/config.js", "start": "node app/src/server.js", "start-dev": "nodemon app/src/server.js", "test": "mocha tests/*.js", diff --git a/tests/test-api.js b/tests/test-api.js index c28934cb..a65ba7b4 100644 --- a/tests/test-api.js +++ b/tests/test-api.js @@ -9,6 +9,7 @@ const proxyquire = require('proxyquire'); const jwt = require('jsonwebtoken'); const CryptoJS = require('crypto-js'); const ServerApi = require('../app/src/api'); +const config = require('../app/src/config'); describe('test-api', () => { let serverApi; @@ -184,12 +185,12 @@ describe('test-api', () => { result.should.equal('jwtToken'); signStub - .calledWith({ data: 'encryptedPayload' }, 'mirotalk_jwt_secret', { expiresIn: '1h' }) + .calledWith({ data: 'encryptedPayload' }, config.jwt.key, { expiresIn: '1h' }) .should.be.true(); encryptStub .calledWith( JSON.stringify({ username: 'user', password: 'pass', presenter: 'true' }), - 'mirotalk_jwt_secret' + config.jwt.key ) .should.be.true();