From 6172cf22bfa87b1ce7a40ff99c5fb6303bdb260d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:44:02 +0100 Subject: [PATCH] Initial setup --- apps/api/src/app/app.module.ts | 5 +-- apps/api/src/main.ts | 6 +++- .../middlewares/bull-board-auth.middleware.ts | 7 ++--- .../admin-jobs/admin-jobs.component.ts | 6 ++-- libs/common/src/lib/config.ts | 8 +++++ package-lock.json | 31 +++++++++++++++++++ package.json | 2 ++ 7 files changed, 55 insertions(+), 10 deletions(-) diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 9c428e6f1..8bf09dc07 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -11,6 +11,7 @@ import { PropertyModule } from '@ghostfolio/api/services/property/property.modul import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module'; import { + BULL_BOARD_ROUTE, DEFAULT_LANGUAGE_CODE, SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; @@ -90,7 +91,7 @@ import { UserModule } from './user/user.module'; } }, middleware: BullBoardAuthMiddleware, - route: '/admin/queues' + route: BULL_BOARD_ROUTE }), BullModule.forRoot({ redis: { @@ -128,8 +129,8 @@ import { UserModule } from './user/user.module'; ScheduleModule.forRoot(), ServeStaticModule.forRoot({ exclude: [ + `${BULL_BOARD_ROUTE}/*wildcard`, '/.well-known/*wildcard', - '/admin/queues/*wildcard', '/api/*wildcard', '/sitemap.xml' ], diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 781639a3f..f08a09a83 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,4 +1,5 @@ import { + BULL_BOARD_ROUTE, DEFAULT_HOST, DEFAULT_PORT, STORYBOOK_PATH, @@ -14,6 +15,7 @@ import { import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import type { NestExpressApplication } from '@nestjs/platform-express'; +import cookieParser from 'cookie-parser'; import { NextFunction, Request, Response } from 'express'; import helmet from 'helmet'; @@ -46,7 +48,7 @@ async function bootstrap() { }); app.setGlobalPrefix('api', { exclude: [ - 'admin/queues{/*wildcard}', + `${BULL_BOARD_ROUTE.substring(1)}{/*wildcard}`, 'sitemap.xml', ...SUPPORTED_LANGUAGE_CODES.map((languageCode) => { // Exclude language-specific routes with an optional wildcard @@ -66,6 +68,8 @@ async function bootstrap() { // Support 10mb csv/json files for importing activities app.useBodyParser('json', { limit: '10mb' }); + app.use(cookieParser()); + if (configService.get('ENABLE_FEATURE_SUBSCRIPTION') === 'true') { app.use((req: Request, res: Response, next: NextFunction) => { if (req.path.startsWith(STORYBOOK_PATH)) { diff --git a/apps/api/src/middlewares/bull-board-auth.middleware.ts b/apps/api/src/middlewares/bull-board-auth.middleware.ts index d061f78d2..8492e41fb 100644 --- a/apps/api/src/middlewares/bull-board-auth.middleware.ts +++ b/apps/api/src/middlewares/bull-board-auth.middleware.ts @@ -1,3 +1,4 @@ +import { BULL_BOARD_COOKIE_NAME } from '@ghostfolio/common/config'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Injectable, NestMiddleware } from '@nestjs/common'; @@ -8,11 +9,7 @@ import passport from 'passport'; @Injectable() export class BullBoardAuthMiddleware implements NestMiddleware { public use(req: Request, res: Response, next: NextFunction) { - const token = req.headers.cookie - ?.split(';') - .map((c) => c.trim()) - .find((c) => c.startsWith('bull_board_token=')) - ?.split('=')[1]; + const token = req.cookies?.[BULL_BOARD_COOKIE_NAME]; if (token) { req.headers.authorization = `Bearer ${token}`; diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index 266313a89..d3edee8d6 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -1,6 +1,8 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { + BULL_BOARD_COOKIE_NAME, + BULL_BOARD_ROUTE, DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_LOW, DATA_GATHERING_QUEUE_PRIORITY_MEDIUM, @@ -184,9 +186,9 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public onOpenBullBoard() { const token = this.tokenStorageService.getToken(); - document.cookie = `bull_board_token=${token}; path=/admin/queues; SameSite=Strict`; + document.cookie = `${BULL_BOARD_COOKIE_NAME}=${token}; path=${BULL_BOARD_ROUTE}; SameSite=Strict`; - window.open('/admin/queues', '_blank'); + window.open(BULL_BOARD_ROUTE, '_blank'); } public onViewData(aData: AdminJobs['jobs'][0]['data']) { diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 5da0e0122..08fa2f030 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -51,6 +51,14 @@ export const ASSET_CLASS_MAPPING = new Map([ [AssetClass.REAL_ESTATE, []] ]); +export const BULL_BOARD_COOKIE_NAME = 'bull_board_token'; + +/** + * WARNING: This route is mirrored in `apps/client/proxy.conf.json`. + * If you update this value, you must also update the proxy configuration. + */ +export const BULL_BOARD_ROUTE = '/admin/queues'; + export const CACHE_TTL_NO_CACHE = 1; export const CACHE_TTL_INFINITE = 0; diff --git a/package-lock.json b/package-lock.json index d6faf84e4..86fc6d778 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "class-transformer": "0.5.1", "class-validator": "0.14.3", "color": "5.0.3", + "cookie-parser": "1.4.7", "countries-and-timezones": "3.8.0", "countries-list": "3.2.2", "countup.js": "2.9.0", @@ -126,6 +127,7 @@ "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "5.2.2", "@types/big.js": "6.2.2", + "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", @@ -12738,6 +12740,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -16970,6 +16982,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", diff --git a/package.json b/package.json index ac08ea557..cb47ed9ae 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "class-transformer": "0.5.1", "class-validator": "0.14.3", "color": "5.0.3", + "cookie-parser": "1.4.7", "countries-and-timezones": "3.8.0", "countries-list": "3.2.2", "countup.js": "2.9.0", @@ -171,6 +172,7 @@ "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "5.2.2", "@types/big.js": "6.2.2", + "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0",