github.com/status-im/status-go@v1.1.0/protocol/messenger_backup_handler.go (about) 1 package protocol 2 3 import ( 4 "database/sql" 5 6 "go.uber.org/zap" 7 8 "github.com/golang/protobuf/proto" 9 10 utils "github.com/status-im/status-go/common" 11 "github.com/status-im/status-go/images" 12 "github.com/status-im/status-go/multiaccounts/errors" 13 "github.com/status-im/status-go/multiaccounts/settings" 14 "github.com/status-im/status-go/protocol/common" 15 "github.com/status-im/status-go/protocol/communities" 16 "github.com/status-im/status-go/protocol/protobuf" 17 v1protocol "github.com/status-im/status-go/protocol/v1" 18 "github.com/status-im/status-go/protocol/wakusync" 19 ensservice "github.com/status-im/status-go/services/ens" 20 ) 21 22 const ( 23 SyncWakuSectionKeyProfile = "profile" 24 SyncWakuSectionKeyContacts = "contacts" 25 SyncWakuSectionKeyCommunities = "communities" 26 SyncWakuSectionKeySettings = "settings" 27 SyncWakuSectionKeyKeypairs = "keypairs" 28 SyncWakuSectionKeyWatchOnlyAccounts = "watchOnlyAccounts" 29 ) 30 31 func (m *Messenger) HandleBackup(state *ReceivedMessageState, message *protobuf.Backup, statusMessage *v1protocol.StatusMessage) error { 32 if !m.processBackedupMessages { 33 return nil 34 } 35 36 errors := m.handleBackup(state, message) 37 if len(errors) > 0 { 38 for _, err := range errors { 39 m.logger.Warn("failed to handle Backup", zap.Error(err)) 40 } 41 return errors[0] 42 } 43 return nil 44 } 45 46 func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.Backup) []error { 47 var errors []error 48 49 err := m.handleBackedUpProfile(message.Profile, message.Clock) 50 if err != nil { 51 errors = append(errors, err) 52 } 53 54 for _, contact := range message.Contacts { 55 err = m.HandleSyncInstallationContactV2(state, contact, nil) 56 if err != nil { 57 errors = append(errors, err) 58 } 59 } 60 61 err = m.handleSyncChats(state, message.Chats) 62 if err != nil { 63 errors = append(errors, err) 64 } 65 66 communityErrors := m.handleSyncedCommunities(state, message) 67 if len(communityErrors) > 0 { 68 errors = append(errors, communityErrors...) 69 } 70 71 err = m.handleBackedUpSettings(message.Setting) 72 if err != nil { 73 errors = append(errors, err) 74 } 75 76 err = m.handleKeypair(message.Keypair) 77 if err != nil { 78 errors = append(errors, err) 79 } 80 81 err = m.handleWatchOnlyAccount(message.WatchOnlyAccount) 82 if err != nil { 83 errors = append(errors, err) 84 } 85 86 // Send signal about applied backup progress 87 if m.config.messengerSignalsHandler != nil { 88 response := wakusync.WakuBackedUpDataResponse{ 89 Clock: message.Clock, 90 } 91 92 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyProfile, message.ProfileDetails) 93 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyContacts, message.ContactsDetails) 94 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyCommunities, message.CommunitiesDetails) 95 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeySettings, message.SettingsDetails) 96 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyKeypairs, message.KeypairDetails) 97 response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyWatchOnlyAccounts, message.WatchOnlyAccountDetails) 98 99 m.config.messengerSignalsHandler.SendWakuFetchingBackupProgress(&response) 100 } 101 102 state.Response.BackupHandled = true 103 104 return errors 105 } 106 107 func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, backupTime uint64) error { 108 if message == nil { 109 return nil 110 } 111 112 response := wakusync.WakuBackedUpDataResponse{ 113 Profile: &wakusync.BackedUpProfile{}, 114 } 115 116 err := utils.ValidateDisplayName(&message.DisplayName) 117 if err != nil { 118 // Print a warning and set the display name to the account name, but don't stop the recovery 119 m.logger.Warn("invalid display name found", zap.Error(err)) 120 response.SetDisplayName(m.account.Name) 121 } else { 122 err = m.SaveSyncDisplayName(message.DisplayName, message.DisplayNameClock) 123 if err != nil && err != errors.ErrNewClockOlderThanCurrent { 124 return err 125 } 126 127 response.SetDisplayName(message.DisplayName) 128 129 // if we already have a newer clock, then we don't need to update the display name 130 if err == errors.ErrNewClockOlderThanCurrent { 131 response.SetDisplayName(m.account.Name) 132 } 133 } 134 135 syncWithBackedUpImages := false 136 dbImages, err := m.multiAccounts.GetIdentityImages(message.KeyUid) 137 if err != nil { 138 if err != sql.ErrNoRows { 139 return err 140 } 141 // if images are deleted and no images were backed up, then we need to delete them on other devices, 142 // that's why we don't return in case of `sql.ErrNoRows` 143 syncWithBackedUpImages = true 144 } 145 if len(dbImages) == 0 { 146 if len(message.Pictures) > 0 { 147 syncWithBackedUpImages = true 148 } 149 } else { 150 // 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 151 lastImageStoredClock := dbImages[0].Clock 152 syncWithBackedUpImages = lastImageStoredClock < backupTime 153 } 154 155 if syncWithBackedUpImages { 156 if len(message.Pictures) == 0 { 157 err = m.multiAccounts.DeleteIdentityImage(message.KeyUid) 158 if err != nil { 159 return err 160 } 161 response.SetImages(nil) 162 } else { 163 idImages := make([]images.IdentityImage, len(message.Pictures)) 164 for i, pic := range message.Pictures { 165 img := images.IdentityImage{ 166 Name: pic.Name, 167 Payload: pic.Payload, 168 Width: int(pic.Width), 169 Height: int(pic.Height), 170 FileSize: int(pic.FileSize), 171 ResizeTarget: int(pic.ResizeTarget), 172 Clock: pic.Clock, 173 } 174 idImages[i] = img 175 } 176 err = m.multiAccounts.StoreIdentityImages(message.KeyUid, idImages, false) 177 if err != nil { 178 return err 179 } 180 response.SetImages(idImages) 181 } 182 } 183 184 profileShowcasePreferences, err := m.saveProfileShowcasePreferencesProto(message.ProfileShowcasePreferences, false) 185 if err != nil { 186 return err 187 } 188 if profileShowcasePreferences != nil { 189 response.SetProfileShowcasePreferences(profileShowcasePreferences) 190 } 191 192 var ensUsernameDetails []*ensservice.UsernameDetail 193 for _, d := range message.EnsUsernameDetails { 194 dd, err := m.saveEnsUsernameDetailProto(d) 195 if err != nil { 196 return err 197 } 198 ensUsernameDetails = append(ensUsernameDetails, dd) 199 } 200 response.SetEnsUsernameDetails(ensUsernameDetails) 201 202 if m.config.messengerSignalsHandler != nil { 203 m.config.messengerSignalsHandler.SendWakuBackedUpProfile(&response) 204 } 205 206 return err 207 } 208 209 func (m *Messenger) handleBackedUpSettings(message *protobuf.SyncSetting) error { 210 if message == nil { 211 return nil 212 } 213 214 // DisplayName is recovered via `protobuf.BackedUpProfile` message 215 if message.GetType() == protobuf.SyncSetting_DISPLAY_NAME { 216 return nil 217 } 218 219 settingField, err := m.extractAndSaveSyncSetting(message) 220 if err != nil { 221 m.logger.Warn("failed to handle SyncSetting from backed up message", zap.Error(err)) 222 return nil 223 } 224 225 if settingField != nil { 226 if message.GetType() == protobuf.SyncSetting_PREFERRED_NAME && message.GetValueString() != "" { 227 displayNameClock, err := m.settings.GetSettingLastSynced(settings.DisplayName) 228 if err != nil { 229 m.logger.Warn("failed to get last synced clock for display name", zap.Error(err)) 230 return nil 231 } 232 // there is a race condition between display name and preferred name on updating m.account.Name, so we need to check the clock 233 // there is also a similar check within SaveSyncDisplayName 234 if displayNameClock < message.GetClock() { 235 m.account.Name = message.GetValueString() 236 err = m.multiAccounts.SaveAccount(*m.account) 237 if err != nil { 238 m.logger.Warn("[handleBackedUpSettings] failed to save account", zap.Error(err)) 239 return nil 240 } 241 } 242 } 243 244 if m.config.messengerSignalsHandler != nil { 245 response := wakusync.WakuBackedUpDataResponse{ 246 Setting: settingField, 247 } 248 m.config.messengerSignalsHandler.SendWakuBackedUpSettings(&response) 249 } 250 } 251 252 return nil 253 } 254 255 func (m *Messenger) handleKeypair(message *protobuf.SyncKeypair) error { 256 if message == nil { 257 return nil 258 } 259 260 multiAcc, err := m.multiAccounts.GetAccount(message.KeyUid) 261 if err != nil { 262 return err 263 } 264 // If user is recovering his account via seed phrase, but the backed up messages indicate that the profile keypair 265 // is a keycard related profile, then we need to remove related profile keycards (only profile, other keycards should remain). 266 if multiAcc != nil && multiAcc.KeyUID == message.KeyUid && !multiAcc.RefersToKeycard() && len(message.Keycards) > 0 { 267 message.Keycards = []*protobuf.SyncKeycard{} 268 } 269 270 keypair, err := m.handleSyncKeypair(message, false, nil) 271 if err != nil { 272 if err == ErrTryingToStoreOldKeypair { 273 return nil 274 } 275 return err 276 } 277 278 if m.config.messengerSignalsHandler != nil { 279 kpResponse := wakusync.WakuBackedUpDataResponse{ 280 Keypair: keypair.CopyKeypair(), 281 } 282 283 m.config.messengerSignalsHandler.SendWakuBackedUpKeypair(&kpResponse) 284 } 285 286 return nil 287 } 288 289 func (m *Messenger) handleWatchOnlyAccount(message *protobuf.SyncAccount) error { 290 if message == nil { 291 return nil 292 } 293 294 acc, err := m.handleSyncWatchOnlyAccount(message, true) 295 if err != nil { 296 if err == ErrTryingToStoreOldWalletAccount { 297 return nil 298 } 299 return err 300 } 301 302 if m.config.messengerSignalsHandler != nil { 303 response := wakusync.WakuBackedUpDataResponse{ 304 WatchOnlyAccount: acc, 305 } 306 307 m.config.messengerSignalsHandler.SendWakuBackedUpWatchOnlyAccount(&response) 308 } 309 310 return nil 311 } 312 313 func syncInstallationCommunitiesSet(communities []*protobuf.SyncInstallationCommunity) map[string]*protobuf.SyncInstallationCommunity { 314 ret := map[string]*protobuf.SyncInstallationCommunity{} 315 for _, c := range communities { 316 id := string(c.GetId()) 317 prevC, ok := ret[id] 318 if !ok || prevC.Clock < c.Clock { 319 ret[id] = c 320 } 321 } 322 return ret 323 } 324 325 func (m *Messenger) handleSyncedCommunities(state *ReceivedMessageState, message *protobuf.Backup) []error { 326 var errors []error 327 for _, syncCommunity := range syncInstallationCommunitiesSet(message.Communities) { 328 err := m.handleSyncInstallationCommunity(state, syncCommunity) 329 if err != nil { 330 errors = append(errors, err) 331 } 332 333 err = m.requestCommunityKeysAndSharedAddresses(state, syncCommunity) 334 if err != nil { 335 errors = append(errors, err) 336 } 337 } 338 339 return errors 340 } 341 342 func (m *Messenger) requestCommunityKeysAndSharedAddresses(state *ReceivedMessageState, syncCommunity *protobuf.SyncInstallationCommunity) error { 343 if !syncCommunity.Joined { 344 return nil 345 } 346 347 community, err := m.GetCommunityByID(syncCommunity.Id) 348 if err != nil { 349 return err 350 } 351 352 if community == nil { 353 return communities.ErrOrgNotFound 354 } 355 356 // Send a request to get back our previous shared addresses 357 request := &protobuf.CommunitySharedAddressesRequest{ 358 CommunityId: syncCommunity.Id, 359 } 360 361 payload, err := proto.Marshal(request) 362 if err != nil { 363 return err 364 } 365 366 rawMessage := &common.RawMessage{ 367 Payload: payload, 368 Sender: m.identity, 369 CommunityID: community.ID(), 370 SkipEncryptionLayer: true, 371 MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_SHARED_ADDRESSES_REQUEST, 372 } 373 374 _, err = m.SendMessageToControlNode(community, rawMessage) 375 376 if err != nil { 377 m.logger.Error("failed to request shared addresses", zap.String("communityId", community.IDString()), zap.Error(err)) 378 return err 379 } 380 381 // If the community is encrypted or one channel is, ask for the encryption keys back 382 isEncrypted := syncCommunity.Encrypted || len(syncCommunity.EncryptionKeysV2) > 0 383 if !isEncrypted { 384 // check if we have encrypted channels 385 myPk := m.IdentityPublicKeyString() 386 for channelID, channel := range community.Chats() { 387 _, exists := channel.GetMembers()[myPk] 388 if exists && community.ChannelEncrypted(channelID) { 389 isEncrypted = true 390 break 391 } 392 } 393 } 394 395 if isEncrypted { 396 err = m.requestCommunityEncryptionKeys(community, nil) 397 if err != nil { 398 m.logger.Error("failed to request community encryption keys", zap.String("communityId", community.IDString()), zap.Error(err)) 399 return err 400 } 401 } 402 403 return nil 404 }