Skip to content

Commit 04c5a82

Browse files
aprimakinaclaude
andcommitted
feat: send CLI User-Agent on the OAuth token exchange
The login token exchange used oauth2's default HTTP client, so it sent no tiger-cli User-Agent — leaving the gateway to record the session's device_name as the Go default. Route the exchange through a client that sets the CLI User-Agent, and enrich it with OS/arch (tiger-cli/<version> (os/arch)) via a shared config.UserAgent() helper reused by the API client. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 555ad3a commit 04c5a82

4 files changed

Lines changed: 28 additions & 4 deletions

File tree

internal/tiger/api/client_util.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func NewTigerClient(cfg *config.Config, apiKey string) (*ClientWithResponses, er
4444
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(apiKey))
4545
client, err := NewClientWithResponses(cfg.APIURL, WithHTTPClient(getHTTPClient()), WithRequestEditorFn(func(_ context.Context, req *http.Request) error {
4646
req.Header.Set("Authorization", authHeader)
47-
req.Header.Set("User-Agent", fmt.Sprintf("tiger-cli/%s", config.Version))
47+
req.Header.Set("User-Agent", config.UserAgent())
4848
return nil
4949
}))
5050
if err != nil {
@@ -82,7 +82,7 @@ func NewTigerClientWithToken(cfg *config.Config, token *oauth2.Token, persist fu
8282
httpClient.Timeout = 30 * time.Second
8383

8484
client, err := NewClientWithResponses(cfg.APIURL, WithHTTPClient(httpClient), WithRequestEditorFn(func(_ context.Context, req *http.Request) error {
85-
req.Header.Set("User-Agent", fmt.Sprintf("tiger-cli/%s", config.Version))
85+
req.Header.Set("User-Agent", config.UserAgent())
8686
return nil
8787
}))
8888
if err != nil {

internal/tiger/cmd/auth_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ func startMockOAuthServer(t *testing.T, projects []api.Project) *httptest.Server
232232
http.Error(w, "Missing required parameters", http.StatusBadRequest)
233233
return
234234
}
235+
// Exchange must carry the CLI User-Agent.
236+
if ua := r.Header.Get("User-Agent"); !strings.HasPrefix(ua, "tiger-cli/") {
237+
t.Errorf("code exchange User-Agent = %q, want \"tiger-cli/\" prefix", ua)
238+
}
235239
}
236240

237241
tokenResponse := map[string]interface{}{

internal/tiger/cmd/oauth.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ type oauthCallback struct {
167167
resultChan chan<- oauthResult
168168
}
169169

170+
// userAgentTransport sets the CLI User-Agent on outgoing requests.
171+
type userAgentTransport struct{ base http.RoundTripper }
172+
173+
func (t *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
174+
req.Header.Set("User-Agent", config.UserAgent())
175+
return t.base.RoundTrip(req)
176+
}
177+
170178
func (c *oauthCallback) ServeHTTP(w http.ResponseWriter, r *http.Request) {
171179
query := r.URL.Query()
172180

@@ -188,8 +196,10 @@ func (c *oauthCallback) ServeHTTP(w http.ResponseWriter, r *http.Request) {
188196
return
189197
}
190198

191-
// Exchange authorization code for tokens
192-
token, err := c.oauthCfg.Exchange(r.Context(), code, oauth2.VerifierOption(c.codeVerifier))
199+
// Set the CLI User-Agent on the exchange so it's recorded as the session's user_agent.
200+
ctx := context.WithValue(r.Context(), oauth2.HTTPClient,
201+
&http.Client{Transport: &userAgentTransport{base: http.DefaultTransport}})
202+
token, err := c.oauthCfg.Exchange(ctx, code, oauth2.VerifierOption(c.codeVerifier))
193203
if err != nil {
194204
w.WriteHeader(http.StatusInternalServerError)
195205
fmt.Fprintf(w, "Failed to exchange authorization code for tokens")

internal/tiger/config/version.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
package config
22

3+
import (
4+
"fmt"
5+
"runtime"
6+
)
7+
38
// These variables are set at build time via ldflags in the GoReleaser pipeline
49
// for production releases. Default values are used for local development builds.
510
var Version = "dev"
611
var BuildTime = "unknown"
712
var GitCommit = "unknown"
13+
14+
// UserAgent returns the User-Agent the CLI sends on HTTP requests.
15+
func UserAgent() string {
16+
return fmt.Sprintf("tiger-cli/%s (%s/%s)", Version, runtime.GOOS, runtime.GOARCH)
17+
}

0 commit comments

Comments
 (0)