forked from MicahParks/keyfunc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoptions.go
136 lines (114 loc) · 6.18 KB
/
options.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package keyfunc
import (
"context"
"encoding/json"
"errors"
"fmt"
io "io/ioutil"
"net/http"
"time"
)
// ErrInvalidHTTPStatusCode indicates that the HTTP status code is invalid.
var ErrInvalidHTTPStatusCode = errors.New("invalid HTTP status code")
// Options represents the configuration options for a JWKS.
//
// If RefreshInterval and or RefreshUnknownKID is not nil, then a background goroutine will be launched to refresh the
// remote JWKS under the specified circumstances.
//
// When using a background refresh goroutine, make sure to use RefreshRateLimit if paired with RefreshUnknownKID. Also
// make sure to end the background refresh goroutine with the JWKS.EndBackground method when it's no longer needed.
type Options struct {
// Client is the HTTP client used to get the JWKS via HTTP.
Client *http.Client
// Ctx is the context for the keyfunc's background refresh. When the context expires or is canceled, the background
// goroutine will end.
Ctx context.Context
// GivenKeys is a map of JWT key IDs, `kid`, to their given keys. If the JWKS has a background refresh goroutine,
// these values persist across JWKS refreshes. By default, if the remote JWKS resource contains a key with the same
// `kid` any given keys with the same `kid` will be overwritten by the keys from the remote JWKS. Use the
// GivenKIDOverride option to flip this behavior.
GivenKeys map[string]GivenKey
// GivenKIDOverride will make a GivenKey override any keys with the same ID (`kid`) in the remote JWKS. The is only
// effectual if GivenKeys is provided.
GivenKIDOverride bool
// JWKUseWhitelist is a whitelist of JWK `use` parameter values that will restrict what keys can be returned for
// jwt.Keyfunc. The assumption is that jwt.Keyfunc is only used for JWT signature verification.
// The default behavior is to only return a JWK if its `use` parameter has the value `"sig"`, an empty string, or if
// the parameter was omitted entirely.
JWKUseWhitelist []JWKUse
// JWKUseNoWhitelist overrides the JWKUseWhitelist field and its default behavior. If set to true, all JWKs will be
// returned regardless of their `use` parameter value.
JWKUseNoWhitelist bool
// RefreshErrorHandler is a function that consumes errors that happen during a JWKS refresh. This is only effectual
// if a background refresh goroutine is active.
RefreshErrorHandler ErrorHandler
// RefreshInterval is the duration to refresh the JWKS in the background via a new HTTP request. If this is not nil,
// then a background goroutine will be used to refresh the JWKS once per the given interval. Make sure to call the
// JWKS.EndBackground method to end this goroutine when it's no longer needed.
RefreshInterval time.Duration
// RefreshRateLimit limits the rate at which refresh requests are granted. Only one refresh request can be queued
// at a time any refresh requests received while there is already a queue are ignored. It does not make sense to
// have RefreshInterval's value shorter than this.
RefreshRateLimit time.Duration
// RefreshTimeout is the duration for the context timeout used to create the HTTP request for a refresh of the JWKS.
// This defaults to one minute. This is used for the HTTP request and any background goroutine refreshes.
RefreshTimeout time.Duration
// RefreshUnknownKID indicates that the JWKS refresh request will occur every time a kid that isn't cached is seen.
// This is done through a background goroutine. Without specifying a RefreshInterval a malicious client could
// self-sign X JWTs, send them to this service, then cause potentially high network usage proportional to X. Make
// sure to call the JWKS.EndBackground method to end this goroutine when it's no longer needed.
RefreshUnknownKID bool
// InitAsync indicates that the JWKS will be fetched asynchronously upon initialization.
InitAsync bool
// RequestFactory creates HTTP requests for the remote JWKS resource located at the given url. For example, an
// HTTP header could be added to indicate a User-Agent.
RequestFactory func(ctx context.Context, url string) (*http.Request, error)
// ResponseExtractor consumes a *http.Response and produces the raw JSON for the JWKS. By default, the
// ResponseExtractorStatusOK function is used. The default behavior changed in v1.4.0.
ResponseExtractor func(ctx context.Context, resp *http.Response) (json.RawMessage, error)
}
// ResponseExtractorStatusOK is meant to be used as the ResponseExtractor field for Options. It confirms that response
// status code is 200 OK and returns the raw JSON from the response body.
func ResponseExtractorStatusOK(ctx context.Context, resp *http.Response) (json.RawMessage, error) {
//goland:noinspection GoUnhandledErrorResult
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %d", ErrInvalidHTTPStatusCode, resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
// ResponseExtractorStatusAny is meant to be used as the ResponseExtractor field for Options. It returns the raw JSON
// from the response body regardless of the response status code.
func ResponseExtractorStatusAny(ctx context.Context, resp *http.Response) (json.RawMessage, error) {
//goland:noinspection GoUnhandledErrorResult
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// applyOptions applies the given options to the given JWKS.
func applyOptions(jwks *JWKS, options Options) {
if options.Ctx != nil {
jwks.ctx, jwks.cancel = context.WithCancel(options.Ctx)
}
if options.GivenKeys != nil {
jwks.givenKeys = make(map[string]GivenKey)
for kid, key := range options.GivenKeys {
jwks.givenKeys[kid] = key
}
}
if !options.JWKUseNoWhitelist {
jwks.jwkUseWhitelist = make(map[JWKUse]struct{})
for _, use := range options.JWKUseWhitelist {
jwks.jwkUseWhitelist[use] = struct{}{}
}
}
jwks.client = options.Client
jwks.givenKIDOverride = options.GivenKIDOverride
jwks.refreshErrorHandler = options.RefreshErrorHandler
jwks.refreshInterval = options.RefreshInterval
jwks.refreshRateLimit = options.RefreshRateLimit
jwks.refreshTimeout = options.RefreshTimeout
jwks.refreshUnknownKID = options.RefreshUnknownKID
jwks.initAsync = options.InitAsync
jwks.requestFactory = options.RequestFactory
jwks.responseExtractor = options.ResponseExtractor
}