Metadata-Version: 2.4
Name: google-auth-core
Version: 0.3.0
Summary: Shared Google OAuth token store and authorized-service factory for CLIs and MCP servers
Author: Nitai Aharoni
License-Expression: MIT
Project-URL: Homepage, https://github.com/nitaiaharoni1/google-auth-core
Project-URL: Repository, https://github.com/nitaiaharoni1/google-auth-core
Keywords: google,oauth,authentication,credentials,mcp,cli
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: google-api-python-client>=2.0.0
Requires-Dist: google-auth>=2.0.0
Requires-Dist: google-auth-oauthlib>=0.5.0
Requires-Dist: google-auth-httplib2>=0.1.0
Requires-Dist: click>=8.0.0
Requires-Dist: requests>=2.0.0
Dynamic: license-file

# google-auth-core

Single source of truth for the `~/.google` credential store, shared by the
Google CLIs (`gmail-cli`, `google-calendar-cli`) and the Google Workspace MCP
servers (`gmail-mcp`, `gcal-mcp`, `gsheets-mcp`, `gdocs-mcp`, `gdrive-mcp`).

## What it provides

- A unified per-account token store under `~/.google/tokens/google_<account>.json`
  holding all scopes (Gmail + Calendar + Sheets + Docs + Drive + Contacts), so one
  consent covers every service for both CLIs and MCP servers.
- Account management: default account, aliases (`work`, `personal`), env and
  directory overrides.
- `get_service(api, version, account)`: an authorized `googleapiclient` service,
  cached per `(account, api, version)` for persistent, multi-account-safe use.
- A consistent error taxonomy (`AuthError`, `NotFoundError`, `RateLimitError`,
  `InvalidArgumentError`, `UpstreamError`) and `map_http_error`.

## CLI

```bash
google-auth login [account]        # one-time OAuth, writes a unified token
google-auth login --readonly you@work.com   # read-only token, no writes possible
google-auth list                   # accounts, default, aliases
google-auth alias work you@work.com
google-auth status                 # token health per account
google-auth default you@work.com
google-auth logout you@work.com    # revoke at Google, then delete locally
google-auth logout --local-only you@work.com   # delete locally, skip revoke
```

Place your OAuth client (Desktop app) at `~/.google/credentials.json` first.
`google-auth setup` walks you through creating one; `google-auth doctor` checks
an existing setup.

**Avoid "Testing" status on the OAuth consent screen.** Google expires refresh
tokens after 7 days for an External app left in Testing, so you'd have to re-run
`google-auth login` every week. Instead:

- **Google Workspace account:** set the user type to **Internal**. No test
  users, no warning screen, tokens don't expire weekly. The Cloud project must
  belong to your Workspace organization, and only its accounts can sign in.
- **Personal Gmail, or accounts from several domains:** use **External**, then
  click **Publish app** so the status is **In production** (no need to submit
  for verification). Each login shows an "unverified app" warning: click
  **Advanced**, then **Go to (your app name) (unsafe)**. Google caps unverified
  apps at 100 users, which doesn't matter for personal use.

Already in Testing? Switch to In production and run `google-auth login` again
for each account.

### Which feature needs which scope

| Feature | Full login | `--readonly` login |
|---|---|---|
| Gmail | `gmail.readonly`, `gmail.send`, `gmail.modify`, `gmail.settings.basic` | `gmail.readonly` |
| Calendar | `calendar` | `calendar.readonly` |
| Sheets | `spreadsheets` | `spreadsheets.readonly` |
| Docs | `documents` | `documents.readonly` |
| Drive | `drive` | `drive.readonly` |
| Contacts (`search_contacts`) | `contacts.readonly`, `contacts.other.readonly`, `directory.readonly` | same |

Each feature also needs its API enabled in your Cloud project (the People API
for Contacts); `google-auth setup` links to all of them.

**Tokens from before Contacts existed keep working.** They don't have its
scopes, but `get_credentials` and `check_token_health` still treat
them as healthy: the missing feature scopes are left out of the refresh
request (asking Google for scopes it never granted fails with
`invalid_scope`), so Gmail, Calendar, Sheets, Docs and Drive carry on. Only contact
search fails, with Google's insufficient-scope error. `google-auth status`
and `google-auth doctor` name what's missing:

```
you@work.com: valid
  contacts not granted. Run: google-auth login you@work.com to grant contacts
```

In code, `missing_features(granted_scopes)` and
`token_missing_features(account)` return `{feature: [missing scopes]}`, empty
when everything is covered.

### Read-only login

`google-auth login --readonly` requests only the read-only scopes in the
table above (`gmail.readonly`, `calendar.readonly`, `spreadsheets.readonly`,
`documents.readonly`, `drive.readonly` and the three contacts scopes). The token itself cannot send mail, edit events, or change or
delete files, whatever the MCP server or CLI tries. This is stronger than
`GOOGLE_MCP_READONLY`, which only hides write tools.

The granted scopes saved in the token file record which set it holds
(`scope_set()` returns `full`, `readonly` or `partial`). `get_credentials` and
`check_token_health` treat a read-only token as healthy even when called with
`ALL_SCOPES`, and `status` / `doctor` label it read-only. Writes fail with an
`AuthError` that says the account is read-only: call
`require_write_access(account)` before a write to fail early, or let the 403
from Google map to the same message. Run `google-auth login <account>` without
the flag to upgrade to full access.

### Logout

`google-auth logout` revokes the refresh token at Google
(`POST https://oauth2.googleapis.com/revoke`) before deleting the local file, so
a copied token stops working too. A token Google already treats as invalid is
not an error. If Google cannot be reached, nothing is deleted; retry, or pass
`--local-only`.
