Skip to content

feat(otdfctl): Store refresh tokens to extend sessions in otdfctl - #3829

Open
elizabethhealy wants to merge 3 commits into
mainfrom
dspx-4111-otdfctl-refresh-token-support
Open

feat(otdfctl): Store refresh tokens to extend sessions in otdfctl#3829
elizabethhealy wants to merge 3 commits into
mainfrom
dspx-4111-otdfctl-refresh-token-support

Conversation

@elizabethhealy

@elizabethhealy elizabethhealy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Wires the existing (previously dead-code) refresh token helpers into the CLI's active authentication path, so expired access tokens are transparently refreshed instead of forcing users through the browser login flow
  • Replaces the static oauth2.TokenSource with a new profileTokenSource that refreshes via the IdP and persists rotated credentials back to the profile store
  • Adds eager refresh at handler init (ValidateProfileAuthCredentials) and in GetTokenWithProfile (used by print-access-token) so errors surface up-front
  • Detects invalid_grant (expired/revoked refresh token) to cleanly prompt re-login instead of surfacing a raw OAuth error
  • Emits slog.Info to stderr when a refresh occurs, and slog.Warn on failures

Background

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

  • 401-retry Connect interceptor (belt-and-braces for clock skew / server-side revocation mid-call)
  • Cross-process profile-file race protection (advisory locking in go-osprofiles)
  • Configurable expiry buffer via OTDFCTL_TOKEN_EXPIRY_BUFFER env var

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

Summary by CodeRabbit

  • New Features

    • Access tokens now refresh automatically when they expire, reducing interruptions during authenticated sessions.
    • Refreshed credentials are saved for continued use, including rotated refresh tokens.
  • Bug Fixes

    • Invalid or expired refresh tokens now clear stored credentials and provide a clear re-login prompt.
    • Transient refresh failures preserve credentials and display more actionable guidance.
    • Improved handling of concurrent token refreshes to prevent duplicate requests.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@elizabethhealy, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abd692c7-ef0d-4321-9aa8-c65f6cbf9f99

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc8697 and 6906def.

📒 Files selected for processing (5)
  • otdfctl/pkg/auth/auth.go
  • otdfctl/pkg/auth/refresh.go
  • otdfctl/pkg/auth/refresh_test.go
  • otdfctl/pkg/auth/token_source.go
  • otdfctl/pkg/auth/token_source_test.go
📝 Walkthrough

Walkthrough

The 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.

Changes

Authentication refresh flow

Layer / File(s) Summary
Refresh error classification and clearing
otdfctl/pkg/auth/errors.go, otdfctl/pkg/auth/refresh.go, otdfctl/pkg/auth/refresh_test.go
Invalid OAuth2 grants return ErrRefreshTokenInvalid and clear credentials. Generic refresh failures return ErrRefreshFailed and preserve the refresh token.
Profile-backed token source
otdfctl/pkg/auth/token_source.go, otdfctl/pkg/auth/token_source_test.go
profileTokenSource refreshes expired tokens, persists rotated credentials, retains omitted refresh tokens, serializes concurrent refreshes, supports invalidation, and applies a fallback expiry.
SDK and CLI authentication integration
otdfctl/pkg/auth/auth.go, otdfctl/cmd/common/common.go, otdfctl/cmd/auth/login.go
SDK authentication uses the profile token source. CLI warnings distinguish invalid refresh tokens from failed access-token refreshes. The login output includes spacing before completion.

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
Loading

Possibly related PRs

Suggested labels: comp:middleware:auth

Suggested reviewers: alkalescent

Poem

I’m a rabbit with tokens tucked tight,
Refreshing them softly by moonlight.
Invalid grants clear the old trail,
While transient storms leave secrets hale.
SDK paths now hop and renew—
CLI warnings tell what’s true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: storing refresh tokens to extend authentication sessions in otdfctl.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dspx-4111-otdfctl-refresh-token-support

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size/m label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 210.404796ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 118.602137ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 492.978875ms
Throughput 202.85 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 49.884368148s
Average Latency 496.992008ms
Throughput 100.23 requests/second

@elizabethhealy
elizabethhealy marked this pull request as ready for review August 4, 2026 14:20
@elizabethhealy
elizabethhealy requested a review from a team as a code owner August 4, 2026 14:20
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 153.648271ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 84.291921ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 335.912711ms
Throughput 297.70 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 38.559655919s
Average Latency 384.712561ms
Throughput 129.67 requests/second

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2619a40 and 3bc8697.

📒 Files selected for processing (8)
  • otdfctl/cmd/auth/login.go
  • otdfctl/cmd/common/common.go
  • otdfctl/pkg/auth/auth.go
  • otdfctl/pkg/auth/errors.go
  • otdfctl/pkg/auth/refresh.go
  • otdfctl/pkg/auth/refresh_test.go
  • otdfctl/pkg/auth/token_source.go
  • otdfctl/pkg/auth/token_source_test.go

Comment thread otdfctl/pkg/auth/auth.go
Comment thread otdfctl/pkg/auth/auth.go Outdated
Comment thread otdfctl/pkg/auth/refresh.go
Comment thread otdfctl/pkg/auth/token_source.go Outdated
Comment thread otdfctl/pkg/auth/token_source.go
Comment thread otdfctl/pkg/auth/token_source.go
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 189.112612ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 114.94925ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 427.807094ms
Throughput 233.75 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 50.722820984s
Average Latency 505.251322ms
Throughput 98.57 requests/second

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants