Compare commits

...

3 Commits

Author SHA1 Message Date
Peter Steinberger
a57f0e8cac fix(auth): polish remote replay flags (#427) (thanks @doodaaatimmy-creator)
Some checks failed
ci / test (push) Has been cancelled
ci / worker (push) Has been cancelled
ci / windows (push) Has been cancelled
ci / darwin-cgo-build (push) Has been cancelled
2026-03-07 22:46:34 +00:00
doodaaatimmy-creator
a59086de90 docs(changelog): note remote auth replay fix 2026-03-07 22:44:51 +00:00
doodaaatimmy-creator
4bd27d0d51 fix(auth): preserve remote step 2 replay flags 2026-03-07 22:44:32 +00:00
3 changed files with 36 additions and 3 deletions

View File

@ -24,6 +24,7 @@
- Auth: persist rotated OAuth refresh tokens returned during API calls so later commands keep working without re-auth. (#373) — thanks @joshp123. - Auth: persist rotated OAuth refresh tokens returned during API calls so later commands keep working without re-auth. (#373) — thanks @joshp123.
- Groups: include required label filters in transitive group searches so `groups list` doesnt 400 on Cloud Identity. (#315) — thanks @salmonumbrella. - Groups: include required label filters in transitive group searches so `groups list` doesnt 400 on Cloud Identity. (#315) — thanks @salmonumbrella.
- Gmail: fall back to `MimeType` charset hints when `Content-Type` headers are missing so GBK/GB2312 message bodies decode correctly. (#428) — thanks @WinnCook. - Gmail: fall back to `MimeType` charset hints when `Content-Type` headers are missing so GBK/GB2312 message bodies decode correctly. (#428) — thanks @WinnCook.
- Auth: preserve scope-shaping flags in the remote step-2 replay guidance for `auth add --remote`. (#427) — thanks @doodaaatimmy-creator.
- Gmail: add a fetch delay in `watch serve` so History API reads don't race message indexing. (#397) — thanks @salmonumbrella. - Gmail: add a fetch delay in `watch serve` so History API reads don't race message indexing. (#397) — thanks @salmonumbrella.
- Gmail: allow Workspace-managed send-as aliases with empty verification status in `send` and `drafts create`. (#407) — thanks @salmonumbrella. - Gmail: allow Workspace-managed send-as aliases with empty verification status in `send` and `drafts create`. (#407) — thanks @salmonumbrella.
- Gmail: preserve the selected `--client` during `watch serve` push handling instead of falling back to the default client. (#411) — thanks @chrysb. - Gmail: preserve the selected `--client` during `watch serve` push handling instead of falling back to the default client. (#411) — thanks @chrysb.

View File

@ -488,6 +488,32 @@ type AuthAddCmd struct {
GmailScope string `name:"gmail-scope" help:"Gmail scope mode: full|readonly" enum:"full,readonly" default:"full"` GmailScope string `name:"gmail-scope" help:"Gmail scope mode: full|readonly" enum:"full,readonly" default:"full"`
} }
const authScopeFull = "full"
func formatRemoteStep2Instruction(services []googleauth.Service, c *AuthAddCmd) string {
parts := []string{"--remote", "--step", "2", "--auth-url", "<redirect-url>"}
if len(services) > 0 {
serialized := make([]string, 0, len(services))
for _, service := range services {
serialized = append(serialized, string(service))
}
parts = append(parts, "--services", strings.Join(serialized, ","))
}
if c.Readonly {
parts = append(parts, "--readonly")
}
if driveScope := strings.ToLower(strings.TrimSpace(c.DriveScope)); driveScope != "" && driveScope != authScopeFull {
parts = append(parts, "--drive-scope", driveScope)
}
if gmailScope := strings.ToLower(strings.TrimSpace(c.GmailScope)); gmailScope != "" && gmailScope != authScopeFull {
parts = append(parts, "--gmail-scope", gmailScope)
}
if c.ForceConsent {
parts = append(parts, "--force-consent")
}
return strings.Join(parts, " ")
}
func (c *AuthAddCmd) Run(ctx context.Context, flags *RootFlags) error { func (c *AuthAddCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx) u := ui.FromContext(ctx)
@ -570,7 +596,7 @@ func (c *AuthAddCmd) Run(ctx context.Context, flags *RootFlags) error {
} }
u.Out().Printf("auth_url\t%s", result.URL) u.Out().Printf("auth_url\t%s", result.URL)
u.Out().Printf("state_reused\t%t", result.StateReused) u.Out().Printf("state_reused\t%t", result.StateReused)
u.Err().Println("Run again with --remote --step 2 --auth-url <redirect-url>") u.Err().Printf("Run again with the same root flags and %s\n", formatRemoteStep2Instruction(services, c))
return nil return nil
case 2: case 2:
if authCode != "" { if authCode != "" {
@ -1091,7 +1117,8 @@ func (c *AuthKeepCmd) Run(ctx context.Context, _ *RootFlags) error {
return err return err
} }
data, err := os.ReadFile(keyPath) //nolint:gosec // user-provided path //nolint:gosec // user-provided path
data, err := os.ReadFile(keyPath)
if err != nil { if err != nil {
return fmt.Errorf("read service account key: %w", err) return fmt.Errorf("read service account key: %w", err)
} }

View File

@ -626,14 +626,16 @@ func TestAuthAddCmd_RemoteStep1_PrintsAuthURL(t *testing.T) {
return nil return nil
} }
var stderr string
out := captureStdout(t, func() { out := captureStdout(t, func() {
_ = captureStderr(t, func() { stderr = captureStderr(t, func() {
if err := Execute([]string{ if err := Execute([]string{
"auth", "auth",
"add", "add",
"user@example.com", "user@example.com",
"--services", "--services",
"gmail", "gmail",
"--readonly",
"--remote", "--remote",
"--step", "--step",
"1", "1",
@ -652,6 +654,9 @@ func TestAuthAddCmd_RemoteStep1_PrintsAuthURL(t *testing.T) {
if !strings.Contains(out, "state_reused\ttrue") { if !strings.Contains(out, "state_reused\ttrue") {
t.Fatalf("expected state_reused output, got: %q", out) t.Fatalf("expected state_reused output, got: %q", out)
} }
if !strings.Contains(stderr, "Run again with the same root flags and --remote --step 2 --auth-url <redirect-url> --services gmail --readonly") {
t.Fatalf("expected step 2 guidance to preserve replay flags, got: %q", stderr)
}
} }
func TestAuthAddCmd_RemoteStep2_RejectsAuthCode(t *testing.T) { func TestAuthAddCmd_RemoteStep2_RejectsAuthCode(t *testing.T) {