Merge pull request 'Merge from Dev to LOAF Production' (#23) from dev into loaf-prod
Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
Binary file not shown.
144
server.py
144
server.py
@@ -60,11 +60,29 @@ async def lifespan(app: FastAPI):
|
||||
# Shutdown
|
||||
logger.info("Application shutdown")
|
||||
|
||||
# Environment detection
|
||||
ENVIRONMENT = os.environ.get('ENVIRONMENT', 'development')
|
||||
IS_PRODUCTION = ENVIRONMENT == 'production'
|
||||
|
||||
# Security: Disable API documentation in production
|
||||
if IS_PRODUCTION:
|
||||
print("🔒 Production mode: API documentation disabled")
|
||||
app_config = {
|
||||
"lifespan": lifespan,
|
||||
"root_path": "/membership",
|
||||
"docs_url": None, # Disable /docs
|
||||
"redoc_url": None, # Disable /redoc
|
||||
"openapi_url": None # Disable /openapi.json
|
||||
}
|
||||
else:
|
||||
print("🔓 Development mode: API documentation enabled at /docs and /redoc")
|
||||
app_config = {
|
||||
"lifespan": lifespan,
|
||||
"root_path": "/membership"
|
||||
}
|
||||
|
||||
# Create the main app
|
||||
app = FastAPI(
|
||||
lifespan=lifespan,
|
||||
root_path="/membership" # Configure for serving under /membership path
|
||||
)
|
||||
app = FastAPI(**app_config)
|
||||
|
||||
# Create a router with the /api prefix
|
||||
api_router = APIRouter(prefix="/api")
|
||||
@@ -790,6 +808,53 @@ async def get_config():
|
||||
"max_file_size_mb": int(max_file_size_mb)
|
||||
}
|
||||
|
||||
@api_router.get("/diagnostics/cors")
|
||||
async def cors_diagnostics(request: Request):
|
||||
"""
|
||||
CORS Diagnostics Endpoint
|
||||
Shows current CORS configuration and request details for debugging
|
||||
|
||||
Use this to verify:
|
||||
1. What origins are allowed
|
||||
2. What origin is making the request
|
||||
3. Whether CORS is properly configured
|
||||
"""
|
||||
cors_origins_env = os.environ.get('CORS_ORIGINS', '')
|
||||
|
||||
if cors_origins_env:
|
||||
configured_origins = [origin.strip() for origin in cors_origins_env.split(',')]
|
||||
cors_status = "✅ CONFIGURED"
|
||||
else:
|
||||
configured_origins = [
|
||||
"http://localhost:3000",
|
||||
"http://localhost:8000",
|
||||
"http://127.0.0.1:3000",
|
||||
"http://127.0.0.1:8000"
|
||||
]
|
||||
cors_status = "⚠️ NOT CONFIGURED (using defaults)"
|
||||
|
||||
request_origin = request.headers.get('origin', 'None')
|
||||
origin_allowed = request_origin in configured_origins
|
||||
|
||||
return {
|
||||
"cors_status": cors_status,
|
||||
"environment": ENVIRONMENT,
|
||||
"cors_origins_env_variable": cors_origins_env or "(not set)",
|
||||
"allowed_origins": configured_origins,
|
||||
"request_origin": request_origin,
|
||||
"origin_allowed": origin_allowed,
|
||||
"diagnosis": {
|
||||
"cors_configured": bool(cors_origins_env),
|
||||
"origin_matches": origin_allowed,
|
||||
"issue": None if origin_allowed else f"Origin '{request_origin}' is not in allowed origins list"
|
||||
},
|
||||
"fix_instructions": None if origin_allowed else (
|
||||
f"Add to backend .env file:\n"
|
||||
f"CORS_ORIGINS={request_origin}"
|
||||
f"{(',' + ','.join(configured_origins)) if cors_origins_env else ''}"
|
||||
)
|
||||
}
|
||||
|
||||
# User Profile Routes
|
||||
@api_router.get("/users/profile", response_model=UserResponse)
|
||||
async def get_profile(current_user: User = Depends(get_current_user)):
|
||||
@@ -6236,10 +6301,77 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)):
|
||||
# Include the router in the main app
|
||||
app.include_router(api_router)
|
||||
|
||||
# ============================================================================
|
||||
# MIDDLEWARE CONFIGURATION
|
||||
# ============================================================================
|
||||
# IMPORTANT: In FastAPI, middleware is executed in REVERSE order of addition
|
||||
# Last added = First executed
|
||||
# So we add them in this order: Security Headers -> CORS
|
||||
# Execution order will be: CORS -> Security Headers
|
||||
|
||||
# Security Headers Middleware (Added first, executes second)
|
||||
@app.middleware("http")
|
||||
async def add_security_headers(request: Request, call_next):
|
||||
response = await call_next(request)
|
||||
|
||||
# Security headers to protect against common vulnerabilities
|
||||
security_headers = {
|
||||
# Prevent clickjacking attacks
|
||||
"X-Frame-Options": "DENY",
|
||||
|
||||
# Prevent MIME type sniffing
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
|
||||
# Enable XSS protection in older browsers
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
|
||||
# Control referrer information
|
||||
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||
|
||||
# Permissions policy (formerly Feature-Policy)
|
||||
"Permissions-Policy": "geolocation=(), microphone=(), camera=()",
|
||||
}
|
||||
|
||||
# Add HSTS header in production (force HTTPS)
|
||||
if IS_PRODUCTION:
|
||||
security_headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||
|
||||
# Apply all security headers
|
||||
for header, value in security_headers.items():
|
||||
response.headers[header] = value
|
||||
|
||||
# Remove server identification headers (use del, not pop for MutableHeaders)
|
||||
if "Server" in response.headers:
|
||||
del response.headers["Server"]
|
||||
|
||||
return response
|
||||
|
||||
print(f"✓ Security headers configured (Production: {IS_PRODUCTION})")
|
||||
|
||||
# CORS Configuration (Added second, executes first)
|
||||
cors_origins = os.environ.get('CORS_ORIGINS', '')
|
||||
if cors_origins:
|
||||
# Use explicitly configured origins
|
||||
allowed_origins = [origin.strip() for origin in cors_origins.split(',')]
|
||||
else:
|
||||
# Default to common development origins if not configured
|
||||
allowed_origins = [
|
||||
"http://localhost:3000",
|
||||
"http://localhost:8000",
|
||||
"http://127.0.0.1:3000",
|
||||
"http://127.0.0.1:8000"
|
||||
]
|
||||
print(f"⚠️ WARNING: CORS_ORIGINS not set. Using defaults: {allowed_origins}")
|
||||
print("⚠️ For production, set CORS_ORIGINS in .env file!")
|
||||
|
||||
print(f"✓ CORS allowed origins: {allowed_origins}")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_credentials=True,
|
||||
allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
|
||||
allow_methods=["*"],
|
||||
allow_origins=allowed_origins,
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
|
||||
allow_headers=["*"],
|
||||
expose_headers=["*"],
|
||||
max_age=600, # Cache preflight requests for 10 minutes
|
||||
)
|
||||
Reference in New Issue
Block a user