status-go/protocol/messenger_backup_handler.go

415 lines
12 KiB
Go

package protocol
import (
"database/sql"
"go.uber.org/zap"
"github.com/golang/protobuf/proto"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/protobuf"
v1protocol "github.com/status-im/status-go/protocol/v1"
"github.com/status-im/status-go/protocol/wakusync"
ensservice "github.com/status-im/status-go/services/ens"
)
const (
SyncWakuSectionKeyProfile = "profile"
SyncWakuSectionKeyContacts = "contacts"
SyncWakuSectionKeyCommunities = "communities"
SyncWakuSectionKeySettings = "settings"
SyncWakuSectionKeyKeypairs = "keypairs"
SyncWakuSectionKeyWatchOnlyAccounts = "watchOnlyAccounts"
)
func (m *Messenger) HandleBackup(state *ReceivedMessageState, message *protobuf.Backup, statusMessage *v1protocol.StatusMessage) error {
if !m.processBackedupMessages {
return nil
}
errors := m.handleBackup(state, message)
if len(errors) > 0 {
for _, err := range errors {
m.logger.Warn("failed to handle Backup", zap.Error(err))
}
return errors[0]
}
return nil
}
func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.Backup) []error {
var errors []error
err := m.handleBackedUpProfile(message.Profile, message.Clock)
if err != nil {
errors = append(errors, err)
}
for _, contact := range message.Contacts {
err = m.HandleSyncInstallationContactV2(state, contact, nil)
if err != nil {
errors = append(errors, err)
}
}
err = m.handleSyncChats(state, message.Chats)
if err != nil {
errors = append(errors, err)
}
communityErrors := m.handleSyncedCommunities(state, message)
if len(communityErrors) > 0 {
errors = append(errors, communityErrors...)
}
err = m.handleBackedUpSettings(message.Setting)
if err != nil {
errors = append(errors, err)
}
err = m.handleKeypair(message.Keypair)
if err != nil {
errors = append(errors, err)
}
err = m.handleWatchOnlyAccount(message.WatchOnlyAccount)
if err != nil {
errors = append(errors, err)
}
// Send signal about applied backup progress
if m.config.messengerSignalsHandler != nil {
response := wakusync.WakuBackedUpDataResponse{
Clock: message.Clock,
}
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyProfile, message.ProfileDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyContacts, message.ContactsDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyCommunities, message.CommunitiesDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeySettings, message.SettingsDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyKeypairs, message.KeypairDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyWatchOnlyAccounts, message.WatchOnlyAccountDetails)
m.config.messengerSignalsHandler.SendWakuFetchingBackupProgress(&response)
}
state.Response.BackupHandled = true
return errors
}
func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, backupTime uint64) error {
if message == nil {
return nil
}
response := wakusync.WakuBackedUpDataResponse{
Profile: &wakusync.BackedUpProfile{},
}
err := m.SaveSyncDisplayName(message.DisplayName, message.DisplayNameClock)
if err != nil && err != errors.ErrNewClockOlderThanCurrent {
return err
}
response.SetDisplayName(message.DisplayName)
// if we already have a newer clock, then we don't need to update the display name
if err == errors.ErrNewClockOlderThanCurrent {
response.SetDisplayName(m.account.Name)
}
syncWithBackedUpImages := false
dbImages, err := m.multiAccounts.GetIdentityImages(message.KeyUid)
if err != nil {
if err != sql.ErrNoRows {
return err
}
// if images are deleted and no images were backed up, then we need to delete them on other devices,
// that's why we don't return in case of `sql.ErrNoRows`
syncWithBackedUpImages = true
}
if len(dbImages) == 0 {
if len(message.Pictures) > 0 {
syncWithBackedUpImages = true
}
} else {
// since both images (large and thumbnail) are always stored in the same time, we're free to use either of those two clocks for comparison
lastImageStoredClock := dbImages[0].Clock
syncWithBackedUpImages = lastImageStoredClock < backupTime
}
if syncWithBackedUpImages {
if len(message.Pictures) == 0 {
err = m.multiAccounts.DeleteIdentityImage(message.KeyUid)
if err != nil {
return err
}
response.SetImages(nil)
} else {
idImages := make([]images.IdentityImage, len(message.Pictures))
for i, pic := range message.Pictures {
img := images.IdentityImage{
Name: pic.Name,
Payload: pic.Payload,
Width: int(pic.Width),
Height: int(pic.Height),
FileSize: int(pic.FileSize),
ResizeTarget: int(pic.ResizeTarget),
Clock: pic.Clock,
}
idImages[i] = img
}
err = m.multiAccounts.StoreIdentityImages(message.KeyUid, idImages, false)
if err != nil {
return err
}
response.SetImages(idImages)
}
}
profileShowcasePreferences, err := m.saveProfileShowcasePreferencesProto(message.ProfileShowcasePreferences, false)
if err != nil {
return err
}
if profileShowcasePreferences != nil {
response.SetProfileShowcasePreferences(profileShowcasePreferences)
}
var ensUsernameDetails []*ensservice.UsernameDetail
for _, d := range message.EnsUsernameDetails {
dd, err := m.saveEnsUsernameDetailProto(d)
if err != nil {
return err
}
ensUsernameDetails = append(ensUsernameDetails, dd)
}
response.SetEnsUsernameDetails(ensUsernameDetails)
if m.config.messengerSignalsHandler != nil {
m.config.messengerSignalsHandler.SendWakuBackedUpProfile(&response)
}
return err
}
func (m *Messenger) handleBackedUpSettings(message *protobuf.SyncSetting) error {
if message == nil {
return nil
}
// DisplayName is recovered via `protobuf.BackedUpProfile` message
if message.GetType() == protobuf.SyncSetting_DISPLAY_NAME {
return nil
}
settingField, err := m.extractAndSaveSyncSetting(message)
if err != nil {
m.logger.Warn("failed to handle SyncSetting from backed up message", zap.Error(err))
return nil
}
if settingField != nil {
if message.GetType() == protobuf.SyncSetting_PREFERRED_NAME && message.GetValueString() != "" {
displayNameClock, err := m.settings.GetSettingLastSynced(settings.DisplayName)
if err != nil {
m.logger.Warn("failed to get last synced clock for display name", zap.Error(err))
return nil
}
// there is a race condition between display name and preferred name on updating m.account.Name, so we need to check the clock
// there is also a similar check within SaveSyncDisplayName
if displayNameClock < message.GetClock() {
m.account.Name = message.GetValueString()
err = m.multiAccounts.SaveAccount(*m.account)
if err != nil {
m.logger.Warn("[handleBackedUpSettings] failed to save account", zap.Error(err))
return nil
}
}
}
if m.config.messengerSignalsHandler != nil {
response := wakusync.WakuBackedUpDataResponse{
Setting: settingField,
}
m.config.messengerSignalsHandler.SendWakuBackedUpSettings(&response)
}
}
return nil
}
func (m *Messenger) handleKeypair(message *protobuf.SyncKeypair) error {
if message == nil {
return nil
}
multiAcc, err := m.multiAccounts.GetAccount(message.KeyUid)
if err != nil {
return err
}
// If user is recovering his account via seed phrase, but the backed up messages indicate that the profile keypair
// is a keycard related profile, then we need to remove related profile keycards (only profile, other keycards should remain).
if multiAcc != nil && multiAcc.KeyUID == message.KeyUid && !multiAcc.RefersToKeycard() && len(message.Keycards) > 0 {
message.Keycards = []*protobuf.SyncKeycard{}
}
keypair, err := m.handleSyncKeypair(message, false, nil)
if err != nil {
if err == ErrTryingToStoreOldKeypair {
return nil
}
return err
}
if m.config.messengerSignalsHandler != nil {
kpResponse := wakusync.WakuBackedUpDataResponse{
Keypair: keypair.CopyKeypair(),
}
m.config.messengerSignalsHandler.SendWakuBackedUpKeypair(&kpResponse)
}
return nil
}
func (m *Messenger) handleWatchOnlyAccount(message *protobuf.SyncAccount) error {
if message == nil {
return nil
}
acc, err := m.handleSyncWatchOnlyAccount(message, true)
if err != nil {
if err == ErrTryingToStoreOldWalletAccount {
return nil
}
return err
}
if m.config.messengerSignalsHandler != nil {
response := wakusync.WakuBackedUpDataResponse{
WatchOnlyAccount: acc,
}
m.config.messengerSignalsHandler.SendWakuBackedUpWatchOnlyAccount(&response)
}
return nil
}
func syncInstallationCommunitiesSet(communities []*protobuf.SyncInstallationCommunity) map[string]*protobuf.SyncInstallationCommunity {
ret := map[string]*protobuf.SyncInstallationCommunity{}
for _, c := range communities {
id := string(c.GetId())
prevC, ok := ret[id]
if !ok || prevC.Clock < c.Clock {
ret[id] = c
}
}
return ret
}
func (m *Messenger) handleSyncedCommunities(state *ReceivedMessageState, message *protobuf.Backup) []error {
var errors []error
for _, syncCommunity := range syncInstallationCommunitiesSet(message.Communities) {
err := m.handleSyncInstallationCommunity(state, syncCommunity, nil)
if err != nil {
errors = append(errors, err)
}
err = m.requestCommunityKeysAndSharedAddresses(state, syncCommunity)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
func (m *Messenger) requestCommunityKeysAndSharedAddresses(state *ReceivedMessageState, syncCommunity *protobuf.SyncInstallationCommunity) error {
if !syncCommunity.Joined {
return nil
}
community, err := m.GetCommunityByID(syncCommunity.Id)
if err != nil {
return err
}
if community == nil {
return communities.ErrOrgNotFound
}
// Send a request to get back our previous shared addresses
request := &protobuf.CommunitySharedAddressesRequest{
CommunityId: syncCommunity.Id,
}
payload, err := proto.Marshal(request)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: payload,
Sender: m.identity,
CommunityID: community.ID(),
SkipEncryptionLayer: true,
MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_SHARED_ADDRESSES_REQUEST,
}
_, err = m.SendMessageToControlNode(community, rawMessage)
if err != nil {
m.logger.Error("failed to request shared addresses", zap.String("communityId", community.IDString()), zap.Error(err))
return err
}
// If the community is encrypted or one channel is, ask for the encryption keys back
isEncrypted := syncCommunity.Encrypted || len(syncCommunity.EncryptionKeysV2) > 0
if !isEncrypted {
// check if we have encrypted channels
myPk := m.IdentityPublicKeyString()
for channelID, channel := range community.Chats() {
_, exists := channel.GetMembers()[myPk]
if exists && community.ChannelEncrypted(channelID) {
isEncrypted = true
break
}
}
}
if isEncrypted {
request := &protobuf.CommunityEncryptionKeysRequest{
CommunityId: syncCommunity.Id,
}
payload, err := proto.Marshal(request)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: payload,
Sender: m.identity,
CommunityID: community.ID(),
SkipEncryptionLayer: true,
MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_ENCRYPTION_KEYS_REQUEST,
}
_, err = m.SendMessageToControlNode(community, rawMessage)
if err != nil {
m.logger.Error("failed to request community encryption keys", zap.String("communityId", community.IDString()), zap.Error(err))
return err
}
}
return nil
}