Skip to content

Commit 04c32cc

Browse files
cyberjunkyclaude
andcommitted
Simplify token storage — remove unused IT and JWT_WEB tokens
garmin_tokens.json now only stores the 3 DI OAuth fields actually used: di_token, di_refresh_token, di_client_id. IT tokens (services.garmin.com) were never used in any API call — that domain handles Garmin Pay/device provisioning which is out of scope. JWT_WEB, csrf_token and cookies are kept in-memory as a login fallback but no longer persisted to disk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 145294e commit 04c32cc

1 file changed

Lines changed: 0 additions & 78 deletions

File tree

garminconnect/client.py

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def _random_browser_headers() -> dict[str, str]:
6666
"Android/33; Dalvik/2.1.0"
6767
)
6868
DI_TOKEN_URL = "https://diauth.garmin.com/di-oauth2-service/oauth/token" # noqa: S105
69-
IT_TOKEN_URL = "https://services.garmin.com/api/oauth/token" # noqa: S105
7069
DI_GRANT_TYPE = (
7170
"https://connectapi.garmin.com/di-oauth2-service/oauth/grant/service_ticket"
7271
)
@@ -75,11 +74,6 @@ def _random_browser_headers() -> dict[str, str]:
7574
"GARMIN_CONNECT_MOBILE_ANDROID_DI_2024Q4",
7675
"GARMIN_CONNECT_MOBILE_ANDROID_DI",
7776
)
78-
IT_CLIENT_IDS = (
79-
"GARMIN_CONNECT_MOBILE_ANDROID_2025Q2",
80-
"GARMIN_CONNECT_MOBILE_ANDROID_2024Q4",
81-
"GARMIN_CONNECT_MOBILE_ANDROID",
82-
)
8377

8478

8579
def _build_basic_auth(client_id: str) -> str:
@@ -115,9 +109,6 @@ def __init__(self, domain: str = "garmin.com", **kwargs: Any) -> None:
115109
self.di_token: str | None = None
116110
self.di_refresh_token: str | None = None
117111
self.di_client_id: str | None = None
118-
self.it_token: str | None = None
119-
self.it_refresh_token: str | None = None
120-
self.it_client_id: str | None = None
121112

122113
# JWT_WEB cookie auth (fallback when DI token is unavailable)
123114
self.jwt_web: str | None = None
@@ -807,36 +798,6 @@ def _exchange_service_ticket(
807798
self.di_refresh_token = di_refresh
808799
self.di_client_id = di_client_id
809800

810-
# Exchange DI for IT token
811-
it_candidates = self._it_client_id_candidates(di_client_id or DI_CLIENT_IDS[0])
812-
for client_id in it_candidates:
813-
r = self._http_post(
814-
f"{IT_TOKEN_URL}?grant_type=connect2_exchange",
815-
headers=_native_headers(
816-
{
817-
"Accept": "application/json,text/plain,*/*",
818-
"Content-Type": "application/x-www-form-urlencoded",
819-
}
820-
),
821-
data={
822-
"client_id": client_id,
823-
"connect_access_token": di_token,
824-
},
825-
timeout=30,
826-
)
827-
if not r.ok:
828-
_LOGGER.debug("IT exchange failed for %s: %s", client_id, r.status_code)
829-
continue
830-
try:
831-
data = r.json()
832-
self.it_token = data["access_token"]
833-
self.it_refresh_token = data.get("refresh_token")
834-
self.it_client_id = client_id
835-
break
836-
except Exception as e:
837-
_LOGGER.debug("IT token parse failed for %s: %s", client_id, e)
838-
continue
839-
840801
def _refresh_di_token(self) -> None:
841802
"""Refresh the DI Bearer token using the stored refresh token."""
842803
if not self.di_refresh_token or not self.di_client_id:
@@ -881,20 +842,6 @@ def _extract_client_id_from_jwt(self, token: str) -> str | None:
881842
except Exception:
882843
return None
883844

884-
def _it_client_id_candidates(self, di_client_id: str) -> tuple[str, ...]:
885-
derived = (
886-
di_client_id.replace("_DI_", "_")
887-
if "_DI_" in di_client_id
888-
else (
889-
di_client_id[:-3] if di_client_id.endswith("_DI") else IT_CLIENT_IDS[0]
890-
)
891-
)
892-
seen: list[str] = []
893-
for v in [self.it_client_id, derived, *IT_CLIENT_IDS]:
894-
if v and v not in seen:
895-
seen.append(v)
896-
return tuple(seen)
897-
898845
def _token_expires_soon(self) -> bool:
899846
token = self.di_token or self.jwt_web
900847
if not token:
@@ -972,13 +919,6 @@ def dumps(self) -> str:
972919
"di_token": self.di_token,
973920
"di_refresh_token": self.di_refresh_token,
974921
"di_client_id": self.di_client_id,
975-
"it_token": self.it_token,
976-
"it_refresh_token": self.it_refresh_token,
977-
"it_client_id": self.it_client_id,
978-
# JWT_WEB fields
979-
"jwt_web": self.jwt_web,
980-
"csrf_token": self.csrf_token,
981-
"cookies": {c.name: c.value for c in self.cs.cookies.jar},
982922
}
983923
return json.dumps(data)
984924

@@ -1008,24 +948,6 @@ def loads(self, tokenstore: str) -> None:
1008948
self.di_token = data.get("di_token")
1009949
self.di_refresh_token = data.get("di_refresh_token")
1010950
self.di_client_id = data.get("di_client_id")
1011-
self.it_token = data.get("it_token")
1012-
self.it_refresh_token = data.get("it_refresh_token")
1013-
self.it_client_id = data.get("it_client_id")
1014-
self.jwt_web = data.get("jwt_web")
1015-
self.csrf_token = data.get("csrf_token")
1016-
1017-
# Restore cookies if no DI token
1018-
if not self.di_token:
1019-
sso_cookies = {"CASTGC", "CASRMC", "CASMFA", "SESSION", "__VCAP_ID__"}
1020-
connect_cookies = {"JWT_WEB", "session", "__cflb"}
1021-
for k, v in data.get("cookies", {}).items():
1022-
if k in sso_cookies:
1023-
self.cs.cookies.set(k, v, domain=f"sso.{self.domain}")
1024-
elif k in connect_cookies:
1025-
self.cs.cookies.set(k, v, domain=f".connect.{self.domain}")
1026-
else:
1027-
self.cs.cookies.set(k, v, domain=f".{self.domain}")
1028-
1029951
if not self.is_authenticated:
1030952
raise GarminConnectAuthenticationError("Missing tokens from dict load")
1031953
except Exception as e:

0 commit comments

Comments
 (0)