Skip to content

feat: Gmail provider (Google OAuth, Gmail loader, Sources Google mode) (GATE EDIT FOR OWNER REVIEW) - #61

Merged
urnlahzer merged 2 commits into
mainfrom
feat/google-gmail-provider
Oct 4, 2026
Merged

urnlahzer merged 2 commits into
mainfrom
feat/google-gmail-provider

Conversation

@urnlahzer

Copy link
Copy Markdown
Owner

What

PR-2 of docs/plans/2026-09-27-google-provider-and-shared-registration.md: a Gmail account can be connected and reviewed, one provider at a time. Google Tasks reminders are PR-3; both providers together are PR-4.

Owner-approved gate edit: tools/check-public-repo.ps1's "personal Unix home-directory path" rule now also excludes me/ and @me/ in its negative lookahead so the Gmail and Tasks API paths (users/me/…, users/@me/…) pass while real home paths are still blocked. New tools/test-public-repo.ps1 proves the three cases against the real checker.

Graph crate (commit 1)

  • Google OAuth through the shared OAuthEndpoints/authorize_with path: accounts.google.com authorize, oauth2.googleapis.com token, loopback-IP redirect, the installed-app client secret in the request body alongside PKCE, access_type=online, prompt=select_account, include_granted_scopes=true. Google's userinfo.email/userinfo.profile scope URLs are normalised back to email/profile so the cached session matches (caught in review). Refresh tokens are still removed before the OAuth library parses the response.
  • Per-provider cached sessions (run_with_session_for); a Google 401 clears only the Google slot and re-authorizes once, like Microsoft.
  • Gmail loader: userinfo identity (google:{sub}), INBOX and SENT listings (q=newer_than:30d, 100 ids, ≤10 pages, partial on cap), hydration through the shared path with cache hits skipping the fetch, conversation = threadId, Gmail web link, internalDate cutoff, MessageTooLarge parity, header bounds matching the Microsoft item. Every fetch is origin-confined; tests use loopback servers.
  • MIME: attachment-skipping part walk (html preferred), padded or unpadded base64url, utf-8 / iso-8859-1 / windows-1252 / lossy charsets, RFC 2047 B and Q words joined per §6.2, address lists.
  • check_connection for Google via userinfo + Gmail profile, content discarded.

Desktop (commit 2)

See the commit message: "Mail provider" Segmented on the Sources card, Google client fields with the same shipped/BYO disclosure pattern as Microsoft, per-provider status lines, AccountConfig routing for load/retry, Service::Mailbox(MailProvider), Google reminders disabled with a fixed status until PR-3.

Verification

  • cargo fmt --all -- --check; clippy -D warnings on openloops-graph (live-connection) and openloops-desktop (native-ui,ui-screenshot): clean
  • cargo test -p openloops-graph --features live-connection: 182 passed
  • cargo test -p openloops-desktop --features native-ui -- --test-threads=1: 444 passed, 0 failed, 3 ignored
  • tools/test-public-repo.ps1 passed; tools/check-public-repo.ps1 -Mode Staged passed on each commit
  • The twelve tools/check-*.ps1 checkers match the main baseline (the four pre-existing failures are unchanged)
  • Cargo.lock unchanged
  • Read-only Opus reviews of both halves with Codex fix passes

Not yet exercised live

No Google sign-in has been performed against a real Google Cloud client; the loopback tests pin the request shapes. The owner's smoke test (plan §Verification) needs the Google Cloud project and Desktop OAuth client from plan phase G0, with the owner's address on the test-user list.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YCesN8ZHnhqa6n3rWFThas

urnlahzer and others added 2 commits October 2, 2026 23:11
…ction (gate allowance: OWNER-APPROVED)

First slice of PR-2 in docs/plans/2026-09-27-google-provider-and-shared-registration.md.
Graph crate only; the Sources screen Google mode follows in the next commit.

- live/google/mod.rs: google_endpoints(config) (accounts.google.com authorize,
  oauth2.googleapis.com token, loopback-IP redirect, installed-app client secret in the
  request body alongside PKCE, access_type=online, prompt=select_account,
  include_granted_scopes=true); scopes openid/email/profile + gmail.readonly or tasks;
  with_google_session over the per-provider session slot; check_connection via userinfo +
  the Gmail profile endpoint (content discarded).
