Fix save refresh token

This commit is contained in:
Nirvana
2026-03-19 15:12:30 +01:00
parent 27aa14baf7
commit 3436d3175c
2 changed files with 102 additions and 61 deletions
@@ -992,69 +992,15 @@ class Magenta2Provider(StreamingProvider):
"""
Return a valid tvhubs-scoped access_token for use as Bearer.
The tvhubs token has audience "https://tvhubs.telekom.de" and is what
the real device sends in Authorization: Bearer for all tvhubs/wcps calls.
If the stored token is expired, refreshes it automatically using the
shared refresh_token via sam3_client.refresh_access_token("tvhubs").
Falls back to None if unavailable (caller falls back to persona_jwt).
Delegates entirely to TokenFlowManager.get_tvhubs_token() which owns
the full lifecycle: cache check → refresh → full re-auth chain.
No token logic lives here.
"""
import time as _time
try:
tfm = self.authenticator.token_flow_manager
token_data = tfm.session_manager.load_scoped_token(
self.provider_name, "tvhubs", self.country
)
if not token_data or not token_data.get("access_token"):
return None
# Check if expired
issued_at = token_data.get("issued_at", 0)
expires_in = token_data.get("expires_in", 7200)
if _time.time() < issued_at + expires_in - 60:
# Still valid (with 60s safety margin)
return token_data["access_token"]
# Expired — refresh using the shared refresh_token.
# The refresh_token lives in top-level session data (shared across
# all subordinate tokens: tvhubs, taa, yo_digital).
logger.debug("tvhubs token expired, refreshing via sam3_client")
sam3 = tfm.sam3_client
# Load shared refresh_token from session storage if not on instance
shared_rt = (
sam3.refresh_token
if sam3 and sam3.refresh_token
else (
(tfm.session_manager.load_session(self.provider_name, self.country) or {})
.get("refresh_token")
)
)
if not shared_rt:
logger.warning("Cannot refresh tvhubs token: no shared refresh_token found")
return None
# Inject into sam3_client so refresh_access_token can use it
sam3.refresh_token = shared_rt
new_token = sam3.refresh_access_token("tvhubs")
if new_token:
# Persist the refreshed token
tfm._save_tvhubs_token({
"access_token": new_token,
"token_type": "Bearer",
"expires_in": 7200,
})
# ✅ ADD THIS — sam3 already has the new RT on its instance after get_token()
if sam3.refresh_token:
tfm._save_refresh_token(sam3.refresh_token)
logger.debug("tvhubs refresh_token updated in session after refresh")
logger.debug("tvhubs token refreshed successfully")
return new_token
logger.warning("tvhubs token refresh failed")
return self.authenticator.token_flow_manager.get_tvhubs_token()
except Exception as exc:
logger.debug(f"Could not load/refresh tvhubs token: {exc}")
return None
logger.debug(f"Could not obtain tvhubs token: {exc}")
return None
def _vod_auth_headers(self) -> Dict[str, str]:
"""
@@ -820,6 +820,101 @@ class TokenFlowManager:
)
logger.debug("tvhubs token saved")
# ========================================================================
# Public API - tvhubs token (centralized lifecycle)
# ========================================================================
def get_tvhubs_token(self) -> Optional[str]:
"""
Return a valid tvhubs-scoped access_token, managing the full lifecycle:
1. Cached token still valid? → return it
2. Expired + shared refresh_token? → refresh tvhubs, save both
access_token and refresh_token
3. Refresh token dead/missing? → clear stale state, fall through
to the full re-auth chain
(line_auth → remote_login)
4. Full chain succeeded? → tvhubs token now in session,
return it
This is the single place that owns tvhubs token lifecycle at runtime.
provider.py must never inline refresh/save logic — call this instead.
"""
# Step 1: valid cached token
try:
token_data = self.session_manager.load_scoped_token(
self.provider_name, "tvhubs", self.country
)
if token_data and token_data.get("access_token"):
if not self._is_token_expired(token_data):
logger.debug("tvhubs token: using valid cached token")
return token_data["access_token"]
except Exception as exc:
logger.debug(f"tvhubs token: error reading cache: {exc}")
# Step 2: try refreshing via shared refresh_token
try:
session_data = self.session_manager.load_session(self.provider_name, self.country) or {}
shared_rt = self.sam3_client.refresh_token or session_data.get("refresh_token")
if shared_rt:
logger.debug("tvhubs token: expired, attempting refresh")
self.sam3_client.refresh_token = shared_rt
new_access_token = self.sam3_client.refresh_access_token("tvhubs")
if new_access_token:
# Persist the new access_token
self._save_tvhubs_token({
"access_token": new_access_token,
"token_type": "Bearer",
"expires_in": 7200,
})
# Persist the new refresh_token — the old one is now invalid
if self.sam3_client.refresh_token:
self._save_refresh_token(self.sam3_client.refresh_token)
logger.debug("tvhubs token: refresh_token updated in session")
logger.debug("tvhubs token: refreshed successfully")
return new_access_token
# Refresh call failed → the refresh_token is dead; clear it so
# downstream steps don't waste a round-trip attempting it again.
logger.warning(
"tvhubs token: refresh failed — shared refresh_token likely invalidated; "
"clearing and falling back to full re-auth chain"
)
if "refresh_token" in session_data:
del session_data["refresh_token"]
self.session_manager.save_session(self.provider_name, session_data, self.country)
self.sam3_client.refresh_token = None
else:
logger.debug("tvhubs token: no shared refresh_token available, skipping refresh")
except Exception as exc:
logger.warning(f"tvhubs token: error during refresh attempt: {exc}")
# Step 3/4: fall back to the full re-auth chain (steps 5/6 in
# get_yo_digital_token: line_auth → remote_login). We call
# get_yo_digital_token with force_refresh=True so it skips the
# yo_digital / taa checks (already known stale) and goes straight
# to the login flows which re-acquire tvhubs + refresh_token as a
# side effect.
logger.info("tvhubs token: entering full re-auth chain")
result = self.get_yo_digital_token(force_refresh=True)
if result.success:
# The chain saved a fresh tvhubs token as a side effect — return it.
try:
token_data = self.session_manager.load_scoped_token(
self.provider_name, "tvhubs", self.country
)
if token_data and token_data.get("access_token"):
logger.debug("tvhubs token: obtained via full re-auth chain")
return token_data["access_token"]
except Exception as exc:
logger.debug(f"tvhubs token: error reading token after re-auth: {exc}")
logger.warning("tvhubs token: all acquisition attempts failed")
return None
def _save_refresh_token(self, refresh_token: str) -> None:
"""Save shared refresh_token at provider level"""
session_data = self.session_manager.load_session(self.provider_name, self.country) or {}
@@ -899,4 +994,4 @@ class TokenFlowManager:
if not session_data or "refresh_token" not in session_data:
return {"exists": False}
return {"exists": True, "has_refresh_token": True}
return {"exists": True, "has_refresh_token": True}