From eadb5c0c7cf97db4ac8d16a9041e3892390f3373 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Wed, 26 Aug 2026 13:10:47 +0100 Subject: [PATCH 1/5] feat: add configurable regional wager blocking --- backend/middleware/geoBlock.js | 23 +++++++++++++++++++++++ backend/server.js | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 backend/middleware/geoBlock.js diff --git a/backend/middleware/geoBlock.js b/backend/middleware/geoBlock.js new file mode 100644 index 0000000..9e2f122 --- /dev/null +++ b/backend/middleware/geoBlock.js @@ -0,0 +1,23 @@ +const blockedCountries = () => new Set( + String(process.env.BLOCKED_COUNTRIES || '') + .split(',').map((country) => country.trim().toUpperCase()).filter(Boolean), +); + +function clientIp(req) { + const forwarded = req.headers['x-forwarded-for']; + return String(forwarded || req.headers['cf-connecting-ip'] || req.ip || '').split(',')[0].trim(); +} + +function geoBlock(req, res, next) { + const country = String( + req.headers['cf-ipcountry'] || req.headers['x-vercel-ip-country'] || req.headers['x-country-code'] || '', + ).toUpperCase(); + if (country && blockedCountries().has(country)) { + return res.status(451).json({ error: 'This region is not permitted to access wagered gameplay.' }); + } + req.clientIp = clientIp(req); + req.clientCountry = country || null; + return next(); +} + +module.exports = { geoBlock, clientIp }; diff --git a/backend/server.js b/backend/server.js index cfb6011..b0941c0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,6 +13,7 @@ const cronService = require("./services/cronService"); const supabase = require("./config/supabase"); const logger = require("./utils/logger"); const { errorHandler, installGlobalHandlers } = require("./middleware/errorHandler"); +const { geoBlock } = require("./middleware/geoBlock"); const app = express(); const server = http.createServer(app); @@ -38,6 +39,7 @@ app.use( }), ); app.use(express.json()); +app.use(geoBlock); // Mount routes app.use("/api", gameRoutes); From 5f306c8d1a6fda90dca44151feeb664b7c44e3da Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Sat, 29 Aug 2026 13:37:25 +0100 Subject: [PATCH 2/5] fix: stabilize backend CI test environment --- backend/jest.config.js | 1 + backend/package-lock.json | 39 +++++++++++++++++++++ backend/package.json | 3 +- backend/tests/escrowService.horizon.test.js | 4 +++ backend/tests/setup.js | 5 +++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 backend/tests/setup.js diff --git a/backend/jest.config.js b/backend/jest.config.js index e20ad0b..877542d 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,5 +1,6 @@ module.exports = { testEnvironment: 'node', + setupFiles: ['/tests/setup.js'], coverageDirectory: 'coverage', collectCoverageFrom: [ '**/*.js', diff --git a/backend/package-lock.json b/backend/package-lock.json index fdcad95..df9157e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,6 +18,7 @@ "helmet": "^8.0.0", "jsonwebtoken": "^9.0.3", "pg": "^8.23.0", + "prom-client": "^15.1.3", "socket.io": "^4.8.3", "stellar-sdk": "^13.3.0" }, @@ -1062,6 +1063,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", @@ -2166,6 +2176,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -5592,6 +5608,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "deprecated": "prom-client has been replaced by @prometheus-io/client", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -6479,6 +6509,15 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tdigest": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.3.tgz", + "integrity": "sha512-zbRt+lT+/H4fRItHshczHErVCQnitJk8MfMT24MqFJf3YL7SJJPqGIGeuOdvxXxM/AHFzKBl7WoyaYwqO9s3Kw==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index c9daaed..2357ad6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,11 +24,12 @@ "@supabase/supabase-js": "^2.95.3", "axios": "^1.6.0", "cors": "^2.8.5", - "helmet": "^8.0.0", "dotenv": "^16.4.5", "express": "^5.2.1", + "helmet": "^8.0.0", "jsonwebtoken": "^9.0.3", "pg": "^8.23.0", + "prom-client": "^15.1.3", "socket.io": "^4.8.3", "stellar-sdk": "^13.3.0" }, diff --git a/backend/tests/escrowService.horizon.test.js b/backend/tests/escrowService.horizon.test.js index 687e0f9..427b7a2 100644 --- a/backend/tests/escrowService.horizon.test.js +++ b/backend/tests/escrowService.horizon.test.js @@ -2,6 +2,10 @@ * Tests for Stellar Horizon Transaction Indexer */ +jest.mock("@stellar/stellar-sdk", () => ({ + Networks: { TESTNET: "Test SDF Network ; September 2015" }, +})); + const escrowService = require("../services/escrowService"); jest.mock("axios"); diff --git a/backend/tests/setup.js b/backend/tests/setup.js new file mode 100644 index 0000000..ca6b1de --- /dev/null +++ b/backend/tests/setup.js @@ -0,0 +1,5 @@ +// Keep tests hermetic: production configuration remains fail-closed, while +// modules imported by isolated route/service tests get safe placeholders. +process.env.SUPABASE_URL ||= 'http://localhost'; +process.env.SUPABASE_ANON_KEY ||= 'test-anon-key'; +process.env.SUPABASE_KEY ||= 'test-service-key'; From 641d7f848e53c6ab4daec36ba9fb9e5d7b921150 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Sat, 29 Aug 2026 13:38:56 +0100 Subject: [PATCH 3/5] ci: replace unavailable Rust audit action --- .github/workflows/security-audit.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index f1e3de0..f002d22 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -47,7 +47,10 @@ jobs: uses: actions/checkout@v4 - name: Run Rust Security Audit Check - uses: rustsec/audit-check-action@v2.0.0 + uses: taiki-e/install-action@v2 with: - token: ${{ secrets.GITHUB_TOKEN }} - working-directory: ./contracts/soroban + tool: cargo-audit + + - name: Audit Rust dependencies + working-directory: ./contracts/soroban + run: cargo audit From 5bf0b10c7562e1aae05a20b056dc3f3bc3fccf5e Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Sat, 29 Aug 2026 13:51:49 +0100 Subject: [PATCH 4/5] test: isolate health route SDK dependency --- backend/tests/healthRoutes.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/tests/healthRoutes.test.js b/backend/tests/healthRoutes.test.js index 1b01562..20f27ff 100644 --- a/backend/tests/healthRoutes.test.js +++ b/backend/tests/healthRoutes.test.js @@ -4,6 +4,13 @@ const request = require("supertest"); const express = require("express"); + +// Keep route tests independent of the SDK's ESM-only transitive hash module. +jest.mock("@stellar/stellar-sdk", () => ({ + Networks: { TESTNET: "Test SDF Network ; September 2015" }, + rpc: { Server: jest.fn(() => ({ getLatestLedger: jest.fn().mockResolvedValue({ sequence: 1 }) })) }, +})); + const healthRoutes = require("../routes/healthRoutes"); describe("Health Check Routes", () => { From 7d2dbc50e2237d0dce793bd1110f20e7277aa354 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Sat, 29 Aug 2026 14:14:45 +0100 Subject: [PATCH 5/5] test: isolate health route configuration --- backend/tests/healthRoutes.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/tests/healthRoutes.test.js b/backend/tests/healthRoutes.test.js index 20f27ff..ca225f6 100644 --- a/backend/tests/healthRoutes.test.js +++ b/backend/tests/healthRoutes.test.js @@ -17,6 +17,12 @@ describe("Health Check Routes", () => { let app; beforeEach(() => { + // Other suites exercise missing configuration and may mutate process.env. + // Restore the route test's safe configuration for every test in this file. + process.env.SUPABASE_URL = "http://localhost"; + process.env.SUPABASE_ANON_KEY = "test-anon-key"; + process.env.SUPABASE_KEY = "test-service-key"; + process.env.SOROBAN_RPC_URL = "http://localhost"; app = express(); app.use(express.json()); app.use("/api", healthRoutes);