Skip to content

fix(auth): preserve user token state on refresh failure - #1820

Open
xu91102 wants to merge 3 commits into
larksuite:mainfrom
xu91102:fix/auth-refresh-preserve-token-state
Open

fix(auth): preserve user token state on refresh failure#1820
xu91102 wants to merge 3 commits into
larksuite:mainfrom
xu91102:fix/auth-refresh-preserve-token-state

Conversation

@xu91102

@xu91102 xu91102 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Preserve local user auth state when a refresh endpoint failure happens before the locally recorded refreshExpiresAt, so a failed refresh no longer collapses immediately to token_missing.

Changes

  • Return typed refresh errors for failed refresh responses instead of deleting the stored user token.
  • Keep local refresh state available for retry and diagnosis when the refresh token is not locally expired.
  • Continue clearing the stored token when local metadata says refreshExpiresAt is already expired.
  • Add focused UAT refresh regression tests using a stubbed token store and token endpoint.

Test Plan

  • GOCACHE=$PWD/.cache/go-build GOPROXY=https://goproxy.cn,direct go test ./internal/auth -run 'TestGetValidAccessToken_PreservesUnexpiredRefreshTokenOnRefreshReused|TestGetValidAccessToken_ClearsLocallyExpiredRefreshToken' -count=1
  • GOCACHE=$PWD/.cache/go-build GOPROXY=https://goproxy.cn,direct go test ./internal/auth -count=1
  • GOCACHE=$PWD/.cache/go-build GOPROXY=https://goproxy.cn,direct go test ./cmd/auth ./internal/credential -count=1
  • GOCACHE=$PWD/.cache/go-build GOPROXY=https://goproxy.cn,direct go test ./internal/errclass -count=1
  • GOCACHE=$PWD/.cache/go-build GOPROXY=https://goproxy.cn,direct go build -o ./lark-cli .
  • git diff --check
  • conflict marker scan

Related Issues

Summary by CodeRabbit

  • Bug Fixes
    • Improved sign-in token refresh behavior to preserve existing local authentication state during retryable failures (avoids unnecessary token clearing).
    • Added clearer, structured refresh errors for cases like “refresh token already used,” authorization required, and “refresh in progress.”
    • Automatically clears locally expired refresh tokens so users can re-authenticate cleanly when needed.
  • Tests
    • Expanded automated coverage for refresh-state preservation and refresh failure/error shaping, including transport-level failure cases.

@xu91102
xu91102 requested a review from liangshuo-1 as a code owner July 9, 2026 05:00
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a7f848a-8b45-4153-9317-9b32f5ee41df

📥 Commits

Reviewing files that changed from the base of the PR and between 855b9e3 and 3257991.

📒 Files selected for processing (1)
  • internal/auth/uat_client.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/auth/uat_client.go

📝 Walkthrough

Walkthrough

Updates UAT refresh handling to use injectable stored-token accessors, preserve local token state across refresh failures, and return structured refresh errors. Adds tests covering token reuse, expiry, transport failures, and error shaping.

Changes

UAT refresh token handling

Layer / File(s) Summary
Stored-token accessors
internal/auth/uat_client.go
Adds injectable package-level stored-token accessors and routes stored-token reads and removals through them in the access-token and expiry paths.
Lock and re-check flow
internal/auth/uat_client.go
Updates refresh lock setup, cross-process locking, and the in-process wait path to return internal errors with subtypes and to re-read stored token state before deciding how to continue.
Refresh failure handling
internal/auth/uat_client.go
Changes refresh expiry, transport failure, retryable failure, and non-retryable failure branches to preserve stored state, wrap transport errors, and build structured authentication errors from API payload fields.
Refresh behavior tests
internal/auth/uat_client_test.go
Adds a stubbed UAT store helper and tests for reused refresh tokens, locally expired refresh tokens, waiting refresh state, transport failures, and refresh-failure error shaping.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% 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
Title check ✅ Passed The title clearly summarizes the main auth-token preservation change and is concise.
Description check ✅ Passed The description follows the template with Summary, Changes, Test Plan, and Related Issues filled in.
Linked Issues check ✅ Passed The PR preserves local auth state on refresh failure and adds typed refresh-error handling, matching #1404's core requirements.
Out of Scope Changes check ✅ Passed The changes stay within auth refresh handling and regression tests, with no unrelated scope visible.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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/L Large or sensitive change across domains or core paths label Jul 9, 2026

@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: 5

🤖 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 `@internal/auth/uat_client_test.go`:
- Around line 134-143: The missing-authorization test in `uat_client_test.go`
only checks `errors.As` and token removal; extend this assertion to verify the
typed metadata on the returned `NeedAuthorizationError` using `errs.ProblemOf`.
Locate the existing error-path assertions in the relevant test and add checks
for the expected category/subtype mapping so the test confirms the error still
resolves to the right `errs.ProblemOf` classification.
- Around line 18-19: The withStubbedUATStore test helper does not isolate the
auth config directory, so refresh-path lock files can leak into shared config
state. Update withStubbedUATStore to set LARKSUITE_CLI_CONFIG_DIR to a temp
directory via t.Setenv before any code that may call core.GetConfigDir(), so the
UAT refresh tests stay self-contained and do not affect developer/CI config.

