From cd158466d1fbccb625ad59fd15cc1190d1d40382 Mon Sep 17 00:00:00 2001 From: webtecnica Date: Mon, 27 Jul 2026 15:05:22 -0300 Subject: [PATCH] fix(plugins/photon): clear stale token and re-enable channel after setup (#72763) Two related bugs in hermes photon setup / gateway_setup: Bug 1 -- stale token reused (401) ---------------------------------- _cmd_setup reused an existing dashboard token without validation. The device token has a short TTL (~3-4 days observed); reusing a stale token caused every management API call (find_project_by_name, regenerate_project_secret, etc.) to fail with 401. The operator saw confusing "spectrum provisioning failed: 401" errors. Fix: check GET /api/auth/get-session before using the stored token. On 401/403, clear the stale token with clear_photon_token() and fall back to a fresh device-login flow automatically. Bug 2 -- channel left disabled after successful setup ----------------------------------------------------- After all five provisioning steps completed, config.yaml still had photon.enabled: false, so the gateway never loaded the Photon adapter. Every inbound iMessage hit Photon's offline auto-responder without the operator being notified. Fix: call write_platform_config_field('photon', 'enabled', True, raw=True) as a final setup step so the gateway picks up the freshly configured channel on its next start. New public API in auth.py: - clear_photon_token() -- discard stored token from auth.json - check_photon_token_valid(token) -- lightweight session-check test References: #72763 --- plugins/platforms/photon/auth.py | 39 ++++++++++++++++++++++++++++++++ plugins/platforms/photon/cli.py | 28 +++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/plugins/platforms/photon/auth.py b/plugins/platforms/photon/auth.py index e1327795a97d..066d5e99cdf7 100644 --- a/plugins/platforms/photon/auth.py +++ b/plugins/platforms/photon/auth.py @@ -180,6 +180,45 @@ def store_photon_token(token: str) -> None: _save_auth(auth) +def clear_photon_token() -> None: + """Remove any stored Photon dashboard token from auth.json. + + Used to discard a stale/expired token before re-authentication. + """ + auth = _load_auth() + pool = auth.get("credential_pool", {}) + photon = pool.get("photon", []) + if isinstance(photon, list) and photon: + pool["photon"] = [] + _save_auth(auth) + # Also clear the legacy shape if present. + providers = auth.get("providers", {}) + if "photon" in providers: + providers["photon"] = {} + _save_auth(auth) + + +def check_photon_token_valid(token: str) -> bool: + """Return True if the token is accepted by the dashboard API. + + Makes a lightweight ``GET /api/auth/get-session`` call. A non-401 + response (including non-auth errors like network blips) is treated as + "probably valid" so transient failures don't force unnecessary re-login. + Only a definitive 401 / 403 is treated as stale. + """ + if not token: + return False + try: + resp = _dashboard_get("/api/auth/get-session", token) + if resp.status_code in (401, 403): + return False + return True + except Exception: + # Transient error — don't force a re-auth; let the caller's + # management-call error propagate if the token really is bad. + return True + + def load_project_credentials() -> Tuple[Optional[str], Optional[str]]: """Return the runtime SDK creds ``(spectrum_project_id, project_secret)``. diff --git a/plugins/platforms/photon/cli.py b/plugins/platforms/photon/cli.py index 89e1c6bc8bc4..af8b33441888 100644 --- a/plugins/platforms/photon/cli.py +++ b/plugins/platforms/photon/cli.py @@ -127,10 +127,23 @@ def _run_device_login(args: argparse.Namespace) -> int: def _cmd_setup(args: argparse.Namespace) -> int: - # 1. Login (skip if we already have a token). + # 1. Login (skip if we already have a valid token). token = photon_auth.load_photon_token() + if token: + # Validate the existing token — the dashboard token has a short TTL + # and can go stale between runs (observed: ~3-4 days). Reusing a + # stale token causes every management call to fail with 401 and + # leaves the operator confused about why setup "succeeds" but nothing + # works. Check upfront so we fail fast and fall back to fresh login. + print("[1/5] Checking existing Photon token...") + if photon_auth.check_photon_token_valid(token): + print(" ✓ token is valid") + else: + print(" ✗ token is stale (dashboard rejected it) — re-authenticating") + photon_auth.clear_photon_token() + token = None if not token: - print("[1/5] No Photon token found — running device login...") + print("[1/5] No valid Photon token found — running device login...") rc = _run_device_login(args) if rc != 0: return rc @@ -270,6 +283,17 @@ def _cmd_setup(args: argparse.Namespace) -> int: if rc != 0: return rc + # 7. Ensure the photon platform is enabled in config.yaml so the + # gateway loads it on next start. Without this the channel stays + # disabled even after a successful provisioning run, silently + # keeping iMessage offline. + try: + from hermes_cli.config import write_platform_config_field + write_platform_config_field("photon", "enabled", True, raw=True) + print(" ✓ photon platform enabled in config.yaml") + except Exception as e: + print(f" (could not enable Photon in config: {e})", file=sys.stderr) + print() print("✓ Photon setup complete.") print(" Start the gateway: hermes gateway start")