github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/oauth/store.go (about)

     1  package oauth
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/cozy/cozy-stack/pkg/config/config"
     9  	"github.com/cozy/cozy-stack/pkg/prefixer"
    10  	"github.com/redis/go-redis/v9"
    11  )
    12  
    13  // Store is an object to store and retrieve session codes.
    14  type Store interface {
    15  	SaveChallenge(db prefixer.Prefixer, clientID, nonce string) error
    16  	CheckAndClearChallenge(db prefixer.Prefixer, clientID, nonce string) bool
    17  }
    18  
    19  // storeTTL is the time an entry stay alive (1 week)
    20  var storeTTL = 7 * 24 * time.Hour
    21  
    22  // storeCleanInterval is the time interval between each cleanup.
    23  var storeCleanInterval = 1 * time.Hour
    24  
    25  var mu sync.Mutex
    26  var globalStore Store
    27  
    28  // GetStore returns the store for temporary move objects.
    29  func GetStore() Store {
    30  	mu.Lock()
    31  	defer mu.Unlock()
    32  	if globalStore != nil {
    33  		return globalStore
    34  	}
    35  	cli := config.GetConfig().SessionStorage
    36  	if cli == nil {
    37  		globalStore = newMemStore()
    38  	} else {
    39  		ctx := context.Background()
    40  		globalStore = &redisStore{cli, ctx}
    41  	}
    42  	return globalStore
    43  }
    44  
    45  func newMemStore() Store {
    46  	store := &memStore{vals: make(map[string]time.Time)}
    47  	go store.cleaner()
    48  	return store
    49  }
    50  
    51  type memStore struct {
    52  	mu   sync.Mutex
    53  	vals map[string]time.Time // session_code -> expiration time
    54  }
    55  
    56  func (s *memStore) cleaner() {
    57  	for range time.Tick(storeCleanInterval) {
    58  		now := time.Now()
    59  		for k, v := range s.vals {
    60  			if now.After(v) {
    61  				delete(s.vals, k)
    62  			}
    63  		}
    64  	}
    65  }
    66  
    67  func (s *memStore) SaveChallenge(db prefixer.Prefixer, clientID, nonce string) error {
    68  	s.mu.Lock()
    69  	defer s.mu.Unlock()
    70  	key := challengeKey(db, clientID, nonce)
    71  	s.vals[key] = time.Now().Add(storeTTL)
    72  	return nil
    73  }
    74  
    75  func (s *memStore) CheckAndClearChallenge(db prefixer.Prefixer, clientID, nonce string) bool {
    76  	s.mu.Lock()
    77  	defer s.mu.Unlock()
    78  	key := challengeKey(db, clientID, nonce)
    79  	exp, ok := s.vals[key]
    80  	if !ok {
    81  		return false
    82  	}
    83  	delete(s.vals, key)
    84  	return time.Now().Before(exp)
    85  }
    86  
    87  type redisStore struct {
    88  	c   redis.UniversalClient
    89  	ctx context.Context
    90  }
    91  
    92  func (s *redisStore) SaveChallenge(db prefixer.Prefixer, clientID, nonce string) error {
    93  	key := challengeKey(db, clientID, nonce)
    94  	return s.c.Set(s.ctx, key, "1", storeTTL).Err()
    95  }
    96  
    97  func (s *redisStore) CheckAndClearChallenge(db prefixer.Prefixer, clientID, nonce string) bool {
    98  	key := challengeKey(db, clientID, nonce)
    99  	n, err := s.c.Del(s.ctx, key).Result()
   100  	return err == nil && n > 0
   101  }
   102  
   103  func challengeKey(db prefixer.Prefixer, clientID, nonce string) string {
   104  	return db.DBPrefix() + ":challenge:" + clientID + ":" + nonce
   105  }