[#21578] Keycard - Factory reset a Keycard

This commit is contained in:
andrey 2025-01-10 12:19:24 +01:00
parent 10434b53e0
commit 150aa988c6
No known key found for this signature in database
GPG Key ID: C20F2FDE9A98BA61
9 changed files with 177 additions and 15 deletions

View File

@ -20,7 +20,8 @@
(rf/dispatch [:navigate-back]) (rf/dispatch [:navigate-back])
(rf/dispatch [:open-modal :screen/confirm-backup (rf/dispatch [:open-modal :screen/confirm-backup
{:masked-seed-phrase masked-seed-phrase {:masked-seed-phrase masked-seed-phrase
:on-success #(rf/dispatch [:keycard/create.phrase-backed-up %])}])) :on-success #(rf/dispatch [:keycard/create.phrase-backed-up
masked-seed-phrase])}]))
(rf/reg-event-fx :keycard/create.get-phrase (rf/reg-event-fx :keycard/create.get-phrase
(fn [{:keys [db]}] (fn [{:keys [db]}]

View File

@ -52,6 +52,10 @@
(fn [args] (fn [args]
(keycard/export-key (keycard.utils/wrap-handlers args)))) (keycard/export-key (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/factory-reset
(fn [args]
(keycard/factory-reset (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/sign (rf/reg-fx :effects.keycard/sign
(fn [args] (fn [args]
(-> (keycard/sign args) (-> (keycard/sign args)

View File

@ -2,6 +2,7 @@
(:require [quo.core :as quo] (:require [quo.core :as quo]
[react-native.core :as rn] [react-native.core :as rn]
[status-im.common.events-helper :as events-helper] [status-im.common.events-helper :as events-helper]
[status-im.contexts.keycard.factory-reset.view :as factory-reset]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -31,7 +32,17 @@
:description-text description}] :description-text description}]
[rn/view {:style {:margin-horizontal 20}} [rn/view {:style {:margin-horizontal 20}}
[quo/keycard {:holder-name ""}] [quo/keycard {:holder-name ""}]
[quo/information-box (when (not= :keycard/error.keycard-blank error)
{:type :default [:<>
:style {:margin-top 20}} [quo/section-label
(i18n/label :t/unlock-reset-instructions)]]])) {:section (i18n/label :t/what-you-can-do) :container-style {:padding-vertical 8}}]
[quo/settings-item
{:title (i18n/label :t/factory-reset)
:image :icon
:image-props :i/placeholder
:action :arrow
:description :text
:description-props {:text (i18n/label :t/remove-keycard-content)}
:on-press (fn []
(rf/dispatch [:show-bottom-sheet
{:content factory-reset/sheet}]))}]])]]))

View File

@ -68,6 +68,10 @@
(fn [_ [data]] (fn [_ [data]]
{:effects.keycard/export-key data})) {:effects.keycard/export-key data}))
(rf/reg-event-fx :keycard/factory-reset
(fn [_ [data]]
{:effects.keycard/factory-reset data}))
(rf/reg-event-fx :keycard/connect-derive-address-and-add-account (rf/reg-event-fx :keycard/connect-derive-address-and-add-account
(fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]] (fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]]
{:fx [[:dispatch {:fx [[:dispatch

View File

@ -0,0 +1,91 @@
(ns status-im.contexts.keycard.factory-reset.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- reset-card
[]
(rf/dispatch
[:keycard/factory-reset
{:on-success (fn []
(rf/dispatch [:navigate-back])
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.factory-reset.success]))
:on-failure (fn []
(rf/dispatch [:navigate-back])
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.factory-reset.fail]))}]))
(defn- connect-and-reset
[key-uid]
(rf/dispatch
[:keycard/connect
{:key-uid key-uid
:on-success reset-card
:on-error (fn [error]
(if (or (= error :keycard/error.keycard-wrong-profile)
(= error :keycard/error.keycard-blank))
(do
(rf/dispatch [:navigate-back])
(rf/dispatch [:keycard/on-application-info-error error]))
(reset-card)))}]))
(defn success-view
[]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard-reset-success)
:description :text
:description-text (i18n/label :t/keycard-empty-ready)}]
[rn/view {:style {:flex 1}}]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press events-helper/navigate-back}
(i18n/label :t/done)]]])
(defn failed-view
[]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard-reset-failed)}]
[rn/view {:style {:flex 1}}]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press events-helper/navigate-back}
(i18n/label :t/try-again)]]])
(defn sheet
[]
(let [customization-color (rf/sub [:profile/customization-color])
key-uid (rf/sub [:keycard/key-uid])
[checked? set-checked] (rn/use-state false)]
[:<>
[quo/drawer-top {:title (i18n/label :t/factory-reset-keycard)}]
[quo/text
{:style {:padding-horizontal 20
:padding-top 8
:padding-bottom 8}}
(i18n/label :t/factory-reset-warning)]
[quo/disclaimer
{:checked? checked?
:container-style {:margin-horizontal 20}
:on-change #(set-checked (not checked?))}
(i18n/label :t/key-pair-erased)]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/continue)
:button-one-props {:disabled? (not checked?)
:customization-color customization-color
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(connect-and-reset key-uid))}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet]))}}]]))

