GET /api/v1/integrations/google/connect sends the browser to Google's
consent screen (calendar.events and calendar.calendarlist.readonly,
access_type=offline, prompt=consent, PKCE S256) with a state carried in a
signed __Host-mealquest_google flow cookie bound to the session.
GET /api/v1/integrations/google/callback checks the state and session,
exchanges the code, refuses a response without a refresh token
(reconnect_required) or without both scopes, seals the refresh token with
secretbox into integrations_google and sets connected_at.
DELETE /api/v1/integrations/google revokes the token at Google and clears
it; a revoke failure keeps the connection so it can be retried.
- internal/gcal: the connect service; Google endpoints are overridable and
internal/gcal/gcaltest fakes the token and revoke endpoints.
- internal/auth: the login cookie becomes the exported FlowCookie both
flows use (same wire format); CheckRedirect and ExchangeError are
exported for reuse.
- internal/config: MEALQUEST_GOOGLE_CLIENT_ID and
MEALQUEST_GOOGLE_CLIENT_SECRET_FILE, required at boot.
- DeleteIntegrationsGoogle query (sqlc regenerated); no migration, the
integrations_google table already exists.
- deploy: compose mounts google_client_secret; .env.example and RUNBOOK
document the Google OAuth client, its redirect URI and rotation.
Maestro task 1e9d7850-3a09-4572-b8a1-cb18cd4774cb.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>