github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/instance/lifecycle/store.go (about)

     1  package lifecycle
     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 magic link codes.
    14  //
    15  // TODO: Move to [token.Service] with [token.MagicLink] namespace.
    16  type Store interface {
    17  	SaveMagicLinkCode(db prefixer.Prefixer, code string) error
    18  	CheckMagicLinkCode(db prefixer.Prefixer, code string) bool
    19  }
    20  
    21  // storeTTL is the time an entry stay alive
    22  var storeTTL = 15 * time.Minute
    23  
    24  // storeCleanInterval is the time interval between each cleanup.
    25  var storeCleanInterval = 1 * time.Hour
    26  
    27  var mu sync.Mutex
    28  var globalStore Store
    29  
    30  // GetStore returns the store for temporary move objects.
    31  func GetStore() Store {
    32  	mu.Lock()
    33  	defer mu.Unlock()
    34  	if globalStore != nil {
    35  		return globalStore
    36  	}
    37  	cli := config.GetConfig().SessionStorage
    38  	if cli == nil {
    39  		globalStore = newMemStore()
    40  	} else {
    41  		ctx := context.Background()
    42  		globalStore = &redisStore{cli, ctx}
    43  	}
    44  	return globalStore
    45  }
    46  
    47  func newMemStore() Store {
    48  	store := &memStore{vals: make(map[string]time.Time)}
    49  	go store.cleaner()
    50  	return store
    51  }
    52  
    53  type memStore struct {
    54  	mu   sync.Mutex
    55  	vals map[string]time.Time // code -> expiration time
    56  }
    57  
    58  func (s *memStore) cleaner() {
    59  	for range time.Tick(storeCleanInterval) {
    60  		now := time.Now()
    61  		for k, v := range s.vals {
    62  			if now.After(v) {
    63  				delete(s.vals, k)
    64  			}
    65  		}
    66  	}
    67  }
    68  
    69  func (s *memStore) SaveMagicLinkCode(db prefixer.Prefixer, code string) error {
    70  	s.mu.Lock()
    71  	defer s.mu.Unlock()
    72  	s.vals[code] = time.Now().Add(storeTTL)
    73  	return nil
    74  }
    75  
    76  func (s *memStore) CheckMagicLinkCode(db prefixer.Prefixer, code string) bool {
    77  	s.mu.Lock()
    78  	defer s.mu.Unlock()
    79  	exp, ok := s.vals[code]
    80  	if !ok {
    81  		return false
    82  	}
    83  	return time.Now().Before(exp)
    84  }
    85  
    86  type redisStore struct {
    87  	c   redis.UniversalClient
    88  	ctx context.Context
    89  }
    90  
    91  func (s *redisStore) SaveMagicLinkCode(db prefixer.Prefixer, code string) error {
    92  	key := magicLinkCodeKey(db, code)
    93  	return s.c.Set(s.ctx, key, "1", storeTTL).Err()
    94  }
    95  
    96  func (s *redisStore) CheckMagicLinkCode(db prefixer.Prefixer, code string) bool {
    97  	key := magicLinkCodeKey(db, code)
    98  	r, err := s.c.Exists(s.ctx, key).Result()
    99  	return err == nil && r > 0
   100  }
   101  
   102  func magicLinkCodeKey(db prefixer.Prefixer, suffix string) string {
   103  	return db.DBPrefix() + ":magic_link:" + suffix
   104  }