Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions cmd/src/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/cmderrors"
"github.com/sourcegraph/src-cli/internal/keyring"
"github.com/sourcegraph/src-cli/internal/oauthdevice"
)

Expand Down Expand Up @@ -125,6 +126,13 @@ func loginCmd(ctx context.Context, p loginParams) error {
noToken := cfg.AccessToken == ""
endpointConflict := endpointArg != cfg.Endpoint

secretStore, err := keyring.Open()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you only open this if doing deviceflow. If we are not doing deviceflow don't open secret storage. IE avoid interacting with secret storage unless we actually need to.

Maybe you can add a wrapper around secretstore which lazily opens?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stop looking over my shoulder :P busy addressing this atm 😄

if err != nil {
printProblem(fmt.Sprintf("could not open keyring for secret storage: %s", err))
}

cfg.Endpoint = endpointArg

if p.useDeviceFlow {
token, err := runDeviceFlow(ctx, endpointArg, out, p.deviceFlowClient)
if err != nil {
Expand All @@ -133,8 +141,11 @@ func loginCmd(ctx context.Context, p loginParams) error {
return cmderrors.ExitCode1
}

cfg.AccessToken = token
cfg.Endpoint = endpointArg
if err := oauthdevice.StoreToken(secretStore, token); err != nil {
printProblem(fmt.Sprintf("Failed to store token in keyring store: %s", err))
return cmderrors.ExitCode1
}

client = cfg.apiClient(p.apiFlags, out)
} else if noToken || endpointConflict {
fmt.Fprintln(out)
Expand Down Expand Up @@ -184,10 +195,10 @@ func loginCmd(ctx context.Context, p loginParams) error {
return nil
}

func runDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauthdevice.Client) (string, error) {
func runDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauthdevice.Client) (*oauthdevice.Token, error) {
authResp, err := client.Start(ctx, endpoint, nil)
if err != nil {
return "", err
return nil, err
}

fmt.Fprintln(out)
Expand All @@ -205,10 +216,10 @@ func runDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client o
interval = 5 * time.Second
}

tokenResp, err := client.Poll(ctx, endpoint, authResp.DeviceCode, interval, authResp.ExpiresIn)
resp, err := client.Poll(ctx, endpoint, authResp.DeviceCode, interval, authResp.ExpiresIn)
if err != nil {
return "", err
return nil, err
}

return tokenResp.AccessToken, nil
return resp.Token(endpoint), nil
}
16 changes: 14 additions & 2 deletions cmd/src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/sourcegraph/sourcegraph/lib/errors"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/keyring"
"github.com/sourcegraph/src-cli/internal/oauthdevice"
)

