github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/bitwarden/settings/settings.go (about) 1 package settings 2 3 import ( 4 "encoding/base64" 5 "errors" 6 7 "github.com/cozy/cozy-stack/model/account" 8 "github.com/cozy/cozy-stack/model/instance" 9 "github.com/cozy/cozy-stack/model/job" 10 "github.com/cozy/cozy-stack/pkg/consts" 11 "github.com/cozy/cozy-stack/pkg/couchdb" 12 "github.com/cozy/cozy-stack/pkg/crypto" 13 "github.com/cozy/cozy-stack/pkg/metadata" 14 "github.com/gofrs/uuid/v5" 15 ) 16 17 // DocTypeVersion represents the doctype version. Each time this document 18 // structure is modified, update this value 19 const DocTypeVersion = "1" 20 21 // ErrMissingOrgKey is used when the organization key does not exist 22 var ErrMissingOrgKey = errors.New("No organization key") 23 24 // Settings is the struct that holds the birwarden settings 25 type Settings struct { 26 CouchID string `json:"_id,omitempty"` 27 CouchRev string `json:"_rev,omitempty"` 28 PassphraseKdf int `json:"passphrase_kdf,omitempty"` 29 PassphraseKdfIterations int `json:"passphrase_kdf_iterations,omitempty"` 30 PassphraseHint string `json:"passphrase_hint,omitempty"` 31 SecurityStamp string `json:"security_stamp,omitempty"` 32 Key string `json:"key,omitempty"` 33 PublicKey string `json:"public_key,omitempty"` 34 PrivateKey string `json:"private_key,omitempty"` 35 EncryptedOrgKey string `json:"encrypted_organization_key,omitempty"` 36 OrganizationID string `json:"organization_id,omitempty"` 37 CollectionID string `json:"collection_id,omitempty"` 38 EquivalentDomains [][]string `json:"equivalent_domains,omitempty"` 39 GlobalEquivalentDomains []int `json:"global_equivalent_domains,omitempty"` 40 Metadata *metadata.CozyMetadata `json:"cozyMetadata,omitempty"` 41 ExtensionInstalled bool `json:"extension_installed,omitempty"` 42 } 43 44 // ID returns the settings qualified identifier 45 func (s *Settings) ID() string { return s.CouchID } 46 47 // Rev returns the settings revision 48 func (s *Settings) Rev() string { return s.CouchRev } 49 50 // DocType returns the settings document type 51 func (s *Settings) DocType() string { return consts.Settings } 52 53 // Clone implements couchdb.Doc 54 func (s *Settings) Clone() couchdb.Doc { 55 cloned := *s 56 if s.Metadata != nil { 57 cloned.Metadata = s.Metadata.Clone() 58 } 59 return &cloned 60 } 61 62 // SetID changes the settings qualified identifier 63 func (s *Settings) SetID(id string) { s.CouchID = id } 64 65 // SetRev changes the settings revision 66 func (s *Settings) SetRev(rev string) { s.CouchRev = rev } 67 68 // Save persists the settings document for bitwarden in CouchDB. 69 func (s *Settings) Save(inst *instance.Instance) error { 70 s.CouchID = consts.BitwardenSettingsID 71 if s.Metadata == nil { 72 md := metadata.New() 73 md.DocTypeVersion = DocTypeVersion 74 s.Metadata = md 75 } else { 76 s.Metadata.ChangeUpdatedAt() 77 } 78 if s.CouchRev == "" { 79 return couchdb.CreateNamedDocWithDB(inst, s) 80 } 81 return couchdb.UpdateDoc(inst, s) 82 } 83 84 // SetKeyPair is used to save the key pair of the user, that will be used to 85 // share passwords with the cozy organization. 86 func (s *Settings) SetKeyPair(inst *instance.Instance, pub, priv string) error { 87 s.PublicKey = pub 88 s.PrivateKey = priv 89 if err := s.EnsureCozyOrganization(inst); err != nil { 90 return err 91 } 92 return couchdb.UpdateDoc(inst, s) 93 } 94 95 // EnsureCozyOrganization make sure that the settings for the Cozy organization 96 // are set. 97 func (s *Settings) EnsureCozyOrganization(inst *instance.Instance) error { 98 var err error 99 if len(s.EncryptedOrgKey) == 0 { 100 orgKey := crypto.GenerateRandomBytes(64) 101 b64 := base64.StdEncoding.EncodeToString(orgKey) 102 s.EncryptedOrgKey, err = account.EncryptCredentialsData(b64) 103 if err != nil { 104 return err 105 } 106 } 107 if s.OrganizationID == "" { 108 uid, err := uuid.NewV7() 109 if err != nil { 110 return err 111 } 112 s.OrganizationID = uid.String() 113 } 114 if s.CollectionID == "" { 115 uid, err := uuid.NewV7() 116 if err != nil { 117 return err 118 } 119 s.CollectionID = uid.String() 120 } 121 return nil 122 } 123 124 // OrganizationKey returns the organization key (in clear, not encrypted). 125 func (s *Settings) OrganizationKey() ([]byte, error) { 126 if len(s.EncryptedOrgKey) == 0 { 127 return nil, ErrMissingOrgKey 128 } 129 decrypted, err := account.DecryptCredentialsData(s.EncryptedOrgKey) 130 if err != nil { 131 return nil, err 132 } 133 b64, ok := decrypted.(string) 134 if !ok { 135 return nil, errors.New("Invalid key") 136 } 137 return base64.StdEncoding.DecodeString(b64) 138 } 139 140 // Get returns the settings document for bitwarden. 141 func Get(inst *instance.Instance) (*Settings, error) { 142 settings := &Settings{} 143 err := couchdb.GetDoc(inst, consts.Settings, consts.BitwardenSettingsID, settings) 144 if err != nil && !couchdb.IsNotFoundError(err) { 145 return nil, err 146 } 147 return settings, nil 148 } 149 150 // HasVault returns true if a pass/bitwarden has been used on this instance. 151 func HasVault(inst *instance.Instance) bool { 152 bitwardenSettings, err := Get(inst) 153 if err != nil { 154 return false 155 } 156 return bitwardenSettings.ExtensionInstalled 157 } 158 159 // UpdateRevisionDate updates the updatedAt field of the bitwarden settings 160 // document. This field is used to know by some clients to know the date of the 161 // last change on the server before doing a full sync. 162 func UpdateRevisionDate(inst *instance.Instance, settings *Settings) error { 163 var err error 164 if settings == nil { 165 settings, err = Get(inst) 166 } 167 if err == nil { 168 err = settings.Save(inst) 169 } 170 if err != nil { 171 inst.Logger().WithNamespace("bitwarden"). 172 Infof("Cannot update revision date: %s", err) 173 } 174 return err 175 } 176 177 // MigrateAccountsToCiphers creates a job to copy the konnectors accounts 178 // inside the bitwarden vault (and set the extension_installed flag). 179 func MigrateAccountsToCiphers(inst *instance.Instance) error { 180 msg, err := job.NewMessage(map[string]interface{}{ 181 "type": "accounts-to-organization", 182 }) 183 if err != nil { 184 return err 185 } 186 _, err = job.System().PushJob(inst, &job.JobRequest{ 187 WorkerType: "migrations", 188 Message: msg, 189 }) 190 return err 191 } 192 193 var _ couchdb.Doc = &Settings{}