From cebe7c884d9ed9d0c5ff8dc98fd1c48ccefc8ac5 Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 2 Jul 2024 13:29:00 +1000 Subject: [PATCH] feat: move db deletion methods to accountManager --- ts/components/dialog/DeleteAccountModal.tsx | 163 +------------------- ts/util/accountManager.ts | 160 ++++++++++++++++++- 2 files changed, 164 insertions(+), 159 deletions(-) diff --git a/ts/components/dialog/DeleteAccountModal.tsx b/ts/components/dialog/DeleteAccountModal.tsx index d577dd7b3..bd3c9a169 100644 --- a/ts/components/dialog/DeleteAccountModal.tsx +++ b/ts/components/dialog/DeleteAccountModal.tsx @@ -1,171 +1,18 @@ import { useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { SnodeAPI } from '../../session/apis/snode_api/SNodeAPI'; -import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/sync/syncUtils'; -import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog'; +import { updateDeleteAccountModal } from '../../state/ducks/modalDialog'; import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; import { SpacerLG } from '../basic/Text'; import { SessionSpinner } from '../loading'; -import { Data } from '../../data/data'; -import { deleteAllLogs } from '../../node/logs'; -import { clearInbox } from '../../session/apis/open_group_api/sogsv3/sogsV3ClearInbox'; -import { getAllValidOpenGroupV2ConversationRoomInfos } from '../../session/apis/open_group_api/utils/OpenGroupUtils'; -import { ed25519Str } from '../../session/utils/String'; +import { + deleteEverythingAndNetworkData, + sendConfigMessageAndDeleteEverything, +} from '../../util/accountManager'; import { SessionRadioGroup } from '../basic/SessionRadioGroup'; -const deleteDbLocally = async () => { - window?.log?.info('last message sent successfully. Deleting everything'); - await window.persistStore?.purge(); - window?.log?.info('store purged'); - - await deleteAllLogs(); - window?.log?.info('deleteAllLogs: done'); - - await Data.removeAll(); - window?.log?.info('Data.removeAll: done'); - - await Data.close(); - window?.log?.info('Data.close: done'); - await Data.removeDB(); - window?.log?.info('Data.removeDB: done'); - - await Data.removeOtherData(); - window?.log?.info('Data.removeOtherData: done'); - - window.localStorage.setItem('restart-reason', 'delete-account'); -}; - -async function sendConfigMessageAndDeleteEverything() { - try { - // DELETE LOCAL DATA ONLY, NOTHING ON NETWORK - window?.log?.info('DeleteAccount => Sending a last SyncConfiguration'); - - // be sure to wait for the message being effectively sent. Otherwise we won't be able to encrypt it for our devices ! - await forceSyncConfigurationNowIfNeeded(true); - window?.log?.info('Last configuration message sent!'); - await deleteDbLocally(); - window.restart(); - } catch (error) { - // if an error happened, it's not related to the delete everything on network logic as this is handled above. - // this could be a last sync configuration message not being sent. - // in all case, we delete everything, and restart - window?.log?.error( - 'Something went wrong deleting all data:', - error && error.stack ? error.stack : error - ); - try { - await deleteDbLocally(); - } catch (e) { - window?.log?.error(e); - } finally { - window.restart(); - } - } -} - -async function deleteEverythingAndNetworkData() { - try { - // DELETE EVERYTHING ON NETWORK, AND THEN STUFF LOCALLY STORED - // a bit of duplicate code below, but it's easier to follow every case like that (helped with returns) - - // clear all sogs inboxes (includes message requests) - const allRoomInfos = await getAllValidOpenGroupV2ConversationRoomInfos(); - if (allRoomInfos && allRoomInfos.size > 0) { - // clear each inbox per sogs - // eslint-disable-next-line no-restricted-syntax - for (const roomInfo of allRoomInfos.values()) { - try { - // eslint-disable-next-line no-await-in-loop - const success = await clearInbox(roomInfo); - if (!success) { - throw Error(`Failed to clear inbox for ${roomInfo.conversationId}`); - } - } catch (error) { - window.log.info('DeleteAccount =>', error); - continue; - } - } - } - - // send deletion message to the network - const potentiallyMaliciousSnodes = await SnodeAPI.forceNetworkDeletion(); - if (potentiallyMaliciousSnodes === null) { - window?.log?.warn('DeleteAccount => forceNetworkDeletion failed'); - - // close this dialog - window.inboxStore?.dispatch(updateDeleteAccountModal(null)); - window.inboxStore?.dispatch( - updateConfirmModal({ - title: window.i18n('dialogClearAllDataDeletionFailedTitle'), - message: window.i18n('dialogClearAllDataDeletionFailedDesc'), - okTheme: SessionButtonColor.Danger, - okText: window.i18n('deviceOnly'), - onClickOk: async () => { - await deleteDbLocally(); - window.restart(); - }, - onClickClose: () => { - window.inboxStore?.dispatch(updateConfirmModal(null)); - }, - }) - ); - return; - } - - if (potentiallyMaliciousSnodes.length > 0) { - const snodeStr = potentiallyMaliciousSnodes.map(ed25519Str); - window?.log?.warn( - 'DeleteAccount => forceNetworkDeletion Got some potentially malicious snodes', - snodeStr - ); - // close this dialog - window.inboxStore?.dispatch(updateDeleteAccountModal(null)); - // open a new confirm dialog to ask user what to do - window.inboxStore?.dispatch( - updateConfirmModal({ - title: window.i18n('dialogClearAllDataDeletionFailedTitle'), - message: window.i18n('dialogClearAllDataDeletionFailedMultiple', [ - potentiallyMaliciousSnodes.join(', '), - ]), - messageSub: window.i18n('dialogClearAllDataDeletionFailedTitleQuestion'), - okTheme: SessionButtonColor.Danger, - okText: window.i18n('deviceOnly'), - onClickOk: async () => { - await deleteDbLocally(); - window.restart(); - }, - onClickClose: () => { - window.inboxStore?.dispatch(updateConfirmModal(null)); - }, - }) - ); - return; - } - - // We removed everything on the network successfully (no malicious node!). Now delete the stuff we got locally - // without sending a last configuration message (otherwise this one will still be on the network) - await deleteDbLocally(); - window.restart(); - } catch (error) { - // if an error happened, it's not related to the delete everything on network logic as this is handled above. - // this could be a last sync configuration message not being sent. - // in all case, we delete everything, and restart - window?.log?.error( - 'Something went wrong deleting all data:', - error && error.stack ? error.stack : error - ); - try { - await deleteDbLocally(); - } catch (e) { - window?.log?.error(e); - } - window.restart(); - } -} - const DEVICE_ONLY = 'device_only'; const DEVICE_AND_NETWORK = 'device_and_network'; type DeleteModes = typeof DEVICE_ONLY | typeof DEVICE_AND_NETWORK; diff --git a/ts/util/accountManager.ts b/ts/util/accountManager.ts index 5761b2b1e..986736424 100644 --- a/ts/util/accountManager.ts +++ b/ts/util/accountManager.ts @@ -1,16 +1,24 @@ import { isEmpty } from 'lodash'; import { getConversationController } from '../session/conversations'; import { getSodiumRenderer } from '../session/crypto'; -import { fromArrayBufferToBase64, fromHex, toHex } from '../session/utils/String'; +import { ed25519Str, fromArrayBufferToBase64, fromHex, toHex } from '../session/utils/String'; import { configurationMessageReceived, trigger } from '../shims/events'; +import { SessionButtonColor } from '../components/basic/SessionButton'; +import { Data } from '../data/data'; import { SettingsKey } from '../data/settings-key'; import { ConversationTypeEnum } from '../models/conversationAttributes'; +import { deleteAllLogs } from '../node/logs'; import { SessionKeyPair } from '../receiver/keypairs'; +import { clearInbox } from '../session/apis/open_group_api/sogsv3/sogsV3ClearInbox'; +import { getAllValidOpenGroupV2ConversationRoomInfos } from '../session/apis/open_group_api/utils/OpenGroupUtils'; import { getSwarmPollingInstance } from '../session/apis/snode_api'; +import { SnodeAPI } from '../session/apis/snode_api/SNodeAPI'; import { mnDecode, mnEncode } from '../session/crypto/mnemonic'; import { getOurPubKeyStrFromCache } from '../session/utils/User'; import { LibSessionUtil } from '../session/utils/libsession/libsession_utils'; +import { forceSyncConfigurationNowIfNeeded } from '../session/utils/sync/syncUtils'; +import { updateConfirmModal, updateDeleteAccountModal } from '../state/ducks/modalDialog'; import { actions as userActions } from '../state/ducks/user'; import { Registration } from './registration'; import { Storage, saveRecoveryPhrase, setLocalPubKey, setSignInByLinking } from './storage'; @@ -236,3 +244,153 @@ export async function registrationDone(ourPubkey: string, displayName: string) { // this will make the poller start fetching messages trigger('registration_done'); } + +const deleteDbLocally = async () => { + window?.log?.info('last message sent successfully. Deleting everything'); + await window.persistStore?.purge(); + window?.log?.info('store purged'); + + await deleteAllLogs(); + window?.log?.info('deleteAllLogs: done'); + + await Data.removeAll(); + window?.log?.info('Data.removeAll: done'); + + await Data.close(); + window?.log?.info('Data.close: done'); + await Data.removeDB(); + window?.log?.info('Data.removeDB: done'); + + await Data.removeOtherData(); + window?.log?.info('Data.removeOtherData: done'); + + window.localStorage.setItem('restart-reason', 'delete-account'); +}; + +export async function sendConfigMessageAndDeleteEverything() { + try { + // DELETE LOCAL DATA ONLY, NOTHING ON NETWORK + window?.log?.info('DeleteAccount => Sending a last SyncConfiguration'); + + // be sure to wait for the message being effectively sent. Otherwise we won't be able to encrypt it for our devices ! + await forceSyncConfigurationNowIfNeeded(true); + window?.log?.info('Last configuration message sent!'); + await deleteDbLocally(); + window.restart(); + } catch (error) { + // if an error happened, it's not related to the delete everything on network logic as this is handled above. + // this could be a last sync configuration message not being sent. + // in all case, we delete everything, and restart + window?.log?.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + try { + await deleteDbLocally(); + } catch (e) { + window?.log?.error(e); + } finally { + window.restart(); + } + } +} + +export async function deleteEverythingAndNetworkData() { + try { + // DELETE EVERYTHING ON NETWORK, AND THEN STUFF LOCALLY STORED + // a bit of duplicate code below, but it's easier to follow every case like that (helped with returns) + + // clear all sogs inboxes (includes message requests) + const allRoomInfos = await getAllValidOpenGroupV2ConversationRoomInfos(); + if (allRoomInfos && allRoomInfos.size > 0) { + // clear each inbox per sogs + // eslint-disable-next-line no-restricted-syntax + for (const roomInfo of allRoomInfos.values()) { + try { + // eslint-disable-next-line no-await-in-loop + const success = await clearInbox(roomInfo); + if (!success) { + throw Error(`Failed to clear inbox for ${roomInfo.conversationId}`); + } + } catch (error) { + window.log.info('DeleteAccount =>', error); + continue; + } + } + } + + // send deletion message to the network + const potentiallyMaliciousSnodes = await SnodeAPI.forceNetworkDeletion(); + if (potentiallyMaliciousSnodes === null) { + window?.log?.warn('DeleteAccount => forceNetworkDeletion failed'); + + // close this dialog + window?.inboxStore?.dispatch(updateDeleteAccountModal(null)); + window?.inboxStore?.dispatch( + updateConfirmModal({ + title: window.i18n('dialogClearAllDataDeletionFailedTitle'), + message: window.i18n('dialogClearAllDataDeletionFailedDesc'), + okTheme: SessionButtonColor.Danger, + okText: window.i18n('deviceOnly'), + onClickOk: async () => { + await deleteDbLocally(); + window.restart(); + }, + onClickClose: () => { + window.inboxStore?.dispatch(updateConfirmModal(null)); + }, + }) + ); + return; + } + + if (potentiallyMaliciousSnodes.length > 0) { + const snodeStr = potentiallyMaliciousSnodes.map(ed25519Str); + window?.log?.warn( + 'DeleteAccount => forceNetworkDeletion Got some potentially malicious snodes', + snodeStr + ); + // close this dialog + window?.inboxStore?.dispatch(updateDeleteAccountModal(null)); + // open a new confirm dialog to ask user what to do + window?.inboxStore?.dispatch( + updateConfirmModal({ + title: window.i18n('dialogClearAllDataDeletionFailedTitle'), + message: window.i18n('dialogClearAllDataDeletionFailedMultiple', [ + potentiallyMaliciousSnodes.join(', '), + ]), + messageSub: window.i18n('dialogClearAllDataDeletionFailedTitleQuestion'), + okTheme: SessionButtonColor.Danger, + okText: window.i18n('deviceOnly'), + onClickOk: async () => { + await deleteDbLocally(); + window.restart(); + }, + onClickClose: () => { + window.inboxStore?.dispatch(updateConfirmModal(null)); + }, + }) + ); + return; + } + + // We removed everything on the network successfully (no malicious node!). Now delete the stuff we got locally + // without sending a last configuration message (otherwise this one will still be on the network) + await deleteDbLocally(); + window.restart(); + } catch (error) { + // if an error happened, it's not related to the delete everything on network logic as this is handled above. + // this could be a last sync configuration message not being sent. + // in all case, we delete everything, and restart + window?.log?.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + try { + await deleteDbLocally(); + } catch (e) { + window?.log?.error(e); + } + window.restart(); + } +}