- live.rs: run_with_session_for(provider, ..) generalises the cached-session path; the
  Microsoft wrapper is unchanged; a Google 401 clears only the Google slot. Refresh tokens
  are still removed before the OAuth library parses the response.
- live/google/gmail.rs: userinfo identity (account "google:{sub}", lowercased address),
  INBOX and SENT listings (q=newer_than:30d, 100 ids, <=10 pages, partial on cap), hydration
  through the shared add_hydrated path with cache hits skipping the fetch, MailItem with
  provider Google, conversation = threadId, Gmail web link, internalDate cutoff,
  MessageTooLarge parity. Every fetch is origin-confined; tests use loopback servers.
- live/google/mime.rs: part walk (attachments skipped, html preferred), base64url via
  encoding::base64url_decode (new), utf-8 / iso-8859-1 / windows-1252 / lossy charsets,
  RFC 2047 B and Q words, address-list parsing matching the Microsoft item() formatting.
- live/provider.rs: the Google arms call the real functions.
- tools/check-public-repo.ps1 (OWNER-APPROVED gate edit): the "personal Unix
  home-directory path" rule's negative lookahead also excludes `me/` and `@me/`, so the
  Gmail and Tasks API paths (users/me/..., users/@me/...) pass while real home paths are
  still blocked. New tools/test-public-repo.ps1 proves all three cases against the real
  checker in throwaway repositories.

Verified: cargo fmt --check; clippy -D warnings (graph live-connection, desktop
native-ui,ui-screenshot); graph 182 passed; desktop 438 passed, 0 failed, 3 ignored;
tools/test-public-repo.ps1 passed; public-repo gate on staged files; the other checkers
match the main baseline; Cargo.lock unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCesN8ZHnhqa6n3rWFThas
…vider

Second slice of PR-2 in docs/plans/2026-09-27-google-provider-and-shared-registration.md.

- Sources card 1 is "Connect your mailbox" with a "Mail provider" Segmented (Microsoft 365 /
  Google). The Microsoft branch is today's content unchanged. The Google branch mirrors the
  Microsoft shipped/BYO pattern: with a shipped Google client, helper text about the
  unverified-app notice and the test-user list plus an optional "Use my own Google Cloud
  client" disclosure; without one, the client-ID and client-secret fields (Show secret
  checkbox, "Open Google Cloud console" link). One "Sign in & check inbox" button and
  per-provider status lines. The scope-summary cells name "Inbox / Sent" and "Microsoft
  only" for Groups in Google mode.
- AppModel: active_mail_provider() from settings.mail_providers (single element in this PR;
  PR-4 widens it); switching persists the choice and clears every session.
  effective_google_registration() (BYO fields > shipped > none), account_config(provider)
  builds the ConnectionConfig or GoogleConfig, Service::Mailbox(MailProvider) routes worker
  failures to the right status, connection_report_status(provider, report).
- Loading, retry and sign-in hints go through AccountConfig for the active provider.
- Reminders stay Microsoft-only until PR-3: in Google mode the reminder control is disabled
  with "Reminders for Google accounts arrive in a later release."

Verified: cargo fmt --check; clippy -D warnings (desktop native-ui,ui-screenshot, graph
live-connection); desktop 444 passed, 0 failed, 3 ignored; Cargo.lock unchanged;
public-repo gate on staged files. Review fixes folded in: sign-in enablement is based on
registration presence (validation errors still surface on click, as before); the Google-mode
reminder notice no longer masks decision-store errors; the inbox pill follows the active
provider; switching providers resets the review state; shared helpers for status routing,
edit clearing and sign-in labels; dispatch_reminder_sync guarded to Microsoft until PR-3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCesN8ZHnhqa6n3rWFThas
@urnlahzer
urnlahzer merged commit 3d7ad26 into main Oct 4, 2026
2 checks passed
@urnlahzer
urnlahzer deleted the feat/google-gmail-provider branch October 4, 2026 19:10
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