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
This commit is contained in:
webtecnica 2026-07-27 15:05:22 -03:00 committed by Teknium
parent b378bc6fab
commit cd158466d1
2 changed files with 65 additions and 2 deletions

View file

@ -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)``.

View file

@ -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")