2020-01-13 22:11:00 +00:00
|
|
|
package v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"cwtch.im/cwtch/event"
|
|
|
|
"cwtch.im/cwtch/model"
|
|
|
|
"encoding/json"
|
2020-02-10 22:09:24 +00:00
|
|
|
"git.openprivacy.ca/openprivacy/log"
|
2020-01-13 22:11:00 +00:00
|
|
|
"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 {
|
2021-11-11 00:41:43 +00:00
|
|
|
fs FileStore
|
|
|
|
directory string
|
|
|
|
profile *model.Profile
|
|
|
|
key [32]byte
|
|
|
|
salt [128]byte
|
2020-01-13 22:11:00 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-09-29 18:38:03 +00:00
|
|
|
// 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)
|
|
|
|
|
2020-06-25 18:21:10 +00:00
|
|
|
key, salt, err := CreateKeySalt(password)
|
2020-01-13 22:11:00 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not create key for profile store from password: %v\n", err)
|
|
|
|
return [32]byte{}, [128]byte{}, err
|
|
|
|
}
|
|
|
|
|
2021-09-29 18:38:03 +00:00
|
|
|
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
|
|
|
|
}
|
2020-01-13 22:11:00 +00:00
|
|
|
|
|
|
|
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 {
|
2021-09-29 18:38:03 +00:00
|
|
|
key, salt, err := InitV1Directory(directory, password)
|
2020-01-13 22:11:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-11 00:41:43 +00:00
|
|
|
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, salt: salt, directory: directory, profile: profile}
|
2020-01-13 22:11:00 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-06-25 18:21:10 +00:00
|
|
|
key := CreateKey(password, salt)
|
2020-01-13 22:11:00 +00:00
|
|
|
|
2021-11-11 00:41:43 +00:00
|
|
|
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, directory: directory, profile: nil}
|
2020-01-13 22:11:00 +00:00
|
|
|
copy(ps.salt[:], salt)
|
|
|
|
|
2020-03-18 23:10:03 +00:00
|
|
|
err = ps.load()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-13 22:11:00 +00:00
|
|
|
|
|
|
|
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)
|
2021-11-11 00:41:43 +00:00
|
|
|
ps := &ProfileStoreV1{fs: NewFileStore(directory, profileFilename, key), key: key, salt: salt, directory: directory, profile: nil}
|
2020-01-13 22:11:00 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2020-01-13 22:11:00 +00:00
|
|
|
if err == nil {
|
|
|
|
ps.profile = cp
|
|
|
|
|
2020-06-16 00:16:04 +00:00
|
|
|
// 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())
|
|
|
|
}
|
2020-06-16 00:16:04 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 22:11:00 +00:00
|
|
|
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
|
|
|
|
}
|
2020-01-13 22:11:00 +00:00
|
|
|
ss := NewStreamStore(ps.directory, group.LocalID, ps.key)
|
|
|
|
cp.Groups[gid].Timeline.SetMessages(ss.Read())
|
2021-06-24 01:29:23 +00:00
|
|
|
cp.Groups[gid].Timeline.Sort()
|
2020-01-13 22:11:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProfileCopy returns a copy of the stored profile
|
|
|
|
func (ps *ProfileStoreV1) GetProfileCopy(timeline bool) *model.Profile {
|
|
|
|
return ps.profile.GetCopy(timeline)
|
|
|
|
}
|