const usageText = `src is a tool that provides access to Sourcegraph instances.
Expand Down Expand Up @@ -123,15 +125,25 @@ type config struct {

// apiClient returns an api.Client built from the configuration.
func (c *config) apiClient(flags *api.Flags, out io.Writer) api.Client {
return api.NewClient(api.ClientOpts{
opts := api.ClientOpts{
Endpoint: c.Endpoint,
AccessToken: c.AccessToken,
AdditionalHeaders: c.AdditionalHeaders,
Flags: flags,
Out: out,
ProxyURL: c.ProxyURL,
ProxyPath: c.ProxyPath,
})
}
store, err := keyring.Open()
if err != nil {
panic("HALP")
}

if t, err := oauthdevice.LoadToken(store, c.Endpoint); err == nil {
opts.OAuthToken = t
}

return api.NewClient(opts)
}

// readConfig reads the config file from the given path.
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ require (
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect
Expand All @@ -64,26 +66,30 @@ require (
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v24.0.4+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v25.0.6+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-containerregistry v0.15.2 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
Expand All @@ -92,6 +98,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6Q
cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
Expand Down Expand Up @@ -138,6 +142,8 @@ github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglD
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand All @@ -164,6 +170,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
Expand Down Expand Up @@ -211,6 +219,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
Expand Down Expand Up @@ -254,6 +264,8 @@ github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7E
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hexops/autogold v0.8.1/go.mod h1:97HLDXyG23akzAoRYJh/2OBs3kd80eHyKPvZw0S5ZBY=
github.com/hexops/autogold v1.3.1 h1:YgxF9OHWbEIUjhDbpnLhgVsjUDsiHDTyDfy2lrfdlzo=
github.com/hexops/autogold v1.3.1/go.mod h1:sQO+mQUCVfxOKPht+ipDSkJ2SCJ7BNJVHZexsXqWMx4=
Expand Down Expand Up @@ -353,6 +365,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
Expand Down
10 changes: 10 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"github.com/kballard/go-shellquote"
"github.com/mattn/go-isatty"

"github.com/sourcegraph/src-cli/internal/oauthdevice"
"github.com/sourcegraph/src-cli/internal/version"
)

Expand Down Expand Up @@ -85,6 +86,8 @@

ProxyURL *url.URL
ProxyPath string

OAuthToken *oauthdevice.Token
}

func buildTransport(opts ClientOpts, flags *Flags) *http.Transport {
Expand All @@ -102,6 +105,13 @@
transport = withProxyTransport(transport, opts.ProxyURL, opts.ProxyPath)
}

if opt.AccessToken == "" && opt.OAuthToken != nil {
transport = &oauthdevice.Transport{
Base: transport,
Token: opts.OAuthToken

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / go-lint

missing ',' before newline in composite literal (typecheck)

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / go-lint

syntax error: unexpected newline in composite literal; possibly missing comma or } (typecheck)

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / go-test (macos-latest)

syntax error: unexpected newline in composite literal; possibly missing comma or }

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / go-test (ubuntu-latest)

syntax error: unexpected newline in composite literal; possibly missing comma or }

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / go-test (windows-latest)

syntax error: unexpected newline in composite literal; possibly missing comma or }

Check failure on line 111 in internal/api/api.go

View workflow job for this annotation

GitHub Actions / scip-go

syntax error: unexpected newline in composite literal; possibly missing comma or }
}
}

return transport
}

Expand Down
62 changes: 62 additions & 0 deletions internal/keyring/keyring.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Package keyring provides secure credential storage using the system keychain.
package keyring

import (
"github.com/99designs/keyring"
"github.com/sourcegraph/sourcegraph/lib/errors"
)

const serviceName = "sourcegraph-cli"

// Store provides secure credential storage operations.
type Store struct {
ring keyring.Keyring
}

// Open opens the system keyring for the Sourcegraph CLI.
func Open() (*Store, error) {
ring, err := keyring.Open(keyring.Config{
ServiceName: serviceName,
KeychainName: "login", // This is the default name for the keychain where MacOS puts all login passwords
KeychainTrustApplication: true, // the keychain can trust src-cli!
})
if err != nil {
return nil, errors.Wrap(err, "opening keyring")
}
return &Store{ring: ring}, nil
}

// Set stores a key-value pair in the keyring.
func (s *Store) Set(key string, data []byte) error {
err := s.ring.Set(keyring.Item{
Key: key,
Data: data,
Label: key,
})
if err != nil {
return errors.Wrap(err, "storing item in keyring")
}
return nil
}

// Get retrieves a value by key from the keyring.
// Returns nil, nil if the key is not found.
func (s *Store) Get(key string) ([]byte, error) {
item, err := s.ring.Get(key)
if err != nil {
if err == keyring.ErrKeyNotFound {
return nil, nil
}
return nil, errors.Wrap(err, "getting item from keyring")
}
return item.Data, nil
}

// Delete removes a key from the keyring.
func (s *Store) Delete(key string) error {
err := s.ring.Remove(key)
if err != nil && err != keyring.ErrKeyNotFound {
return errors.Wrap(err, "removing item from keyring")
}
return nil
}
Loading
Loading