ResCanvas implements comprehensive API rate limiting to protect backend services from abuse, ensure fair resource allocation, and maintain system stability. The rate limiting system uses Redis for distributed counters and Flask-Limiter for enforcement.
- Anonymous users: 1000 requests/hour
- Authenticated users: 5000 requests/hour
These limits apply to all API endpoints as a baseline.
| Endpoint | Limit | Reason |
|---|---|---|
| POST /auth/login | 100/hour | Prevent brute force attacks |
| POST /auth/register | 50/hour | Prevent spam accounts |
| POST /auth/refresh | 200/hour | Normal token refresh patterns |
| Endpoint | Limit | Reason |
|---|---|---|
| POST /submitNewLineRoom | 300/minute | Active drawing sessions |
| POST /rooms/<id>/strokes | 300/minute | Alternative stroke endpoint |
| POST /rooms/<id>/undo | 60/minute | Reasonable undo frequency |
| POST /rooms/<id>/redo | 60/minute | Reasonable redo frequency |
| Endpoint | Limit | Reason |
|---|---|---|
| POST /rooms | 10/hour | Prevent room spam |
| POST /submitClearCanvasTimestamp | 5/minute per room | Prevent clear spam |
| PUT /rooms/<id> | 20/minute | Normal editing frequency |
| Endpoint | Limit | Reason |
|---|---|---|
| GET /users/suggest | 30/minute | User search |
| GET /rooms/suggest | 30/minute | Room search |
All rate limits can be customized via environment variables:
# Enable/disable rate limiting
RATE_LIMIT_ENABLED=True
# Redis storage for rate limit counters
RATE_LIMIT_STORAGE=redis://localhost:6379
# Global limits
RATE_LIMIT_GLOBAL_HOURLY=1000
RATE_LIMIT_GLOBAL_AUTH_HOURLY=5000
# Authentication endpoints
RATE_LIMIT_LOGIN_HOURLY=100
RATE_LIMIT_REGISTER_HOURLY=50
RATE_LIMIT_REFRESH_HOURLY=200
# Stroke operations
RATE_LIMIT_STROKE_MINUTE=300
RATE_LIMIT_UNDO_REDO_MINUTE=60
# Room operations
RATE_LIMIT_ROOM_CREATE_HOURLY=10
RATE_LIMIT_ROOM_CLEAR_MINUTE=5
RATE_LIMIT_ROOM_UPDATE_MINUTE=20
# Search and discovery
RATE_LIMIT_SEARCH_MINUTE=30
# Burst protection
RATE_LIMIT_BURST_SECOND=10For development or testing, rate limiting can be disabled:
RATE_LIMIT_ENABLED=FalseAll API responses include rate limit headers:
X-RateLimit-Limit: 300 # Maximum requests allowed
X-RateLimit-Remaining: 245 # Requests remaining in window
X-RateLimit-Reset: 1730000000 # Unix timestamp when limit resetsWhen rate limited (429 response), additional header:
Retry-After: 45 # Seconds to wait before retryingWhen rate limit is exceeded (HTTP 429):
{
"status": "error",
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please try again in 45 seconds."
}The enhanced API client (frontend/src/api/apiClient.js) automatically handles rate limits:
import apiClient from './api/apiClient';
try {
const room = await apiClient.post('/rooms', {
name: 'My Room',
type: 'public'
});
} catch (error) {
if (error.status === 429) {
// Rate limited - auto-retry is already attempted
console.log('Rate limited:', error.message);
}
}Use the RateLimitWarning component:
import RateLimitWarning from './components/RateLimitWarning';
function MyComponent() {
const [rateLimitInfo, setRateLimitInfo] = useState(null);
try {
await apiClient.post('/rooms/<id>/strokes', strokeData);
} catch (error) {
if (error.status === 429) {
setRateLimitInfo({
exceeded: true,
limit: error.rateLimitInfo?.limit,
remaining: 0,
reset: error.rateLimitInfo?.reset,
});
}
}
return (
<div>
{rateLimitInfo && (
<RateLimitWarning
rateLimitInfo={rateLimitInfo}
onDismiss={() => setRateLimitInfo(null)}
/>
)}
{/* Your component content */}
</div>
);
}The rate limit handler includes automatic retry:
import { retryWithBackoff } from './utils/rateLimitHandler';
const result = await retryWithBackoff(
async () => {
return await apiClient.post('/endpoint', data);
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, delay) => {
console.log(`Retrying attempt ${attempt} after ${delay}ms`);
}
}
);- Import the limiter:
from middleware.rate_limit import limiter
from config import RATE_LIMIT_STROKE_MINUTE- Apply decorator:
@my_blueprint.route('/my-endpoint', methods=['POST'])
@require_auth
@limiter.limit(f"{RATE_LIMIT_STROKE_MINUTE}/minute")
def my_endpoint():
# Your endpoint logic
pass- Room-specific limits:
@limiter.limit("5/minute", key_func=lambda: request.get_json().get('roomId'))
def room_specific_endpoint():
passFor complex scenarios:
from middleware.rate_limit import user_rate_limit, room_specific_limit
@my_blueprint.route('/endpoint', methods=['POST'])
@user_rate_limit("100/hour") # Per authenticated user
def my_endpoint():
passRate limit violations are automatically logged:
WARNING: Rate limit exceeded: user_id=abc123, ip=192.168.1.1, endpoint=submit_stroke, method=POST
Check current rate limit counters:
redis-cli
> KEYS LIMITER/*
> GET LIMITER/<key>Monitor rate limit hits in your logging/metrics system. Key metrics:
- Number of 429 responses per endpoint
- Top rate-limited IPs/users
- Rate limit reset frequency
- Average retry attempts
Run rate limiting tests:
cd backend
pytest tests/test_rate_limiting.py -vTest rate limits with rapid requests:
# Test login rate limit (should block after 100 requests)
for i in {1..105}; do
curl -X POST http://localhost:10010/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test"}'
echo ""
done- Always check response headers - Monitor
X-RateLimit-Remaining - Implement retry logic - Use exponential backoff
- Handle 429 gracefully - Show user-friendly messages
- Queue requests - Don't spam the API
- Cache responses - Reduce unnecessary requests
- Monitor Redis - Ensure rate limit counters are working
- Adjust limits - Based on actual usage patterns
- Set alerts - For abnormal rate limit hits
- Log violations - Track abuse patterns
-
Check Redis connection:
redis-cli ping
-
Verify environment variables:
echo $RATE_LIMIT_ENABLED echo $RATE_LIMIT_STORAGE
-
Check limiter initialization in
app.py
If legitimate users are being rate limited:
- Check if limits are too restrictive
- Verify IP detection is working correctly
- Consider whitelisting specific IPs/users
- Adjust limits via environment variables
If Redis memory is growing:
- Rate limit keys expire automatically
- Check TTL on keys:
redis-cli TTL LIMITER/<key> - Consider separate Redis instance for rate limiting
- Rate limits are server-side - Never trust client-side throttling
- Per-IP tracking - Prevents distributed abuse
- Authentication-aware - Different limits for authenticated users
- Bypass protection - Limits cannot be circumvented client-side
- Graceful degradation - If Redis fails, requests still succeed (swallow_errors=True)
Potential improvements:
- Adaptive rate limiting based on server load
- Whitelist/blacklist support
- Per-user custom limits
- Geographic rate limiting
- Real-time rate limit dashboard
- Machine learning for abuse detection