Add rate limiting to login endpoint

Security:
- Add slowapi dependency for rate limiting
- Create rate_limit.py module with configurable limits
- Apply 5 requests/minute limit to login endpoint
- Make rate limit configurable via ORCHARD_LOGIN_RATE_LIMIT env var

Testing:
- Set high rate limit (1000/min) in docker-compose.local.yml for tests
- All 265 tests pass
This commit is contained in:
Mondo Diaz
2026-01-08 18:18:29 -06:00
parent d61c7a71fb
commit 6aa199b80b
5 changed files with 30 additions and 1 deletions

View File

@@ -1,15 +1,19 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from contextlib import asynccontextmanager
import logging
import os
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from .config import get_settings
from .database import init_db, SessionLocal
from .routes import router
from .seed import seed_database
from .auth import create_default_admin
from .rate_limit import limiter
settings = get_settings()
logging.basicConfig(level=logging.INFO)
@@ -55,6 +59,10 @@ app = FastAPI(
lifespan=lifespan,
)
# Set up rate limiting
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Include API routes
app.include_router(router)

16
backend/app/rate_limit.py Normal file
View File

@@ -0,0 +1,16 @@
"""Rate limiting configuration for Orchard API.
Uses slowapi for rate limiting with IP-based keys.
"""
import os
from slowapi import Limiter
from slowapi.util import get_remote_address
# Rate limiter - uses IP address as key
limiter = Limiter(key_func=get_remote_address)
# Rate limit strings - configurable via environment for testing
# Default: 5 login attempts per minute per IP
# In tests: set ORCHARD_LOGIN_RATE_LIMIT to a high value like "1000/minute"
LOGIN_RATE_LIMIT = os.environ.get("ORCHARD_LOGIN_RATE_LIMIT", "5/minute")

View File

@@ -374,9 +374,11 @@ from .auth import (
check_project_access,
AuthorizationService,
)
from .rate_limit import limiter, LOGIN_RATE_LIMIT
@router.post("/api/v1/auth/login", response_model=LoginResponse)
@limiter.limit(LOGIN_RATE_LIMIT)
def login(
login_request: LoginRequest,
request: Request,

View File

@@ -10,6 +10,7 @@ pydantic-settings==2.1.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
bcrypt==4.0.1
slowapi==0.1.9
# Test dependencies
pytest>=7.4.0