cwtch/storage/v1/profile_store.go

97 lines
2.4 KiB
Go

package v1
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"encoding/json"
"git.openprivacy.ca/openprivacy/log"
"os"
"path"
)
const profileFilename = "profile"
const saltFile = "SALT"
// ProfileStoreV1 storage for profiles and message streams that uses in memory key and fs stored salt instead of in memory password
type ProfileStoreV1 struct {
fs FileStore
directory string
profile *model.Profile
key [32]byte
salt [128]byte
}
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
// directory should be $appDir/profiles/$rand
func LoadProfileWriterStore(directory, password string) (*ProfileStoreV1, error) {
salt, err := os.ReadFile(path.Join(directory, saltFile))
if err != nil {
return nil, err
}
key := CreateKey(password, salt)
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, directory: directory, profile: nil}
copy(ps.salt[:], salt)
err = ps.load()
if err != nil {
return nil, err
}
return ps, nil
}
// load instantiates a cwtchPeer from the file store
func (ps *ProfileStoreV1) load() error {
decrypted, err := ps.fs.Read()
if err != nil {
return err
}
cp := new(model.Profile)
err = json.Unmarshal(decrypted, &cp)
if err == nil {
ps.profile = cp
// TODO 2020.06.09: v1 update, Remove on v2
// if we already have the contact it can be assumed "approved" unless blocked
for _, contact := range cp.Contacts {
if contact.Authorization == "" {
if contact.DeprecatedBlocked {
contact.Authorization = model.AuthBlocked
} else {
contact.Authorization = model.AuthApproved
}
}
if contact.Attributes[event.SaveHistoryKey] == event.SaveHistoryConfirmed {
ss := NewStreamStore(ps.directory, contact.LocalID, ps.key)
if contact, exists := cp.Contacts[contact.Onion]; exists {
contact.Timeline.SetMessages(ss.Read())
}
}
}
for gid, group := range cp.Groups {
if group.Version == 0 {
log.Debugf("group %v is of unsupported version 0. dropping group...\n", group.GroupID)
delete(cp.Groups, gid)
continue
}
ss := NewStreamStore(ps.directory, group.LocalID, ps.key)
if group, exists := cp.Groups[gid]; exists {
group.Timeline.SetMessages(ss.Read())
group.Timeline.Sort()
}
}
}
return err
}
// GetProfileCopy returns a copy of the stored profile
func (ps *ProfileStoreV1) GetProfileCopy(timeline bool) *model.Profile {
return ps.profile.GetCopy(timeline)
}