Skip to content

Commit

Permalink
[#20035] feat: import missing key pair by scanning QR code (#20144)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohsen-ghafouri authored May 30, 2024
1 parent dafab10 commit e04c7f4
Show file tree
Hide file tree
Showing 17 changed files with 371 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,16 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC
callback)
}

@ReactMethod
fun inputConnectionStringForImportingKeypairsKeystores(connectionString: String, configJSON: String, callback: Callback) {
val jsonConfig = JSONObject(configJSON)
val receiverConfig = jsonConfig.getJSONObject("receiverConfig")
val keyStorePath = utils.pathCombine(utils.getNoBackupDirectory(), "/keystore")
receiverConfig.put("keystorePath", keyStorePath)

utils.executeRunnableStatusGoMethod(
{ Statusgo.inputConnectionStringForImportingKeypairsKeystores(connectionString, jsonConfig.toString()) },
callback
)
}
}
19 changes: 19 additions & 0 deletions modules/react-native-status/ios/RCTStatus/NetworkManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,23 @@ @implementation NetworkManager
callback(@[result]);
}

RCT_EXPORT_METHOD(inputConnectionStringForImportingKeypairsKeystores:(NSString *)cs
configJSON:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {

NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSMutableDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:&error];
NSMutableDictionary *receiverConfig = configDict[@"receiverConfig"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *keystoreDir = multiaccountKeystoreDir.path;

[receiverConfig setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:configDict];
NSString *result = StatusgoInputConnectionStringForImportingKeypairsKeystores(cs, modifiedConfigJSON);
callback(@[result]);
}

@end
29 changes: 24 additions & 5 deletions src/native_module/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,27 @@

(defn get-connection-string-for-exporting-keypairs-keystores
"Generates connection string form status-go for the purpose of exporting keypairs and keystores on sender side"
[config-json callback]
(log/info "[native-module] Fetching Export Keypairs Connection String"
{:fn :get-connection-string-for-exporting-keypairs-keystores
:config-json config-json})
(.getConnectionStringForExportingKeypairsKeystores ^js (network) config-json callback))
([config-json]
(native-utils/promisify-native-module-call get-connection-string-for-exporting-keypairs-keystores
config-json))
([config-json callback]
(log/info "[native-module] Fetching Export Keypairs Connection String"
{:fn :get-connection-string-for-exporting-keypairs-keystores
:config-json config-json})
(.getConnectionStringForExportingKeypairsKeystores ^js (network) config-json callback)))

(defn input-connection-string-for-importing-keypairs-keystores
"Provides connection string to status-go for the purpose of importing keypairs and keystores on the receiver side"
([connection-string config-json]
(native-utils/promisify-native-module-call input-connection-string-for-importing-keypairs-keystores
connection-string
config-json))
([connection-string config-json callback]
(log/info "[native-module] Sending Import Keypairs Connection String"
{:fn :input-connection-string-for-importing-keypairs-keystores
:config-json config-json
:connection-string connection-string})
(.inputConnectionStringForImportingKeypairsKeystores ^js (network)
connection-string
config-json
callback)))
8 changes: 7 additions & 1 deletion src/quo/components/wallet/missing_keypairs/style.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
{:top 1})

(def title-info-container
{:padding-left 8})
{:padding-left 8
:flex 1})

(def title-row
{:display :flex
:flex-direction :row
:justify-content :space-between})

