diff --git a/auth.py b/auth.py index ac42627..7505f23 100644 --- a/auth.py +++ b/auth.py @@ -1,5 +1,6 @@ import httpx import secrets +import logging from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi.responses import RedirectResponse from sqlalchemy.orm import Session @@ -11,6 +12,9 @@ import security router = APIRouter() +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + # Dependency to get a DB session def get_db(): db = SessionLocal() @@ -27,6 +31,7 @@ async def login_with_twitch(request: Request): # Generate a random state token for CSRF protection state = secrets.token_urlsafe(16) request.session['oauth_state'] = state + logger.info(f"Generated OAuth state: {state} for session.") # As per RESEARCH_REPORT.md, these are the minimum required scopes scopes = "chat:read" @@ -48,7 +53,9 @@ async def auth_twitch_callback(code: str, state: str, request: Request, db: Sess Step 2 of OAuth flow: Handle the callback from Twitch after user authorization. """ # CSRF Protection: Validate the state - if state != request.session.pop('oauth_state', None): + session_state = request.session.pop('oauth_state', None) + if state != session_state: + logger.error(f"OAuth state mismatch! Received state: '{state}', Session state: '{session_state}'") raise HTTPException(status_code=403, detail="Invalid state parameter. CSRF attack suspected.") # Step 4: Exchange the authorization code for an access token diff --git a/main.py b/main.py index 2068344..694e1f3 100644 --- a/main.py +++ b/main.py @@ -65,13 +65,13 @@ async def lifespan(app: FastAPI): app = FastAPI(lifespan=lifespan) -# Add middleware to trust proxy headers (X-Forwarded-For, X-Forwarded-Proto) -# This is crucial for running behind a reverse proxy like Nginx or Caddy. -app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") - # Add session middleware. A secret key is required for signing the session cookie. # We can reuse our encryption key for this, but in production you might want a separate key. +# Note: Middleware is applied in reverse order (last added is first executed). +# We want ProxyHeaders to run FIRST (outermost) to fix the scheme/host, +# then SessionMiddleware to run SECOND (inner) so it sees the correct scheme. app.add_middleware(SessionMiddleware, secret_key=settings.ENCRYPTION_KEY) +app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") # Mount the 'static' directory using an absolute path for reliability # This MUST be done before the routes that depend on it are defined.