From 2694a4f8023a4899e7cfff2fa77ea98a69c27455 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Thu, 23 Jan 2025 16:31:49 +0100 Subject: [PATCH] fix: load origins from settings --- api/src/app.instance.ts | 24 +++++++++++++++ api/src/main.ts | 10 +++++-- api/src/setting/services/setting.service.ts | 26 +++++++++------- api/src/utils/constants/cache.ts | 4 +-- api/src/websocket/utils/gateway-options.ts | 33 ++++++++++++++------- 5 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 api/src/app.instance.ts diff --git a/api/src/app.instance.ts b/api/src/app.instance.ts new file mode 100644 index 00000000..c83df405 --- /dev/null +++ b/api/src/app.instance.ts @@ -0,0 +1,24 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { INestApplication } from '@nestjs/common'; + +export class AppInstance { + private static app: INestApplication; + + static setApp(app: INestApplication) { + this.app = app; + } + + static getApp(): INestApplication { + if (!this.app) { + throw new Error('App instance has not been set yet.'); + } + return this.app; + } +} diff --git a/api/src/main.ts b/api/src/main.ts index d2e2a4cd..281ee174 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -18,6 +18,7 @@ moduleAlias.addAliases({ '@': __dirname, }); +import { AppInstance } from './app.instance'; import { HexabotModule } from './app.module'; import { config } from './config'; import { LoggerService } from './logger/logger.service'; @@ -36,6 +37,9 @@ async function bootstrap() { bodyParser: false, }); + // Set the global app instance + AppInstance.setApp(app); + const rawBodyBuffer = (req, res, buf, encoding) => { if (buf?.length) { req.rawBody = buf.toString(encoding || 'utf8'); @@ -45,10 +49,10 @@ async function bootstrap() { app.use(bodyParser.json({ verify: rawBodyBuffer })); const settingService = app.get(SettingService); - const allowedDomains = await settingService.getAllowedDomains(); + const allowedOrigins = await settingService.getAllowedOrigins(); app.enableCors({ origin: (origin, callback) => { - if (!origin || allowedDomains.has(origin)) { + if (!origin || allowedOrigins.has(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); diff --git a/api/src/setting/services/setting.service.ts b/api/src/setting/services/setting.service.ts index 3219dc31..57ba1ba6 100644 --- a/api/src/setting/services/setting.service.ts +++ b/api/src/setting/services/setting.service.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -15,7 +15,7 @@ import { config } from '@/config'; import { Config } from '@/config/types'; import { LoggerService } from '@/logger/logger.service'; import { - ALLOWED_DOMAINS_CACHE_KEY, + ALLOWED_ORIGINS_CACHE_KEY, SETTING_CACHE_KEY, } from '@/utils/constants/cache'; import { Cacheable } from '@/utils/decorators/cacheable.decorator'; @@ -113,7 +113,7 @@ export class SettingService extends BaseService { */ async clearCache() { this.cacheManager.del(SETTING_CACHE_KEY); - this.cacheManager.del(ALLOWED_DOMAINS_CACHE_KEY); + this.cacheManager.del(ALLOWED_ORIGINS_CACHE_KEY); } /** @@ -126,20 +126,24 @@ export class SettingService extends BaseService { } /** - * Retrieves allowed_domains from the cache if available, or loads them from the - * repository and caches the result. + * Retrieves a set of unique allowed origins for CORS configuration. * - * @returns A promise that resolves to a Set of`allowed_domains` string. + * This method combines all `allowed_domains` settings, + * splits their values (comma-separated), and removes duplicates to produce a + * whitelist of origins. The result is cached for better performance using the + * `Cacheable` decorator with the key `ALLOWED_ORIGINS_CACHE_KEY`. + * + * @returns A promise that resolves to a set of allowed origins */ - @Cacheable(ALLOWED_DOMAINS_CACHE_KEY) - async getAllowedDomains() { - // combines all allowed_doamins and whitelist them for cors + @Cacheable(ALLOWED_ORIGINS_CACHE_KEY) + async getAllowedOrigins() { const settings = await this.find({ label: 'allowed_domains' }); - const whiteListedOrigins = new Set( + const uniqueOrigins = new Set( settings.flatMap((setting) => setting.value.split(',')), ); - return whiteListedOrigins; + + return uniqueOrigins; } /** diff --git a/api/src/utils/constants/cache.ts b/api/src/utils/constants/cache.ts index 1f4467cc..ccbf392a 100644 --- a/api/src/utils/constants/cache.ts +++ b/api/src/utils/constants/cache.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -17,4 +17,4 @@ export const LANGUAGES_CACHE_KEY = 'languages'; export const DEFAULT_LANGUAGE_CACHE_KEY = 'default_language'; -export const ALLOWED_DOMAINS_CACHE_KEY = 'allowed-domains'; +export const ALLOWED_ORIGINS_CACHE_KEY = 'allowed_origins'; diff --git a/api/src/websocket/utils/gateway-options.ts b/api/src/websocket/utils/gateway-options.ts index 1b4e9ce3..4d2795c8 100644 --- a/api/src/websocket/utils/gateway-options.ts +++ b/api/src/websocket/utils/gateway-options.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -10,7 +10,9 @@ import util from 'util'; import type { ServerOptions } from 'socket.io'; +import { AppInstance } from '@/app.instance'; import { config } from '@/config'; +import { SettingService } from '@/setting/services/setting.service'; export const buildWebSocketGatewayOptions = (): Partial => { const opts: Partial = { @@ -53,16 +55,25 @@ export const buildWebSocketGatewayOptions = (): Partial => { ...(config.sockets.onlyAllowOrigins && { cors: { origin: (origin, cb) => { - if (origin && config.sockets.onlyAllowOrigins.includes(origin)) { - cb(null, true); - } else { - // eslint-disable-next-line no-console - console.log( - `A socket was rejected via the config.sockets.onlyAllowOrigins array.\n` + - `It attempted to connect with origin: ${origin}`, - ); - cb(new Error('Origin not allowed'), false); - } + // Retrieve the allowed origins from the settings + const app = AppInstance.getApp(); + const settingService = app.get(SettingService); + + settingService + .getAllowedOrigins() + .then((allowedOrigins) => { + if (origin && allowedOrigins.has(origin)) { + cb(null, true); + } else { + // eslint-disable-next-line no-console + console.log( + `A socket was rejected via the config.sockets.onlyAllowOrigins array.\n` + + `It attempted to connect with origin: ${origin}`, + ); + cb(new Error('Origin not allowed'), false); + } + }) + .catch(cb); }, }, }),