Skip to content

fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops#407

Merged
jpoehnelt merged 5 commits intogoogleworkspace:mainfrom
etanase:fix/392-refresh-long-running-tokens
Mar 12, 2026
Merged

fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops#407
jpoehnelt merged 5 commits intogoogleworkspace:mainfrom
etanase:fix/392-refresh-long-running-tokens

Conversation

@etanase
Copy link
Contributor

@etanase etanase commented Mar 11, 2026

Summary

  • Introduce AccessTokenProvider trait and ScopedTokenProvider in auth.rs so long-running helpers can request fresh tokens before each API call instead of holding a single token string that expires after ~1 hour
  • Refactor gmail +watch (watch_pull_loop) and events +subscribe (pull_loop) to accept a &dyn AccessTokenProvider and refresh tokens per-request
  • Refresh tokens before cleanup phase (original tokens may have expired after hours of polling)
  • Extract PUBSUB_API_BASE / GMAIL_API_BASE URL constants to enable test injection

Test plan

  • cargo test — 553 tests pass (3 new)
  • cargo clippy -- -D warnings — clean
  • New: test_scoped_token_provider_uses_get_token — verifies ScopedTokenProvider delegates to get_token
  • New: test_pull_loop_refreshes_pubsub_token_between_requests — fake TCP server + FakeTokenProvider verify each Pub/Sub request uses a distinct fresh token
  • New: test_watch_pull_loop_refreshes_tokens_for_each_request — same pattern for Gmail watch, verifying all 4 HTTP calls (pull, history, message fetch, ack) each get a fresh token

Closes #392

🤖 Generated with Claude Code

@etanase etanase requested a review from jpoehnelt as a code owner March 11, 2026 15:08
@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: f40d037

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@google-cla
Copy link

google-cla bot commented Mar 11, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@googleworkspace-bot googleworkspace-bot added area: auth area: core Core CLI parsing, commands, error handling, utilities labels Mar 11, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the reliability of long-running operations, specifically Gmail watch and Workspace Events subscribe, by implementing a robust mechanism for refreshing OAuth2 access tokens. It introduces a new AccessTokenProvider abstraction, allowing these helpers to acquire fresh tokens on demand for each API request, thereby preventing authentication failures due to token expiration. The changes also improve the testability of API interactions by externalizing base URLs.

Highlights

  • OAuth2 Token Refreshing: Introduced an AccessTokenProvider trait and a ScopedTokenProvider struct to enable long-running helpers to request fresh OAuth2 tokens before each API call, preventing issues with expired tokens.
  • Refactored Long-Running Loops: The gmail +watch (watch_pull_loop) and events +subscribe (pull_loop) functions were refactored to accept and utilize the new AccessTokenProvider, ensuring tokens are refreshed dynamically.
  • Cleanup Phase Token Refresh: Implemented token refreshing before the cleanup phase in long-running helpers, addressing potential failures if original tokens expired during extended polling.
  • API Base URL Extraction: Extracted PUBSUB_API_BASE and GMAIL_API_BASE URL constants to improve testability and maintainability of API endpoints.
Changelog
  • .changeset/refresh-long-running-helper-tokens.md
    • Added a new changeset entry documenting the token refresh feature for long-running helpers.
  • src/auth.rs
    • Defined the AccessTokenProvider trait for fetching access tokens.
    • Implemented ScopedTokenProvider to provide tokens for a fixed set of scopes.
    • Added a new test test_scoped_token_provider_uses_get_token to verify ScopedTokenProvider functionality.
  • src/helpers/events/subscribe.rs
    • Added PUBSUB_API_BASE constant for the Pub/Sub API endpoint.
    • Updated handle_subscribe to initialize and use ScopedTokenProvider for Pub/Sub operations.
    • Modified pull_loop to accept &dyn AccessTokenProvider and refresh tokens before pull and acknowledge requests.
    • Ensured cleanup operations use a freshly acquired token.
    • Added a new test test_pull_loop_refreshes_pubsub_token_between_requests to validate token refreshing in the pull loop.
  • src/helpers/gmail/watch.rs
    • Added PUBSUB_API_BASE and GMAIL_API_BASE constants for API endpoints.
    • Introduced WatchRuntime struct to encapsulate client, token providers, and configuration.
    • Refactored handle_watch to initialize ScopedTokenProvider for both Gmail and Pub/Sub.
    • Modified watch_pull_loop to accept &WatchRuntime and refresh tokens for Pub/Sub pull, Gmail history, Gmail message fetch, and Pub/Sub acknowledge requests.
    • Ensured cleanup operations use a freshly acquired token.
    • Added a new test test_watch_pull_loop_refreshes_tokens_for_each_request to verify token refreshing across all API calls in the watch loop.
