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,73 @@
|
||||
"""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)
|
||||
Reference in new issue
Block a user