From 9632210d0e7df3d08a416baa3df2c42bbe80ad8c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 13 Dec 2022 15:10:50 -0500 Subject: [PATCH 1/4] prevent cache locking when read and allow config timeout for cache --- adaptors/lestrratGoJwx/lestrratGoJwx.go | 7 +++++-- jwtverifier.go | 28 +++++++++++++++++++++---- utils/cache.go | 10 ++++++--- utils/cache_example_test.go | 3 ++- utils/cache_test.go | 4 ++-- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/adaptors/lestrratGoJwx/lestrratGoJwx.go b/adaptors/lestrratGoJwx/lestrratGoJwx.go index 3f6b7fb..feafa2d 100644 --- a/adaptors/lestrratGoJwx/lestrratGoJwx.go +++ b/adaptors/lestrratGoJwx/lestrratGoJwx.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "time" "github.com/lestrrat-go/jwx/jwk" "github.com/lestrrat-go/jwx/jws" @@ -33,8 +34,10 @@ func fetchJwkSet(jwkUri string) (interface{}, error) { type LestrratGoJwx struct { JWKSet jwk.Set - Cache func(func(string) (interface{}, error)) (utils.Cacher, error) + Cache func(func(string) (interface{}, error), time.Duration, time.Duration) (utils.Cacher, error) jwkSetCache utils.Cacher + Timeout time.Duration + Cleanup time.Duration } func (lgj *LestrratGoJwx) New() adaptors.Adaptor { @@ -50,7 +53,7 @@ func (lgj *LestrratGoJwx) GetKey(jwkUri string) { func (lgj *LestrratGoJwx) Decode(jwt string, jwkUri string) (interface{}, error) { if lgj.jwkSetCache == nil { - jwkSetCache, err := lgj.Cache(fetchJwkSet) + jwkSetCache, err := lgj.Cache(fetchJwkSet, lgj.Timeout, lgj.Cleanup) if err != nil { return nil, err } diff --git a/jwtverifier.go b/jwtverifier.go index 1a359c2..445fa85 100644 --- a/jwtverifier.go +++ b/jwtverifier.go @@ -20,6 +20,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "log" "net/http" "regexp" "strings" @@ -47,11 +48,13 @@ type JwtVerifier struct { Adaptor adaptors.Adaptor // Cache allows customization of the cache used to store resources - Cache func(func(string) (interface{}, error)) (utils.Cacher, error) + Cache func(func(string) (interface{}, error), time.Duration, time.Duration) (utils.Cacher, error) metadataCache utils.Cacher - leeway int64 + leeway int64 + Timeout time.Duration + Cleanup time.Duration } type Jwt struct { @@ -78,19 +81,28 @@ func fetchMetaData(url string) (interface{}, error) { } func (j *JwtVerifier) New() *JwtVerifier { + log.Println("here") // Default to OIDC discovery if none is defined if j.Discovery == nil { disc := oidc.Oidc{} j.Discovery = disc.New() } + if j.Timeout == 0 { + j.Timeout = 5 * time.Minute + } + + if j.Cleanup == 0 { + j.Cleanup = 10 * time.Minute + } + if j.Cache == nil { j.Cache = utils.NewDefaultCache } // Default to LestrratGoJwx Adaptor if none is defined if j.Adaptor == nil { - adaptor := &lestrratGoJwx.LestrratGoJwx{Cache: j.Cache} + adaptor := &lestrratGoJwx.LestrratGoJwx{Cache: j.Cache, Timeout: j.Timeout, Cleanup: j.Cleanup} j.Adaptor = adaptor.New() } @@ -105,6 +117,14 @@ func (j *JwtVerifier) SetLeeway(duration string) { j.leeway = int64(dur.Seconds()) } +func (j *JwtVerifier) SetTimeOut(duration time.Duration) { + j.Timeout = duration +} + +func (j *JwtVerifier) SetCleanUp(duration time.Duration) { + j.Cleanup = duration +} + func (j *JwtVerifier) VerifyAccessToken(jwt string) (*Jwt, error) { validJwt, err := j.isValidJwt(jwt) if !validJwt { @@ -317,7 +337,7 @@ func (j *JwtVerifier) getMetaData() (map[string]interface{}, error) { metaDataUrl := j.Issuer + j.Discovery.GetWellKnownUrl() if j.metadataCache == nil { - metadataCache, err := j.Cache(fetchMetaData) + metadataCache, err := j.Cache(fetchMetaData, j.Timeout, j.Cleanup) if err != nil { return nil, err } diff --git a/utils/cache.go b/utils/cache.go index be795af..215409f 100644 --- a/utils/cache.go +++ b/utils/cache.go @@ -21,9 +21,13 @@ type defaultCache struct { } func (c *defaultCache) Get(key string) (interface{}, error) { + if value, found := c.cache.Get(key); found { + return value, nil + } c.mutex.Lock() defer c.mutex.Unlock() - + // once lock, check the cache again because there could be + // another thread that has update the keys during the last check if value, found := c.cache.Get(key); found { return value, nil } @@ -41,9 +45,9 @@ func (c *defaultCache) Get(key string) (interface{}, error) { var _ Cacher = (*defaultCache)(nil) // NewDefaultCache returns cache with a 5 minute expiration. -func NewDefaultCache(lookup func(string) (interface{}, error)) (Cacher, error) { +func NewDefaultCache(lookup func(string) (interface{}, error), timeout, cleanup time.Duration) (Cacher, error) { return &defaultCache{ - cache: cache.New(5*time.Minute, 10*time.Minute), + cache: cache.New(timeout, cleanup), lookup: lookup, mutex: &sync.Mutex{}, }, nil diff --git a/utils/cache_example_test.go b/utils/cache_example_test.go index 2f27055..db7ade3 100644 --- a/utils/cache_example_test.go +++ b/utils/cache_example_test.go @@ -2,6 +2,7 @@ package utils_test import ( "fmt" + "time" jwtverifier "github.com/okta/okta-jwt-verifier-golang" "github.com/okta/okta-jwt-verifier-golang/utils" @@ -31,7 +32,7 @@ func (c *ForeverCache) Get(key string) (interface{}, error) { var _ utils.Cacher = (*ForeverCache)(nil) // NewForeverCache takes a lookup function and returns a cache -func NewForeverCache(lookup func(string) (interface{}, error)) (utils.Cacher, error) { +func NewForeverCache(lookup func(string) (interface{}, error), t, c time.Duration) (utils.Cacher, error) { return &ForeverCache{ values: map[string]interface{}{}, lookup: lookup, diff --git a/utils/cache_test.go b/utils/cache_test.go index 0728aa3..b09b7aa 100644 --- a/utils/cache_test.go +++ b/utils/cache_test.go @@ -2,6 +2,7 @@ package utils_test import ( "testing" + "time" "github.com/okta/okta-jwt-verifier-golang/utils" ) @@ -14,8 +15,7 @@ func TestNewDefaultCache(t *testing.T) { lookup := func(key string) (interface{}, error) { return &Value{key: key}, nil } - - cache, err := utils.NewDefaultCache(lookup) + cache, err := utils.NewDefaultCache(lookup, 5*time.Minute, 10*time.Minute) if err != nil { t.Fatalf("unexpected error: %v", err) } From e95d62245c7d4125adf16073bf49711fca5719bf Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 4 Jan 2023 14:20:48 -0500 Subject: [PATCH 2/4] add circleci config --- .circleci/config.yml | 13 +++++++++++++ CHANGELOG.md | 6 ++++++ README.md | 5 +++-- jwtverifier.go | 2 -- utils/cache.go | 1 - 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..0a03000 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,13 @@ +version: 2.1 +jobs: + test: + description: Run tests + steps: + - checkout + - run: + name: Test Runner + command: make test +workflows: + "Circle CI Tests": + jobs: + - test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f05a3ab..c0e303f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.4.0 (January 4th, 2023) + +### Enhancements: + +* Customizable cache timeout and change to the cache method. [#92](https://github.com/okta/okta-jwt-verifier-golang/pull/92) + ## v1.3.1 (April 6th, 2022) ### Updates: diff --git a/README.md b/README.md index 80aa439..c710e99 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,9 @@ verifier.SetLeeway("2m") //String instance of time that will be parsed by `time. The verifier setup has a default cache based on [`patrickmn/go-cache`](https://github.com/patrickmn/go-cache) with a 5 minute -expiry and 10 minute purge setting that is used to store resources fetched over -HTTP. It also defines a `Cacher` interface with a `Get` method allowing +expiry and 10 minute purge default setting that is used to store resources fetched over +HTTP. The expiry and purge setting is configurable through SetCleanUp and SetTimeOut method. +It also defines a `Cacher` interface with a `Get` method allowing customization of that caching. If you want to establish your own caching strategy then provide your own `Cacher` object that implements that interface. Your custom cache is set in the verifier via the `Cache` attribute. See the diff --git a/jwtverifier.go b/jwtverifier.go index 445fa85..3ab7c0c 100644 --- a/jwtverifier.go +++ b/jwtverifier.go @@ -20,7 +20,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "log" "net/http" "regexp" "strings" @@ -81,7 +80,6 @@ func fetchMetaData(url string) (interface{}, error) { } func (j *JwtVerifier) New() *JwtVerifier { - log.Println("here") // Default to OIDC discovery if none is defined if j.Discovery == nil { disc := oidc.Oidc{} diff --git a/utils/cache.go b/utils/cache.go index 215409f..e15a51c 100644 --- a/utils/cache.go +++ b/utils/cache.go @@ -44,7 +44,6 @@ func (c *defaultCache) Get(key string) (interface{}, error) { // defaultCache implements the Cacher interface var _ Cacher = (*defaultCache)(nil) -// NewDefaultCache returns cache with a 5 minute expiration. func NewDefaultCache(lookup func(string) (interface{}, error), timeout, cleanup time.Duration) (Cacher, error) { return &defaultCache{ cache: cache.New(timeout, cleanup), From 7cff0caa273b6c130e19c280858c73fce5c7a064 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 4 Jan 2023 15:37:36 -0500 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e303f..ef0ba2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v1.4.0 (January 4th, 2023) +## v2.0.0 (January 4th, 2023) ### Enhancements: From 1d51eb0ca13872740996d5157d3a9ffb0df3f761 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 4 Jan 2023 17:58:06 -0500 Subject: [PATCH 4/4] remove cci config --- .circleci/config.yml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 0a03000..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 2.1 -jobs: - test: - description: Run tests - steps: - - checkout - - run: - name: Test Runner - command: make test -workflows: - "Circle CI Tests": - jobs: - - test \ No newline at end of file