Skip to content

Commit 1f298b0

Browse files
yaronfcursoragent
andcommitted
feat: add Fields.AddRequestComponent for ;req bindings
Convenience wrapper for required associated-request components, with tests and a v0.6.1 release-notes mention. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f16c47f commit 1f298b0

4 files changed

Lines changed: 56 additions & 20 deletions

File tree

fields.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ func (fs *Fields) AddHeaderOptional(hdr string) *Fields {
131131
return fs.AddHeaderExt(hdr, true, false, false, false)
132132
}
133133

134+
// AddRequestComponent appends a required component from the associated request (sets ;req).
135+
// Equivalent to AddHeaderExt(name, false, false, true, false). Typical for response signatures
136+
// that bind derived components such as @method, @path, or @query from the request (RFC 9421 §2.2).
137+
func (fs *Fields) AddRequestComponent(name string) *Fields {
138+
return fs.AddHeaderExt(name, false, false, true, false)
139+
}
140+
134141
func fromQueryParam(qp string) *field {
135142
i := httpsfv.NewItem("@query-param")
136143
i.Params.Add("name", QueryEscapeForSignature(qp))

fields_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package httpsign
22

33
import (
4+
"testing"
5+
46
"github.com/dunglas/httpsfv"
57
"github.com/stretchr/testify/assert"
6-
"testing"
8+
"github.com/stretchr/testify/require"
79
)
810

911
func TestFields_asSignatureInput(t *testing.T) {
@@ -80,6 +82,12 @@ func TestFields_hasHeader(t *testing.T) {
8082
header: "Content-Digest",
8183
want: true,
8284
},
85+
{
86+
name: "associated-request component via AddRequestComponent",
87+
fields: NewFields().AddRequestComponent("@method"),
88+
header: "@method",
89+
want: true,
90+
},
8391
{
8492
name: "header not in fields",
8593
fields: NewFields().AddHeaders("content-type"),
@@ -101,6 +109,25 @@ func TestFields_hasHeader(t *testing.T) {
101109
}
102110
}
103111

112+
func TestAddRequestComponent(t *testing.T) {
113+
got := NewFields().AddRequestComponent("@method").AddRequestComponent("@path")
114+
want := NewFields().
115+
AddHeaderExt("@method", false, false, true, false).
116+
AddHeaderExt("@path", false, false, true, false)
117+
require.Equal(t, len(want.f), len(got.f))
118+
for i := range got.f {
119+
assert.True(t, got.f[i].Equal(want.f[i]), "field %d: got %s want %s", i, got.f[i], want.f[i])
120+
req, err := got.f[i].associatedRequest()
121+
require.NoError(t, err)
122+
assert.True(t, req)
123+
opt, err := got.f[i].optional()
124+
require.NoError(t, err)
125+
assert.False(t, opt)
126+
}
127+
assert.Equal(t, `"@method";req`, got.f[0].String())
128+
assert.Equal(t, `"@path";req`, got.f[1].String())
129+
}
130+
104131
func Test_field_String(t *testing.T) {
105132
tests := []struct {
106133
name string

internal-docs/RELEASE-v0.6.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Copy the **Summary** section below into the GitHub release when tagging `v0.6.1`
1414
- **Escape hatch:** `NewJWSVerifierWithAlg(allowed, alg, key, …)` when alg cannot be inferred (raw RSA/HMAC) or the store already chose alg. Today’s `NewJWSVerifier(alg, key, …)` migrates here.
1515
- **`JWSAlgAllowlist`:** both verify constructors take an allowlist (`nil` skips policy for tests/lazy use). Prefer a non-nil allowlist when `keyid` can select among keys.
1616
- **`NewJWSSigner`:** nil config defaults to `SignAlg(false)`; configs with `SignAlg(true)` are rejected.
17+
- **`Fields.AddRequestComponent`:** convenience for required associated-request components (`;req`), equivalent to `AddHeaderExt(name, false, false, true, false)`.
1718
- **jwx** bump to **≥ v4.5.0** (if not already on the release branch).
1819

1920
### Upgrade from v0.6.0
@@ -24,6 +25,7 @@ Copy the **Summary** section below into the GitHub release when tagging `v0.6.1`
2425
| **`NewJWSVerifier(alg, key, …)`** | Prefer `NewJWSVerifier(allowed, key, …)` when the key is inferrable; else `NewJWSVerifierWithAlg(allowed, alg, key, …)`. |
2526
| **`NewJWSSigner`** | Omit config or keep `SignAlg(false)`; `SignAlg(true)` now errors. |
2627
| **HTTP `SetAllowedAlgs`** | Unchanged — Signature-Input `alg` only, not JWS `jwa`. |
28+
| **Response `;req` fields** | Prefer `AddRequestComponent("@method")` (etc.) over `AddHeaderExt(..., false, false, true, false)`. |
2729

2830
```go
2931
allowed, _ := httpsign.NewJWSAlgAllowlist(jwa.ES256(), jwa.MLDSA65())

signatures_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,7 +2036,7 @@ func TestSignResponseNilRequestWhenAssocFieldsRequired(t *testing.T) {
20362036
priv, pub, err := genP256KeyPair()
20372037
assert.NoError(t, err)
20382038
fields := *NewFields().AddHeaders("@status", "date").
2039-
AddHeaderExt("@authority", false, false, true, false) // requires associated request
2039+
AddRequestComponent("@authority") // requires associated request
20402040
signer, err := NewP256Signer(*priv, NewSignConfig().SetKeyID("key"), fields)
20412041
assert.NoError(t, err)
20422042
res := readResponse(httpres2)
@@ -3221,10 +3221,10 @@ func TestRequestBinding17(t *testing.T) {
32213221
pubKey2, err := parseECPublicKeyFromPemStr(p256PubKey2)
32223222
assert.NoError(t, err, "read pub key")
32233223
fields2 := *NewFields().AddHeaders("@status", "content-digest", "content-type").
3224-
AddHeaderExt("@authority", false, false, true, false).
3225-
AddHeaderExt("@method", false, false, true, false).
3226-
AddHeaderExt("@path", false, false, true, false).
3227-
AddHeaderExt("content-digest", false, false, true, false)
3224+
AddRequestComponent("@authority").
3225+
AddRequestComponent("@method").
3226+
AddRequestComponent("@path").
3227+
AddRequestComponent("content-digest")
32283228
verifier2, err := NewP256Verifier(*pubKey2, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("test-key-ecc-p256"), fields2)
32293229
assert.NoError(t, err, "create verifier")
32303230
sigBase, err := verifyResponseDebug("reqres", *verifier2, res, req)
@@ -3257,10 +3257,10 @@ func TestMessageRequestBinding17(t *testing.T) {
32573257
pubKey2, err := parseECPublicKeyFromPemStr(p256PubKey2)
32583258
assert.NoError(t, err, "read pub key")
32593259
fields2 := *NewFields().AddHeaders("@status", "content-digest", "content-type").
3260-
AddHeaderExt("@authority", false, false, true, false).
3261-
AddHeaderExt("@method", false, false, true, false).
3262-
AddHeaderExt("@path", false, false, true, false).
3263-
AddHeaderExt("content-digest", false, false, true, false)
3260+
AddRequestComponent("@authority").
3261+
AddRequestComponent("@method").
3262+
AddRequestComponent("@path").
3263+
AddRequestComponent("content-digest")
32643264
verifier2, err := NewP256Verifier(*pubKey2, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("test-key-ecc-p256"), fields2)
32653265
assert.NoError(t, err, "create verifier")
32663266
sigBase, _, err := verifyDebug("reqres", *verifier2, msg)
@@ -3393,11 +3393,11 @@ func TestRequestBindingSignedResponse17(t *testing.T) {
33933393
pubKey2, err := parseECPublicKeyFromPemStr(p256PubKey2)
33943394
assert.NoError(t, err, "read pub key")
33953395
fields2 := *NewFields().AddHeaders("@status", "content-digest", "content-type").
3396-
AddHeaderExt("@authority", false, false, true, false).
3397-
AddHeaderExt("@method", false, false, true, false).
3398-
AddHeaderExt("@path", false, false, true, false).
3399-
AddHeaderExt("@query", false, false, true, false).
3400-
AddHeaderExt("content-digest", false, false, true, false)
3396+
AddRequestComponent("@authority").
3397+
AddRequestComponent("@method").
3398+
AddRequestComponent("@path").
3399+
AddRequestComponent("@query").
3400+
AddRequestComponent("content-digest")
34013401
verifier2, err := NewP256Verifier(*pubKey2, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("test-key-ecc-p256"), fields2)
34023402
assert.NoError(t, err, "create verifier")
34033403
sigBase, err := verifyResponseDebug("reqres", *verifier2, res, req)
@@ -3521,11 +3521,11 @@ func TestMessageRequestBindingSignedResponse17(t *testing.T) {
35213521
pubKey2, err := parseECPublicKeyFromPemStr(p256PubKey2)
35223522
assert.NoError(t, err, "read pub key")
35233523
fields2 := *NewFields().AddHeaders("@status", "content-digest", "content-type").
3524-
AddHeaderExt("@authority", false, false, true, false).
3525-
AddHeaderExt("@method", false, false, true, false).
3526-
AddHeaderExt("@path", false, false, true, false).
3527-
AddHeaderExt("@query", false, false, true, false).
3528-
AddHeaderExt("content-digest", false, false, true, false)
3524+
AddRequestComponent("@authority").
3525+
AddRequestComponent("@method").
3526+
AddRequestComponent("@path").
3527+
AddRequestComponent("@query").
3528+
AddRequestComponent("content-digest")
35293529
verifier2, err := NewP256Verifier(*pubKey2, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("test-key-ecc-p256"), fields2)
35303530
assert.NoError(t, err, "create verifier")
35313531
sigBase, _, err := verifyDebug("reqres", *verifier2, msg)

0 commit comments

Comments
 (0)