From 4c14116a410c1706afd646334941cdef6cdf3b84 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 17 Jan 2018 21:53:53 +0100 Subject: [PATCH] added blacklist storage interface --- blacklist.go | 40 ++++++++++++++++++++++++++++++++++++++++ jwt.go | 37 +++++++++++++++---------------------- jwt_test.go | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 blacklist.go diff --git a/blacklist.go b/blacklist.go new file mode 100644 index 0000000..2b9204a --- /dev/null +++ b/blacklist.go @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Marius Schellenberger + +package jwt + +// Blacklist is the blacklisting storage interface +type Blacklist interface { + Add(string, int64) + Remove(string) + Check(string) bool + Map() MapBlacklist +} + +// MapBlacklist implements the Blacklist interface +type MapBlacklist map[string]int64 + +// NewMapBlacklist +func NewMapBlacklist() MapBlacklist { + return make(MapBlacklist) +} + +// Add adds a new token signature with expiration time to the blacklist +func (mb MapBlacklist) Add(sig string, exp int64) { + mb[sig] = exp +} + +// Remove deletes a token signature from the blacklist +func (mb MapBlacklist) Remove(sig string) { + delete(mb, sig) +} + +// Check returns true if a token signature is blacklisted and false otherwise +func (mb MapBlacklist) Check(sig string) (ok bool) { + _, ok = mb[sig] + return +} + +// Map returns the blacklist in the form of a iterable map structure for cleanup +func (mb MapBlacklist) Map() MapBlacklist { + return mb +} diff --git a/jwt.go b/jwt.go index a5a997d..7d61567 100644 --- a/jwt.go +++ b/jwt.go @@ -46,16 +46,15 @@ type JWT struct { // protects list sync.RWMutex - blacklist bool - list map[string]int64 - stop chan struct{} + blacklist Blacklist + done chan struct{} } // New returns a new JWT object with the given expiry timeout. // If the timeout is less or equal to zero the default expiry (12 hours) is used. // If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens. // Call the Stop() method to exit the goroutine. -func New(expiry time.Duration, blacklist bool) *JWT { +func New(expiry time.Duration, blacklist Blacklist) *JWT { if expiry <= 0 { expiry = DefaultExpiry } @@ -66,9 +65,8 @@ func New(expiry time.Duration, blacklist bool) *JWT { expiry: expiry, blacklist: blacklist, } - if blacklist { - jwt.list = make(map[string]int64) - jwt.stop = make(chan struct{}) + if blacklist != nil { + jwt.done = make(chan struct{}) go jwt.clean() } return jwt @@ -83,7 +81,7 @@ func (jwt *JWT) sum(token string, h Hash) []byte { // Invalidate checks if a token is already blacklisted // If the token is not blacklisted, it will get blacklisted func (jwt *JWT) Invalidate(t *Token) error { - if !jwt.blacklist { + if jwt.blacklist == nil { return ErrBlacklistNotEnabled } if t == nil { @@ -111,7 +109,7 @@ func (jwt *JWT) Invalidate(t *Token) error { } jwt.Lock() defer jwt.Unlock() - jwt.list[t.Sig()] = exp + jwt.blacklist.Add(t.Sig(), exp) return nil } @@ -122,28 +120,27 @@ func (jwt *JWT) blacklisted(sig string) error { } jwt.RLock() defer jwt.RUnlock() - _, ok := jwt.list[sig] - if ok { + if jwt.blacklist.Check(sig) { return ErrBlacklisted } return nil } -// clean will look for expired blacklisted tokens and removes them +// clean looks for expired blacklisted tokens and removes them func (jwt *JWT) clean() { for { t := time.NewTimer(time.Hour) select { case <-t.C: - case <-jwt.stop: + case <-jwt.done: t.Stop() return } now := time.Now().UTC().Unix() jwt.Lock() - for k, v := range jwt.list { + for k, v := range jwt.blacklist.Map() { if now > v { - delete(jwt.list, k) + jwt.blacklist.Remove(k) } } jwt.Unlock() @@ -243,7 +240,7 @@ func (jwt *JWT) Verify(t *Token) error { nbf = int64(n) nbfOK = true } - if expOK && jwt.blacklist { + if jwt.blacklist != nil { err := jwt.blacklisted(t.Sig()) if err != nil { return err @@ -264,14 +261,10 @@ func (jwt *JWT) Verify(t *Token) error { // Stop will end the cleaner goroutinge execution // Calling Stop twice or more will panic func (jwt *JWT) Stop() error { - if !jwt.blacklist { + if jwt.blacklist == nil { return ErrBlacklistNotEnabled } - select { - case jwt.stop <- struct{}{}: - close(jwt.stop) - default: - } + close(jwt.done) return nil } diff --git a/jwt_test.go b/jwt_test.go index 7a4673e..089df0d 100644 --- a/jwt_test.go +++ b/jwt_test.go @@ -9,7 +9,7 @@ import ( ) func TestValidate(t *testing.T) { - jwt := New(time.Second, true) + jwt := New(time.Second, NewMapBlacklist()) token := NewToken(map[string]interface{}{ "sub": "1234567890", "name": "John Doe", @@ -55,3 +55,39 @@ func TestValidate(t *testing.T) { t.Error(err) } } + +func TestNoBlacklist(t *testing.T) { + jwt := New(time.Second, nil) + token := NewToken(map[string]interface{}{ + "sub": "1234567890", + "name": "John Doe", + "admin": true, + "fizz": "buzz", + }, nil) + err := jwt.Sign(token) + if err != nil { + t.Error(err) + } + _, err = DecodeToken("") + if err == nil { + t.Error(errors.New("token is empty")) + } + nt, err := DecodeToken(token.String()) + if err != nil { + t.Error(err) + } + err = jwt.Verify(nt) + if err != nil { + t.Error(err) + } + time.Sleep(time.Second * 2) + err = jwt.Verify(nt) + if err == nil { + t.Error(errors.New("token should be expired")) + } + + err = jwt.Invalidate(nt) + if err == nil { + t.Error(errors.New("blacklisting should be disabled")) + } +}