github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/instance/service.go (about) 1 package instance 2 3 import ( 4 "encoding/json" 5 "time" 6 7 "github.com/cozy/cozy-stack/pkg/cache" 8 "github.com/cozy/cozy-stack/pkg/couchdb" 9 "github.com/cozy/cozy-stack/pkg/crypto" 10 "github.com/cozy/cozy-stack/pkg/logger" 11 "github.com/cozy/cozy-stack/pkg/prefixer" 12 ) 13 14 const cacheTTL = 5 * time.Minute 15 const cachePrefix = "i:" 16 17 type InstanceService struct { 18 cache cache.Cache 19 logger logger.Logger 20 } 21 22 func NewService(cache cache.Cache, logger logger.Logger) *InstanceService { 23 return &InstanceService{ 24 cache: cache, 25 logger: logger, 26 } 27 } 28 29 // Get finds an instance from its domain by using CouchDB or the cache. 30 func (s *InstanceService) Get(domain string) (*Instance, error) { 31 if data, ok := s.cache.Get(cachePrefix + domain); ok { 32 inst := &Instance{} 33 err := json.Unmarshal(data, inst) 34 if err == nil && inst.MakeVFS() == nil { 35 return inst, nil 36 } 37 } 38 39 inst, err := s.GetWithoutCache(domain) 40 if err != nil { 41 return nil, err 42 } 43 44 if data, err := json.Marshal(inst); err == nil { 45 s.cache.SetNX(cacheKey(inst), data, cacheTTL) 46 } 47 return inst, nil 48 } 49 50 // GetWithoutCache finds an instance in CouchDB from its domain. 51 // 52 // NOTE: You should probably use [InstanceService.Get] instead. This method 53 // is only useful if you want to bypass the cache. 54 func (s *InstanceService) GetWithoutCache(domain string) (*Instance, error) { 55 var res couchdb.ViewResponse 56 err := couchdb.ExecView(prefixer.GlobalPrefixer, couchdb.DomainAndAliasesView, &couchdb.ViewRequest{ 57 Key: domain, 58 IncludeDocs: true, 59 Limit: 1, 60 }, &res) 61 if couchdb.IsNoDatabaseError(err) { 62 return nil, ErrNotFound 63 } 64 65 if err != nil { 66 return nil, err 67 } 68 69 if len(res.Rows) == 0 { 70 return nil, ErrNotFound 71 } 72 73 inst := &Instance{} 74 err = json.Unmarshal(res.Rows[0].Doc, inst) 75 if err != nil { 76 return nil, err 77 } 78 79 if err = inst.MakeVFS(); err != nil { 80 return nil, err 81 } 82 83 return inst, nil 84 } 85 86 // Update saves the changes in CouchDB. 87 func (s *InstanceService) Update(inst *Instance) error { 88 if err := couchdb.UpdateDoc(prefixer.GlobalPrefixer, inst); err != nil { 89 return err 90 } 91 92 if data, err := json.Marshal(inst); err == nil { 93 s.cache.Set(cacheKey(inst), data, cacheTTL) 94 } 95 96 return nil 97 } 98 99 // Delete removes the instance document in CouchDB. 100 func (s *InstanceService) Delete(inst *Instance) error { 101 err := couchdb.DeleteDoc(prefixer.GlobalPrefixer, inst) 102 103 s.cache.Clear(cacheKey(inst)) 104 105 return err 106 } 107 108 // CheckPassphrase confirm an instance password 109 func (s *InstanceService) CheckPassphrase(inst *Instance, pass []byte) error { 110 if len(pass) == 0 { 111 return ErrMissingPassphrase 112 } 113 114 needUpdate, err := crypto.CompareHashAndPassphrase(inst.PassphraseHash, pass) 115 if err != nil { 116 return err 117 } 118 119 if !needUpdate { 120 return nil 121 } 122 123 newHash, err := crypto.GenerateFromPassphrase(pass) 124 if err != nil { 125 return err 126 } 127 128 inst.PassphraseHash = newHash 129 if err = s.Update(inst); err != nil { 130 s.logger.WithDomain(inst.Domain).Errorf("Failed to update hash in db: %s", err) 131 } 132 return nil 133 } 134 135 func cacheKey(inst *Instance) string { 136 return cachePrefix + inst.Domain 137 }