Skip to content

Commit ac67db3

Browse files
committed
fix(auth): preserve scope response on refresh failure
1 parent 070af73 commit ac67db3

2 files changed

Lines changed: 62 additions & 4 deletions

File tree

internal/googleapi/transport.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,14 @@ func (t *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
150150
if insufficientScopes {
151151
slog.Debug("insufficient scopes response, refreshing auth token and retrying")
152152

153-
drainAndClose(resp.Body)
154-
155153
if err := t.RefreshAuth(req.Context()); err != nil {
156-
return nil, fmt.Errorf("refresh auth after insufficient scopes response: %w", err)
154+
slog.Debug("could not refresh auth after insufficient scopes response", "err", err)
155+
156+
return resp, nil
157157
}
158158

159+
drainAndClose(resp.Body)
160+
159161
retriedAuth = true
160162

161163
continue

internal/googleapi/transport_more_test.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import (
1515

1616
type roundTripFunc func(*http.Request) (*http.Response, error)
1717

18-
var errUnexpectedRequestBody = errors.New("unexpected request body")
18+
var (
19+
errUnexpectedRequestBody = errors.New("unexpected request body")
20+
errRefreshFailed = errors.New("refresh failed")
21+
)
1922

2023
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
2124
return f(req)
@@ -300,6 +303,59 @@ func TestRetryTransportRefreshesAuthOnceForInsufficientScope403(t *testing.T) {
300303
}
301304
}
302305

306+
func TestRetryTransportPreservesInsufficientScope403WhenAuthRefreshFails(t *testing.T) {
307+
const body = `{"error":{"errors":[{"reason":"insufficientPermissions"}],"message":"Insufficient Permission","code":403}}`
308+
309+
refreshes := 0
310+
calls := 0
311+
base := roundTripFunc(func(req *http.Request) (*http.Response, error) {
312+
calls++
313+
314+
return newTestResponse(http.StatusForbidden, body), nil
315+
})
316+
317+
rt := &RetryTransport{
318+
Base: base,
319+
RefreshAuth: func(context.Context) error {
320+
refreshes++
321+
322+
return errRefreshFailed
323+
},
324+
}
325+
326+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example.com", nil)
327+
if err != nil {
328+
t.Fatalf("new request: %v", err)
329+
}
330+
331+
resp, err := rt.RoundTrip(req)
332+
if err != nil {
333+
t.Fatalf("round trip: %v", err)
334+
}
335+
336+
gotBody, err := io.ReadAll(resp.Body)
337+
if err != nil {
338+
t.Fatalf("read response body: %v", err)
339+
}
340+
_ = resp.Body.Close()
341+
342+
if resp.StatusCode != http.StatusForbidden {
343+
t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusForbidden)
344+
}
345+
346+
if string(gotBody) != body {
347+
t.Fatalf("response body = %q, want %q", gotBody, body)
348+
}
349+
350+
if calls != 1 {
351+
t.Fatalf("calls = %d, want 1", calls)
352+
}
353+
354+
if refreshes != 1 {
355+
t.Fatalf("refreshes = %d, want 1", refreshes)
356+
}
357+
}
358+
303359
func TestRetryTransportDoesNotRefreshAuthForOrdinary403(t *testing.T) {
304360
refreshes := 0
305361
calls := 0

0 commit comments

Comments
 (0)