# Rate Limiting Policy

**Last Updated**: November 3, 2025  
**Status**: ACTIVE  
**Coverage**: All public edge functions

---

## Overview

ThrillWiki enforces rate limiting on all public edge functions to prevent abuse, ensure fair usage, and protect against denial-of-service (DoS) attacks.

---

## Rate Limit Tiers

### Strict (5 requests/minute per IP)
**Use Case**: Expensive operations that consume significant resources

**Protected Endpoints**:
- `/upload-image` - File upload operations
- Future: Data exports, account deletion

**Reasoning**: File uploads are resource-intensive and should be limited to prevent storage abuse and bandwidth exhaustion.

---

### Standard (10 requests/minute per IP)
**Use Case**: Most API endpoints with moderate resource usage

**Protected Endpoints**:
- `/detect-location` - IP geolocation service
- Future: Public search/filter endpoints

**Reasoning**: Standard protection for endpoints that query external APIs or perform moderate processing.

---

### Lenient (30 requests/minute per IP)
**Use Case**: Read-only, cached endpoints with minimal resource usage

**Protected Endpoints**:
- Future: Cached entity data queries
- Future: Static content endpoints

**Reasoning**: Allow higher throughput for lightweight operations that don't strain resources.

---

### Per-User (Configurable, default 20 requests/minute)
**Use Case**: Authenticated endpoints where rate limiting by user ID provides better protection

**Protected Endpoints**:
- `/process-selective-approval` - 10 requests/minute per moderator
- Future: User-specific API endpoints

**Reasoning**: Moderators have different usage patterns than public users. Per-user limiting prevents credential sharing while allowing legitimate high-volume usage.

**Implementation**:
```typescript
const approvalRateLimiter = rateLimiters.perUser(10); // Custom limit
```

---

## Rate Limit Headers

All responses include rate limit information:

```http
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
```

**On Rate Limit Exceeded** (HTTP 429):
```http
Retry-After: 45
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
```

---

## Error Response Format

When rate limit is exceeded, you'll receive:

```json
{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Please try again later.",
  "retryAfter": 45
}
```

**HTTP Status Code**: 429 Too Many Requests

---

## Client Implementation

### Handling Rate Limits

```typescript
async function uploadImage(file: File) {
  try {
    const response = await fetch('/upload-image', {
      method: 'POST',
      body: formData,
    });

    if (response.status === 429) {
      const data = await response.json();
      const retryAfter = data.retryAfter || 60;
      
      console.warn(`Rate limited. Retry in ${retryAfter} seconds`);
      
      // Wait and retry
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      return uploadImage(file); // Retry
    }

    return response.json();
  } catch (error) {
    console.error('Upload failed:', error);
    throw error;
  }
}
```

### Exponential Backoff

For production clients, implement exponential backoff:

```typescript
async function uploadWithBackoff(file: File, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch('/upload-image', {
        method: 'POST',
        body: formData,
      });

      if (response.status !== 429) {
        return response.json();
      }

      // Exponential backoff: 1s, 2s, 4s
      const backoffDelay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, backoffDelay));
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
    }
  }
  throw new Error('Max retries exceeded');
}
```

---

## Monitoring & Metrics

### Key Metrics to Track

1. **Rate Limit Hit Rate**: Percentage of requests hitting limits
2. **429 Response Count**: Total rate limit errors by endpoint
3. **Top Rate Limited IPs**: Identify potential abuse patterns
4. **False Positive Rate**: Legitimate users hitting limits

### Alerting Thresholds

**Warning Alerts**:
- Rate limit hit rate > 5% on any endpoint
- Single IP hits rate limit > 10 times in 1 hour

**Critical Alerts**:
- Rate limit hit rate > 20% (may indicate DDoS)
- Multiple IPs hitting limits simultaneously (coordinated attack)

---

## Rate Limit Adjustments

### Increasing Limits for Legitimate Use

If you have a legitimate use case requiring higher limits:

1. **Contact Support**: Describe your use case and expected volume
2. **Verification**: We'll verify your account and usage patterns
3. **Temporary Increase**: May grant temporary limit increase
4. **Custom Tier**: High-volume verified accounts may get custom limits

**Examples of Valid Requests**:
- Bulk data migration project
- Integration with external service
- High-traffic public API client

---

## Technical Implementation

### Architecture

