Skip to content

Rate Limiting

RankHub uses a Durable Object-based rate limiting system (RateLimitDO) for distributed, consistent rate enforcement across all Cloudflare edge locations.

Overview

Rate limiting is implemented as a Cloudflare Durable Object (RateLimitDO in apps/do-worker/src/RateLimitDO.ts). Each rate limit scope gets its own DO instance, providing:

  • Sub-20ms response times -- In-memory sliding window counters.
  • Distributed consistency -- Durable Object single-threaded guarantee means no race conditions.
  • Automatic cleanup -- Expired entries are purged every 5 minutes.

Algorithm

RateLimitDO uses a sliding window counter algorithm:

  1. Each client (identified by IP hash or user ID) has a counter and window start time.
  2. On each request, check if the current window has expired. If so, reset the counter.
  3. If the counter is below the limit, increment and allow the request.
  4. If the counter exceeds the limit, reject with a 429 Too Many Requests response.

Rate Limit Tiers

API Requests (General)

ScopeAuthenticatedAnonymousWindow
api30 req/min10 req/min60s

Voting

ScopeAuthenticatedAnonymousWindow
vote30 req/min10 req/min60s

Voting uses a hybrid approach: the web app enforces a client-side 2-second cooldown between votes (1 vote / 2s = 30 votes / 60s), while the DO enforces the server-side limit.

Authentication

EndpointLimitWindow
POST /auth/signup5 req/hour3600s
POST /auth/signin20 req/min60s
POST /auth/forgot-password3 req/hour3600s

Response Headers

Rate-limited responses include standard headers:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000060
Retry-After: 42

Client Identification

  • Authenticated users: Rate limited by user ID (from JWT).
  • Anonymous users: Rate limited by SHA-256 hash of IP address + IP_HASH_SALT.
  • The salt ensures rate limit keys cannot be reverse-engineered from DO state.

Configuration

Default limits are defined as constants in RateLimitDO.ts:

  • DEFAULT_MAX_REQUESTS = 30
  • DEFAULT_WINDOW_MS = 60_000 (1 minute)
  • CLEANUP_INTERVAL_MS = 300_000 (5 minutes)