From fe2bd6eea886ea2466fe7c45d2c608ae30405b18 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 28 Sep 2021 21:37:01 +0200 Subject: [PATCH] Feature/protect endpoints (#396) * Protect endpoints * Update changelog --- CHANGELOG.md | 4 ++ .../src/app/portfolio/portfolio.controller.ts | 38 +++++++++++++++++-- .../src/app/core/http-response.interceptor.ts | 18 ++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b4030ae..a5aa3c454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a protection for endpoints (subscriptions) + ### Changed - Reformatted the exchange rates table in the admin control panel diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 97dd4d008..24811a1b3 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -3,6 +3,7 @@ import { hasNotDefinedValuesInObject, nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { PortfolioDetails, @@ -38,6 +39,7 @@ import { PortfolioService } from './portfolio.service'; @Controller('portfolio') export class PortfolioController { public constructor( + private readonly configurationService: ConfigurationService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser, @@ -47,8 +49,17 @@ export class PortfolioController { @Get('investments') @UseGuards(AuthGuard('jwt')) public async findAll( - @Headers('impersonation-id') impersonationId + @Headers('impersonation-id') impersonationId, + @Res() res: Response ): Promise { + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + this.request.user.subscription.type === 'Basic' + ) { + res.status(StatusCodes.FORBIDDEN); + return res.json([]); + } + let investments = await this.portfolioService.getInvestments( impersonationId ); @@ -68,7 +79,7 @@ export class PortfolioController { })); } - return investments; + return res.json(investments); } @Get('chart') @@ -125,6 +136,14 @@ export class PortfolioController { @Query('range') range, @Res() res: Response ): Promise { + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + this.request.user.subscription.type === 'Basic' + ) { + res.status(StatusCodes.FORBIDDEN); + return res.json({ accounts: {}, holdings: {} }); + } + const { accounts, holdings, hasErrors } = await this.portfolioService.getDetails(impersonationId, range); @@ -295,8 +314,19 @@ export class PortfolioController { @Get('report') @UseGuards(AuthGuard('jwt')) public async getReport( - @Headers('impersonation-id') impersonationId + @Headers('impersonation-id') impersonationId, + @Res() res: Response ): Promise { - return await this.portfolioService.getReport(impersonationId); + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + this.request.user.subscription.type === 'Basic' + ) { + res.status(StatusCodes.FORBIDDEN); + return res.json({ rules: [] }); + } + + return ( + res.json(await this.portfolioService.getReport(impersonationId)) + ); } } diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index 50489d10a..ed9e7110c 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -61,7 +61,23 @@ export class HttpResponseInterceptor implements HttpInterceptor { return event; }), catchError((error: HttpErrorResponse) => { - if (error.status === StatusCodes.INTERNAL_SERVER_ERROR) { + if (error.status === StatusCodes.FORBIDDEN) { + if (!this.snackBarRef) { + this.snackBarRef = this.snackBar.open( + 'This feature requires a subscription.', + 'Upgrade Plan', + { duration: 6000 } + ); + + this.snackBarRef.afterDismissed().subscribe(() => { + this.snackBarRef = undefined; + }); + + this.snackBarRef.onAction().subscribe(() => { + this.router.navigate(['/pricing']); + }); + } + } else if (error.status === StatusCodes.INTERNAL_SERVER_ERROR) { if (!this.snackBarRef) { this.snackBarRef = this.snackBar.open( 'Oops! Something went wrong. Please try again later.',