La v0.8.0 laissait cohabiter deux vérifications du code à 6 chiffres : la fenêtre MFA locale, qui contrôlait un code contre un secret détenu par le poste lui-même — et ne verrouillait donc que sa propre interface — puis la validation par le serveur, seule à décider de l'accès réseau. L'ancienne disparaît, avec le secret TOTP qu'elle stockait en clair dans config.json (effacé au premier lancement) et l'onglet MFA du panneau Administrateur. La saisie passe par une fenêtre dédiée qui relaie le refus exact du serveur et laisse réessayer, la requête partant dans un thread pour qu'un serveur muet ne fige pas l'application. Une ligne « Accès distant » distingue désormais l'état de l'autorisation de celui du tunnel : monté sans code validé, celui-ci ne mène nulle part. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
6.4 KiB
Python
149 lines
6.4 KiB
Python
"""Contenu de la page « À propos ».
|
|
|
|
Source de vérité unique pour les textes de la page : l'onglet Qt
|
|
(app/ui/admin_window.py, méthode `_tab_about`) ne fait que les mettre en
|
|
forme. La version, elle, vient de `app.__version__` (app/__init__.py), le
|
|
seul endroit où elle est bumpée à chaque release — jamais recopiée en dur ici.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from app import __version__
|
|
|
|
# En développement, CHANGELOG.md vit à la racine du dépôt, deux niveaux
|
|
# au-dessus de ce fichier. Une fois empaqueté par PyInstaller (--onefile),
|
|
# `__file__` pointe vers l'intérieur de l'exécutable et ce chemin n'existe
|
|
# plus : le fichier doit alors être cherché dans `sys._MEIPASS`, le dossier
|
|
# temporaire où l'exe extrait les données embarquées via `--add-data`.
|
|
if getattr(sys, "frozen", False):
|
|
_CHANGELOG_PATH = os.path.join(getattr(sys, "_MEIPASS", ""), "CHANGELOG.md")
|
|
else:
|
|
_CHANGELOG_PATH = os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "CHANGELOG.md"
|
|
)
|
|
|
|
APP_NAME = "WGSecure"
|
|
TAGLINE = "Client WireGuard à accès authentifié par le serveur, pour Linux et Windows"
|
|
VERSION = __version__
|
|
LICENSE = "Usage personnel"
|
|
REPO_URL = "https://git.tuxtech.fr/tuxgyver/WGSecure"
|
|
AUTHOR_NAME = "Johnny"
|
|
AUTHOR_ROLE = "Auteur"
|
|
|
|
FEATURES = [
|
|
(
|
|
"Connexion en un clic",
|
|
"Icône dans la barre système, montage/démontage du tunnel WireGuard "
|
|
"sans quitter le systray.",
|
|
),
|
|
(
|
|
"Suivi en direct",
|
|
"Statut RX/TX, dernier handshake et latence affichés en continu ; "
|
|
"graphique de bande passante sur les 30 dernières secondes.",
|
|
),
|
|
(
|
|
"Journal et historique",
|
|
"Horodatage de chaque connexion, déconnexion et erreur ; historique "
|
|
"des sessions passées (durée, volume transféré, serveur).",
|
|
),
|
|
(
|
|
"Multi-profils",
|
|
"Plusieurs configurations WireGuard sauvegardées et basculables, "
|
|
"avec import/export de fichiers .conf standards. Les champs issus "
|
|
"d'un import se verrouillent pour éviter une divergence silencieuse "
|
|
"avec le pair déjà configuré côté serveur.",
|
|
),
|
|
(
|
|
"Génération de clés",
|
|
"Paire de clés Curve25519 générée nativement en Python, sans "
|
|
"dépendre du binaire `wg` pour cette étape.",
|
|
),
|
|
(
|
|
"Mode Admin protégé",
|
|
"Configuration complète (serveur, DNS, IP, keepalive, MTU) derrière "
|
|
"un mot de passe, et l'accès aux ressources distantes par un code "
|
|
"à 6 chiffres validé par le serveur.",
|
|
),
|
|
(
|
|
"Diagnostic réseau",
|
|
"Test de handshake réel sans monter le tunnel, détection du "
|
|
"filtrage sur port source, et test de fuite DNS.",
|
|
),
|
|
(
|
|
"Auto-reconnexion",
|
|
"Surveillance du tunnel et reconnexion automatique en cas de "
|
|
"coupure, à l'intervalle choisi.",
|
|
),
|
|
(
|
|
"Split-DNS",
|
|
"Configuration d'un serveur DNS dédié pour les noms du réseau distant "
|
|
"(ex: 192.168.1.210 pour .local), tout en utilisant le DNS système "
|
|
"pour le reste du trafic internet. Le test de connexion vérifie que "
|
|
"les domaines configurés s'y résolvent réellement, et un outil de "
|
|
"résolution à la demande permet de tester n'importe quel nom précis.",
|
|
),
|
|
]
|
|
|
|
GUARANTEES_INTRO = "Ce que l'application garantit, quel que soit le profil configuré :"
|
|
GUARANTEES = [
|
|
"Le mot de passe du mode Admin n'est jamais stocké en clair : il est "
|
|
"haché avec bcrypt (12 tours) avant d'être écrit dans la configuration.",
|
|
"La clé privée WireGuard est toujours écrite avec des permissions "
|
|
"restrictives (600) — aussi bien dans la configuration locale que dans "
|
|
"le fichier installé sous /etc/wireguard.",
|
|
"Le test de handshake et le sondage de statut (dernier handshake, "
|
|
"RX/TX) n'ouvrent jamais de tunnel et ne modifient aucun fichier : ce "
|
|
"sont des lectures ou un simple datagramme UDP jetable.",
|
|
"Les commandes système nécessitant les privilèges root sont exécutées "
|
|
"une par une, jamais l'application entière : `make setup-sudoers` "
|
|
"autorise sans mot de passe uniquement wg-quick, wg, et la suppression "
|
|
"d'interface/DNS orpheline — jamais un accès root généralisé.",
|
|
"Le sondage périodique de l'état du tunnel (toutes les 3 secondes) "
|
|
"n'ouvre jamais de dialogue de mot de passe ou d'UAC : il échoue "
|
|
"silencieusement si les privilèges nécessaires ne sont pas déjà "
|
|
"accordés.",
|
|
"Le test de fuite DNS vérifie que les requêtes sortent bien par le "
|
|
"resolver attendu une fois le tunnel monté, plutôt que de supposer la "
|
|
"configuration correcte.",
|
|
]
|
|
|
|
TECH_STACK = [
|
|
("Interface", "PyQt6"),
|
|
("Cryptographie", "cryptography (X25519, ChaCha20-Poly1305, bcrypt)"),
|
|
("QR Code", "qrcode/Pillow pour l'export de la config client"),
|
|
("Tunnel", "wg-quick / wg (Linux), WireGuard for Windows"),
|
|
("Élévation de privilèges", "sudo NOPASSWD ciblé, repli askpass puis pkexec"),
|
|
("Empaquetage", "PyInstaller (Linux/Windows) + Inno Setup (installeur Windows)"),
|
|
]
|
|
|
|
|
|
def parse_changelog() -> list[dict]:
|
|
"""Lit CHANGELOG.md : titres `## [x.y.z] — date`, sous-titres `### ...`
|
|
et puces `- ...` (avec leurs lignes de continuation) uniquement — le
|
|
format effectivement utilisé par ce fichier, sans dépendance Markdown.
|
|
"""
|
|
entries: list[dict] = []
|
|
version = None
|
|
section = None
|
|
try:
|
|
with open(_CHANGELOG_PATH, encoding="utf-8") as f:
|
|
for raw in f:
|
|
line = raw.rstrip("\n")
|
|
if line.startswith("## "):
|
|
version = {"title": line[3:].strip(), "sections": []}
|
|
entries.append(version)
|
|
section = None
|
|
elif line.startswith("### ") and version is not None:
|
|
section = {"name": line[4:].strip(), "items": []}
|
|
version["sections"].append(section)
|
|
elif line.startswith("- ") and section is not None:
|
|
section["items"].append(line[2:].strip())
|
|
elif (line.strip() and section is not None and section["items"]
|
|
and not line.startswith(("#", "---"))):
|
|
# Ligne de continuation d'une puce étalée sur plusieurs lignes.
|
|
section["items"][-1] += " " + line.strip()
|
|
except FileNotFoundError:
|
|
pass
|
|
return entries
|