Activity
  • All 553 existing cargo test tests passed, with 3 new tests introduced.
  • cargo clippy -- -D warnings ran clean, indicating no new warnings.
  • New tests test_scoped_token_provider_uses_get_token, test_pull_loop_refreshes_pubsub_token_between_requests, and test_watch_pull_loop_refreshes_tokens_for_each_request were added to validate the token refreshing logic.
  • This pull request addresses and closes issue Bug: gmail +watch and events +subscribe exit after ~1 hour due to expired access token #392.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism for refreshing OAuth2 tokens in long-running processes using an AccessTokenProvider trait, improving reliability and correctly refactoring watch and subscribe helpers. However, a critical security concern has been identified: potential URL injection vulnerabilities in both the Gmail watch and Workspace Events subscribe helpers, where user-supplied subscription names and event types are used to construct API URLs without sufficient validation. Additionally, the current implementation fetches tokens excessively within loops, leading to unnecessary performance overhead. It is recommended to address the URL injection by applying strict resource name validation consistently across all helpers, and to optimize token usage by reusing tokens within a single loop iteration where they are still valid.

Remove redundant access_token() calls for acknowledge requests (both
loops) and per-message Gmail fetches.  The token obtained at the start
of the iteration / function is still valid and is now reused for all
HTTP calls within the same pass.

Addresses review feedback from gemini-code-assist.
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an AccessTokenProvider trait and ScopedTokenProvider to enable long-running helpers, specifically for Gmail watch and Workspace Events subscribe, to refresh OAuth access tokens before each API call, preventing token expiration issues. The changes involve refactoring token acquisition in src/auth.rs, src/helpers/events/subscribe.rs, and src/helpers/gmail/watch.rs to use these new providers, along with adding new test utilities and tests. Review comments highlight several issues: potential URL path traversal vulnerabilities in both the Pub/Sub subscription and Gmail watch cleanup operations due to unvalidated user input, code duplication of the FakeTokenProvider test utility, and a re-introduced bug in the Gmail watch helper where the gmail_token might still expire during long fetch_and_output_messages operations.

…r message

- Move FakeTokenProvider to auth.rs behind #[cfg(test)] so both helper
  test modules share one definition.
- Refresh the Gmail token per message inside fetch_and_output_messages
  to guard against expiry during large batches.
- Update test expectations accordingly.
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism for refreshing OAuth2 tokens in long-running processes by using an AccessTokenProvider trait. The refactoring of the gmail +watch and events +subscribe helpers to use this provider is well-executed and addresses the critical issue of token expiration. The addition of constants for API base URLs and comprehensive tests, including mock servers, significantly improves the code's testability and reliability.

I've identified a couple of high-severity issues in the cleanup logic where a failure to refresh a token is not handled correctly, potentially misleading the user. Please see the detailed comments.

Move the success message inside the token-refresh guard and add an else
branch that warns about potential orphaned resources.
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an AccessTokenProvider trait and its implementation, ScopedTokenProvider, to enable refreshing OAuth access tokens for long-running operations. The Gmail watch and Workspace Events subscribe helpers are refactored to use these new token providers, ensuring that fresh access tokens are obtained before each Pub/Sub and Gmail API request, including during cleanup and for individual message fetches in Gmail. This prevents token expiration issues in long-running processes. New test helpers and integration tests were added to validate this token refreshing behavior.

Note: Security Review did not run due to the size of the PR.

A single history batch won't approach the token's 1-hour lifetime,
so per-message refresh is unnecessary overhead. The outer watch_pull_loop
already refreshes tokens each iteration for long-running resilience.
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request introduces an AccessTokenProvider trait and ScopedTokenProvider to enable long-running helpers (Gmail watch and Workspace Events subscribe) to refresh OAuth access tokens before each API request, addressing potential token expiration. The subscribe.rs and watch.rs helpers are updated to utilize this new token refresh mechanism for Pub/Sub and Gmail API calls, including cleanup operations. New tests were added to verify this behavior. However, the review comments highlight that the newly added tests for token refreshing in both subscribe.rs and watch.rs are insufficient. They currently use single tokens for multiple requests, failing to properly validate that tokens are indeed refreshed between calls. Additionally, a potential issue was identified in watch.rs where the Gmail token for fetching individual messages is only refreshed once per fetch_and_output_messages call, which could still lead to expiration if many messages are processed. This token should be refreshed before each individual message fetch for improved robustness.

@etanase etanase changed the title fix: refresh OAuth2 tokens in long-running watch/subscribe loops fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops Mar 11, 2026
@codecov
Copy link

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 84.38538% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.67%. Comparing base (7e22a3d) to head (f40d037).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
src/helpers/gmail/watch.rs 85.62% 24 Missing ⚠️
src/helpers/events/subscribe.rs 80.35% 22 Missing ⚠️
src/auth.rs 95.45% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #407      +/-   ##
==========================================
+ Coverage   64.40%   65.67%   +1.26%     
==========================================
  Files          38       38              
  Lines       15584    15843     +259     
==========================================
+ Hits        10037    10405     +368     
+ Misses       5547     5438     -109     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jpoehnelt jpoehnelt merged commit 3dcf818 into googleworkspace:main Mar 12, 2026
44 of 45 checks passed
@etanase etanase deleted the fix/392-refresh-long-running-tokens branch March 12, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: auth area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: gmail +watch and events +subscribe exit after ~1 hour due to expired access token

3 participants