Browse Source

Harden validation of assetClasses and dataSource in endpoints with filters

pull/7809/head
Thomas Kaul 1 month ago
parent
commit
8fae03f88a
  1. 5
      apps/api/src/app/endpoints/ai/ai.controller.ts
  2. 2
      apps/api/src/app/endpoints/ai/ai.module.ts
  3. 4
      apps/api/src/app/endpoints/mcp/mcp.controller.ts
  4. 16
      apps/api/src/dtos/filter.dto.ts
  5. 6
      apps/api/src/services/api/api.service.ts

5
apps/api/src/app/endpoints/ai/ai.controller.ts

@ -1,6 +1,7 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { FilterDto } from '@ghostfolio/api/dtos/filter.dto';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
@ -12,7 +13,8 @@ import {
Inject,
Param,
Query,
UseGuards
UseGuards,
UseInterceptors
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@ -30,6 +32,7 @@ export class AiController {
@Get('prompt/:mode')
@HasPermission(permissions.readAiPrompt)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getPrompt(
@Param('mode') mode: AiPromptMode,
@Query()

2
apps/api/src/app/endpoints/ai/ai.module.ts

@ -7,6 +7,7 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
@ -46,6 +47,7 @@ import { AiService } from './ai.service';
RedisCacheModule,
SymbolProfileModule,
TagModule,
TransformDataSourceInRequestModule,
UserModule
],
providers: [

4
apps/api/src/app/endpoints/mcp/mcp.controller.ts

@ -176,7 +176,7 @@ export class GhostfolioMcpController {
) {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts: accountIds?.join(','),
filterByAssetClasses: assetClasses?.join(','),
filterByAssetClasses: assetClasses,
filterByDataSource: holding?.dataSource,
filterBySymbol: holding?.symbol
});
@ -222,7 +222,7 @@ export class GhostfolioMcpController {
}
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAssetClasses: assetClasses?.join(','),
filterByAssetClasses: assetClasses,
filterByDataSource: holding?.dataSource,
filterBySymbol: holding?.symbol
});

16
apps/api/src/dtos/filter.dto.ts

@ -1,17 +1,23 @@
import { IsOptional, IsString } from 'class-validator';
import { AssetClass, DataSource } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import { IsEnum, IsOptional, IsString } from 'class-validator';
import { isString } from 'lodash';
export class FilterDto {
@IsOptional()
@IsString()
accounts?: string;
@IsEnum(AssetClass, { each: true })
@IsOptional()
@IsString()
assetClasses?: string;
@Transform(({ value }: TransformFnParams) => {
return isString(value) ? value.split(',') : value;
})
assetClasses?: AssetClass[];
@IsEnum(DataSource)
@IsOptional()
@IsString()
dataSource?: string;
dataSource?: DataSource;
@IsOptional()
@IsString()

6
apps/api/src/services/api/api.service.ts

@ -15,7 +15,7 @@ export class ApiService {
filterByTags
}: {
filterByAccounts?: string;
filterByAssetClasses?: string;
filterByAssetClasses?: string[];
filterByAssetSubClasses?: string;
filterByDataSource?: string;
filterByHoldingType?: string;
@ -24,7 +24,7 @@ export class ApiService {
filterByTags?: string;
}): Filter[] {
const accountIds = filterByAccounts?.split(',') ?? [];
const assetClasses = filterByAssetClasses?.split(',') ?? [];
const assetClasses = filterByAssetClasses ?? [];
const assetSubClasses = filterByAssetSubClasses?.split(',') ?? [];
const dataSource = filterByDataSource;
const holdingType = filterByHoldingType;
@ -97,7 +97,7 @@ export class ApiService {
}): Filter[] {
return this.buildFiltersFromQueryParams({
filterByAccounts: userSettings?.['filters.accounts']?.[0],
filterByAssetClasses: userSettings?.['filters.assetClasses']?.[0],
filterByAssetClasses: userSettings?.['filters.assetClasses'],
filterByDataSource: userSettings?.['filters.dataSource'],
filterBySymbol: userSettings?.['filters.symbol'],
filterByTags: userSettings?.['filters.tags']?.[0]

Loading…
Cancel
Save