Interface : corrections à la souris et accès protégé
Onglet « Carte et corrections » : glisser une photo ou une vidéo, placer les médias sans position, masquer des zones de trace (pour un voyage, ou zone privée pour tous). Corrections notées dans voyage.toml ([positions], [corrections]) ou site.toml ([confidentialite]), appliquées par les pages ; les originaux ne changent pas. L'extraction garde les médias datés sans position pour qu'on puisse les placer. Accès : clé d'accès aléatoire à chaque lancement, session par cookie HttpOnly/SameSite=Strict, mot de passe facultatif (rtg mot-de-passe, empreinte scrypt hors du site, blocage après 5 échecs), accès réseau seulement avec mot de passe (--distant), HTTPS possible (--certificat, --cle), Content-Security-Policy et interdiction des cadres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
1 parent
06153a227e
commit
71779f6f53
17 files changed
+1047
-38
No files matched your search
@@ -0,0 +1,93 @@
|
||||
"""Contrôle d'accès à l'interface.
|
||||
|
||||
Deux façons d'entrer :
|
||||
- la clé d'accès (jeton) tirée au hasard à chaque lancement et ajoutée à
|
||||
l'adresse que RTG ouvre dans le navigateur (et affiche dans le Terminal) :
|
||||
seul celui qui voit ce Terminal peut entrer ;
|
||||
- le mot de passe défini par `rtg mot-de-passe`, indispensable pour un accès
|
||||
depuis une autre machine (`rtg interface --distant`).
|
||||
|
||||
Une fois entré, le navigateur garde un cookie de session (HttpOnly,
|
||||
SameSite=Strict), valable 12 heures après la dernière requête et perdu à
|
||||
l'arrêt de l'interface. Après 5 mots de passe faux, une adresse IP est
|
||||
bloquée une minute, puis deux, quatre… jusqu'à une heure.
|
||||
"""
|
||||
import hmac
|
||||
import secrets
|
||||
import threading
|
||||
import time
|
||||
|
||||
from rtg import acces
|
||||
|
||||
DUREE_SESSION = 12 * 3600
|
||||
ESSAIS_LIBRES = 5
|
||||
|
||||
|
||||
class Gardien:
|
||||
def __init__(self, port, distant=False, https=False):
|
||||
self.jeton = secrets.token_urlsafe(24)
|
||||
self.cookie = f"rtg_{port}"
|
||||
self.distant, self.https = distant, https
|
||||
self._sessions = {}
|
||||
self._echecs = {} # ip → (nombre d'échecs, heure du dernier)
|
||||
self._verrou = threading.Lock()
|
||||
|
||||
# --- sessions
|
||||
def ouvrir_session(self):
|
||||
sid = secrets.token_urlsafe(32)
|
||||
with self._verrou:
|
||||
self._sessions[sid] = time.time() + DUREE_SESSION
|
||||
return sid
|
||||
|
||||
def session_valide(self, sid):
|
||||
if not sid:
|
||||
return False
|
||||
maintenant = time.time()
|
||||
with self._verrou:
|
||||
fin = self._sessions.get(sid)
|
||||
if not fin or fin < maintenant:
|
||||
self._sessions.pop(sid, None)
|
||||
return False
|
||||
self._sessions[sid] = maintenant + DUREE_SESSION
|
||||
return True
|
||||
|
||||
def fermer_session(self, sid):
|
||||
with self._verrou:
|
||||
self._sessions.pop(sid, None)
|
||||
|
||||
def entete_cookie(self, sid, effacer=False):
|
||||
valeur = f"{self.cookie}={'' if effacer else sid}; Path=/; HttpOnly; SameSite=Strict"
|
||||
if effacer:
|
||||
valeur += "; Max-Age=0"
|
||||
if self.https:
|
||||
valeur += "; Secure"
|
||||
return valeur
|
||||
|
||||
# --- clé d'accès et mot de passe
|
||||
def jeton_valide(self, jeton):
|
||||
return bool(jeton) and hmac.compare_digest(jeton.encode(), self.jeton.encode())
|
||||
|
||||
def attente(self, ip):
|
||||
"""Secondes à attendre avant un nouvel essai (0 si libre)."""
|
||||
with self._verrou:
|
||||
n, dernier = self._echecs.get(ip, (0, 0))
|
||||
if n < ESSAIS_LIBRES:
|
||||
return 0
|
||||
blocage = min(60 * 2 ** (n - ESSAIS_LIBRES), 3600)
|
||||
return max(0, int(dernier + blocage - time.time()))
|
||||
|
||||
def essayer(self, ip, mdp):
|
||||
"""Vérifie le mot de passe. Renvoie (ok, message d'erreur)."""
|
||||
reste = self.attente(ip)
|
||||
if reste:
|
||||
return False, f"Trop d'essais : réessayez dans {reste} s."
|
||||
if not acces.defini():
|
||||
return False, "Aucun mot de passe n'est défini (commande rtg mot-de-passe)."
|
||||
if acces.verifier(mdp):
|
||||
with self._verrou:
|
||||
self._echecs.pop(ip, None)
|
||||
return True, ""
|
||||
with self._verrou:
|
||||
n, _ = self._echecs.get(ip, (0, 0))
|
||||
self._echecs[ip] = (n + 1, time.time())
|
||||
return False, "Mot de passe incorrect."
|
||||
Reference in new issue
Block a user