View File

@ -30,19 +30,42 @@
:style style/keycard-owner-name} :style style/keycard-owner-name}
profile-name]]]]) profile-name]]]])
(defn- check-keycard
[]
(rf/dispatch
[:keycard/connect
{:on-error
(fn [error]
(if (= error :keycard/error.keycard-wrong-profile)
(rf/dispatch [:keycard/on-application-info-error error])
(rf/dispatch [:keycard/on-application-info-error error])))}]))
(defn registered-keycards (defn registered-keycards
[] []
(let [keycards (rf/sub [:keycard/registered-keycards])] (let [keycards (rf/sub [:keycard/registered-keycards])]
[:<> [:<>
[quo/divider-label [rn/view {:style {:flex 1}}
{:counter? false [quo/divider-label
:tight? true {:counter? false
:blur? true} :tight? true
(i18n/label :t/registered-keycards)] :blur? true}
[rn/view {:style style/registered-keycards-container} (i18n/label :t/registered-keycards)]
(for [keycard keycards] [rn/view {:style style/registered-keycards-container}
^{:key (:keycard-uid keycard)} (for [keycard keycards]
[registered-keycard keycard])]])) ^{:key (:keycard-uid keycard)}
[registered-keycard keycard])]]
[quo/text
{:size :heading-2
:weight :semi-bold
:style {:margin-left 20}}
(i18n/label :t/scan-keycard-actions)]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[quo/markdown-list {:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/ready-to-scan)
:button-one-props {:on-press check-keycard}}]]))
(defn view (defn view
[] []

View File

@ -35,6 +35,7 @@
[status-im.contexts.keycard.create.view :as keycard.create] [status-im.contexts.keycard.create.view :as keycard.create]
[status-im.contexts.keycard.empty.view :as keycard.empty] [status-im.contexts.keycard.empty.view :as keycard.empty]
[status-im.contexts.keycard.error.view :as keycard.error] [status-im.contexts.keycard.error.view :as keycard.error]
[status-im.contexts.keycard.factory-reset.view :as keycard.factory-reset]
[status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail] [status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail]
[status-im.contexts.keycard.migrate.profile-keys.view :as keycard.migrate.profile-keys] [status-im.contexts.keycard.migrate.profile-keys.view :as keycard.migrate.profile-keys]
[status-im.contexts.keycard.migrate.re-encrypting.view :as keycard.re-encrypting] [status-im.contexts.keycard.migrate.re-encrypting.view :as keycard.re-encrypting]
@ -964,7 +965,19 @@
{:name :screen/keycard.create.ready-to-add {:name :screen/keycard.create.ready-to-add
:metrics {:track? true} :metrics {:track? true}
:options {:insets {:top? true :bottom? true}} :options {:insets {:top? true :bottom? true}}
:component keycard.create/ready-to-add}]) :component keycard.create/ready-to-add}
{:name :screen/keycard.factory-reset.success
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.factory-reset/success-view}
{:name :screen/keycard.factory-reset.fail
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.factory-reset/failed-view}])
(defn screens (defn screens
[] []

View File

@ -82,3 +82,9 @@
:<- [:keycard] :<- [:keycard]
(fn [keycard] (fn [keycard]
(get-in keycard [:application-info :initialized?]))) (get-in keycard [:application-info :initialized?])))
(rf/reg-sub
:keycard/key-uid
:<- [:keycard]
(fn [keycard]
(get-in keycard [:application-info :key-uid])))

View File

