From efffd2dcd7318bc7f5671450a8f13bd06c80eeb9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 00:57:49 +0000 Subject: [PATCH] feat(auth): support redirect-uri in manual flows (#398) (thanks @salmonumbrella) --- CHANGELOG.md | 1 + internal/cmd/auth.go | 3 +++ internal/cmd/auth_add_test.go | 47 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) 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",