From 725ebc3e9f0b23d0b1be699f3aa9dbdef9265580 Mon Sep 17 00:00:00 2001 From: Sag Date: Mon, 24 Jun 2024 17:33:39 +0200 Subject: [PATCH] Fixed invalid tierId handling during member paid checkout (#20455) - fixes https://linear.app/tryghost/issue/SLO-90 --- .../lib/controllers/RouterController.js | 18 ++++++- .../test/unit/lib/controllers/router.test.js | 52 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/ghost/members-api/lib/controllers/RouterController.js b/ghost/members-api/lib/controllers/RouterController.js index b5af1d8599..3ef803d5a0 100644 --- a/ghost/members-api/lib/controllers/RouterController.js +++ b/ghost/members-api/lib/controllers/RouterController.js @@ -10,6 +10,7 @@ const messages = { notFound: 'Not Found.', offerNotFound: 'This offer does not exist.', offerArchived: 'This offer is archived.', + tierNotFound: 'This tier does not exist.', tierArchived: 'This tier is archived.', existingSubscription: 'A subscription exists for this Member.', unableToCheckout: 'Unable to initiate checkout session', @@ -258,9 +259,22 @@ module.exports = class RouterController { tier = await this._tiersService.api.read(offer.tier.id); cadence = offer.cadence; - } else { + } else if (tierId) { offer = null; - tier = await this._tiersService.api.read(tierId); + + try { + // If the tierId is not a valid ID, the following line will throw + tier = await this._tiersService.api.read(tierId); + + if (!tier) { + throw undefined; + } + } catch (err) { + throw new BadRequestError({ + message: tpl(messages.tierNotFound), + context: 'Tier with id "' + tierId + '" not found' + }); + } } if (tier.status === 'archived') { diff --git a/ghost/members-api/test/unit/lib/controllers/router.test.js b/ghost/members-api/test/unit/lib/controllers/router.test.js index ef1d5dd7fb..cd85e09321 100644 --- a/ghost/members-api/test/unit/lib/controllers/router.test.js +++ b/ghost/members-api/test/unit/lib/controllers/router.test.js @@ -162,7 +162,7 @@ describe('RouterController', function () { } }); - it('returns a BadRequestError if offer is not found', async function () { + it('returns a BadRequestError if offer is not found by offerId', async function () { offersAPI = { getOffer: sinon.stub().resolves(null) }; @@ -184,6 +184,56 @@ describe('RouterController', function () { assert.equal(error.context, 'Offer with id "invalid" not found'); } }); + + it('returns a BadRequestError if tier is not found by tierId', async function () { + tiersService = { + api: { + read: sinon.stub().resolves(null) + } + }; + + const routerController = new RouterController({ + tiersService, + paymentsService, + offersAPI, + stripeAPIService, + labsService + }); + + try { + await routerController._getSubscriptionCheckoutData({tierId: 'invalid', cadence: 'year'}); + + assert.fail('Expected function to throw BadRequestError'); + } catch (error) { + assert(error instanceof errors.BadRequestError, 'Error should be an instance of BadRequestError'); + assert.equal(error.context, 'Tier with id "invalid" not found'); + } + }); + + it('returns a BadRequestError if fetching tier by tierId throws', async function () { + tiersService = { + api: { + read: sinon.stub().rejects(new Error('Fail to fetch tier')) + } + }; + + const routerController = new RouterController({ + tiersService, + paymentsService, + offersAPI, + stripeAPIService, + labsService + }); + + try { + await routerController._getSubscriptionCheckoutData({tierId: 'invalid', cadence: 'year'}); + + assert.fail('Expected function to throw BadRequestError'); + } catch (error) { + assert(error instanceof errors.BadRequestError, 'Error should be an instance of BadRequestError'); + assert.equal(error.context, 'Tier with id "invalid" not found'); + } + }); }); afterEach(function () {