Architecture backend (FastAPI)¶
backend/
├── pyproject.toml, uv.lock, alembic.ini
├── alembic/
│ ├── env.py
│ └── versions/ # migrations (0001 → 0004)
└── app/
├── main.py # point d'entrée FastAPI
├── config.py # AppSettings (pydantic-settings)
├── database.py # moteur async + session
├── auth.py # cookies JWT, dépendances get_current_user/admin
├── security.py # middlewares (CSRF, rate-limit, en-têtes)
├── proxy.py # résolution IP client derrière reverse-proxy
├── exceptions.py # handlers d'erreurs normalisés
├── helpers.py # utilitaires (ex. anti path-traversal)
├── models.py # entités SQLModel (tables)
├── schemas.py # schémas Pydantic (entrée/sortie API)
├── routers/ # un fichier par domaine fonctionnel
├── services/ # logique métier réutilisable
├── static/swagger/ # Swagger UI auto-hébergé
└── templates/ # emails HTML (en/fr/es)
main.py¶
C'est le point d'entrée de l'application (app.main:app). Il assemble tout ce qui suit, dans cet ordre :
1. Création de l'app¶
app = FastAPI(
title="WireGuard UI",
version=app_settings.app_version,
lifespan=lifespan,
docs_url=None, # Swagger géré manuellement (auto-hébergé)
redoc_url=None,
openapi_url="/api/openapi.json" if app_settings.swagger_enabled else None,
)
2. Middlewares (dans l'ordre d'ajout)¶
| Middleware | Rôle |
|---|---|
SecurityHeadersMiddleware |
Ajoute les en-têtes de sécurité (CSP, HSTS, X-Frame-Options, etc.) à chaque réponse. |
CsrfMiddleware |
Protection CSRF double-submit cookie : sur les méthodes non sûres (POST/PUT/PATCH/DELETE) d'une route /api/* authentifiée par cookie, exige que le header X-CSRF-Token corresponde au cookie csrf_token. |
RateLimitMiddleware |
Limite le nombre de requêtes par IP (fenêtre glissante), avec un seuil plus strict pour /api/auth/login et /api/auth/token. |
Détails complets : Authentification & sécurité.
3. Exception handlers¶
RequestValidationError et StarletteHTTPException sont interceptées et reformatées en JSON normalisé {code, message, details} (exceptions.py), pour que le frontend Angular ait un format d'erreur prévisible.
4. Cycle de vie (lifespan)¶
@asynccontextmanager
async def lifespan(app: FastAPI):
await seed_initial_data()
if app_settings.wg_autostart:
await auto_start_wireguard()
yield
await engine.dispose()
seed_initial_data()(services/seed.py) : crée les rôles, l'administrateur initial et les réglages par défaut s'ils n'existent pas déjà. Exécuté à chaque démarrage (idempotent).auto_start_wireguard(): siWIREGUARD_AUTOSTARTest activé, écrit la configuration serveur (write_server_config) et démarre le service WireGuard (start_service). En cas d'échec, jusqu'à 3 tentatives espacées de 5 secondes, puis abandon avec un log d'erreur — sans bloquer le démarrage de l'API.- À l'arrêt,
engine.dispose()ferme proprement le pool de connexions à la base.
5. Routers¶
Chaque router est monté avec un préfixe /api/<domaine> (détail complet dans Routers (endpoints)) :
app.include_router(auth.router, prefix="/api/auth")
app.include_router(clients.router, prefix="/api/clients")
app.include_router(server.router, prefix="/api/server")
app.include_router(settings.router, prefix="/api/settings")
app.include_router(oidc.router, prefix="/api/oidc")
app.include_router(status.router, prefix="/api/status")
app.include_router(users.router, prefix="/api/users")
app.include_router(smtp.router, prefix="/api/smtp")
app.include_router(audit.router, prefix="/api/audit")
app.include_router(pat.router, prefix="/api/pat")
6. Endpoints hors routers¶
GET /api/health— healthcheck simple, exclu du rate-limiting.GET /api/docs— Swagger UI auto-hébergé (aucune dépendance CDN), actif uniquement siSWAGGER_ENABLED=true.GET /{full_path:path}— doit rester en dernier : sert le build Angular (frontend/dist/wireguard-ui/browsercopié en production dansfrontend/relatif àbackend/app/). Utiliseresolve_safe_path()pour empêcher tout accès en dehors du dossier du SPA (path traversal), avec repli surindex.htmlpour laisser le routeur Angular gérer les routes côté client.
config.py¶
Classe AppSettings(BaseSettings) chargée une fois via get_settings() (@lru_cache) et exposée comme singleton app_settings. Voir le détail complet des variables dans Variables d'environnement.
Point notable : _resolve_secret_key() génère et persiste une clé secrète aléatoire dans DATA_DIR/secret_key si SECRET_KEY n'est pas fourni explicitement, pour que les sessions restent valides entre redémarrages sans exposer une valeur par défaut connue.
database.py¶
Moteur SQLAlchemy asynchrone (create_async_engine, driver aiosqlite ou asyncpg) et une factory de session (AsyncSessionLocal). La dépendance FastAPI get_db() fournit une session par requête, avec rollback automatique en cas d'exception.
proxy.py¶
Résout l'adresse IP réelle du client (client_ip(request)), en tenant compte de X-Forwarded-For uniquement si la requête provient d'une IP listée dans TRUSTED_PROXIES. Utilisé par le rate-limiting et par la détection HTTPS (pour marquer le cookie de session Secure).
Modèles et schémas¶
models.py: entités SQLModel table (persistées en base) — voir Base de données & migrations.schemas.py: schémas Pydantic non-table utilisés pour la validation des requêtes et le formatage des réponses API (ex.ClientCreate,UserResponse,AuditLogPage…), regroupés par domaine fonctionnel.
Diagramme de la requête HTTP¶
flowchart TD
Req["Requête HTTP"] --> SH[SecurityHeadersMiddleware]
SH --> CSRF[CsrfMiddleware]
CSRF --> RL[RateLimitMiddleware]
RL --> Router["Router concerné (/api/...)"]
Router --> Dep["Dépendances FastAPI\n(get_db, get_current_user/admin)"]
Dep --> Service["Service métier"]
Service --> DB[(Base de données)]
Service --> WG["wireguard.py (subprocess)"]
Router --> Resp["Réponse JSON\n(schemas.py)"]