Skip to content

feat(auth): renew browser logins with the stored refresh token - #2

Open
trukhinyuri wants to merge 1 commit into
spancerxing:mainfrom
trukhinyuri:feat/oauth-refresh
Open

trukhinyuri wants to merge 1 commit into
spancerxing:mainfrom
trukhinyuri:feat/oauth-refresh

Conversation

@trukhinyuri

Copy link
Copy Markdown

Summary

Browser logins are never renewed. CPA's refresh loop only calls auth.refresh for plugin credentials that name a refresh interval, and the plugin never sets one, so an account has to sign in again when its session token expires. Cursor's login poll issues a 60-day session token, so a login made today stops working in about two months with 401 Cursor token expired; sign in to Cursor again.

Change

  • auth.refresh exchanges the stored refresh token at api2.cursor.sh/oauth/token (grant_type=refresh_token, the client id Cursor's IDE and CLI use) once the access token is within a day of expiry; fresh tokens are still checked locally without a request. shouldLogout or a rejected grant is reported as 401.
  • The login poll returns one session token as both access and refresh token, and renewal answers with a new session token and no refresh token. When the stored refresh token is a JWT that expires before the new token, the new token becomes the refresh token as well, so renewal does not depend on the original login's expiry. A rotated refresh token is stored as returned; opaque or longer-lived ones are kept.
  • Renewable logins carry the host attribute refresh_interval_seconds=3600, which makes CPA schedule auth.refresh. API keys and cursor-agent imports are unchanged.
  • /oauth/ requests are sent without Connect-Protocol-Version. README and README_CN describe renewal.

Tests

  • refresh_test.go: renewal inside and outside the one-day window, shouldLogout, rejected grant, chained refresh token (four cases), refresh-token rotation, API-key and import credentials untouched, interval attribute set only for renewable logins.
  • go test -race ./..., gofmt and go vet pass.
  • Live check against Cursor on CPA 8.0.50 with a separate login (credential expiry edited to force renewal): the released v0.1.1 never renews (renewal_state=awaiting_first_renewal, the forced refresh leaves the file unchanged, then chat fails with 401 after expiry). With this change CPA's scheduler renews the credential on its own, a following composer-2.5 chat succeeds, and a second and third renewal (using the renewed token as the refresh credential) are accepted.

Browser logins kept Cursor's refresh token but never used it, so every
account had to sign in again when its access token expired (about two
months after login).

auth.refresh now exchanges the refresh token at api2.cursor.sh/oauth/token
(grant_type refresh_token, the client id Cursor's IDE and CLI use) once
the access token is within a day of expiry, and reports shouldLogout or
a rejected grant as 401. Fresh tokens are still checked locally without
a request.

Cursor's login poll returns one session token as both access and refresh
token, and renewal answers with a new session token and no refresh
token. When the stored refresh token is a JWT that expires before the
new token, the new token becomes the refresh token as well, so renewal
does not depend on the original login's expiry. A rotated refresh token
is stored as returned; opaque or longer-lived ones are kept.

CPA's refresh loop only calls auth.refresh for plugin credentials that
name an interval, so renewable logins now carry the host attribute
refresh_interval_seconds=3600. API keys and cursor-agent imports are
unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant