Skip to content

Commit e2cb620

Browse files
authored
feat(oauth): Add refresh to oauthdevice.Client (#1227)
* add refresh to oauthdevice.Client * oauthdevice: add RefreshToken field and Refresh method * feat(oauth): Use keyring to store oauth token (#1228) * add refresh to oauthdevice.Client * add OAuth Transport and use it if no access token * secrets: switch to zalando/go-keyring and add context support * secrets: scope keyring by endpoint
1 parent 61a1070 commit e2cb620

File tree

12 files changed

+704
-52
lines changed

12 files changed

+704
-52
lines changed

cmd/src/login.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ func loginCmd(ctx context.Context, p loginParams) error {
112112
export SRC_ACCESS_TOKEN=(your access token)
113113
114114
To verify that it's working, run the login command again.
115-
`, endpointArg, endpointArg)
115+
116+
Alternatively, you can try logging in using OAuth by running: src login --oauth %s
117+
`, endpointArg, endpointArg, endpointArg)
116118

117119
if cfg.ConfigFilePath != "" {
118120
fmt.Fprintln(out)
@@ -121,6 +123,17 @@ func loginCmd(ctx context.Context, p loginParams) error {
121123

122124
noToken := cfg.AccessToken == ""
123125
endpointConflict := endpointArg != cfg.Endpoint
126+
if !p.useOAuth && (noToken || endpointConflict) {
127+
fmt.Fprintln(out)
128+
switch {
129+
case noToken:
130+
printProblem("No access token is configured.")
131+
case endpointConflict:
132+
printProblem(fmt.Sprintf("The configured endpoint is %s, not %s.", cfg.Endpoint, endpointArg))
133+
}
134+
fmt.Fprintln(out, createAccessTokenMessage)
135+
return cmderrors.ExitCode1
136+
}
124137

125138
if p.useOAuth {
126139
token, err := runOAuthDeviceFlow(ctx, endpointArg, out, p.deviceFlowClient)
@@ -130,19 +143,20 @@ func loginCmd(ctx context.Context, p loginParams) error {
130143
return cmderrors.ExitCode1
131144
}
132145

133-
cfg.AccessToken = token
134-
cfg.Endpoint = endpointArg
135-
client = cfg.apiClient(p.apiFlags, out)
136-
} else if noToken || endpointConflict {
137-
fmt.Fprintln(out)
138-
switch {
139-
case noToken:
140-
printProblem("No access token is configured.")
141-
case endpointConflict:
142-
printProblem(fmt.Sprintf("The configured endpoint is %s, not %s.", cfg.Endpoint, endpointArg))
146+
if err := oauth.StoreToken(ctx, token); err != nil {
147+
fmt.Fprintln(out)
148+
fmt.Fprintf(out, "⚠️ Warning: Failed to store token in keyring store: %q. Continuing with this session only.\n", err)
143149
}
144-
fmt.Fprintln(out, createAccessTokenMessage)
145-
return cmderrors.ExitCode1
150+
151+
client = api.NewClient(api.ClientOpts{
152+
Endpoint: cfg.Endpoint,
153+
AdditionalHeaders: cfg.AdditionalHeaders,
154+
Flags: p.apiFlags,
155+
Out: out,
156+
ProxyURL: cfg.ProxyURL,
157+
ProxyPath: cfg.ProxyPath,
158+
OAuthToken: token,
159+
})
146160
}
147161

148162
// See if the user is already authenticated.
@@ -179,10 +193,10 @@ func loginCmd(ctx context.Context, p loginParams) error {
179193
return nil
180194
}
181195

182-
func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauth.Client) (string, error) {
196+
func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauth.Client) (*oauth.Token, error) {
183197
authResp, err := client.Start(ctx, endpoint, nil)
184198
if err != nil {
185-
return "", err
199+
return nil, err
186200
}
187201

188202
authURL := authResp.VerificationURIComplete
@@ -204,12 +218,14 @@ func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, cli
204218
interval = 5 * time.Second
205219
}
206220

207-
tokenResp, err := client.Poll(ctx, endpoint, authResp.DeviceCode, interval, authResp.ExpiresIn)
221+
resp, err := client.Poll(ctx, endpoint, authResp.DeviceCode, interval, authResp.ExpiresIn)
208222
if err != nil {
209-
return "", err
223+
return nil, err
210224
}
211225

212-
return tokenResp.AccessToken, nil
226+
token := resp.Token(endpoint)
227+
token.ClientID = client.ClientID()
228+
return token, nil
213229
}
214230

215231
func openInBrowser(url string) error {

cmd/src/login_test.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@ import (
1111
"testing"
1212

1313
"github.com/sourcegraph/src-cli/internal/cmderrors"
14+
"github.com/sourcegraph/src-cli/internal/oauth"
1415
)
1516

1617
func TestLogin(t *testing.T) {
1718
check := func(t *testing.T, cfg *config, endpointArg string) (output string, err error) {
1819
t.Helper()
1920

2021
var out bytes.Buffer
21-
err = loginCmd(context.Background(), loginParams{cfg: cfg, client: cfg.apiClient(nil, io.Discard), endpoint: endpointArg, out: &out})
22+
err = loginCmd(context.Background(), loginParams{
23+
cfg: cfg,
24+
client: cfg.apiClient(nil, io.Discard),
25+
endpoint: endpointArg,
26+
out: &out,
27+
deviceFlowClient: oauth.NewClient(oauth.DefaultClientID),
28+
})
2229
return strings.TrimSpace(out.String()), err
2330
}
2431

@@ -27,7 +34,7 @@ func TestLogin(t *testing.T) {
2734
if err != cmderrors.ExitCode1 {
2835
t.Fatal(err)
2936
}
30-
wantOut := "❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://sourcegraph.example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://sourcegraph.example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again."
37+
wantOut := "❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://sourcegraph.example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://sourcegraph.example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n Alternatively, you can try logging in using OAuth by running: src login --oauth https://sourcegraph.example.com"
3138
if out != wantOut {
3239
t.Errorf("got output %q, want %q", out, wantOut)
3340
}
@@ -38,7 +45,7 @@ func TestLogin(t *testing.T) {
3845
if err != cmderrors.ExitCode1 {
3946
t.Fatal(err)
4047
}
41-
wantOut := "❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://sourcegraph.example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://sourcegraph.example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again."
48+
wantOut := "❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://sourcegraph.example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://sourcegraph.example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n Alternatively, you can try logging in using OAuth by running: src login --oauth https://sourcegraph.example.com"
4249
if out != wantOut {
4350
t.Errorf("got output %q, want %q", out, wantOut)
4451
}
@@ -49,7 +56,7 @@ func TestLogin(t *testing.T) {
4956
if err != cmderrors.ExitCode1 {
5057
t.Fatal(err)
5158
}
52-
wantOut := "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove f. See https://github.com/sourcegraph/src-cli#readme for more information.\n\n❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again."
59+
wantOut := "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove f. See https://github.com/sourcegraph/src-cli#readme for more information.\n\n❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n Alternatively, you can try logging in using OAuth by running: src login --oauth https://example.com"
5360
if out != wantOut {
5461
t.Errorf("got output %q, want %q", out, wantOut)
5562
}
@@ -67,7 +74,7 @@ func TestLogin(t *testing.T) {
6774
if err != cmderrors.ExitCode1 {
6875
t.Fatal(err)
6976
}
70-
wantOut := "❌ Problem: Invalid access token.\n\n🛠 To fix: Create an access token by going to $ENDPOINT/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=$ENDPOINT\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n (If you need to supply custom HTTP request headers, see information about SRC_HEADER_* and SRC_HEADERS env vars at https://github.com/sourcegraph/src-cli/blob/main/AUTH_PROXY.md)"
77+
wantOut := "❌ Problem: Invalid access token.\n\n🛠 To fix: Create an access token by going to $ENDPOINT/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=$ENDPOINT\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n Alternatively, you can try logging in using OAuth by running: src login --oauth $ENDPOINT\n\n (If you need to supply custom HTTP request headers, see information about SRC_HEADER_* and SRC_HEADERS env vars at https://github.com/sourcegraph/src-cli/blob/main/AUTH_PROXY.md)"
7178
wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", endpoint)
7279
if out != wantOut {
7380
t.Errorf("got output %q, want %q", out, wantOut)

cmd/src/main.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/json"
56
"flag"
67
"io"
@@ -15,6 +16,7 @@ import (
1516
"github.com/sourcegraph/sourcegraph/lib/errors"
1617

1718
"github.com/sourcegraph/src-cli/internal/api"
19+
"github.com/sourcegraph/src-cli/internal/oauth"
1820
)
1921

2022
const usageText = `src is a tool that provides access to Sourcegraph instances.
@@ -122,15 +124,24 @@ type config struct {
122124

123125
// apiClient returns an api.Client built from the configuration.
124126
func (c *config) apiClient(flags *api.Flags, out io.Writer) api.Client {
125-
return api.NewClient(api.ClientOpts{
127+
opts := api.ClientOpts{
126128
Endpoint: c.Endpoint,
127129
AccessToken: c.AccessToken,
128130
AdditionalHeaders: c.AdditionalHeaders,
129131
Flags: flags,
130132
Out: out,
131133
ProxyURL: c.ProxyURL,
132134
ProxyPath: c.ProxyPath,
133-
})
135+
}
136+
137+
// Only use OAuth if we do not have SRC_ACCESS_TOKEN set
138+
if c.AccessToken == "" {
139+
if t, err := oauth.LoadToken(context.Background(), c.Endpoint); err == nil {
140+
opts.OAuthToken = t
141+
}
142+
}
143+
144+
return api.NewClient(opts)
134145
}
135146

136147
// readConfig reads the config file from the given path.

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ require (
3030
github.com/sourcegraph/sourcegraph/lib v0.0.0-20240709083501-1af563b61442
3131
github.com/stretchr/testify v1.11.1
3232
github.com/tliron/glsp v0.2.2
33+
github.com/zalando/go-keyring v0.2.6
3334
golang.org/x/sync v0.18.0
3435
google.golang.org/api v0.256.0
3536
google.golang.org/protobuf v1.36.10
@@ -41,6 +42,7 @@ require (
4142
)
4243

4344
require (
45+
al.essio.dev/pkg/shellescape v1.5.1 // indirect
4446
cel.dev/expr v0.24.0 // indirect
4547
cloud.google.com/go/auth v0.17.0 // indirect
4648
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -64,6 +66,7 @@ require (
6466
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
6567
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
6668
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
69+
github.com/danieljoos/wincred v1.2.2 // indirect
6770
github.com/distribution/reference v0.6.0 // indirect
6871
github.com/docker/cli v24.0.4+incompatible // indirect
6972
github.com/docker/distribution v2.8.2+incompatible // indirect
@@ -78,6 +81,7 @@ require (
7881
github.com/go-chi/chi/v5 v5.2.2 // indirect
7982
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
8083
github.com/go-logr/stdr v1.2.2 // indirect
84+
github.com/godbus/dbus/v5 v5.1.0 // indirect
8185
github.com/gofrs/uuid/v5 v5.0.0 // indirect
8286
github.com/google/gnostic-models v0.6.9 // indirect
8387
github.com/google/go-containerregistry v0.19.1 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
2+
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
13
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
24
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
35
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
@@ -139,6 +141,8 @@ github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglD
139141
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
140142
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
141143
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
144+
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
145+
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
142146
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
143147
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
144148
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -212,6 +216,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
212216
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
213217
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
214218
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
219+
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
220+
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
215221
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
216222
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
217223
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
@@ -243,6 +249,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
243249
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
244250
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
245251
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
252+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
253+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
246254
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
247255
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
248256
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
@@ -495,6 +503,8 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
495503
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
496504
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
497505
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
506+
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
507+
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
498508
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
499509
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
500510
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=

internal/api/api.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/kballard/go-shellquote"
1919
"github.com/mattn/go-isatty"
2020

21+
"github.com/sourcegraph/src-cli/internal/oauth"
2122
"github.com/sourcegraph/src-cli/internal/version"
2223
)
2324

@@ -85,21 +86,35 @@ type ClientOpts struct {
8586

8687
ProxyURL *url.URL
8788
ProxyPath string
89+
90+
OAuthToken *oauth.Token
8891
}
8992

90-
func buildTransport(opts ClientOpts, flags *Flags) *http.Transport {
91-
transport := http.DefaultTransport.(*http.Transport).Clone()
93+
func buildTransport(opts ClientOpts, flags *Flags) http.RoundTripper {
94+
var transport http.RoundTripper
95+
{
96+
tp := http.DefaultTransport.(*http.Transport).Clone()
9297

93-
if flags.insecureSkipVerify != nil && *flags.insecureSkipVerify {
94-
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
95-
}
98+
if flags.insecureSkipVerify != nil && *flags.insecureSkipVerify {
99+
tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
100+
}
96101

97-
if transport.TLSClientConfig == nil {
98-
transport.TLSClientConfig = &tls.Config{}
102+
if tp.TLSClientConfig == nil {
103+
tp.TLSClientConfig = &tls.Config{}
104+
}
105+
106+
if opts.ProxyURL != nil || opts.ProxyPath != "" {
107+
tp = withProxyTransport(tp, opts.ProxyURL, opts.ProxyPath)
108+
}
109+
110+
transport = tp
99111
}
100112

101-
if opts.ProxyURL != nil || opts.ProxyPath != "" {
102-
transport = withProxyTransport(transport, opts.ProxyURL, opts.ProxyPath)
113+
if opts.AccessToken == "" && opts.OAuthToken != nil {
114+
transport = &oauth.Transport{
115+
Base: transport,
116+
Token: opts.OAuthToken,
117+
}
103118
}
104119

105120
return transport
@@ -168,6 +183,7 @@ func (c *client) createHTTPRequest(ctx context.Context, method, p string, body i
168183
} else {
169184
req.Header.Set("User-Agent", "src-cli/"+version.BuildTag)
170185
}
186+
171187
if c.opts.AccessToken != "" {
172188
req.Header.Set("Authorization", "token "+c.opts.AccessToken)
173189
}
@@ -249,10 +265,20 @@ func (r *request) do(ctx context.Context, result any) (bool, error) {
249265
// confirm the status code. You can test this easily with e.g. an invalid
250266
// endpoint like -endpoint=https://google.com
251267
if resp.StatusCode != http.StatusOK {
252-
if resp.StatusCode == http.StatusUnauthorized && isatty.IsCygwinTerminal(os.Stdout.Fd()) {
253-
fmt.Println("You may need to specify or update your access token to use this endpoint.")
254-
fmt.Println("See https://github.com/sourcegraph/src-cli#readme")
255-
fmt.Println("")
268+
if resp.StatusCode == http.StatusUnauthorized {
269+
if oauth.IsOAuthTransport(r.client.httpClient.Transport) {
270+
fmt.Println("The OAuth token is invalid. Please check that the Sourcegraph CLI client is still authorized.")
271+
fmt.Println("")
272+
fmt.Printf("To re-authorize, run: src login --oauth %s\n", r.client.opts.Endpoint)
273+
fmt.Println("")
274+
fmt.Println("Learn more at https://github.com/sourcegraph/src-cli#readme")
275+
fmt.Println("")
276+
}
277+
if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
278+
fmt.Println("You may need to specify or update your access token to use this endpoint.")
279+
fmt.Println("See https://github.com/sourcegraph/src-cli#readme")
280+
fmt.Println("")
281+
}
256282
}
257283
body, err := io.ReadAll(resp.Body)
258284
if err != nil {

0 commit comments

Comments
 (0)