...

Source file src/github.com/MicahParks/keyfunc/v2/options.go

Documentation: github.com/MicahParks/keyfunc/v2

     1  package keyfunc
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"time"
    11  
    12  	"github.com/golang-jwt/jwt/v5"
    13  )
    14  
    15  // ErrInvalidHTTPStatusCode indicates that the HTTP status code is invalid.
    16  var ErrInvalidHTTPStatusCode = errors.New("invalid HTTP status code")
    17  
    18  // Options represents the configuration options for a JWKS.
    19  //
    20  // If either RefreshInterval is non-zero or RefreshUnknownKID is true, then a background goroutine will be launched to refresh the
    21  // remote JWKS under the specified circumstances.
    22  //
    23  // When using a background refresh goroutine, make sure to use RefreshRateLimit if paired with RefreshUnknownKID. Also
    24  // make sure to end the background refresh goroutine with the JWKS.EndBackground method when it's no longer needed.
    25  type Options struct {
    26  	// Client is the HTTP client used to get the JWKS via HTTP.
    27  	Client *http.Client
    28  
    29  	// Ctx is the context for the keyfunc's background refresh. When the context expires or is canceled, the background
    30  	// goroutine will end.
    31  	Ctx context.Context
    32  
    33  	// GivenKeys is a map of JWT key IDs, `kid`, to their given keys. If the JWKS has a background refresh goroutine,
    34  	// these values persist across JWKS refreshes. By default, if the remote JWKS resource contains a key with the same
    35  	// `kid` any given keys with the same `kid` will be overwritten by the keys from the remote JWKS. Use the
    36  	// GivenKIDOverride option to flip this behavior.
    37  	GivenKeys map[string]GivenKey
    38  
    39  	// GivenKIDOverride will make a GivenKey override any keys with the same ID (`kid`) in the remote JWKS. The is only
    40  	// effectual if GivenKeys is provided.
    41  	GivenKIDOverride bool
    42  
    43  	// JWKUseWhitelist is a whitelist of JWK `use` parameter values that will restrict what keys can be returned for
    44  	// jwt.Keyfunc. The assumption is that jwt.Keyfunc is only used for JWT signature verification.
    45  	// The default behavior is to only return a JWK if its `use` parameter has the value `"sig"`, an empty string, or if
    46  	// the parameter was omitted entirely.
    47  	JWKUseWhitelist []JWKUse
    48  
    49  	// JWKUseNoWhitelist overrides the JWKUseWhitelist field and its default behavior. If set to true, all JWKs will be
    50  	// returned regardless of their `use` parameter value.
    51  	JWKUseNoWhitelist bool
    52  
    53  	// RefreshErrorHandler is a function that consumes errors that happen during a JWKS refresh. This is only effectual
    54  	// if a background refresh goroutine is active.
    55  	RefreshErrorHandler ErrorHandler
    56  
    57  	// RefreshInterval is the duration to refresh the JWKS in the background via a new HTTP request. If this is not zero,
    58  	// then a background goroutine will be used to refresh the JWKS once per the given interval. Make sure to call the
    59  	// JWKS.EndBackground method to end this goroutine when it's no longer needed.
    60  	RefreshInterval time.Duration
    61  
    62  	// RefreshRateLimit limits the rate at which refresh requests are granted. Only one refresh request can be queued
    63  	// at a time any refresh requests received while there is already a queue are ignored. It does not make sense to
    64  	// have RefreshInterval's value shorter than this.
    65  	RefreshRateLimit time.Duration
    66  
    67  	// RefreshTimeout is the duration for the context timeout used to create the HTTP request for a refresh of the JWKS.
    68  	// This defaults to one minute. This is used for the HTTP request and any background goroutine refreshes.
    69  	RefreshTimeout time.Duration
    70  
    71  	// RefreshUnknownKID indicates that the JWKS refresh request will occur every time a kid that isn't cached is seen.
    72  	// This is done through a background goroutine. Without specifying a RefreshInterval a malicious client could
    73  	// self-sign X JWTs, send them to this service, then cause potentially high network usage proportional to X. Make
    74  	// sure to call the JWKS.EndBackground method to end this goroutine when it's no longer needed.
    75  	//
    76  	// It is recommended this option is not used when in MultipleJWKS. This is because KID collisions SHOULD be uncommon
    77  	// meaning nearly any JWT SHOULD trigger a refresh for the number of JWKS in the MultipleJWKS minus one.
    78  	RefreshUnknownKID bool
    79  
    80  	// RequestFactory creates HTTP requests for the remote JWKS resource located at the given url. For example, an
    81  	// HTTP header could be added to indicate a User-Agent.
    82  	RequestFactory func(ctx context.Context, url string) (*http.Request, error)
    83  
    84  	// ResponseExtractor consumes a *http.Response and produces the raw JSON for the JWKS. By default, the
    85  	// ResponseExtractorStatusOK function is used. The default behavior changed in v1.4.0.
    86  	ResponseExtractor func(ctx context.Context, resp *http.Response) (json.RawMessage, error)
    87  
    88  	// TolerateInitialJWKHTTPError will tolerate any error from the initial HTTP JWKS request. If an error occurs,
    89  	// the RefreshErrorHandler will be given the error. The program will continue to run as if the error did not occur
    90  	// and a valid JWK Set with no keys was received in the response. This allows for the background goroutine to
    91  	// request the JWKS at a later time.
    92  	//
    93  	// It does not make sense to mark this field as true unless the background refresh goroutine is active.
    94  	TolerateInitialJWKHTTPError bool
    95  }
    96  
    97  // MultipleOptions is used to configure the behavior when multiple JWKS are used by MultipleJWKS.
    98  type MultipleOptions struct {
    99  	// KeySelector is a function that selects the key to use for a given token. It will be used in the implementation
   100  	// for jwt.Keyfunc. If implementing this custom selector extract the key ID and algorithm from the token's header.
   101  	// Use the key ID to select a token and confirm the key's algorithm before returning it.
   102  	//
   103  	// This value defaults to KeySelectorFirst.
   104  	KeySelector func(multiJWKS *MultipleJWKS, token *jwt.Token) (key interface{}, err error)
   105  }
   106  
   107  // RefreshOptions are used to specify manual refresh behavior.
   108  type RefreshOptions struct {
   109  	IgnoreRateLimit bool
   110  }
   111  
   112  type refreshRequest struct {
   113  	cancel          context.CancelFunc
   114  	ignoreRateLimit bool
   115  }
   116  
   117  // ResponseExtractorStatusOK is meant to be used as the ResponseExtractor field for Options. It confirms that response
   118  // status code is 200 OK and returns the raw JSON from the response body.
   119  func ResponseExtractorStatusOK(ctx context.Context, resp *http.Response) (json.RawMessage, error) {
   120  	//goland:noinspection GoUnhandledErrorResult
   121  	defer resp.Body.Close()
   122  	if resp.StatusCode != http.StatusOK {
   123  		return nil, fmt.Errorf("%w: %d", ErrInvalidHTTPStatusCode, resp.StatusCode)
   124  	}
   125  	return io.ReadAll(resp.Body)
   126  }
   127  
   128  // ResponseExtractorStatusAny is meant to be used as the ResponseExtractor field for Options. It returns the raw JSON
   129  // from the response body regardless of the response status code.
   130  func ResponseExtractorStatusAny(ctx context.Context, resp *http.Response) (json.RawMessage, error) {
   131  	//goland:noinspection GoUnhandledErrorResult
   132  	defer resp.Body.Close()
   133  	return io.ReadAll(resp.Body)
   134  }
   135  
   136  // applyOptions applies the given options to the given JWKS.
   137  func applyOptions(jwks *JWKS, options Options) {
   138  	if options.Ctx != nil {
   139  		jwks.ctx, jwks.cancel = context.WithCancel(options.Ctx)
   140  	}
   141  
   142  	if options.GivenKeys != nil {
   143  		jwks.givenKeys = make(map[string]GivenKey)
   144  		for kid, key := range options.GivenKeys {
   145  			jwks.givenKeys[kid] = key
   146  		}
   147  	}
   148  
   149  	if !options.JWKUseNoWhitelist {
   150  		jwks.jwkUseWhitelist = make(map[JWKUse]struct{})
   151  		for _, use := range options.JWKUseWhitelist {
   152  			jwks.jwkUseWhitelist[use] = struct{}{}
   153  		}
   154  	}
   155  
   156  	jwks.client = options.Client
   157  	jwks.givenKIDOverride = options.GivenKIDOverride
   158  	jwks.refreshErrorHandler = options.RefreshErrorHandler
   159  	jwks.refreshInterval = options.RefreshInterval
   160  	jwks.refreshRateLimit = options.RefreshRateLimit
   161  	jwks.refreshTimeout = options.RefreshTimeout
   162  	jwks.refreshUnknownKID = options.RefreshUnknownKID
   163  	jwks.requestFactory = options.RequestFactory
   164  	jwks.responseExtractor = options.ResponseExtractor
   165  }
   166  

View as plain text