Rate limiting is implemented using in-memory rate limiting with:
- **Storage**: Map-based storage (IP → {count, resetAt})
- **Cleanup**: Periodic cleanup of expired entries (every 30 seconds)
- **Capacity Management**: LRU eviction when map exceeds 10,000 entries
- **Emergency Handling**: Automatic cleanup if memory pressure detected

### Memory Management

**Map Capacity**: 10,000 unique IPs tracked simultaneously
**Cleanup Interval**: Every 30 seconds or half the rate limit window
**LRU Eviction**: Removes 30% oldest entries when at capacity

### Shared Middleware

All edge functions use the shared rate limiter:

```typescript
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts';

const limiter = rateLimiters.strict; // or .standard, .lenient, .perUser(n)

serve(withRateLimit(async (req) => {
  // Your edge function logic
}, limiter, corsHeaders));
```

---

## Security Considerations

### IP Spoofing Protection

Rate limiting uses `X-Forwarded-For` header (first IP in chain):
- Trusts proxy headers in production (Cloudflare, Supabase)
- Prevents IP spoofing by using first IP only
- Falls back to `X-Real-IP` if `X-Forwarded-For` unavailable

### Distributed Attacks

**Current Limitation**: In-memory rate limiting is per-edge-function instance
- Distributed attacks across multiple instances may bypass limits
- Future: Consider distributed rate limiting (Redis, Supabase table)

**Mitigation**:
- Monitor aggregate request rates across all instances
- Use Cloudflare rate limiting as first line of defense
- Alert on unusual traffic patterns

---

## Bypassing Rate Limits

**Important**: Rate limits CANNOT be bypassed, even for authenticated users.

**Why No Bypass?**:
- Prevents credential compromise from affecting system stability
- Ensures fair usage across all users
- Protects backend infrastructure

**Moderator/Admin Considerations**:
- Per-user rate limiting allows higher individual limits
- Moderators have different tiers for moderation actions
- No complete bypass to prevent abuse of compromised accounts

---

## Testing Rate Limits

### Manual Testing

```bash
# Test upload-image rate limit (5 req/min)
for i in {1..6}; do
  curl -X POST https://api.thrillwiki.com/functions/v1/upload-image \
    -H "Authorization: Bearer $TOKEN" \
    -d '{}' && echo "Request $i succeeded"
done
# Expected: First 5 succeed, 6th returns 429
```

### Automated Testing

```typescript
describe('Rate Limiting', () => {
  test('enforces strict limits on upload-image', async () => {
    const requests = [];
    
    // Make 6 requests (limit is 5)
    for (let i = 0; i < 6; i++) {
      requests.push(fetch('/upload-image', { method: 'POST' }));
    }
    
    const responses = await Promise.all(requests);
    const statuses = responses.map(r => r.status);
    
    expect(statuses.filter(s => s === 200).length).toBe(5);
    expect(statuses.filter(s => s === 429).length).toBe(1);
  });
});
```

---

## Future Enhancements

### Planned Improvements

1. **Database-Backed Rate Limiting**: Persistent rate limiting across edge function instances
2. **Dynamic Rate Limits**: Adjust limits based on system load
3. **User Reputation System**: Higher limits for trusted users
4. **API Keys**: Rate limiting by API key for integrations
5. **Cost-Based Limiting**: Different limits for different operation costs

---

## Related Documentation

- [Security Fixes (P0)](./SECURITY_FIXES_P0.md)
- [Edge Function Development](./EDGE_FUNCTIONS.md)
- [Error Tracking](./ERROR_TRACKING.md)

---

## Troubleshooting

### "Rate limit exceeded" when I haven't made many requests

**Possible Causes**:
1. **Shared IP**: You're behind a NAT/VPN sharing an IP with others
2. **Recent Requests**: Rate limit window hasn't reset yet
3. **Multiple Tabs**: Multiple browser tabs making requests

**Solutions**:
- Wait for rate limit window to reset (shown in `Retry-After` header)
- Check browser dev tools for unexpected background requests
- Disable browser extensions that might be making requests

### Rate limit seems inconsistent

**Explanation**: Rate limiting is per-edge-function instance
- Multiple instances may have separate rate limit counters
- Distributed traffic may see different limits
- This is expected behavior for in-memory rate limiting

---

## Contact

For rate limit issues or increase requests:
- **Support**: [Contact form on ThrillWiki]
- **Documentation**: https://docs.thrillwiki.com
- **Status**: https://status.thrillwiki.com
