From 4a932d4fa3f34ec0efcef91ea2130c5d98a1e145 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sat, 2 May 2026 22:23:43 +0530 Subject: [PATCH] refactor: only register callback routes when their processor is active --- ...allback-routes-conditional-registration.md | 5 ++++ .../callbacks/lnbits-callback-controller.ts | 7 ----- .../callbacks/opennode-callback-controller.ts | 9 ------ .../callbacks/zebedee-callback-controller.ts | 7 ----- src/routes/callbacks/index.ts | 26 ++++++++++------- .../lnbits-callback-controller.spec.ts | 29 ------------------- .../opennode-callback-controller.spec.ts | 14 --------- .../zebedee-callback-controller.spec.ts | 15 ---------- test/unit/routes/callbacks.spec.ts | 7 +++++ 9 files changed, 28 insertions(+), 91 deletions(-) create mode 100644 .changeset/callback-routes-conditional-registration.md diff --git a/.changeset/callback-routes-conditional-registration.md b/.changeset/callback-routes-conditional-registration.md new file mode 100644 index 00000000..eb5db6ab --- /dev/null +++ b/.changeset/callback-routes-conditional-registration.md @@ -0,0 +1,5 @@ +--- +"nostream": patch +--- + +refactor: only register OpenNode, LNbits, and Zebedee callback routes when their processor is active diff --git a/src/controllers/callbacks/lnbits-callback-controller.ts b/src/controllers/callbacks/lnbits-callback-controller.ts index 30fdae34..5f0c0db5 100644 --- a/src/controllers/callbacks/lnbits-callback-controller.ts +++ b/src/controllers/callbacks/lnbits-callback-controller.ts @@ -25,13 +25,6 @@ export class LNbitsCallbackController implements IController { const settings = createSettings() const remoteAddress = getRemoteAddress(request, settings) - const paymentProcessor = settings.payments?.processor ?? 'null' - - if (paymentProcessor !== 'lnbits') { - logger('denied request from %s to /callbacks/lnbits which is not the current payment processor', remoteAddress) - response.status(403).send('Forbidden') - return - } const queryValidation = validateSchema(lnbitsCallbackQuerySchema)(request.query) if (queryValidation.error) { diff --git a/src/controllers/callbacks/opennode-callback-controller.ts b/src/controllers/callbacks/opennode-callback-controller.ts index 66eb7981..cc349c95 100644 --- a/src/controllers/callbacks/opennode-callback-controller.ts +++ b/src/controllers/callbacks/opennode-callback-controller.ts @@ -22,15 +22,6 @@ export class OpenNodeCallbackController implements IController { const settings = createSettings() const remoteAddress = getRemoteAddress(request, settings) - const paymentProcessor = settings.payments?.processor - - if (paymentProcessor !== 'opennode') { - logger('denied request from %s to /callbacks/opennode which is not the current payment processor', remoteAddress) - response - .status(403) - .send('Forbidden') - return - } const bodyValidation = validateSchema(opennodeWebhookCallbackBodySchema)(request.body) if (bodyValidation.error) { diff --git a/src/controllers/callbacks/zebedee-callback-controller.ts b/src/controllers/callbacks/zebedee-callback-controller.ts index e9c448a3..e4c2b792 100644 --- a/src/controllers/callbacks/zebedee-callback-controller.ts +++ b/src/controllers/callbacks/zebedee-callback-controller.ts @@ -30,7 +30,6 @@ export class ZebedeeCallbackController implements IController { const { ipWhitelist = [] } = settings.paymentsProcessors?.zebedee ?? {} const remoteAddress = getRemoteAddress(request, settings) - const paymentProcessor = settings.payments?.processor if (ipWhitelist.length && !ipWhitelist.includes(remoteAddress)) { logger('unauthorized request from %s to /callbacks/zebedee', remoteAddress) @@ -38,12 +37,6 @@ export class ZebedeeCallbackController implements IController { return } - if (paymentProcessor !== 'zebedee') { - logger('denied request from %s to /callbacks/zebedee which is not the current payment processor', remoteAddress) - response.status(403).send('Forbidden') - return - } - const invoice = fromZebedeeInvoice(request.body) logger('invoice', invoice) diff --git a/src/routes/callbacks/index.ts b/src/routes/callbacks/index.ts index eed0ea49..c835c863 100644 --- a/src/routes/callbacks/index.ts +++ b/src/routes/callbacks/index.ts @@ -1,4 +1,4 @@ -import { json, Router, urlencoded } from 'express' +import { json, NextFunction, Request, Response, Router, urlencoded } from 'express' import { createLNbitsCallbackController } from '../../factories/controllers/lnbits-callback-controller-factory' import { createNodelessCallbackController } from '../../factories/controllers/nodeless-callback-controller-factory' @@ -9,17 +9,23 @@ import { withController } from '../../handlers/request-handlers/with-controller- const router: Router = Router() -const settings = createSettings() -const processor = settings.payments?.processor +const requireProcessor = (name: string) => + (_req: Request, res: Response, next: NextFunction) => { + const settings = createSettings() + if (settings.payments?.processor !== name) { + res.status(403).send('Forbidden') + return + } + next() + } router - .post('/zebedee', json(), withController(createZebedeeCallbackController)) - .post('/lnbits', json(), withController(createLNbitsCallbackController)) - .post('/opennode', urlencoded({ extended: false }), json(), withController(createOpenNodeCallbackController)) - -if (processor === 'nodeless') { - router.post( + .post('/zebedee', requireProcessor('zebedee'), json(), withController(createZebedeeCallbackController)) + .post('/lnbits', requireProcessor('lnbits'), json(), withController(createLNbitsCallbackController)) + .post('/opennode', requireProcessor('opennode'), urlencoded({ extended: false }), json(), withController(createOpenNodeCallbackController)) + .post( '/nodeless', + requireProcessor('nodeless'), json({ verify(req, _res, buf) { ;(req as any).rawBody = buf @@ -27,6 +33,6 @@ if (processor === 'nodeless') { }), withController(createNodelessCallbackController), ) -} export default router + diff --git a/test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts b/test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts index 05cd99e0..9ac78fef 100644 --- a/test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts +++ b/test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts @@ -108,35 +108,6 @@ describe('LNbitsCallbackController', () => { }) describe('authorization and validation', () => { - it('returns 403 when payment processor settings are missing', async () => { - createSettingsStub.returns({ - network: { remoteIpHeader: 'x-forwarded-for' }, - }) - const { controller, paymentsService } = makeController() - const res = makeRes() - - await controller.handleRequest(makeReq(), res) - - expect(res.status).to.have.been.calledWith(403) - expect(res.send).to.have.been.calledWith('Forbidden') - expect(paymentsService.getInvoiceFromPaymentsProcessor).to.not.have.been.called - }) - - it('returns 403 when lnbits is not the configured processor', async () => { - createSettingsStub.returns({ - ...baseSettings, - payments: { processor: 'opennode' }, - }) - const { controller, paymentsService } = makeController() - const res = makeRes() - - await controller.handleRequest(makeReq(), res) - - expect(res.status).to.have.been.calledWith(403) - expect(res.send).to.have.been.calledWith('Forbidden') - expect(paymentsService.getInvoiceFromPaymentsProcessor).to.not.have.been.called - }) - it('returns 403 for invalid query parameters', async () => { const { controller } = makeController() const res = makeRes() diff --git a/test/unit/controllers/callbacks/opennode-callback-controller.spec.ts b/test/unit/controllers/callbacks/opennode-callback-controller.spec.ts index 744669f2..cc781a4a 100644 --- a/test/unit/controllers/callbacks/opennode-callback-controller.spec.ts +++ b/test/unit/controllers/callbacks/opennode-callback-controller.spec.ts @@ -99,20 +99,6 @@ describe('OpenNodeCallbackController', () => { }) describe('authorization and validation', () => { - it('returns 403 when opennode is not the configured processor', async () => { - createSettingsStub.returns({ - payments: { processor: 'lnbits' }, - }) - const { controller, paymentsService } = makeController() - const res = makeRes() - - await controller.handleRequest(makeReq(), res) - - expect(res.status).to.have.been.calledWith(403) - expect(res.send).to.have.been.calledWith('Forbidden') - expect(paymentsService.updateInvoiceStatus).to.not.have.been.called - }) - it('returns 400 for malformed request body', async () => { const { controller, paymentsService } = makeController() const res = makeRes() diff --git a/test/unit/controllers/callbacks/zebedee-callback-controller.spec.ts b/test/unit/controllers/callbacks/zebedee-callback-controller.spec.ts index afb05a7b..acfbec9f 100644 --- a/test/unit/controllers/callbacks/zebedee-callback-controller.spec.ts +++ b/test/unit/controllers/callbacks/zebedee-callback-controller.spec.ts @@ -136,21 +136,6 @@ describe('ZebedeeCallbackController', () => { expect(res.send).to.have.been.calledWith('Forbidden') expect(paymentsService.updateInvoiceStatus).to.not.have.been.called }) - - it('returns 403 when zebedee is not the configured processor', async () => { - createSettingsStub.returns({ - ...baseSettings, - payments: { processor: 'lnbits' }, - }) - const { controller, paymentsService } = makeController() - const res = makeRes() - - await controller.handleRequest(makeReq(), res) - - expect(res.status).to.have.been.calledWith(403) - expect(res.send).to.have.been.calledWith('Forbidden') - expect(paymentsService.updateInvoiceStatus).to.not.have.been.called - }) }) describe('invoice state handling', () => { diff --git a/test/unit/routes/callbacks.spec.ts b/test/unit/routes/callbacks.spec.ts index 4d5e867c..1a4bda27 100644 --- a/test/unit/routes/callbacks.spec.ts +++ b/test/unit/routes/callbacks.spec.ts @@ -4,15 +4,21 @@ import express from 'express' import Sinon from 'sinon' import * as openNodeControllerFactory from '../../../src/factories/controllers/opennode-callback-controller-factory' +import * as settingsFactory from '../../../src/factories/settings-factory' describe('callbacks router', () => { let createOpenNodeCallbackControllerStub: Sinon.SinonStub + let createSettingsStub: Sinon.SinonStub let receivedBody: unknown let server: any beforeEach(async () => { receivedBody = undefined + createSettingsStub = Sinon.stub(settingsFactory, 'createSettings').returns({ + payments: { processor: 'opennode' }, + } as any) + createOpenNodeCallbackControllerStub = Sinon.stub(openNodeControllerFactory, 'createOpenNodeCallbackController').returns({ handleRequest: async (request: any, response: any) => { receivedBody = request.body @@ -35,6 +41,7 @@ describe('callbacks router', () => { afterEach(async () => { createOpenNodeCallbackControllerStub.restore() + createSettingsStub.restore() delete require.cache[require.resolve('../../../src/routes/callbacks')] if (server) {