fix: address multiple code audit findings

- CORS: replace wildcard with explicit origin list from CORS_ORIGINS env
- Auth: enforce strong defaults, JWT blacklist (RevokedToken model), login rate limiting
- Auth: validate password length before bcrypt (72-byte limit)
- Scheduler: single-threaded worker to mitigate SQLite write contention
- Scheduler: graceful shutdown (wait=True)
- Snapshots: add prune_snapshots() with configurable retention count
- Storage: isolate localStorage keys via VITE_APP_KEY prefix
- Config: add cors_origins, login_rate_limit, snapshot_retention_count settings
This commit is contained in:
SmartUp Developer
2026-05-17 10:52:18 +08:00
parent a42ecf7bcc
commit ad16618406
25 changed files with 792 additions and 165 deletions
+17 -5
View File
@@ -12,7 +12,7 @@ from app.config import get_settings
from app.database import init_db
from app.models.admin_user import AdminUser
from app.database import SessionLocal
from app.utils.auth import hash_password
from app.utils.auth import hash_password, validate_password_supported
from app.services.scheduler import start_scheduler, stop_scheduler
from app.routers import auth, upstreams, webhooks, logs, custom_pages, browser_sessions, websites
from app.services.browser_session_service import browser_sessions as browser_session_service
@@ -21,11 +21,21 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name
logger = logging.getLogger(__name__)
def _init_admin() -> None:
def _validate_runtime_settings() -> None:
settings = get_settings()
if not settings.admin_password:
logger.warning("ADMIN_PASSWORD not set, skip admin init")
return
raise RuntimeError("ADMIN_PASSWORD must be set")
if settings.admin_password in {"changeme", "changeme123"}:
raise RuntimeError("ADMIN_PASSWORD must not use the default placeholder")
if not settings.jwt_secret or settings.jwt_secret == "change-me-in-production":
raise RuntimeError("JWT_SECRET must be set to a non-default value")
if not settings.cors_origin_list:
raise RuntimeError("CORS_ORIGINS must include at least one explicit origin")
validate_password_supported(settings.admin_password)
def _init_admin() -> None:
settings = get_settings()
db = SessionLocal()
try:
exists = db.query(AdminUser).filter(AdminUser.email == settings.admin_email).first()
@@ -45,6 +55,7 @@ def _init_admin() -> None:
@asynccontextmanager
async def lifespan(app: FastAPI):
_validate_runtime_settings()
init_db()
_init_admin()
start_scheduler()
@@ -63,9 +74,10 @@ app = FastAPI(
openapi_url="/api/openapi.json",
)
settings = get_settings()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=settings.cors_origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],