Skip to content

Commit

Permalink
Merge pull request #92 from okta/OKTA-554042-prevent-cache-locking
Browse files Browse the repository at this point in the history
prevent cache locking when read and allow config timeout for cache
  • Loading branch information
MikeMondragon-okta authored Jan 4, 2023
2 parents 93264d2 + aa1e0e4 commit 06f5827
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 15 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v2.0.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:
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions adaptors/lestrratGoJwx/lestrratGoJwx.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"time"

"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
26 changes: 22 additions & 4 deletions jwtverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,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 {
Expand Down Expand Up @@ -84,13 +86,21 @@ func (j *JwtVerifier) New() *JwtVerifier {
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()
}

Expand All @@ -105,6 +115,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 {
Expand Down Expand Up @@ -317,7 +335,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
}
Expand Down
11 changes: 7 additions & 4 deletions utils/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -40,10 +44,9 @@ 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)) (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
Expand Down
3 changes: 2 additions & 1 deletion utils/cache_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions utils/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package utils_test

import (
"testing"
"time"

"github.com/okta/okta-jwt-verifier-golang/utils"
)
Expand All @@ -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)
}
Expand Down

0 comments on commit 06f5827

Please sign in to comment.