From 1ee52fe82392872a6b9a9ff16615ea86c02ae644 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Sun, 8 Mar 2026 14:26:23 +0800 Subject: [PATCH 1/2] fix: use gmail.readonly scope in +triage to avoid metadata scope 403 The +triage helper uses the `q` query parameter when listing messages, but Gmail's metadata scope does not support `q` and returns 403. When a user's OAuth token includes both gmail.metadata and gmail.modify scopes, the API may resolve to the metadata code path and reject the query. Switch +triage from gmail.modify to gmail.readonly, which is the minimum scope that supports query filtering and aligns with the read-only nature of the triage command. Fixes #265 Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-gmail-triage-scope.md | 5 +++++ src/helpers/gmail/mod.rs | 1 + src/helpers/gmail/triage.rs | 8 ++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-gmail-triage-scope.md diff --git a/.changeset/fix-gmail-triage-scope.md b/.changeset/fix-gmail-triage-scope.md new file mode 100644 index 00000000..c14c6287 --- /dev/null +++ b/.changeset/fix-gmail-triage-scope.md @@ -0,0 +1,5 @@ +--- +"@googleworkspace/cli": patch +--- + +Fix gmail +triage 403 error by using gmail.readonly scope instead of gmail.modify to avoid conflict with gmail.metadata scope that does not support the q parameter diff --git a/src/helpers/gmail/mod.rs b/src/helpers/gmail/mod.rs index b7019d53..da82d1e6 100644 --- a/src/helpers/gmail/mod.rs +++ b/src/helpers/gmail/mod.rs @@ -34,6 +34,7 @@ use std::pin::Pin; pub struct GmailHelper; pub(super) const GMAIL_SCOPE: &str = "https://www.googleapis.com/auth/gmail.modify"; +pub(super) const GMAIL_READONLY_SCOPE: &str = "https://www.googleapis.com/auth/gmail.readonly"; pub(super) const PUBSUB_SCOPE: &str = "https://www.googleapis.com/auth/pubsub"; impl Helper for GmailHelper { diff --git a/src/helpers/gmail/triage.rs b/src/helpers/gmail/triage.rs index 6899d4a8..d8adffba 100644 --- a/src/helpers/gmail/triage.rs +++ b/src/helpers/gmail/triage.rs @@ -32,8 +32,12 @@ pub async fn handle_triage(matches: &ArgMatches) -> Result<(), GwsError> { .map(|s| crate::formatter::OutputFormat::from_str(s)) .unwrap_or(crate::formatter::OutputFormat::Table); - // Authenticate - let token = auth::get_token(&[GMAIL_SCOPE]) + // Authenticate — use gmail.readonly instead of gmail.modify because triage + // is read-only and the `q` query parameter is not supported under the + // gmail.metadata scope. When a token carries both metadata and modify + // scopes the API may resolve to the metadata path and reject `q` with 403. + // gmail.readonly always supports `q`. + let token = auth::get_token(&[GMAIL_READONLY_SCOPE]) .await .map_err(|e| GwsError::Auth(format!("Gmail auth failed: {e}")))?; From d51e7805b898fddd8a9f8963f6c54f78954920c8 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Sun, 8 Mar 2026 14:26:24 +0800 Subject: [PATCH 2/2] fix: handle --help flag in `gws auth setup` before running setup checks When `gws auth setup --help` is invoked on a machine without gcloud, the command errors instead of printing help. This adds an early check for -h/--help that prints usage info and exits before any setup logic. Fixes #280 Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-auth-setup-help-flag.md | 5 ++++ src/setup.rs | 35 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .changeset/fix-auth-setup-help-flag.md diff --git a/.changeset/fix-auth-setup-help-flag.md b/.changeset/fix-auth-setup-help-flag.md new file mode 100644 index 00000000..10a4935e --- /dev/null +++ b/.changeset/fix-auth-setup-help-flag.md @@ -0,0 +1,5 @@ +--- +"@googleworkspace/cli": patch +--- + +Handle -h/--help flag in `gws auth setup` to print usage and exit before running setup checks diff --git a/src/setup.rs b/src/setup.rs index a2730ec8..5cbf5dee 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1431,8 +1431,25 @@ async fn stage_configure_oauth(ctx: &mut SetupContext) -> Result Use a specific GCP project\n", + " --dry-run Preview setup actions without making changes\n", + " -h, --help Show this help message\n", +); + +fn is_help_requested(args: &[String]) -> bool { + args.iter().any(|arg| arg == "--help" || arg == "-h") +} + /// Run the full setup flow. Orchestrates all steps and outputs JSON summary. pub async fn run_setup(args: &[String]) -> Result<(), GwsError> { + if is_help_requested(args) { + println!("{SETUP_USAGE}"); + return Ok(()); + } + let opts = parse_setup_args(args); let dry_run = opts.dry_run; let interactive = std::io::IsTerminal::is_terminal(&std::io::stdin()) && !dry_run; @@ -1678,6 +1695,24 @@ mod tests { assert_eq!(opts.project.as_deref(), Some("p")); } + #[test] + fn test_is_help_requested_true_for_long_flag() { + let args: Vec = vec!["--help".into()]; + assert!(is_help_requested(&args)); + } + + #[test] + fn test_is_help_requested_true_for_short_flag() { + let args: Vec = vec!["--project".into(), "my-project".into(), "-h".into()]; + assert!(is_help_requested(&args)); + } + + #[test] + fn test_is_help_requested_false_without_help_flags() { + let args: Vec = vec!["--project".into(), "my-project".into()]; + assert!(!is_help_requested(&args)); + } + // ── Account selection → gcloud action ─────────────────────── #[test]