Öffentliches Formular /personalisierung (Code, dann E-Mail mit Domänenvorgabe, Anmeldename, Passwort; ?kiosk=1 zeigt Abmelden), Passwort nur als SHA-512-crypt an anode, Bestätigungslink per mail_ausgang (GET zeigt Knopf, erst POST bestätigt). Geräte-Info: Code erzeugen (einmalig angezeigt), Kästchen „Bestehenden Zugang behalten“, zurückziehen, Verlauf. Geräteliste: Spalte Personalisiert, Schuladressen sichtbar. Organisation: E-Mail-Domänen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
2.8 KiB
Python
87 lines
2.8 KiB
Python
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
import auth
|
|
import mail
|
|
from routers import (
|
|
admin_accounts,
|
|
admin_dashboard,
|
|
admin_flows,
|
|
admin_geraete,
|
|
admin_kunden,
|
|
admin_technik,
|
|
admin_workspaces,
|
|
dashboard,
|
|
geraete,
|
|
gruppen,
|
|
installationsmedium,
|
|
organisation,
|
|
personalisierung,
|
|
struktur,
|
|
webapps,
|
|
workspace,
|
|
)
|
|
|
|
|
|
# Sicherheits-Review 25.08.2026 (vor der geplanten oeffentlichen Freigabe
|
|
# ueber Pangolin): /docs, /redoc, /openapi.json waren zuvor unauthentifiziert
|
|
# erreichbar und legten die komplette interne Routenstruktur (inkl. aller
|
|
# /admin/*-Pfade) offen - fuer eine Plattform in aktiver Entwicklung
|
|
# unnoetige Aufklaerungshilfe fuer Angreifer.
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Versandschleife für die Ausgangstabelle (siehe mail.py, ADR-0041).
|
|
versand = asyncio.create_task(mail.versand_schleife())
|
|
yield
|
|
versand.cancel()
|
|
|
|
|
|
app = FastAPI(
|
|
title="Tuxflotte Flotten-Management", docs_url=None, redoc_url=None, openapi_url=None, lifespan=lifespan
|
|
)
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
|
|
@app.middleware("http")
|
|
async def security_headers(request: Request, call_next):
|
|
"""
|
|
Basis-Security-Header (Sicherheits-Review 25.08.2026). Pangolin
|
|
terminiert TLS und kann HSTS ergaenzen, das hier ist die App-seitige
|
|
Schicht unabhaengig davon. CSP erlaubt 'unsafe-inline' nur fuer Styles
|
|
(einzelne style="display:inline"-Attribute in ein paar Admin-Templates),
|
|
nicht fuer Skripte - die Templates enthalten ohnehin kein Inline-JS.
|
|
"""
|
|
|
|
response = await call_next(request)
|
|
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
|
response.headers.setdefault("X-Frame-Options", "DENY")
|
|
response.headers.setdefault("Referrer-Policy", "same-origin")
|
|
response.headers.setdefault(
|
|
"Content-Security-Policy",
|
|
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:",
|
|
)
|
|
|
|
return response
|
|
|
|
app.include_router(auth.router)
|
|
app.include_router(dashboard.router)
|
|
app.include_router(geraete.router)
|
|
app.include_router(organisation.router)
|
|
app.include_router(personalisierung.router)
|
|
app.include_router(struktur.router)
|
|
app.include_router(gruppen.router)
|
|
app.include_router(workspace.router)
|
|
app.include_router(installationsmedium.router)
|
|
app.include_router(webapps.router)
|
|
app.include_router(admin_technik.router)
|
|
app.include_router(admin_workspaces.router)
|
|
app.include_router(admin_workspaces.technik_weiterleitung)
|
|
app.include_router(admin_geraete.router)
|
|
app.include_router(admin_flows.router)
|
|
app.include_router(admin_kunden.router)
|
|
app.include_router(admin_accounts.router)
|
|
app.include_router(admin_dashboard.router)
|