From 69088b93a61afe93153e23b178360b95d613c76e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 27 May 2022 11:21:47 +0200 Subject: [PATCH] Feature/add value redaction as interceptor (#960) * Add value redaction as interceptor * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/order/order.controller.ts | 2 + .../src/app/portfolio/portfolio.controller.ts | 2 + .../redact-values-in-response.interceptor.ts | 50 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 apps/api/src/interceptors/redact-values-in-response.interceptor.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd5e5381..1094ddec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the benchmarks of the markets overview by the current market condition (bear and bull market) - Extended the twitter bot service by benchmarks +- Added value redaction for the impersonation mode in the API response as an interceptor ### Changed diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 73c546d83..e61c57ef7 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -1,5 +1,6 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper'; +import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; @@ -62,6 +63,7 @@ export class OrderController { @Get() @UseGuards(AuthGuard('jwt')) + @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getAllOrders( @Headers('impersonation-id') impersonationId diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index f0e75e731..606ce658b 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -4,6 +4,7 @@ import { hasNotDefinedValuesInObject, nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper'; +import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; @@ -106,6 +107,7 @@ export class PortfolioController { @Get('details') @UseGuards(AuthGuard('jwt')) + @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getDetails( @Headers('impersonation-id') impersonationId: string, diff --git a/apps/api/src/interceptors/redact-values-in-response.interceptor.ts b/apps/api/src/interceptors/redact-values-in-response.interceptor.ts new file mode 100644 index 000000000..b5889328d --- /dev/null +++ b/apps/api/src/interceptors/redact-values-in-response.interceptor.ts @@ -0,0 +1,50 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class RedactValuesInResponseInterceptor + implements NestInterceptor +{ + public constructor() {} + + public intercept( + context: ExecutionContext, + next: CallHandler + ): Observable { + return next.handle().pipe( + map((data: any) => { + const request = context.switchToHttp().getRequest(); + const hasImpersonationId = !!request.headers?.['impersonation-id']; + + if (hasImpersonationId) { + if (data.accounts) { + for (const accountId of Object.keys(data.accounts)) { + if (data.accounts[accountId]?.balance !== undefined) { + data.accounts[accountId].balance = null; + } + } + } + + if (data.activities) { + data.activities = data.activities.map((activity: Activity) => { + if (activity.Account?.balance !== undefined) { + activity.Account.balance = null; + } + + return activity; + }); + } + } + + return data; + }) + ); + } +}