diff --git a/CHANGELOG.md b/CHANGELOG.md index 276cb3b..142343c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - Auth: add `--access-token` / `GOG_ACCESS_TOKEN` for direct access-token auth in headless or CI flows, bypassing stored refresh tokens. (#419) — thanks @mmkal. +- Auth: add `auth add --redirect-uri` for manual/remote OAuth flows, so custom callback hosts can be reused across the printed auth URL, state cache, and code exchange. (#398) — thanks @salmonumbrella. - Sheets: add `sheets insert` to insert rows/columns into a sheet. (#203) — thanks @andybergon. - Sheets: add `sheets links` (alias `hyperlinks`) to list cell links from ranges, including rich-text links. (#374) — thanks @omothm. - Sheets: add `sheets create --parent` to place new spreadsheets in a Drive folder. (#424) — thanks @ManManavadaria. diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index 7d7dcc5..941bfac 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -491,6 +491,9 @@ type AuthAddCmd struct { func formatRemoteStep2Instruction(services []googleauth.Service, c *AuthAddCmd) string { parts := []string{"--remote", "--step", "2", "--auth-url", ""} + if redirectURI := strings.TrimSpace(c.RedirectURI); redirectURI != "" { + parts = append(parts, "--redirect-uri", redirectURI) + } if len(services) > 0 { serialized := make([]string, 0, len(services)) for _, service := range services { diff --git a/internal/cmd/auth_add_test.go b/internal/cmd/auth_add_test.go index 52f5ff2..5e37b67 100644 --- a/internal/cmd/auth_add_test.go +++ b/internal/cmd/auth_add_test.go @@ -802,6 +802,53 @@ func TestAuthAddCmd_RemoteStep1_PassesRedirectURI(t *testing.T) { } } +func TestAuthAddCmd_RemoteStep1_ReplaysRedirectURIInGuidance(t *testing.T) { + origManualURL := manualAuthURL + origAuth := authorizeGoogle + origKeychain := ensureKeychainAccess + t.Cleanup(func() { + manualAuthURL = origManualURL + authorizeGoogle = origAuth + ensureKeychainAccess = origKeychain + }) + + manualAuthURL = func(context.Context, googleauth.AuthorizeOptions) (googleauth.ManualAuthURLResult, error) { + return googleauth.ManualAuthURLResult{URL: "https://example.com/auth"}, nil + } + authorizeGoogle = func(context.Context, googleauth.AuthorizeOptions) (string, error) { + t.Fatal("authorizeGoogle should not be called in remote step 1") + return "", nil + } + ensureKeychainAccess = func() error { + t.Fatal("keychain access should not be checked in remote step 1") + return nil + } + + stderr := captureStderr(t, func() { + _ = captureStdout(t, func() { + if err := Execute([]string{ + "auth", + "add", + "user@example.com", + "--services", + "gmail", + "--remote", + "--step", + "1", + "--redirect-uri", + "https://molty2.tail8108.ts.net/oauth2/callback", + }); err != nil { + t.Fatalf("Execute: %v", err) + } + }) + }) + + want := "--remote --step 2 --auth-url --redirect-uri https://molty2.tail8108.ts.net/oauth2/callback --services gmail" + if !strings.Contains(stderr, want) { + t.Fatalf("expected replay guidance %q, got %q", want, stderr) + } +} + func TestAuthAddCmd_RemoteStep2_RejectsAuthCode(t *testing.T) { err := Execute([]string{ "auth",