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

     1  package instance
     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  	SaveSessionCode(db prefixer.Prefixer, code string) error
    16  	SaveEmailVerfiedCode(db prefixer.Prefixer, code string) error
    17  	CheckAndClearSessionCode(db prefixer.Prefixer, code string) bool
    18  	CheckEmailVerifiedCode(db prefixer.Prefixer, code string) bool
    19  }
    20  
    21  // sessionCodeTTL is the time an entry for a session_code stays alive (1 week)
    22  var sessionCodeTTL = 7 * 24 * time.Hour
    23  
    24  // emailVerifiedCodeTTL is the time an entry for an email_verified_code stays alive
    25  var emailVerifiedCodeTTL = 15 * time.Minute
    26  
    27  // storeCleanInterval is the time interval between each cleanup.
    28  var storeCleanInterval = 1 * time.Hour
    29  
    30  var mu sync.Mutex
    31  var globalStore Store
    32  
    33  // GetStore returns the store for temporary move objects.
    34  func GetStore() Store {
    35  	mu.Lock()
    36  	defer mu.Unlock()
    37  	if globalStore != nil {
    38  		return globalStore
    39  	}
    40  	cli := config.GetConfig().SessionStorage
    41  	if cli == nil {
    42  		globalStore = newMemStore()
    43  	} else {
    44  		ctx := context.Background()
    45  		globalStore = &redisStore{cli, ctx}
    46  	}
    47  	return globalStore
    48  }
    49  
    50  func newMemStore() Store {
    51  	store := &memStore{vals: make(map[string]time.Time)}
    52  	go store.cleaner()
    53  	return store
    54  }
    55  
    56  type memStore struct {
    57  	mu   sync.Mutex
    58  	vals map[string]time.Time // session_code -> expiration time
    59  }
    60  
    61  func (s *memStore) cleaner() {
    62  	for range time.Tick(storeCleanInterval) {
    63  		now := time.Now()
    64  		for k, v := range s.vals {
    65  			if now.After(v) {
    66  				delete(s.vals, k)
    67  			}
    68  		}
    69  	}
    70  }
    71  
    72  func (s *memStore) SaveSessionCode(db prefixer.Prefixer, code string) error {
    73  	s.mu.Lock()
    74  	defer s.mu.Unlock()
    75  	key := sessionCodeKey(db, code)
    76  	s.vals[key] = time.Now().Add(sessionCodeTTL)
    77  	return nil
    78  }
    79  
    80  func (s *memStore) SaveEmailVerfiedCode(db prefixer.Prefixer, code string) error {
    81  	s.mu.Lock()
    82  	defer s.mu.Unlock()
    83  	key := emailVerifiedCodeKey(db, code)
    84  	s.vals[key] = time.Now().Add(emailVerifiedCodeTTL)
    85  	return nil
    86  }
    87  
    88  func (s *memStore) CheckAndClearSessionCode(db prefixer.Prefixer, code string) bool {
    89  	s.mu.Lock()
    90  	defer s.mu.Unlock()
    91  	key := sessionCodeKey(db, code)
    92  	exp, ok := s.vals[key]
    93  	if !ok {
    94  		return false
    95  	}
    96  	delete(s.vals, key)
    97  	return time.Now().Before(exp)
    98  }
    99  
   100  func (s *memStore) CheckEmailVerifiedCode(db prefixer.Prefixer, code string) bool {
   101  	s.mu.Lock()
   102  	defer s.mu.Unlock()
   103  	key := emailVerifiedCodeKey(db, code)
   104  	exp, ok := s.vals[key]
   105  	if !ok {
   106  		return false
   107  	}
   108  	if time.Now().After(exp) {
   109  		delete(s.vals, key)
   110  		return false
   111  	}
   112  	return true
   113  }
   114  
   115  type redisStore struct {
   116  	c   redis.UniversalClient
   117  	ctx context.Context
   118  }
   119  
   120  func (s *redisStore) SaveSessionCode(db prefixer.Prefixer, code string) error {
   121  	key := sessionCodeKey(db, code)
   122  	return s.c.Set(s.ctx, key, "1", sessionCodeTTL).Err()
   123  }
   124  
   125  func (s *redisStore) SaveEmailVerfiedCode(db prefixer.Prefixer, code string) error {
   126  	key := emailVerifiedCodeKey(db, code)
   127  	return s.c.Set(s.ctx, key, "1", emailVerifiedCodeTTL).Err()
   128  }
   129  
   130  func (s *redisStore) CheckAndClearSessionCode(db prefixer.Prefixer, code string) bool {
   131  	key := sessionCodeKey(db, code)
   132  	n, err := s.c.Del(s.ctx, key).Result()
   133  	return err == nil && n > 0
   134  }
   135  
   136  func (s *redisStore) CheckEmailVerifiedCode(db prefixer.Prefixer, code string) bool {
   137  	key := emailVerifiedCodeKey(db, code)
   138  	n, err := s.c.Exists(s.ctx, key).Result()
   139  	return err == nil && n > 0
   140  }
   141  
   142  func sessionCodeKey(db prefixer.Prefixer, suffix string) string {
   143  	return db.DBPrefix() + ":sessioncode:" + suffix
   144  }
   145  
   146  func emailVerifiedCodeKey(db prefixer.Prefixer, suffix string) string {
   147  	return db.DBPrefix() + ":emailverifiedcode:" + suffix
   148  }