File size: 4,881 Bytes
d60934b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
from fastapi import FastAPI, Request, HTTPException, Security, Depends
from fastapi.middleware.cors import CORSMiddleware
from app.routes import health, wagons, chat, players, generate
from app.core.logging import get_logger, setup_logging
from dotenv import load_dotenv
from datetime import datetime
import time
from pathlib import Path
from fastapi.security.api_key import APIKeyHeader
import os
# Load environment variables
load_dotenv()
# Setup logging
logger = get_logger("main")
API_KEY = os.getenv("API_KEY")
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=True)
async def get_api_key(api_key_header: str = Security(api_key_header)):
if not API_KEY:
raise HTTPException(status_code=500, detail="API key not configured on server")
if api_key_header != API_KEY:
raise HTTPException(status_code=403, detail="Invalid API key")
return api_key_header
app = FastAPI(
title="Game Jam API",
description="API for Game Jam Hackathon",
version="1.0.0",
dependencies=[Depends(get_api_key)],
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"],
allow_headers=["*"],
expose_headers=[
"Location",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Allow-Credentials",
"Access-Control-Expose-Headers",
],
max_age=3600,
)
@app.middleware("http")
async def handle_redirects(request: Request, call_next):
"""Ensure CORS headers are in redirect responses and force https in the 'Location' header."""
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH"
)
response.headers["Access-Control-Allow-Headers"] = "*"
response.headers["Access-Control-Max-Age"] = "3600"
if response.status_code in [301, 302, 307, 308]:
response.headers["Access-Control-Expose-Headers"] = "Location"
if "Location" in response.headers:
location = response.headers["Location"]
if location.startswith("http://"):
response.headers["Location"] = location.replace(
"http://", "https://", 1
)
return response
@app.middleware("http")
async def security_headers(request: Request, call_next):
"""Add security-related headers to all responses."""
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "SAMEORIGIN" # More permissive than DENY
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
response.headers["Content-Security-Policy"] = (
"default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src *"
)
response.headers["Referrer-Policy"] = "no-referrer-when-downgrade"
return response
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Middleware to log all requests and responses."""
start_time = time.time()
logger.info(
"Incoming request",
extra={
"method": request.method,
"url": str(request.url),
"client_host": request.client.host if request.client else None,
"timestamp": datetime.utcnow().isoformat(),
},
)
try:
response = await call_next(request)
process_time = time.time() - start_time
logger.info(
"Request completed",
extra={
"method": request.method,
"url": str(request.url),
"status_code": response.status_code,
"process_time_ms": round(process_time * 1000, 2),
},
)
return response
except Exception as e:
logger.error(
"Request failed",
extra={"method": request.method, "url": str(request.url), "error": str(e)},
)
raise
app.include_router(health.router)
app.include_router(wagons.router)
app.include_router(chat.router)
app.include_router(players.router)
app.include_router(generate.router)
@app.get("/")
async def root():
logger.info("Root endpoint accessed")
return {
"message": "Welcome to Game Jam API",
"docs_url": "/docs",
"health_check": "/health",
"wagons_endpoint": "/api/wagons",
"chat_endpoint": "/api/chat",
"players_endpoint": "/api/players",
}
@app.on_event("startup")
async def startup_event():
logs_dir = Path("logs")
logs_dir.mkdir(exist_ok=True)
setup_logging()
|