Skip to content

Commit 0bc535e

Browse files
authored
feat(oauth): use oauth flow to authenticate with predefined src-cli OAuth client (#1223)
* removed unused func * add refresh token to device response unmarshall * make NewClient take ClientID as param * add oauth flow and use oauth token when SRC_ACCESS_TOKEN is empty * 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 fcfad36 commit 0bc535e

File tree

12 files changed

+1600
-26
lines changed

12 files changed

+1600
-26
lines changed

cmd/src/login.go

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"os/exec"
10+
"runtime"
911
"strings"
12+
"time"
1013

1114
"github.com/sourcegraph/src-cli/internal/api"
1215
"github.com/sourcegraph/src-cli/internal/cmderrors"
16+
"github.com/sourcegraph/src-cli/internal/oauth"
1317
)
1418

1519
func init() {
1620
usage := `'src login' helps you authenticate 'src' to access a Sourcegraph instance with your user credentials.
1721
1822
Usage:
1923
20-
src login SOURCEGRAPH_URL
24+
src login [flags] SOURCEGRAPH_URL
2125
2226
Examples:
2327
@@ -28,6 +32,15 @@ Examples:
2832
Authenticate to Sourcegraph.com:
2933
3034
$ src login https://sourcegraph.com
35+
36+
Use OAuth device flow to authenticate:
37+
38+
$ src login --oauth https://sourcegraph.com
39+
40+
41+
Override the default client id used during device flow when authenticating:
42+
43+
$ src login --oauth https://sourcegraph.com
3144
`
3245

3346
flagSet := flag.NewFlagSet("login", flag.ExitOnError)
@@ -38,6 +51,7 @@ Examples:
3851

3952
var (
4053
apiFlags = api.NewFlags(flagSet)
54+
useOAuth = flagSet.Bool("oauth", false, "Use OAuth device flow to obtain an access token interactively")
4155
)
4256

4357
handler := func(args []string) error {
@@ -54,7 +68,15 @@ Examples:
5468

5569
client := cfg.apiClient(apiFlags, io.Discard)
5670

57-
return loginCmd(context.Background(), cfg, client, endpoint, os.Stdout)
71+
return loginCmd(context.Background(), loginParams{
72+
cfg: cfg,
73+
client: client,
74+
endpoint: endpoint,
75+
out: os.Stdout,
76+
useOAuth: *useOAuth,
77+
apiFlags: apiFlags,
78+
deviceFlowClient: oauth.NewClient(oauth.DefaultClientID),
79+
})
5880
}
5981

6082
commands = append(commands, &command{
@@ -64,8 +86,21 @@ Examples:
6486
})
6587
}
6688

67-
func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg string, out io.Writer) error {
68-
endpointArg = cleanEndpoint(endpointArg)
89+
type loginParams struct {
90+
cfg *config
91+
client api.Client
92+
endpoint string
93+
out io.Writer
94+
useOAuth bool
95+
apiFlags *api.Flags
96+
deviceFlowClient oauth.Client
97+
}
98+
99+
func loginCmd(ctx context.Context, p loginParams) error {
100+
endpointArg := cleanEndpoint(p.endpoint)
101+
cfg := p.cfg
102+
client := p.client
103+
out := p.out
69104

70105
printProblem := func(problem string) {
71106
fmt.Fprintf(out, "❌ Problem: %s\n", problem)
@@ -77,7 +112,9 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
77112
export SRC_ACCESS_TOKEN=(your access token)
78113
79114
To verify that it's working, run the login command again.
80-
`, endpointArg, endpointArg)
115+
116+
Alternatively, you can try logging in using OAuth by running: src login --oauth %s
117+
`, endpointArg, endpointArg, endpointArg)
81118

82119
if cfg.ConfigFilePath != "" {
83120
fmt.Fprintln(out)
@@ -86,7 +123,7 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
86123

87124
noToken := cfg.AccessToken == ""
88125
endpointConflict := endpointArg != cfg.Endpoint
89-
if noToken || endpointConflict {
126+
if !p.useOAuth && (noToken || endpointConflict) {
90127
fmt.Fprintln(out)
91128
switch {
92129
case noToken:
@@ -98,6 +135,30 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
98135
return cmderrors.ExitCode1
99136
}
100137

138+
if p.useOAuth {
139+
token, err := runOAuthDeviceFlow(ctx, endpointArg, out, p.deviceFlowClient)
140+
if err != nil {
141+
printProblem(fmt.Sprintf("OAuth Device flow authentication failed: %s", err))
142+
fmt.Fprintln(out, createAccessTokenMessage)
143+
return cmderrors.ExitCode1
144+
}
145+
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)
149+
}
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+
})
160+
}
161+
101162
// See if the user is already authenticated.
102163
query := `query CurrentUser { currentUser { username } }`
103164
var result struct {
@@ -122,6 +183,65 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
122183
}
123184
fmt.Fprintln(out)
124185
fmt.Fprintf(out, "✔️ Authenticated as %s on %s\n", result.CurrentUser.Username, endpointArg)
186+
187+
if p.useOAuth {
188+
fmt.Fprintln(out)
189+
fmt.Fprintf(out, "Authenticated with OAuth credentials")
190+
}
191+
125192
fmt.Fprintln(out)
126193
return nil
127194
}
195+
196+
func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauth.Client) (*oauth.Token, error) {
197+
authResp, err := client.Start(ctx, endpoint, nil)
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
authURL := authResp.VerificationURIComplete
203+
msg := fmt.Sprintf("If your browser did not open automatically, visit %s.", authURL)
204+
if authURL == "" {
205+
authURL = authResp.VerificationURI
206+
msg = fmt.Sprintf("If your browser did not open automatically, visit %s and enter the user code %s", authURL, authResp.DeviceCode)
207+
}
208+
_ = openInBrowser(authURL)
209+
fmt.Fprintln(out)
210+
fmt.Fprint(out, msg)
211+
212+
fmt.Fprintln(out)
213+
fmt.Fprint(out, "Waiting for authorization...")
214+
defer fmt.Fprintf(out, "DONE\n\n")
215+
216+
interval := time.Duration(authResp.Interval) * time.Second
217+
if interval <= 0 {
218+
interval = 5 * time.Second
219+
}
220+
221+
resp, err := client.Poll(ctx, endpoint, authResp.DeviceCode, interval, authResp.ExpiresIn)
222+
if err != nil {
223+
return nil, err
224+
}
225+
226+
token := resp.Token(endpoint)
227+
token.ClientID = client.ClientID()
228+
return token, nil
229+
}
230+
231+
func openInBrowser(url string) error {
232+
if url == "" {
233+
return nil
234+
}
235+
236+
var cmd *exec.Cmd
237+
switch runtime.GOOS {
238+
case "darwin":
239+
cmd = exec.Command("open", url)
240+
case "windows":
241+
// "start" is a cmd.exe built-in; the empty string is the window title.
242+
cmd = exec.Command("cmd", "/c", "start", "", url)
243+
default:
244+
cmd = exec.Command("xdg-open", url)
245+
}
246+
return cmd.Run()
247+
}

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(), cfg, cfg.apiClient(nil, io.Discard), endpointArg, &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=

0 commit comments

Comments
 (0)