bastienp commited on
Commit
5873878
·
1 Parent(s): a5399cd

feat(security): configure CORS and API validation

Browse files
Files changed (2) hide show
  1. app/main.py +34 -9
  2. requirements.txt +5 -1
app/main.py CHANGED
@@ -8,6 +8,7 @@ import time
8
  from pathlib import Path
9
  from fastapi.security.api_key import APIKeyHeader
10
  import os
 
11
 
12
  # Load environment variables
13
  load_dotenv()
@@ -24,10 +25,26 @@ FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000") # Add default
24
 
25
  async def get_api_key(api_key_header: str = Security(api_key_header)):
26
  if not API_KEY:
27
- raise HTTPException(status_code=500, detail="API key not configured on server")
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- if api_key_header != API_KEY:
30
- raise HTTPException(status_code=403, detail="Invalid API key")
 
 
 
 
 
31
 
32
  return api_key_header
33
 
@@ -87,15 +104,23 @@ async def security_headers(request: Request, call_next):
87
  response = await call_next(request)
88
 
89
  response.headers["X-Content-Type-Options"] = "nosniff"
90
- response.headers["X-Frame-Options"] = "SAMEORIGIN" # More permissive than DENY
91
  response.headers["X-XSS-Protection"] = "1; mode=block"
92
- response.headers["Strict-Transport-Security"] = (
93
- "max-age=31536000; includeSubDomains"
94
- )
95
  response.headers["Content-Security-Policy"] = (
96
- "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src *"
 
 
 
 
 
 
 
 
97
  )
98
- response.headers["Referrer-Policy"] = "no-referrer-when-downgrade"
 
 
99
 
100
  return response
101
 
 
8
  from pathlib import Path
9
  from fastapi.security.api_key import APIKeyHeader
10
  import os
11
+ import secrets
12
 
13
  # Load environment variables
14
  load_dotenv()
 
25
 
26
  async def get_api_key(api_key_header: str = Security(api_key_header)):
27
  if not API_KEY:
28
+ logger.error("API key not configured on server")
29
+ raise HTTPException(
30
+ status_code=500,
31
+ detail="Server configuration error" # Don't expose specific details
32
+ )
33
+
34
+ if not api_key_header:
35
+ raise HTTPException(
36
+ status_code=401,
37
+ detail="Missing API key",
38
+ headers={"WWW-Authenticate": "ApiKey"},
39
+ )
40
 
41
+ if not secrets.compare_digest(api_key_header, API_KEY): # Constant-time comparison
42
+ logger.warning(f"Invalid API key attempt from {request.client.host}")
43
+ raise HTTPException(
44
+ status_code=403,
45
+ detail="Invalid authentication credentials",
46
+ headers={"WWW-Authenticate": "ApiKey"},
47
+ )
48
 
49
  return api_key_header
50
 
 
104
  response = await call_next(request)
105
 
106
  response.headers["X-Content-Type-Options"] = "nosniff"
107
+ response.headers["X-Frame-Options"] = "DENY" # Stricter than SAMEORIGIN
108
  response.headers["X-XSS-Protection"] = "1; mode=block"
109
+ response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
 
 
110
  response.headers["Content-Security-Policy"] = (
111
+ "default-src 'self'; "
112
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
113
+ "style-src 'self' 'unsafe-inline'; "
114
+ "img-src 'self' data: https:; "
115
+ "connect-src 'self' "
116
+ )
117
+ response.headers["Permissions-Policy"] = (
118
+ "accelerometer=(), camera=(), geolocation=(), gyroscope=(), "
119
+ "magnetometer=(), microphone=(), payment=(), usb=()"
120
  )
121
+ response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, proxy-revalidate"
122
+ response.headers["Pragma"] = "no-cache"
123
+ response.headers["Expires"] = "0"
124
 
125
  return response
126
 
requirements.txt CHANGED
@@ -7,4 +7,8 @@ mistralai>=0.0.7
7
  python-dotenv>=1.0.0
8
  langchain>=0.3.15
9
  langchain-mistralai>=0.2.4
10
- elevenlabs>=0.1.0
 
 
 
 
 
7
  python-dotenv>=1.0.0
8
  langchain>=0.3.15
9
  langchain-mistralai>=0.2.4
10
+ elevenlabs>=0.1.0
11
+ slowapi
12
+ cryptography
13
+ python-jose[cryptography]
14
+ passlib[bcrypt]