Fix CORS configuration to allow requests from Vercel frontend

This commit is contained in:
Automated Action 2025-06-05 11:11:51 +00:00
parent 2b5afe103b
commit 1f546e5189
3 changed files with 100 additions and 26 deletions

View File

@ -188,7 +188,7 @@ The application uses SQLite as the database. The database file is created at `/a
## CORS Configuration
The API has CORS (Cross-Origin Resource Sharing) enabled with the following configuration:
The API has robust CORS (Cross-Origin Resource Sharing) enabled with the following configuration:
- Allowed origins:
- http://localhost
@ -200,17 +200,43 @@ The API has CORS (Cross-Origin Resource Sharing) enabled with the following conf
- * (wildcard for development)
- Allowed methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
- Allowed headers: Content-Type, Authorization, Accept, Origin, X-Requested-With, X-CSRF-Token, Access-Control-Allow-Credentials
- Exposed headers: Content-Length, Content-Type
- Credentials support: Enabled
- Max age for preflight requests: 600 seconds (10 minutes)
- Allowed headers:
- Authorization
- Content-Type
- Accept
- Accept-Language
- Content-Language
- Content-Length
- Origin
- X-Requested-With
- X-CSRF-Token
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials
- X-HTTP-Method-Override
- Exposed headers: Content-Length, Content-Type, Authorization
- Credentials support: Enabled (supports JWT authentication)
- Max age for preflight requests: 3600 seconds (1 hour)
### Custom CORS Handling
This application uses both FastAPI's built-in CORSMiddleware and a custom CORS middleware that provides enhanced handling of preflight OPTIONS requests. The custom middleware also supports wildcard pattern matching for origins (e.g., https://*.vercel.app) to better support deployment platforms.
This application implements a custom CORS middleware that properly handles preflight OPTIONS requests for all endpoints, including authentication routes. The middleware includes:
1. Direct handling of OPTIONS requests for all endpoints
2. Proper header handling for preflight responses
3. Explicit support for POST requests with JSON content-type
4. Full support for Authorization headers for authenticated endpoints
5. Pattern matching for wildcard domains (e.g., *.vercel.app)
### CORS Test Endpoint
The API includes a special endpoint for testing CORS functionality:
- `OPTIONS /api/v1/cors-test` - Test preflight requests
- `POST /api/v1/cors-test` - Test POST requests with JSON body
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| USE_CUSTOM_CORS_ONLY | Whether to use only the custom CORS middleware | False |
| USE_CUSTOM_CORS_ONLY | Whether to use only the custom CORS middleware | True |

View File

@ -41,7 +41,7 @@ class Settings(BaseSettings):
]
# Whether to use only the custom CORS middleware
USE_CUSTOM_CORS_ONLY: bool = False
USE_CUSTOM_CORS_ONLY: bool = True
# Security settings
PASSWORD_HASH_ROUNDS: int = 12

84
main.py
View File

@ -19,45 +19,69 @@ class CustomCORSMiddleware(BaseHTTPMiddleware):
if origin in settings.CORS_ORIGINS:
return True
# Wildcard match
# Wildcard match - if "*" is in the allowed origins list
if "*" in settings.CORS_ORIGINS:
return True
# Check for pattern matching (e.g., https://*.vercel.app)
for allowed_origin in settings.CORS_ORIGINS:
if "*" in allowed_origin:
pattern = allowed_origin.replace("*", "")
if origin.startswith(pattern.split("*")[0]) and origin.endswith(pattern.split("*")[-1]):
return True
if "*" in allowed_origin and not allowed_origin == "*":
pattern_parts = allowed_origin.split("*")
if len(pattern_parts) == 2:
if origin.startswith(pattern_parts[0]) and origin.endswith(pattern_parts[1]):
return True
return False
async def dispatch(self, request: Request, call_next):
origin = request.headers.get("origin", "")
# Always respond to OPTIONS requests directly for preflight handling
if request.method == "OPTIONS":
# Handle preflight requests
response = Response()
# Create a new response for preflight
response = Response(status_code=204) # No content needed for preflight
# If no origin or not allowed, return 204 with minimal headers
# This will not block the request but won't allow CORS either
if not origin or not self.is_origin_allowed(origin):
return response
# If origin is allowed, set the full CORS headers
response.headers["Access-Control-Allow-Origin"] = origin
# Include all possible headers that might be used by the frontend
# Make sure Content-Type is included to support application/json
response.headers["Access-Control-Allow-Headers"] = (
"Authorization, Content-Type, Accept, Accept-Language, " +
"Content-Language, Content-Length, Origin, X-Requested-With, " +
"X-CSRF-Token, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, " +
"X-Requested-With, X-HTTP-Method-Override"
)
# Expose headers that frontend might need to access
response.headers["Access-Control-Expose-Headers"] = (
"Content-Length, Content-Type, Authorization"
)
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH"
response.headers["Access-Control-Max-Age"] = "3600" # 1 hour cache
response.status_code = 200 # OK for successful preflight
# Check if the origin is allowed
if self.is_origin_allowed(origin):
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, Accept, Origin, X-Requested-With, X-CSRF-Token, Access-Control-Allow-Credentials"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Max-Age"] = "600"
response.status_code = 200
return response
# Process regular requests
# For regular requests, process normally then add CORS headers
response = await call_next(request)
# Set CORS headers for the response
# Add CORS headers to all responses if origin is allowed
if self.is_origin_allowed(origin):
# Set required CORS headers
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
# Add Vary header to indicate caching should consider Origin
response.headers["Vary"] = "Origin"
return response
app = FastAPI(
@ -92,5 +116,29 @@ app.include_router(api_router)
async def health_check():
return {"status": "healthy"}
# CORS test endpoint
@app.options("/api/v1/cors-test", tags=["cors"])
async def cors_preflight_test():
"""Test endpoint for CORS preflight requests."""
return None
@app.post("/api/v1/cors-test", tags=["cors"])
async def cors_test(request: Request):
"""Test endpoint for CORS POST requests with JSON."""
try:
body = await request.json()
return {
"success": True,
"message": "CORS is working correctly for POST requests with JSON",
"received_data": body,
"headers": dict(request.headers)
}
except Exception as e:
return {
"success": False,
"message": f"Error: {str(e)}",
"headers": dict(request.headers)
}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)