Files
bacchus/apps/backend/app/main.py
2025-09-28 19:13:01 +02:00

109 lines
3.4 KiB
Python

from pathlib import Path
from dotenv import load_dotenv
import os
ENV_PATH = Path(__file__).resolve().parent.parent / ".env"
os.environ["DOTENV_PATH"] = str(ENV_PATH)
load_dotenv(dotenv_path=ENV_PATH, override=True)
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from app.api import (
auth, users, products, bookings, deliveries, stats, topups,
audit_logs, profile, transactions, categories, ledger, admin_settings, paypal_ipn
)
from app.core.auth import CSRF_HEADER_NAME, verify_csrf
ALLOWED_ORIGINS = ["http://localhost:5173"]
# CSRF-Ausnahmen (nur für DEV sinnvoll!)
CSRF_EXEMPT = {
"/auth/pin-login",
"/auth/management-login",
"/auth/logout",
"/auth/csrf",
"/docs",
"/openapi.json",
"/redoc",
"/docs/oauth2-redirect",
}
# Swagger-/Redoc-Pfade
DOCS_PATHS = ("/docs", "/redoc", "/openapi.json")
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", CSRF_HEADER_NAME],
)
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
path = request.url.path
# CSRF nur für mutierende Methoden prüfen, ausgenommene Pfade überspringen
if (
request.method in {"POST", "PUT", "PATCH", "DELETE"}
and not any(path == p or path.startswith(p + "/") for p in CSRF_EXEMPT)
):
verify_csrf(request)
response: Response = await call_next(request)
if any(path == p or path.startswith(p + "/") for p in DOCS_PATHS):
response.headers.setdefault(
"Content-Security-Policy",
"default-src 'self' https:; "
"img-src 'self' data: https:; "
"style-src 'self' 'unsafe-inline' https:; "
"script-src 'self' 'unsafe-inline' https:"
)
return response
# Normale Security-Header + strikte CSP
response.headers.setdefault("X-Frame-Options", "DENY")
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("Referrer-Policy", "no-referrer")
response.headers.setdefault(
"Content-Security-Policy",
"default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'"
)
return response
app.add_middleware(SecurityHeadersMiddleware)
# Medienordner sicherstellen (verhindert RuntimeError beim Mount)
os.makedirs("media/avatars", exist_ok=True)
# Static files für Avatare & Medien
app.mount("/media", StaticFiles(directory="media"), name="media")
# Router registrieren
app.include_router(auth.router)
app.include_router(users.router)
app.include_router(products.router)
app.include_router(bookings.router)
app.include_router(deliveries.router)
app.include_router(stats.router)
app.include_router(topups.router)
app.include_router(audit_logs.router)
app.include_router(profile.router)
app.include_router(transactions.router)
app.include_router(categories.router)
app.include_router(ledger.router)
app.include_router(admin_settings.router)
app.include_router(paypal_ipn.router)
# app.include_router(sessions.router)
@app.get("/")
def root():
return {"message": "Bacchus API läuft"}