fix(ui): ne plus réclamer un code déjà validé après une reconnexion (v0.8.6)
Le client tenait pour acquis que l'autorisation ne survivait pas à la chute du tunnel — un commentaire l'affirmait noir sur blanc. C'est l'inverse : le serveur autorise l'adresse de tunnel du pair jusqu'à l'échéance enregistrée, et cette adresse ne change pas d'une reconnexion à l'autre. Se reconnecter ne révoque rien ; seul le client oubliait, et redemandait un code que le serveur avait déjà accepté. Il pose maintenant la question au lieu de la supposer, via GET /api/session (serveur >= 0.3.0), dès que le tunnel monte : démarrage sur un tunnel déjà actif, reconnexion automatique, ou avant toute invite de saisie. Le serveur identifie l'appelant à l'IP source, aucun identifiant ne circule. Serveur injoignable, réponse illisible ou serveur antérieur à la 0.3.0 ne valent pas « accès ouvert » : sans information, le client ne conclut rien et laisse le bouton disponible — le sens sûr, et le comportement de la 0.8.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,48 @@ def api_base_url(cfg: Config) -> str:
|
||||
return f"http://{gateway}:8000"
|
||||
|
||||
|
||||
def session_status(cfg: Config) -> AuthResult:
|
||||
"""Demande au serveur si l'accès de ce poste est déjà ouvert.
|
||||
|
||||
Un tunnel qui tombe et remonte ne révoque rien côté serveur :
|
||||
l'autorisation porte sur l'adresse du pair, et cette adresse ne change
|
||||
pas. Le client, lui, oubliait tout à chaque reconnexion et réclamait un
|
||||
code déjà validé — c'est ce que cette question évite.
|
||||
|
||||
Aucun identifiant n'est envoyé : le serveur reconnaît l'appelant à l'IP
|
||||
source du tunnel, et ne renseigne donc jamais sur un autre compte que
|
||||
celui qui parle. Rien à demander avant que le tunnel ne soit monté,
|
||||
l'API n'étant joignable que par l'intérieur.
|
||||
|
||||
Un `ok` faux ne distingue pas « accès fermé » de « serveur injoignable » :
|
||||
l'appelant traite les deux pareil — il redemande un code, ce qui est le
|
||||
comportement sûr dans le doute.
|
||||
"""
|
||||
base = api_base_url(cfg)
|
||||
if not base:
|
||||
return AuthResult(False, "Adresse du serveur d'authentification inconnue.")
|
||||
|
||||
request = urllib.request.Request(f"{base}/api/session", method="GET")
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=_TIMEOUT) as response:
|
||||
body = json.loads(response.read().decode("utf-8"))
|
||||
until = _parse_until(body.get("access_until"))
|
||||
if until is None:
|
||||
return AuthResult(True, "Accès réseau déjà ouvert")
|
||||
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 = ""
|
||||
return AuthResult(False, detail or f"Accès fermé (code {exc.code})")
|
||||
except (urllib.error.URLError, OSError, ValueError) as exc:
|
||||
# Serveur ancien (route absente), injoignable, ou réponse illisible :
|
||||
# aucune information, donc aucune conclusion — on ne prétend pas que
|
||||
# l'accès est ouvert.
|
||||
return AuthResult(False, f"État de l'accès inconnu : {exc}")
|
||||
|
||||
def authenticate(cfg: Config, code: str, username: str = "") -> AuthResult:
|
||||
"""Valide un code TOTP auprès du serveur et ouvre l'accès réseau.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user