diff --git a/mobile_files/package.json.orig b/mobile_files/package.json.orig index a8dcd96fe8..20d019de9f 100644 --- a/mobile_files/package.json.orig +++ b/mobile_files/package.json.orig @@ -54,7 +54,7 @@ "react-native-safe-area-view": "0.9.0", "react-native-securerandom": "git+https://github.com/status-im/react-native-securerandom.git#0.1.1-2", "react-native-splash-screen": "3.1.1", - "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#v2.3.1", + "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#v2.3.2", "react-native-svg": "6.5.2", "react-native-tcp": "git+https://github.com/status-im/react-native-tcp.git#v3.3.0-1-status", "react-native-udp": "git+https://github.com/status-im/react-native-udp.git#2.3.1-1", diff --git a/mobile_files/yarn.lock b/mobile_files/yarn.lock index 97881ec278..2e95cd858b 100644 --- a/mobile_files/yarn.lock +++ b/mobile_files/yarn.lock @@ -6007,9 +6007,9 @@ react-native-splash-screen@3.1.1: resolved "https://registry.yarnpkg.com/react-native-splash-screen/-/react-native-splash-screen-3.1.1.tgz#1a4e46c9fdce53ff52af2a2cb4181788c4e30b30" integrity sha512-PU2YocOSGbLjL9Vgcq/cwMNuHHKNjjuPpa1IPMuWo+6EB/fSZ5VOmxSa7+eucQe3631s3NhGuk3eHKahU03a4Q== -"react-native-status-keycard@git+https://github.com/status-im/react-native-status-keycard.git#v2.3.1": - version "2.3.1" - resolved "git+https://github.com/status-im/react-native-status-keycard.git#2dffe6aae4d2d29bc2649637f3c4a9157b2460c6" +"react-native-status-keycard@git+https://github.com/status-im/react-native-status-keycard.git#v2.3.2": + version "2.3.2" + resolved "git+https://github.com/status-im/react-native-status-keycard#4c2aada5dc3b9d106935ab1747ab33ad4e94547e" react-native-svg@6.5.2: version "6.5.2" diff --git a/resources/icons/logout.svg b/resources/icons/logout.svg new file mode 100755 index 0000000000..28806e8456 --- /dev/null +++ b/resources/icons/logout.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/ui/hardwallet-card.png b/resources/images/ui/hardwallet-card.png old mode 100644 new mode 100755 index 9f50141657..57687f8e35 Binary files a/resources/images/ui/hardwallet-card.png and b/resources/images/ui/hardwallet-card.png differ diff --git a/resources/images/ui/warning-sign.png b/resources/images/ui/warning-sign.png new file mode 100755 index 0000000000..59518585fb Binary files /dev/null and b/resources/images/ui/warning-sign.png differ diff --git a/src/status_im/accounts/create/core.cljs b/src/status_im/accounts/create/core.cljs index 625cf018ac..c22e1baa5f 100644 --- a/src/status_im/accounts/create/core.cljs +++ b/src/status_im/accounts/create/core.cljs @@ -61,7 +61,8 @@ [{:keys [signing-phrase status db] :as cofx} - {:keys [pubkey address mnemonic installation-id keycard-instance-uid]} + {:keys [pubkey address mnemonic installation-id + keycard-instance-uid keycard-pairing keycard-paired-on]} password {:keys [seed-backed-up? login?] :or {login? true}}] (let [normalized-address (utils.hex/normalize-hex address) @@ -77,6 +78,8 @@ :seed-backed-up? seed-backed-up? :mnemonic mnemonic :keycard-instance-uid keycard-instance-uid + :keycard-pairing keycard-pairing + :keycard-paired-on keycard-paired-on :settings (constants/default-account-settings)}] (log/debug "account-created") (when-not (string/blank? pubkey) diff --git a/src/status_im/accounts/db.cljs b/src/status_im/accounts/db.cljs index 168ea87623..6d9ca2ffcb 100644 --- a/src/status_im/accounts/db.cljs +++ b/src/status_im/accounts/db.cljs @@ -57,6 +57,9 @@ (spec/def :account/wallet-set-up-passed? (spec/nilable boolean?)) (spec/def :account/mainnet-warning-shown? (spec/nilable boolean?)) (spec/def :account/desktop-alpha-release-warning-shown? (spec/nilable boolean?)) +(spec/def :account/keycard-instance-uid (spec/nilable string?)) +(spec/def :account/keycard-pairing (spec/nilable string?)) +(spec/def :account/keycard-paired-on (spec/nilable int?)) (spec/def :accounts/account (spec/keys :req-un [:account/name :account/address :account/public-key :account/photo-path :account/signing-phrase @@ -69,7 +72,10 @@ :account/wallet-set-up-passed? :account/last-request :account/bootnodes :account/extensions :account/mainnet-warning-shown? - :account/desktop-alpha-release-warning-shown?])) + :account/desktop-alpha-release-warning-shown? + :account/keycard-instance-uid + :account/keycard-pairing + :account/keycard-paired-on])) (spec/def :accounts/accounts (spec/nilable (spec/map-of :account/address :accounts/account))) diff --git a/src/status_im/data_store/realm/schemas/base/account.cljs b/src/status_im/data_store/realm/schemas/base/account.cljs index 35489fbf7c..43d5c80550 100644 --- a/src/status_im/data_store/realm/schemas/base/account.cljs +++ b/src/status_im/data_store/realm/schemas/base/account.cljs @@ -217,3 +217,8 @@ (def v16 (assoc-in v15 [:properties :keycard-instance-uid] {:type :string :optional true})) + +(def v17 (update v16 :properties merge {:keycard-pairing + {:type :string :optional true} + :keycard-paired-on + {:type :int :optional true}})) diff --git a/src/status_im/data_store/realm/schemas/base/core.cljs b/src/status_im/data_store/realm/schemas/base/core.cljs index 4b2dbfb557..0647a664ce 100644 --- a/src/status_im/data_store/realm/schemas/base/core.cljs +++ b/src/status_im/data_store/realm/schemas/base/core.cljs @@ -76,6 +76,11 @@ (def v20 v19) +(def v21 [network/v1 + bootnode/v4 + extension/v12 + account/v17]) + ;; put schemas ordered by version (def schemas [{:schema v1 :schemaVersion 1 @@ -136,4 +141,7 @@ :migration migrations/v19} {:schema v20 :schemaVersion 20 - :migration migrations/v20}]) + :migration migrations/v20} + {:schema v21 + :schemaVersion 21 + :migration migrations/v21}]) diff --git a/src/status_im/data_store/realm/schemas/base/migrations.cljs b/src/status_im/data_store/realm/schemas/base/migrations.cljs index e2d5f28b66..147585a6cb 100644 --- a/src/status_im/data_store/realm/schemas/base/migrations.cljs +++ b/src/status_im/data_store/realm/schemas/base/migrations.cljs @@ -196,3 +196,6 @@ ;; - nightlies that had v18 as an empty transition will apply the empty transition ;; v19 again, and migrate Infura IDs as v20. (migrate-infura-project-ids! new-realm)) + +(defn v21 [old-realm new-realm] + (log/debug "migrating base database v21: " old-realm new-realm)) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index f1cea296bb..ea30a4f110 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -863,8 +863,14 @@ (handlers/register-handler-fx :hardwallet.ui/get-application-info - (fn [_ _] - {:hardwallet/get-application-info nil})) + (fn [{:keys [db]} _] + {:hardwallet/get-application-info (get-in db [:hardwallet :secrets :pairing])})) + +(handlers/register-handler-fx + :hardwallet.callback/on-retrieve-pairing-success + (fn [{:keys [db]} [_ pairing-data]] + {:db (update-in db [:hardwallet :secrets] merge (select-keys pairing-data + [:pairing :paired-on]))})) (handlers/register-handler-fx :hardwallet.callback/on-register-card-events @@ -944,10 +950,60 @@ (fn [cofx [_ error]] (hardwallet/on-generate-and-load-key-error cofx error))) +(handlers/register-handler-fx + :hardwallet.callback/on-unblock-pin-success + (fn [cofx _] + (hardwallet/on-unblock-pin-success cofx))) + +(handlers/register-handler-fx + :hardwallet.callback/on-unblock-pin-error + (fn [cofx [_ error]] + (hardwallet/on-unblock-pin-error cofx error))) + +(handlers/register-handler-fx + :hardwallet.callback/on-verify-pin-success + (fn [cofx _] + (hardwallet/on-verify-pin-success cofx))) + +(handlers/register-handler-fx + :hardwallet.callback/on-verify-pin-error + (fn [cofx [_ error]] + (hardwallet/on-verify-pin-error cofx error))) + +(handlers/register-handler-fx + :hardwallet.callback/on-change-pin-success + (fn [cofx _] + (hardwallet/on-change-pin-success cofx))) + +(handlers/register-handler-fx + :hardwallet.callback/on-change-pin-error + (fn [cofx [_ error]] + (hardwallet/on-change-pin-error cofx error))) + +(handlers/register-handler-fx + :hardwallet.callback/on-unpair-success + (fn [cofx _] + (hardwallet/on-unpair-success cofx))) + +(handlers/register-handler-fx + :hardwallet.callback/on-unpair-error + (fn [cofx [_ error]] + (hardwallet/on-unpair-error cofx error))) + +(handlers/register-handler-fx + :hardwallet.callback/on-delete-success + (fn [cofx _] + (hardwallet/on-delete-success cofx))) + +(handlers/register-handler-fx + :hardwallet.callback/on-delete-error + (fn [cofx [_ error]] + (hardwallet/on-delete-error cofx error))) + (handlers/register-handler-fx :hardwallet.ui/status-hardwallet-option-pressed (fn [cofx _] - (hardwallet/navigate-to-connect-screen cofx))) + (hardwallet/status-hardwallet-option-pressed cofx))) (handlers/register-handler-fx :hardwallet.ui/password-option-pressed @@ -1072,7 +1128,12 @@ (handlers/register-handler-fx :hardwallet.ui/pin-numpad-button-pressed (fn [cofx [_ number step]] - (hardwallet/process-pin-input cofx number step))) + (hardwallet/update-pin cofx number step))) + +(handlers/register-handler-fx + :hardwallet/process-pin-input + (fn [cofx _] + (hardwallet/process-pin-input cofx))) (handlers/register-handler-fx :hardwallet.ui/pin-numpad-delete-button-pressed @@ -1107,6 +1168,61 @@ (fn [cofx _] (hardwallet/error-button-pressed cofx))) +(handlers/register-handler-fx + :keycard-settings.ui/change-pin-pressed + (fn [cofx _] + (hardwallet/change-pin-pressed cofx))) + +(handlers/register-handler-fx + :hardwallet/proceed-to-change-pin + (fn [cofx _] + (hardwallet/proceed-to-change-pin cofx))) + +(handlers/register-handler-fx + :keycard-settings.ui/unpair-card-pressed + (fn [cofx _] + (hardwallet/unpair-card-pressed cofx))) + +(handlers/register-handler-fx + :keycard-settings.ui/unpair-card-confirmed + (fn [cofx _] + (hardwallet/unpair-card-confirmed cofx))) + +(handlers/register-handler-fx + :hardwallet/unpair + (fn [cofx _] + (hardwallet/unpair cofx))) + +(handlers/register-handler-fx + :keycard-settings.ui/reset-card-pressed + (fn [cofx _] + (hardwallet/reset-card-pressed cofx))) + +(handlers/register-handler-fx + :keycard-settings.ui/reset-card-next-button-pressed + (fn [cofx _] + (hardwallet/reset-card-next-button-pressed cofx))) + +(handlers/register-handler-fx + :hardwallet/proceed-to-reset-card + (fn [cofx _] + (hardwallet/proceed-to-reset-card cofx))) + +(handlers/register-handler-fx + :hardwallet/unpair-and-delete + (fn [cofx _] + (hardwallet/unpair-and-delete cofx))) + +(handlers/register-handler-fx + :hardwallet/navigate-to-enter-pin-screen + (fn [cofx _] + (hardwallet/navigate-to-enter-pin-screen cofx))) + +(handlers/register-handler-fx + :hardwallet/navigate-to-reset-card-screen + (fn [cofx _] + (hardwallet/navigate-to-reset-card-screen cofx))) + ;; browser module (handlers/register-handler-fx @@ -1303,6 +1419,11 @@ (fn [cofx] (browser/open-url cofx "names.statusnet.eth"))) +(handlers/register-handler-fx + :profile.ui/keycard-settings-button-pressed + (fn [cofx] + (hardwallet/navigate-to-keycard-settings cofx))) + ;; transport module (handlers/register-handler-fx diff --git a/src/status_im/hardwallet/card.cljs b/src/status_im/hardwallet/card.cljs index 8ef9290476..a8b7a6ede8 100644 --- a/src/status_im/hardwallet/card.cljs +++ b/src/status_im/hardwallet/card.cljs @@ -13,14 +13,15 @@ :error (.-message object)}) (defn check-nfc-support [] - (when config/hardwallet-enabled? + (when (and config/hardwallet-enabled? + platform/android?) (.. keycard nfcIsSupported (then #(re-frame/dispatch [:hardwallet.callback/check-nfc-support-success %]))))) (defn check-nfc-enabled [] - (when (and platform/android? - config/hardwallet-enabled?) + (when (and config/hardwallet-enabled? + platform/android?) (.. keycard nfcIsEnabled (then #(re-frame/dispatch [:hardwallet.callback/check-nfc-enabled-success %]))))) @@ -49,19 +50,12 @@ "keyCardOnDisconnected" #(re-frame/dispatch [:hardwallet.callback/on-card-disconnected %]))}]))) -(defn get-application-info [] +(defn get-application-info [pairing] (.. keycard - getApplicationInfo + (getApplicationInfo (str pairing)) (then #(re-frame/dispatch [:hardwallet.callback/on-get-application-info-success %])) (catch #(re-frame/dispatch [:hardwallet.callback/on-get-application-info-error (error-object->map %)])))) -(defn start [] - (when config/hardwallet-enabled? - (.. keycard - start - (then #(log/debug "[hardwallet] module started")) - (catch #(log/debug "[hardwallet] module not started " %))))) - (defn install-applet-and-init-card [] (when config/hardwallet-enabled? (.. keycard @@ -76,6 +70,7 @@ (pair password) (then #(re-frame/dispatch [:hardwallet.callback/on-pairing-success %])) (catch #(re-frame/dispatch [:hardwallet.callback/on-pairing-error (error-object->map %)]))))) + (defn generate-mnemonic [{:keys [pairing]}] (when pairing @@ -91,3 +86,50 @@ (generateAndLoadKey mnemonic pairing pin) (then #(re-frame/dispatch [:hardwallet.callback/on-generate-and-load-key-success %])) (catch #(re-frame/dispatch [:hardwallet.callback/on-generate-and-load-key-error (error-object->map %)]))))) + +(defn unblock-pin + [{:keys [puk new-pin pairing]}] + (when (and pairing new-pin puk) + (.. keycard + (unblockPin pairing puk new-pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-unblock-pin-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-unblock-pin-error (error-object->map %)]))))) + +(defn verify-pin + [{:keys [pin pairing]}] + (when (and pairing pin) + (.. keycard + (verifyPin pairing pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-verify-pin-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-verify-pin-error (error-object->map %)]))))) + +(defn change-pin + [{:keys [current-pin new-pin pairing]}] + (when (and pairing current-pin new-pin) + (.. keycard + (changePin pairing current-pin new-pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-change-pin-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-change-pin-error (error-object->map %)]))))) + +(defn unpair + [{:keys [pin pairing]}] + (when (and pairing pin) + (.. keycard + (unpair pairing pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-unpair-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-unpair-error (error-object->map %)]))))) + +(defn delete + [] + (.. keycard + (delete) + (then #(re-frame/dispatch [:hardwallet.callback/on-delete-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-delete-error (error-object->map %)])))) + +(defn unpair-and-delete + [{:keys [pin pairing]}] + (when (and pairing pin) + (.. keycard + (unpairAndDelete pairing pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-delete-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-delete-error (error-object->map %)]))))) diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index fc33b48ddf..ed89319188 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -8,31 +8,97 @@ [taoensso.timbre :as log] [status-im.i18n :as i18n] [status-im.accounts.create.core :as accounts.create] - [status-im.accounts.login.core :as accounts.login] - [status-im.node.core :as node])) + [status-im.node.core :as node] + [status-im.utils.datetime :as utils.datetime] + [status-im.data-store.accounts :as accounts-store] + [clojure.string :as string])) + +(def default-pin "000000") + +(defn get-pairing [db] + (or + (get-in db [:hardwallet :secrets :pairing]) + (get-in db [:account/account :pairing]))) + +(fx/defn remove-pairing-from-account + [{:keys [db]}] + (let [account (-> db + (get :account/account) + (assoc :keycard-pairing nil) + (assoc :keycard-paired-on nil))] + ;TODO remove remove-pairing when keycard login will be ready + {:hardwallet/remove-pairing nil + :data-store/base-tx [(accounts-store/save-account-tx account)]})) (defn hardwallet-supported? [{:keys [db]}] (and config/hardwallet-enabled? platform/android? (get-in db [:hardwallet :nfc-supported?]))) +(fx/defn unauthorized-operation + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-connected] nil) + :utils/show-popup {:title "" + :content (i18n/label :t/keycard-unauthorized-operation)}} + (navigation/navigate-to-cofx :keycard-settings nil))) + +(fx/defn navigate-to-keycard-settings + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :on-verified] nil) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :setup-step] nil))} + (navigation/navigate-to-cofx :keycard-settings nil))) + +(fx/defn navigate-to-enter-pin-screen + [{:keys [db] :as cofx}] + (let [keycard-instance-uid (get-in db [:hardwallet :application-info :instance-uid]) + account-instance-uid (get-in db [:account/account :keycard-instance-uid])] + (if (or (nil? account-instance-uid) + (and keycard-instance-uid + (= keycard-instance-uid account-instance-uid))) + (fx/merge cofx + {:db (assoc-in db [:hardwallet :pin :current] [])} + (navigation/navigate-to-cofx :enter-pin nil)) + (unauthorized-operation cofx)))) + (fx/defn navigate-to-authentication-method [cofx] (if (hardwallet-supported? cofx) (navigation/navigate-to-cofx cofx :hardwallet-authentication-method nil) (accounts.create/navigate-to-create-account-screen cofx))) +(defn settings-screen-did-load + [{:keys [db]}] + {:db (-> db + (assoc-in [:hardwallet :pin :on-verified] nil) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :setup-step] nil))}) + +(defn reset-card-screen-did-load + [{:keys [db]}] + {:db (assoc-in db [:hardwallet :reset-card :disabled?] false)}) + (fx/defn on-register-card-events [{:keys [db]} listeners] {:db (update-in db [:hardwallet :listeners] merge listeners)}) (fx/defn on-get-application-info-success - [{:keys [db]} info] - (let [info' (js->clj info :keywordize-keys true)] - {:db (-> db - (assoc-in [:hardwallet :application-info] info') - (assoc-in [:hardwallet :application-info :applet-installed?] true) - (assoc-in [:hardwallet :application-info-error] nil))})) + [{:keys [db] :as cofx} info] + (let [info' (js->clj info :keywordize-keys true) + {:keys [pin-retry-counter puk-retry-counter]} info' + enter-step (get-in db [:hardwallet :pin :enter-step]) + enter-step' (if (zero? pin-retry-counter) :puk enter-step)] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :enter-step] enter-step') + (assoc-in [:hardwallet :application-info] info') + (assoc-in [:hardwallet :application-info :applet-installed?] true) + (assoc-in [:hardwallet :application-info-error] nil))} + (when (zero? puk-retry-counter) + (navigation/navigate-to-cofx :keycard-settings nil))))) (fx/defn on-get-application-info-error [{:keys [db]} error] @@ -49,17 +115,172 @@ [{:keys [db]} enabled?] {:db (assoc-in db [:hardwallet :nfc-enabled?] enabled?)}) -(fx/defn navigate-to-connect-screen [{:keys [db] :as cofx}] +(fx/defn status-hardwallet-option-pressed [{:keys [db] :as cofx}] (fx/merge cofx {:hardwallet/check-nfc-enabled nil :hardwallet/register-card-events nil - :db (assoc-in db [:hardwallet :setup-step] :begin)} + :db (-> db + (assoc-in [:hardwallet :setup-step] :begin) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin :on-verified] nil))} (navigation/navigate-to-cofx :hardwallet-connect nil))) (fx/defn success-button-pressed [cofx] ;; login not implemented yet ) +(fx/defn change-pin-pressed + [{:keys [db] :as cofx}] + (let [card-connected? (get-in db [:hardwallet :card-connected?]) + pin-retry-counter (get-in db [:hardwallet :application-info :pin-retry-counter]) + enter-step (if (zero? pin-retry-counter) :puk :current)] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] :hardwallet/navigate-to-enter-pin-screen) + (assoc-in [:hardwallet :pin] {:enter-step enter-step + :current [] + :puk [] + :original [] + :confirmation [] + :status nil + :error-label nil + :on-verified :hardwallet/proceed-to-change-pin}))} + (if card-connected? + (navigate-to-enter-pin-screen) + (navigation/navigate-to-cofx :hardwallet-connect nil))))) + +(fx/defn proceed-to-change-pin + [{:keys [db]}] + {:db (-> db + (assoc-in [:hardwallet :pin :enter-step] :original) + (assoc-in [:hardwallet :pin :status] nil))}) + +(fx/defn unpair-card-pressed + [_] + {:ui/show-confirmation {:title (i18n/label :t/unpair-card) + :content (i18n/label :t/unpair-card-confirmation) + :confirm-button-text (i18n/label :t/yes) + :cancel-button-text (i18n/label :t/no) + :on-accept #(re-frame/dispatch [:keycard-settings.ui/unpair-card-confirmed]) + :on-cancel #()}}) + +(fx/defn unpair-card-confirmed + [{:keys [db] :as cofx}] + (let [card-connected? (get-in db [:hardwallet :card-connected?]) + pin-retry-counter (get-in db [:hardwallet :application-info :pin-retry-counter]) + enter-step (if (zero? pin-retry-counter) :puk :current)] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] :hardwallet/navigate-to-enter-pin-screen) + (assoc-in [:hardwallet :pin] {:enter-step enter-step + :current [] + :puk [] + :status nil + :error-label nil + :on-verified :hardwallet/unpair}))} + (if card-connected? + (navigate-to-enter-pin-screen) + (navigation/navigate-to-cofx :hardwallet-connect nil))))) + +(defn- vector->string [v] + "Converts numbers stored in vector into string, + e.g. [1 2 3 4 5 6] -> \"123456\"" + (apply str v)) + +(fx/defn unpair + [{:keys [db]}] + (let [pin (vector->string (get-in db [:hardwallet :pin :current])) + pairing (get-pairing db)] + {:hardwallet/unpair {:pin pin + :pairing pairing}})) + +(fx/defn unpair-and-delete + [{:keys [db]}] + (let [pin (vector->string (get-in db [:hardwallet :pin :current])) + pairing (get-pairing db)] + {:hardwallet/unpair-and-delete {:pin pin + :pairing pairing}})) + +(fx/defn on-delete-success + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :secrets] nil) + (assoc-in [:hardwallet :application-info] nil) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin] {:status nil + :error-label nil + :on-verified nil})) + :utils/show-popup {:title "" + :content (i18n/label :t/card-reseted)}} + (remove-pairing-from-account) + (navigation/navigate-to-cofx :keycard-settings nil))) + +(fx/defn on-delete-error + [{:keys [db] :as cofx} error] + (log/debug "[hardwallet] delete error" error) + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin] {:status nil + :error-label nil + :on-verified nil})) + :hardwallet/get-application-info nil + :utils/show-popup {:title "" + :content (i18n/label :t/something-went-wrong)}} + (navigation/navigate-to-cofx :keycard-settings nil))) + +(fx/defn reset-card-pressed + [{:keys [db] :as cofx}] + (let [card-connected? (get-in db [:hardwallet :card-connected?])] + (if card-connected? + (navigation/navigate-to-cofx cofx :reset-card nil) + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/navigate-to-reset-card-screen)} + (navigation/navigate-to-cofx :hardwallet-connect nil))))) + +(fx/defn delete-card + [{:keys [db] :as cofx}] + (let [keycard-instance-uid (get-in db [:hardwallet :application-info :instance-uid]) + account-instance-uid (get-in db [:account/account :keycard-instance-uid])] + (if (or (nil? account-instance-uid) + (and keycard-instance-uid + (= keycard-instance-uid account-instance-uid))) + {:hardwallet/delete nil} + (unauthorized-operation cofx)))) + +(fx/defn navigate-to-reset-card-screen + [cofx] + (navigation/navigate-to-cofx cofx :reset-card nil)) + +(fx/defn reset-card-next-button-pressed + [{:keys [db]}] + {:db (assoc-in db [:hardwallet :reset-card :disabled?] true) + :dispatch [:hardwallet/proceed-to-reset-card]}) + +(fx/defn proceed-to-reset-card + [{:keys [db] :as cofx}] + (let [card-connected? (get-in db [:hardwallet :card-connected?]) + puk-retry-counter (get-in db [:hardwallet :application-info :puk-retry-counter]) + pin-retry-counter (get-in db [:hardwallet :application-info :pin-retry-counter]) + pairing (get-pairing db) + enter-step (if (zero? pin-retry-counter) :puk :current)] + (if (or (zero? puk-retry-counter) + (empty? pairing)) + (delete-card cofx) + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] :hardwallet/navigate-to-enter-pin-screen) + (assoc-in [:hardwallet :pin] {:enter-step enter-step + :current [] + :puk [] + :status nil + :error-label nil + :on-verified :hardwallet/unpair-and-delete}))} + (if card-connected? + (navigate-to-enter-pin-screen) + (navigation/navigate-to-cofx :hardwallet-connect nil)))))) + (fx/defn error-button-pressed [{:keys [db] :as cofx}] (let [return-to-step (get-in db [:hardwallet :return-to-step] :begin)] (fx/merge cofx @@ -82,35 +303,203 @@ (defn- proceed-to-pin-confirmation [fx] (assoc-in fx [:db :hardwallet :pin :enter-step] :confirmation)) -(defn- pin-match [fx] - (assoc-in fx [:db :hardwallet :pin :status] :validating)) +(fx/defn pin-match + [{:keys [db] :as fx}] + (let [pairing (get-pairing db) + new-pin (vector->string (get-in db [:hardwallet :pin :original])) + current-pin (vector->string (get-in db [:hardwallet :pin :current]))] + (fx/merge fx + {:db (assoc-in db [:hardwallet :pin :status] :verifying) + :hardwallet/change-pin {:new-pin new-pin + :current-pin current-pin + :pairing pairing}}))) -(defn- pin-mismatch [fx] - (assoc-in fx [:db :hardwallet :pin] {:status :error - :error :t/pin-mismatch - :original [] - :confirmation [] - :enter-step :original})) +(fx/defn dispatch-on-verified-event + [{:keys [db]} event] + {:dispatch [event] + :db (assoc-in db [:hardwallet :pin :on-verified] nil)}) +(fx/defn on-unblock-pin-success + [{:keys [db] :as cofx}] + (let [pairing (get-pairing db)] + (fx/merge cofx + {:hardwallet/get-application-info pairing + :db (-> db + (update-in [:hardwallet :pin] merge {:status nil + :enter-step :original + :current (vec (string/split default-pin #"")) + :puk [] + :error-label nil}))} + (navigation/navigate-to-cofx :enter-pin nil)))) + +(defn on-unblock-pin-error + [{:keys [db]} error] + (let [pairing (get-pairing db)] + (log/debug "[hardwallet] unblock pin error" error) + {:hardwallet/get-application-info pairing + :db (update-in db [:hardwallet :pin] merge {:status :error + :error-label :t/puk-mismatch + :enter-step :puk + :puk []})})) +(fx/defn get-application-info [cofx pairing] + {:hardwallet/get-application-info pairing}) + +(fx/defn on-verify-pin-success + [{:keys [db] :as cofx}] + (let [on-verified (get-in db [:hardwallet :pin :on-verified]) + pairing (get-pairing db)] + (fx/merge cofx + {:db (-> db + (update-in [:hardwallet :pin] merge {:status nil + :error-label nil}))} + (when (not= on-verified :hardwallet/unpair) + (get-application-info pairing)) + (when on-verified + (dispatch-on-verified-event on-verified))))) + +(defn on-verify-pin-error + [{:keys [db]} error] + (let [pairing (get-pairing db)] + (log/debug "[hardwallet] verify pin error" error) + {:hardwallet/get-application-info pairing + :db (update-in db [:hardwallet :pin] merge {:status :error + :error-label :t/pin-mismatch + :enter-step :current + :puk [] + :current [] + :original [] + :confirmation []})})) + +(fx/defn on-change-pin-success + [{:keys [db] :as cofx}] + (let [pin (vector->string (get-in db [:hardwallet :pin :original]))] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin] {:status nil + :error-label nil})) + :utils/show-popup {:title "" + :content (i18n/label :t/pin-changed {:pin pin})}} + (navigation/navigate-to-cofx :keycard-settings nil)))) + +(fx/defn on-change-pin-error + [{:keys [db]} error] + (log/debug "[hardwallet] change pin error" error) + {:db (update-in db [:hardwallet :pin] merge {:status :error + :error-label :t/pin-mismatch + :enter-step :original + :puk [] + :confirmation [] + :original []})}) + +(fx/defn on-unpair-success + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :secrets] nil) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin] {:status nil + :error-label nil + :on-verified nil})) + :utils/show-popup {:title "" + :content (i18n/label :t/card-unpaired)}} + (remove-pairing-from-account) + (navigation/navigate-to-cofx :keycard-settings nil))) + +(fx/defn on-unpair-error + [{:keys [db] :as cofx} error] + (log/debug "[hardwallet] unpair error" error) + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :pin] {:status nil + :error-label nil + :on-verified nil})) + :hardwallet/get-application-info nil + :utils/show-popup {:title "" + :content (i18n/label :t/something-went-wrong)}} + (navigation/navigate-to-cofx :keycard-settings nil))) + +(defn- verify-pin + [{:keys [db] :as fx}] + (let [pin (vector->string (get-in fx [:db :hardwallet :pin :current])) + pairing (get-pairing db)] + {:db (assoc-in db [:hardwallet :pin :status] :verifying) + :hardwallet/verify-pin {:pin pin + :pairing pairing}})) + +(defn- unblock-pin + [{:keys [db] :as fx}] + (let [puk (vector->string (get-in fx [:db :hardwallet :pin :puk])) + pairing (get-pairing db)] + {:db (assoc-in db [:hardwallet :pin :status] :verifying) + :hardwallet/unblock-pin {:puk puk + :new-pin default-pin + :pairing pairing}})) + +(def pin-code-length 6) +(def puk-code-length 12) + +(fx/defn handle-pin-input + [{:keys [db]} enter-step] + (let [numbers-entered (count (get-in db [:hardwallet :pin enter-step]))] + (when (or (= numbers-entered pin-code-length) + (= numbers-entered puk-code-length)) + {:dispatch [:hardwallet/process-pin-input]}))) + +(fx/defn update-pin + [{:keys [db] :as cofx} number enter-step] + (fx/merge cofx + {:db (-> db + (update-in [:hardwallet :pin enter-step] (fnil conj []) number) + (assoc-in [:hardwallet :pin :status] nil))} + (handle-pin-input enter-step))) + +(defn- pin-enter-error [fx error-label] + (update-in fx [:db :hardwallet :pin] merge {:status :error + :error-label error-label + :enter-step :original + :original [] + :confirmation []})) + +; PIN enter steps: +; current - current PIN to perform actions which require PIN auth +; original - new PIN when user changes it or creates new one +; confirmation - confirmation for new PIN (fx/defn process-pin-input - [{:keys [db]} number enter-step] - (let [db' (update-in db [:hardwallet :pin enter-step] conj number) - numbers-entered (count (get-in db' [:hardwallet :pin enter-step]))] - (cond-> {:db (assoc-in db' [:hardwallet :pin :status] nil)} + [{:keys [db]}] + (let [enter-step (get-in db [:hardwallet :pin :enter-step]) + pin (get-in db [:hardwallet :pin enter-step]) + numbers-entered (count pin)] + (cond-> {:db (assoc-in db [:hardwallet :pin :status] nil)} + (and (= enter-step :original) - (= 6 numbers-entered)) + (= pin-code-length numbers-entered)) (proceed-to-pin-confirmation) + (and (= enter-step :original) + (= pin-code-length numbers-entered) + (= default-pin (vector->string pin))) + (pin-enter-error :t/cannot-use-default-pin) + + (and (= enter-step :current) + (= pin-code-length numbers-entered)) + (verify-pin) + + (and (= enter-step :puk) + (= puk-code-length numbers-entered)) + (unblock-pin) + (and (= enter-step :confirmation) - (= (get-in db' [:hardwallet :pin :original]) - (get-in db' [:hardwallet :pin :confirmation]))) + (= (get-in db [:hardwallet :pin :original]) + (get-in db [:hardwallet :pin :confirmation]))) (pin-match) (and (= enter-step :confirmation) - (= 6 numbers-entered) - (not= (get-in db' [:hardwallet :pin :original]) - (get-in db' [:hardwallet :pin :confirmation]))) - (pin-mismatch)))) + (= pin-code-length numbers-entered) + (not= (get-in db [:hardwallet :pin :original]) + (get-in db [:hardwallet :pin :confirmation]))) + (pin-enter-error :t/pin-mismatch)))) (fx/defn load-loading-keys-screen [{:keys [db]}] @@ -127,27 +516,38 @@ (let [{:keys [pairing]} (get-in cofx [:db :hardwallet :secrets])] {:hardwallet/generate-mnemonic {:pairing pairing}})) +(fx/defn dispatch-on-card-connected-event + [{:keys [db]} event] + {;:db (assoc-in db [:hardwallet :on-card-connected] nil) + :dispatch [event]}) + (fx/defn on-card-connected [{:keys [db] :as cofx} data] (log/debug "[hardwallet] card connected " data) (let [return-to-step (get-in db [:hardwallet :return-to-step]) - setup-running? (get-in db [:hardwallet :setup-step])] + setup-running? (get-in db [:hardwallet :setup-step]) + on-card-connected (get-in db [:hardwallet :on-card-connected]) + pairing (get-pairing db)] (fx/merge cofx {:db (cond-> db return-to-step (assoc-in [:hardwallet :setup-step] return-to-step) true (assoc-in [:hardwallet :card-connected?] true) true (assoc-in [:hardwallet :return-to-step] nil)) - :hardwallet/get-application-info nil} + :hardwallet/get-application-info pairing} + (when on-card-connected + (dispatch-on-card-connected-event on-card-connected)) (when setup-running? (navigation/navigate-to-cofx :hardwallet-setup nil))))) (fx/defn on-card-disconnected [{:keys [db] :as cofx} _] (log/debug "[hardwallet] card disconnected ") - (let [setup-running? (get-in db [:hardwallet :setup-step])] + (let [setup-running? (get-in db [:hardwallet :setup-step]) + on-card-connected (get-in db [:hardwallet :on-card-connected])] (fx/merge cofx {:db (assoc-in db [:hardwallet :card-connected?] false)} - (when setup-running? + (when (or setup-running? + on-card-connected) (navigation/navigate-to-cofx :hardwallet-connect nil))))) (fx/defn load-preparing-screen @@ -185,9 +585,12 @@ (fx/defn on-pairing-success [{:keys [db]} pairing] - {:db (-> db - (assoc-in [:hardwallet :setup-step] :card-ready) - (assoc-in [:hardwallet :secrets :pairing] pairing))}) + ;TODO remove persistence to async storage when keycard login will be ready + {:hardwallet/persist-pairing pairing + :db (-> db + (assoc-in [:hardwallet :setup-step] :card-ready) + (assoc-in [:hardwallet :secrets :pairing] pairing) + (assoc-in [:hardwallet :secrets :paired-on] (utils.datetime/timestamp)))}) (fx/defn on-pairing-error [{:keys [db] :as cofx} {:keys [error code]}] @@ -265,7 +668,9 @@ (let [{{:keys [whisper-public-key wallet-address encryption-public-key - keycard-instance-uid]} :hardwallet} db] + keycard-instance-uid + secrets]} :hardwallet} db + {:keys [pairing paired-on]} secrets] (fx/merge (-> cofx (accounts.create/get-signing-phrase) (accounts.create/get-status)) @@ -273,7 +678,9 @@ (accounts.create/on-account-created {:pubkey whisper-public-key :address wallet-address :mnemonic "" - :keycard-instance-uid keycard-instance-uid} + :keycard-instance-uid keycard-instance-uid + :keycard-pairing pairing + :keycard-paired-on paired-on} encryption-public-key {:seed-backed-up? true :login? false}) @@ -297,7 +704,8 @@ (assoc-in [:hardwallet :encryption-public-key] encryption-public-key) (assoc-in [:hardwallet :keycard-instance-uid] keycard-instance-uid) (assoc :node/on-ready :create-keycard-account) - (assoc :accounts/new-installation-id (random-guid-generator)))} + (assoc :accounts/new-installation-id (random-guid-generator)) + (update-in [:hardwallet :secrets] dissoc :mnemonic))} (node/initialize nil)))) (fx/defn on-generate-and-load-key-error diff --git a/src/status_im/hardwallet/fx.cljs b/src/status_im/hardwallet/fx.cljs index 4b4ee6840c..e0594280f3 100644 --- a/src/status_im/hardwallet/fx.cljs +++ b/src/status_im/hardwallet/fx.cljs @@ -1,7 +1,8 @@ (ns status-im.hardwallet.fx (:require [re-frame.core :as re-frame] [status-im.hardwallet.card :as card] - [status-im.react-native.js-dependencies :as js-dependencies])) + [status-im.react-native.js-dependencies :as js-dependencies] + [status-im.utils.datetime :as utils.datetime])) (re-frame/reg-fx :hardwallet/get-application-info @@ -19,10 +20,6 @@ :hardwallet/open-nfc-settings card/open-nfc-settings) -(re-frame/reg-fx - :hardwallet/start-module - card/start) - (re-frame/reg-fx :hardwallet/install-applet-and-init-card card/install-applet-and-init-card) @@ -46,3 +43,58 @@ (re-frame/reg-fx :hardwallet/generate-and-load-key card/generate-and-load-key) + +(re-frame/reg-fx + :hardwallet/unblock-pin + card/unblock-pin) + +(re-frame/reg-fx + :hardwallet/verify-pin + card/verify-pin) + +(re-frame/reg-fx + :hardwallet/change-pin + card/change-pin) + +(re-frame/reg-fx + :hardwallet/unpair + card/unpair) + +(re-frame/reg-fx + :hardwallet/delete + card/delete) + +(re-frame/reg-fx + :hardwallet/unpair-and-delete + card/unpair-and-delete) + +;TODO remove when keycard login will be ready +(re-frame/reg-fx + :hardwallet/persist-pairing + (fn [pairing] + (.. js-dependencies/react-native + -AsyncStorage + (setItem "status-keycard-pairing" + (js/JSON.stringify + #js {"pairing" pairing + "paired-on" (utils.datetime/timestamp)}))))) + +;TODO remove when keycard login will be ready +(re-frame/reg-fx + :hardwallet/retrieve-pairing + (fn [] + (.. js-dependencies/react-native + -AsyncStorage + (getItem "status-keycard-pairing") + (then #(re-frame/dispatch [:hardwallet.callback/on-retrieve-pairing-success + (-> % + (js/JSON.parse) + (js->clj :keywordize-keys true))]))))) + +;TODO remove when keycard login will be ready +(re-frame/reg-fx + :hardwallet/remove-pairing + (fn [] + (.. js-dependencies/react-native + -AsyncStorage + (removeItem "status-keycard-pairing")))) diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index dd0b9b4dfd..1fde99c652 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -56,7 +56,7 @@ (then #(re-frame/dispatch [:init.callback/keychain-reset])))) -(defn reset-data! [] +(defn reset-data! [] (.. (realm/delete-realms) (then reset-keychain!) (catch reset-keychain!))) @@ -80,39 +80,40 @@ :notifications/init nil :network/listen-to-network-status nil :network/listen-to-connection-status nil - :hardwallet/check-nfc-support nil - :hardwallet/check-nfc-enabled nil - :hardwallet/start-module nil :hardwallet/register-card-events nil} (initialize-keychain))) (fx/defn initialize-app-db "Initialize db to initial state" - [{{:keys [status-module-initialized? view-id hardwallet - initial-props desktop/desktop - network-status network peers-count peers-summary device-UUID - push-notifications/stored] + [{{:keys [status-module-initialized? view-id hardwallet + initial-props desktop/desktop + network-status network peers-count peers-summary device-UUID + push-notifications/stored] :node/keys [status] :or {network (get app-db :network)}} :db}] - {:db (assoc app-db - :contacts/contacts {} - :initial-props initial-props - :desktop/desktop (merge desktop (:desktop/desktop app-db)) - :network-status network-status - :peers-count (or peers-count 0) - :peers-summary (or peers-summary []) - :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) - :node/status status - :network network - :hardwallet hardwallet - :device-UUID device-UUID - :view-id view-id - :push-notifications/stored stored)}) + ;TODO remove retrieve-pairing when keycard login will be ready + {:hardwallet/retrieve-pairing nil + :db (assoc app-db + :contacts/contacts {} + :initial-props initial-props + :desktop/desktop (merge desktop (:desktop/desktop app-db)) + :network-status network-status + :peers-count (or peers-count 0) + :peers-summary (or peers-summary []) + :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) + :node/status status + :network network + :hardwallet hardwallet + :device-UUID device-UUID + :view-id view-id + :push-notifications/stored stored)}) (fx/defn initialize-app [cofx encryption-key] (fx/merge cofx - {:init/init-store encryption-key} + {:init/init-store encryption-key + :hardwallet/check-nfc-support nil + :hardwallet/check-nfc-enabled nil} (initialize-app-db))) (fx/defn set-device-uuid @@ -166,12 +167,12 @@ (fx/defn initialize-account-db [{:keys [db web3]} address] (let [{:universal-links/keys [url] - :keys [accounts/accounts accounts/create networks/networks network - network-status peers-count peers-summary view-id navigation-stack - desktop/desktop hardwallet - status-module-initialized? device-UUID semaphores accounts/login] - :node/keys [status on-ready] - :or {network (get app-db :network)}} db + :keys [accounts/accounts accounts/create networks/networks network + network-status peers-count peers-summary view-id navigation-stack + desktop/desktop hardwallet + status-module-initialized? device-UUID semaphores accounts/login] + :node/keys [status on-ready] + :or {network (get app-db :network)}} db current-account (get accounts address) account-network-id (get current-account :network network) account-network (get-in current-account [:networks account-network-id])] diff --git a/src/status_im/react_native/resources.cljs b/src/status_im/react_native/resources.cljs index 081adf9119..503cf8d0e2 100644 --- a/src/status_im/react_native/resources.cljs +++ b/src/status_im/react_native/resources.cljs @@ -69,5 +69,6 @@ :secret-keys (js/require "./resources/images/ui/secret-keys.png") :keycard-lock (js/require "./resources/images/ui/keycard-lock.png") :hold-card-animation (js/require "./resources/images/ui/hold-card-animation.png") + :warning-sign (js/require "./resources/images/ui/warning-sign.png") :phone-nfc-on (js/require "./resources/images/ui/phone-nfc-on.png") :phone-nfc-off (js/require "./resources/images/ui/phone-nfc-off.png")}) diff --git a/src/status_im/ui/components/colors.cljs b/src/status_im/ui/components/colors.cljs index 166db3dc75..2f6b929fbe 100644 --- a/src/status_im/ui/components/colors.cljs +++ b/src/status_im/ui/components/colors.cljs @@ -37,6 +37,7 @@ ;; RED (def red "#ff2d55") ;; Used to highlight errors or "dangerous" actions +(def red-transparent-10 (alpha red 0.1)) ;; GREEN (def green "#44d058") ;; icon for successful inboud transaction diff --git a/src/status_im/ui/components/icons/vector_icons.cljs b/src/status_im/ui/components/icons/vector_icons.cljs index 8f09730a68..c1c98c2722 100644 --- a/src/status_im/ui/components/icons/vector_icons.cljs +++ b/src/status_im/ui/components/icons/vector_icons.cljs @@ -91,6 +91,7 @@ :icons/refresh (js/require "./resources/icons/refresh.svg") :icons/newchat (js/require "./resources/icons/newchat.svg") :icons/logo (js/require "./resources/icons/logo.svg") + :icons/logout (js/require "./resources/icons/logout.svg") :icons/camera (js/require "./resources/icons/camera.svg") :icons/check (js/require "./resources/icons/check.svg") :icons/dots (js/require "./resources/icons/dots.svg") @@ -169,6 +170,7 @@ :icons/refresh (components.svg/slurp-svg "./resources/icons/refresh.svg") :icons/newchat (components.svg/slurp-svg "./resources/icons/newchat.svg") :icons/logo (components.svg/slurp-svg "./resources/icons/logo.svg") + :icons/logout (components.svg/slurp-svg "./resources/icons/logout.svg") :icons/camera (components.svg/slurp-svg "./resources/icons/camera.svg") :icons/check (components.svg/slurp-svg "./resources/icons/check.svg") :icons/tiny-check (components.svg/slurp-svg "./resources/icons/tiny_check.svg") diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index a722efd3f6..06098675fa 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -67,6 +67,8 @@ :nfc-enabled? false :pin {:original [] :confirmation [] + :current [] + :puk [] :enter-step :original}} :chats/loading? true}) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index fa82606015..672908fb0d 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -170,4 +170,14 @@ (handlers/register-handler-fx :update-window-dimensions (fn [{:keys [db]} [_ dimensions]] - {:db (assoc db :dimensions/window (dimensions/window dimensions))})) \ No newline at end of file + {:db (assoc db :dimensions/window (dimensions/window dimensions))})) + +(handlers/register-handler-fx + :screens/on-will-focus + (fn [{:keys [db] :as cofx} [_ view-id]] + (fx/merge cofx + {:db (assoc db :view-id view-id)} + #(case view-id + :keycard-settings (hardwallet/settings-screen-did-load %) + :reset-card (hardwallet/reset-card-screen-did-load %) + nil)))) diff --git a/src/status_im/ui/screens/hardwallet/pin/styles.cljs b/src/status_im/ui/screens/hardwallet/pin/styles.cljs index f2d12faa94..c38ffb5418 100644 --- a/src/status_im/ui/screens/hardwallet/pin/styles.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/styles.cljs @@ -6,6 +6,7 @@ {:flex 1 :flex-direction :column :justify-content :space-between + :padding-bottom 10 :ios {:margin-top 30}}) (defstyle error-container diff --git a/src/status_im/ui/screens/hardwallet/pin/subs.cljs b/src/status_im/ui/screens/hardwallet/pin/subs.cljs index 7098e27492..fe0f622ee0 100644 --- a/src/status_im/ui/screens/hardwallet/pin/subs.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/subs.cljs @@ -2,7 +2,7 @@ (:require [re-frame.core :as re-frame])) (re-frame/reg-sub - :hardwallet/pin + :hardwallet/original-pin (fn [db] (get-in db [:hardwallet :pin :original]))) @@ -16,12 +16,29 @@ (fn [db] (get-in db [:hardwallet :pin :enter-step] :original))) +(re-frame/reg-sub + :hardwallet/pin-operation + (fn [db] + (get-in db [:hardwallet :pin :operation]))) + +(re-frame/reg-sub + :hardwallet/pin-data + (fn [db] + (get-in db [:hardwallet :pin]))) + +(re-frame/reg-sub + :hardwallet/pin + :<- [:hardwallet/pin-data] + :<- [:hardwallet/pin-enter-step] + (fn [[pin-data step]] + (get pin-data step))) + (re-frame/reg-sub :hardwallet/pin-status (fn [db] (get-in db [:hardwallet :pin :status]))) (re-frame/reg-sub - :hardwallet/pin-error + :hardwallet/pin-error-label (fn [db] - (get-in db [:hardwallet :pin :error]))) + (get-in db [:hardwallet :pin :error-label]))) diff --git a/src/status_im/ui/screens/hardwallet/pin/views.cljs b/src/status_im/ui/screens/hardwallet/pin/views.cljs index bf1f7be991..e4b6e2ed49 100644 --- a/src/status_im/ui/screens/hardwallet/pin/views.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/views.cljs @@ -40,8 +40,8 @@ (defn pin-indicator [pressed?] [react/view (styles/pin-indicator pressed?)]) -(defn pin-indicators [pin] - [react/view styles/pin-indicator-container +(defn pin-indicators [pin style] + [react/view (merge styles/pin-indicator-container style) (map-indexed (fn [i group] ^{:key i} @@ -56,42 +56,64 @@ (repeat (- 6 (count pin)) nil)))))]) -(defn pin-view [{:keys [pin title step status error]}] - (let [enabled? (not= status :validating)] - [react/view styles/pin-container - [react/view styles/center-container - ;[components/wizard-step 4] - [react/text {:style styles/center-title-text - :font :bold} - (i18n/label title)] - [react/text {:style styles/create-pin-text - :number-of-lines 2} - (i18n/label :t/create-pin-description)] - (case status - :validating [react/view styles/waiting-indicator-container +(defn puk-indicators [puk] + [react/view + (map-indexed + (fn [i puk-group] + ^{:key i} + [pin-indicators puk-group {:margin-top 15}]) + (partition 6 + (concat puk + (repeat (- 12 (count puk)) + nil))))]) + +(defn pin-view [{:keys [pin title-label description-label step status error-label + retry-counter]}] + (let [enabled? (not= status :verifying)] + [react/scroll-view + [react/view styles/pin-container + [react/view styles/center-container + [react/text {:style styles/center-title-text + :font :bold} + (i18n/label title-label)] + [react/text {:style styles/create-pin-text + :number-of-lines 2} + (i18n/label description-label)] + (when retry-counter + [react/text {:style {:font-weight :bold + :padding-top 10 + :font-size 15 + :color colors/red}} + (i18n/label :t/pin-retries-left {:number retry-counter})]) + (case status + :verifying [react/view styles/waiting-indicator-container [react/activity-indicator {:animating true :size :small}]] - :error [react/view styles/error-container - [react/text {:style styles/error-text - :font :medium} - (i18n/label error)]] - [pin-indicators pin]) - [numpad step enabled?]]])) + :error [react/view styles/error-container + [react/text {:style styles/error-text + :font :medium} + (i18n/label error-label)]] + (if (= step :puk) + [puk-indicators pin] + [pin-indicators pin])) + [numpad step enabled?]]]])) (defview main [] - (letsubs [original [:hardwallet/pin] + (letsubs [original [:hardwallet/original-pin] confirmation [:hardwallet/pin-confirmation] enter-step [:hardwallet/pin-enter-step] status [:hardwallet/pin-status] - error [:hardwallet/pin-error]] + error-label [:hardwallet/pin-error-label]] (case enter-step - :original [pin-view {:pin original - :title :t/create-pin - :step :original - :status status - :error error}] - :confirmation [pin-view {:pin confirmation - :title :t/repeat-pin - :step :confirmation - :status status - :error error}]))) \ No newline at end of file + :original [pin-view {:pin original + :title-label :t/create-pin + :description-label :t/create-pin-description + :step :original + :status status + :error-label error-label}] + :confirmation [pin-view {:pin confirmation + :title-label :t/repeat-pin + :description-label :t/create-pin-description + :step :confirmation + :status status + :error-label error-label}]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/hardwallet/settings/subs.cljs b/src/status_im/ui/screens/hardwallet/settings/subs.cljs new file mode 100644 index 0000000000..10afee4608 --- /dev/null +++ b/src/status_im/ui/screens/hardwallet/settings/subs.cljs @@ -0,0 +1,32 @@ +(ns status-im.ui.screens.hardwallet.settings.subs + (:require [re-frame.core :as re-frame] + [status-im.hardwallet.core :as core] + [status-im.utils.datetime :as utils.datetime])) + +(re-frame/reg-sub + :keycard-paired-on + (fn [db] + (some-> (or + (get-in db [:hardwallet :secrets :paired-on]) + (get-in db [:account/account :keycard-paired-on])) + (utils.datetime/timestamp->year-month-day-date)))) + +(re-frame/reg-sub + :keycard-pairing + (fn [db] + (core/get-pairing db))) + +(re-frame/reg-sub + :hardwallet/pin-retry-counter + (fn [db] + (get-in db [:hardwallet :application-info :pin-retry-counter]))) + +(re-frame/reg-sub + :hardwallet/puk-retry-counter + (fn [db] + (get-in db [:hardwallet :application-info :puk-retry-counter]))) + +(re-frame/reg-sub + :keycard-reset-card-disabled? + (fn [db] + (get-in db [:hardwallet :reset-card :disabled?] false))) diff --git a/src/status_im/ui/screens/hardwallet/settings/views.cljs b/src/status_im/ui/screens/hardwallet/settings/views.cljs new file mode 100644 index 0000000000..46658a5c53 --- /dev/null +++ b/src/status_im/ui/screens/hardwallet/settings/views.cljs @@ -0,0 +1,178 @@ +(ns status-im.ui.screens.hardwallet.settings.views + (:require-macros [status-im.utils.views :refer [defview letsubs]]) + (:require [re-frame.core :as re-frame] + [status-im.i18n :as i18n] + [status-im.ui.components.react :as react] + [status-im.ui.components.status-bar.view :as status-bar] + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.react-native.resources :as resources] + [status-im.ui.components.icons.vector-icons :as vector-icons] + [status-im.ui.components.colors :as colors] + [status-im.ui.screens.hardwallet.components :as components] + [status-im.ui.screens.hardwallet.pin.views :as pin.views] + [status-im.ui.components.common.common :as components.common] + [reagent.core :as reagent])) + +(def pin-retries 3) +(def puk-retries 5) + +(defview enter-pin [] + (letsubs [pin [:hardwallet/pin] + step [:hardwallet/pin-enter-step] + status [:hardwallet/pin-status] + pin-retry-counter [:hardwallet/pin-retry-counter] + puk-retry-counter [:hardwallet/puk-retry-counter] + error-label [:hardwallet/pin-error-label]] + [react/keyboard-avoiding-view {:flex 1} + [react/view {:flex 1 + :background-color colors/white} + [react/view {:flex-direction :column + :flex 1 + :align-items :center + :justify-content :space-between} + [components/maintain-card nil] + (if (zero? pin-retry-counter) + [pin.views/pin-view {:pin pin + :retry-counter (when (< puk-retry-counter puk-retries) puk-retry-counter) + :title-label :t/enter-puk-code + :description-label :t/enter-puk-code-description + :step step + :status status + :error-label error-label}] + [pin.views/pin-view {:pin pin + :retry-counter (when (< pin-retry-counter pin-retries) pin-retry-counter) + :title-label (case step + :current :t/current-pin + :original :t/create-pin + :confirmation :t/repeat-pin + :t/current-pin) + :description-label (case step + :current :t/current-pin-description + :t/new-pin-description) + :step step + :status status + :error-label error-label}])]]])) + +(defn- action-row [{:keys [icon label on-press color-theme]}] + [react/touchable-highlight + {:on-press on-press} + [react/view {:flex-direction :row + :margin-top 15} + [react/view {:background-color (case color-theme + :red colors/red-transparent-10 + colors/blue-light) + :width 40 + :height 40 + :border-radius 50 + :align-items :center + :justify-content :center} + [vector-icons/icon icon {:color (case color-theme + :red colors/red + colors/blue)}]] + [react/view {:align-items :center + :justify-content :center + :margin-left 16} + [react/text {:style {:font-size 17 + :color (case color-theme + :red colors/red + colors/blue)}} + (i18n/label label)]]]]) + +(defn- activity-indicator [loading?] + (when loading? + [react/view {:margin-top 35} + [react/activity-indicator {:animating true + :size :large}]])) + +(defn- reset-card-next-button [disabled?] + [react/view {:margin-right 18} + [components.common/bottom-button + {:on-press #(re-frame/dispatch [:keycard-settings.ui/reset-card-next-button-pressed]) + :disabled? disabled? + :uppercase? false + :forward? true}]]) + +(defview reset-card [] + (letsubs [disabled? [:keycard-reset-card-disabled?]] + [react/view {:flex 1} + [status-bar/status-bar] + [toolbar/simple-toolbar + (i18n/label :t/reset-card)] + [react/view {:flex 1 + :background-color :white} + [react/view {:margin-top 71 + :flex 1 + :align-items :center} + [react/image {:source (:warning-sign resources/ui) + :style {:width 160 + :height 160}}]] + [react/view {:flex 1 + :padding-horizontal 30} + [react/text {:style {:font-weight :bold + :color colors/black + :font-size 22 + :text-align :center}} + (i18n/label :t/reset-card-description)] + [activity-indicator disabled?]] + [react/view {:flex-direction :row + :justify-content :space-between + :align-items :center + :width "100%" + :height 52 + :border-top-width 1 + :border-color colors/gray-light} + [react/view {:flex 1}] + [reset-card-next-button disabled?]]]])) + +(defn- card-blocked [] + [react/view + [react/text {:style {:font-size 20 + :text-align :center + :padding-horizontal 40 + :color colors/black}} + (i18n/label :t/keycard-blocked)]]) + +(defview keycard-settings [] + (letsubs [paired-on [:keycard-paired-on] + puk-retry-counter [:hardwallet/puk-retry-counter] + pairing [:keycard-pairing]] + [react/view {:flex 1} + [status-bar/status-bar] + [toolbar/simple-toolbar + (i18n/label :t/status-keycard)] + [react/view {:flex 1 + :background-color :white} + [react/view {:margin-top 47 + :flex 1 + :align-items :center} + [react/image {:source (:hardwallet-card resources/ui) + :style {:width 255 + :height 160}}] + (when paired-on + [react/view {:margin-top 27} + [react/text + (i18n/label :t/linked-on {:date paired-on})]])] + [react/view {:margin-left 16 + :flex 1 + :width "90%" + :flex-direction :column} + (if (zero? puk-retry-counter) + [card-blocked] + [react/view + [action-row {:icon :icons/info + :label :t/help-capitalized + :on-press #(.openURL react/linking "https://hardwallet.status.im")}] + (when pairing + [react/view + [action-row {:icon :icons/add + :label :t/change-pin + :on-press #(re-frame/dispatch [:keycard-settings.ui/change-pin-pressed])}] + [action-row {:icon :icons/close + :label :t/unpair-card + :on-press #(re-frame/dispatch [:keycard-settings.ui/unpair-card-pressed])}]])])] + [react/view {:margin-bottom 20 + :margin-left 16} + [action-row {:icon :icons/logout + :color-theme :red + :label :t/reset-card + :on-press #(re-frame/dispatch [:keycard-settings.ui/reset-card-pressed])}]]]])) diff --git a/src/status_im/ui/screens/hardwallet/setup/styles.cljs b/src/status_im/ui/screens/hardwallet/setup/styles.cljs index 11a32b497b..f852d8b6c1 100644 --- a/src/status_im/ui/screens/hardwallet/setup/styles.cljs +++ b/src/status_im/ui/screens/hardwallet/setup/styles.cljs @@ -27,7 +27,8 @@ (def maintain-card-text {:font-size 12 - :padding-horizontal 30 + :flex 1 + :padding-horizontal 20 :color colors/blue}) (def setup-steps-container diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index 760fc3fc72..eb99f88043 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -111,6 +111,10 @@ :action-fn #(re-frame/dispatch [:navigate-to :currency-settings]) :accessibility-label :currency-button}] [profile.components/settings-item-separator] + (when config/hardwallet-enabled? + [profile.components/settings-item {:label-kw :t/status-keycard + :accessibility-label :keycard-button + :action-fn #(re-frame/dispatch [:profile.ui/keycard-settings-button-pressed])}]) [profile.components/settings-item {:label-kw :t/notifications :accessibility-label :notifications-button :action-fn #(.openURL react/linking "app-settings://notification/status-im")}] diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index 1009b3252b..5a01ca6443 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -15,6 +15,7 @@ status-im.ui.screens.wallet.send.subs status-im.ui.screens.wallet.settings.subs status-im.ui.screens.wallet.transactions.subs + status-im.ui.screens.hardwallet.settings.subs status-im.ui.screens.network-settings.subs status-im.ui.screens.log-level-settings.subs status-im.ui.screens.fleet-settings.subs diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 10b2f01c92..1450589958 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -52,6 +52,7 @@ [status-im.ui.screens.pairing.views :refer [installations]] [status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]] [status-im.ui.screens.currency-settings.views :refer [currency-settings]] + [status-im.ui.screens.hardwallet.settings.views :refer [keycard-settings enter-pin reset-card]] [status-im.ui.screens.help-center.views :refer [help-center]] [status-im.ui.screens.browser.views :refer [browser]] [status-im.ui.screens.add-new.open-dapp.views :refer [open-dapp dapp-description]] @@ -81,7 +82,7 @@ {:on-will-focus (fn [] (log/debug :on-will-focus view-id) - (re-frame/dispatch [:set :view-id view-id]))}]]))) + (re-frame/dispatch [:screens/on-will-focus view-id]))}]]))) (defn wrap-modal [modal-view component] (fn [] @@ -334,7 +335,10 @@ (assoc :hardwallet-authentication-method hardwallet-authentication-method :hardwallet-connect hardwallet-connect :hardwallet-setup hardwallet-setup - :hardwallet-success hardwallet-success))) + :hardwallet-success hardwallet-success + :keycard-settings keycard-settings + :reset-card reset-card + :enter-pin enter-pin))) {:headerMode "none" :initialRouteName "my-profile"})} :profile-qr-viewer diff --git a/src/status_im/utils/datetime.cljs b/src/status_im/utils/datetime.cljs index da2c2191a5..578c8a7e67 100644 --- a/src/status_im/utils/datetime.cljs +++ b/src/status_im/utils/datetime.cljs @@ -145,6 +145,9 @@ (defn timestamp [] (inst-ms (js/Date.))) +(defn timestamp->year-month-day-date [ms] + (unparse (:year-month-day formatters) (to-date ms))) + (re-frame/reg-cofx :now (fn [coeffects _] diff --git a/test/cljs/status_im/test/hardwallet/core.cljs b/test/cljs/status_im/test/hardwallet/core.cljs index 5749c87228..0e8faf955e 100644 --- a/test/cljs/status_im/test/hardwallet/core.cljs +++ b/test/cljs/status_im/test/hardwallet/core.cljs @@ -8,42 +8,38 @@ :confirmation [] :status nil :enter-step :original}}}} - (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [] + (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [1] :confirmation [] - :enter-step :original}}}} - 1 - :original)))) + :enter-step :original}}}})))) (testing "first 6 numbers entered" (is (= {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] :confirmation [] :status nil :enter-step :confirmation}}}} - (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [1 2 3 4 5] - :confirmation [] - :enter-step :original}}}} - 6 - :original)))) - (testing "confirmation entered" - (is (= {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] - :confirmation [1 2 3 4 5 6] - :enter-step :confirmation - :status :validating}}}} (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] - :confirmation [1 2 3 4 5] - :enter-step :confirmation}}}} - 6 - :confirmation)))) + :confirmation [] + :enter-step :original}}}})))) + (testing "confirmation entered" + (is (= {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] + :confirmation [1 2 3 4 5 6] + :enter-step :confirmation + :status :verifying}}} + :hardwallet/change-pin {:new-pin "123456" + :current-pin "" + :pairing nil}} + (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] + :confirmation [1 2 3 4 5 6] + :enter-step :confirmation}}}})))) + (testing "confirmation doesn't match" (is (= {:db {:hardwallet {:pin {:original [] :confirmation [] :enter-step :original - :error :t/pin-mismatch + :error-label :t/pin-mismatch :status :error}}}} (hardwallet/process-pin-input {:db {:hardwallet {:pin {:original [1 2 3 4 5 6] - :confirmation [1 2 3 4 5] - :enter-step :confirmation}}}} - 7 - :confirmation))))) + :confirmation [1 2 3 4 5 7] + :enter-step :confirmation}}}}))))) (deftest on-generate-and-load-key-success (is (= (select-keys diff --git a/translations/en.json b/translations/en.json index de08a90bd0..6971b0d2c3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -775,10 +775,12 @@ "send-command-payment": "Send a payment", "request-command-payment": "Request a payment", "choose-authentication-method": "Choose an authentication method to protect your account", + "status-keycard": "Status Keycard", "keycard": "Keycard", "status-hardwallet-capitalized": "Status Hardwallet", "status-hardwallet": "Status hardwallet", "secure-your-assets": "secure your assets", + "linked-on": "Linked on {{date}}", "link-card": "Link the card and use it to confirm your identity on the platform.", "hold-card": "Hold card to the back\n of your phone", "turn-nfc-on": "Turn NFC on to continue", @@ -821,17 +823,38 @@ "taking-long-hold-phone-connected": "This will take a few seconds.\n Hold card connected to the phone.", "pin-code": "PIN code", "pin-code-description": "Unlocks the card", + "enter-puk-code": "Enter PUK code", + "enter-puk-code-description": "PIN has been blocked.\n Please enter PUK code to unblock PIN.", + "pin-unblocked": "PIN has been unblocked", + "pin-unblocked-description": "Your new PIN {{pin}}", + "current-pin": "Enter PIN", + "current-pin-description": "Enter your PIN to proceed", + "new-pin-description": "Enter new PIN code", + "change-pin": "Change PIN", "create-pin": "Create a PIN", "create-pin-description": "You'll need your card + this PIN to log in and to confirm transactions", - "repeat-pin": "Repeate your PIN", + "repeat-pin": "Repeat new PIN", + "puk-mismatch": "PUK code does not match", "pin-mismatch": "PIN does not match", + "cannot-use-default-pin": "PIN 000000 is not allowed.\nPlease use another number", + "pin-changed": "PIN has been changed to {{pin}}", + "pin-retries-left": "You have {{number}} retries left", + "keycard-blocked": "Keycard has been blocked.\nYou need to reset card to continue using it.", + "reset-card": "Reset card", + "reset-card-description": "This operation will reset card to initial state. It will erase all card data including private keys. Operation is not reversible.", + "unpair-card": "Unpair card", + "card-unpaired": "Card has been unpaired from current device", + "card-reseted": "Card has been reseted", + "unpair-card-confirmation": "This operation will unpair card from current device. Requires PIN authorization. Do you want to proceed?", "pair-card": "pair card", "pair-card-question": "Do you want to pair card to this device?", "enter-pair-code": "Enter pair code", "enter-pair-code-description": "Needed to link your card to this device", "no-pairing-slots-available": "No pairing slots are available.\n You could unpair the device\n that already paired with the card", "card-already-linked": "Card is already linked to another account", + "keycard-unauthorized-operation": "You're unauthorized to perform this operation.", "help": "help", + "help-capitalized": "Help", "pairing-card": "Pairing card", "complete-exclamation": "Complete!", "complete-hardwallet-setup": "This card is now an essential part your account security. Transactions can't be sent without it.",