feat(auth): transparent JWT exchange#87
Conversation
| pool_args = { | ||
| "cert_reqs": cert_reqs, | ||
| "ca_certs": configuration.ssl_ca_cert, | ||
| "cert_file": configuration.cert_file, | ||
| "key_file": configuration.key_file, | ||
| "ca_cert_data": configuration.ca_cert_data, | ||
| } | ||
| # Mirror rest.py's hostname/SNI handling so the exchange call does not |
There was a problem hiding this comment.
nit: _pool_from_config mirrors rest.py's TLS/proxy handling but silently drops configuration.socket_options (rest.py:99-100 forwards it as a pool_args["socket_options"] kwarg). The inline comment below justifies omitting retries/maxsize for the "fail fast, single request" exchange, but doesn't address socket_options. A user who configures socket-level options (e.g. TCP keepalive) for all other SDK requests will see the token-exchange pool diverge from that. Either forward it or add it to the "intentionally not mirrored" comment to make the choice explicit. (not blocking)
| def _needs_exchange(self): | ||
| # Opt-out wins outright: any truthy HOTDATA_DISABLE_JWT_EXCHANGE means | ||
| # send the credential as-is, never touching the token endpoint. | ||
| if os.environ.get(_DISABLE_ENV): |
There was a problem hiding this comment.
nit: os.environ.get(_DISABLE_ENV) returns the raw string, so HOTDATA_DISABLE_JWT_EXCHANGE=0 and =false both disable the exchange (non-empty strings are truthy in Python). The module docstring documents this as "any truthy value", but users setting =0/=false to enable exchange will silently be opted out. Consider parsing only obviously-affirmative values (e.g. value.lower() in {"1","true","yes","on"}) — matches typical env-var conventions and removes the footgun. (not blocking)
| if self._token_manager is None: | ||
| return None | ||
| return self._token_manager.bearer_value() |
There was a problem hiding this comment.
super nit: the property reads self._token_manager twice. If another thread does cfg.api_key = None between the is None check and the .bearer_value() call, this raises AttributeError: 'NoneType' object has no attribute 'bearer_value'. The same risk you call out in auth_settings() (where you cache the value into BearerAuth_token) applies here too. Read once:
tm = self._token_manager
return None if tm is None else tm.bearer_value()(not blocking) (not blocking)
|
Addressed all three inline nits in e73d4f6 + 9cf117b:
Added tests for the first two; full suite 41 passed. |
The SDK now transparently exchanges an API token for a short-lived JWT (via
POST {host}/v1/auth/jwt) and sends the JWT on every request, with zero user-code change. Server-side enablers (Gateway route + OAuth client) are tracked separately in the monopoly repo.