feat(cli): Store refresh tokens to extend sessions in otdfctl - #3829
Conversation
📝 WalkthroughWalkthroughThe PR adds a concurrent-safe, profile-backed OAuth2 token source. It refreshes expired access tokens, persists credentials, clears invalid sessions, distinguishes transient failures, and integrates these outcomes into SDK authentication and CLI warnings. ChangesAuthentication refresh flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SDKAuth
participant profileTokenSource
participant ProfileStorage
participant OAuth2TokenEndpoint
SDKAuth->>profileTokenSource: request access token
profileTokenSource->>ProfileStorage: load profile credentials
profileTokenSource->>OAuth2TokenEndpoint: refresh expired access token
OAuth2TokenEndpoint-->>profileTokenSource: return refreshed credentials
profileTokenSource->>ProfileStorage: persist credentials
profileTokenSource-->>SDKAuth: return access token
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@otdfctl/pkg/auth/auth.go`:
- Around line 198-203: Update RefreshAccessToken and its eager-refresh callers
to preserve the profile’s existing refresh token when the IdP response omits a
replacement, rather than storing an empty value; retain a newly returned refresh
token when present. Add coverage in refresh_test.go for both eager-refresh paths
and the non-rotated-token response.
- Around line 217-223: Update the token-return flow around buildToken and
HasRefreshToken so an invalid access token with no refresh token is rejected by
returning ErrAccessTokenExpired instead of returning the expired token. Preserve
the existing RefreshAccessToken path and its error propagation when a refresh
token is available.
In `@otdfctl/pkg/auth/refresh.go`:
- Around line 85-90: In otdfctl/pkg/auth/refresh.go at lines 85-90, capture the
error returned by SetAuthCredentials in the isInvalidGrant path and propagate it
along with ErrRefreshTokenInvalid instead of discarding it with underscore. In
otdfctl/pkg/auth/token_source.go at lines 53-56, apply the same error-handling
approach: capture and propagate the SetAuthCredentials error before resetting
p.inner, combining it with any existing error that describes the invalid-grant
condition. Ensure both locations preserve the original invalid-token error
context while surfacing credential-clear failures.
In `@otdfctl/pkg/auth/token_source.go`:
- Around line 69-76: Update profileTokenSource.Invalidate so it both clears the
cached inner source and ensures the next Token() call cannot reuse the currently
stored unexpired access token. Record a forced-refresh state or mark the
in-memory token expired, and have the existing token-source rebuild honor that
state while preserving normal caching behavior afterward.
- Around line 62-64: Update the token refresh logic around persist so it returns
whether credential persistence succeeded, and assign cachedAccess only when that
result is successful. Preserve the token-change check so a failed write for the
same rotated token remains eligible for retry on later calls, including the
analogous flow near the second persist call.
- Around line 103-109: Update the HTTP client created in rebuild’s tlsNoVerify
branch via utils.NewHTTPClient to configure a finite http.Client.Timeout before
storing it in oauth2.HTTPClient. Preserve the existing context and TokenSource
flow while ensuring p.inner.Token cannot wait indefinitely.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1386105b-1d70-4245-9868-40eb847c63cc
📒 Files selected for processing (8)
otdfctl/cmd/auth/login.gootdfctl/cmd/common/common.gootdfctl/pkg/auth/auth.gootdfctl/pkg/auth/errors.gootdfctl/pkg/auth/refresh.gootdfctl/pkg/auth/refresh_test.gootdfctl/pkg/auth/token_source.gootdfctl/pkg/auth/token_source_test.go
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
|
Summary
oauth2.TokenSourcewith a newprofileTokenSourcethat refreshes via the IdP and persists rotated credentials back to the profile storeValidateProfileAuthCredentials) and inGetTokenWithProfile(used byprint-access-token) so errors surface up-frontinvalid_grant(expired/revoked refresh token) to cleanly prompt re-login instead of surfacing a raw OAuth errorslog.Infoto stderr when a refresh occurs, andslog.Warnon failuresBackground
The PKCE login flow already captured and stored the refresh token, but it was never used — the CLI exited with "Access token expired. Please login" on every expiry. App sec has confirmed no FedRAMP/compliance concerns with storing refresh tokens in the profile, with a recommendation to prefer OS keychain storage.
Follow-ups
go-osprofiles)OTDFCTL_TOKEN_EXPIRY_BUFFERenv varChecklist
Testing Instructions
Summary by CodeRabbit
New Features
Bug Fixes