Administration depuis le site : bouton Connexion sur l'accueil

site.toml [administration] adresse : l'accueil affiche un bouton
« Connexion » vers l'interface. Celle-ci peut tourner derrière le serveur
web du site (rtg interface --proxy --prefixe /admin) : adresses
préfixées, mot de passe obligatoire, visiteur identifié par
X-Forwarded-For, cookie Secure limité au préfixe quand le site est en
HTTPS. Exemples nginx et Apache dans le README. Le bouton Arrêter ne
remplace plus le bloc de la tâche en même temps que la relève.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
FabriceandClaude Opus 5.5 committed 2026-10-09 14:49:47 +00:00
1 parent 71779f6f53
commit 4366db94ed
22 files changed
+229 -86

No files matched your search

+43
View File
@@ -176,3 +176,46 @@ def test_corrections(client):
for pistes in v.traces.values():
for piste, pts in pistes:
assert all(corrections.distance_m(la, lo, 56.7, 2.24) > 3000 for la, lo in pts)
def test_derriere_le_serveur_web(voyage_sans_heures, tmp_path, monkeypatch):
"""--proxy --prefixe /admin : adresses préfixées, mot de passe, cookie Secure."""
racine, voyage = voyage_sans_heures
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config"))
monkeypatch.setenv("APPDATA", str(tmp_path / "config"))
acces.definir("un mot de passe solide")
app = serveur.Application(str(racine), "/admin")
srv = ThreadingHTTPServer(("127.0.0.1", 0), None)
port = srv.server_address[1]
gardien = Gardien(port, proxy=True, prefixe=app.prefixe)
srv.RequestHandlerClass = serveur.creer_gestionnaire(app, port, gardien)
threading.Thread(target=srv.serve_forever, daemon=True).start()
ouvreur = urllib.request.build_opener(_SansRedirection)
entetes = {"Host": "roadtrip.example", "X-Forwarded-For": "203.0.113.7", "X-Forwarded-Proto": "https"}
def requete(chemin, donnees=None, extra=None):
req = urllib.request.Request(f"http://127.0.0.1:{port}{chemin}", headers=dict(entetes, **(extra or {})),
data=urllib.parse.urlencode(donnees).encode() if donnees else None)
try:
return ouvreur.open(req)
except urllib.error.HTTPError as e:
return e
try:
r = requete("/admin/")
assert r.status == 303 and r.headers["Location"] == "/admin/connexion"
assert requete("/").status == 404 # hors du préfixe
assert requete("/admin/connexion").status == 200
r = requete("/admin/connexion", {"mot_de_passe": "un mot de passe solide"}, {"HX-Request": "true"})
cookie = r.headers["Set-Cookie"]
assert r.headers["HX-Redirect"] == "/admin/" and "Path=/admin/" in cookie and "Secure" in cookie
sid = cookie.split(";")[0]
r = requete("/admin/", extra={"Cookie": sid})
html = r.read().decode()
assert r.status == 200 and 'href="/admin/nouveau"' in html and 'href="/"' in html # Voir le site
# Les échecs sont comptés par visiteur (X-Forwarded-For), pas pour le serveur web
for _ in range(5):
requete("/admin/connexion", {"mot_de_passe": "faux"}, {"HX-Request": "true"})
assert gardien.attente("203.0.113.7") > 0 and gardien.attente("127.0.0.1") == 0
finally:
srv.shutdown()
serveur.ENV.globals["base"] = ""