github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/move/store.go (about) 1 package move 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "errors" 8 "sync" 9 "time" 10 11 "github.com/cozy/cozy-stack/pkg/config/config" 12 "github.com/cozy/cozy-stack/pkg/crypto" 13 "github.com/cozy/cozy-stack/pkg/prefixer" 14 "github.com/redis/go-redis/v9" 15 ) 16 17 // Store is essentially an object to store and retrieve move requests 18 type Store interface { 19 GetRequest(db prefixer.Prefixer, secret string) (*Request, error) 20 SaveRequest(db prefixer.Prefixer, req *Request) (string, error) 21 SetAllowDeleteAccounts(db prefixer.Prefixer) error 22 ClearAllowDeleteAccounts(db prefixer.Prefixer) error 23 AllowDeleteAccounts(db prefixer.Prefixer) bool 24 } 25 26 // storeTTL is the time an entry stay alive 27 var storeTTL = 5 * time.Minute 28 29 // storeCleanInterval is the time interval between each cleanup. 30 var storeCleanInterval = 1 * time.Hour 31 32 var mu sync.Mutex 33 var globalStore Store 34 35 // GetStore returns the store for temporary move objects. 36 func GetStore() Store { 37 mu.Lock() 38 defer mu.Unlock() 39 if globalStore != nil { 40 return globalStore 41 } 42 cli := config.GetConfig().SessionStorage 43 if cli == nil { 44 globalStore = newMemStore() 45 } else { 46 ctx := context.Background() 47 globalStore = &redisStore{cli, ctx} 48 } 49 return globalStore 50 } 51 52 type memRef struct { 53 val *Request 54 exp time.Time 55 } 56 57 func newMemStore() Store { 58 store := &memStore{vals: make(map[string]*memRef)} 59 go store.cleaner() 60 return store 61 } 62 63 type memStore struct { 64 mu sync.Mutex 65 vals map[string]*memRef 66 } 67 68 func (s *memStore) cleaner() { 69 for range time.Tick(storeCleanInterval) { 70 now := time.Now() 71 for k, v := range s.vals { 72 if now.After(v.exp) { 73 delete(s.vals, k) 74 } 75 } 76 } 77 } 78 79 func (s *memStore) GetRequest(db prefixer.Prefixer, secret string) (*Request, error) { 80 s.mu.Lock() 81 defer s.mu.Unlock() 82 key := requestKey(db, secret) 83 ref, ok := s.vals[key] 84 if !ok { 85 return nil, nil 86 } 87 if time.Now().After(ref.exp) { 88 delete(s.vals, key) 89 return nil, nil 90 } 91 return ref.val, nil 92 } 93 94 func (s *memStore) SaveRequest(db prefixer.Prefixer, req *Request) (string, error) { 95 secret := makeSecret() 96 s.mu.Lock() 97 defer s.mu.Unlock() 98 key := requestKey(db, secret) 99 s.vals[key] = &memRef{ 100 val: req, 101 exp: time.Now().Add(storeTTL), 102 } 103 return secret, nil 104 } 105 106 func (s *memStore) SetAllowDeleteAccounts(db prefixer.Prefixer) error { 107 s.mu.Lock() 108 defer s.mu.Unlock() 109 key := deleteAccountsKey(db) 110 s.vals[key] = &memRef{ 111 val: nil, 112 exp: time.Now().Add(storeTTL), 113 } 114 return nil 115 } 116 117 func (s *memStore) ClearAllowDeleteAccounts(db prefixer.Prefixer) error { 118 s.mu.Lock() 119 defer s.mu.Unlock() 120 key := deleteAccountsKey(db) 121 delete(s.vals, key) 122 return nil 123 } 124 125 func (s *memStore) AllowDeleteAccounts(db prefixer.Prefixer) bool { 126 s.mu.Lock() 127 defer s.mu.Unlock() 128 key := deleteAccountsKey(db) 129 ref, ok := s.vals[key] 130 if !ok { 131 return false 132 } 133 if time.Now().After(ref.exp) { 134 delete(s.vals, key) 135 return false 136 } 137 return true 138 } 139 140 type redisStore struct { 141 c redis.UniversalClient 142 ctx context.Context 143 } 144 145 func (s *redisStore) GetRequest(db prefixer.Prefixer, secret string) (*Request, error) { 146 key := requestKey(db, secret) 147 b, err := s.c.Get(s.ctx, key).Bytes() 148 if errors.Is(err, redis.Nil) { 149 return nil, nil 150 } 151 if err != nil { 152 return nil, err 153 } 154 var req Request 155 if err = json.Unmarshal(b, &req); err != nil { 156 return nil, err 157 } 158 return &req, nil 159 } 160 161 func (s *redisStore) SaveRequest(db prefixer.Prefixer, req *Request) (string, error) { 162 v, err := json.Marshal(req) 163 if err != nil { 164 return "", err 165 } 166 secret := makeSecret() 167 key := requestKey(db, secret) 168 if err = s.c.Set(s.ctx, key, v, storeTTL).Err(); err != nil { 169 return "", err 170 } 171 return secret, nil 172 } 173 174 func (s *redisStore) SetAllowDeleteAccounts(db prefixer.Prefixer) error { 175 key := deleteAccountsKey(db) 176 return s.c.Set(s.ctx, key, true, storeTTL).Err() 177 } 178 179 func (s *redisStore) ClearAllowDeleteAccounts(db prefixer.Prefixer) error { 180 key := deleteAccountsKey(db) 181 return s.c.Del(s.ctx, key).Err() 182 } 183 184 func (s *redisStore) AllowDeleteAccounts(db prefixer.Prefixer) bool { 185 key := deleteAccountsKey(db) 186 r, err := s.c.Exists(s.ctx, key).Result() 187 if err != nil { 188 return false 189 } 190 return r > 0 191 } 192 193 func requestKey(db prefixer.Prefixer, suffix string) string { 194 return db.DBPrefix() + ":req:" + suffix 195 } 196 197 func deleteAccountsKey(db prefixer.Prefixer) string { 198 return db.DBPrefix() + ":allow_delete_accounts" 199 } 200 201 func makeSecret() string { 202 return hex.EncodeToString(crypto.GenerateRandomBytes(8)) 203 }