Skip to content

Commit b9dd8d8

Browse files
fix(sdk): fix linting violations in DPoP transport (DSPX-3397)
- Fix errcheck: use comma-ok for type assertion in dpop_transport_test.go - Fix govet shadow: rename inner ok vars (isStr, athOK, jtiOK) to avoid shadowing outer ok declaration in TestDPoPTransport_AddsProofToRequests - Fix nestif in RoundTrip: extract 401 nonce-retry into retryWithNonce method - Fix nestif in sdk.go New: extract DPoP key selection into pickDPoPKey helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
1 parent 73f21ce commit b9dd8d8

3 files changed

Lines changed: 62 additions & 43 deletions

File tree

sdk/auth/dpop_transport.go

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,38 +78,12 @@ func (t *DPoPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
7878

7979
// Handle DPoP-Nonce challenge (RFC 9449 §8)
8080
if resp.StatusCode == http.StatusUnauthorized {
81-
if newNonce := resp.Header.Get("DPoP-Nonce"); newNonce != "" {
82-
// Check if this was a retry with a nonce already
83-
if nonce != "" {
84-
// Already tried with a nonce, don't retry again
85-
return resp, nil
86-
}
87-
88-
// Cache the new nonce
89-
t.setCachedNonce(origin, newNonce)
90-
91-
// Close the failed response body
92-
resp.Body.Close()
93-
94-
// Clone the original request again for retry
95-
req3 := cloneRequest(req)
96-
97-
// Reset body using GetBody if available
98-
if req.GetBody != nil {
99-
body, err := req.GetBody()
100-
if err != nil {
101-
return nil, fmt.Errorf("failed to reset request body for retry: %w", err)
102-
}
103-
req3.Body = body
104-
}
105-
106-
// Regenerate proof with nonce
107-
if err := t.addDPoPProof(req3, base, newNonce, isTokenRequest); err != nil {
108-
return nil, fmt.Errorf("failed to add DPoP proof with nonce: %w", err)
109-
}
110-
111-
// Retry the request
112-
return base.RoundTrip(req3)
81+
retryResp, retried, err := t.retryWithNonce(req, base, resp, origin, nonce, isTokenRequest)
82+
if err != nil {
83+
return nil, err
84+
}
85+
if retried {
86+
return retryResp, nil
11387
}
11488
}
11589

@@ -123,6 +97,38 @@ func (t *DPoPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
12397
return resp, nil
12498
}
12599

100+
// retryWithNonce handles a DPoP-Nonce server challenge. It returns the retried
101+
// response and true when a retry was performed, or the original response and
102+
// false when no retry was needed (missing nonce header or nonce already used).
103+
func (t *DPoPTransport) retryWithNonce(
104+
req *http.Request, base http.RoundTripper,
105+
resp *http.Response, origin, nonce string, isTokenRequest bool,
106+
) (*http.Response, bool, error) {
107+
newNonce := resp.Header.Get("DPoP-Nonce")
108+
if newNonce == "" || nonce != "" {
109+
return resp, false, nil
110+
}
111+
112+
t.setCachedNonce(origin, newNonce)
113+
resp.Body.Close()
114+
115+
req3 := cloneRequest(req)
116+
if req.GetBody != nil {
117+
body, err := req.GetBody()
118+
if err != nil {
119+
return nil, false, fmt.Errorf("failed to reset request body for retry: %w", err)
120+
}
121+
req3.Body = body
122+
}
123+
124+
if err := t.addDPoPProof(req3, base, newNonce, isTokenRequest); err != nil {
125+
return nil, false, fmt.Errorf("failed to add DPoP proof with nonce: %w", err)
126+
}
127+
128+
retryResp, err := base.RoundTrip(req3)
129+
return retryResp, true, err
130+
}
131+
126132
// addDPoPProof generates and adds DPoP proof to the request headers.
127133
func (t *DPoPTransport) addDPoPProof(req *http.Request, base http.RoundTripper, nonce string, isTokenRequest bool) error {
128134
// Normalize the htu (RFC 9449 HTTP URI Normalization)

sdk/auth/dpop_transport_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ func TestDPoPTransport_AddsProofToRequests(t *testing.T) {
9898
htu, ok := token.Get("htu")
9999
if !ok {
100100
t.Error("htu claim missing")
101-
} else if htuStr, ok := htu.(string); !ok {
101+
} else if htuStr, isStr := htu.(string); !isStr {
102102
t.Errorf("htu claim not a string: %v", htu)
103103
} else if htuStr == "" {
104104
t.Error("htu claim is empty")
105105
}
106106

107107
// Check ath claim (access token hash)
108-
if ath, ok := token.Get("ath"); !ok {
108+
if ath, athOK := token.Get("ath"); !athOK {
109109
t.Error("ath claim missing")
110110
} else {
111111
expectedHash := sha256.Sum256([]byte("test-access-token"))
@@ -116,7 +116,7 @@ func TestDPoPTransport_AddsProofToRequests(t *testing.T) {
116116
}
117117

118118
// Check jti claim
119-
if jti, ok := token.Get("jti"); !ok || jti == "" {
119+
if jti, jtiOK := token.Get("jti"); !jtiOK || jti == "" {
120120
t.Error("jti claim missing or empty")
121121
}
122122

@@ -269,7 +269,10 @@ func TestDPoPTransport_URINormalization(t *testing.T) {
269269
}
270270

271271
// The htu should have normalized the URL
272-
htuStr := htu.(string)
272+
htuStr, isStr := htu.(string)
273+
if !isStr {
274+
t.Fatalf("htu claim is not a string: %T", htu)
275+
}
273276
if !strings.Contains(htuStr, "/path") {
274277
t.Errorf("htu = %s, want to contain normalized path", htuStr)
275278
}

sdk/sdk.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,9 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) {
206206
httpClient := cfg.httpClient
207207
if accessTokenSource != nil {
208208
var dpopKey jwk.Key
209-
if cfg.dpopJWK != nil {
210-
dpopKey = cfg.dpopJWK
211-
} else if cfg.dpopKey != nil {
212-
dpopKey, err = getDPoPJWK(cfg.dpopKey)
213-
if err != nil {
214-
return nil, fmt.Errorf("failed to create DPoP JWK: %w", err)
215-
}
209+
dpopKey, err = pickDPoPKey(cfg)
210+
if err != nil {
211+
return nil, err
216212
}
217213
if dpopKey != nil {
218214
httpClient = auth.NewDPoPHTTPClient(cfg.httpClient, dpopKey, accessTokenSource, cfg.tokenEndpoint)
@@ -286,6 +282,20 @@ func getDPoPJWK(dpopKey *ocrypto.RsaKeyPair) (jwk.Key, error) {
286282
return key, nil
287283
}
288284

285+
func pickDPoPKey(cfg *config) (jwk.Key, error) {
286+
if cfg.dpopJWK != nil {
287+
return cfg.dpopJWK, nil
288+
}
289+
if cfg.dpopKey != nil {
290+
key, err := getDPoPJWK(cfg.dpopKey)
291+
if err != nil {
292+
return nil, fmt.Errorf("failed to create DPoP JWK: %w", err)
293+
}
294+
return key, nil
295+
}
296+
return nil, nil //nolint:nilnil // nil key means DPoP not configured; caller checks for nil
297+
}
298+
289299
func buildIDPTokenSource(c *config) (auth.AccessTokenSource, error) {
290300
if c.customAccessTokenSource != nil {
291301
return c.customAccessTokenSource, nil

0 commit comments

Comments
 (0)