Skip to content

auth: bring-your-own Google OAuth client (no app verification needed)#1

Open
GazingInSpring wants to merge 1 commit into
masterfrom
byo-google-auth
Open

auth: bring-your-own Google OAuth client (no app verification needed)#1
GazingInSpring wants to merge 1 commit into
masterfrom
byo-google-auth

Conversation

@GazingInSpring
Copy link
Copy Markdown
Collaborator

Why

gmail.readonly is a Google restricted scope. A single shared ZeroEntropy-owned OAuth app must pass Google verification (a CASA security audit, ~$8k plus extensive requirements) before external users can authorize without the "unverified app" cap / warnings. To ship without that, this changes auth so each user brings their own Google OAuth client — authorization then runs against a Google Cloud project the user controls, and using your own client for your own account needs no verification.

Current vs new auth

Before: zemail/gmail_client.py hardcoded a single shared OAuth client (_OAUTH_CLIENT_CONFIG with a baked-in client_id + client_secret for the zerank-2 project). Every user authorized through that one ZeroEntropy app, which is what triggers the restricted-scope verification requirement.

After: the hardcoded client is removed. config.get_google_client_config() resolves the user's own OAuth client, mirroring exactly how the existing ZEROENTROPY_API_KEY is handled (env var → local file fallback). Resolution order:

  1. ZEMAIL_GOOGLE_CLIENT_ID + ZEMAIL_GOOGLE_CLIENT_SECRET env vars.
  2. GOOGLE_CLIENT_SECRETS_FILE — path to a client_secret_*.json downloaded from the Google Cloud Console (supports installed and web shapes).
  3. google_client.json saved in the data dir via the new set_google_credentials MCP tool.

When none are configured, Gmail tools return a GoogleCredentialsRequired message with step-by-step setup instructions instead of silently using a shared app.

Interpretation chosen

The task allowed BYO client credentials, a user-provided token, or a service account. BYO OAuth client is the right fit here because: (a) it keeps the existing installed-app/loopback OAuth flow and gmail.readonly scope intact (minimal change), (b) a service account can't read a normal consumer Gmail mailbox without domain-wide delegation (Workspace-only), and (c) the credential-handling pattern matches the existing ZeroEntropy API key plumbing. A raw user-supplied token alone wouldn't support the refresh flow already in get_gmail_service.

User setup (now in README "Setup")

  1. Google Cloud Console → create/select a project.
  2. Enable the Gmail API (APIs & Services → Library).
  3. Configure the OAuth consent screen (User type: External; add yourself as a Test user).
  4. Credentials → Create OAuth client IDDesktop app; copy Client ID + secret.
  5. Give them to zemail via set_google_credentials, the ZEMAIL_GOOGLE_CLIENT_ID/ZEMAIL_GOOGLE_CLIENT_SECRET env vars (see .env.example), or GOOGLE_CLIENT_SECRETS_FILE.

Changes

  • zemail/config.pyget_google_client_config, save_google_client, setup message.
  • zemail/gmail_client.py — removed _OAUTH_CLIENT_CONFIG; flows now use the resolved client; new GoogleCredentialsRequired.
  • zemail/server.py — new set_google_credentials tool; Gmail tools handle GoogleCredentialsRequired.
  • .mcp.json / .claude-plugin/plugin.json — pass through / document the new env vars.
  • README.md, skills/search-emails/SKILL.md, .env.example — setup docs (placeholders only).

Verification done (no real Google creds used)

  • uv sync succeeds.
  • Server module imports; all 7 MCP tools register (including set_google_credentials).
  • Credential resolution verified for all paths: env vars, unexpanded ${...} treated as unset, saved google_client.json, and GOOGLE_CLIENT_SECRETS_FILE — in an isolated temp data dir.
  • No-creds path raises GoogleCredentialsRequired with the setup steps (not a shared-app fallback).
  • set_google_credentials validates client-ID format and rejects empty secrets.
  • No leftover references to the old hardcoded client.

Needs the owner's live verification / follow-up

  • Live Google OAuth could not be tested (no real BYO creds). Owner should: create a test Google Cloud OAuth client, supply it, and confirm the full authorize → sync flow end to end.
  • Rotate the old shared secret: the previously hardcoded zerank-2 client_id/secret remain in git history (the initial commit). Now unused — revoke/rotate it in the Google Cloud Console.
  • Pre-existing unused-import lint (urlparse in gmail_client.py) left untouched as it's unrelated to this change and predates it on master.

🤖 Generated with Claude Code

gmail.readonly is a Google restricted scope, so a single shared ZeroEntropy
OAuth app would need a CASA security audit before external users could
authorize without warnings. Instead, each user now supplies their own Google
Cloud OAuth client credentials, so authorization runs against a Google project
they control — self-use needs no Google verification.

- Remove the hardcoded shared client_id/client_secret from gmail_client.py.
- config.get_google_client_config resolves BYO credentials, mirroring how the
  ZeroEntropy API key is handled: env vars (ZEMAIL_GOOGLE_CLIENT_ID /
  ZEMAIL_GOOGLE_CLIENT_SECRET), a GOOGLE_CLIENT_SECRETS_FILE path, or a locally
  saved google_client.json.
- New set_google_credentials MCP tool + GoogleCredentialsRequired error with
  clear setup instructions when no client is configured.
- Pass the new env vars through .mcp.json; document them in plugin.json setup,
  the README Setup section, the search-emails skill, and .env.example
  (placeholders only — no real secrets).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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