Working on: The Twitch authentication

This commit is contained in:
2025-12-25 21:05:11 +01:00
parent 773288faf0
commit e69d423deb
2 changed files with 12 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
import httpx import httpx
import secrets import secrets
import logging
from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -11,6 +12,9 @@ import security
router = APIRouter() router = APIRouter()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Dependency to get a DB session # Dependency to get a DB session
def get_db(): def get_db():
db = SessionLocal() db = SessionLocal()
@@ -27,6 +31,7 @@ async def login_with_twitch(request: Request):
# Generate a random state token for CSRF protection # Generate a random state token for CSRF protection
state = secrets.token_urlsafe(16) state = secrets.token_urlsafe(16)
request.session['oauth_state'] = state 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 # As per RESEARCH_REPORT.md, these are the minimum required scopes
scopes = "chat:read" 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. Step 2 of OAuth flow: Handle the callback from Twitch after user authorization.
""" """
# CSRF Protection: Validate the state # 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.") raise HTTPException(status_code=403, detail="Invalid state parameter. CSRF attack suspected.")
# Step 4: Exchange the authorization code for an access token # Step 4: Exchange the authorization code for an access token

View File

@@ -65,13 +65,13 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan) 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. # 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. # 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(SessionMiddleware, secret_key=settings.ENCRYPTION_KEY)
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
# Mount the 'static' directory using an absolute path for reliability # Mount the 'static' directory using an absolute path for reliability
# This MUST be done before the routes that depend on it are defined. # This MUST be done before the routes that depend on it are defined.