(def title-container
{:align-items :flex-start
Expand Down
20 changes: 14 additions & 6 deletions src/quo/components/wallet/missing_keypairs/view.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns quo.components.wallet.missing-keypairs.view
(:require
[quo.components.buttons.button.view :as button]
[quo.components.icon :as icon]
[quo.components.list-items.missing-keypair.view :as missing-keypair]
[quo.components.markdown.text :as text]
Expand All @@ -10,7 +11,7 @@
[utils.i18n :as i18n]))

(defn title-view
[{:keys [keypairs blur?]}]
[{:keys [keypairs blur? on-import-press]}]
(let [theme (quo.theme/use-theme)]
[rn/view
{:accessibility-label :title
Expand All @@ -22,11 +23,18 @@
:color colors/warning-60}]]
[rn/view
{:style style/title-info-container}
[text/text
{:weight :medium
:style {:color colors/warning-60}}
(i18n/label :t/amount-missing-keypairs
{:amount (str (count keypairs))})]
[rn/view {:style style/title-row}
[text/text
{:weight :medium
:style {:color colors/warning-60}}
(i18n/label :t/amount-missing-keypairs
{:amount (str (count keypairs))})]
[button/button
{:type :outline
:background :blur
:size 24
:on-press on-import-press}
(i18n/label :t/import)]]
[text/text
{:size :paragraph-2
:style (style/subtitle blur? theme)}
Expand Down
47 changes: 47 additions & 0 deletions src/status_im/contexts/settings/wallet/data_store.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
(ns status-im.contexts.settings.wallet.data-store)

(defn extract-keypair-name
[db key-uids-set]
(when (= (count key-uids-set) 1)
(let [key-uid (first key-uids-set)
keypairs (get-in db [:wallet :keypairs])]
(->> (filter #(= (:key-uid %) key-uid) keypairs)
first
:name))))

(defn update-keypair
[keypairs key-uid update-fn]
(mapcat (fn [keypair]
(if (= (keypair :key-uid) key-uid)
(if-let [updated (update-fn keypair)]
[updated]
[])
[keypair]))
keypairs))

(defn make-accounts-fully-operable
[accounts key-uids-set]
(reduce-kv
(fn [acc k account]
(if (and (contains? key-uids-set (:key-uid account))
(= (keyword (:operable account)) :no))
(assoc acc k (assoc account :operable :fully))
(assoc acc k account)))
{}
accounts))

(defn- make-keypairs-accounts-fully-operable
[accounts]
(map (fn [account]
(assoc account :operable :fully))
accounts))

(defn make-keypairs-fully-operable
[keypairs key-uids-set]
(map (fn [keypair]
(if (contains? key-uids-set (:key-uid keypair))
(update keypair
:accounts
make-keypairs-accounts-fully-operable)
keypair))
keypairs))
38 changes: 38 additions & 0 deletions src/status_im/contexts/settings/wallet/effects.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
(ns status-im.contexts.settings.wallet.effects
(:require [native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.syncing.utils :as sync-utils]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as transforms]))

(rf/reg-fx :effects.connection-string/export-keypair
(fn [{:keys [key-uid sha3-pwd keypair-key-uid on-success on-fail]}]
(let [config-map (transforms/clj->json {:senderConfig {:loggedInKeyUid key-uid
:keystorePath ""
:keypairsToExport [keypair-key-uid]
:password (security/safe-unmask-data
sha3-pwd)}
:serverConfig {:timeout 0}})]
(-> (native-module/get-connection-string-for-exporting-keypairs-keystores
config-map)
(promesa/then (fn [response]
(if (sync-utils/valid-connection-string? response)
(on-success response)
(on-fail (js/Error.
"generic-error: failed to get connection string")))))
(promesa/catch on-fail)))))

(rf/reg-fx :effects.connection-string/import-keypair
(fn [{:keys [key-uid sha3-pwd keypairs-key-uids connection-string on-success on-fail]}]
(let [config-map (transforms/clj->json {:receiverConfig
{:loggedInKeyUid key-uid
:keystorePath ""
:password (security/safe-unmask-data
sha3-pwd)
:keypairsToImport keypairs-key-uids}})]
(-> (native-module/input-connection-string-for-importing-keypairs-keystores
connection-string
config-map)
(promesa/then #(on-success keypairs-key-uids))
(promesa/catch on-fail)))))
107 changes: 71 additions & 36 deletions src/status_im/contexts/settings/wallet/events.cljs
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
(ns status-im.contexts.settings.wallet.events
(:require
[native-module.core :as native-module]
[status-im.contexts.syncing.utils :as sync-utils]
[status-im.contexts.settings.wallet.data-store :as data-store]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as transforms]))

(defn- update-keypair
[keypairs key-uid update-fn]
(mapcat (fn [keypair]
(if (= (keypair :key-uid) key-uid)
(if-let [updated (update-fn keypair)]
[updated]
[])
[keypair]))
keypairs))
[utils.re-frame :as rf]))

(rf/reg-event-fx
:wallet/rename-keypair-success
(fn [{:keys [db]} [key-uid name]]
{:db (update-in db
[:wallet :keypairs]
#(update-keypair % key-uid (fn [keypair] (assoc keypair :name name))))
#(data-store/update-keypair % key-uid (fn [keypair] (assoc keypair :name name))))
:fx [[:dispatch [:navigate-back]]
[:dispatch
[:toasts/upsert
Expand All @@ -41,31 +28,28 @@

(rf/reg-event-fx :wallet/rename-keypair rename-keypair)

(defn get-key-pair-export-connection
(defn get-keypair-export-connection
[{:keys [db]} [{:keys [sha3-pwd keypair-key-uid callback]}]]
(let [key-uid (get-in db [:profile/profile :key-uid])
config-map (transforms/clj->json {:senderConfig {:loggedInKeyUid key-uid
:keystorePath ""
:keypairsToExport [keypair-key-uid]
:password (security/safe-unmask-data
sha3-pwd)}
:serverConfig {:timeout 0}})
handle-connection (fn [response]
(when (sync-utils/valid-connection-string? response)
(callback response)
(rf/dispatch [:hide-bottom-sheet])))]
(native-module/get-connection-string-for-exporting-keypairs-keystores
config-map
handle-connection)))
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:effects.connection-string/export-keypair
{:key-uid key-uid
:sha3-pwd sha3-pwd
:keypair-key-uid keypair-key-uid
:on-success (fn [connect-string]
(callback connect-string)
(rf/dispatch [:hide-bottom-sheet]))
:on-fail (fn [error]
(rf/dispatch [:toasts/upsert
{:type :negative
:text (.-message error)}]))}]]}))

(rf/reg-event-fx :wallet/get-key-pair-export-connection get-key-pair-export-connection)
(rf/reg-event-fx :wallet/get-keypair-export-connection get-keypair-export-connection)

(rf/reg-event-fx
:wallet/remove-keypair-success
(rf/reg-event-fx :wallet/remove-keypair-success
(fn [{:keys [db]} [key-uid]]
{:db (update-in db
[:wallet :keypairs]
#(update-keypair % key-uid (fn [_] nil)))
#(data-store/update-keypair % key-uid (fn [_] nil)))
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch
[:toasts/upsert
Expand All @@ -79,6 +63,57 @@
[{:method "accounts_deleteKeypair"
:params [key-uid]
:on-success [:wallet/remove-keypair-success key-uid]
:on-error #(log/info "failed to remove keypair " %)}]]]})
:on-error #(log/error "failed to remove keypair " {:error %})}]]]})

(rf/reg-event-fx :wallet/remove-keypair remove-keypair)

(defn make-keypairs-accounts-fully-operable
[{:keys [db]} [key-uids-to-update]]
(let [key-uids-set (set key-uids-to-update)
keypair-name (data-store/extract-keypair-name db key-uids-set)]
{:db (-> db
(update-in [:wallet :accounts] #(data-store/make-accounts-fully-operable % key-uids-set))
(update-in [:wallet :keypairs] #(data-store/make-keypairs-fully-operable % key-uids-set)))
:fx [[:dispatch
[:toasts/upsert
{:type :positive
:theme :dark
:text (if (= (count key-uids-to-update) 1)
(i18n/label :t/key-pair-imported-successfully {:name keypair-name})
(i18n/label :t/key-pairs-successfully-imported
{:count (count key-uids-to-update)}))}]]]}))

(rf/reg-event-fx :wallet/make-keypairs-accounts-fully-operable make-keypairs-accounts-fully-operable)

(defn connection-string-for-import-keypair
[{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string]}]]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:effects.connection-string/import-keypair
{:key-uid key-uid
:sha3-pwd sha3-pwd
:keypairs-key-uids keypairs-key-uids
:connection-string connection-string
:on-success #(rf/dispatch [:wallet/make-keypairs-accounts-fully-operable %])
:on-fail #(rf/dispatch [:toasts/upsert
{:type :negative
:theme :dark
:text %}])}]]}))

(rf/reg-event-fx :wallet/connection-string-for-import-keypair connection-string-for-import-keypair)

(defn success-keypair-qr-scan
[_ [connection-string keypairs-key-uids]]
{:fx [[:dispatch
[:standard-auth/authorize-with-password
{:blur? true
:theme :dark
:auth-button-label (i18n/label :t/confirm)
:on-auth-success (fn [password]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:wallet/connection-string-for-import-keypair
{:connection-string connection-string
:keypairs-key-uids keypairs-key-uids
:sha3-pwd password}]))}]]]})

(rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan)
Loading

0 comments on commit e04c7f4

Please sign in to comment.