security: fix auth (argon2, cookie, JWT), CORS, session middleware, pyproject.toml

This commit is contained in:
ZdenekSrotyr 2026-04-08 12:08:52 +02:00
parent d5659d7091
commit 224635b88d
7 changed files with 23 additions and 19 deletions

View file

@ -65,7 +65,7 @@ async def get_optional_user(
if not authorization or not authorization.startswith("Bearer "):
return None
try:
return await get_current_user(authorization, conn)
return await get_current_user(request=None, authorization=authorization, conn=conn)
except HTTPException:
return None

View file

@ -7,6 +7,14 @@ from typing import Optional
import jwt
SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "dev-jwt-secret-change-in-production")
import warnings as _warnings
if len(SECRET_KEY) < 32 and os.environ.get("TESTING", "").lower() not in ("1", "true"):
_warnings.warn(
f"JWT_SECRET_KEY is {len(SECRET_KEY)} chars — minimum 32 recommended for production",
UserWarning, stacklevel=2,
)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = 24 * 30 # 30 days

View file

@ -89,10 +89,12 @@ async def google_callback(request: Request):
jwt_token = create_access_token(user["id"], user["email"], user["role"])
# Redirect to dashboard with token in cookie
is_https = request.url.scheme == "https"
response = RedirectResponse(url="/dashboard", status_code=302)
response.set_cookie(
key="access_token", value=jwt_token,
httponly=True, max_age=86400 * 30, samesite="lax",
secure=is_https,
)
return response

View file

@ -68,13 +68,9 @@ async def password_setup(
raise HTTPException(status_code=400, detail="Invalid setup token")
# Hash and save password
try:
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(request.password)
except ImportError:
import hashlib
hashed = hashlib.sha256(request.password.encode()).hexdigest()
repo.update(id=user["id"], password_hash=hashed, setup_token=None)
token = create_access_token(user["id"], user["email"], user["role"])

View file

@ -88,12 +88,8 @@ async def bootstrap(
user_id = str(uuid.uuid4())
password_hash = None
if request.password:
try:
from argon2 import PasswordHasher
password_hash = PasswordHasher().hash(request.password)
except ImportError:
import hashlib
password_hash = hashlib.sha256(request.password.encode()).hexdigest()
repo.create(
id=user_id,

View file

@ -39,15 +39,15 @@ def create_app() -> FastAPI:
)
# Session middleware (required for OAuth state)
app.add_middleware(
SessionMiddleware,
secret_key=os.environ.get("JWT_SECRET_KEY", "dev-session-secret"),
)
import secrets as _secrets
session_secret = os.environ.get("SESSION_SECRET", os.environ.get("JWT_SECRET_KEY", _secrets.token_hex(32)))
app.add_middleware(SessionMiddleware, secret_key=session_secret)
# CORS for CLI and external clients
cors_origins = os.environ.get("CORS_ORIGINS", "http://localhost:3000,http://localhost:8000").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[o.strip() for o in cors_origins],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],

View file

@ -16,6 +16,8 @@ dependencies = [
# Authentication
"PyJWT>=2.8.0",
"itsdangerous>=2.1.0",
"authlib>=1.3.0",
"argon2-cffi>=23.1.0",
# HTTP client
"httpx>=0.27.0",
# CLI