Skip to main content

Google

One connected Google account covers Gmail and Sheets: the assistant can read spreadsheets and send mail as the logged-in user. The assistant never holds the user's real access token; see Google token flow for how.

What the user is trying to do​

A typical turn is "make a chart of this sheet and email it to …". Recipients see mail from the logged-in user's own Gmail address, not a shared service account. An optional PNG comes from a prior rasterize_chart call, not from bytes the model generated.

1. Log in to harnesi​

The chat UI authenticates the same way as every other page: Django session cookie (django_auth on the ninja routers). That identity is request.user. It is not a Google identity yet. Superuser vs ordinary user only matters for saving the shared OAuth app (client ID / secret) on the Tools page; it does not widen the agent sandbox.

2. One-time: save the Google OAuth app​

A superuser pastes a Google Cloud Web application client ID and secret into Tools → Google (chat/vault_api.py). Those values live encrypted in VaultSecret, not in .env. The redirect URI that Google must allow is:

http://localhost:8000/api/integrations/google/callback

(local ./dev.sh; must match GOOGLE_OAUTH_REDIRECT_URI / the vault override exactly). The consent scopes include Sheets, Drive metadata, gmail.send, and userinfo.email (chat/google_oauth_api.py).

Until both client ID and secret are stored, Connect Google account stays disabled — GET /api/integrations/google/status reports configured: false.

3. Connect this user's Google account​

The user clicks Connect. The browser goes to /api/integrations/google/authorize, then to Google's consent screen. Google redirects to the backend callback. CSRF/state is a signed payload (user id + return path + SPA origin), not the Django session cookie — Google's redirect hits port 8000, which often has no SPA cookie.

On success, GoogleCredential is upserted for that user: refresh token, a cached access token, and the Gmail address. That row is what lets session start attach the gmail and sheets proxy routes. chat/nono_profile.py's entitlements_for records the Google credential; it does not write an MCP config.

Disconnect revokes at Google when it can and deletes the row. A later Connect needs prompt=consent so Google issues a new refresh token.

Failure modes that look like "email doesn't work"​

What you seeUsual cause
Connect disabledOAuth app client ID/secret not saved, or vault could not encrypt them
redirect_uri_mismatchURI not on this Web client in Google Cloud (must match the Client ID on the Tools page, exactly)
Tool error: connect Google and start a new SessionNo GoogleCredential, or a warm session started before Connect
Gmail API 401 on sendAccess token expired (~1 hour); start a new Session so the warm process is recreated
Chart missingchart_attachment was paraphrased; it must be the exact rasterize_chart filename