From 0840e38849fec68744d344ec8e305447b409aa14 Mon Sep 17 00:00:00 2001 From: markov Date: Wed, 25 Feb 2026 11:24:01 +0100 Subject: [PATCH] enforce: auth required on ALL API endpoints except login --- src/tracker/app.py | 69 +++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/tracker/app.py b/src/tracker/app.py index 1d588d1..f5083a9 100644 --- a/src/tracker/app.py +++ b/src/tracker/app.py @@ -89,47 +89,48 @@ app = FastAPI( lifespan=lifespan, ) -# Paths that don't require auth -NO_AUTH_PATHS = {"/health", "/docs", "/openapi.json", "/ws"} +# API paths that don't require auth +NO_AUTH_API = {"/api/v1/auth/login"} @app.middleware("http") async def auth_middleware(request: Request, call_next): - """Verify token for REST API requests.""" + """Verify token for all API requests (except login).""" path = request.url.path - # Skip auth for non-API paths and excluded paths - if not path.startswith("/api/") and path not in NO_AUTH_PATHS: - # WS and health don't need REST auth - pass - elif path.startswith("/api/"): - auth_header = request.headers.get("authorization", "") - token = None - if auth_header.startswith("Bearer "): - token = auth_header[7:] - elif request.query_params.get("token"): - token = request.query_params["token"] - if token: - # Check agent token - async with async_session() as db: - result = await db.execute(select(Member).where(Member.token == token)) - member = result.scalar_one_or_none() - if member: - request.state.member = member - else: - # Try JWT - from tracker.api.auth import decode_jwt - try: - payload = decode_jwt(token) - result = await db.execute(select(Member).where(Member.id == payload["sub"])) - member = result.scalar_one_or_none() - if member: - request.state.member = member - except Exception: - pass - # Don't enforce auth yet — BFF uses its own token via proxy - # TODO: enforce when all clients are updated + if not path.startswith("/api/") or path in NO_AUTH_API: + return await call_next(request) + # Extract token from header or query param + auth_header = request.headers.get("authorization", "") + token = None + if auth_header.startswith("Bearer "): + token = auth_header[7:] + elif request.query_params.get("token"): + token = request.query_params["token"] + + if not token: + return JSONResponse(status_code=401, content={"error": "Authentication required"}) + + # Resolve member from token + async with async_session() as db: + # Try agent token first + result = await db.execute(select(Member).where(Member.token == token)) + member = result.scalar_one_or_none() + if not member: + # Try JWT + from tracker.api.auth import decode_jwt + try: + payload = decode_jwt(token) + result = await db.execute(select(Member).where(Member.id == payload["sub"])) + member = result.scalar_one_or_none() + except Exception: + pass + + if not member: + return JSONResponse(status_code=401, content={"error": "Invalid or expired token"}) + + request.state.member = member return await call_next(request)