@ -1028,6 +1028,9 @@
"export-key": "Export private key", "export-key": "Export private key",
"external-link": "External link", "external-link": "External link",
"external-storage-denied": "Access to external storage is denied", "external-storage-denied": "Access to external storage is denied",
"factory-reset": "Factory reset",
"factory-reset-keycard": "Factory reset this Keycard",
"factory-reset-warning": "All data will be erased from the Keycard. Please ensure you have a backup of your recovery phrase before proceeding with the factory reset.",
"failed": "Failed", "failed": "Failed",
"failed-on": "Failed on", "failed-on": "Failed on",
"failed-to-fetch-community": "Failed to fetch community", "failed-to-fetch-community": "Failed to fetch community",
@ -1346,6 +1349,7 @@
"key-name-error-taken": "Key pair name already in use", "key-name-error-taken": "Key pair name already in use",
"key-name-error-too-short": "Key pair name must be at least {{count}} characters", "key-name-error-too-short": "Key pair name must be at least {{count}} characters",
"key-on-device": "Private key is saved on this device", "key-on-device": "Private key is saved on this device",
"key-pair-erased": "Key pair will be erased from Keycard",
"key-pair-imported-successfully": "{{name}} key pair imported successfully", "key-pair-imported-successfully": "{{name}} key pair imported successfully",
"key-pair-migrated-successfully": "Profile key pair successfully migrated", "key-pair-migrated-successfully": "Profile key pair successfully migrated",
"key-pair-name-updated": "Key pair name updated", "key-pair-name-updated": "Key pair name updated",
@ -1369,6 +1373,7 @@
"keycard-desc": "Own a Keycard? Store your keys on it; youll need it for transactions", "keycard-desc": "Own a Keycard? Store your keys on it; youll need it for transactions",
"keycard-dont-ask-card": "Don't ask for card to sign in", "keycard-dont-ask-card": "Don't ask for card to sign in",
"keycard-empty": "Keycard is empty", "keycard-empty": "Keycard is empty",
"keycard-empty-ready": "Keycard is empty and ready to be used",
"keycard-enter-new-passcode": "Enter new passcode {{step}}/2", "keycard-enter-new-passcode": "Enter new passcode {{step}}/2",
"keycard-error-description": "Connect the card again to continue", "keycard-error-description": "Connect the card again to continue",
"keycard-error-title": "Connection lost", "keycard-error-title": "Connection lost",
@ -1427,7 +1432,9 @@
"keycard-redeem-title": "Redeem to", "keycard-redeem-title": "Redeem to",
"keycard-redeem-tx": "Redeem assets", "keycard-redeem-tx": "Redeem assets",
"keycard-redeem-tx-desc": "Tap the card to sign and receive assets", "keycard-redeem-tx-desc": "Tap the card to sign and receive assets",
"keycard-reset-failed": "Failed to reset Keycard",
"keycard-reset-passcode": "Reset passcode", "keycard-reset-passcode": "Reset passcode",
"keycard-reset-success": "Keycard has been reset",
"keycard-success-description": "You may remove the card now", "keycard-success-description": "You may remove the card now",
"keycard-success-title": "Success", "keycard-success-title": "Success",
"keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.", "keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.",
@ -2140,6 +2147,7 @@
"remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them", "remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them",
"remove-group": "Remove group", "remove-group": "Remove group",
"remove-key-pair-and-derived-accounts": "Remove key pair and derived accounts", "remove-key-pair-and-derived-accounts": "Remove key pair and derived accounts",
"remove-keycard-content": "Remove all content from this Keycard",
"remove-network": "Remove network", "remove-network": "Remove network",
"remove-nickname": "Remove nickname", "remove-nickname": "Remove nickname",
"remove-nickname-toast": "You have removed {{secondary-name}}'s nickname", "remove-nickname-toast": "You have removed {{secondary-name}}'s nickname",
@ -2232,6 +2240,7 @@
"scan-an-address-qr-code": "Scan an address QR code", "scan-an-address-qr-code": "Scan an address QR code",
"scan-key-pairs-qr-code": "Scan key pairs QR code", "scan-key-pairs-qr-code": "Scan key pairs QR code",
"scan-keycard": "Scan Keycard", "scan-keycard": "Scan Keycard",
"scan-keycard-actions": "Scan Keycard to see available actions",
"scan-or-enter-a-sync-code": "Scan or enter a sync code", "scan-or-enter-a-sync-code": "Scan or enter a sync code",
"scan-or-enter-sync-code": "Scan or enter sync code", "scan-or-enter-sync-code": "Scan or enter sync code",
"scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device", "scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device",