github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/auth/store.go (about) 1 package auth 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "sync" 8 "time" 9 10 "github.com/cozy/cozy-stack/pkg/config/config" 11 "github.com/cozy/cozy-stack/pkg/crypto" 12 "github.com/cozy/cozy-stack/pkg/prefixer" 13 "github.com/redis/go-redis/v9" 14 ) 15 16 // Store is essentially an object to store and retrieve confirmation codes 17 type Store interface { 18 AddCode(db prefixer.Prefixer) (string, error) 19 GetCode(db prefixer.Prefixer, code string) (bool, error) 20 } 21 22 // storeTTL is the time an entry stay alive 23 var storeTTL = 5 * time.Minute 24 25 // storeCleanInterval is the time interval between each cleanup. 26 var storeCleanInterval = 1 * time.Hour 27 28 var mu sync.Mutex 29 var globalStore Store 30 31 // GetStore returns the store for temporary move objects. 32 func GetStore() Store { 33 mu.Lock() 34 defer mu.Unlock() 35 if globalStore != nil { 36 return globalStore 37 } 38 cli := config.GetConfig().SessionStorage 39 if cli == nil { 40 globalStore = newMemStore() 41 } else { 42 ctx := context.Background() 43 globalStore = &redisStore{cli, ctx} 44 } 45 return globalStore 46 } 47 48 type memStore struct { 49 mu sync.Mutex 50 vals map[string]time.Time 51 } 52 53 func newMemStore() Store { 54 store := &memStore{vals: make(map[string]time.Time)} 55 go store.cleaner() 56 return store 57 } 58 59 func (s *memStore) cleaner() { 60 for range time.Tick(storeCleanInterval) { 61 now := time.Now() 62 for k, v := range s.vals { 63 if now.After(v) { 64 delete(s.vals, k) 65 } 66 } 67 } 68 } 69 70 func (s *memStore) AddCode(db prefixer.Prefixer) (string, error) { 71 code := makeSecret() 72 s.mu.Lock() 73 defer s.mu.Unlock() 74 key := confirmKey(db, code) 75 s.vals[key] = time.Now().Add(storeTTL) 76 return code, nil 77 } 78 79 func (s *memStore) GetCode(db prefixer.Prefixer, code string) (bool, error) { 80 s.mu.Lock() 81 defer s.mu.Unlock() 82 key := confirmKey(db, code) 83 exp, ok := s.vals[key] 84 if !ok { 85 return false, nil 86 } 87 if time.Now().After(exp) { 88 delete(s.vals, key) 89 return false, nil 90 } 91 return true, nil 92 } 93 94 type redisStore struct { 95 c redis.UniversalClient 96 ctx context.Context 97 } 98 99 func (s *redisStore) AddCode(db prefixer.Prefixer) (string, error) { 100 code := makeSecret() 101 key := confirmKey(db, code) 102 if err := s.c.Set(s.ctx, key, "1", storeTTL).Err(); err != nil { 103 return "", err 104 } 105 return code, nil 106 } 107 108 func (s *redisStore) GetCode(db prefixer.Prefixer, code string) (bool, error) { 109 key := confirmKey(db, code) 110 n, err := s.c.Exists(s.ctx, key).Result() 111 if errors.Is(err, redis.Nil) || n == 0 { 112 return false, nil 113 } 114 if err != nil { 115 return false, err 116 } 117 return true, nil 118 } 119 120 func confirmKey(db prefixer.Prefixer, suffix string) string { 121 return db.DBPrefix() + ":confirm_auth:" + suffix 122 } 123 124 func makeSecret() string { 125 return hex.EncodeToString(crypto.GenerateRandomBytes(8)) 126 }