Public Access
feat(auth): harden sessions + HA Ingress support
- backend: settings SESSION_COOKIE_SECURE + TRUSTED_NETWORK_AUTO_AUTH, require_session uses secrets.compare_digest and respects trusted-network opt-in, main.py adds require_family_session middleware gating all /api/ routes except auth/admin/email-vote-token paths - docker-compose: pass SESSION_COOKIE_SECURE + TRUSTED_NETWORK_AUTO_AUTH through to backend + scheduler (fixes env-file changes not reaching runtime) - frontend: Ingress path-prefix support (APP_BASE_PATH, BrowserRouter basename, vite base './'), Login redirect honors APP_BASE_PATH - nginx: no-cache headers on root + /assets/ - docs: Home Assistant Ingress install/troubleshooting + plan file - tests: test_auth expects 401 on no-session GET Defaults: SESSION_COOKIE_SECURE=false, TRUSTED_NETWORK_AUTO_AUTH=true (HA is the auth boundary; MealPlanner must not be port-forwarded directly).
This commit is contained in:
+28
-1
@@ -1,9 +1,11 @@
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi import FastAPI, Depends, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
from app.database import get_db
|
||||
from app.config import settings
|
||||
from app.security import require_session
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=settings.LOG_LEVEL)
|
||||
@@ -19,6 +21,31 @@ app = FastAPI(
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
def _requires_session(path: str, method: str) -> bool:
|
||||
if method == "OPTIONS" or not path.startswith("/api/"):
|
||||
return False
|
||||
if path.startswith("/api/auth/") or path.startswith("/api/admin/"):
|
||||
return False
|
||||
# Email approval links carry their own signed, single-use token.
|
||||
if path.startswith("/api/meals/vote/"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def require_family_session(request: Request, call_next):
|
||||
if _requires_session(request.url.path, request.method):
|
||||
try:
|
||||
require_session(request)
|
||||
except HTTPException as exc:
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"detail": exc.detail},
|
||||
headers=getattr(exc, "headers", None),
|
||||
)
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check(db: Session = Depends(get_db)):
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user