Connecting local program to Twitch dev-portal credentials

This commit is contained in:
2025-11-17 00:19:13 +01:00
parent 60417d4594
commit 264e4c276d
4 changed files with 123 additions and 1 deletions

View File

@@ -1,4 +1,7 @@
# This is an example file. Copy it to .env and fill in your actual secrets.
# The .env file is ignored by Git and should NEVER be committed.
ENCRYPTION_KEY="your_32_byte_url_safe_base64_encoded_key_goes_here"
ENCRYPTION_KEY=your_32_byte_url_safe_base64_encoded_key_goes_here
TWITCH_CLIENT_ID=your_twitch_client_id_goes_here
TWITCH_CLIENT_SECRET=your_twitch_client_secret_goes_here

91
auth.py Normal file
View File

@@ -0,0 +1,91 @@
import httpx
import secrets
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
from config import settings
from database import SessionLocal
import models
import security
router = APIRouter()
# Dependency to get a DB session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("/login/twitch")
async def login_with_twitch(request: Request):
"""
Step 1 of OAuth flow: Redirect the user to Twitch's authorization page.
"""
# Generate a random state token for CSRF protection
state = secrets.token_urlsafe(16)
request.session['oauth_state'] = state
# As per RESEARCH_REPORT.md, these are the minimum required scopes
scopes = "chat:read chat:write"
# Construct the authorization URL
auth_url = (
f"https://id.twitch.tv/oauth2/authorize"
f"?response_type=code"
f"&client_id={settings.TWITCH_CLIENT_ID}"
f"&redirect_uri={settings.APP_BASE_URL}/auth/twitch/callback"
f"&scope={scopes}"
f"&state={state}"
)
return RedirectResponse(url=auth_url)
@router.get("/auth/twitch/callback")
async def auth_twitch_callback(code: str, state: str, request: Request, db: Session = Depends(get_db)):
"""
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):
raise HTTPException(status_code=403, detail="Invalid state parameter. CSRF attack suspected.")
# Step 4: Exchange the authorization code for an access token
token_url = "https://id.twitch.tv/oauth2/token"
token_data = {
"client_id": settings.TWITCH_CLIENT_ID,
"client_secret": settings.TWITCH_CLIENT_SECRET,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": f"{settings.APP_BASE_URL}/auth/twitch/callback",
}
async with httpx.AsyncClient() as client:
token_response = await client.post(token_url, data=token_data)
if token_response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to retrieve access token from Twitch.")
token_json = token_response.json()
access_token = token_json["access_token"]
refresh_token = token_json["refresh_token"]
# Step 5: Validate the user and get their details from Twitch API
users_url = "https://api.twitch.tv/helix/users"
headers = {
"Client-ID": settings.TWITCH_CLIENT_ID,
"Authorization": f"Bearer {access_token}",
}
user_response = await client.get(users_url, headers=headers)
user_data = user_response.json()["data"][0]
# TODO: Upsert user into our database
# For now, we'll just return the user data as a proof of concept.
# Encrypt the tokens for storage
encrypted_tokens = security.encrypt_tokens(access_token, refresh_token)
# Here you would create or update the user in your database
# user = db.query(models.User).filter_by(platform_user_id=user_data['id']).first() ... etc.
return {"message": "Twitch login successful!", "user": user_data, "encrypted_tokens": encrypted_tokens}

18
config.py Normal file
View File

@@ -0,0 +1,18 @@
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
class Settings:
"""
A simple class to hold all application settings, loaded from environment variables.
"""
ENCRYPTION_KEY: str = os.getenv("ENCRYPTION_KEY")
TWITCH_CLIENT_ID: str = os.getenv("TWITCH_CLIENT_ID")
TWITCH_CLIENT_SECRET: str = os.getenv("TWITCH_CLIENT_SECRET")
# The full URL where our app is running, needed for the redirect_uri
APP_BASE_URL: str = "http://localhost:8000" # Update for production
settings = Settings()

10
main.py
View File

@@ -1,7 +1,10 @@
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
import models
from database import engine
import auth # Import the new auth module
from config import settings # Import settings to get the secret key
# This line tells SQLAlchemy to create all the tables based on the models
# we defined. It will create the `multichat_overlay.db` file with the
@@ -10,6 +13,13 @@ models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Add the authentication router
app.include_router(auth.router)
# 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.
app.add_middleware(SessionMiddleware, secret_key=settings.ENCRYPTION_KEY)
@app.get("/")
async def read_root():
return {"message": "MultiChatOverlay API"}