1 package jwk 2 3 import ( 4 "crypto" 5 "time" 6 7 "github.com/lestrrat-go/backoff/v2" 8 "github.com/lestrrat-go/jwx/internal/json" 9 "github.com/lestrrat-go/option" 10 ) 11 12 type Option = option.Interface 13 14 type identHTTPClient struct{} 15 type identThumbprintHash struct{} 16 type identRefreshInterval struct{} 17 type identMinRefreshInterval struct{} 18 type identFetchBackoff struct{} 19 type identPEM struct{} 20 type identTypedField struct{} 21 type identLocalRegistry struct{} 22 type identFetchWhitelist struct{} 23 type identIgnoreParseError struct{} 24 25 // AutoRefreshOption is a type of Option that can be passed to the 26 // AutoRefresh object. 27 type AutoRefreshOption interface { 28 Option 29 autoRefreshOption() 30 } 31 32 type autoRefreshOption struct { 33 Option 34 } 35 36 func (*autoRefreshOption) autoRefreshOption() {} 37 38 // FetchOption is a type of Option that can be passed to `jwk.Fetch()` 39 // FetchOption also implements the `AutoRefreshOption`, and thus can 40 // safely be passed to `(*jwk.AutoRefresh).Configure()` 41 type FetchOption interface { 42 AutoRefreshOption 43 fetchOption() 44 } 45 46 type fetchOption struct { 47 Option 48 } 49 50 func (*fetchOption) autoRefreshOption() {} 51 func (*fetchOption) fetchOption() {} 52 53 // ParseOption is a type of Option that can be passed to `jwk.Parse()` 54 // ParseOption also implmentsthe `ReadFileOPtion` and `AutoRefreshOption`, 55 // and thus safely be passed to `jwk.ReadFile` and `(*jwk.AutoRefresh).Configure()` 56 type ParseOption interface { 57 ReadFileOption 58 AutoRefreshOption 59 parseOption() 60 } 61 62 type parseOption struct { 63 Option 64 } 65 66 func (*parseOption) autoRefreshOption() {} 67 func (*parseOption) parseOption() {} 68 func (*parseOption) readFileOption() {} 69 70 // WithHTTPClient allows users to specify the "net/http".Client object that 71 // is used when fetching jwk.Set objects. 72 func WithHTTPClient(cl HTTPClient) FetchOption { 73 return &fetchOption{option.New(identHTTPClient{}, cl)} 74 } 75 76 // WithFetchBackoff specifies the backoff policy to use when 77 // refreshing a JWKS from a remote server fails. 78 // 79 // This does not have any effect on initial `Fetch()`, or any of the `Refresh()` calls -- 80 // the backoff is applied ONLY on the background refreshing goroutine. 81 func WithFetchBackoff(v backoff.Policy) FetchOption { 82 return &fetchOption{option.New(identFetchBackoff{}, v)} 83 } 84 85 func WithThumbprintHash(h crypto.Hash) Option { 86 return option.New(identThumbprintHash{}, h) 87 } 88 89 // WithRefreshInterval specifies the static interval between refreshes 90 // of jwk.Set objects controlled by jwk.AutoRefresh. 91 // 92 // Providing this option overrides the adaptive token refreshing based 93 // on Cache-Control/Expires header (and jwk.WithMinRefreshInterval), 94 // and refreshes will *always* happen in this interval. 95 func WithRefreshInterval(d time.Duration) AutoRefreshOption { 96 return &autoRefreshOption{ 97 option.New(identRefreshInterval{}, d), 98 } 99 } 100 101 // WithMinRefreshInterval specifies the minimum refresh interval to be used 102 // when using AutoRefresh. This value is ONLY used if you did not specify 103 // a user-supplied static refresh interval via `WithRefreshInterval`. 104 // 105 // This value is used as a fallback value when tokens are refreshed. 106 // 107 // When we fetch the key from a remote URL, we first look at the max-age 108 // directive from Cache-Control response header. If this value is present, 109 // we compare the max-age value and the value specified by this option 110 // and take the larger one. 111 // 112 // Next we check for the Expires header, and similarly if the header is 113 // present, we compare it against the value specified by this option, 114 // and take the larger one. 115 // 116 // Finally, if neither of the above headers are present, we use the 117 // value specified by this option as the next refresh timing 118 // 119 // If unspecified, the minimum refresh interval is 1 hour 120 func WithMinRefreshInterval(d time.Duration) AutoRefreshOption { 121 return &autoRefreshOption{ 122 option.New(identMinRefreshInterval{}, d), 123 } 124 } 125 126 // WithPEM specifies that the input to `Parse()` is a PEM encoded key. 127 func WithPEM(v bool) ParseOption { 128 return &parseOption{ 129 option.New(identPEM{}, v), 130 } 131 } 132 133 type typedFieldPair struct { 134 Name string 135 Value interface{} 136 } 137 138 // WithTypedField allows a private field to be parsed into the object type of 139 // your choice. It works much like the RegisterCustomField, but the effect 140 // is only applicable to the jwt.Parse function call which receives this option. 141 // 142 // While this can be extremely useful, this option should be used with caution: 143 // There are many caveats that your entire team/user-base needs to be aware of, 144 // and therefore in general its use is discouraged. Only use it when you know 145 // what you are doing, and you document its use clearly for others. 146 // 147 // First and foremost, this is a "per-object" option. Meaning that given the same 148 // serialized format, it is possible to generate two objects whose internal 149 // representations may differ. That is, if you parse one _WITH_ the option, 150 // and the other _WITHOUT_, their internal representation may completely differ. 151 // This could potentially lead to problems. 152 // 153 // Second, specifying this option will slightly slow down the decoding process 154 // as it needs to consult multiple definitions sources (global and local), so 155 // be careful if you are decoding a large number of tokens, as the effects will stack up. 156 func WithTypedField(name string, object interface{}) ParseOption { 157 return &parseOption{ 158 option.New(identTypedField{}, 159 typedFieldPair{Name: name, Value: object}, 160 ), 161 } 162 } 163 164 // This option is only available for internal code. Users don't get to play with it 165 func withLocalRegistry(r *json.Registry) ParseOption { 166 return &parseOption{option.New(identLocalRegistry{}, r)} 167 } 168 169 // WithFetchWhitelist specifies the Whitelist object to use when 170 // fetching JWKs from a remote source. This option can be passed 171 // to both `jwk.Fetch()`, `jwk.NewAutoRefresh()`, and `(*jwk.AutoRefresh).Configure()` 172 func WithFetchWhitelist(w Whitelist) FetchOption { 173 return &fetchOption{option.New(identFetchWhitelist{}, w)} 174 } 175 176 // WithIgnoreParseError is only applicable when used with `jwk.Parse()` 177 // (i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function 178 // will return an error no matter what the input is. 179 // 180 // DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST. 181 // 182 // The option specifies that errors found during parsing of individual 183 // keys are ignored. For example, if you had keys A, B, C where B is 184 // invalid (e.g. it does not contain the required fields), then the 185 // resulting JWKS will contain keys A and C only. 186 // 187 // This options exists as an escape hatch for those times when a 188 // key in a JWKS that is irrelevant for your use case is causing 189 // your JWKS parsing to fail, and you want to get to the rest of the 190 // keys in the JWKS. 191 // 192 // Again, DO NOT USE unless you have exhausted all other routes. 193 // When you use this option, you will not be able to tell if you are 194 // using a faulty JWKS, except for when there are JSON syntax errors. 195 func WithIgnoreParseError(b bool) ParseOption { 196 return &parseOption{option.New(identIgnoreParseError{}, b)} 197 } 198