From 42cc7ec2cbf9ceb24e719acd53e2fcc1d644a05e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 29 Jun 2026 10:34:25 +0000 Subject: [PATCH] fix: restrict CORS origins and gate webhook events endpoint - Replace wildcard cors() with cors({ origin: appBaseUrl }) on all four reference servers to prevent cross-origin API key abuse from arbitrary websites. - Add optional ADMIN_TOKEN Bearer auth on /api/webhooks/events to prevent unauthenticated access to sensitive payment and customer data. - Update .env.example files to document the new ADMIN_TOKEN variable. Fixes: wildcard CORS + unauthenticated payment endpoints (HIGH), unauthenticated webhook event endpoint data exposure (MEDIUM). Co-authored-by: Babacar Diop --- direct-charge-integration-reference/.env.example | 3 +++ direct-charge-integration-reference/server/index.js | 8 ++++++-- lomi-edupay-connector/.env.example | 3 +++ lomi-edupay-connector/server/index.js | 8 ++++++-- payment-integration-reference/.env.example | 3 +++ payment-integration-reference/server/index.js | 8 ++++++-- payment-integration-sdk-reference/.env.example | 3 +++ payment-integration-sdk-reference/server/index.js | 8 ++++++-- 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/direct-charge-integration-reference/.env.example b/direct-charge-integration-reference/.env.example index 9ca5bac..6319598 100644 --- a/direct-charge-integration-reference/.env.example +++ b/direct-charge-integration-reference/.env.example @@ -4,3 +4,6 @@ LOMI_API_KEY=your-lomi-secret-key LOMI_PUBLISHABLE_KEY=lomi_pk_your_publishable_key LOMI_WEBHOOK_SECRET=whsec_replace_with_your_webhook_signing_secret + +# Optional: protect the /api/webhooks/events debug endpoint with a Bearer token +# ADMIN_TOKEN=your-random-admin-token diff --git a/direct-charge-integration-reference/server/index.js b/direct-charge-integration-reference/server/index.js index 5305384..2d396f0 100644 --- a/direct-charge-integration-reference/server/index.js +++ b/direct-charge-integration-reference/server/index.js @@ -23,7 +23,7 @@ app.use( }, }), ); -app.use(cors()); +app.use(cors({ origin: appBaseUrl })); app.use(express.static(path.join(__dirname, "..", "frontend"))); const eventStore = []; @@ -246,7 +246,11 @@ app.post("/api/webhooks/lomi", (req, res) => { return res.status(200).json({ ok: true }); }); -app.get("/api/webhooks/events", (_req, res) => { +app.get("/api/webhooks/events", (req, res) => { + const token = process.env.ADMIN_TOKEN; + if (token && req.get("Authorization") !== `Bearer ${token}`) { + return res.status(401).json({ error: "Unauthorized" }); + } res.json({ count: eventStore.length, events: eventStore }); }); diff --git a/lomi-edupay-connector/.env.example b/lomi-edupay-connector/.env.example index c07bdbf..efe3afa 100644 --- a/lomi-edupay-connector/.env.example +++ b/lomi-edupay-connector/.env.example @@ -9,3 +9,6 @@ APP_BASE_URL=http://localhost:3010 # Optional: forward PAYMENT_SUCCEEDED to EduPay backend # EDUPAY_WEBHOOK_FORWARD_URL=https://api.edupay.example/webhooks/lomi + +# Optional: protect the /api/webhooks/events debug endpoint with a Bearer token +# ADMIN_TOKEN=your-random-admin-token diff --git a/lomi-edupay-connector/server/index.js b/lomi-edupay-connector/server/index.js index 692ca9b..6ba4e35 100644 --- a/lomi-edupay-connector/server/index.js +++ b/lomi-edupay-connector/server/index.js @@ -32,7 +32,7 @@ app.use( }, }), ); -app.use(cors()); +app.use(cors({ origin: appBaseUrl })); app.use(express.static(path.join(__dirname, "..", "frontend"))); const webhookEvents = []; @@ -234,7 +234,11 @@ app.post("/api/webhooks/lomi", async (req, res) => { return res.status(200).json({ ok: true }); }); -app.get("/api/webhooks/events", (_req, res) => { +app.get("/api/webhooks/events", (req, res) => { + const token = process.env.ADMIN_TOKEN; + if (token && req.get("Authorization") !== `Bearer ${token}`) { + return res.status(401).json({ error: "Unauthorized" }); + } res.json({ count: webhookEvents.length, events: webhookEvents }); }); diff --git a/payment-integration-reference/.env.example b/payment-integration-reference/.env.example index 58ba45d..fa3be79 100644 --- a/payment-integration-reference/.env.example +++ b/payment-integration-reference/.env.example @@ -1,3 +1,6 @@ LOMI_BASE_URL=https://api.lomi.africa LOMI_API_KEY=your-lomi-api-key LOMI_WEBHOOK_SECRET=whsec_replace_with_your_webhook_signing_secret + +# Optional: protect the /api/webhooks/events debug endpoint with a Bearer token +# ADMIN_TOKEN=your-random-admin-token diff --git a/payment-integration-reference/server/index.js b/payment-integration-reference/server/index.js index f85198d..1fa8351 100644 --- a/payment-integration-reference/server/index.js +++ b/payment-integration-reference/server/index.js @@ -22,7 +22,7 @@ app.use( }, }), ); -app.use(cors()); +app.use(cors({ origin: appBaseUrl })); app.use(express.static(path.join(__dirname, "..", "frontend"))); const eventStore = []; @@ -258,7 +258,11 @@ app.post("/api/webhooks/lomi", (req, res) => { return res.status(200).json({ ok: true }); }); -app.get("/api/webhooks/events", (_req, res) => { +app.get("/api/webhooks/events", (req, res) => { + const token = process.env.ADMIN_TOKEN; + if (token && req.get("Authorization") !== `Bearer ${token}`) { + return res.status(401).json({ error: "Unauthorized" }); + } res.json({ count: eventStore.length, events: eventStore }); }); diff --git a/payment-integration-sdk-reference/.env.example b/payment-integration-sdk-reference/.env.example index 3e83b30..e652b7e 100644 --- a/payment-integration-sdk-reference/.env.example +++ b/payment-integration-sdk-reference/.env.example @@ -2,3 +2,6 @@ LOMI_BASE_URL=https://api.lomi.africa LOMI_API_KEY=your-lomi-api-key LOMI_WEBHOOK_SECRET=whsec_your_webhook_signing_secret LOMI_PUBLIC_KEY=lomi_pk_your_publishable_key + +# Optional: protect the /api/webhooks/events debug endpoint with a Bearer token +# ADMIN_TOKEN=your-random-admin-token diff --git a/payment-integration-sdk-reference/server/index.js b/payment-integration-sdk-reference/server/index.js index 20acafb..0cdb554 100644 --- a/payment-integration-sdk-reference/server/index.js +++ b/payment-integration-sdk-reference/server/index.js @@ -38,7 +38,7 @@ app.use( }, }), ); -app.use(cors()); +app.use(cors({ origin: appBaseUrl })); app.use(express.static(path.join(__dirname, "..", "frontend"))); app.get("/vendor/lomi-embed.js", (_req, res) => { @@ -267,7 +267,11 @@ app.post("/api/webhooks/lomi", (req, res) => { return res.status(200).json({ ok: true }); }); -app.get("/api/webhooks/events", (_req, res) => { +app.get("/api/webhooks/events", (req, res) => { + const token = process.env.ADMIN_TOKEN; + if (token && req.get("Authorization") !== `Bearer ${token}`) { + return res.status(401).json({ error: "Unauthorized" }); + } res.json({ count: eventStore.length, events: eventStore }); });