fix(ui): le bouton de saisie du code reparaît à l'expiration de l'accès (v0.8.5)
L'autorisation obtenue auprès du serveur a un terme, mais le client n'en tenait aucun compte : `access_until`, pourtant renvoyé à chaque validation, ne servait qu'à composer une ligne de journal. L'accès était marqué ouvert jusqu'à la chute du tunnel, si bien qu'une fois le délai écoulé l'interface affichait « Ouvert ✓ » et masquait le bouton comme l'entrée de systray — laissant l'utilisateur devant un réseau muet, sans issue visible, dans la situation même que ce bouton existe pour couvrir depuis la v0.8.2. L'échéance remonte désormais jusqu'à la fenêtre principale, qui la relit sans jamais la calculer : sa durée est un réglage serveur, que le poste n'a pas à deviner. Un serveur qui n'en annonce pas laisse l'accès ouvert jusqu'à la déconnexion, plutôt que de se voir imposer un terme inventé ici. Corrige au passage l'heure de fin, annoncée en UTC mais présentée comme locale — deux heures d'avance en été. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+38
-2
@@ -1,4 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
@@ -38,6 +41,10 @@ class MainWindow(QMainWindow):
|
||||
self._ping_worker: ValueWorker | None = None
|
||||
self._is_connected = False
|
||||
self._access_open = False
|
||||
# Échéance annoncée par le serveur pour l'autorisation en cours. None
|
||||
# signifie « aucune échéance connue » — soit aucun accès ouvert, soit
|
||||
# un serveur qui n'en annonce pas — et jamais « expire maintenant ».
|
||||
self._access_until: datetime | None = None
|
||||
self._reconnect_failures = 0
|
||||
self._quitting = False
|
||||
self._warned_foreign: set[str] = set()
|
||||
@@ -592,6 +599,21 @@ class MainWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------ #
|
||||
# Statut WireGuard
|
||||
# ------------------------------------------------------------------ #
|
||||
def _access_valid(self) -> bool:
|
||||
"""Vrai tant que l'autorisation obtenue court encore.
|
||||
|
||||
L'échéance vient du serveur et de lui seul : le client la relit, il
|
||||
ne la calcule pas — sa durée est un réglage serveur, modifiable sans
|
||||
que le poste en sache rien. Un serveur qui n'annonce aucune échéance
|
||||
laisse l'autorisation valable jusqu'à la chute du tunnel, faute de
|
||||
terme connu ; en inventer un fermerait un accès encore ouvert.
|
||||
"""
|
||||
if not self._access_open:
|
||||
return False
|
||||
if self._access_until is None:
|
||||
return True
|
||||
return datetime.now(timezone.utc) < self._access_until
|
||||
|
||||
def _access_state(self) -> str:
|
||||
"""État de l'autorisation réseau accordée par le serveur.
|
||||
|
||||
@@ -607,8 +629,14 @@ class MainWindow(QMainWindow):
|
||||
"""
|
||||
if not self._is_connected:
|
||||
return "—"
|
||||
if self._access_valid():
|
||||
if self._access_until is None:
|
||||
return "Ouvert ✓"
|
||||
return f"Ouvert ✓ jusqu'à {self._access_until.astimezone():%H:%M}"
|
||||
if self._access_open:
|
||||
return "Ouvert ✓"
|
||||
# L'autorisation a couru puis expiré : le dire, plutôt que de
|
||||
# laisser croire qu'aucun code n'a jamais été saisi.
|
||||
return "Autorisation expirée (code requis)"
|
||||
if (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
return "Quarantaine (code requis)"
|
||||
return "Non vérifié"
|
||||
@@ -642,6 +670,7 @@ class MainWindow(QMainWindow):
|
||||
# Tunnel tombé : l'autorisation obtenue auprès du serveur ne vaut
|
||||
# que pour l'adresse du pair, elle ne survit pas à la déconnexion.
|
||||
self._access_open = False
|
||||
self._access_until = None
|
||||
self._info_labels["access"].setText(self._access_state())
|
||||
|
||||
# Le bouton n'apparaît que lorsqu'il y a réellement un code à saisir ;
|
||||
@@ -759,6 +788,12 @@ class MainWindow(QMainWindow):
|
||||
Deux conditions seulement : un tunnel monté — l'API du serveur n'est
|
||||
joignable que par l'intérieur — et aucune autorisation en cours.
|
||||
|
||||
« En cours » et non « obtenue » : une autorisation a un terme, fixé
|
||||
par le serveur. Passé celui-ci le pair retourne en quarantaine sans
|
||||
que rien ne bouge côté poste, et le bouton doit reparaître — sans
|
||||
quoi l'utilisateur retrouve un réseau muet et un bouton masqué,
|
||||
exactement la situation que ce bouton existe pour éviter.
|
||||
|
||||
L'identifiant VPN n'en fait volontairement pas partie. Il en faisait
|
||||
partie, et c'était un piège : un poste bel et bien tenu en quarantaine
|
||||
par le serveur, mais dont ce champ n'avait jamais été renseigné,
|
||||
@@ -766,7 +801,7 @@ class MainWindow(QMainWindow):
|
||||
situation où il en avait besoin. La fenêtre de saisie demande
|
||||
l'identifiant quand il manque.
|
||||
"""
|
||||
return self._is_connected and not self._access_open
|
||||
return self._is_connected and not self._access_valid()
|
||||
|
||||
def request_access_code(self):
|
||||
"""Ouvre la saisie du code, à la demande de l'utilisateur.
|
||||
@@ -810,6 +845,7 @@ class MainWindow(QMainWindow):
|
||||
dlg = AccessCodeDialog(self._cfg, self)
|
||||
accepted = dlg.exec() == AccessCodeDialog.DialogCode.Accepted
|
||||
self._access_open = accepted
|
||||
self._access_until = dlg.access_until() if accepted else None
|
||||
if accepted:
|
||||
self._add_log(dlg.message(), "success")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user