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