feat(ui): une saisie réussie se voit, et le trafic est vérifié derrière
Un code accepté ne se manifestait que par une ligne de journal, écrite pendant que la fenêtre de saisie se refermait : rien à l'écran ne distinguait une validation d'un abandon, et l'échéance obtenue — ce que l'utilisateur venait précisément de demander — restait invisible. Une confirmation l'annonce désormais, avec sa date de fin et le temps restant. Le serveur peut par ailleurs enregistrer l'autorisation sans que le trafic passe pour autant : la règle de pare-feu peut ne pas s'appliquer, et le réseau reste alors muet malgré un code accepté. Le client le constate maintenant en fond, en observant ce qui passe là où le sondage se contente de demander au serveur ce qu'il a enregistré. `remote_network_reachable` devient publique pour cela — l'interface l'appelle, et un nom privé traversant la frontière du module aurait menti sur sa portée. Corrige au passage un refus qui, au lieu de s'afficher, interrompait le traitement de la réponse. Contre un serveur antérieur à la v0.3.2, qui exige encore l'identifiant que ce poste ne transmet plus, `POST /api/session` répond 422 ; le `detail` d'une erreur de validation FastAPI est une liste, non une phrase, et elle atterrissait telle quelle dans un champ que l'interface passe ensuite à Qt. Les deux gestionnaires `HTTPError` extrayaient ce champ par les mêmes quatre lignes : elles deviennent `_error_detail`, qui ne retient qu'une chaîne et rattrape en plus le corps JSON sans objet, sur lequel `.get` levait une `AttributeError` non capturée. Le 422 dit désormais quoi faire — mettre le serveur à jour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+30
-10
@@ -101,6 +101,23 @@ def _parse_until(raw: object) -> datetime | None:
|
||||
return moment if moment.tzinfo else moment.replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _error_detail(exc: urllib.error.HTTPError) -> str:
|
||||
"""Le motif du refus tel que le serveur l'énonce, ou "" s'il n'en dit rien.
|
||||
|
||||
FastAPI répond aux erreurs de validation (422) par un `detail` qui est une
|
||||
*liste* d'erreurs, pas une phrase. Relayée telle quelle, elle atterrissait
|
||||
dans un champ `message` que l'interface passe ensuite à Qt : le refus
|
||||
n'était alors pas affiché, il levait une exception au moment de le lire.
|
||||
Seule une chaîne est un message ; tout autre corps ne dit rien
|
||||
d'affichable, et le message générique de l'appelant vaut mieux.
|
||||
"""
|
||||
try:
|
||||
detail = json.loads(exc.read().decode("utf-8")).get("detail", "")
|
||||
except (ValueError, OSError, AttributeError):
|
||||
return "" # corps illisible, ou JSON sans objet
|
||||
return detail if isinstance(detail, str) else ""
|
||||
|
||||
|
||||
def api_base_url(cfg: Config) -> str:
|
||||
"""URL de l'API du serveur, telle qu'atteignable depuis le tunnel.
|
||||
|
||||
@@ -179,7 +196,7 @@ def _routed_through_tunnel(cfg: Config, target: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _remote_network_reachable(cfg: Config) -> bool:
|
||||
def remote_network_reachable(cfg: Config) -> bool:
|
||||
"""Le trafic passe-t-il réellement vers le réseau distant ?
|
||||
|
||||
Repli pour les serveurs antérieurs à la v0.3.0, qui n'exposent pas
|
||||
@@ -243,11 +260,8 @@ def session_status(cfg: Config) -> AuthResult:
|
||||
return AuthResult(True, "Accès réseau déjà ouvert jusqu'à "
|
||||
f"{until.astimezone():%d/%m/%Y %H:%M}", until)
|
||||
except urllib.error.HTTPError as exc:
|
||||
try:
|
||||
detail = json.loads(exc.read().decode("utf-8")).get("detail", "")
|
||||
except (ValueError, OSError):
|
||||
detail = ""
|
||||
if exc.code == 404 and _remote_network_reachable(cfg):
|
||||
detail = _error_detail(exc)
|
||||
if exc.code == 404 and remote_network_reachable(cfg):
|
||||
# Serveur antérieur à la v0.3.0 : la route n'existe pas, mais le
|
||||
# trafic atteint le réseau distant — l'accès est donc bien ouvert.
|
||||
# Aucune échéance en revanche : seul le serveur la connaît, et il
|
||||
@@ -324,10 +338,16 @@ def authenticate(cfg: Config, code: str) -> AuthResult:
|
||||
# Le serveur explique lui-même le refus (code invalide, compte
|
||||
# verrouillé, expiré…) : le relayer tel quel vaut mieux qu'un
|
||||
# message générique qui obligerait à consulter les journaux.
|
||||
try:
|
||||
detail = json.loads(exc.read().decode("utf-8")).get("detail", "")
|
||||
except (ValueError, OSError):
|
||||
detail = ""
|
||||
detail = _error_detail(exc)
|
||||
if exc.code == 422:
|
||||
# Serveur antérieur à la v0.3.2 : il exige encore un identifiant
|
||||
# dans le corps, que ce poste ne transmet plus. Son `detail` est
|
||||
# la liste d'erreurs de validation de FastAPI — `_error_detail`
|
||||
# l'écarte, et il n'en resterait qu'un « code 422 » énigmatique.
|
||||
return AuthResult(False, "Ce serveur attend un identifiant VPN "
|
||||
"que ce poste ne transmet plus. Mettez "
|
||||
"le serveur à jour (v0.3.2 ou "
|
||||
"supérieur).")
|
||||
return AuthResult(False, detail
|
||||
or f"Authentification refusée (code {exc.code})")
|
||||
except urllib.error.URLError as exc:
|
||||
|
||||
Reference in New Issue
Block a user