Create 04-CHALLENGES.md

This commit is contained in:
Carter Perez 2026-02-04 02:48:41 -05:00 committed by GitHub
parent de09e4f532
commit 5e0da67c76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 899 additions and 0 deletions

View File

@ -0,0 +1,899 @@
# Extension Challenges
You've built the base project. Now make it yours by extending it with new features.
These challenges are ordered by difficulty. Start with the easier ones to build confidence, then tackle the harder ones when you want to dive deeper.
## Easy Challenges
### Challenge 1: Add Custom Rate Limit Headers
**What to build:**
Add custom headers to rate limit responses beyond the standard RateLimit-* headers. Include headers like X-RateLimit-Policy (which rule was hit) and X-RateLimit-Scope (user/endpoint/global).
**Why it's useful:**
Debugging and client-side logic. Clients can see exactly which limit they hit and adjust behavior accordingly. If they hit the per-endpoint limit vs per-user limit, they know whether to slow down across all endpoints or just the current one.
**What you'll learn:**
- Modifying the RateLimitResult data structure
- Adding information to HTTP responses via middleware
- Debugging rate limiting behavior
**Hints:**
- Look at `src/fastapi_420/types.py:66-92` where RateLimitResult is defined. Add new fields for policy name and scope.
- Modify the `headers` property at lines 84-92 to include your new headers.
- Test it by making requests and checking response headers: `curl -i localhost:8000/api/test`
**Test it works:**
```bash
curl -i http://localhost:8000/api/test
# Should see:
# RateLimit-Limit: 100
# RateLimit-Remaining: 99
# X-RateLimit-Policy: default
# X-RateLimit-Scope: user
```
### Challenge 2: Add Endpoint-Specific Limits via Decorator
**What to build:**
Create a decorator that applies different limits to different endpoints without using middleware configuration. Allow developers to write `@rate_limit("10/minute", "100/hour")` directly on route functions.
**Why it's useful:**
More explicit than middleware configuration. Developers see limits directly in code next to the endpoint. Easier to maintain because limits live with the code they protect.
**What you'll learn:**
- Python decorators with parameters
- Preserving function metadata with functools.wraps
- Integrating with FastAPI's dependency injection system
**Hints:**
- Study the existing decorator at `src/fastapi_420/limiter.py:143-177`
- The challenge is making it work without passing request explicitly. Use FastAPI's Depends() to inject request.
- Don't forget to call `functools.wraps(func)` to preserve the original function's name and docstring.
**Test it works:**
```python
@app.get("/custom")
@rate_limit("5/minute")
async def custom_endpoint():
return {"limited": True}
# Make 5 requests, should work
# 6th request should return HTTP 420
```
### Challenge 3: Add Redis Key Prefix Per Environment
**What to build:**
Automatically prefix Redis keys with environment name (dev/staging/prod) so different environments can share a Redis instance without key collisions.
**Why it's useful:**
Cost savings. Instead of running 3 separate Redis instances, run one with namespaced keys. Development keys don't interfere with production keys.
**What you'll learn:**
- Redis key design patterns
- Environment-based configuration
- Key namespacing strategies
**Hints:**
- Modify `RateLimitKey.build()` at `src/fastapi_420/types.py:354-362` to include environment prefix
- Get environment from settings: `self._settings.ENVIRONMENT`
- Result should be: `dev:ratelimit:v1:user:...` instead of `ratelimit:v1:user:...`
**Test it works:**
```bash
# Set environment
export RATELIMIT_ENVIRONMENT=staging
# Start app, make requests
# Check Redis keys
redis-cli keys "*"
# Should see: staging:ratelimit:v1:user:...
```
## Intermediate Challenges
### Challenge 4: Implement Request Cost Weighting
**What to build:**
Allow different requests to consume different amounts of rate limit "budget." A simple GET might cost 1 point, while an expensive report generation costs 10 points. Clients with 100 points/minute can make 100 simple requests or 10 expensive ones.
**Real world application:**
OpenAI's API uses this pattern. Different models cost different amounts of "tokens." GPT-4 is more expensive than GPT-3.5. Same concept applies to APIs where some endpoints are computationally expensive.
**What you'll learn:**
- Extending rate limiting beyond simple request counting
- Designing flexible APIs that account for resource consumption
- Balancing simplicity with power
**Implementation approach:**
1. **Add cost parameter to check method**
- Files to create: None (modify existing)
- Files to modify: `src/fastapi_420/limiter.py:181`, `src/fastapi_420/algorithms/base.py:26`
2. **Multiply counter by cost**
- Hook into storage increment operations
- Pass cost to Lua scripts
- Update sliding window algorithm to handle weighted counts
3. **Test edge cases:**
- What if cost=0? (Free requests)
- What if cost=1000 but limit is 100? (Single request exhausts limit)
- What if cost is negative? (Should reject as invalid)
**Hints:**
- Start by adding an optional `cost: int = 1` parameter to `limiter.check()`
- In the Lua script, change `redis.call('INCR', current_key)` to `redis.call('INCRBY', current_key, cost)`
- For testing, create endpoints with different costs: `/cheap` (cost=1), `/expensive` (cost=10)
**Extra credit:**
Make cost configurable per endpoint via decorator: `@limiter.limit("100/minute", cost=5)`
### Challenge 5: Add CAPTCHA Challenge for Suspicious Clients
**What to build:**
When a client hits rate limits repeatedly, instead of blocking them completely, return a CAPTCHA challenge. If they solve it, allow the request through. Track CAPTCHA success rate per client.
**Real world application:**
Cloudflare's "I'm Under Attack" mode works this way. Suspicious traffic gets CAPTCHA challenges instead of hard blocks. Reduces false positives (blocking legitimate users) while still stopping bots.
**What you'll learn:**
- Integrating third-party services (hCaptcha, reCAPTCHA)
- Progressive enforcement strategies
- Balancing security with user experience
**Implementation approach:**
1. **Track violation count per client**
- Add counter: "violations:{identifier}" in Redis
- Increment on rate limit exceeded
- Reset on successful CAPTCHA
2. **Return CAPTCHA challenge instead of 420**
- When violations > threshold (e.g., 3), return HTTP 429 with CAPTCHA challenge
- Response body includes CAPTCHA site key
- Client solves CAPTCHA, submits solution token
3. **Validate CAPTCHA solution**
- New endpoint: POST /verify-captcha
- Validate token with CAPTCHA API
- If valid, grant temporary bypass (store token in Redis with expiration)
4. **Bypass rate limit with valid CAPTCHA**
- Check for CAPTCHA bypass token in fingerprint extraction
- If present and valid, skip rate limit check
**Hints:**
- Use hCaptcha (simpler API than reCAPTCHA)
- Store bypass tokens with 5-minute expiration
- Track CAPTCHA solve rate: `captcha_solved / captcha_presented`
**Testing:**
Can't easily test CAPTCHA in automated tests. Instead:
1. Create mock CAPTCHA verifier that always returns true
2. Inject mock in test environment
3. In production, use real hCaptcha API
### Challenge 6: Implement Geolocation-Based Rate Limits
**What to build:**
Apply different rate limits based on client geographic location. Stricter limits for regions with high bot activity (e.g., limit clients from certain countries to 10/minute vs 100/minute for domestic traffic).
**Real world application:**
E-commerce sites often see bot traffic primarily from certain regions. Applying regional limits reduces fraud without impacting legitimate international customers.
**What you'll learn:**
- IP geolocation databases (MaxMind GeoIP2)
- Policy-based rate limiting
- Geographic discrimination considerations (be careful with this)
**Implementation approach:**
1. **Add geolocation lookup**
- Library: `geoip2` with MaxMind database
- On fingerprint extraction, lookup IP address
- Add country code to FingerprintData
2. **Define geo-based policies**
- Config: `GEO_LIMITS = {"US": "100/minute", "CN": "10/minute", "default": "50/minute"}`
- Load from environment or config file
3. **Apply policy at rate limit check**
- In LayeredDefense, check client's country code
- Select appropriate limit from GEO_LIMITS
- Fall back to default if country not configured
4. **Monitor and adjust**
- Log rate limit violations by country
- Dashboard showing requests/violations per region
**Hints:**
- MaxMind GeoLite2 database is free but requires registration
- Cache geo lookups (IP to country mapping doesn't change frequently)
- Consider privacy implications. Logging IP addresses with country codes might be PII in some jurisdictions.
**Ethical considerations:**
Geo-based limiting can be discriminatory. Only use it for legitimate security purposes. Document why certain regions have stricter limits. Consider offering CAPTCHA challenge instead of hard blocking.
## Advanced Challenges
### Challenge 7: Build a Rate Limit Dashboard
**What to build:**
Web dashboard showing real-time rate limiting stats. Metrics include requests/sec, top violators, circuit breaker status, algorithm performance. Built with FastAPI + htmx or React.
**Why this is hard:**
Requires collecting metrics without impacting rate limiting performance. Need efficient aggregation of high-frequency events. Must handle dashboard queries without slowing down rate limit checks.
**What you'll learn:**
- High-performance metrics collection
- Time-series data aggregation
- Building admin dashboards
- WebSocket or Server-Sent Events for real-time updates
**Architecture changes needed:**
```
┌─────────────────┐
│ Rate Limiter │
│ (existing) │
└────────┬────────┘
├─ Check limit (fast path)
└─ Emit metrics (async, non-blocking)
┌────────────────┐
│ Metrics Buffer │
│ (in-memory) │
└────────┬───────┘
Batch write every 5 seconds
┌────────────────┐
│ Redis TimeSeries│
│ or InfluxDB │
└────────┬───────┘
┌────────────────┐
│ Dashboard │
│ (FastAPI) │
└────────────────┘
```
**Implementation steps:**
**Phase 1: Metrics Collection** (3-5 hours)
- Add metrics emitter to rate limiter
- Batch metrics in memory buffer
- Flush to Redis every 5 seconds
- Metrics: requests_total, violations_total, latency_histogram
**Phase 2: Aggregation** (3-5 hours)
- Create background task to aggregate 5-second metrics into 1-minute/1-hour/1-day buckets
- Store in Redis sorted sets: `ZADD metrics:requests:minute:{timestamp} {count} {endpoint}`
- Retention: Keep 5-second data for 10 minutes, 1-minute data for 24 hours, 1-hour data for 30 days
**Phase 3: Dashboard Backend** (4-6 hours)
- FastAPI endpoints:
- `GET /api/metrics/summary` - Current state (last minute stats)
- `GET /api/metrics/timeseries?metric=requests&window=1h` - Historical data
- `GET /api/metrics/top-violators?limit=10` - Clients with most violations
- WebSocket endpoint: `/ws/metrics` - Real-time updates
**Phase 4: Dashboard Frontend** (6-8 hours)
- Chart.js or Recharts for time-series graphs
- Auto-refresh every 5 seconds
- Cards showing: Total requests, Rate limit violations, Circuit breaker status, Top endpoints
- Table of recent violations with client fingerprint (truncated), endpoint, timestamp
**Gotchas:**
- **Don't block rate limit checks**: Metrics collection must be async. Use `asyncio.create_task()` to fire-and-forget.
- **Buffer overflow**: If metrics buffer grows too large (million events), drop oldest or sample.
- **Redis memory**: Time-series data grows fast. Set expiration policies.
**Success criteria:**
Your implementation should:
- [ ] Collect metrics without adding >5ms latency to rate limit checks
- [ ] Display requests/sec updated every 5 seconds
- [ ] Show top 10 rate limit violators with endpoint breakdown
- [ ] Visualize circuit breaker state transitions
- [ ] Handle 10k requests/sec without overloading dashboard backend
### Challenge 8: Implement Distributed Circuit Breaker
**What to build:**
Circuit breaker that shares state across multiple API servers using Redis. When one server trips the circuit, all servers immediately enter the same state. Current implementation is per-process only.
**Estimated time:**
2-3 days for full implementation with testing
**Prerequisites:**
You should have completed Challenge 4 (request cost weighting) and Challenge 7 (dashboard) first because understanding metrics is crucial for distributed coordination.
**What you'll learn:**
- Distributed systems coordination
- Redis pub/sub for event propagation
- Eventually consistent state management
- Handling network partitions and race conditions
**Planning this feature:**
Before you code, think through:
- **Consensus**: How do servers agree on circuit state? (Redis as single source of truth)
- **Propagation delay**: Server A trips circuit, how long until Server B knows? (Pub/sub gives ~100ms)
- **Failure modes**: What if Redis connection fails? (Fall back to local circuit breaker)
- **Race conditions**: Multiple servers trying to trip circuit simultaneously (Use Redis SET NX)
**High level architecture:**
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Server 1 │ │ Server 2 │ │ Server 3 │
│ Circuit │ │ Circuit │ │ Circuit │
│ Breaker │ │ Breaker │ │ Breaker │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└──────────────────────┼──────────────────────┘
┌──────────▼───────────┐
│ Redis │
│ Circuit State: │
│ - is_open │
│ - trip_time │
│ - request_count │
│ │
│ Pub/Sub Channel: │
│ circuit:events │
└──────────────────────┘
```
**Implementation phases:**
**Phase 1: Shared State** (6-8 hours)
- Store circuit state in Redis hash: `circuit:state`
- Fields: is_open (0/1), last_trip_time, failure_count, request_count
- Each server reads state before checking thresholds
- Use Redis TTL to auto-reset after recovery_time
**Phase 2: Atomic Tripping** (4-6 hours)
- Use Lua script for atomic trip operation:
```lua
local current_count = redis.call('GET', 'circuit:requests')
if current_count >= threshold then
redis.call('HSET', 'circuit:state', 'is_open', '1')
redis.call('HSET', 'circuit:state', 'trip_time', now)
redis.call('PUBLISH', 'circuit:events', 'TRIPPED')
return 1
end
return 0
```
- Only one server successfully trips, others see is_open=1 immediately
**Phase 3: Event Propagation** (5-7 hours)
- Subscribe to Redis pub/sub channel: `circuit:events`
- Events: TRIPPED, HALF_OPEN, CLOSED
- Background task listens for events, updates local cache
- Local cache reduces Redis reads (check local first, Redis if stale)
**Phase 4: Graceful Degradation** (4-6 hours)
- If Redis connection fails, fall back to local circuit breaker
- Log warning: "Distributed circuit breaker unavailable, using local"
- When Redis reconnects, sync local state with distributed state
- Handle case where local circuit is open but distributed is closed (trust distributed)
**Known challenges:**
1. **Clock Skew**
- Problem: Servers have slightly different clocks. Server A thinks circuit should close at 12:00:30, Server B thinks 12:00:31.
- Hint: Use Redis TIME command to get server time, not local time. Or use TTL-based expiration instead of timestamp comparison.
2. **Network Partition**
- Problem: Server C can't reach Redis, falls back to local circuit. Local circuit trips at lower threshold, blocks legitimate traffic.
- Hint: Add "confidence" metric. If Redis connection is flaky, increase thresholds or disable circuit entirely (fail-open).
3. **Thundering Herd**
- Problem: Circuit opens, 100 servers all try to enter half-open state simultaneously after recovery_time.
- Hint: Randomize recovery time: `recovery_time ± random(0, 5)` so servers stagger their retry attempts.
**Success criteria:**
Your implementation should:
- [ ] Trip circuit across all servers within 500ms of threshold exceeded
- [ ] Sync state even if a server restarts (reads from Redis on startup)
- [ ] Gracefully handle Redis connection failure (fall back to local)
- [ ] Support manual circuit state changes via Redis CLI: `HSET circuit:state is_open 1`
- [ ] Pass load test: 10 servers, 100k requests/sec, circuit trips correctly across all
### Challenge 9: Build Custom Algorithm - Generic Cell Rate Algorithm (GCRA)
**What to build:**
Implement the GCRA algorithm used by ATM networks and some high-performance rate limiters. More precise than token bucket, allows bursting within a defined "cell delay variation tolerance."
**Estimated time:**
3-4 days including research, implementation, and thorough testing
**Prerequisites:**
Strong understanding of token bucket and sliding window algorithms. Read the GCRA spec before starting.
**What you'll learn:**
- Telecoms network algorithms
- Sub-second precision rate limiting
- Continuous vs discrete time modeling
- Algorithm correctness proofs
**How GCRA works:**
GCRA tracks "Theoretical Arrival Time" (TAT). Each request should arrive no earlier than TAT. If request arrives at time T:
```
if T >= TAT:
TAT = max(TAT, T) + 1/rate
ALLOW
else:
DENY
```
With burst tolerance, allow requests that arrive up to `limit` time units early:
```
if T >= TAT - limit:
TAT = max(TAT, T) + 1/rate
ALLOW
else:
DENY
```
**Implementation:**
1. **Create algorithm file**: `src/fastapi_420/algorithms/gcra.py`
2. **Define state**: Store TAT in Redis/memory
3. **Implement check method**:
```python
async def check(self, storage, key, rule, timestamp=None):
now = timestamp or time.time()
rate = rule.requests / rule.window_seconds # requests per second
limit = rule.requests # burst tolerance
tat = await storage.get_gcra_state(key)
if tat is None:
tat = now
if now >= tat - limit / rate:
# Allow
new_tat = max(tat, now) + 1/rate
await storage.set_gcra_state(key, new_tat, ttl=rule.window_seconds)
remaining = calculate_remaining(new_tat, now, rate, limit)
return RateLimitResult(allowed=True, remaining=remaining, ...)
else:
# Deny
return RateLimitResult(allowed=False, retry_after=tat - now, ...)
```
4. **Add storage methods**: `get_gcra_state()` and `set_gcra_state()` to storage backends
5. **Write comprehensive tests**: Edge cases like clock adjustments, very high rates, fractional rates
**Testing strategy:**
```python
# Test burst tolerance
rule = RateLimitRule(requests=10, window_seconds=10) # 1 req/sec, burst of 10
algo = GCRAAlgorithm()
# Should allow 10 immediate requests
for i in range(10):
assert algo.check(storage, "test", rule, timestamp=0).allowed
# 11th should deny
assert not algo.check(storage, "test", rule, timestamp=0).allowed
# After 1 second, should allow 1 more
assert algo.check(storage, "test", rule, timestamp=1.0).allowed
```
**Compare with token bucket:**
Both algorithms allow bursting, but GCRA is more precise. Token bucket updates in discrete increments (add X tokens every Y seconds). GCRA uses continuous time (TAT can be any float value).
## Mix and Match
Combine features for bigger projects:
**Project Idea 1: Full Rate Limiting Dashboard with Geo Limits**
- Combine Challenge 7 (Dashboard) + Challenge 6 (Geolocation)
- Add map visualization showing requests by country
- Admin panel to adjust geo limits in real-time
- Result: Production-ready rate limiting with geographic policies
**Project Idea 2: Cost-Weighted Limits with CAPTCHA Bypass**
- Combine Challenge 4 (Cost Weighting) + Challenge 5 (CAPTCHA)
- Expensive operations cost more points
- When points exhausted, offer CAPTCHA to get temporary boost
- Result: Flexible system that allows bursts with verification
## Real World Integration Challenges
### Integrate with Prometheus for Monitoring
**The goal:**
Export rate limiting metrics to Prometheus for visualization in Grafana. Track requests, violations, algorithm latency, storage health.
**What you'll need:**
- prometheus-client library
- Grafana dashboard JSON
- Understanding of metric types (counter, gauge, histogram)
**Implementation plan:**
1. Add prometheus_client to dependencies
2. Create metrics in `src/fastapi_420/metrics.py`:
```python
from prometheus_client import Counter, Histogram
requests_total = Counter(
'ratelimit_requests_total',
'Total requests checked',
['algorithm', 'allowed']
)
check_latency = Histogram(
'ratelimit_check_duration_seconds',
'Time to check rate limit'
)
```
3. Instrument code: `requests_total.labels(algorithm='sliding_window', allowed='true').inc()`
4. Expose metrics endpoint: `@app.get("/metrics")` returns `prometheus_client.generate_latest()`
5. Configure Prometheus scrape config, point at `/metrics`
**Watch out for:**
- High cardinality labels (don't include user IDs in labels, use <10 unique values per label)
- Metric name conventions (use underscores, suffix with unit)
- Performance impact (prometheus_client is fast but not free, adds ~50μs per metric update)
### Deploy to Kubernetes
**The goal:**
Run the rate-limited API in Kubernetes with Redis, monitoring, and auto-scaling.
**What you'll learn:**
- Kubernetes deployments and services
- ConfigMaps and Secrets for configuration
- Horizontal Pod Autoscaler
- Redis deployment with persistence
**Steps:**
1. **Create Dockerfile** for API:
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0"]
```
2. **Kubernetes manifests**:
```yaml
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
template:
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: your-api:latest
env:
- name: RATELIMIT_REDIS_URL
value: "redis://redis:6379"
ports:
- containerPort: 8000
# api-service.yaml
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
```
3. **ConfigMap** for settings:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ratelimit-config
data:
RATELIMIT_ALGORITHM: "sliding_window"
RATELIMIT_DEFAULT_LIMIT: "100/minute"
RATELIMIT_CIRCUIT_THRESHOLD: "10000"
```
4. **Horizontal Pod Autoscaler**:
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
```
**Production checklist:**
- [ ] Redis has persistent volume for data
- [ ] Secrets used for sensitive config (not ConfigMap)
- [ ] Resource limits set (CPU/memory)
- [ ] Liveness and readiness probes configured
- [ ] Logging goes to stdout (captured by Kubernetes)
- [ ] Metrics endpoint exposed for Prometheus scraping
## Performance Challenges
### Challenge: Handle 100k Requests/Second
**The goal:**
Optimize the rate limiter to handle 100k requests/sec on a single machine without falling over.
**Current bottleneck:**
At 10k req/sec, Redis network latency becomes limiting factor. Each request = 1 Redis call = 1ms round trip = max 1000 req/sec per connection.
**Optimization approaches:**
**Approach 1: Connection Pooling**
- How: Increase `REDIS_MAX_CONNECTIONS` from 100 to 1000
- Gain: 100x parallelism, can handle 100k req/sec
- Tradeoff: Redis connection overhead, more memory
**Approach 2: Local Caching with Eventual Consistency**
- How: Cache rate limit state in-memory for 100ms. Only sync with Redis every 100ms.
- Gain: Reduce Redis calls by 90%, max 10x improvement
- Tradeoff: Accuracy drops to ~95%. Two servers might allow 110/100 requests in a window.
**Approach 3: Batching**
- How: Accumulate requests for 10ms, send to Redis in single pipeline
- Gain: Reduce network overhead, ~5x improvement
- Tradeoff: 10ms added latency, complexity
**Benchmark it:**
```bash
# Load test with wrk
wrk -t12 -c400 -d30s http://localhost:8000/api/test
# Should see:
# Requests/sec: 100000
# Latency avg: 4ms
# Latency 99th: 15ms
```
Target metrics:
- **Throughput**: >100k requests/sec
- **Latency p50**: <5ms
- **Latency p99**: <20ms
### Challenge: Reduce Memory Usage by 90%
**The goal:**
Reduce memory consumption for 1 million active rate limit keys from ~100MB to ~10MB.
**Current usage:**
Each key stores:
- Python dict entry: 56 bytes
- WindowEntry: 24 bytes
- String key (~50 chars): 50 bytes
Total: ~130 bytes/key * 1M keys = 130MB
**Optimization areas:**
**Area 1: Use Redis instead of Memory**
- What's inefficient: Python objects have overhead
- How to fix: Store in Redis (more memory efficient)
- Savings: 70% reduction
**Area 2: Compress Keys**
- What's inefficient: Keys like "ratelimit:v1:user:POST:/api/endpoint:192.168.1.1:Mozilla...:60" are 100+ chars
- How to fix: Hash keys to 16-char hex strings
- Savings: 80% reduction in key size
**Area 3: Use Slots**
- What's inefficient: Python's `__dict__` attribute on dataclasses
- How to fix: Add `__slots__` to WindowEntry, RateLimitResult
- Savings: 30% reduction in object size
## Security Challenges
### Challenge: Add JWT-Based Rate Limit Tiers
**What to implement:**
Different rate limits based on JWT claims. Free tier: 10/minute. Paid tier: 100/minute. Enterprise: 1000/minute.
**Threat model:**
This protects against:
- Free tier abuse (creating many accounts to get more quota)
- Fair resource allocation (paying customers get what they paid for)
**Implementation:**
1. **Extract tier from JWT**:
```python
# In auth extractor
payload = jwt.decode(token, secret, algorithms=['HS256'])
tier = payload.get('tier', 'free') # free, paid, enterprise
```
2. **Add tier to fingerprint**:
```python
# In FingerprintData
tier: str | None = None
```
3. **Select limit based on tier**:
```python
# In RateLimiter.check
tier_limits = {
'free': ['10/minute'],
'paid': ['100/minute'],
'enterprise': ['1000/minute'],
}
rules = [RateLimitRule.parse(l) for l in tier_limits.get(tier, tier_limits['free'])]
```
**Testing the security:**
- Try to forge JWT with tier=enterprise
- Should fail signature validation
- Try to create free account, abuse endpoint
- Should hit 10/minute limit
- Verify paid users can exceed free tier limits
### Challenge: Pass OWASP API Security Top 10
**The goal:**
Audit the rate limiter against OWASP API Security Top 10 and fix any gaps.
**Current gaps:**
- **API4:2023 - Unrestricted Resource Consumption**: Partially covered by rate limiting. Gap: No per-IP limits on unauthenticated endpoints.
- **API5:2023 - Broken Function Level Authorization**: Gap: Circuit breaker bypass doesn't check if "authenticated" user actually has permission.
- **API8:2023 - Security Misconfiguration**: Gap: No validation that production has Redis configured.
**Remediation:**
1. **Add per-IP limits for unauthenticated**:
```python
if not fingerprint.auth_identifier:
# Stricter limits for anonymous
rules = [RateLimitRule.parse('10/minute')]
```
2. **Add permission check to circuit bypass**:
```python
def _should_bypass_circuit(self, context):
if not context.is_authenticated:
return False
# Check actual permissions, not just authentication
return context.has_permission('circuit.bypass')
```
3. **Add production validation**:
```python
@model_validator(mode='after')
def validate_production(self):
if self.ENVIRONMENT == 'production':
if not self.storage.REDIS_URL:
raise ValueError("Production requires Redis")
return self
```
## Contribution Ideas
Finished a challenge? Share it back:
1. **Fork the repo**
2. **Implement your extension** in a feature branch: `git checkout -b feature/captcha-challenge`
3. **Document it** - Add to learn/ folder explaining how it works
4. **Submit a PR** with:
- Your implementation
- Tests proving it works
- Documentation in learn/ folder
- Example usage in examples/
Good extensions might get merged into the main project. Even if not merged, you'll have a portfolio piece showing real-world security engineering.
## Challenge Yourself Further
### Build Something New
Use the concepts you learned here to build:
- **API Gateway with rate limiting** - Reverse proxy that adds rate limiting to any backend API
- **Database query rate limiter** - Limit queries/second to prevent database overload
- **Distributed task queue throttler** - Rate limit background job execution
### Study Real Implementations
Compare your implementation to production tools:
- **Kong API Gateway** - Supports multiple rate limiting strategies, read their Lua plugin code
- **Cloudflare Workers** - Edge-based rate limiting, study their architecture blog posts
- **GitHub API** - Read their rate limiting docs, understand their tier system
Read their code, understand their tradeoffs, steal their good ideas.
### Write About It
Document your extensions:
- Blog post: "Adding Geolocation-Based Rate Limiting to FastAPI"
- Tutorial: "How I Built a Rate Limiting Dashboard in a Weekend"
- Comparison: "Sliding Window vs GCRA: Performance Benchmarks"
Teaching others is the best way to verify you understand it.
## Getting Help
Stuck on a challenge?
1. **Debug systematically**
- What did you expect? (Specific behavior)
- What actually happened? (Logs, errors, wrong output)
- What's the smallest test case that reproduces it?
2. **Read the existing code**
- Similar features already implemented? (Look at algorithms/ for patterns)
- Tests showing how components work? (tests/test_*.py has examples)
3. **Search for similar problems**
- GitHub issues on similar projects
- Stack Overflow for specific errors
- Redis/FastAPI docs for API questions
4. **Ask for help**
- Open a GitHub discussion with:
- Challenge you're working on
- What you've tried
- Specific error or unexpected behavior
- Your hypothesis about what's wrong
- Don't just paste errors. Explain your understanding.
## Challenge Completion
Track your progress:
- [ ] Easy Challenge 1: Custom headers
- [ ] Easy Challenge 2: Decorator syntax
- [ ] Easy Challenge 3: Environment prefixes
- [ ] Intermediate Challenge 4: Request cost weighting
- [ ] Intermediate Challenge 5: CAPTCHA integration
- [ ] Intermediate Challenge 6: Geolocation limits
- [ ] Advanced Challenge 7: Rate limit dashboard
- [ ] Advanced Challenge 8: Distributed circuit breaker
- [ ] Advanced Challenge 9: GCRA algorithm
Completed all of them? You've mastered rate limiting. Time to build something new or contribute back to the community.