### Summary The SDK has server-side OAuth but no reusable client-side token handling. ### Reference `ts:auth/token-store.ts`, `ts:auth/client.ts`. ### Prior work: start here Closed PR #4 (branch `release/v0.3.2-full-parity`) has a Python implementation that never reached `main`: - `nitrostack/auth/token_store.py` (213 lines): `MemoryTokenStore`, `FileTokenStore(store_path, encryption_key=None)`, `StoredToken`. - `nitrostack/auth/client.py` (260 lines): `OAuth2Client` with `discoverAuthorizationServerMetadata`, `discoverProtectedResourceMetadata`, `startAuthorizationFlow`, `exchangeCodeForToken`, `getClientCredentialsToken`, `refreshToken`, `revokeToken` and `registerClient`. Both import and run on current `main`. ```bash git show origin/release/v0.3.2-full-parity:nitrostack/auth/token_store.py git show origin/release/v0.3.2-full-parity:nitrostack/auth/client.py ``` ### Scope - Port both files, with snake_case public names (drop the camelCase methods). - `FileTokenStore`: file mode `0600` and atomic writes. - Helpers: `is_token_expired(token, skew)`, `calculate_expiration(expires_in)`, `token_response_to_stored(...)`. - PKCE through the existing `nitrostack/auth/pkce.py`. - Refresh is single-flight per key. - Discovery URLs that are not operator-configured go through the SSRF checks in `nitrostack/auth/cimd.py` (`assert_safe_fetch_target`). ### Acceptance criteria - [ ] Round-trip tests for both stores; file permissions asserted. - [ ] Concurrent refreshes for one key send a single upstream request (mocked transport). - [ ] Tokens never appear in `repr()` or log output. - [ ] No new HTTP client dependency.
Activity