From a1027cdfcca3418a11a40ef598543cb93cc11fdd Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Thu, 14 May 2020 17:35:43 +0300 Subject: [PATCH] [#10452] Keycard: updated PUK flow --- src/status_im/hardwallet/card.cljs | 3 +- src/status_im/hardwallet/change_pin.cljs | 27 +- src/status_im/hardwallet/common.cljs | 133 +++++---- src/status_im/hardwallet/core.cljs | 84 ++++-- src/status_im/hardwallet/export_key.cljs | 2 + src/status_im/hardwallet/login.cljs | 51 +++- src/status_im/hardwallet/real_keycard.cljs | 14 +- src/status_im/hardwallet/sign.cljs | 8 +- .../hardwallet/simulated_keycard.cljs | 82 ++++- src/status_im/hardwallet/wallet.cljs | 29 +- src/status_im/i18n_test.cljs | 1 - src/status_im/multiaccounts/login/core.cljs | 23 +- src/status_im/navigation.cljs | 1 + src/status_im/subs.cljs | 3 + .../ui/components/bottom_panel/views.cljs | 23 +- .../screens/hardwallet/frozen_card/view.cljs | 44 +++ .../ui/screens/hardwallet/pin/styles.cljs | 52 +++- .../ui/screens/hardwallet/pin/subs.cljs | 9 +- .../ui/screens/hardwallet/pin/views.cljs | 279 +++++++++++++----- .../ui/screens/hardwallet/settings/views.cljs | 11 +- .../ui/screens/keycard/recovery/views.cljs | 53 ++-- src/status_im/ui/screens/keycard/views.cljs | 252 ++++++++++++---- src/status_im/ui/screens/popover/views.cljs | 6 +- .../ui/screens/routing/profile_stack.cljs | 2 + src/status_im/ui/screens/signing/views.cljs | 17 +- .../ui/screens/wallet/accounts/views.cljs | 22 +- .../ui/screens/wallet/add_new/views.cljs | 34 ++- translations/en.json | 20 +- 28 files changed, 950 insertions(+), 335 deletions(-) create mode 100644 src/status_im/ui/screens/hardwallet/frozen_card/view.cljs diff --git a/src/status_im/hardwallet/card.cljs b/src/status_im/hardwallet/card.cljs index 8b87a74989..5c51fbe00d 100644 --- a/src/status_im/hardwallet/card.cljs +++ b/src/status_im/hardwallet/card.cljs @@ -348,7 +348,8 @@ response])) :on-failure (fn [response] - (log/info "[keycard response fail] get-keys") + (log/info "[keycard response fail] get-keys" + (error-object->map response)) (re-frame/dispatch [:hardwallet.callback/on-get-keys-error (error-object->map response)]))}))) diff --git a/src/status_im/hardwallet/change_pin.cljs b/src/status_im/hardwallet/change_pin.cljs index 06f1eff0d0..c38c563ca2 100644 --- a/src/status_im/hardwallet/change_pin.cljs +++ b/src/status_im/hardwallet/change_pin.cljs @@ -4,24 +4,27 @@ [status-im.hardwallet.onboarding :as onboarding] [status-im.utils.fx :as fx] [taoensso.timbre :as log] - [status-im.hardwallet.common :as common])) + [status-im.hardwallet.common :as common] + [status-im.hardwallet.login :as hardwallet.login])) (fx/defn change-pin-pressed {:events [:keycard-settings.ui/change-pin-pressed]} [{:keys [db] :as cofx}] (let [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 - (assoc-in db [:hardwallet :pin] {:enter-step enter-step - :current [] - :puk [] - :original [] - :confirmation [] - :status nil - :error-label nil - :on-verified :hardwallet/proceed-to-change-pin})} - (common/navigate-to-enter-pin-screen)))) + (if (= enter-step :puk) + (hardwallet.login/reset-pin cofx) + (fx/merge cofx + {:db + (assoc-in db [:hardwallet :pin] {:enter-step enter-step + :current [] + :puk [] + :original [] + :confirmation [] + :status nil + :error-label nil + :on-verified :hardwallet/proceed-to-change-pin})} + (common/navigate-to-enter-pin-screen))))) (fx/defn proceed-to-change-pin {:events [:hardwallet/proceed-to-change-pin]} diff --git a/src/status_im/hardwallet/common.cljs b/src/status_im/hardwallet/common.cljs index 1efff71212..9e674d659a 100644 --- a/src/status_im/hardwallet/common.cljs +++ b/src/status_im/hardwallet/common.cljs @@ -204,27 +204,30 @@ (fx/defn clear-pin [{:keys [db] :as cofx}] - (fx/merge cofx - {:db (update-in db - [:hardwallet :pin] - merge - {:status nil - :login (get-in db [:hardwallet :pin :original]) - :export-key [] - :sign [] - :puk [] - :current [] - :original [] - :confirmation [] - :error-label nil})})) + (fx/merge + cofx + {:db (assoc-in db + [:hardwallet :pin] + {:status nil + :login (get-in db [:hardwallet :pin :original]) + :export-key [] + :sign [] + :puk [] + :current [] + :original [] + :confirmation [] + :error-label nil + :on-verified (get-in db [:hardwallet :pin :on-verified]) + :on-verified-failure (get-in db [:hardwallet :pin :on-verified])})})) (fx/defn cancel-sheet-confirm {:events [::cancel-sheet-confirm :hardwallet/back-button-pressed]} - [cofx] - (fx/merge cofx - (hide-connection-sheet) - (clear-pin))) + [{:keys [db] :as cofx}] + (when-not (get-in db [:hardwallet :card-connected?]) + (fx/merge cofx + (hide-connection-sheet) + (clear-pin)))) (fx/defn cancel-sheet {:events [::cancel-sheet]} @@ -293,6 +296,9 @@ (let [key-uid (get-in db [:multiaccounts/login :key-uid]) pairing (get-in db [:multiaccounts/multiaccounts key-uid :keycard-pairing]) pin (string/join (get-in db [:hardwallet :pin :login]))] + (log/debug "[keycard] get-keys-from-keycard" + "not nil pairing:" (boolean pairing) + ", not empty pin:" (boolean (seq pin))) (when (and pairing (seq pin)) {:db (assoc-in db [:hardwallet :pin :status] :verifying) @@ -344,15 +350,20 @@ (if tag-was-lost? {:db (assoc-in db [:hardwallet :pin :status] nil)} (if (re-matches pin-mismatch-error (:error error)) - (fx/merge cofx - {:hardwallet/get-application-info {:pairing (get-pairing db key-uid)} - :db (update-in db [:hardwallet :pin] merge {:status :error - :login [] - :import-multiaccount [] - :error-label :t/pin-mismatch})} - (hide-connection-sheet) - (when (= flow :import) - (navigation/navigate-to-cofx :keycard-recovery-pin nil))) + (fx/merge + cofx + {:hardwallet/get-application-info + {:pairing (get-pairing db key-uid)} + + :db + (update-in db [:hardwallet :pin] merge + {:status :error + :login [] + :import-multiaccount [] + :error-label :t/pin-mismatch})} + (hide-connection-sheet) + (when (= flow :import) + (navigation/navigate-to-cofx :keycard-recovery-pin nil))) (show-wrong-keycard-alert true))))) ;; Get application info @@ -360,44 +371,57 @@ (fx/defn get-application-info {:events [:hardwallet/get-application-info]} [{:keys [db]} pairing on-card-read] - (let [key-uid (get-in db [:hardwallet :application-info :key-uid]) + (let [key-uid (get-in + db [:hardwallet :application-info :key-uid] + (get-in db [:multiaccounts/login :key-uid])) pairing' (or pairing (some->> key-uid (get-pairing db)))] (log/debug "[hardwallet] get-application-info" "pairing" pairing') {:hardwallet/get-application-info {:pairing pairing' :on-success on-card-read}})) +(fx/defn frozen-keycard-popup + [{:keys [db] :as cofx}] + (if (:multiaccounts/login db) + (fx/merge + cofx + {:db (assoc-in db [:hardwallet :pin :status] :frozen-card)} + hide-connection-sheet) + {:db (assoc db :popover/popover {:view :frozen-card})})) + (fx/defn on-get-application-info-success {:events [:hardwallet.callback/on-get-application-info-success]} [{:keys [db] :as cofx} info on-success] - (let [info' (-> info - (js->clj :keywordize-keys true) - (update :key-uid ethereum/normalized-hex)) - {:keys [pin-retry-counter puk-retry-counter]} info' + (let [{:keys [pin-retry-counter puk-retry-counter]} info view-id (:view-id db) {:keys [on-card-read]} (:hardwallet db) on-success' (or on-success on-card-read) - enter-step (if (zero? pin-retry-counter) - :puk - (get-in db [:hardwallet :pin :enter-step]))] + enter-step (get-in db [:hardwallet :pin :enter-step])] (log/debug "[hardwallet] on-get-application-info-success" - "on-success" on-success') - (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :pin :enter-step] enter-step) - (update-in [:hardwallet :pin :error-label] #(if (= :puk enter-step) - :t/enter-puk-code-description - %)) - (assoc-in [:hardwallet :application-info] info') - (assoc-in [:hardwallet :application-info :applet-installed?] true) - (assoc-in [:hardwallet :application-info-error] nil))} - (stash-on-card-read) - (if (zero? puk-retry-counter) - {:utils/show-popup {:title (i18n/label :t/error) - :content (i18n/label :t/keycard-blocked)}} - (when on-success' - (dispatch-event on-success')))))) + "on-success" on-success' + "pin-retry-counter" pin-retry-counter + "puk-retry-counter" puk-retry-counter) + (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))} + (stash-on-card-read) + (when (and (zero? pin-retry-counter) + (pos? puk-retry-counter) + (not= enter-step :puk)) + (frozen-keycard-popup)) + (fn [{:keys [db] :as cofx}] + (if (zero? puk-retry-counter) + (fx/merge + cofx + {:db (assoc-in db [:hardwallet :pin :status] :blocked-card)} + hide-connection-sheet) + (when on-success' + (dispatch-event cofx on-success'))))))) (fx/defn on-get-application-info-error {:events [:hardwallet.callback/on-get-application-info-error]} @@ -484,3 +508,12 @@ {:db (assoc-in db [:hardwallet :pin :status] :verifying) :hardwallet/verify-pin {:pin pin :pairing pairing}})))})))) + +(fx/defn navigete-to-keycard-settings + {:events [::navigate-to-keycard-settings]} + [cofx] + (navigation/navigate-reset + cofx + {:index 1 + :routes [{:name :my-profile} + {:name :keycard-settings}]})) diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index 1dd4909d32..6eb87ca53a 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -120,22 +120,35 @@ (defn- proceed-to-pin-confirmation [fx] (assoc-in fx [:db :hardwallet :pin :enter-step] :confirmation)) +(defn- proceed-to-pin-reset-confirmation [fx] + (-> fx + (update-in [:db :hardwallet :pin] dissoc :reset-confirmation) + (assoc-in [:db :hardwallet :pin :enter-step] :reset-confirmation))) + +(defn- proceed-to-puk-confirmation [fx] + (assoc-in fx [:db :hardwallet :pin :enter-step] :puk)) + (fx/defn on-unblock-pin-success {:events [:hardwallet.callback/on-unblock-pin-success]} [{:keys [db] :as cofx}] - (let [pairing (common/get-pairing db)] + (let [pairing (common/get-pairing db) + reset-pin (get-in db [:hardwallet :pin :reset])] (fx/merge cofx - {:hardwallet/get-application-info {:pairing pairing} - :db (-> db - (update-in [:hardwallet :pin] merge {:status nil - :enter-step :original - :current [0 0 0 0 0 0] - :confirmation [] - :puk [] - :puk-restore? true - :error-label nil}))} + {:hardwallet/get-application-info + {:pairing pairing} + + :db + (update-in db [:hardwallet :pin] merge + {:status :after-unblocking + :enter-step :login + :login reset-pin + :confirmation [] + :puk [] + :puk-restore? true + :error-label nil})} (common/hide-connection-sheet) - (navigation/navigate-to-cofx :enter-pin-settings nil)))) + (common/clear-on-card-connected) + (common/clear-on-card-read)))) (fx/defn on-unblock-pin-error {:events [:hardwallet.callback/on-unblock-pin-error]} @@ -145,11 +158,15 @@ (log/debug "[hardwallet] unblock pin error" error) (when-not tag-was-lost? (fx/merge cofx - {:hardwallet/get-application-info {:pairing pairing} - :db (update-in db [:hardwallet :pin] merge {:status :error - :error-label :t/puk-mismatch - :enter-step :puk - :puk []})} + {:hardwallet/get-application-info + {:pairing pairing} + + :db + (update-in db [:hardwallet :pin] merge + {:status :error + :error-label :t/puk-mismatch + :enter-step :puk + :puk []})} (common/hide-connection-sheet))))) (fx/defn clear-on-verify-handlers @@ -215,7 +232,7 @@ (fn [_] {:utils/dispatch-later [{:dispatch [on-verified-failure] :ms 200}]})) - (clear-on-verify-handlers)) + #_(clear-on-verify-handlers)) (fx/merge cofx (common/hide-connection-sheet) @@ -231,12 +248,13 @@ :handler (fn [{:keys [db]}] (let [puk (common/vector->string (get-in db [:hardwallet :pin :puk])) + pin (common/vector->string (get-in db [:hardwallet :pin :reset])) key-uid (get-in db [:hardwallet :application-info :key-uid]) pairing (common/get-pairing db key-uid)] {:db (assoc-in db [:hardwallet :pin :status] :verifying) :hardwallet/unblock-pin {:puk puk - :new-pin common/default-pin + :new-pin pin :pairing pairing}}))})) (def pin-code-length 6) @@ -252,12 +270,15 @@ (fx/defn update-pin {:events [:hardwallet.ui/pin-numpad-button-pressed]} [{:keys [db] :as cofx} number enter-step] + (log/debug "update-pin" enter-step) (let [numbers-entered (count (get-in db [:hardwallet :pin enter-step])) need-update? (if (= enter-step :puk) (< numbers-entered puk-code-length) (< numbers-entered pin-code-length))] (fx/merge cofx - {:db (cond-> (assoc-in db [:hardwallet :pin :status] nil) + {:db (cond-> (-> db + (assoc-in [:hardwallet :pin :enter-step] enter-step) + (assoc-in [:hardwallet :pin :status] nil)) need-update? (update-in [:hardwallet :pin enter-step] (fnil conj []) number))} (when need-update? (handle-pin-input enter-step))))) @@ -269,6 +290,13 @@ :original [] :confirmation []})) +(defn- pin-reset-error [fx error-label] + (update-in fx [:db :hardwallet :pin] merge {:status :error + :error-label error-label + :enter-step :reset + :reset [] + :reset-confirmation []})) + ; PIN enter steps: ; login - PIN is used to login ; sign - PIN for transaction sign @@ -309,7 +337,7 @@ (and (= enter-step :export-key) (= pin-code-length numbers-entered)) - (wallet/hide-pin-sheet) + (wallet/verify-pin-with-delay) (and (= enter-step :sign) (= pin-code-length numbers-entered)) @@ -328,7 +356,21 @@ (= pin-code-length numbers-entered) (not= (get-in db [:hardwallet :pin :original]) (get-in db [:hardwallet :pin :confirmation]))) - (pin-enter-error :t/pin-mismatch)))) + (pin-enter-error :t/pin-mismatch) + + (= enter-step :reset) + (proceed-to-pin-reset-confirmation) + + (and (= enter-step :reset-confirmation) + (= (get-in db [:hardwallet :pin :reset]) + (get-in db [:hardwallet :pin :reset-confirmation]))) + (proceed-to-puk-confirmation) + + (and (= enter-step :reset-confirmation) + (= pin-code-length numbers-entered) + (not= (get-in db [:hardwallet :pin :reset]) + (get-in db [:hardwallet :pin :reset-confirmation]))) + (pin-reset-error :t/pin-mismatch)))) (fx/defn set-multiaccount-pairing [cofx _ pairing paired-on] diff --git a/src/status_im/hardwallet/export_key.cljs b/src/status_im/hardwallet/export_key.cljs index f29ec0f4eb..9476eebcfc 100644 --- a/src/status_im/hardwallet/export_key.cljs +++ b/src/status_im/hardwallet/export_key.cljs @@ -1,6 +1,7 @@ (ns status-im.hardwallet.export-key (:require [status-im.utils.fx :as fx] [taoensso.timbre :as log] + [status-im.hardwallet.wallet :as wallet] [status-im.hardwallet.common :as common])) (fx/defn on-export-key-error @@ -39,5 +40,6 @@ (let [callback-fn (get-in db [:hardwallet :on-export-success])] (fx/merge cofx {:dispatch (callback-fn pubkey)} + (wallet/hide-pin-sheet) (common/clear-pin) (common/hide-connection-sheet)))) diff --git a/src/status_im/hardwallet/login.cljs b/src/status_im/hardwallet/login.cljs index 1c908cc364..2e874ff769 100644 --- a/src/status_im/hardwallet/login.cljs +++ b/src/status_im/hardwallet/login.cljs @@ -8,7 +8,8 @@ [status-im.hardwallet.recovery :as recovery] [status-im.hardwallet.onboarding :as onboarding] status-im.hardwallet.fx - [status-im.ui.components.bottom-sheet.core :as bottom-sheet])) + [status-im.ui.components.bottom-sheet.core :as bottom-sheet] + [status-im.signing.core :as signing.core])) (fx/defn login-got-it-pressed {:events [:keycard.login.pin.ui/got-it-pressed @@ -49,16 +50,56 @@ {:db (assoc-in db [:hardwallet :flow] :login)} (navigation/navigate-to-cofx :keycard-recovery-pair nil))) +(fx/defn frozen-keycard-popup + [{:keys [db]}] + {:db (assoc db :popover/popover {:view :frozen-card})}) + +(fx/defn reset-pin + {:events [::reset-pin]} + [{:keys [db] :as cofx}] + (fx/merge + cofx + {:db (assoc db :hardwallet/new-account-sheet? false)} + (signing.core/discard) + (fn [{:keys [db]}] + {:db (-> db + (dissoc :popover/popover) + (update-in [:hardwallet :pin] dissoc + :reset :puk) + (update-in [:hardwallet :pin] assoc + :enter-step :reset + :error nil + :status nil))}) + (when-not (:multiaccounts/login db) + (navigation/navigate-to-cofx + :profile-stack + {:screen :keycard-pin})))) + +(fx/defn dismiss-frozen-keycard-popover + {:events [::frozen-keycard-popover-dismissed]} + [{:keys [db]}] + {:db (-> db + (dissoc :popover/popover) + (update :hardwallet dissoc :setup-step))}) + (fx/defn login-with-keycard {:events [:hardwallet/login-with-keycard]} [{:keys [db] :as cofx}] - (let [application-info (get-in db [:hardwallet :application-info]) + (let [{:keys [:pin-retry-counter :puk-retry-counter] + :as application-info} + (get-in db [:hardwallet :application-info]) + key-uid (get-in db [:hardwallet :application-info :key-uid]) multiaccount (get-in db [:multiaccounts/multiaccounts (get-in db [:multiaccounts/login :key-uid])]) multiaccount-key-uid (get multiaccount :key-uid) multiaccount-mismatch? (or (nil? multiaccount) (not= multiaccount-key-uid key-uid)) pairing (:keycard-pairing multiaccount)] + (log/debug "[keycard] login-with-keycard" + "empty application info" (empty? application-info) + "no key-uid" (empty? key-uid) + "multiaccount-mismatch?" multiaccount-mismatch? + "no pairing" (empty? pairing)) (cond (empty? application-info) (fx/merge cofx @@ -80,10 +121,16 @@ (common/hide-connection-sheet) (navigation/navigate-to-cofx :keycard-unpaired nil)) + (and (zero? pin-retry-counter) + (or (nil? puk-retry-counter) + (= 5 puk-retry-counter))) + nil #_(frozen-keycard-popup cofx) + :else (common/get-keys-from-keycard cofx)))) (fx/defn proceed-to-login + {:events [::login-after-reset]} [cofx] (log/debug "[hardwallet] proceed-to-login") (common/show-connection-sheet diff --git a/src/status_im/hardwallet/real_keycard.cljs b/src/status_im/hardwallet/real_keycard.cljs index b4d221dc3b..e27705bac6 100644 --- a/src/status_im/hardwallet/real_keycard.cljs +++ b/src/status_im/hardwallet/real_keycard.cljs @@ -3,6 +3,7 @@ ["react-native" :as rn] [status-im.utils.types :as types] [status-im.native-module.core :as status] + [status-im.ethereum.core :as ethereum] [status-im.hardwallet.keycard :as keycard])) (defonce event-emitter (.-DeviceEventEmitter rn)) @@ -57,9 +58,20 @@ (defn get-application-info [{:keys [pairing on-success on-failure]}] + ;; NOTE: if the card fails to get application info in the middle of the call + ;; it doesn't returns a Tar was lost. error properly + ;; https://github.com/status-im/react-native-status-keycard/blob/master/android/src/main/java/im/status/ethereum/keycard/SmartCard.java#L235 + (.. status-keycard (getApplicationInfo (str pairing)) - (then on-success) + (then (fn [response] + (let [info (-> response + (js->clj :keywordize-keys true) + (update :key-uid ethereum/normalized-hex))] + (if (and pairing (nil? (:pin-retry-counter info))) + (on-failure (clj->js {:message "Tag was lost." + :code "android.nfc.TagLostException"})) + (on-success info))))) (catch on-failure))) (defn install-applet [{:keys [on-success on-failure]}] diff --git a/src/status_im/hardwallet/sign.cljs b/src/status_im/hardwallet/sign.cljs index 96cd9b25fa..3b1ebae78e 100644 --- a/src/status_im/hardwallet/sign.cljs +++ b/src/status_im/hardwallet/sign.cljs @@ -197,17 +197,21 @@ {:events [:hardwallet.callback/on-sign-error]} [{:keys [db] :as cofx} error] (log/debug "[hardwallet] sign error: " error) - (let [tag-was-lost? (common/tag-lost? (:error error))] + (let [tag-was-lost? (common/tag-lost? (:error error)) + pin-retries (get-in db [:hardwallet :application-info :pin-retry-counter])] (when-not tag-was-lost? (if (re-matches common/pin-mismatch-error (:error error)) (fx/merge cofx {:db (-> db + (assoc-in [:hardwallet :application-info :pin-retry-counter] (dec pin-retries)) (update-in [:hardwallet :pin] merge {:status :error :sign [] :error-label :t/pin-mismatch}) (assoc-in [:signing/sign :keycard-step] :pin))} (common/hide-connection-sheet) - (common/get-application-info (common/get-pairing db) nil)) + (common/get-application-info (common/get-pairing db) nil) + (when (zero? (dec pin-retries)) + (common/frozen-keycard-popup))) (fx/merge cofx (common/hide-connection-sheet) (common/show-wrong-keycard-alert true)))))) diff --git a/src/status_im/hardwallet/simulated_keycard.cljs b/src/status_im/hardwallet/simulated_keycard.cljs index 8c100e8365..c8e930c6cf 100644 --- a/src/status_im/hardwallet/simulated_keycard.cljs +++ b/src/status_im/hardwallet/simulated_keycard.cljs @@ -31,6 +31,8 @@ :paired? true :has-master-key? true :initialized? true + :pin-retry-counter 3 + :puk-retry-counter 5 :key-uid (get-in @re-frame.db/app-db [:multiaccounts/login :key-uid])}) (connect-card)) @@ -84,7 +86,7 @@ (defn install-cash-applet [_]) (def kk1-password "000000") - +(def default-puk "000000000000") (defn init-card [{:keys [pin on-success]}] (swap! state assoc :application-info {:free-pairing-slots 5 @@ -98,7 +100,7 @@ (swap! state assoc :pin pin) (later #(on-success {:password kk1-password - :puk "000000000000" + :puk default-puk :pin pin}))) (defn install-applet-and-init-card [_]) @@ -141,12 +143,34 @@ password #(on-success response))))) -(defn unblock-pin [_]) +(defn unblock-pin + [{:keys [puk on-success on-failure]}] + (if (= puk default-puk) + (do + (swap! state update :application-info assoc + :pin-retry-counter 3 + :puk-retry-counter 5) + (later #(on-success true))) + (do + (swap! state update-in + [:application-info :puk-retry-counter] + (fnil dec 5)) + (later + #(on-failure + #js {:code "EUNSPECIFIED" + :message "Unexpected error SW, 0x63C2"}))))) -(defn verify-pin [{:keys [pin pairing on-success]}] - (when (and (= pairing kk1-pair) - (= pin (get @state :pin))) - (later #(on-success 3)))) +(defn verify-pin [{:keys [pin pairing on-success on-failure]}] + (if (and (= pairing kk1-pair) + (= pin (get @state :pin))) + (later #(on-success 3)) + (do + (swap! state update-in + [:application-info :pin-retry-counter] + (fnil dec 3)) + (later #(on-failure + #js {:code "EUNSPECIFIED" + :message "Unexpected error SW, 0x63C2"}))))) (defn change-pin [args] (log/warn "change-pin not implemented" args)) @@ -199,16 +223,42 @@ (on-success publicKey))))))))))))))) (defn unpair-and-delete [_]) -(defn get-keys [{:keys [on-success pin]}] - (swap! state assoc :pin pin) - ;;TODO(rasom): verify password before callback - (later - #(on-success - {:key-uid (get-in @state [:application-info :key-uid]) - :encryption-public-key (ethereum/sha3 pin)}))) -(defn sign [{:keys [on-success]}] - (on-success "123")) +;; It is a bit complicated to verify password before we have multiaccs main +;; wallet address, so we just define a set of "allowed" pins +(def allowed-pins + #{"121212" "111111" "222222" "123123"}) + +(defn get-keys [{:keys [on-success on-failure pin]}] + (if (contains? allowed-pins pin) + (do + (swap! state assoc :pin pin) + (later + #(on-success + {:key-uid (get-in @state [:application-info :key-uid]) + :encryption-public-key (ethereum/sha3 pin)}))) + (do + (log/debug "Incorrect PIN" pin) + (swap! state update-in + [:application-info :pin-retry-counter] + (fnil dec 3)) + (later + #(on-failure + #js {:code "EUNSPECIFIED" + :message "Unexpected error SW, 0x63C2"}))))) + +(defn sign [{:keys [pin on-success on-failure]}] + (if (= pin (get @state :pin)) + (later + #(on-success "123")) + (do + (swap! state update-in + [:application-info :pin-retry-counter] + (fnil dec 3)) + (later + #(on-failure + #js {:code "EUNSPECIFIED" + :message "Unexpected error SW, 0x63C2"}))))) (defn sign-typed-data [args] (log/warn "sign-typed-data not implemented" args)) diff --git a/src/status_im/hardwallet/wallet.cljs b/src/status_im/hardwallet/wallet.cljs index 91bfa6931e..e5970d64f5 100644 --- a/src/status_im/hardwallet/wallet.cljs +++ b/src/status_im/hardwallet/wallet.cljs @@ -1,11 +1,9 @@ (ns status-im.hardwallet.wallet (:require [status-im.ethereum.core :as ethereum] [status-im.utils.fx :as fx] - [status-im.ui.screens.wallet.add-new.views :as add-new.views] [status-im.hardwallet.common :as common] [status-im.constants :as constants] [status-im.ethereum.eip55 :as eip55] - [status-im.ui.components.bottom-sheet.core :as bottom-sheet] [status-im.utils.hex :as utils.hex])) (fx/defn show-pin-sheet @@ -15,21 +13,20 @@ cofx {:db (-> db (assoc-in [:hardwallet :pin :enter-step] :export-key) - (update-in [:hardwallet :pin] dissoc :export-key))} - (bottom-sheet/show-bottom-sheet - {:view {:content add-new.views/pin - :height 256}}))) + (update-in [:hardwallet :pin] dissoc :export-key) + (assoc :hardwallet/new-account-sheet? true))})) + +(fx/defn verify-pin-with-delay + [cofx] + {:utils/dispatch-later + ;; We need to give previous sheet some time to be fully hidden + [{:ms 200 + :dispatch [:wallet.accounts/verify-pin]}]}) (fx/defn hide-pin-sheet - {:events [:hardwallet/hide-new-account-pin-sheet]} - [cofx] - (fx/merge - cofx - {:utils/dispatch-later - ;; We need to give previous sheet some time to be fully hidden - [{:ms 200 - :dispatch [:wallet.accounts/verify-pin]}]} - (bottom-sheet/hide-bottom-sheet))) + {:events [:hardwallet/new-account-pin-sheet-hide]} + [{:keys [db]}] + {:db (assoc db :hardwallet/new-account-sheet? false)}) (fx/defn generate-new-keycard-account {:events [:wallet.accounts/generate-new-keycard-account]} @@ -59,6 +56,6 @@ (common/verify-pin cofx {:pin-step :export-key - :on-card-connected :wallet.accounts/generate-new-keycard-account + :on-card-connected :wallet.accounts/verify-pin :on-success :wallet.accounts/generate-new-keycard-account :on-failure :hardwallet/new-account-pin-sheet})) diff --git a/src/status_im/i18n_test.cljs b/src/status_im/i18n_test.cljs index b4b1192484..e2e04239fd 100644 --- a/src/status_im/i18n_test.cljs +++ b/src/status_im/i18n_test.cljs @@ -716,7 +716,6 @@ :pin-changed :pin-code :pin-mismatch - :pin-retries-left :preview-privacy :privacy :privacy-and-security diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index f874f64506..d67d655c45 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -7,6 +7,7 @@ [status-im.ethereum.core :as ethereum] [status-im.ethereum.eip55 :as eip55] [status-im.ethereum.json-rpc :as json-rpc] + [status-im.hardwallet.common :as hardwallet.common] [status-im.fleet.core :as fleet] [status-im.i18n :as i18n] [status-im.multiaccounts.biometric.core :as biometric] @@ -367,16 +368,18 @@ (log/debug "[login] get-auth-method-success" "auth-method" auth-method "keycard-multiacc?" keycard-multiaccount?) - (fx/merge cofx - {:db (assoc db :auth-method auth-method)} - #(cond - (= auth-method - keychain/auth-method-biometric) - (biometric/biometric-auth %) - (= auth-method - keychain/auth-method-password) - (get-credentials % key-uid)) - (open-login-callback nil)))) + (fx/merge + cofx + {:db (assoc db :auth-method auth-method)} + #(cond + (= auth-method keychain/auth-method-biometric) + (biometric/biometric-auth %) + (= auth-method keychain/auth-method-password) + (get-credentials % key-uid) + (and keycard-multiaccount? + (get-in db [:hardwallet :card-connected?])) + (hardwallet.common/get-application-info % nil nil)) + (open-login-callback nil)))) (fx/defn biometric-auth-done {:events [:biometric-auth-done]} diff --git a/src/status_im/navigation.cljs b/src/status_im/navigation.cljs index 63c77c0f2f..fba7d41c5f 100644 --- a/src/status_im/navigation.cljs +++ b/src/status_im/navigation.cljs @@ -60,6 +60,7 @@ (navigate-to-cofx cofx view-id params)) (fx/defn navigate-replace + {:events [:navigate-replace]} [{:keys [db]} go-to-view-id screen-params] (let [db (cond-> (assoc db :view-id go-to-view-id) (seq screen-params) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 096907c0c7..568aaf1ff8 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -188,6 +188,9 @@ (reg-root-key-sub ::message-lists :message-lists) (reg-root-key-sub ::pagination-info :pagination-info) +;; keycard +(reg-root-key-sub :hardwallet/new-account-sheet? :hardwallet/new-account-sheet?) + ;;GENERAL ============================================================================================================== (re-frame/reg-sub diff --git a/src/status_im/ui/components/bottom_panel/views.cljs b/src/status_im/ui/components/bottom_panel/views.cljs index daa49cc13f..b43420c566 100644 --- a/src/status_im/ui/components/bottom_panel/views.cljs +++ b/src/status_im/ui/components/bottom_panel/views.cljs @@ -49,7 +49,28 @@ update? (atom nil) current-obj (reagent/atom nil)] (reagent/create-class - {:component-will-update (fn [_ [_ obj _ _]] + {:component-will-mount (fn [args] + (let [[_ obj _ _] (.-argv (.-props args))] + (when @clear-timeout (js/clearTimeout @clear-timeout)) + (when (or (not= obj @current-obj) @update?) + (cond + @update? + (do (reset! update? false) + (show-panel-anim bottom-anim-value alpha-value)) + + (and @current-obj obj) + (do (reset! update? true) + (js/setTimeout #(reset! current-obj obj) 600) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))) + + obj + (do (reset! current-obj obj) + (show-panel-anim bottom-anim-value alpha-value)) + + :else + (do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600)) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))))))) + :component-will-update (fn [_ [_ obj _ _]] (when @clear-timeout (js/clearTimeout @clear-timeout)) (when (or (not= obj @current-obj) @update?) (cond diff --git a/src/status_im/ui/screens/hardwallet/frozen_card/view.cljs b/src/status_im/ui/screens/hardwallet/frozen_card/view.cljs new file mode 100644 index 0000000000..a670426c0c --- /dev/null +++ b/src/status_im/ui/screens/hardwallet/frozen_card/view.cljs @@ -0,0 +1,44 @@ +(ns status-im.ui.screens.hardwallet.frozen-card.view + (:require-macros [status-im.utils.views :as views]) + (:require [status-im.ui.components.react :as react] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.icons.vector-icons :as icons] + [status-im.ui.components.common.common :as components.common] + [status-im.hardwallet.login :as login] + [status-im.i18n :as i18n] + [re-frame.core :as re-frame])) + +(views/defview frozen-card + [{:keys [show-dismiss-button?] + :or {show-dismiss-button? true}}] + [react/view {:style (when-not show-dismiss-button? + {:flex 1})} + [react/view {:margin-top 24 + :margin-horizontal 24 + :align-items :center} + [react/view {:background-color colors/blue-light + :width 32 :height 32 + :border-radius 16 + :align-items :center + :justify-content :center} + [icons/icon :main-icons/warning {:color colors/blue}]] + [react/text {:style {:typography :title-bold + :margin-top 16 + :margin-bottom 8}} + (i18n/label :t/keycard-is-frozen-title)] + [react/text {:style {:color colors/gray + :text-align :center}} + (i18n/label :t/keycard-is-frozen-details)]] + [react/view {:margin-bottom 24 + :margin-horizontal 24 + :align-items :center} + [components.common/button + {:on-press #(re-frame/dispatch [::login/reset-pin]) + :button-style {:margin-top 24} + :label (i18n/label :t/keycard-is-frozen-reset)}] + (when show-dismiss-button? + [components.common/button + {:on-press #(re-frame/dispatch [::login/frozen-keycard-popover-dismissed]) + :button-style {:margin-top 24} + :background? false + :label (i18n/label :t/dismiss)}])]]) diff --git a/src/status_im/ui/screens/hardwallet/pin/styles.cljs b/src/status_im/ui/screens/hardwallet/pin/styles.cljs index 9bed3b82ad..e4cbaa9739 100644 --- a/src/status_im/ui/screens/hardwallet/pin/styles.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/styles.cljs @@ -1,22 +1,41 @@ (ns status-im.ui.screens.hardwallet.pin.styles - (:require [status-im.ui.components.colors :as colors] - [status-im.utils.styles :as styles])) + (:require [status-im.ui.components.colors :as colors])) -(styles/def pin-container +(def pin-container {:flex 1 :flex-direction :column :justify-content :space-between}) -(defn error-container [small-screen?] - {:height (when small-screen? 18) - :margin-top (if small-screen? 14 10) - :margin-bottom (if small-screen? 10 0)}) +(defn info-container [small-screen?] + {:height 44 + :width "100%" + :justify-content :center + :margin-top (if small-screen? 14 10)}) + +(defn error-container [y-translation opacity] + {:left 0 + :right 0 + :align-items :center + :position :absolute + :transform [{:translateY y-translation}] + :opacity opacity + :justify-content :center}) (defn error-text [small-screen?] - {:color colors/red + {:position :absolute + :color colors/red :font-size (if small-screen? 12 15) :text-align :center}) +(defn retry-container [y-translation opacity] + {:left 0 + :right 0 + :align-items :center + :position :absolute + :transform [{:translateY y-translation}] + :opacity opacity + :justify-content :center}) + (defn center-container [title] {:flex-direction :column :align-items :center @@ -34,16 +53,18 @@ (def pin-indicator-container {:flex-direction :row :justify-content :space-between - :margin-top 16}) + :align-items :center + :height 22 + :margin-top 5}) (def pin-indicator-group-container {:flex-direction :row :justify-content :space-between}) -(defn pin-indicator [pressed? status] +(defn pin-indicator [pressed? error?] {:width 8 :height 8 - :background-color (if (= status :error) + :background-color (if error? colors/red (if pressed? colors/blue @@ -51,6 +72,15 @@ :border-radius 50 :margin-horizontal 5}) +(defn puk-indicator [error?] + {:width 8 + :height 8 + :background-color (if error? + colors/red + colors/black-transparent) + :border-radius 50 + :margin-horizontal 5}) + (def waiting-indicator-container {:margin-top 26}) diff --git a/src/status_im/ui/screens/hardwallet/pin/subs.cljs b/src/status_im/ui/screens/hardwallet/pin/subs.cljs index 22803eb5aa..6d7a124909 100644 --- a/src/status_im/ui/screens/hardwallet/pin/subs.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/subs.cljs @@ -19,7 +19,7 @@ (re-frame/reg-sub :hardwallet/pin-enter-step (fn [db] - (get-in db [:hardwallet :pin :enter-step] :original))) + (get-in db [:hardwallet :pin :enter-step]))) (re-frame/reg-sub :hardwallet/pin-operation @@ -47,3 +47,10 @@ :hardwallet/pin-error-label (fn [db] (get-in db [:hardwallet :pin :error-label]))) + +(re-frame/reg-sub + :hardwallet/frozen-card? + (fn [db] + (let [{:keys [pin-retry-counter]} + (get-in db [:hardwallet :application-info])] + (zero? pin-retry-counter)))) diff --git a/src/status_im/ui/screens/hardwallet/pin/views.cljs b/src/status_im/ui/screens/hardwallet/pin/views.cljs index e30b2b061b..2e10180149 100644 --- a/src/status_im/ui/screens/hardwallet/pin/views.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/views.cljs @@ -1,6 +1,8 @@ (ns status-im.ui.screens.hardwallet.pin.views (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.ui.components.animation :as animation] [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors] [status-im.ui.components.icons.vector-icons :as vector-icons] @@ -8,9 +10,11 @@ [status-im.ui.screens.hardwallet.pin.styles :as styles] [status-im.ui.components.checkbox.view :as checkbox] [status-im.utils.platform :as platform] + [status-im.utils.utils :as utils] [status-im.ui.components.topbar :as topbar])) (def default-pin-retries-number 3) +(def default-puk-retries-number 5) (defn numpad-button [n step enabled? small-screen?] [react/touchable-highlight @@ -40,32 +44,36 @@ [react/view (styles/numpad-delete-button small-screen?) [vector-icons/icon :main-icons/backspace {:color colors/blue}]]]]]) -(defn pin-indicator [pressed? status] - [react/view (styles/pin-indicator pressed? status)]) - -(defn pin-indicators [pin status style] - [react/view (merge styles/pin-indicator-container style) +(defn pin-indicators [pin error?] + [react/view styles/pin-indicator-container (map-indexed - (fn [i group] - ^{:key i} - [react/view styles/pin-indicator-group-container - group]) - (partition 3 - (map-indexed - (fn [i n] - ^{:key i} - [pin-indicator (number? n) status]) - (concat pin - (repeat (- 6 (count pin)) - nil)))))]) + (fn [i n] + (let [pressed? (number? n)] + ^{:key i} [react/view (styles/pin-indicator pressed? error?)])) + (concat pin (repeat (- 6 (count pin)) nil)))]) -(defn puk-indicators [puk status] - [react/view {:margin-top 28} +(defn puk-indicators [puk error?] + [react/view {:margin-top 28 + :flex-direction :row + :justify-content :space-between} (map-indexed (fn [i puk-group] ^{:key i} - [pin-indicators puk-group status {:margin-top 8}]) - (partition 6 + [react/view (merge styles/pin-indicator-container + {:margin-top 8 + :margin 12}) + (map-indexed + (fn [j n] + (if (number? n) + ^{:key j} [react/text {:style {:font-size 20 + :width 18 + :color (if error? + colors/red + colors/black)}} + n] + ^{:key j} [react/view (styles/puk-indicator error?)])) + puk-group)]) + (partition 4 (concat puk (repeat (- 12 (count puk)) nil))))]) @@ -82,38 +90,142 @@ :on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}] [react/text (i18n/label :t/hardwallet-dont-ask-card)]]))) +(defn bezier-easing [] + (.bezier ^js animation/easing 0.77, 0.000, 0.175, 1)) + +(defn animate-info-in + "animation that makes the error message appear for a few seconds, then + replaces it with the number of attempts left" + [error-y-translation error-opacity retries-y-translation retries-opacity] + (animation/start + (animation/anim-sequence + [(animation/parallel + [(animation/timing error-opacity + {:toValue 1 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true}) + (animation/timing error-y-translation + {:toValue 0 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true})]) + (animation/anim-delay 2200) + (animation/parallel + [(animation/timing error-opacity + {:toValue 0 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true}) + (animation/timing error-y-translation + {:toValue 8 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true}) + (animation/timing retries-opacity + {:toValue 1 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true}) + (animation/timing retries-y-translation + {:toValue 0 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true})])]))) + +(defn animate-info-out + [retries-y-translation retries-opacity] + (animation/start + (animation/parallel + [(animation/timing retries-opacity + {:toValue 0 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true}) + (animation/timing retries-y-translation + {:toValue -8 + :easing (bezier-easing) + :duration 400 + :useNativeDriver true})]))) + (defn pin-view - [{:keys [pin title-label description-label step status error-label - retry-counter small-screen? save-password-checkbox?]}] - (let [enabled? (not= status :verifying)] - [react/scroll-view - [react/view styles/pin-container - [react/view (styles/center-container title-label) - (when title-label - [react/text {:style styles/center-title-text} - (i18n/label title-label)]) - (when description-label - [react/text {:style styles/create-pin-text - :number-of-lines 2} - (i18n/label description-label)]) - [react/view {:flex 1} - (case status - :verifying [react/view styles/waiting-indicator-container - [react/activity-indicator {:animating true - :size :small}]] - :error [react/view (styles/error-container small-screen?) - [react/text {:style (styles/error-text small-screen?)} - (i18n/label error-label)]] - (when (and retry-counter (< retry-counter default-pin-retries-number)) - [react/view {:margin-top (if (= step :puk) 24 8)} - [react/text {:style {:text-align :center}} - (i18n/label :t/pin-retries-left {:number retry-counter})]]))] - (when save-password-checkbox? - [save-password]) - (if (= step :puk) - [puk-indicators pin status] - [pin-indicators pin status nil]) - [numpad step enabled? small-screen?]]]])) + [{:keys [retry-counter]}] + (let [error-y-translation (animation/create-value -8) + error-opacity (animation/create-value 0) + retries-y-translation (animation/create-value (if retry-counter 8 0)) + retries-opacity (animation/create-value (if retry-counter 0 1)) + !error? (reagent/atom false)] + (reagent/create-class + {:component-did-update + (fn [this [_ previous-props]] + (let [[_ props] (.-argv (.-props ^js this)) + previous-status (:status previous-props) + new-status (:status props)] + (case new-status + :error (when (or (nil? previous-status) + (= :verifying previous-status)) + (utils/vibrate) + (reset! !error? true) + (animate-info-in error-y-translation + error-opacity + retries-y-translation + retries-opacity) + (js/setTimeout (fn [] (reset! !error? false)) 3000)) + :verifying (do + (animation/set-value error-y-translation -8) + (animate-info-out retries-y-translation + retries-opacity)) + nil))) + :reagent-render + (fn [{:keys [pin title-label description-label step error-label status + retry-counter small-screen? save-password-checkbox?]}] + (let [enabled? (and (not= status :verifying) + (not @!error?)) + puk? (= step :puk)] + [react/scroll-view + [react/view styles/pin-container + [react/view (styles/center-container title-label) + (when title-label + [react/text {:style styles/center-title-text} + (i18n/label title-label)]) + (when description-label + [react/text {:style styles/create-pin-text + :number-of-lines 2} + (i18n/label description-label)]) + (when save-password-checkbox? + [save-password]) + [react/view {:style (styles/info-container small-screen?)} + (when error-label + [react/animated-view {:style (styles/error-container error-y-translation error-opacity)} + [react/text {:style (styles/error-text small-screen?)} + (i18n/label error-label)]]) + [react/animated-view {:style (styles/retry-container retries-y-translation retries-opacity)} + (cond + (and retry-counter (= retry-counter 1)) + [react/nested-text {:style {:text-align :center + :color colors/gray}} + (i18n/label (if puk? + :t/pin-one-attempt-blocked-before + :t/pin-one-attempt-frozen-before)) + [{:style {:color colors/black + :font-weight "700"}} + (i18n/label :t/pin-one-attempt)] + (i18n/label (if puk? + :t/pin-one-attempt-blocked-after + :t/pin-one-attempt-frozen-after))] + + (and retry-counter (< retry-counter (if puk? + default-puk-retries-number + default-pin-retries-number))) + [react/text {:style {:text-align :center + :color colors/gray}} + (i18n/label :t/pin-retries-left {:number retry-counter})] + :else + nil)]] + (if puk? + [puk-indicators pin @!error?] + [pin-indicators pin @!error?]) + [numpad step enabled? small-screen?]]]]))}))) (def pin-retries 3) (def puk-retries 5) @@ -125,32 +237,37 @@ pin-retry-counter [:hardwallet/pin-retry-counter] puk-retry-counter [:hardwallet/puk-retry-counter] error-label [:hardwallet/pin-error-label]] - [react/view {:flex 1 - :background-color colors/white} - [topbar/topbar {}] - (if (zero? pin-retry-counter) - [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-view {:pin pin - :retry-counter (when (< pin-retry-counter pin-retries) pin-retry-counter) - :title-label (case step - :current :t/current-pin - :login :t/current-pin - :import-multiaccount :t/current-pin - :original :t/create-a-pin - :confirmation :t/repeat-pin - :t/current-pin) - :description-label (case step - :current :t/current-pin-description - :sign :t/current-pin-description - :import-multiaccount :t/current-pin-description - :login :t/login-pin-description - :t/new-pin-description) - :step step - :status status - :error-label error-label}])])) + (let [;; TODO(rasom): retarded hack to prevent state mess on opening pin + ;; sheet on another tab and returning back to this screen. Should be + ;; properly rewritten so that different instances of pin-view do not + ;; mess with state unrelated to them. + step (or step :current)] + [react/view {:flex 1 + :background-color colors/white} + [topbar/topbar {}] + (if (zero? pin-retry-counter) + [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-view {:pin pin + :retry-counter (when (< pin-retry-counter pin-retries) pin-retry-counter) + :title-label (case step + :current :t/current-pin + :login :t/current-pin + :import-multiaccount :t/current-pin + :original :t/create-a-pin + :confirmation :t/repeat-pin + :t/current-pin) + :description-label (case step + :current :t/current-pin-description + :sign :t/current-pin-description + :import-multiaccount :t/current-pin-description + :login :t/login-pin-description + :t/new-pin-description) + :step step + :status status + :error-label error-label}])]))) diff --git a/src/status_im/ui/screens/hardwallet/settings/views.cljs b/src/status_im/ui/screens/hardwallet/settings/views.cljs index 724577f60e..1f9da30353 100644 --- a/src/status_im/ui/screens/hardwallet/settings/views.cljs +++ b/src/status_im/ui/screens/hardwallet/settings/views.cljs @@ -8,7 +8,9 @@ [status-im.ui.components.colors :as colors] [status-im.ui.components.common.common :as components.common] [status-im.ui.components.topbar :as topbar] - [status-im.constants :as constants])) + [status-im.constants :as constants] + [status-im.ui.screens.keycard.views :as keycard.views] + [status-im.hardwallet.common :as hardwallet.common])) (defn- action-row [{:keys [icon label on-press color-theme]}] [react/touchable-highlight @@ -131,3 +133,10 @@ :color-theme :red :label :t/reset-card :on-press #(re-frame/dispatch [:keycard-settings.ui/reset-card-pressed])}]])]])) + +(defn reset-pin [] + [keycard.views/login-pin + {:back-button-handler + ::hardwallet.common/navigate-to-keycard-settings + :hide-login-actions? true + :default-enter-step :reset}]) diff --git a/src/status_im/ui/screens/keycard/recovery/views.cljs b/src/status_im/ui/screens/keycard/recovery/views.cljs index 289f6defb4..5b613211c8 100644 --- a/src/status_im/ui/screens/keycard/recovery/views.cljs +++ b/src/status_im/ui/screens/keycard/recovery/views.cljs @@ -15,8 +15,9 @@ [status-im.ui.screens.keycard.styles :as styles] [status-im.utils.core :as utils.core] [status-im.utils.gfycat.core :as gfy] - [status-im.utils.identicon :as identicon] - [status-im.constants :as constants]) + [status-im.constants :as constants] + [status-im.ui.screens.keycard.views :as keycard.views] + [status-im.utils.identicon :as identicon]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn intro [] @@ -83,26 +84,34 @@ {:handler #(re-frame/dispatch [::hardwallet.recovery/cancel-pressed]) :style {:padding-left 21}} (i18n/label :t/cancel)] - [react/text {:style {:color colors/gray}} - (i18n/label :t/step-i-of-n {:number 2 - :step 2})]] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/enter-your-code)]]] - [pin.views/pin-view - {:pin pin - :retry-counter retry-counter - :small-screen? small-screen? - :status status - :error-label error-label - :step :import-multiaccount}]]])) + (when-not (#{:frozen-card :blocked-card} status) + [react/text {:style {:color colors/gray}} + (i18n/label :t/step-i-of-n {:number 2 + :step 2})])] + (case status + :frozen-card + [keycard.views/frozen-card] + + :blocked-card + [keycard.views/blocked-card] + + [react/view {:flex 1 + :flex-direction :column + :justify-content :space-between + :align-items :center} + [react/view {:flex-direction :column + :align-items :center} + [react/view {:margin-top 16} + [react/text {:style {:typography :header + :text-align :center}} + (i18n/label :t/enter-your-code)]]] + [pin.views/pin-view + {:pin pin + :retry-counter retry-counter + :small-screen? small-screen? + :status status + :error-label error-label + :step :import-multiaccount}]])])) (defview pair [] (letsubs [pair-code [:hardwallet-pair-code] diff --git a/src/status_im/ui/screens/keycard/views.cljs b/src/status_im/ui/screens/keycard/views.cljs index c9045ef707..a945953499 100644 --- a/src/status_im/ui/screens/keycard/views.cljs +++ b/src/status_im/ui/screens/keycard/views.cljs @@ -12,7 +12,10 @@ [status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.hardwallet.pin.views :as pin.views] [status-im.ui.screens.keycard.styles :as styles] - [status-im.constants :as constants]) + [status-im.constants :as constants] + [status-im.ui.components.button :as button] + [status-im.hardwallet.login :as hardwallet.login] + [status-im.ui.screens.hardwallet.frozen-card.view :as frozen-card.view]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) ;; NOTE(Ferossgp): Seems like it should be in popover @@ -208,74 +211,199 @@ [photos/photo (multiaccounts/displayed-photo account) {:size (if small-screen? 45 61)}])})) -(defview login-pin [] +(defn access-is-reset [{:keys [hide-login-actions?]}] + [react/view + {:style {:flex 1 + :align-items :center}} + [react/view + {:style {:flex 1 + :align-items :center + :justify-content :center}} + [react/view + {:style + {:background-color colors/green-transparent-10 + :margin-bottom 32 + :width 40 + :height 40 + :align-items :center + :justify-content :center + :border-radius 20}} + [vector-icons/icon + :main-icons/check + {:color colors/green}]] + [react/text {:style {:typography :header}} + (i18n/label :t/keycard-access-reset)] + [react/text (i18n/label :t/keycard-can-use-with-new-passcode)]] + (when-not hide-login-actions? + [react/view + {:style {:width 160 + :margin-bottom 15}} + [button/button + {:type :main + :style {:align-self :stretch} + :container-style {:height 52} + :label (i18n/label :t/open) + :on-press #(re-frame/dispatch + [::hardwallet.login/login-after-reset])}]])]) + +(defn frozen-card [] + [frozen-card.view/frozen-card + {:show-dismiss-button? false}]) + +(defn blocked-card [] + [react/view {:style {:flex 1 + :align-items :center}} + [react/view {:margin-top 24 + :margin-horizontal 24 + :align-items :center} + [react/view {:background-color colors/red-transparent-10 + :width 32 + :height 32 + :border-radius 16 + :align-items :center + :justify-content :center} + [vector-icons/icon + :main-icons/cancel + {:color colors/red + :width 20 + :height 20}]] + [react/text {:style {:typography :title-bold + :margin-top 16 + :margin-bottom 8}} + (i18n/label :t/keycard-is-blocked-title)] + [react/text {:style {:color colors/gray + :text-align :center}} + (i18n/label :t/keycard-is-blocked-details)] + [react/text "\n"] + [react/nested-text + {:style {:color colors/gray + :text-align :center}} + (i18n/label :t/keycard-is-blocked-instructions) + [{} " "] + [{:style {:color colors/blue} + :on-press #(.openURL ^js react/linking "https://status.im/faq/#keycard")} + (i18n/label :t/learn-more)]]]]) + +(defn- step-view [step] + [react/view + {:style {:flex 1 + :justify-content :center + :align-items :center}} + [react/text {:style {:typography :title-bold :text-align :center}} + (i18n/label :t/keycard-reset-passcode)] + [react/text {:style {:color colors/gray}} + (i18n/label :t/keycard-enter-new-passcode {:step step})]]) + +(defview login-pin [{:keys [back-button-handler + hide-login-actions? + default-enter-step] + :or {default-enter-step :login}}] (letsubs [pin [:hardwallet/pin] enter-step [:hardwallet/pin-enter-step] status [:hardwallet/pin-status] error-label [:hardwallet/pin-error-label] - {:keys [name] :as account} [:multiaccounts/login] + login-multiaccount [:multiaccounts/login] + multiaccount [:multiaccount] small-screen? [:dimensions/small-screen?] retry-counter [:hardwallet/retry-counter]] - [react/view styles/container - [topbar/topbar - {:accessories [{:icon :main-icons/more - :handler #(re-frame/dispatch [:keycard.login.pin.ui/more-icon-pressed])}] - :navigation - {:icon :main-icons/back - :accessibility-label :back-button - :handler #(re-frame/dispatch [:keycard.login.pin.ui/cancel-pressed])}}] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :justify-content :center - :align-items :center - :height 140} - [react/view {:margin-horizontal 16 - :flex-direction :column} - [react/view {:justify-content :center + (let [{:keys [name] :as account} (or login-multiaccount multiaccount) + ;; TODO(rasom): this hack fixes state mess when more then two + ;; pin-view instances are used at the same time. Should be properly + ;; refactored instead + enter-step (or enter-step default-enter-step)] + [react/view styles/container + [topbar/topbar + {:accessories [(when-not hide-login-actions? + {:icon :main-icons/more + :handler #(re-frame/dispatch [:keycard.login.pin.ui/more-icon-pressed])})] + :content (cond + (= :reset enter-step) + [step-view 1] + + (= :reset-confirmation enter-step) + [step-view 2] + + (and (= :puk enter-step) + (not= :blocked-card status)) + [react/view + {:style {:flex 1 + :justify-content :center + :align-items :center}} + [react/text {:style {:color colors/gray}} + (i18n/label :t/enter-puk-code)]]) + :navigation + {:icon :main-icons/back + :accessibility-label :back-button + :handler #(re-frame/dispatch + [(or back-button-handler + :keycard.login.pin.ui/cancel-pressed)])}}] + [react/view {:flex 1 + :flex-direction :column + :justify-content :space-between + :align-items :center} + [react/view {:flex-direction :column + :justify-content :center :align-items :center - :flex-direction :row} - [react/view {:width (if small-screen? 50 69) - :height (if small-screen? 50 69) - :justify-content :center - :align-items :center} - [photo account small-screen?] - [react/view {:justify-content :center - :align-items :center - :width (if small-screen? 18 24) - :height (if small-screen? 18 24) - :border-radius (if small-screen? 18 24) - :position :absolute - :right 0 - :bottom 0 - :background-color colors/white - :border-width 1 - :border-color colors/black-transparent} - [react/image {:source (resources/get-image :keycard-key) - :style {:width (if small-screen? 6 8) - :height (if small-screen? 11 14)}}]]]] - [react/text {:style {:text-align :center - :margin-top (if small-screen? 8 12) - :color colors/black - :font-weight "500"} - :number-of-lines 1 - :ellipsize-mode :middle} - name]]] - [pin.views/pin-view - {:pin pin - :retry-counter retry-counter - :small-screen? small-screen? - :status status - :error-label error-label - :step enter-step - :save-password-checkbox? true}] - [react/view {:margin-bottom (if small-screen? 25 32)} - [react/touchable-highlight - {:on-press #(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed])} - [react/text {:style {:color colors/blue}} - (i18n/label :t/recover-key)]]]]])) + :height 140} + [react/view {:margin-horizontal 16 + :flex-direction :column} + [react/view {:justify-content :center + :align-items :center + :flex-direction :row} + [react/view {:width (if small-screen? 50 69) + :height (if small-screen? 50 69) + :justify-content :center + :align-items :center} + [photo account small-screen?] + [react/view {:justify-content :center + :align-items :center + :width (if small-screen? 18 24) + :height (if small-screen? 18 24) + :border-radius (if small-screen? 18 24) + :position :absolute + :right 0 + :bottom 0 + :background-color colors/white + :border-width 1 + :border-color colors/black-transparent} + [react/image {:source (resources/get-image :keycard-key) + :style {:width (if small-screen? 6 8) + :height (if small-screen? 11 14)}}]]]] + [react/text {:style {:text-align :center + :margin-top (if small-screen? 8 12) + :color colors/black + :font-weight "500"} + :number-of-lines 1 + :ellipsize-mode :middle} + name]]] + (cond + (= :after-unblocking status) + [access-is-reset + {:hide-login-actions? hide-login-actions?}] + + (= :frozen-card status) + [frozen-card] + + (= :blocked-card status) + [blocked-card] + + :else + [pin.views/pin-view + {:pin pin + :retry-counter retry-counter + :small-screen? small-screen? + :status status + :error-label error-label + :step enter-step + :save-password-checkbox? (not (contains? + #{:reset :reset-confirmation :puk} + enter-step))}]) + (when-not hide-login-actions? + [react/view {:margin-bottom (if small-screen? 25 32)} + [react/touchable-highlight + {:on-press #(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed])} + [react/text {:style {:color colors/blue}} + (i18n/label :t/recover-key)]]])]]))) (defn- more-sheet-content [] [react/view {:flex 1} diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index 7651b0fa40..52a2bb7e98 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -12,7 +12,8 @@ [status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover] [status-im.ui.screens.signing.views :as signing] [status-im.ui.screens.biometric.views :as biometric] - [status-im.ui.components.colors :as colors])) + [status-im.ui.components.colors :as colors] + [status-im.ui.screens.hardwallet.frozen-card.view :as frozen-card])) (defn hide-panel-anim [bottom-anim-value alpha-value window-height] @@ -137,6 +138,9 @@ (= :transaction-data view) [signing/transaction-data] + (= :frozen-card view) + [frozen-card/frozen-card] + :else [view])]]]]])))}))) diff --git a/src/status_im/ui/screens/routing/profile_stack.cljs b/src/status_im/ui/screens/routing/profile_stack.cljs index b06ce072ed..34ce668313 100644 --- a/src/status_im/ui/screens/routing/profile_stack.cljs +++ b/src/status_im/ui/screens/routing/profile_stack.cljs @@ -126,5 +126,7 @@ :component hardwallet.settings/keycard-settings} {:name :reset-card :component hardwallet.settings/reset-card} + {:name :keycard-pin + :component hardwallet.settings/reset-pin} {:name :enter-pin-settings :component hardwallet.pin/enter-pin}]]) diff --git a/src/status_im/ui/screens/signing/views.cljs b/src/status_im/ui/screens/signing/views.cljs index 35c51e52c2..f1715df52b 100644 --- a/src/status_im/ui/screens/signing/views.cljs +++ b/src/status_im/ui/screens/signing/views.cljs @@ -96,14 +96,15 @@ enter-step [:hardwallet/pin-enter-step] status [:hardwallet/pin-status] retry-counter [:hardwallet/retry-counter]] - [react/view - [pin.views/pin-view - {:pin pin - :retry-counter retry-counter - :step enter-step - :small-screen? small-screen? - :status status - :error-label error-label}]])) + (let [enter-step (or enter-step :sign)] + [react/view + [pin.views/pin-view + {:pin pin + :retry-counter retry-counter + :step enter-step + :small-screen? small-screen? + :status status + :error-label error-label}]]))) (defn sign-with-keycard-button [amount-error gas-error] diff --git a/src/status_im/ui/screens/wallet/accounts/views.cljs b/src/status_im/ui/screens/wallet/accounts/views.cljs index 533e6c74ea..33d7f095ee 100644 --- a/src/status_im/ui/screens/wallet/accounts/views.cljs +++ b/src/status_im/ui/screens/wallet/accounts/views.cljs @@ -12,7 +12,8 @@ [status-im.ui.screens.wallet.accounts.sheets :as sheets] [status-im.ui.screens.wallet.accounts.styles :as styles] [status-im.utils.utils :as utils.utils] - [status-im.wallet.utils :as wallet.utils]) + [status-im.wallet.utils :as wallet.utils] + [status-im.hardwallet.login :as hardwallet.login]) (:require-macros [status-im.utils.views :as views])) (views/defview account-card [{:keys [name color address type] :as account}] @@ -130,14 +131,21 @@ (views/letsubs [currency [:wallet/currency] portfolio-value [:portfolio-value] empty-balances? [:empty-balances?] + frozen-card? [:hardwallet/frozen-card?] {:keys [mnemonic]} [:multiaccount]] [reanimated/view {:style (styles/container {:minimized minimized})} - (when (and mnemonic minimized (not empty-balances?)) + (when (or + (and frozen-card? minimized) + (and mnemonic minimized (not empty-balances?))) [reanimated/view {:style (styles/accounts-mnemonic {:animation animation})} [react/touchable-highlight - {:on-press #(re-frame/dispatch [:navigate-to :profile-stack {:screen :backup-seed - :initial false}])} - [react/view {:flex-direction :row :align-items :center} + {:on-press #(re-frame/dispatch + (if frozen-card? + [::hardwallet.login/reset-pin] + [:navigate-to :profile-stack {:screen :backup-seed + :initial false}]))} + [react/view {:flex-direction :row + :align-items :center} [react/view {:width 14 :height 14 :background-color colors/gray @@ -151,7 +159,9 @@ "!"]] [react/text {:style {:color colors/gray} :accessibility-label :back-up-your-seed-phrase-warning} - (i18n/label :t/back-up-your-seed-phrase)]]]]) + (if frozen-card? + (i18n/label :t/your-card-is-frozen) + (i18n/label :t/back-up-your-seed-phrase))]]]]) [reanimated/view {:style (styles/value-container {:minimized minimized :animation animation}) diff --git a/src/status_im/ui/screens/wallet/add_new/views.cljs b/src/status_im/ui/screens/wallet/add_new/views.cljs index 3a1fcff1e7..4d020337dd 100644 --- a/src/status_im/ui/screens/wallet/add_new/views.cljs +++ b/src/status_im/ui/screens/wallet/add_new/views.cljs @@ -17,7 +17,8 @@ [status-im.ethereum.core :as ethereum] [status-im.utils.security :as security] [clojure.string :as string] - [status-im.utils.platform :as platform])) + [status-im.utils.platform :as platform] + [status-im.ui.components.bottom-panel.views :as bottom-panel])) (defn- request-camera-permissions [] (let [options {:handler :wallet.add-new/qr-scanner-result}] @@ -127,23 +128,40 @@ (re-frame/dispatch [:set-in [:add-account :private-key] (security/mask-data %)]))}])]) (defview pin [] - (letsubs [pin [:hardwallet/pin] - status [:hardwallet/pin-status] - error-label [:hardwallet/pin-error-label]] + (letsubs [pin [:hardwallet/pin] + status [:hardwallet/pin-status] + error-label [:hardwallet/pin-error-label] + retry-counter [:hardwallet/retry-counter]] [react/keyboard-avoiding-view {:style {:flex 1}} [topbar/topbar {:navigation :none :accessories [{:label :t/cancel - :handler #(re-frame/dispatch [:bottom-sheet/hide])}]}] + :handler #(re-frame/dispatch [:hardwallet/new-account-pin-sheet-hide])}]}] [pin.views/pin-view {:pin pin :status status + :retry-counter retry-counter :title-label :t/current-pin :description-label :t/current-pin-description :error-label error-label :step :export-key}]])) +(defn pin-sheet [] + (let [show-sheet? @(re-frame/subscribe [:hardwallet/new-account-sheet?]) + {window-height :height} @(re-frame/subscribe [:dimensions/window])] + [bottom-panel/bottom-panel + show-sheet? + (fn [_] + [react/view {:style + {:background-color colors/white + :border-top-right-radius 16 + :border-top-left-radius 16 + :padding-bottom 40 + :flex 1}} + [pin]]) + window-height])) + (defview add-account [] (letsubs [{:keys [type account] :as add-account} [:add-account] add-account-disabled? [:add-account-disabled?] @@ -164,7 +182,8 @@ :label :t/add-account :accessibility-label :add-account-add-account-button :on-press - (if keycard? + (if (and keycard? + (not= type :watch)) #(re-frame/dispatch [:hardwallet/new-account-pin-sheet {:view {:content pin :height 256}}]) @@ -177,5 +196,6 @@ (and (not keycard?) (not (spec/valid? ::multiaccounts.db/password - @entered-password)))))}}]])) + @entered-password)))))}}] + [pin-sheet]])) diff --git a/translations/en.json b/translations/en.json index 5c65f8a2e4..1e4cba14e2 100644 --- a/translations/en.json +++ b/translations/en.json @@ -543,11 +543,15 @@ "joined-group-chat-description": "You've joined {{group-name}} from invitation by {{username}}", "key": "Key", "keycard": "Keycard", + "keycard-access-reset": "Keycard access is reset", + "keycard-can-use-with-new-passcode": "You can use this card with your new passcode", "keycard-applet-install-instructions": "To install the applet please follow the instructions on https://github.com/status-im/keycard-cli#keycard-applet-installation", "keycard-blocked": "Keycard has been blocked.\nYou need to reset card to continue using it.", "keycard-cancel-setup-text": "This will cancel keycard setup. It's highly recommended to finish the setup in order to use keycard. Do you really want to cancel?", "keycard-cancel-setup-title": "Dangerous operation", "keycard-desc": "Own a Keycard? Store your keys on it; you’ll need it for transactions", + "keycard-reset-passcode": "Reset passcode", + "keycard-enter-new-passcode": "Enter new passcode {{step}}/2", "keycard-has-multiaccount-on-it": "This card is full. Each card can hold one main keypair", "keycard-onboarding-finishing-header": "Finishing up", "keycard-onboarding-intro-header": "Store your keys on Keycard", @@ -576,6 +580,13 @@ "keycard-recovery-phrase-confirmation-title": "Written the seed phrase down?", "keycard-recovery-success-header": "Your keys have been\n successfully recovered", "keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.", + "keycard-is-frozen-title": "Keycard is frozen", + "keycard-is-frozen-details": "To protect your assets, your card is frozen. Reset card access to unlock keys and send transactions. Create a new passcode and enter your PUK to access your account(s) on this card", + "keycard-is-frozen-reset": "Reset card access", + "your-card-is-frozen": "Your Keycard is frozen. Reset card access", + "keycard-is-blocked-title": "Keycard is blocked", + "keycard-is-blocked-details": "You can no longer use this card to access or sign for this account. There have been too many failed passcode and PUK attempts.", + "keycard-is-blocked-instructions": "To access your account reinstall Status and use a new Keycard, use a different wallet or reset Keycard manually.", "language": "Language", "learn-more": "Learn more", "learn-more-about-keycard": "Learn more about Keycard", @@ -772,7 +783,12 @@ "pin-changed": "6-digit passcode has been changed", "pin-code": "6-digit passcode", "pin-mismatch": "Wrong passcode", - "pin-retries-left": "You have {{number}} retries left", + "pin-retries-left": "{{number}} attemps left", + "pin-one-attempt-blocked-before": "Be careful, you have only", + "pin-one-attempt-frozen-before": "Be careful, you have only", + "pin-one-attempt": " one attempt ", + "pin-one-attempt-blocked-after": "before your Keycard gets blocked", + "pin-one-attempt-frozen-after": "before your Keycard gets frozen", "preview-privacy": "Preview privacy mode", "privacy": "Privacy", "privacy-and-security": "Privacy and security", @@ -789,7 +805,7 @@ "puk-and-pairing-codes-displayed": "PUK and pairing codes displayed", "puk-code": "PUK code", "puk-code-explanation": "If you forget your 6-digit passcode or enter it incorrectly 3 times, you'll need this code to unlock your card.", - "puk-mismatch": "PUK code does not match", + "puk-mismatch": "Wrong PUK code", "quiet-days": "{{quiet-days}} days", "quiet-hours": "{{quiet-hours}} hours", "re-encrypt-key": "Re-encrypt your keys",