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 }