cwtch/storage/v1/profile_store.go

172 lines
5.2 KiB
Go
Raw Normal View History

package v1
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"encoding/json"
"git.openprivacy.ca/openprivacy/log"
"io/ioutil"
"os"
"path"
)
const profileFilename = "profile"
const version = "1"
const versionFile = "VERSION"
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
}
2021-06-14 22:42:44 +00:00
// CheckPassword returns true if the given password produces the same key as the current stored key, otherwise false.
func (ps *ProfileStoreV1) CheckPassword(checkpass string) bool {
oldkey := CreateKey(checkpass, ps.salt[:])
return oldkey == ps.key
}
// InitV1Directory generates a key and salt from a password, writes a SALT and VERSION file and returns the key and salt
func InitV1Directory(directory, password string) ([32]byte, [128]byte, error) {
os.Mkdir(directory, 0700)
key, salt, err := CreateKeySalt(password)
if err != nil {
log.Errorf("Could not create key for profile store from password: %v\n", err)
return [32]byte{}, [128]byte{}, err
}
if err = ioutil.WriteFile(path.Join(directory, versionFile), []byte(version), 0600); err != nil {
log.Errorf("Could not write version file: %v", err)
return [32]byte{}, [128]byte{}, err
}
if err = ioutil.WriteFile(path.Join(directory, saltFile), salt[:], 0600); err != nil {
log.Errorf("Could not write salt file: %v", err)
return [32]byte{}, [128]byte{}, err
}
return key, salt, nil
}
// CreateProfileWriterStore creates a profile store backed by a filestore listening for events and saving them
// directory should be $appDir/profiles/$rand
func CreateProfileWriterStore(eventManager event.Manager, directory, password string, profile *model.Profile) *ProfileStoreV1 {
key, salt, err := InitV1Directory(directory, password)
if err != nil {
return nil
}
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, salt: salt, directory: directory, profile: profile}
return ps
}
// LoadProfileWriterStore loads a profile store from filestore listening for events and saving them
// directory should be $appDir/profiles/$rand
func LoadProfileWriterStore(eventManager event.Manager, directory, password string) (*ProfileStoreV1, error) {
salt, err := ioutil.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
}
// ReadProfile reads a profile from storqage and returns the profile
// directory should be $appDir/profiles/$rand
func ReadProfile(directory string, key [32]byte, salt [128]byte) (*model.Profile, error) {
os.Mkdir(directory, 0700)
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, salt: salt, directory: directory, profile: nil}
err := ps.load()
if err != nil {
return nil, err
}
profile := ps.GetProfileCopy(true)
return profile, nil
}
// NewProfile creates a new profile for use in the profile store.
func NewProfile(name string) *model.Profile {
profile := model.GenerateNewProfile(name)
return profile
}
// GetNewPeerMessage is for AppService to call on Reload events, to reseed the AppClient with the loaded peers
func (ps *ProfileStoreV1) GetNewPeerMessage() *event.Event {
message := event.NewEventList(event.NewPeer, event.Identity, ps.profile.LocalID, event.Key, string(ps.key[:]), event.Salt, string(ps.salt[:]))
return &message
}
// 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)
2020-09-28 18:18:18 +00:00
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
}
}
2020-07-08 18:29:33 +00:00
// Check if there is any saved history...
saveHistory, keyExists := contact.GetAttribute(event.SaveHistoryKey)
if !keyExists {
contact.SetAttribute(event.SaveHistoryKey, event.DeleteHistoryDefault)
}
if saveHistory == event.SaveHistoryConfirmed {
ss := NewStreamStore(ps.directory, contact.LocalID, ps.key)
cp.Contacts[contact.Onion].Timeline.SetMessages(ss.Read())
}
}
for gid, group := range cp.Groups {
2020-09-28 18:18:18 +00:00
if group.Version == 0 {
log.Infof("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)
cp.Groups[gid].Timeline.SetMessages(ss.Read())
cp.Groups[gid].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)
}