From 20afbdc20251ab61eece0b23737db9a3bce95e9b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:41:03 +0200 Subject: [PATCH] Task/restrict get symbol data endpoint to authenticated users (#7372) * Restrict symbol data endpoint to authenticated users * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/info/info.module.ts | 2 + apps/api/src/app/info/info.service.ts | 28 ++++++----- apps/api/src/app/symbol/symbol.controller.ts | 1 + .../home-market/home-market.component.ts | 47 +++---------------- .../components/home-market/home-market.html | 21 +-------- .../components/home-market/home-market.scss | 4 -- .../src/lib/interfaces/info-item.interface.ts | 2 +- 8 files changed, 30 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217aba06a..7a4a3bc17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Restricted the symbol data endpoint (`GET /api/v1/symbol/:dataSource/:symbol`) to authenticated users - Removed the deprecated `auth` endpoint of the login with _Security Token_ (`GET`) - Simplified the `getHistorical()` function response in the data provider interface diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts index e33c5e0c2..06b724909 100644 --- a/apps/api/src/app/info/info.module.ts +++ b/apps/api/src/app/info/info.module.ts @@ -7,6 +7,7 @@ import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.mo import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; +import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; @@ -29,6 +30,7 @@ import { InfoService } from './info.service'; secret: process.env.JWT_SECRET_KEY, signOptions: { expiresIn: '30 days' } }), + MarketDataModule, PlatformModule, PropertyModule, RedisCacheModule, diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 10836f7b8..cb7d24bcb 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -1,14 +1,15 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; -import { encodeDataSource } from '@ghostfolio/api/helper/data-source.helper'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { DEFAULT_CURRENCY, + ghostfolioFearAndGreedIndexSymbolStocks, PROPERTY_COUNTRIES_OF_SUBSCRIBERS, PROPERTY_DEMO_USER_ID, PROPERTY_DOCKER_HUB_PULLS, @@ -23,6 +24,7 @@ import { permissions } from '@ghostfolio/common/permissions'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { MarketData } from '@prisma/client'; import { subDays } from 'date-fns'; import { isNil } from 'lodash'; @@ -36,6 +38,7 @@ export class InfoService { private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly jwtService: JwtService, + private readonly marketDataService: MarketDataService, private readonly propertyService: PropertyService, private readonly redisCacheService: RedisCacheService, private readonly subscriptionService: SubscriptionService, @@ -45,6 +48,7 @@ export class InfoService { public async get(): Promise { const info: Partial = {}; let isReadOnlyMode: boolean; + let latestFearAndGreedStocksMarketDataPromise: Promise; const globalPermissions: string[] = []; @@ -61,16 +65,12 @@ export class InfoService { } if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { - const fearAndGreedIndexDataSource = - this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(); - - if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { - info.fearAndGreedDataSource = encodeDataSource( - fearAndGreedIndexDataSource - ); - } else { - info.fearAndGreedDataSource = fearAndGreedIndexDataSource; - } + latestFearAndGreedStocksMarketDataPromise = + this.marketDataService.getLatest({ + dataSource: + this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(), + symbol: ghostfolioFearAndGreedIndexSymbolStocks + }); globalPermissions.push(permissions.enableFearAndGreedIndex); } @@ -102,12 +102,14 @@ export class InfoService { benchmarks, demoAuthToken, isUserSignupEnabled, + latestFearAndGreedStocksMarketData, statistics, subscriptionOffer ] = await Promise.all([ this.benchmarkService.getBenchmarkAssetProfiles(), this.getDemoAuthToken(), this.propertyService.isUserSignupEnabled(), + latestFearAndGreedStocksMarketDataPromise, this.getStatistics(), this.subscriptionService.getSubscriptionOffer({ key: 'default' }) ]); @@ -125,7 +127,9 @@ export class InfoService { statistics, subscriptionOffer, baseCurrency: DEFAULT_CURRENCY, - currencies: this.exchangeRateDataService.getCurrencies() + currencies: this.exchangeRateDataService.getCurrencies(), + fearAndGreedStocksMarketPrice: + latestFearAndGreedStocksMarketData?.marketPrice }; } diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index d94ffb4dc..a1351dbed 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -65,6 +65,7 @@ export class SymbolController { * Must be after /lookup */ @Get(':dataSource/:symbol') + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getSymbolData( diff --git a/apps/client/src/app/components/home-market/home-market.component.ts b/apps/client/src/app/components/home-market/home-market.component.ts index 6bf99b31d..0eec3f2d9 100644 --- a/apps/client/src/app/components/home-market/home-market.component.ts +++ b/apps/client/src/app/components/home-market/home-market.component.ts @@ -1,16 +1,8 @@ import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { ghostfolioFearAndGreedIndexSymbolStocks } from '@ghostfolio/common/config'; -import { resetHours } from '@ghostfolio/common/helper'; -import { - Benchmark, - HistoricalDataItem, - InfoItem, - User -} from '@ghostfolio/common/interfaces'; +import { Benchmark, InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; -import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { DataService } from '@ghostfolio/ui/services'; import { @@ -29,11 +21,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - GfBenchmarkComponent, - GfFearAndGreedIndexComponent, - GfLineChartComponent - ], + imports: [GfBenchmarkComponent, GfFearAndGreedIndexComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-home-market', styleUrls: ['./home-market.scss'], @@ -41,15 +29,13 @@ import { DeviceDetectorService } from 'ngx-device-detector'; }) export class GfHomeMarketComponent implements OnInit { protected readonly benchmarks = signal([]); + protected readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); - protected readonly fearAndGreedIndex = signal(undefined); - protected readonly fearLabel = $localize`Fear`; - protected readonly greedLabel = $localize`Greed`; + + protected fearAndGreedIndex: number | undefined; protected hasPermissionToAccessFearAndGreedIndex: boolean; - protected readonly historicalDataItems = signal([]); - protected readonly numberOfDays = 365; protected user: User; private readonly info: InfoItem; @@ -80,27 +66,8 @@ export class GfHomeMarketComponent implements OnInit { permissions.enableFearAndGreedIndex ); - if ( - this.hasPermissionToAccessFearAndGreedIndex && - this.info.fearAndGreedDataSource - ) { - this.dataService - .fetchSymbolItem({ - dataSource: this.info.fearAndGreedDataSource, - includeHistoricalData: this.numberOfDays, - symbol: ghostfolioFearAndGreedIndexSymbolStocks - }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ historicalData, marketPrice }) => { - this.fearAndGreedIndex.set(marketPrice); - this.historicalDataItems.set([ - ...historicalData, - { - date: resetHours(new Date()).toISOString(), - value: marketPrice - } - ]); - }); + if (this.hasPermissionToAccessFearAndGreedIndex) { + this.fearAndGreedIndex = this.info.fearAndGreedStocksMarketPrice; } this.dataService diff --git a/apps/client/src/app/components/home-market/home-market.html b/apps/client/src/app/components/home-market/home-market.html index a782526ee..b1e21df95 100644 --- a/apps/client/src/app/components/home-market/home-market.html +++ b/apps/client/src/app/components/home-market/home-market.html @@ -1,28 +1,11 @@

Markets

@if (hasPermissionToAccessFearAndGreedIndex) { -
+
-
- Last {{ numberOfDays }} Days -
-
diff --git a/apps/client/src/app/components/home-market/home-market.scss b/apps/client/src/app/components/home-market/home-market.scss index 5b523160d..5d4e87f30 100644 --- a/apps/client/src/app/components/home-market/home-market.scss +++ b/apps/client/src/app/components/home-market/home-market.scss @@ -1,7 +1,3 @@ :host { display: block; - - gf-line-chart { - aspect-ratio: 16 / 9; - } } diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index 01897c066..96db7f9b0 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -9,7 +9,7 @@ export interface InfoItem { countriesOfSubscribers?: string[]; currencies: string[]; demoAuthToken: string; - fearAndGreedDataSource?: string; + fearAndGreedStocksMarketPrice?: number; globalPermissions: string[]; isDataGatheringEnabled?: string; isReadOnlyMode?: boolean;