fix(gmail-auth): request explicit filter settings scope

This commit is contained in:
William Valentin
2026-02-22 23:42:40 -08:00
parent 266c37b353
commit 80ce8d9aaf
5 changed files with 23 additions and 6 deletions
+1 -1
View File
@@ -1154,7 +1154,7 @@ Supported delivery modes:
1. Create a Google Cloud project with the Gmail API enabled 1. Create a Google Cloud project with the Gmail API enabled
2. Create OAuth2 credentials (Desktop application type) and download the JSON file 2. Create OAuth2 credentials (Desktop application type) and download the JSON file
3. Run `flynn gmail-auth` to complete the OAuth2 flow and store the refresh token 3. Run `flynn gmail-auth` to complete the OAuth2 flow and store the refresh token
- Requests full Gmail access scope (`https://mail.google.com/`) so all filter operations are allowed - Requests Gmail scopes for settings + read access (`gmail.settings.basic` + `gmail.readonly`)
For Pub/Sub delivery (push/pull), also enable the Pub/Sub API and create: For Pub/Sub delivery (push/pull), also enable the Pub/Sub API and create:
- A topic (e.g. `projects/your-project/topics/gmail-push`) - A topic (e.g. `projects/your-project/topics/gmail-push`)
+1 -1
View File
@@ -452,7 +452,7 @@ memory:
# credentials_file: ~/.config/flynn/gmail-credentials.json # credentials_file: ~/.config/flynn/gmail-credentials.json
# token_file: ~/.config/flynn/gmail-token.json # token_file: ~/.config/flynn/gmail-token.json
# # Authenticate with: flynn gmail-auth # # Authenticate with: flynn gmail-auth
# # (requests full Gmail scope https://mail.google.com/ for complete Gmail filter permissions) # # (requests gmail.settings.basic + gmail.readonly for filter create + inbox tools)
# #
# # Optional Pub/Sub delivery # # Optional Pub/Sub delivery
# # Push mode: configure a topic and a push subscription that POSTs to /gmail/push # # Push mode: configure a topic and a push subscription that POSTs to /gmail/push
+15 -1
View File
@@ -3,6 +3,20 @@
"updated_at": "2026-02-23", "updated_at": "2026-02-23",
"description": "Tracks the status of all Flynn plans and implementation phases", "description": "Tracks the status of all Flynn plans and implementation phases",
"plans": { "plans": {
"gmail-filter-scope-correction": {
"status": "completed",
"date": "2026-02-23",
"updated": "2026-02-23",
"summary": "Corrected Gmail OAuth scopes for filter creation after validating that `mail.google.com` alone still produced `ACCESS_TOKEN_SCOPE_INSUFFICIENT` for `users.settings.filters.create` in live checks. `flynn gmail-auth` now explicitly requests `gmail.settings.basic` (plus `gmail.readonly`). Updated auth tests and operator docs/comments.",
"files_modified": [
"src/cli/gmail-auth.ts",
"src/cli/gmail-auth.test.ts",
"README.md",
"config/default.yaml",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/cli/gmail-auth.test.ts + pnpm typecheck passing"
},
"minimal-tui-multiline-paste-mode": { "minimal-tui-multiline-paste-mode": {
"status": "completed", "status": "completed",
"date": "2026-02-23", "date": "2026-02-23",
@@ -6345,7 +6359,7 @@
"operator_dx_milestone": "Phase 3 (Live Ops Dashboard): 2/2 plans complete — milestone done", "operator_dx_milestone": "Phase 3 (Live Ops Dashboard): 2/2 plans complete — milestone done",
"dashboard_observability": "completed — service health graphs + core service log viewer added to web UI via observability RPCs and bounded backend sampling", "dashboard_observability": "completed — service health graphs + core service log viewer added to web UI via observability RPCs and bounded backend sampling",
"gmail_auth_cli": "flynn gmail-auth command implemented with OAuth2 flow, doctor check, config routed to Telegram", "gmail_auth_cli": "flynn gmail-auth command implemented with OAuth2 flow, doctor check, config routed to Telegram",
"gmail_filter_creation": "completed — gmail.filter.create tool added with criteria/action validation; gmail-auth now requests full Gmail scope (https://mail.google.com/) for complete filter permissions", "gmail_filter_creation": "completed — gmail.filter.create tool added with criteria/action validation; gmail-auth requests explicit gmail.settings.basic + gmail.readonly scopes for filter creation and inbox reads",
"toolloop_action_intent_recovery": "completed — when a model claims it will execute a tool but emits no tool call, NativeAgent now issues one internal nudge and continues the same turn to execute tools or produce a concrete blocker", "toolloop_action_intent_recovery": "completed — when a model claims it will execute a tool but emits no tool call, NativeAgent now issues one internal nudge and continues the same turn to execute tools or produce a concrete blocker",
"toolloop_execution_claim_recovery": "completed — when a model claims a known tool already succeeded/failed without emitting a tool call, NativeAgent now nudges once and retries the same turn before returning text", "toolloop_execution_claim_recovery": "completed — when a model claims a known tool already succeeded/failed without emitting a tool call, NativeAgent now nudges once and retries the same turn before returning text",
"minimal_tui_multiline_paste_mode": "completed — minimal TUI now supports `/paste`/`/multiline` multiline compose mode ending with single '.' line, preventing newline truncation for pasted prompts", "minimal_tui_multiline_paste_mode": "completed — minimal TUI now supports `/paste`/`/multiline` multiline compose mode ending with single '.' line, preventing newline truncation for pasted prompts",
+2 -1
View File
@@ -71,7 +71,8 @@ describe('gmail-auth', () => {
expect(url).toContain('https://accounts.google.com/o/oauth2/v2/auth'); expect(url).toContain('https://accounts.google.com/o/oauth2/v2/auth');
expect(url).toContain('client_id=my-client-id'); expect(url).toContain('client_id=my-client-id');
expect(url).toContain('redirect_uri=http%3A%2F%2Flocalhost%3A3000'); expect(url).toContain('redirect_uri=http%3A%2F%2Flocalhost%3A3000');
expect(url).toContain('scope=https%3A%2F%2Fmail.google.com%2F'); expect(url).toContain('scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.settings.basic');
expect(url).toContain('https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly');
expect(url).toContain('access_type=offline'); expect(url).toContain('access_type=offline');
expect(url).toContain('prompt=consent'); expect(url).toContain('prompt=consent');
}); });
+4 -2
View File
@@ -7,8 +7,10 @@ import { URL } from 'url';
import { loadConfigSafe } from './shared.js'; import { loadConfigSafe } from './shared.js';
const SCOPES = [ const SCOPES = [
// Full Gmail access (includes all filter operations and settings APIs). // Explicitly request Gmail settings scope required by filters.create.
'https://mail.google.com/', 'https://www.googleapis.com/auth/gmail.settings.basic',
// Keep readonly mailbox access for list/search/read tooling.
'https://www.googleapis.com/auth/gmail.readonly',
]; ];
const REDIRECT_PORT = 3000; const REDIRECT_PORT = 3000;
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`; const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`;