Skip to content

Commit ee2e505

Browse files
committed
API rework
1 parent 0797d31 commit ee2e505

File tree

9 files changed

+317
-228
lines changed

9 files changed

+317
-228
lines changed

http/client.go

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@ import (
1111
"github.com/lestrrat-go/option"
1212
)
1313

14+
// Clock provides the current time for timestamp generation.
15+
type Clock interface {
16+
Now() time.Time
17+
}
18+
19+
// SystemClock uses the system time.
20+
type SystemClock struct{}
21+
22+
func (SystemClock) Now() time.Time {
23+
return time.Now()
24+
}
25+
26+
// fixedClock always returns the same time, useful for testing.
27+
type fixedClock struct {
28+
time time.Time
29+
}
30+
31+
func (c fixedClock) Now() time.Time {
32+
return c.time
33+
}
34+
35+
// FixedClock returns a Clock that always returns the same time.
36+
// This is useful for testing to ensure deterministic timestamps.
37+
func FixedClock(t time.Time) Clock {
38+
return fixedClock{time: t}
39+
}
40+
1441
// SigningTransport is an http.RoundTripper that signs HTTP requests.
1542
type SigningTransport struct {
1643
// Transport is the underlying RoundTripper.
@@ -41,6 +68,9 @@ type SigningTransport struct {
4168

4269
// Tag is an application-specific tag to include in the signature.
4370
Tag string
71+
72+
// Clock provides timestamps. If nil, SystemClock is used.
73+
Clock Clock
4474
}
4575

4676
// NewSigningTransport creates a new SigningTransport with the given configuration.
@@ -110,7 +140,11 @@ func (t *SigningTransport) createSignatureDefinition() *input.Definition {
110140
}
111141

112142
if t.IncludeCreated {
113-
builder = builder.Created(time.Now().Unix())
143+
clock := t.Clock
144+
if clock == nil {
145+
clock = SystemClock{}
146+
}
147+
builder = builder.Created(clock.Now().Unix())
114148
}
115149

116150
if t.Tag != "" {
@@ -144,10 +178,12 @@ func NewClient(key any, keyID string, options ...TransportOption) *http.Client {
144178
transport.Components = option.Value().([]component.Identifier)
145179
case identSignatureLabel{}:
146180
transport.SignatureLabel = option.Value().(string)
147-
case identWithoutCreated{}:
148-
transport.IncludeCreated = false
181+
case identCreated{}:
182+
transport.IncludeCreated = option.Value().(bool)
149183
case identTag{}:
150184
transport.Tag = option.Value().(string)
185+
case identClockOption{}:
186+
transport.Clock = option.Value().(Clock)
151187
}
152188
}
153189

@@ -195,14 +231,14 @@ type identSignatureLabel struct{}
195231

196232
func (identSignatureLabel) String() string { return "WithSignatureLabel" }
197233

198-
// WithoutCreated disables the created parameter.
199-
func WithoutCreated() TransportOption {
200-
return option.New(identWithoutCreated{}, true)
234+
// WithCreated controls whether to include the created parameter.
235+
func WithCreated(include bool) TransportOption {
236+
return option.New(identCreated{}, include)
201237
}
202238

203-
type identWithoutCreated struct{}
239+
type identCreated struct{}
204240

205-
func (identWithoutCreated) String() string { return "WithoutCreated" }
241+
func (identCreated) String() string { return "WithCreated" }
206242

207243
// WithTag sets the application-specific tag.
208244
func WithTag(tag string) TransportOption {
@@ -212,3 +248,30 @@ func WithTag(tag string) TransportOption {
212248
type identTag struct{}
213249

214250
func (identTag) String() string { return "WithTag" }
251+
252+
// SignVerifyOption can be used with both signing and verification operations.
253+
type SignVerifyOption interface {
254+
transportOption()
255+
signerOption()
256+
option.Interface
257+
}
258+
259+
// clockOption implements SignVerifyOption
260+
type clockOption struct {
261+
clock Clock
262+
}
263+
264+
func (c clockOption) Ident() any { return identClockOption{} }
265+
func (c clockOption) Value() any { return c.clock }
266+
func (c clockOption) transportOption() {}
267+
func (c clockOption) signerOption() {}
268+
269+
type identClockOption struct{}
270+
271+
func (identClockOption) String() string { return "WithClock" }
272+
273+
// WithClock sets the clock used for timestamp generation.
274+
// This option works for both TransportOption and signerOption.
275+
func WithClock(clock Clock) SignVerifyOption {
276+
return clockOption{clock: clock}
277+
}

http/doc.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
// signer := http.NewResponseSigner(privateKey, "my-key-id")
2121
//
2222
// // Wrap your handler to verify requests and sign responses
23-
// handler := http.VerifyAndSign(myHandler, verifier, signer)
23+
// handler := http.Wrap(myHandler,
24+
// http.WithVerifier(verifier),
25+
// http.WithSigner(signer))
2426
//
2527
// http.ListenAndServe(":8080", handler)
2628
//
@@ -37,10 +39,29 @@
3739
// Both server and client components support extensive configuration:
3840
//
3941
// // Custom verifier with specific components and error handling
40-
// verifier := http.NewVerifier(keyResolver)
41-
// verifier.RequiredSignatures = 2
42-
// verifier.SkipOnMissing = true
43-
// verifier.ErrorHandler = myCustomErrorHandler
42+
// verifier := http.NewVerifier(keyResolver,
43+
// http.WithMaxSignatureAge(5*time.Minute),
44+
// http.WithRequiredComponents(
45+
// component.Method(),
46+
// component.New("@target-uri"),
47+
// component.New("date"),
48+
// ),
49+
// http.WithAllowedAlgorithms("rsa-pss-sha512"),
50+
// )
51+
//
52+
// // Custom response signer
53+
// signer := http.NewResponseSigner(privateKey, "key-id",
54+
// http.WithSignerComponents(
55+
// component.Status(),
56+
// component.New("content-type"),
57+
// ),
58+
// http.WithFailOnError(true),
59+
// )
60+
//
61+
// // Use in wrapper
62+
// handler := http.Wrap(myHandler,
63+
// http.WithVerifier(verifier),
64+
// http.WithSigner(signer))
4465
//
4566
// // Custom client with specific signature components
4667
// client := http.NewClient(privateKey, "key-id",

http/example_enhanced_test.go

Lines changed: 0 additions & 84 deletions
This file was deleted.

http/example_server_test.go

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)