109 lines
3.4 KiB
Python
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"}
|