From 9b4077f2f10dbc969169ce2b150e9f17558ffefa Mon Sep 17 00:00:00 2001 From: 0xBernny Date: Thu, 3 Sep 2026 15:01:04 +0100 Subject: [PATCH 1/4] api-gateway merchant suspension check is checked too late Implement middleware to check for suspended merchants before request processing. --- .../src/middleware/merchantCheck.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/api-gateway/src/middleware/merchantCheck.ts diff --git a/packages/api-gateway/src/middleware/merchantCheck.ts b/packages/api-gateway/src/middleware/merchantCheck.ts new file mode 100644 index 0000000..088226a --- /dev/null +++ b/packages/api-gateway/src/middleware/merchantCheck.ts @@ -0,0 +1,31 @@ +import { FastifyRequest, FastifyReply } from "fastify"; +import { getMerchantStatusFromDb } from "@/lib/db"; // Adjust matching database or cache layer string + +/** + * Early preHandler hook that intercepts requests before body parsing occurs. + * Rejects suspended merchants immediately to prevent resource consumption and information leaks. + */ +export async function checkMerchantSuspension(request: FastifyRequest, reply: FastifyReply) { + // Extract merchant identity context safely from headers or route parameters + const merchantId = (request.headers["x-merchant-id"] || request.params?.["merchantId"]) as string; + + if (!merchantId) { + return; // Pass through if not a merchant-scoped route context + } + + try { + const merchant = await getMerchantStatusFromDb(merchantId); + + if (merchant && merchant.status === "SUSPENDED") { + // Return a consistent suspension error explicitly before heavy work / body parsing begins + return reply.status(403).send({ + error: "Forbidden", + message: "Merchant account is suspended.", + code: "MERCHANT_SUSPENDED" + }); + } + } catch (error) { + // Fail closed for safety if the lookup fails + return reply.status(500).send({ error: "Internal validation failure." }); + } +} From 67e08326af331b07e20ac7156c087a874b9b0585 Mon Sep 17 00:00:00 2001 From: 0xBernny Date: Thu, 3 Sep 2026 15:02:52 +0100 Subject: [PATCH 2/4] api-gateway merchant suspension check is checked too late Added middleware to check merchant suspension before handling routes. --- packages/api-gateway/src/app/merchant/routes.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/api-gateway/src/app/merchant/routes.ts diff --git a/packages/api-gateway/src/app/merchant/routes.ts b/packages/api-gateway/src/app/merchant/routes.ts new file mode 100644 index 0000000..cff8697 --- /dev/null +++ b/packages/api-gateway/src/app/merchant/routes.ts @@ -0,0 +1,9 @@ +import { checkMerchantSuspension } from "../../middleware/merchantCheck"; + +// When defining your route blocks, register it as a preHandler: +fastify.register(async function (merchantRoutes) { + merchantRoutes.addHook("preHandler", checkMerchantSuspension); + + // Your route definitions here run safely *after* the suspension verification + merchantRoutes.get("/listings", async (req, res) => { /* ... */ }); +}); From f4050caeb179f81b96d445d881dc317ed4ad5f0e Mon Sep 17 00:00:00 2001 From: 0xBernny Date: Thu, 3 Sep 2026 15:05:19 +0100 Subject: [PATCH 3/4] api-gateway merchant suspension check is checked too late From d4642b89eb2058e482d9529d8389959ef8b67e7c Mon Sep 17 00:00:00 2001 From: 0xBernny Date: Thu, 3 Sep 2026 15:07:30 +0100 Subject: [PATCH 4/4] api-gateway merchant suspension check is checked too late Add unit tests for merchant suspension checks in API. --- .../src/__tests__/merchant-suspension.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 packages/api-gateway/src/__tests__/merchant-suspension.test.ts diff --git a/packages/api-gateway/src/__tests__/merchant-suspension.test.ts b/packages/api-gateway/src/__tests__/merchant-suspension.test.ts new file mode 100644 index 0000000..e101d7b --- /dev/null +++ b/packages/api-gateway/src/__tests__/merchant-suspension.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect, vi } from "vitest"; +import { checkMerchantSuspension } from "../middleware/merchantCheck"; + +describe("Merchant Suspension Early Lifecycle Checks", () => { + it("should reject suspended merchants before handling heavy payloads or route handlers", async () => { + const mockRequest = { + headers: { "x-merchant-id": "merch_suspended_99" }, + params: {}, + } as any; + + const mockReply = { + status: vi.fn().mockReturnThis(), + send: vi.fn(), + } as any; + + await checkMerchantSuspension(mockRequest, mockReply); + + // Verify a consistent 403 Forbidden payload is fired upfront + expect(mockReply.status).toHaveBeenCalledWith(403); + expect(mockReply.send).toHaveBeenCalledWith(expect.objectContaining({ + code: "MERCHANT_SUSPENDED" + })); + }); +});