diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index 78e9ad3c82..2faf6f8b63 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -504,10 +504,17 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL StatusThreadPoolExecutor.getInstance().execute(r); } + public String getKeyStorePath(String keyUID) { + final String commonKeydir = pathCombine(this.getNoBackupDirectory(), "/keystore"); + final String keydir = pathCombine(commonKeydir, keyUID); + + return keydir; + } + public void migrateKeyStoreDir(final String accountData, final String password) { try { final String commonKeydir = pathCombine(this.getNoBackupDirectory(), "/keystore"); - final String keydir = pathCombine(commonKeydir, getKeyUID(accountData)); + final String keydir = this.getKeyStorePath(this.getKeyUID(accountData)); Log.d(TAG, "before migrateKeyStoreDir " + keydir); File keydirFile = new File(keydir); @@ -1183,6 +1190,27 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL StatusThreadPoolExecutor.getInstance().execute(r); } + @ReactMethod + public void deleteMultiaccount(final String keyUID, final Callback callback) { + Log.d(TAG, "deleteMultiaccount"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + + final String keyStoreDir = this.getKeyStorePath(keyUID); + Runnable r = new Runnable() { + @Override + public void run() { + String res = Statusgo.deleteMultiaccount(keyUID, keyStoreDir); + + callback.invoke(res); + } + }; + + StatusThreadPoolExecutor.getInstance().execute(r); + } + @ReactMethod(isBlockingSynchronousMethod = true) public String generateAlias(final String seed) { return Statusgo.generateAlias(seed); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index c9299e69ac..b5add0f2b3 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -221,6 +221,17 @@ RCT_EXPORT_METHOD(chaosModeUpdate:(BOOL)on #endif } +//////////////////////////////////////////////////////////////////// multiAccountImportPrivateKey +RCT_EXPORT_METHOD(deleteMultiaccount:(NSString *)keyUID + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"MultiAccountImportPrivateKey() method called"); +#endif + NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID]; + NSString *result = StatusgoDeleteMultiaccount(keyUID, multiaccountKeystoreDir.path); + callback(@[result]); +} + //////////////////////////////////////////////////////////////////// multiAccountGenerateAndDeriveAddresses RCT_EXPORT_METHOD(multiAccountGenerateAndDeriveAddresses:(NSString *)json callback:(RCTResponseSenderBlock)callback) { @@ -440,6 +451,18 @@ RCT_EXPORT_METHOD(saveAccountAndLoginWithKeycard:(NSString *)multiaccountData NSLog(@"%@", result); } +- (NSURL *) getKeyStoreDir:(NSString *)keyUID { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *rootUrl =[[fileManager + URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] + lastObject]; + + NSURL *oldKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"]; + NSURL *multiaccountKeystoreDir = [oldKeystoreDir URLByAppendingPathComponent:keyUID]; + + return multiaccountKeystoreDir; +} + - (void) migrateKeystore:(NSString *)accountData password:(NSString *)password { NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -449,7 +472,7 @@ RCT_EXPORT_METHOD(saveAccountAndLoginWithKeycard:(NSString *)multiaccountData NSString *keyUID = [self getKeyUID:accountData]; NSURL *oldKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"]; - NSURL *multiaccountKeystoreDir = [oldKeystoreDir URLByAppendingPathComponent:keyUID]; + NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID]; NSArray *keys = [fileManager contentsOfDirectoryAtPath:multiaccountKeystoreDir.path error:nil]; if (keys.count == 0) { diff --git a/src/quo/components/separator.cljs b/src/quo/components/separator.cljs new file mode 100644 index 0000000000..5e7731a750 --- /dev/null +++ b/src/quo/components/separator.cljs @@ -0,0 +1,12 @@ +(ns quo.components.separator + (:require [quo.react-native :as react] + [quo.design-system.colors :as colors])) + +(defn separator [{:keys [color style]}] + [react/view + {:style + (merge + style + {:background-color (colors/get-color (or color :ui-01)) + :align-self :stretch + :height 1})}]) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 158f8307cd..2f848197ed 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -10,7 +10,9 @@ [quo.components.list.footer :as list-footer] [quo.components.list.item :as list-item] [quo.components.controls.view :as controls] - [quo.components.bottom-sheet.view :as bottom-sheet])) + [quo.components.bottom-sheet.view :as bottom-sheet] + [quo.components.separator :as separator] + [quo.design-system.colors :as colors])) (def text text/text) (def header header/header) @@ -28,3 +30,5 @@ (def safe-area-provider safe-area/provider) (def safe-area-consumer safe-area/consumer) (def safe-area-view safe-area/view) +(def separator separator/separator) +(def get-color colors/get-color) diff --git a/src/quo/design_system/colors.cljs b/src/quo/design_system/colors.cljs index 37bea57aa9..3068850413 100644 --- a/src/quo/design_system/colors.cljs +++ b/src/quo/design_system/colors.cljs @@ -71,3 +71,6 @@ :backdrop "rgba(0,0,0,0.4)"}) (def theme (reagent/atom light-theme)) + +(defn get-color [color] + (get @theme color)) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index a10a3611e4..cf3295c578 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -65,7 +65,8 @@ status-im.utils.universal-links.events status-im.search.core status-im.ui.screens.profile.events - status-im.chat.models.images)) + status-im.chat.models.images + status-im.ui.screens.privacy-and-security-settings.events)) ;; init module (handlers/register-handler-fx diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 53d978e2dd..97d2b92b8c 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -357,3 +357,10 @@ [mnemonic callback] (log/debug "[native-module] validate-mnemonic") (.validateMnemonic ^js (status) mnemonic callback)) + +(defn delete-multiaccount + "Delete multiaccount from database, deletes multiaccount's database and + key files." + [key-uid callback] + (log/debug "[native-module] delete-multiaccount") + (.deleteMultiaccount ^js (status) key-uid callback)) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 32574a2a18..d0f9936e98 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -188,6 +188,9 @@ ;; keycard (reg-root-key-sub :keycard/new-account-sheet? :keycard/new-account-sheet?) +;; delete profile +(reg-root-key-sub :delete-profile/error :delete-profile/error) + ;;GENERAL ============================================================================================================== (re-frame/reg-sub diff --git a/src/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs b/src/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs new file mode 100644 index 0000000000..2b9d1835b9 --- /dev/null +++ b/src/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs @@ -0,0 +1,86 @@ +(ns status-im.ui.screens.privacy-and-security-settings.delete-profile + (:require [status-im.ui.components.react :as react] + [quo.core :as quo] + [status-im.multiaccounts.core :as multiaccounts] + [status-im.ui.components.chat-icon.screen :as chat-icon.screen] + [re-frame.core :as re-frame] + [status-im.ui.components.topbar :as topbar] + [status-im.ui.components.styles :as components.styles] + [status-im.i18n :as i18n] + [reagent.core :as reagent] + [status-im.utils.security :as security] + [status-im.ui.screens.privacy-and-security-settings.events :as delete-profile])) + +(defn valid-password? [password] + (>= (count password) 6)) + +(defn keycard-pin [] + #_(let [pin @(re-frame/subscribe [:keycard/pin]) + step @(re-frame/subscribe [:keycard/pin-enter-step]) + status @(re-frame/subscribe [:keycard/pin-status]) + pin-retry-counter @(re-frame/subscribe [:keycard/pin-retry-counter]) + puk-retry-counter @(re-frame/subscribe [:keycard/puk-retry-counter]) + error-label @(re-frame/subscribe [:keycard/pin-error-label])] + [pin.views/pin-view + {:pin pin + :status status + :retry-counter pin-retry-counter + :error-label error-label + :step :current}])) + +(defn delete-profile [] + (let [password (reagent/atom nil) + text-input-ref (atom nil)] + (fn [] + (let [keycard? @(re-frame/subscribe [:keycard-multiaccount?]) + multiaccount @(re-frame/subscribe [:multiaccount]) + error @(re-frame/subscribe [:delete-profile/error])] + (when (and @text-input-ref error (not @password)) + (.clear ^js @text-input-ref)) + [react/view components.styles/flex + [topbar/topbar {:modal? true}] + [react/view + {:style {:flex 1 + :justify-content :space-between}} + [react/scroll-view {:style {:flex 1}} + [react/view {:style {:align-items :center}} + [quo/text {:weight :bold + :size :x-large} + (i18n/label :t/delete-profile)]] + [quo/list-item + {:title (multiaccounts/displayed-name multiaccount) + :icon [chat-icon.screen/contact-icon-contacts-tab + (multiaccounts/displayed-photo multiaccount)]}] + [quo/text {:style {:margin-horizontal 24} + :align :center + :color :negative} + (i18n/label :t/delete-profile-warning)] + (if keycard? + [keycard-pin] + [quo/text-input + {:style {:margin-horizontal 36 + :margin-top 36} + :show-cancel false + :secure-text-entry true + :return-key-type :next + :on-submit-editing nil + :auto-focus true + :on-change-text #(reset! password (security/mask-data %)) + :bottom-value 36 + :get-ref #(reset! text-input-ref %) + :error (when (and error (not @password)) + (if (= :wrong-password error) + (i18n/label :t/wrong-password) + (str error)))}])] + (when-not keycard? + [react/view {:style {:align-items :center}} + [quo/separator] + [react/view + {:style {:margin-vertical 8}} + [quo/button {:on-press #(do + (re-frame/dispatch + [::delete-profile/delete-profile @password]) + (reset! password nil)) + :theme :negative + :disabled ((complement valid-password?) @password)} + (i18n/label :t/delete-profile)]]])]])))) diff --git a/src/status_im/ui/screens/privacy_and_security_settings/events.cljs b/src/status_im/ui/screens/privacy_and_security_settings/events.cljs new file mode 100644 index 0000000000..fbeeeb937a --- /dev/null +++ b/src/status_im/ui/screens/privacy_and_security_settings/events.cljs @@ -0,0 +1,67 @@ +(ns status-im.ui.screens.privacy-and-security-settings.events + (:require [status-im.utils.fx :as fx] + [re-frame.core :as re-frame] + [status-im.utils.security :as security] + [status-im.native-module.core :as status] + [status-im.ethereum.core :as ethereum] + [status-im.utils.types :as types] + [taoensso.timbre :as log] + [clojure.string :as clojure.string] + [status-im.i18n :as i18n])) + +(defn safe-blank? [s] + (or (not s) + (clojure.string/blank? s))) + +(re-frame/reg-fx + ::delete-profile + (fn [{:keys [address key-uid callback masked-password]}] + (let [hashed-password + (-> masked-password + security/safe-unmask-data + ethereum/sha3)] + (status/verify + address + hashed-password + (fn [result] + (let [{:keys [error]} (types/json->clj result)] + (log/info "[delete-profile] verify-password" result error) + (if-not (safe-blank? error) + (callback :wrong-password nil) + (status/delete-multiaccount + key-uid + (fn [result] + (let [{:keys [error]} (types/json->clj result)] + (callback error nil))))))))))) + +(fx/defn delete-profile + {:events [::delete-profile]} + [{:keys [db] :as cofx} masked-password] + (log/info "[delete-profile] delete") + (let [{:keys [key-uid wallet-root-address]} (:multiaccount db)] + {:db (dissoc db :delete-profile/error) + ::delete-profile + {:masked-password masked-password + :key-uid key-uid + :address wallet-root-address + :callback + (fn [error result] + (log/info "[delete-profile] callback" error) + (if (safe-blank? error) + (re-frame/dispatch [::on-delete-profile-success result]) + (re-frame/dispatch [::on-delete-profile-failure error])))}})) + +(fx/defn on-delete-profile-success + {:events [::on-delete-profile-success]} + [cofx] + (log/info "[delete-profile] on-success") + {:utils/show-popup + {:title (i18n/label :t/profile-deleted-title) + :content (i18n/label :t/profile-deleted-content) + :on-dismiss #(re-frame/dispatch [:logout])}}) + +(fx/defn on-delete-profile-failure + {:events [::on-delete-profile-failure]} + [{:keys [db]} error] + (log/info "[delete-profile] on-failure" error) + {:db (assoc db :delete-profile/error error)}) diff --git a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs index 1cecb7c4b6..673e128038 100644 --- a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs @@ -10,10 +10,14 @@ [status-im.utils.platform :as platform]) (:require-macros [status-im.utils.views :as views])) +(defn separator [] + [quo/separator {:style {:margin-vertical 8}}]) + (views/defview privacy-and-security [] (views/letsubs [{:keys [mnemonic preview-privacy?]} [:multiaccount] supported-biometric-auth [:supported-biometric-auth] - auth-method [:auth-method]] + auth-method [:auth-method] + keycard? [:keycard-multiaccount?]] [react/view {:flex 1 :background-color colors/white} [topbar/topbar {:title :t/privacy-and-security}] [react/scroll-view {:padding-vertical 8} @@ -34,9 +38,7 @@ :accessory :switch :on-press #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched ((complement boolean) (= auth-method "biometric"))])}]) - [react/view {:margin-vertical 8 - :background-color colors/gray-lighter - :height 1}] + [separator] ;; TODO - uncomment when implemented ;; {:size :small ;; :title (i18n/label :t/change-password) @@ -61,9 +63,14 @@ :on-press #(re-frame/dispatch [:multiaccounts.ui/preview-privacy-mode-switched ((complement boolean) preview-privacy?)])}] - - (comment - {:container-margin-top 8 - :size :small - :title :t/delete-my-account - :theme :negative})]])) + ;; TODO(rasom): remove this condition when kk support will be added + (when-not keycard? + [separator]) + (when-not keycard? + [quo/list-item + {:size :small + :theme :negative + :title (i18n/label :t/delete-my-profile) + :on-press #(re-frame/dispatch [:navigate-to :delete-profile]) + :accessibility-label :dapps-permissions-button + :chevron true}])]])) diff --git a/src/status_im/ui/screens/routing/profile_stack.cljs b/src/status_im/ui/screens/routing/profile_stack.cljs index 1224f22964..c8bf2c21a4 100644 --- a/src/status_im/ui/screens/routing/profile_stack.cljs +++ b/src/status_im/ui/screens/routing/profile_stack.cljs @@ -37,7 +37,8 @@ [status-im.ui.screens.keycard.settings.views :as keycard.settings] [status-im.ui.components.tabbar.styles :as tabbar.styles] [status-im.ui.screens.routing.core :as navigation] - [status-im.ui.screens.appearance.views :as appearance])) + [status-im.ui.screens.appearance.views :as appearance] + [status-im.ui.screens.privacy-and-security-settings.delete-profile :as delete-profile])) (defonce stack (navigation/create-stack)) @@ -115,6 +116,10 @@ :component profile.seed/backup-seed} {:name :tribute-to-talk :component tr-to-talk/tribute-to-talk} + {:name :delete-profile + :transition :presentation-ios + :insets {:bottom true} + :component delete-profile/delete-profile} ;; {:name:my-profile-ext-settings ;; :component} diff --git a/status-go-version.json b/status-go-version.json index 40d3298312..70668810fc 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.55.1", - "commit-sha1": "6d5a93287b2c1b4d4d5d1178a3ecec870bf18b9e", - "src-sha256": "1wly0km9bxxv1wwj6jchqh4d4x2m86fxrdqixjzldy70vl6qbyqa" + "version": "v0.55.2", + "commit-sha1": "0b3cdf7362bbdf9ba7fc11da803105f9417dfbac", + "src-sha256": "1vq3z150p0fbwjc1mqmi8lz4vg28dzqhlpsn7kar8j5z4rx5z5hn" } diff --git a/translations/en.json b/translations/en.json index 33ab260178..048a8acd14 100644 --- a/translations/en.json +++ b/translations/en.json @@ -311,6 +311,11 @@ "delete-network-confirmation": "Are you sure you want to delete this network?", "delete-network-error": "Please connect to a different network before deleting this one", "delete-network-title": "Delete network?", + "delete-profile": "Delete profile", + "delete-my-profile": "Delete my profile", + "delete-profile-warning": "Warning: If you don’t have your seed phrase written down, you will loose access to your funds after you delete your profile", + "profile-deleted-title": "Profile deleted", + "profile-deleted-content": "Your profile was successfully deleted", "deny": "Deny", "description": "Description", "dev-mode": "Development mode",