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  }