Rate limiting - #148
Merged
Merged
Conversation
Contributor
Author
|
@memplethee-lab pr sent |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
All checks pass. Here's a summary of the implementation:
Rate Limiting & Abuse Protection Module
New module: src/rate-limiting/
rate-limiter.service.ts — Core distributed rate limiter:
Token bucket strategy via Redis Lua script (atomic refill + consume)
Sliding window strategy via Redis sorted set (atomic ZREMRANGEBYSCORE + ZCARD + ZADD)
Reuses CACHE_REDIS_CLIENT from the global CacheModule (ioredis)
In-memory fallback when Redis is unavailable (graceful degradation)
Per-key registry for dashboard inspection (getEntry, listEntries, reset)
Health check via ping() with caching
rate-limiting.guard.ts — Distributed replacement for QuotaGuard:
Reads the same @ratelimit() decorator metadata (RATE_LIMIT_KEY)
Resolves tiers per-request (free/paid/enterprise via existing quota.config.ts)
Identity resolution: user ID → wallet address → X-Forwarded-For → req.ip
Sets X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Tier, X-RateLimit-Strategy, and Retry-After headers
Emits Prometheus counters for allowed/denied decisions
rate-limiting.controller.ts — Grant reviewer dashboard:
GET /rate-limiting/dashboard — aggregated totals, denial rate, per-tier breakdown, strategy distribution
GET /rate-limiting/status — filtered key listing
GET /rate-limiting/metrics — raw Prometheus text
POST /rate-limiting/entries — manually set a rate-limit entry
DELETE /rate-limiting/entries/:key — reset a throttled key
GET /rate-limiting/storage — storage health (redis vs memory)
rate-limiting.metrics.ts — Prometheus collectors:
alian_structure_rate_limit_allowed_total (counter)
alian_structure_rate_limit_denied_total (counter)
alian_structure_rate_limit_errors_total (counter)
alian_structure_rate_limit_storage_health (gauge)
rate-limiting.module.ts — @global() dynamic module via forRoot():
Config-driven strategy selection (RATE_LIMIT_DEFAULT_STRATEGY)
Configurable Redis key prefix
Optional in-memory fallback toggle
Modified files
rate-limit.decorator.ts — Extended RateLimitOptions with strategy and key fields
app.module.ts — Imported RateLimitingModule.forRoot(), replaced QuotaGuard with DistributedRateLimitGuard as the global guard
env.validation.ts — Added RATE_LIMIT_DEFAULT_STRATEGY, RATE_LIMIT_REDIS_KEY_PREFIX, RATE_LIMIT_FALLBACK_TO_MEMORY
.env.example / .env.production.example — Documented the new env vars
Dashboard
monitoring/grafana/dashboards/rate-limiting-dashboard.json — Grafana dashboard with overview stats, trends, per-tier pie charts, top-denied-keys table, and storage health gauge
Validation
TypeScript: clean (no errors)
ESLint: clean on all new/modified files (CRLF issues are pre-existing in untouched files)
Tests: 53 passing (rate-limiting module) + 3 existing quota guard tests still pass
Build: nest build compiles successfully
closes #132