security: fix auth (argon2, cookie, JWT), CORS, session middleware, pyproject.toml
This commit is contained in:
parent
d5659d7091
commit
224635b88d
7 changed files with 23 additions and 19 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
10
app/main.py
10
app/main.py
|
|
@ -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=["*"],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue