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
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""Mot de passe de l'interface (`rtg mot-de-passe`).
|
|
|
|
Il est rangé hors du site (le dossier du site est souvent servi tel quel par
|
|
le serveur web) : ~/.config/rtg/acces.toml sous macOS et Linux,
|
|
%APPDATA%\\rtg\\acces.toml sous Windows. Seule une empreinte scrypt salée y
|
|
est écrite, jamais le mot de passe.
|
|
"""
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import os
|
|
import secrets
|
|
|
|
from rtg.config import ecrire_toml, lire_toml
|
|
|
|
LONGUEUR_MIN = 10
|
|
SCRYPT = {"n": 2 ** 15, "r": 8, "p": 1}
|
|
|
|
|
|
def chemin_acces():
|
|
if os.name == "nt":
|
|
base = os.environ.get("APPDATA") or os.path.expanduser("~")
|
|
else:
|
|
base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
|
|
return os.path.join(base, "rtg", "acces.toml")
|
|
|
|
|
|
def _empreinte(mdp, sel, n, r, p):
|
|
return hashlib.scrypt(mdp.encode("utf-8"), salt=sel, n=n, r=r, p=p,
|
|
maxmem=128 * r * n * 2, dklen=32)
|
|
|
|
|
|
def definir(mdp):
|
|
if len(mdp) < LONGUEUR_MIN:
|
|
raise ValueError(f"Le mot de passe doit faire au moins {LONGUEUR_MIN} caractères.")
|
|
sel = secrets.token_bytes(16)
|
|
e = _empreinte(mdp, sel, **SCRYPT)
|
|
chemin = chemin_acces()
|
|
os.makedirs(os.path.dirname(chemin), exist_ok=True)
|
|
ecrire_toml(chemin, {"mot_de_passe": "scrypt${n}${r}${p}${sel}${e}".format(
|
|
**SCRYPT, sel=base64.b64encode(sel).decode(), e=base64.b64encode(e).decode())},
|
|
entete="Accès à l'interface RTG (empreinte du mot de passe, pas le mot de passe).")
|
|
try:
|
|
os.chmod(chemin, 0o600)
|
|
except OSError:
|
|
pass
|
|
return chemin
|
|
|
|
|
|
def defini():
|
|
return bool(lire_toml(chemin_acces()).get("mot_de_passe"))
|
|
|
|
|
|
def supprimer():
|
|
chemin = chemin_acces()
|
|
if os.path.exists(chemin):
|
|
os.remove(chemin)
|
|
return True
|
|
return False
|
|
|
|
|
|
def verifier(mdp):
|
|
"""Vrai si `mdp` est le mot de passe enregistré (comparaison à temps constant)."""
|
|
valeur = lire_toml(chemin_acces()).get("mot_de_passe", "")
|
|
try:
|
|
algo, n, r, p, sel, e = valeur.split("$")
|
|
if algo != "scrypt":
|
|
return False
|
|
attendu = base64.b64decode(e)
|
|
calcule = _empreinte(mdp, base64.b64decode(sel), int(n), int(r), int(p))
|
|
except (ValueError, TypeError):
|
|
return False
|
|
return hmac.compare_digest(attendu, calcule)
|