mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
132 lines
3.3 KiB
Go
132 lines
3.3 KiB
Go
|
package jwt
|
||
|
|
||
|
import (
|
||
|
stdContext "context"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Blocklist is an in-memory storage of tokens that should be
|
||
|
// immediately invalidated by the server-side.
|
||
|
// The most common way to invalidate a token, e.g. on user logout,
|
||
|
// is to make the client-side remove the token itself.
|
||
|
// However, if someone else has access to that token,
|
||
|
// it could be still valid for new requests until its expiration.
|
||
|
type Blocklist struct {
|
||
|
entries map[string]time.Time // key = token | value = expiration time (to remove expired).
|
||
|
mu sync.RWMutex
|
||
|
}
|
||
|
|
||
|
// NewBlocklist returns a new up and running in-memory Token Blocklist.
|
||
|
// The returned value can be set to the JWT instance's Blocklist field.
|
||
|
func NewBlocklist(gcEvery time.Duration) *Blocklist {
|
||
|
return NewBlocklistContext(stdContext.Background(), gcEvery)
|
||
|
}
|
||
|
|
||
|
// NewBlocklistContext same as `NewBlocklist`
|
||
|
// but it also accepts a standard Go Context for GC cancelation.
|
||
|
func NewBlocklistContext(ctx stdContext.Context, gcEvery time.Duration) *Blocklist {
|
||
|
b := &Blocklist{
|
||
|
entries: make(map[string]time.Time),
|
||
|
}
|
||
|
|
||
|
if gcEvery > 0 {
|
||
|
go b.runGC(ctx, gcEvery)
|
||
|
}
|
||
|
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
// Set upserts a given token, with its expiration time,
|
||
|
// to the block list, so it's immediately invalidated by the server-side.
|
||
|
func (b *Blocklist) Set(token string, expiresAt time.Time) {
|
||
|
b.mu.Lock()
|
||
|
b.entries[token] = expiresAt
|
||
|
b.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
// Del removes a "token" from the block list.
|
||
|
func (b *Blocklist) Del(token string) {
|
||
|
b.mu.Lock()
|
||
|
delete(b.entries, token)
|
||
|
b.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
// Count returns the total amount of blocked tokens.
|
||
|
func (b *Blocklist) Count() int {
|
||
|
b.mu.RLock()
|
||
|
n := len(b.entries)
|
||
|
b.mu.RUnlock()
|
||
|
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// Has reports whether the given "token" is blocked by the server.
|
||
|
// This method is called before the token verification,
|
||
|
// so even if was expired it is removed from the block list.
|
||
|
func (b *Blocklist) Has(token string) bool {
|
||
|
if token == "" {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
b.mu.RLock()
|
||
|
_, ok := b.entries[token]
|
||
|
b.mu.RUnlock()
|
||
|
|
||
|
/* No, the Blocklist will be used after the token is parsed,
|
||
|
there we can call the Del method if err was ErrExpired.
|
||
|
if ok {
|
||
|
// As an extra step, to keep the list size as small as possible,
|
||
|
// we delete it from list if it's going to be expired
|
||
|
// ~in the next `blockedExpireLeeway` seconds.~
|
||
|
// - Let's keep it easier for testing by not setting a leeway.
|
||
|
// if time.Now().Add(blockedExpireLeeway).After(expiresAt) {
|
||
|
if time.Now().After(expiresAt) {
|
||
|
b.Del(token)
|
||
|
}
|
||
|
}*/
|
||
|
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
// GC iterates over all entries and removes expired tokens.
|
||
|
// This method is helpful to keep the list size small.
|
||
|
// Depending on the application, the GC method can be scheduled
|
||
|
// to called every half or a whole hour.
|
||
|
// A good value for a GC cron task is the JWT's max age (default).
|
||
|
func (b *Blocklist) GC() int {
|
||
|
now := time.Now()
|
||
|
var markedForDeletion []string
|
||
|
|
||
|
b.mu.RLock()
|
||
|
for token, expiresAt := range b.entries {
|
||
|
if now.After(expiresAt) {
|
||
|
markedForDeletion = append(markedForDeletion, token)
|
||
|
}
|
||
|
}
|
||
|
b.mu.RUnlock()
|
||
|
|
||
|
n := len(markedForDeletion)
|
||
|
if n > 0 {
|
||
|
for _, token := range markedForDeletion {
|
||
|
b.Del(token)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
func (b *Blocklist) runGC(ctx stdContext.Context, every time.Duration) {
|
||
|
t := time.NewTicker(every)
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
t.Stop()
|
||
|
return
|
||
|
case <-t.C:
|
||
|
b.GC()
|
||
|
}
|
||
|
}
|
||
|
}
|