From 3dfd3486fb4924d71f0e3b8f01d2690b1c972667 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:03:25 +0200 Subject: [PATCH] Task/harden validation in Stripe checkout session endpoint (#7824) * Harden validation * Add rate limiting --- .../app/subscription/subscription.controller.ts | 5 +++-- .../src/app/subscription/subscription.service.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts index 0cfc4d90e..778e32243 100644 --- a/apps/api/src/app/subscription/subscription.controller.ts +++ b/apps/api/src/app/subscription/subscription.controller.ts @@ -1,4 +1,5 @@ import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator'; +import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; @@ -46,7 +47,7 @@ export class SubscriptionController { @Post('redeem-coupon') @HttpCode(StatusCodes.OK) - @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseGuards(AuthGuard('jwt'), CustomThrottlerGuard, HasPermissionGuard) public async redeemCoupon(@Body() { couponCode }: { couponCode: string }) { if (!this.request.user) { throw new HttpException( @@ -119,7 +120,7 @@ export class SubscriptionController { } @Post('stripe/checkout-session') - @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseGuards(AuthGuard('jwt'), CustomThrottlerGuard, HasPermissionGuard) public async createStripeCheckoutSession( @Body() { couponId, priceId }: { couponId?: string; priceId: string } ): Promise { diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index aaa4deb5a..312b8c3ae 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -17,7 +17,7 @@ import { UserWithSettings } from '@ghostfolio/common/types'; -import { Injectable, Logger } from '@nestjs/common'; +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { Prisma, Subscription } from '@prisma/client'; import { addMilliseconds, isBefore } from 'date-fns'; import ms, { StringValue } from 'ms'; @@ -64,6 +64,14 @@ export class SubscriptionService { } ); + if (!subscriptionOffer) { + throw new BadRequestException('Invalid priceId'); + } + + if (couponId && couponId !== subscriptionOffer.couponId) { + throw new BadRequestException('Invalid couponId'); + } + const stripeCheckoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { cancel_url: `${this.configurationService.get('ROOT_URL')}/${ @@ -77,9 +85,7 @@ export class SubscriptionService { } ], locale: this.getStripeLocale(user.settings?.settings?.language), - metadata: subscriptionOffer - ? { subscriptionOffer: JSON.stringify(subscriptionOffer) } - : {}, + metadata: { subscriptionOffer: JSON.stringify(subscriptionOffer) }, mode: 'payment', payment_method_types: ['card'], success_url: `${this.configurationService.get(