diff --git a/src/status_im/keycard/backup_key.cljs b/src/status_im/keycard/backup_key.cljs new file mode 100644 index 0000000000..233976e41a --- /dev/null +++ b/src/status_im/keycard/backup_key.cljs @@ -0,0 +1,40 @@ +(ns status-im.keycard.backup-key + (:require [status-im.utils.fx :as fx] + [status-im.ethereum.mnemonic :as mnemonic] + [status-im.multiaccounts.recover.core :as multiaccounts.recover] + [status-im.navigation :as navigation] + [taoensso.timbre :as log])) + +(fx/defn backup-card-pressed + {:events [:keycard-settings.ui/backup-card-pressed]} + [{:keys [db] :as cofx}] + (log/debug "[keycard] start backup") + (fx/merge cofx + {:db (-> db + (assoc-in [:keycard :creating-backup?] true))} + (navigation/navigate-to-cofx :seed-phrase nil))) + +(fx/defn start-keycard-backup + {:events [::start-keycard-backup]} + [{:keys [db] :as cofx}] + {::multiaccounts.recover/import-multiaccount {:passphrase (-> db + :multiaccounts/key-storage + :seed-phrase + mnemonic/sanitize-passphrase) + :password nil + :success-event ::create-backup-card}}) +(fx/defn create-backup-card + {:events [::create-backup-card]} + [{:keys [db] :as cofx} root-data derived-data] + (fx/merge cofx + {:db (-> db + (update :intro-wizard + assoc + :root-key root-data + :derived derived-data + :recovering? true + :selected-storage-type :advanced) + (assoc-in [:keycard :flow] :recovery) + (update :multiaccounts/key-storage dissoc :seed-phrase)) + :dismiss-keyboard nil} + (navigation/navigate-to-cofx :keycard-onboarding-intro nil))) \ No newline at end of file diff --git a/src/status_im/keycard/core.cljs b/src/status_im/keycard/core.cljs index 47693cca6e..9be3aef6a3 100644 --- a/src/status_im/keycard/core.cljs +++ b/src/status_im/keycard/core.cljs @@ -5,6 +5,7 @@ status-im.keycard.delete-key status-im.keycard.export-key status-im.keycard.unpair + status-im.keycard.backup-key [status-im.keycard.login :as login] [status-im.keycard.mnemonic :as mnemonic] [status-im.keycard.onboarding :as onboarding] @@ -65,8 +66,9 @@ "instance-uid" instance-uid) (if (= flow :import) (navigation/navigate-to-cofx cofx :keycard-recovery-no-key nil) - (let [pairing-data (get-in db [:keycard :pairings instance-uid])] - (if pairing-data + (let [pairing-data (get-in db [:keycard :pairings (keyword instance-uid)]) + paired? (get-in db [:keycard :application-info :paired?])] + (if paired? (fx/merge cofx {:db (update-in db [:keycard :secrets] merge pairing-data)} (common/listen-to-hardware-back-button) diff --git a/src/status_im/keycard/recovery.cljs b/src/status_im/keycard/recovery.cljs index 2414e6a6c2..fed743e5bb 100644 --- a/src/status_im/keycard/recovery.cljs +++ b/src/status_im/keycard/recovery.cljs @@ -197,12 +197,20 @@ (navigation/navigate-to-cofx (if platform/android? :notifications-settings :welcome) nil)))))) +(fx/defn on-backup-success + [{:keys [db] :as cofx}] + (fx/merge cofx + {:utils/show-popup {:title (i18n/label :t/keycard-backup-success-title) + :content (i18n/label :t/keycard-backup-success-body)}} + (navigation/navigate-to-cofx :keycard-settings nil))) + (fx/defn on-generate-and-load-key-success {:events [:keycard.callback/on-generate-and-load-key-success] :interceptors [(re-frame/inject-cofx :random-guid-generator) (re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]} [{:keys [db random-guid-generator] :as cofx} data] - (let [account-data (js->clj data :keywordize-keys true)] + (let [account-data (js->clj data :keywordize-keys true) + backup? (get-in db [:keycard :creating-backup?])] (fx/merge cofx {:db (-> db (assoc-in [:keycard :multiaccount] @@ -222,12 +230,13 @@ (assoc-in [:keycard :application-info :key-uid] (ethereum/normalized-hex (:key-uid account-data))) (update :keycard dissoc :recovery-phrase) + (update :keycard dissoc :creating-backup?) (update-in [:keycard :secrets] dissoc :pin :puk :password) (assoc :multiaccounts/new-installation-id (random-guid-generator)) (update-in [:keycard :secrets] dissoc :mnemonic))} (common/remove-listener-to-hardware-back-button) (common/hide-connection-sheet) - (create-keycard-multiaccount)))) + (if backup? (on-backup-success) (create-keycard-multiaccount))))) (fx/defn on-generate-and-load-key-error {:events [:keycard.callback/on-generate-and-load-key-error]} diff --git a/src/status_im/multiaccounts/key_storage/core.cljs b/src/status_im/multiaccounts/key_storage/core.cljs index a48ce36234..7b94214f53 100644 --- a/src/status_im/multiaccounts/key_storage/core.cljs +++ b/src/status_im/multiaccounts/key_storage/core.cljs @@ -10,7 +10,8 @@ [status-im.popover.core :as popover] [status-im.utils.fx :as fx] [status-im.utils.security :as security] - [status-im.utils.types :as types])) + [status-im.utils.types :as types] + [status-im.keycard.backup-key :as keycard.backup])) (fx/defn key-and-storage-management-pressed "This event can be dispatched before login and from profile and needs to redirect accordingly" @@ -53,6 +54,14 @@ [cofx _] (popover/show-popover cofx {:view :seed-key-uid-mismatch})) +(fx/defn key-uid-matches + {:events [::key-uid-matches]} + [{:keys [db] :as cofx} _] + (let [backup? (get-in db [:keycard :creating-backup?])] + (if backup? + (keycard.backup/start-keycard-backup cofx) + (navigation/navigate-to-cofx cofx :storage nil)))) + (defn validate-seed-against-key-uid "Check if the key-uid was generated with the given seed-phrase" [{:keys [import-mnemonic-fn on-success on-error]} {:keys [seed-phrase key-uid]}] @@ -70,7 +79,7 @@ ::validate-seed-against-key-uid (partial validate-seed-against-key-uid {:import-mnemonic-fn native-module/multiaccount-import-mnemonic - :on-success #(re-frame/dispatch [:navigate-to :storage]) + :on-success #(re-frame/dispatch [::key-uid-matches]) :on-error #(re-frame/dispatch [::show-seed-key-uid-mismatch-error-popup])})) (fx/defn seed-phrase-validated @@ -85,7 +94,7 @@ (popover/show-popover cofx {:view :custom-seed-phrase}) {::validate-seed-against-key-uid {:seed-phrase (-> db :multiaccounts/key-storage :seed-phrase) ;; Unique key-uid of the account for which we are going to move keys - :key-uid (-> db :multiaccounts/login :key-uid)}}))) + :key-uid (or (-> db :multiaccounts/login :key-uid) (-> db :multiaccount :key-uid))}}))) (fx/defn choose-storage-pressed {:events [::choose-storage-pressed]} diff --git a/src/status_im/ui/screens/keycard/settings/views.cljs b/src/status_im/ui/screens/keycard/settings/views.cljs index 18e84dfe5d..6c4f9b8d63 100644 --- a/src/status_im/ui/screens/keycard/settings/views.cljs +++ b/src/status_im/ui/screens/keycard/settings/views.cljs @@ -95,19 +95,15 @@ :on-press #(re-frame/dispatch [:keycard-settings.ui/change-pin-pressed])}] ;; TODO(rasom): uncomment this when unpairing will be enabled ;; https://github.com/status-im/status-react/issues/9227 + [quo/list-item {:icon :main-icons/keycard + :size :small + :title (i18n/label :t/keycard-backup) + :accessibility-label "create-backup-keycard" + :on-press #(re-frame/dispatch [:keycard-settings.ui/backup-card-pressed])}] #_[quo/list-item {:icon :main-icons/close :size :small :title (i18n/label :t/unpair-card) - :on-press #(re-frame/dispatch [:keycard-settings.ui/unpair-card-pressed])}]])])] - ; NOTE: Reset card is hidden until multiaccount removal will be implemented - #_(when pairing - [react/view {:margin-bottom 35 - :margin-left 16} - [quo/list-item {:icon :main-icons/warning - :theme :negative - :size :small - :title (i18n/label :t/reset-card) - :on-press #(re-frame/dispatch [:keycard-settings.ui/reset-card-pressed])}]])]])) + :on-press #(re-frame/dispatch [:keycard-settings.ui/unpair-card-pressed])}]])])]]])) (defn reset-pin [] [keycard.views/login-pin diff --git a/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs b/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs index ed8404c059..332717e60b 100644 --- a/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs @@ -103,7 +103,8 @@ (defview seed-phrase [] (letsubs - [{:keys [seed-word-count seed-shape-invalid?]} [:multiaccounts/key-storage]] + [{:keys [seed-word-count seed-shape-invalid?]} [:multiaccounts/key-storage] + {:keys [creating-backup?]} [:keycard]] [react/keyboard-avoiding-view {:flex 1} [local-topbar (i18n/label :t/enter-seed-phrase)] [multiaccounts.views/seed-phrase-input @@ -122,7 +123,7 @@ (nil? seed-shape-invalid?)) :on-press #(re-frame/dispatch [::multiaccounts.key-storage/choose-storage-pressed]) :after :main-icons/next} - (i18n/label :t/choose-storage)]}]])) + (i18n/label (if creating-backup? :t/next :t/choose-storage))]}]])) (defn keycard-subtitle [] [react/view @@ -197,7 +198,8 @@ (i18n/label :t/confirm)]}]]])) (defview seed-key-uid-mismatch-popover [] - (letsubs [{:keys [name]} [:multiaccounts/login]] + (letsubs [{:keys [name]} [:multiaccounts/login] + {logged-in-name :name} [:multiaccount]] [react/view {:margin-top 24 :margin-horizontal 24 :align-items :center} @@ -215,7 +217,7 @@ [react/view [react/text {:style (into styles/popover-text {:margin-bottom 16})} - (i18n/label :t/seed-key-uid-mismatch-desc-1 {:multiaccount-name name})] + (i18n/label :t/seed-key-uid-mismatch-desc-1 {:multiaccount-name (or name logged-in-name)})] [react/text {:style styles/popover-text} (i18n/label :t/seed-key-uid-mismatch-desc-2)]]] [react/view {:margin-vertical 24 diff --git a/src/status_im/ui/screens/routing/main.cljs b/src/status_im/ui/screens/routing/main.cljs index f21dcfca80..80a4d3c968 100644 --- a/src/status_im/ui/screens/routing/main.cljs +++ b/src/status_im/ui/screens/routing/main.cljs @@ -27,7 +27,12 @@ [status-im.ui.screens.status.new.views :as status.new] [status-im.ui.screens.browser.bookmarks.views :as bookmarks] [status-im.ui.screens.routing.status-stack :as status-stack] - [status-im.ui.screens.communities.invite :as communities.invite])) + [status-im.ui.screens.communities.invite :as communities.invite] + [status-im.ui.screens.keycard.onboarding.views :as keycard.onboarding] + [status-im.ui.screens.keycard.recovery.views :as keycard.recovery] + [status-im.keycard.core :as keycard.core] + [status-im.ui.screens.keycard.views :as keycard] + [status-im.ui.screens.multiaccounts.key-storage.views :as key-storage.views])) (defonce main-stack (navigation/create-stack)) (defonce bottom-tabs (navigation/create-bottom-tabs)) @@ -55,7 +60,8 @@ :component profile-stack/profile-stack}]]) (views/defview get-main-component [_] - (views/letsubs [logged-in? [:multiaccount/logged-in?]] + (views/letsubs [logged-in? [:multiaccount/logged-in?] + keycard-account? [:multiaccounts/keycard-account?]] [main-stack (merge {:header-mode :none} ;; https://github.com/react-navigation/react-navigation/issues/6520 (when platform/ios? @@ -170,4 +176,26 @@ (when config/quo-preview-enabled? [{:name :quo-preview :insets {:top false :bottom false} - :component quo.preview/preview-stack}]))])) + :component quo.preview/preview-stack}]) + + (when keycard-account? + [{:name :keycard-onboarding-intro + :back-handler keycard.core/onboarding-intro-back-handler + :component keycard.onboarding/intro} + {:name :keycard-onboarding-puk-code + :back-handler :noop + :component keycard.onboarding/puk-code} + {:name :keycard-onboarding-pin + :back-handler :noop + :component keycard.onboarding/pin} + {:name :keycard-recovery-pair + :back-handler :noop + :component keycard.recovery/pair} + {:name :seed-phrase + :component key-storage.views/seed-phrase} + {:name :keycard-recovery-pin + :component keycard.recovery/pin} + {:name :keycard-wrong + :component keycard/wrong} + {:name :not-keycard + :component keycard/not-keycard}]))])) diff --git a/translations/en.json b/translations/en.json index 403fb305f7..b47c8ed642 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1211,6 +1211,9 @@ "keycard-error-description": "Connect the card again to continue", "keycard-success-title": "Success", "keycard-success-description": "You may remove the card now", + "keycard-backup": "Create a backup Keycard", + "keycard-backup-success-title": "Backup successful", + "keycard-backup-success-body": "Backup card created successfully. You can now use it with your account just like the primary card.", "type-a-message": "Message", "ulc-enabled": "ULC enabled", "unable-to-read-this-code": "Unable to read this code",