Skip to content

Commit 0f2e720

Browse files
committed
better error handling
handle oauth discovery failure and set client id on token - use SRC CLI client id as default and handle discovery failures - add clientID flag and set it on the token improve error message and panic in apiClient if no usable token - warn if we fail to store the token on login - panic if apiClient has no accessToken or OAuth token to use
1 parent 0a6bca8 commit 0f2e720

File tree

4 files changed

+48
-18
lines changed

4 files changed

+48
-18
lines changed

cmd/src/login.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Examples:
7474
endpoint: endpoint,
7575
out: os.Stdout,
7676
useOAuth: *useOAuth,
77+
oauthClientID: *OAuthClientID,
7778
apiFlags: apiFlags,
7879
deviceFlowClient: oauth.NewClient(oauth.DefaultClientID),
7980
})
@@ -92,6 +93,7 @@ type loginParams struct {
9293
endpoint string
9394
out io.Writer
9495
useOAuth bool
96+
oauthClientID string
9597
apiFlags *api.Flags
9698
deviceFlowClient oauth.Client
9799
}
@@ -125,19 +127,27 @@ func loginCmd(ctx context.Context, p loginParams) error {
125127
cfg.Endpoint = endpointArg
126128

127129
if p.useOAuth {
128-
token, err := runOAuthDeviceFlow(ctx, endpointArg, out, p.deviceFlowClient)
130+
token, err := runOAuthDeviceFlow(ctx, endpointArg, p.oauthClientID, out, p.deviceFlowClient)
129131
if err != nil {
130132
printProblem(fmt.Sprintf("OAuth Device flow authentication failed: %s", err))
131133
fmt.Fprintln(out, createAccessTokenMessage)
132134
return cmderrors.ExitCode1
133135
}
134136

135137
if err := oauth.StoreToken(ctx, token); err != nil {
136-
printProblem(fmt.Sprintf("Failed to store token in keyring store: %s", err))
137-
return cmderrors.ExitCode1
138+
fmt.Fprintln(out)
139+
fmt.Fprintf(out, "⚠️ Warning: Failed to store token in keyring store: %q. Continuing with this session only.\n", err)
138140
}
139141

140-
client = cfg.apiClient(p.apiFlags, out)
142+
client = api.NewClient(api.ClientOpts{
143+
Endpoint: cfg.Endpoint,
144+
AdditionalHeaders: cfg.AdditionalHeaders,
145+
Flags: p.apiFlags,
146+
Out: out,
147+
ProxyURL: cfg.ProxyURL,
148+
ProxyPath: cfg.ProxyPath,
149+
OAuthToken: token,
150+
})
141151
} else if noToken || endpointConflict {
142152
fmt.Fprintln(out)
143153
switch {
@@ -184,7 +194,7 @@ func loginCmd(ctx context.Context, p loginParams) error {
184194
return nil
185195
}
186196

187-
func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, client oauth.Client) (*oauth.Token, error) {
197+
func runOAuthDeviceFlow(ctx context.Context, endpoint, clientID string, out io.Writer, client oauth.Client) (*oauth.Token, error) {
188198
authResp, err := client.Start(ctx, endpoint, nil)
189199
if err != nil {
190200
return nil, err
@@ -214,7 +224,9 @@ func runOAuthDeviceFlow(ctx context.Context, endpoint string, out io.Writer, cli
214224
return nil, err
215225
}
216226

217-
return resp.Token(endpoint), nil
227+
token := resp.Token(endpoint)
228+
token.ClientID = clientID
229+
return token, nil
218230
}
219231

220232
func openInBrowser(url string) error {

cmd/src/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ func (c *config) apiClient(flags *api.Flags, out io.Writer) api.Client {
138138
if c.AccessToken == "" {
139139
if t, err := oauthdevice.LoadToken(context.Background(), c.Endpoint); err == nil {
140140
opts.OAuthToken = t
141+
} else {
142+
// TODO(burmudar): should return an error instead
143+
panic("No access token set and no OAuth token found either - unable to create api client: " + err.Error())
141144
}
142145
}
143146

internal/oauth/flow.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package oauth
44

55
import (
6+
"cmp"
67
"context"
78
"encoding/json"
89
"fmt"
@@ -67,6 +68,7 @@ type TokenResponse struct {
6768

6869
type Token struct {
6970
Endpoint string `json:"endpoint"`
71+
ClientID string `json:"client_id,omitempty"`
7072
AccessToken string `json:"access_token"`
7173
RefreshToken string `json:"refresh_token,omitempty"`
7274
ExpiresAt time.Time `json:"expires_at"`
@@ -92,17 +94,14 @@ type httpClient struct {
9294
}
9395

9496
func NewClient(clientID string) Client {
95-
return &httpClient{
96-
clientID: clientID,
97-
client: &http.Client{
98-
Timeout: 30 * time.Second,
99-
},
100-
configCache: make(map[string]*OIDCConfiguration),
101-
}
97+
return NewClientWithHTTPClient(clientID, &http.Client{
98+
Timeout: 30 * time.Second,
99+
})
102100
}
103101

104-
func NewClientWithHTTPClient(c *http.Client) Client {
102+
func NewClientWithHTTPClient(clientID string, c *http.Client) Client {
105103
return &httpClient{
104+
clientID: cmp.Or(clientID, DefaultClientID),
106105
client: c,
107106
configCache: make(map[string]*OIDCConfiguration),
108107
}
@@ -170,7 +169,7 @@ func (c *httpClient) Start(ctx context.Context, endpoint string, scopes []string
170169
}
171170

172171
data := url.Values{}
173-
data.Set("client_id", DefaultClientID)
172+
data.Set("client_id", c.clientID)
174173
if len(scopes) > 0 {
175174
data.Set("scope", strings.Join(scopes, " "))
176175
} else {
@@ -284,7 +283,7 @@ func (e *PollError) Error() string {
284283

285284
func (c *httpClient) pollOnce(ctx context.Context, tokenEndpoint, deviceCode string) (*TokenResponse, error) {
286285
data := url.Values{}
287-
data.Set("client_id", DefaultClientID)
286+
data.Set("client_id", c.clientID)
288287
data.Set("device_code", deviceCode)
289288
data.Set("grant_type", GrantTypeDeviceCode)
290289

@@ -326,11 +325,11 @@ func (c *httpClient) pollOnce(ctx context.Context, tokenEndpoint, deviceCode str
326325
func (c *httpClient) Refresh(ctx context.Context, token *Token) (*TokenResponse, error) {
327326
config, err := c.Discover(ctx, token.Endpoint)
328327
if err != nil {
329-
errors.Wrap(err, "failed to discover OIDC configuration")
328+
return nil, errors.Wrap(err, "failed to discover OIDC configuration")
330329
}
331330

332331
if config.TokenEndpoint == "" {
333-
errors.New("OIDC configuration has no token endpoint")
332+
return nil, errors.New("OIDC configuration has no token endpoint")
334333
}
335334

336335
data := url.Values{}

internal/oauth/flow_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,3 +555,19 @@ func TestRefresh_Success(t *testing.T) {
555555
t.Errorf("RefreshToken = %q, want %q", resp.RefreshToken, "new-refresh-token")
556556
}
557557
}
558+
559+
func TestRefresh_DiscoverFailure(t *testing.T) {
560+
client := NewClient(DefaultClientID)
561+
token := &Token{
562+
Endpoint: "http://127.0.0.1:1",
563+
RefreshToken: "test-refresh-token",
564+
}
565+
566+
_, err := client.Refresh(context.Background(), token)
567+
if err == nil {
568+
t.Fatal("Refresh() expected error, got nil")
569+
}
570+
if !strings.Contains(err.Error(), "failed to discover OIDC configuration") {
571+
t.Errorf("error = %q, want discovery failure context", err.Error())
572+
}
573+
}

0 commit comments

Comments
 (0)