Skip to content

refactor: configurable rate limits, RPC error classification, CORS module, webhook docs - #1659

Merged
hman38705 merged 1 commit into
solutions-plug:mainfrom
Johnero542:feat/issues-1511-1512-1513-1514
Sep 28, 2026
Merged

hman38705 merged 1 commit into
solutions-plug:mainfrom
Johnero542:feat/issues-1511-1512-1513-1514

Conversation

@Johnero542

Copy link
Copy Markdown
Contributor

Add configurable rate limits, distinguish transient vs permanent RPC errors, move CORS layer to library module, and document webhook security mechanism.

Issue #1511: Rate Limit Threshold Configuration

  • Add ADMIN_RATE_LIMIT_MAX and ADMIN_RATE_LIMIT_WINDOW_SECS environment variables
  • Add GLOBAL_RATE_LIMIT_MAX and GLOBAL_RATE_LIMIT_WINDOW_SECS environment variables
  • Defaults: admin (30 req/60s), global (100 req/60s)
  • Validate all thresholds > 0 in production
  • Update middleware to use config instead of hardcoded values
  • Allows tuning rate limits during incidents without redeployment

Issue #1512: RPC Error Classification

  • Add RpcErrorClass enum to distinguish transient vs permanent failures
  • Implement BlockchainError with automatic classification
  • Update replay_events to return classified errors
  • Map errors to HTTP status: 422 (permanent) / 503 (transient)
  • Callers can now determine whether retrying is useful

Issue #1513: Move CORS Layer to Library Module

  • Extract build_cors_layer from main.rs to new cors.rs module
  • Add 7 comprehensive unit tests for dev mode, strict mode, origins, credentials
  • Makes function testable outside the binary
  • No behavior change to production routing

Issue #1514: Webhook Idempotency Documentation

  • Confirm replay protection survives process restarts (Redis-backed nonce)
  • Add documentation tests for two-stage dedup mechanism
  • Create docs/webhook-security.md with comprehensive security guide
  • Document configuration, testing procedures, incident response

Test Plan

  • Rate limit thresholds can be configured via environment variables
  • Rate limit validation rejects 0 values
  • RPC errors are correctly classified as transient vs permanent
  • blockchain_replay handler returns 422 for permanent, 503 for transient
  • CORS tests pass: dev mode permissive, strict mode allowlist enforcement
  • Webhook replay protection survives process restart (Redis persists nonce)

Closes #1511
Closes #1512
Closes #1513
Closes #1514

…S layer, document webhook security

Issue solutions-plug#1511: Make admin and global rate limit thresholds environment-configurable
- Add admin_rate_limit_max, admin_rate_limit_window_secs to Config
- Add global_rate_limit_max, global_rate_limit_window_secs to Config
- Load from ADMIN_RATE_LIMIT_MAX, ADMIN_RATE_LIMIT_WINDOW_SECS env vars (defaults: 30, 60)
- Load from GLOBAL_RATE_LIMIT_MAX, GLOBAL_RATE_LIMIT_WINDOW_SECS env vars (defaults: 100, 60)
- Validate all thresholds are > 0, reject 0 values in config validation
- Update admin_rate_limit_middleware to use config instead of hardcoded values
- Update global_rate_limit_middleware to use config instead of hardcoded values

Issue solutions-plug#1512: Classify blockchain RPC errors (transient vs permanent)
- Add RpcErrorClass enum: Transient, Permanent
- Create BlockchainError type with class and message fields
- Add error classification logic: HTTP 4xx/invalid-params → permanent, 5xx/429/timeout → transient
- Update replay_events to return Result<ReplayProgress, BlockchainError>
- Update blockchain_replay handler to map errors: 422 for permanent, 503 for transient
- Callers can now determine if retrying is useful based on error classification

Issue solutions-plug#1513: Move build_cors_layer to library module with unit tests
- Create new cors.rs module in services/api/src/
- Move build_cors_layer function from main.rs to cors.rs
- Add 7 comprehensive unit tests:
  - dev_mode_is_permissive: validates dev mode warning behavior
  - strict_mode_with_single_origin: enforces allowlist
  - strict_mode_with_empty_origins_blocks_cors: empty origins block cross-origin
  - credentials_flag_is_honored: allow_credentials behavior
  - max_age_is_respected: preflight cache TTL
  - multiple_origins_are_supported: multi-origin allowlist
  - invalid_origin_headers_are_skipped: graceful handling of invalid headers
- Add cors module to lib.rs
- Update main.rs imports and usage to call cors::build_cors_layer
- No production behavior change; all logic preserved

Issue solutions-plug#1514: Verify webhook idempotency window survives restarts and add documentation
- Confirm replay protection state is Redis-backed (nonce key with TTL)
- Confirm secondary dedup via DB unique constraint on (message_id, event_type, email)
- Both guards survive process restarts as long as Redis and PostgreSQL are running
- Add 3 documentation tests confirming Redis and DB backing
- Create docs/webhook-security.md with comprehensive security documentation:
  - Authentication (HMAC-SHA256 signature verification)
  - Replay protection mechanism (Redis nonce + DB dedup)
  - Configuration (WEBHOOK_REPLAY_WINDOW_SECS, SENDGRID_WEBHOOK_SECRET)
  - Payload validation (size limits, HTML sanitization)
  - Observability (logging, metrics)
  - Testing guide and incident response procedures

Closes solutions-plug#1511
Closes solutions-plug#1512
Closes solutions-plug#1513
Closes solutions-plug#1514
@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@Johnero542 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

@hman38705
hman38705 merged commit 5dd7321 into solutions-plug:main Sep 28, 2026
15 of 59 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment