forked from 0xWheatyz/SPARC
feat(security): add JWT startup guard, configurable CORS, and externalize DB credentials
- Add check_jwt_secret() that refuses default JWT secret when APP_ENV != development - Make CORS origins configurable via CORS_ORIGINS env var (comma-separated) - Replace hardcoded postgres credentials in docker-compose.yml with env var references - Add APP_ENV and cors_origins to config.py - Update .env.example with all required variables and documentation - Add tests for JWT startup guard and CORS configuration Closes leeworks-agents/SPARC#4 Closes leeworks-agents/SPARC#5 Closes leeworks-agents/SPARC#6 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -16,6 +16,7 @@ from SPARC.analyzer import CompanyAnalyzer
|
||||
from SPARC.auth import (
|
||||
TokenResponse,
|
||||
UserResponse,
|
||||
check_jwt_secret,
|
||||
create_tokens,
|
||||
decode_token,
|
||||
get_current_admin,
|
||||
@@ -150,6 +151,7 @@ _analyzer: CompanyAnalyzer | None = None
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Initialize resources on startup."""
|
||||
global _analyzer
|
||||
check_jwt_secret()
|
||||
_analyzer = CompanyAnalyzer()
|
||||
yield
|
||||
# Cleanup if needed
|
||||
@@ -167,7 +169,7 @@ app = FastAPI(
|
||||
# Add CORS middleware for React frontend
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:3000", "http://localhost:5173"],
|
||||
allow_origins=config.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
||||
+15
-1
@@ -13,11 +13,25 @@ from SPARC import config
|
||||
from SPARC.database import DatabaseClient
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET = os.getenv("JWT_SECRET", "sparc-secret-key-change-in-production")
|
||||
_DEFAULT_JWT_SECRET = "sparc-secret-key-change-in-production"
|
||||
JWT_SECRET = os.getenv("JWT_SECRET", _DEFAULT_JWT_SECRET)
|
||||
JWT_ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
REFRESH_TOKEN_EXPIRE_DAYS = 7
|
||||
|
||||
|
||||
def check_jwt_secret() -> None:
|
||||
"""Refuse to start with the default JWT secret in non-development environments.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If JWT_SECRET is the default value and APP_ENV is not 'development'.
|
||||
"""
|
||||
if JWT_SECRET == _DEFAULT_JWT_SECRET and config.app_env != "development":
|
||||
raise RuntimeError(
|
||||
f"FATAL: JWT_SECRET is set to the default value and APP_ENV={config.app_env!r}. "
|
||||
"Set a secure JWT_SECRET environment variable before running in non-development environments."
|
||||
)
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
|
||||
@@ -33,3 +33,16 @@ patent_thread_workers = int(os.getenv("PATENT_THREAD_WORKERS", "5"))
|
||||
# Root path for running behind a reverse proxy (e.g., "/api" when served at /api/)
|
||||
# This ensures OpenAPI docs work correctly when accessed via the proxy
|
||||
root_path = os.getenv("ROOT_PATH", "")
|
||||
|
||||
# Application environment: "development", "staging", or "production"
|
||||
# Used for safety checks (e.g., refusing default JWT secret in production)
|
||||
app_env = os.getenv("APP_ENV", "development")
|
||||
|
||||
# CORS allowed origins (comma-separated)
|
||||
# Defaults to localhost dev origins when unset
|
||||
_cors_origins_raw = os.getenv("CORS_ORIGINS", "")
|
||||
cors_origins: list[str] = (
|
||||
[o.strip() for o in _cors_origins_raw.split(",") if o.strip()]
|
||||
if _cors_origins_raw
|
||||
else ["http://localhost:3000", "http://localhost:5173"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user