Phase 2.1 : preparation au deploiement public

Rend une instance exposee sur Internet exploitable sans la transformer en
infrastructure : le produit reste local-first, sans compte et sans telemetrie.

Protection du service : limitation de debit en memoire par client et par
famille d'endpoint (health et ready jamais limites), interrupteur
SHARING_ENABLED, capacite maximale de partages, taille propre aux partages.
X-Forwarded-For n'est cru que si TRUST_PROXY_HEADERS l'autorise, et on lit alors
la derniere valeur, celle ajoutee par le proxy.

Expiration facultative des partages (24 h a 90 jours, ou jamais) : un lien
expire repond 410 et la page publique le dit, sans autre detail. Le proprietaire
peut prolonger ; aucune purge automatique.

Protection des liens non repertories : robots.txt, X-Robots-Tag et meta robots
sur /s/ et /api/, Referrer-Policy no-referrer sur tout le site, redaction des
jetons dans les journaux d'acces, share_token porte a 128 bits (les anciens
restent valides).

Schema gere par Alembic, applique au demarrage. Une base creee avant Alembic est
estampillee puis migree : verifie sur la base reelle de developpement, 4
partages inchanges et expires_at NULL.

Administration par CLI locale au conteneur (stats, liste, suppression, purge,
sauvegarde a chaud via l'API SQLite, restauration prudente) : aucune interface
/admin exposee.

Le jeton de gestion peut enfin etre exporte dans un fichier .openmind-share puis
reimporte depuis un autre navigateur, apres verification aupres du serveur. Il
n'est toujours jamais ajoute a un export .mmap.

Ajoute une page /privacy dont le texte est centralise, et corrige un bug trouve
en observant les logs : Alembic, lance dans le processus applicatif,
reappliquait la configuration de logs d'alembic.ini et desactivait les loggers
existants, ce qui faisait disparaitre les journaux d'acces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 09:22:17 +02:00
parent e3c530348e
commit 49870dcd56
51 changed files with 3341 additions and 110 deletions

View File

@@ -1,19 +1,48 @@
"""Endpoints de sante et de configuration publique."""
"""Endpoints de sante et de configuration publique.
Aucune limitation de debit n'est posee ici : le healthcheck du conteneur doit
rester fiable meme quand une instance subit un afflux de requetes.
"""
from __future__ import annotations
from fastapi import APIRouter
import logging
from app.api.deps import AppSettings
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from sqlalchemy import text
from app.api.deps import AppSettings, DbSession
logger = logging.getLogger(__name__)
router = APIRouter(tags=["system"])
# Durees proposees dans l'interface. `None` = permanent.
SHARE_EXPIRY_OPTIONS: list[int | None] = [1, 7, 30, 90, None]
@router.get("/health", summary="Etat du service")
def health() -> dict[str, str]:
return {"status": "ok"}
@router.get("/ready", summary="Le service peut-il repondre : base de donnees joignable")
def ready(session: DbSession): # type: ignore[no-untyped-def]
"""Complete /health : verifie que SQLite repond reellement.
Utile derriere un orchestrateur qui doit distinguer "processus vivant" de
"capable de servir", par exemple quand le volume de donnees est absent ou
en lecture seule.
"""
try:
session.execute(text("SELECT 1"))
except Exception:
logger.exception("Base de donnees injoignable")
return JSONResponse(status_code=503, content={"status": "unavailable"})
return {"status": "ready"}
@router.get("/config", summary="Configuration publique exposee au frontend")
def public_config(settings: AppSettings) -> dict[str, object]:
"""Aucun secret ici : uniquement ce dont l'interface a besoin."""
@@ -21,5 +50,9 @@ def public_config(settings: AppSettings) -> dict[str, object]:
"appName": settings.app_name,
"maxNodes": settings.max_nodes,
"maxMapSizeMb": settings.max_map_size_mb,
"sharingEnabled": True,
"maxShareSizeMb": settings.max_share_size_mb_effective,
"sharingEnabled": settings.sharing_enabled,
"allowPermanentShares": settings.allow_permanent_shares,
"defaultShareExpiryDays": settings.default_share_expiry_days,
"shareExpiryOptions": SHARE_EXPIRY_OPTIONS,
}