diff --git a/src/integration/alchemy/services/alchemy-webhook.service.ts b/src/integration/alchemy/services/alchemy-webhook.service.ts index 866dba04aa..fbe7be66ef 100644 --- a/src/integration/alchemy/services/alchemy-webhook.service.ts +++ b/src/integration/alchemy/services/alchemy-webhook.service.ts @@ -70,7 +70,6 @@ export class AlchemyWebhookService implements OnModuleInit { const signingKey = this.webhookCache.get(webhookId); if (!signingKey) { this.logger.warn(`Webhook Id ${webhookId} has no signing key`); - this.logger.warn(`Webhook cache: ${JSON.stringify(this.webhookCache)}`); return false; } diff --git a/src/integration/bank/controllers/__tests__/yapeal-webhook.controller.spec.ts b/src/integration/bank/controllers/__tests__/yapeal-webhook.controller.spec.ts new file mode 100644 index 0000000000..248b958269 --- /dev/null +++ b/src/integration/bank/controllers/__tests__/yapeal-webhook.controller.spec.ts @@ -0,0 +1,44 @@ +import { ForbiddenException } from '@nestjs/common'; +import { Config, ConfigService } from 'src/config/config'; +import { YapealWebhookService } from '../../services/yapeal-webhook.service'; +import { YapealWebhookController } from '../yapeal-webhook.controller'; + +describe('YapealWebhookController', () => { + let controller: YapealWebhookController; + let yapealWebhookService: jest.Mocked>; + + const setExpectedKey = (key: string | undefined): void => { + (Config.bank.yapeal as { webhookApiKey?: string }).webhookApiKey = key; + }; + + beforeAll(() => { + new ConfigService(); + }); + + beforeEach(() => { + yapealWebhookService = { processWebhook: jest.fn() }; + controller = new YapealWebhookController(yapealWebhookService as unknown as YapealWebhookService); + }); + + it('processes the webhook when the api key matches', async () => { + setExpectedKey('secret'); + + await expect(controller.handleYapealWebhook('secret', { foo: 'bar' })).resolves.toEqual({ received: true }); + expect(yapealWebhookService.processWebhook).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('rejects a wrong api key', async () => { + setExpectedKey('secret'); + + await expect(controller.handleYapealWebhook('wrong', {})).rejects.toBeInstanceOf(ForbiddenException); + expect(yapealWebhookService.processWebhook).not.toHaveBeenCalled(); + }); + + it('fails closed: rejects every request when no expected key is configured', async () => { + setExpectedKey(undefined); + + await expect(controller.handleYapealWebhook('anything', {})).rejects.toBeInstanceOf(ForbiddenException); + await expect(controller.handleYapealWebhook(undefined, {})).rejects.toBeInstanceOf(ForbiddenException); + expect(yapealWebhookService.processWebhook).not.toHaveBeenCalled(); + }); +}); diff --git a/src/integration/bank/controllers/yapeal-webhook.controller.ts b/src/integration/bank/controllers/yapeal-webhook.controller.ts index 5fd28de571..7ced0e5a61 100644 --- a/src/integration/bank/controllers/yapeal-webhook.controller.ts +++ b/src/integration/bank/controllers/yapeal-webhook.controller.ts @@ -23,9 +23,10 @@ export class YapealWebhookController { private validateApiKey(apiKey: string): void { const expectedKey = Config.bank.yapeal.webhookApiKey; - if (!expectedKey) return; - if (!apiKey || apiKey !== expectedKey) { + // fail closed: a missing expected key must reject every request, not wave them all through — + // this endpoint marks bank payments as received, so any unauthenticated path is forgeable + if (!expectedKey || !apiKey || apiKey !== expectedKey) { throw new ForbiddenException('Invalid API key'); } }