Skip to content

Feat/ip allowlist guard#1072

Open
greatKhalifa-code wants to merge 2 commits into
rinafcode:mainfrom
greatKhalifa-code:feat/ip-allowlist-guard
Open

Feat/ip allowlist guard#1072
greatKhalifa-code wants to merge 2 commits into
rinafcode:mainfrom
greatKhalifa-code:feat/ip-allowlist-guard

Conversation

@greatKhalifa-code

Copy link
Copy Markdown

feat(security): IP allowlist guard for admin endpoints

Problem

Admin endpoints (/tenants, /roles, /sharding) are protected only by RBAC role checks. A stolen admin JWT from any IP is sufficient for full administrative access — there is no
network-level defense.

Solution

Adds IpAllowlistGuard as a defense-in-depth layer. Even with a valid admin JWT, requests originating from an IP not in the configured allowlist are rejected before reaching business
logic.

Changes

New

  • src/common/guards/ip-allowlist.guard.ts — reusable NestJS guard that reads ADMIN_IP_ALLOWLIST from config, parses comma-separated IPv4 addresses and CIDR blocks, and enforces the
    allowlist on every request
  • src/common/guards/ip-allowlist.guard.spec.ts — 34 unit tests

Modified

  • TenancyController — IpAllowlistGuard added before JwtAuthGuard/RolesGuard
  • RolesController — same; also added missing JwtAuthGuard, RolesGuard, and @roles(ADMIN) which were absent
  • ShardingController — same; also added missing auth guards as noted in the controller's own TODO comment
  • TenancyModule, RbacModule, ShardingModule — provide ConfigModule and IpAllowlistGuard

Configuration

Comma-separated IPv4 addresses and/or CIDR blocks

ADMIN_IP_ALLOWLIST=10.0.0.0/8,192.168.1.0/24,203.0.113.42

┌──────────────────────────┬─────────────────────────────────────┐
│ Scenario │ Behaviour │
├──────────────────────────┼─────────────────────────────────────┤
│ IP in allowlist │ Request proceeds normally │
├──────────────────────────┼─────────────────────────────────────┤
│ IP not in allowlist │ 403 Forbidden │
├──────────────────────────┼─────────────────────────────────────┤
│ ADMIN_IP_ALLOWLIST unset │ Guard disabled, startup WARN logged │
└──────────────────────────┴─────────────────────────────────────┘

How IP is resolved

  1. X-Forwarded-For — first (leftmost) value, i.e. the original client before any trusted proxies
  2. request.ip
  3. socket.remoteAddress
  4. IPv6-mapped IPv4 (::ffff:x.x.x.x) is normalised automatically

Tests

34 passing — exact IP allow/deny, CIDR /8 /12 /24 /32, mixed lists,
X-Forwarded-For parsing, ::ffff: stripping, fallback IP resolution,
disabled guard, malformed entry handling

Acceptance criteria

  • Request from unlisted IP returns 403
  • CIDR notation (e.g. 10.0.0.0/8) is supported
  • ADMIN_IP_ALLOWLIST unset disables the guard with a startup warning

closes #834

Wraps all IPaymentProvider HTTP calls with an opossum CircuitBreaker to
prevent a slow/unavailable gateway from exhausting the connection pool.

- Add PaymentProviderCircuitBreakerService (timeout 10s, 50% error
  threshold, 30s half-open probe interval, volumeThreshold 5)
- Proxy all IPaymentProvider methods through the breaker; throw 503
  ServiceUnavailableException when the circuit is OPEN
- Register service in PaymentsModule (providers + exports)
- Extend HealthIndicatorsService with checkPaymentProvider() and include
  paymentProvider in the readiness() map
- Add PaymentProviderHealthController exposing GET /health/payment-provider
  (503/207/200 by circuit state) and GET /health/payment-provider/status
- Update HealthModule to register new controller and providers
- Add 21 unit tests covering CLOSED, OPEN, and HALF_OPEN states

Closes #payment-circuit-breaker
Adds IpAllowlistGuard as a defense-in-depth layer preventing abuse
of admin endpoints from unknown networks, even with a valid stolen JWT.

- Create IpAllowlistGuard reading ADMIN_IP_ALLOWLIST from ConfigService
  - Supports comma-separated IPv4 addresses and CIDR notation (e.g. 10.0.0.0/8)
  - Resolves client IP from X-Forwarded-For first hop, falls back to
    request.ip / socket.remoteAddress
  - Strips ::ffff: IPv6-mapped IPv4 prefix automatically
  - When ADMIN_IP_ALLOWLIST is unset, guard is disabled with a startup WARNING
  - Unlisted IPs receive HTTP 403 Forbidden
- Apply guard to TenancyController, RolesController, ShardingController
  - Guard runs before JwtAuthGuard and RolesGuard (fail-fast on IP)
  - All endpoints on the three controllers now require ADMIN role + allowlisted IP
- Update TenancyModule, RbacModule, ShardingModule to provide ConfigModule
  and IpAllowlistGuard
- Add 34 unit tests covering: exact IP allow/deny, CIDR /8 /12 /24 /32,
  mixed lists, X-Forwarded-For parsing, IPv6-mapped stripping, fallback
  IP resolution, disabled guard, malformed entry handling

Closes #ip-allowlist-guard
@drips-wave

drips-wave Bot commented Jul 25, 2026

Copy link
Copy Markdown

@greatKhalifa-code Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Well done on the job done so far!
Kindly resolve conflict and fix workflow.

@greatKhalifa-code

greatKhalifa-code commented Jul 25, 2026 via email

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add IP-based allowlist guard for admin-only API endpoints

2 participants