In `@internal/auth/uat_client.go`:
- Around line 315-346: The refresh failure path in buildRefreshFailureError
should distinguish OAuth-style responses that only contain
error/error_description from real API errors with a numeric code. Update the
function to bypass errclass.BuildAPIError when code is missing or -1 and instead
return the existing SubtypeRefreshServerError AuthenticationError path,
preserving opts.UserOpenID and the fallback hint. When a real code exists, keep
using BuildAPIError but pass the original response payload (not just code/msg)
so fields like log_id are retained for diagnostics.
- Around line 260-262: The refresh retry path in uat_client’s token refresh flow
is returning raw transport errors from callEndpoint, which bypasses the
command-facing typed error contract. Update the retry failure handling in the
refresh logic to wrap the underlying error with an errs.* type, specifically
using errs.NewNetworkError(...).WithCause(err), and keep the existing cause so
automation can classify it correctly. Make the change in the branch that logs
the retry network warning and returns the error, preserving the current
token-state behavior while ensuring the returned error is typed.
- Line 125: The waiting branch in getValidAccessToken is incorrectly treating
the preserved expired token from getStoredUAToken as a successful refresh
result. Update the logic around getStoredUAToken and the post-refresh wait path
so that if the stored token is expired or the refresh failed,
GetValidAccessToken does not return it with a nil error; instead, propagate the
refresh failure or force a real re-fetch before returning a token.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8774b8bc-7ef1-429d-af09-50232dda4d82

📥 Commits

Reviewing files that changed from the base of the PR and between 74d8458 and 7f68646.

📒 Files selected for processing (2)
  • internal/auth/uat_client.go
  • internal/auth/uat_client_test.go

Comment thread internal/auth/uat_client_test.go
Comment thread internal/auth/uat_client_test.go
Comment thread internal/auth/uat_client.go Outdated
Comment thread internal/auth/uat_client.go
Comment thread internal/auth/uat_client.go

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

🧹 Nitpick comments (1)
internal/auth/uat_client.go (1)

325-367: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the preservation hint into a shared constant.

The same recovery hint is duplicated verbatim in preservedRefreshStateError (Line 328) and twice in buildRefreshFailureError (Lines 359 and 367). A single package-level constant avoids drift if the guidance is reworded later.

🤖 Prompt for 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.

In `@internal/auth/uat_client.go` around lines 325 - 367, The refresh-state
recovery hint is duplicated in preservedRefreshStateError and
buildRefreshFailureError, so extract it into a single shared package-level
constant and reuse it in both helpers. Update preservedRefreshStateError,
wrapRefreshTransportError if needed for consistency, and the AuthenticationError
path in buildRefreshFailureError to reference that constant so the guidance
stays identical and easy to maintain.
🤖 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.

Nitpick comments:
In `@internal/auth/uat_client.go`:
- Around line 325-367: The refresh-state recovery hint is duplicated in
preservedRefreshStateError and buildRefreshFailureError, so extract it into a
single shared package-level constant and reuse it in both helpers. Update
preservedRefreshStateError, wrapRefreshTransportError if needed for consistency,
and the AuthenticationError path in buildRefreshFailureError to reference that
constant so the guidance stays identical and easy to maintain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e295eb9-b6d1-458f-b101-4714ddc41ab2

📥 Commits

Reviewing files that changed from the base of the PR and between 7f68646 and 855b9e3.

📒 Files selected for processing (2)
  • internal/auth/uat_client.go
  • internal/auth/uat_client_test.go

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@855b9e3a69acef9ed1f38654afa70d7beae6d71f

🧩 Skill update

npx skills add xu91102/cli#fix/auth-refresh-preserve-token-state -y -g

@kiraWangRuilong

Copy link
Copy Markdown
Collaborator

@xu91102
从整体处理思路来看,这个改动是合理的。因为 HTTP 400 并不一定代表本地认证状态已经永久失效,也可能由临时请求异常、参数问题或服务端响应异常导致。此时直接删除本地 Token,会使用户被迫重新登录,影响使用体验。

不过PR代码还是存在一些需要修改的地方:

  • 代码中旧的注释和你新修改的逻辑对应一下,目前注释说还是会清除token
  • 以下代码
    var (
    getStoredUAToken = GetStoredToken
    setStoredUAToken = SetStoredToken
    removeStoredUAToken = RemoveStoredToken
    ) 没有实际意义,需要去除
  • 代码中日志的打印需要再调整一下

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

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh HTTP 400 can clear user auth despite valid refreshExpiresAt

2 participants