feat(auth): auto-reauth on expired/revoked refresh tokens (invalid_grant) - #973
feat(auth): auto-reauth on expired/revoked refresh tokens (invalid_grant)#973inamiy wants to merge 1 commit into
Conversation
…ant) When the stored OAuth refresh token is expired or revoked (invalid_grant), gog currently fails with a hard error and requires the user to manually re-run 'gog auth add'. This is in contrast to other CLI tools (e.g. Rust's yup-oauth2 InstalledFlowAuthenticator) which automatically fall back to a browser-based re-authorization flow when the refresh token is invalid. This PR adds auto-reauth support: - In interactive sessions (TTY stdin, --no-input not set), gog detects invalid_grant during token refresh, launches a browser-based OAuth flow (with --force-consent to ensure a new refresh token), persists it to the keyring, resets the in-memory token source, and retries the original API request. - In non-interactive sessions (--no-input or non-TTY stdin), gog surfaces a clear error message with the manual 'gog auth add' command instead. - Excluded for ADC, service accounts, and direct access tokens. - The reauth preserves the stored token's full scope/service set, preventing silent grant narrowing. - The authorized email is verified to match the expected account before persisting. - The in-memory token source (resettableOAuthTokenSource) is rebuilt with the new refresh token so the retried request doesn't reuse the revoked token. Design inspired by yup-oauth2's InstalledFlowAuthenticator.find_token_info() fallback pattern: https://github.com/dermesser/yup-oauth2/blob/master/src/authenticator.rs Co-authored-by: Yasuhiro Inami <inamiy@gmail.com>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs changes before merge. Reviewed August 9, 2026, 7:58 PM ET / 23:58 UTC. ClawSweeper reviewWhat this changesAdds one-time interactive browser reauthorization and request replay when a stored Google OAuth refresh token returns Merge readinessKeep open: current main still requires manual reauthorization, and this PR has credible live proof, but its reset path can overwrite the newly stored refresh token with the revoked cached token on the first post-reauth refresh. The automatic browser-launch behavior also needs maintainer product approval. Priority: P1 Review scores
Verification
How this fits togethergog builds authenticated Google API clients from stored OAuth credentials. Requests pass through token refresh and retry handling before Google Workspace APIs; this PR would replace revoked credentials and replay the failed request. flowchart LR
A[CLI command] --> B[Stored OAuth token]
B --> C[Token refresh]
C --> D{Token revoked?}
D -- No --> E[Google Workspace API]
D -- Yes --> F[Interactive reauthorization]
F --> G[Persist token and retry]
G --> E
Decision needed
Why: This is a new user-visible recovery policy rather than an established bug contract; the repository vision requires discussion for behavior changes that may affect existing workflows. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Repair the persistence cache, retain the explicit non-interactive fallback, and land only the interactive UX that the auth owner approves, preferably with an explicit confirmation before opening a browser. Do we have a high-confidence way to reproduce the issue? Yes—source inspection gives a high-confidence path: reauthorize, then force a subsequent token refresh whose response omits Is this the best way to solve the issue? No—the recovery mechanism is promising, but it must synchronize the persisting cache and receive approval for its automatic browser-launch policy before it is the safest solution. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against bd733de86dbe. LabelsLabel justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (8 earlier review cycles)
|
Live behavior proofTested against real Google Calendar API with a revoked refresh token (revoked via Test 1:
|
|
LAND verdict: I prepared the maintainer landing branch in #974 and preserved your authored feature commit. Thank you for the unusually useful live Google proof—it made the recovery behavior much easier to validate. Before landing, the maintainer branch fixes the stale persistence cache, adds a post-reauth refresh regression, requires confirmation before browser launch, and coalesces recovery across separate service clients so one revoked account cannot trigger multiple concurrent browser flows. It also narrows detection to typed OAuth Please leave this PR open while #974 receives final maintainer review. No action is requested from you. |
|
Landed — thank you, and your commit is preserved in the merge. This shipped as #974 ( Your diagnosis was right and the live Google Calendar proof you attached was
The You are credited in the 0.35.1 changelog. Good catch on a real gap. |
Summary
When the stored OAuth refresh token is expired or revoked (
invalid_grant),gogcurrently fails with a hard error and requires the user to manually re-rungog auth add. This is in contrast to other CLI tools (e.g. Rust'syup-oauth2InstalledFlowAuthenticator) which automatically fall back to a browser-based re-authorization flow when the refresh token is invalid, making the failure transparent to the user.This PR adds auto-reauth support to
gog.How it works
Key design decisions
--force-consentalways used in auto-reauth to ensure Google returns a new refresh tokenretriedReauthflag) — no infinite loops--no-inputand non-TTY stdin — CI/piped environments get a clear error withgog auth addhint, not a browser*persistingTokenSource)resettableOAuthTokenSource.ResetRefreshTokenrebuilds the source with the new refresh token, so the retried request doesn't reuse the revoked tokenPrior art
yup-o-auth2(Rust):InstalledFlowAuthenticator::find_token_info()— on refresh failure, falls back toauth_flow.token()which opens a browser. Source: authenticator.rsinvalid_grantis the next gap in this progression.Files changed
internal/googleauth/reauth.goReauth()function — launches browser OAuth flow, verifies identity, persists new refresh tokeninternal/googleauth/reauth_test.goReauth(), email mismatch, scope preservation,servicesFromScopes()internal/googleapi/transport.goReauthfield onRetryTransport;invalid_grantdetection + auto-reauth retry inRoundTripinternal/googleapi/client_auth.goResetRefreshTokenonresettableOAuthTokenSource/persistingTokenSource;isInvalidGrantError()detector;refreshTokenResetterinterfaceinternal/googleapi/client.goreauthFunctionFromContext()closure — loads stored token, callsReauth, resets token sourceinternal/googleapi/auth_dependencies.goReauthFunctype,Reauthfield,WithNoInput()/NoInputFromContext()internal/cmd/root.goreauthFnintoAuthDependencies; propagates--no-input+ TTY detectioninternal/googleapi/auto_reauth_test.goinvalid_grantdetection, retry state machine,NoInputFromContextinternal/googleapi/reauth_glue_test.godocs/auto-reauth-issue-draft.mdCHANGELOG.mdTesting
go build ./...✅go vet ./...✅go test ./internal/googleapi/... ./internal/googleauth/... -count=1✅go test ./internal/cmd/... -count=1✅go test -race ./internal/googleapi/... -run Reauth✅Related issue
Issue draft included in
docs/auto-reauth-issue-draft.md— can be posted separately.