From 1b836bf7ec208034d5bec55f879197e15056424e Mon Sep 17 00:00:00 2001 From: Dmitry Novotochinov Date: Tue, 4 Dec 2018 16:49:09 +0300 Subject: [PATCH] [#7006] login with keycard Signed-off-by: Dmitry Novotochinov --- STATUS_GO_VERSION | 2 +- mobile_files/package.json.orig | 2 +- mobile_files/yarn.lock | 7 +- .../status/ethereum/module/StatusModule.java | 20 ++ src/status_im/accounts/login/core.cljs | 59 +++-- src/status_im/events.cljs | 24 +- src/status_im/hardwallet/card.cljs | 11 +- src/status_im/hardwallet/core.cljs | 213 +++++++++++++++--- src/status_im/hardwallet/fx.cljs | 13 +- src/status_im/native_module/core.cljs | 4 + src/status_im/native_module/impl/module.cljs | 4 + src/status_im/ui/screens/accounts/styles.cljs | 2 +- src/status_im/ui/screens/accounts/subs.cljs | 5 + src/status_im/ui/screens/accounts/views.cljs | 12 +- src/status_im/ui/screens/events.cljs | 3 + .../ui/screens/hardwallet/connect/subs.cljs | 7 +- .../ui/screens/hardwallet/connect/views.cljs | 23 +- .../ui/screens/hardwallet/login/styles.cljs | 45 ++++ .../ui/screens/hardwallet/login/views.cljs | 48 ++++ .../ui/screens/hardwallet/pin/styles.cljs | 4 + .../ui/screens/hardwallet/pin/subs.cljs | 5 + .../ui/screens/hardwallet/pin/views.cljs | 57 +++-- .../ui/screens/hardwallet/settings/views.cljs | 40 ---- .../ui/screens/hardwallet/setup/views.cljs | 2 +- src/status_im/ui/screens/views.cljs | 46 ++-- test/cljs/status_im/test/sign_in/flow.cljs | 16 ++ translations/en.json | 12 + 27 files changed, 534 insertions(+), 152 deletions(-) create mode 100644 src/status_im/ui/screens/hardwallet/login/styles.cljs create mode 100644 src/status_im/ui/screens/hardwallet/login/views.cljs diff --git a/STATUS_GO_VERSION b/STATUS_GO_VERSION index cb665ce674..eeb6eac346 100644 --- a/STATUS_GO_VERSION +++ b/STATUS_GO_VERSION @@ -1 +1 @@ -0.19.0-beta.9 +0.20.0-beta.0 diff --git a/mobile_files/package.json.orig b/mobile_files/package.json.orig index e632aec504..096fd07b78 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.2", + "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#v2.3.4", "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 d1b8a75c02..f2041b1216 100644 --- a/mobile_files/yarn.lock +++ b/mobile_files/yarn.lock @@ -6012,9 +6012,10 @@ 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.2": - version "2.3.2" - resolved "git+https://github.com/status-im/react-native-status-keycard#4c2aada5dc3b9d106935ab1747ab33ad4e94547e" +"react-native-status-keycard@git+https://github.com/status-im/react-native-status-keycard.git#v2.3.4": + version "2.3.4" + resolved "git+https://github.com/status-im/react-native-status-keycard.git#04b0fb6fcbe588bd7fa076e1b0379e661bf1bddc" + react-native-svg@6.5.2: version "6.5.2" diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index b1e29a6b36..e482c143c0 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -413,6 +413,26 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL StatusThreadPoolExecutor.getInstance().execute(r); } + @ReactMethod + public void loginWithKeycard(final String whisperPrivateKey, final String encryptionPublicKey, final Callback callback) { + Log.d(TAG, "loginWithKeycard"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + + Runnable r = new Runnable() { + @Override + public void run() { + String result = Statusgo.LoginWithKeycard(whisperPrivateKey, encryptionPublicKey); + + callback.invoke(result); + } + }; + + StatusThreadPoolExecutor.getInstance().execute(r); + } + @ReactMethod public void createAccount(final String password, final Callback callback) { Log.d(TAG, "createAccount"); diff --git a/src/status_im/accounts/login/core.cljs b/src/status_im/accounts/login/core.cljs index e739c090c5..c205127d08 100644 --- a/src/status_im/accounts/login/core.cljs +++ b/src/status_im/accounts/login/core.cljs @@ -41,8 +41,13 @@ ;;;; Handlers (fx/defn login [cofx] - (let [{:keys [address password]} (accounts.db/credentials cofx)] - {:accounts.login/login [address password]})) + (if (get-in cofx [:db :hardwallet :whisper-private-key]) + {:hardwallet/login-with-keycard (-> cofx + (get-in [:db :hardwallet]) + (select-keys [:whisper-private-key :encryption-public-key]) + (assoc :on-result #(re-frame/dispatch [:accounts.login.callback/login-success %])))} + (let [{:keys [address password]} (accounts.db/credentials cofx)] + {:accounts.login/login [address password]}))) (fx/defn initialize-wallet [cofx] (fx/merge cofx @@ -88,7 +93,14 @@ (if success (fx/merge cofx - {:db (dissoc db :accounts/login) + {:db (-> db + (dissoc :accounts/login) + (update :hardwallet dissoc + :on-card-read + :card-read-in-progress? + :pin + :whisper-private-key + :encryption-public-key)) :web3/set-default-account [web3 address] :web3/fetch-node-version [web3 #(re-frame/dispatch @@ -202,18 +214,35 @@ (unknown-realm-error {:realm-error realm-error :erase-button erase-button}))))) -(fx/defn open-login [{:keys [db]} address photo-path name] - {:db (-> db - (update :accounts/login assoc - :address address - :photo-path photo-path - :name name) - (update :accounts/login dissoc - :error - :password)) - :keychain/can-save-user-password? nil - :keychain/get-user-password [address - #(re-frame/dispatch [:accounts.login.callback/get-user-password-success %])]}) +(fx/defn open-keycard-login + [{:keys [db] :as cofx}] + (let [navigation-stack (:navigation-stack db)] + (fx/merge cofx + {:db (assoc-in db [:hardwallet :pin :enter-step] :login)} + (if (empty? navigation-stack) + (navigation/navigate-to-cofx :accounts nil) + (navigation/navigate-to-cofx :enter-pin nil))))) + +(fx/defn get-user-password + [_ address] + {:keychain/can-save-user-password? nil + :keychain/get-user-password [address + #(re-frame/dispatch [:accounts.login.callback/get-user-password-success %])]}) + +(fx/defn open-login [{:keys [db] :as cofx} address photo-path name] + (let [keycard-account? (get-in db [:accounts/accounts address :keycard-instance-uid])] + (fx/merge cofx + {:db (-> db + (update :accounts/login assoc + :address address + :photo-path photo-path + :name name) + (update :accounts/login dissoc + :error + :password))} + (if keycard-account? + (open-keycard-login) + (get-user-password address))))) (fx/defn open-login-callback [{:keys [db] :as cofx} password] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 7a3e4eef08..bb76f9119e 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -891,8 +891,8 @@ (handlers/register-handler-fx :hardwallet.callback/on-get-application-info-success - (fn [cofx [_ info]] - (hardwallet/on-get-application-info-success cofx info))) + (fn [cofx [_ info on-success]] + (hardwallet/on-get-application-info-success cofx info on-success))) (handlers/register-handler-fx :hardwallet.callback/on-get-application-info-error @@ -1012,6 +1012,26 @@ (fn [cofx [_ error]] (hardwallet/on-delete-error cofx error))) +(handlers/register-handler-fx + :hardwallet.callback/on-get-keys-success + (fn [cofx [_ data]] + (hardwallet/on-get-keys-success cofx data))) + +(handlers/register-handler-fx + :hardwallet/auto-login + (fn [cofx _] + (hardwallet/login-with-keycard cofx true))) + +(handlers/register-handler-fx + :hardwallet/login-with-keycard + (fn [cofx _] + (hardwallet/login-with-keycard cofx false))) + +(handlers/register-handler-fx + :hardwallet.callback/on-get-keys-error + (fn [cofx [_ error]] + (hardwallet/on-get-keys-error cofx error))) + (handlers/register-handler-fx :hardwallet.ui/status-hardwallet-option-pressed (fn [cofx _] diff --git a/src/status_im/hardwallet/card.cljs b/src/status_im/hardwallet/card.cljs index a8b7a6ede8..5d3570ba7b 100644 --- a/src/status_im/hardwallet/card.cljs +++ b/src/status_im/hardwallet/card.cljs @@ -50,10 +50,10 @@ "keyCardOnDisconnected" #(re-frame/dispatch [:hardwallet.callback/on-card-disconnected %]))}]))) -(defn get-application-info [pairing] +(defn get-application-info [{:keys [pairing on-success]}] (.. keycard (getApplicationInfo (str pairing)) - (then #(re-frame/dispatch [:hardwallet.callback/on-get-application-info-success %])) + (then #(re-frame/dispatch [:hardwallet.callback/on-get-application-info-success % on-success])) (catch #(re-frame/dispatch [:hardwallet.callback/on-get-application-info-error (error-object->map %)])))) (defn install-applet-and-init-card [] @@ -133,3 +133,10 @@ (unpairAndDelete pairing pin) (then #(re-frame/dispatch [:hardwallet.callback/on-delete-success %])) (catch #(re-frame/dispatch [:hardwallet.callback/on-delete-error (error-object->map %)]))))) + +(defn get-keys + [{:keys [pairing pin]}] + (.. keycard + (getKeys pairing pin) + (then #(re-frame/dispatch [:hardwallet.callback/on-get-keys-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-get-keys-error (error-object->map %)])))) diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index ed89319188..365f871ad8 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -11,7 +11,8 @@ [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])) + [clojure.string :as string] + [status-im.accounts.login.core :as accounts.login])) (def default-pin "000000") @@ -81,31 +82,63 @@ [{:keys [db]}] {:db (assoc-in db [:hardwallet :reset-card :disabled?] false)}) +(defn enter-pin-screen-did-load + [{:keys [db]}] + {:db (-> db + (assoc-in [:hardwallet :pin :login] []) + (assoc-in [:hardwallet :pin :current] []))}) + +(defn hardwallet-connect-screen-did-load + [{:keys [db]}] + {:db (assoc-in db [:hardwallet :card-read-in-progress?] false)}) + +(defn accounts-screen-did-load + [{:keys [db]}] + {:db (assoc-in db [:hardwallet :setup-step] nil)}) + (fx/defn on-register-card-events [{:keys [db]} listeners] {:db (update-in db [:hardwallet :listeners] merge listeners)}) +(fx/defn clear-on-card-read + [{:keys [db]}] + {:db (assoc-in db [:hardwallet :on-card-read] nil)}) + (fx/defn on-get-application-info-success - [{:keys [db] :as cofx} info] + [{:keys [db]} info on-success] (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 + connect-screen? (= (:view-id db) :hardwallet-connect) + enter-step (if (zero? pin-retry-counter) + :puk + (get-in db [:hardwallet :pin :enter-step]))] + (fx/merge {} {:db (-> db - (assoc-in [:hardwallet :pin :enter-step] enter-step') + (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))))) + (when-not connect-screen? + (clear-on-card-read)) + (if (zero? puk-retry-counter) + (navigation/navigate-to-cofx :keycard-settings nil) + (when on-success + #(assoc % :dispatch [on-success])))))) (fx/defn on-get-application-info-error - [{:keys [db]} error] + [{:keys [db] :as cofx} error] (log/debug "[hardwallet] application info error " error) - {:db (-> db - (assoc-in [:hardwallet :application-info-error] error) - (assoc-in [:hardwallet :application-info :applet-installed?] false))}) + (let [on-card-read (get-in db [:hardwallet :on-card-read]) + login? (= on-card-read :hardwallet/login-with-keycard)] + (if login? + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-read] nil) + :utils/show-popup {:title (i18n/label :t/wrong-card) + :content (i18n/label :t/wrong-card-text)}} + (navigation/navigate-to-cofx :accounts nil)) + {:db (-> db + (assoc-in [:hardwallet :application-info-error] error) + (assoc-in [:hardwallet :application-info :applet-installed?] false))}))) (fx/defn set-nfc-support [{:keys [db]} supported?] @@ -122,12 +155,12 @@ :db (-> db (assoc-in [:hardwallet :setup-step] :begin) (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :on-card-read] 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 -) + (navigation/navigate-to-cofx cofx :home nil)) (fx/defn change-pin-pressed [{:keys [db] :as cofx}] @@ -323,7 +356,7 @@ [{:keys [db] :as cofx}] (let [pairing (get-pairing db)] (fx/merge cofx - {:hardwallet/get-application-info pairing + {:hardwallet/get-application-info {:pairing pairing} :db (-> db (update-in [:hardwallet :pin] merge {:status nil :enter-step :original @@ -336,13 +369,13 @@ [{:keys [db]} error] (let [pairing (get-pairing db)] (log/debug "[hardwallet] unblock pin error" error) - {:hardwallet/get-application-info pairing + {:hardwallet/get-application-info {:pairing 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}) + {:hardwallet/get-application-info {:pairing pairing}}) (fx/defn on-verify-pin-success [{:keys [db] :as cofx}] @@ -361,7 +394,7 @@ [{:keys [db]} error] (let [pairing (get-pairing db)] (log/debug "[hardwallet] verify pin error" error) - {:hardwallet/get-application-info pairing + {:hardwallet/get-application-info {:pairing pairing} :db (update-in db [:hardwallet :pin] merge {:status :error :error-label :t/pin-mismatch :enter-step :current @@ -449,11 +482,15 @@ (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))) + (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) + need-update? (update-in [:hardwallet :pin enter-step] (fnil conj []) number))} + (when need-update? + (handle-pin-input enter-step))))) (defn- pin-enter-error [fx error-label] (update-in fx [:db :hardwallet :pin] merge {:status :error @@ -462,7 +499,26 @@ :original [] :confirmation []})) +(fx/defn get-keys-from-keycard + [{:keys [db]}] + (let [{:keys [pairing]} (get-in db [:hardwallet :secrets]) + pin (string/join (get-in db [:hardwallet :pin :login]))] + (when (and pairing + (not (empty? pin))) + {:db (-> db + (assoc-in [:hardwallet :pin :status] :verifying)) + :hardwallet/get-keys {:pairing pairing + :pin pin}}))) + +(fx/defn wait-for-card-tap + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :on-card-read] :hardwallet/login-with-keycard))} + (navigation/navigate-to-cofx :hardwallet-connect nil))) + ; PIN enter steps: +; login - PIN is used to login ; 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 @@ -473,6 +529,10 @@ numbers-entered (count pin)] (cond-> {:db (assoc-in db [:hardwallet :pin :status] nil)} + (and (= enter-step :login) + (= 6 numbers-entered)) + (wait-for-card-tap) + (and (= enter-step :original) (= pin-code-length numbers-entered)) (proceed-to-pin-confirmation) @@ -517,24 +577,71 @@ {: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]}) + [_ event] + {:dispatch [event]}) + +(defn login-with-keycard + [{:keys [db] :as cofx} auto-login?] + (let [pairing (get-pairing db) + account-login-address (get-in db [:accounts/login :address]) + account-was-manually-selected? account-login-address + account-instance-uid (get-in db [:accounts/accounts account-login-address :keycard-instance-uid]) + keycard-instance-uid (get-in db [:hardwallet :application-info :instance-uid]) + account-mismatch? (if account-was-manually-selected? + (not= account-instance-uid keycard-instance-uid) + (->> (:accounts/accounts db) + vals + (filter #(= keycard-instance-uid (:keycard-instance-uid %))) + empty?))] + + (cond + (empty? keycard-instance-uid) + (fx/merge cofx + {:utils/show-popup {:title (i18n/label :t/no-account-on-card) + :content (i18n/label :t/no-account-on-card-text)}} + (navigation/navigate-to-cofx :accounts nil)) + + account-mismatch? + (fx/merge cofx + {:db (dissoc db :accounts/login) + :utils/show-popup {:title (i18n/label (if auto-login? :t/account-not-listed :t/wrong-card)) + :content (i18n/label (if auto-login? :t/account-not-listed-text :t/wrong-card-text))}} + (navigation/navigate-to-cofx :accounts nil)) + + (empty? pairing) + {:utils/show-popup {:title (i18n/label :t/error) + :content (i18n/label :t/no-pairing-on-device)}} + + auto-login? + (navigation/navigate-to-cofx cofx :enter-pin nil) + + :else + (get-keys-from-keycard cofx)))) (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]) + pin-enter-step (get-in db [:hardwallet :pin :enter-step]) + login? (= :login pin-enter-step) + accounts-screen? (= :accounts (:view-id db)) + auto-login? accounts-screen? on-card-connected (get-in db [:hardwallet :on-card-connected]) + on-card-read (if auto-login? + :hardwallet/auto-login + (get-in db [:hardwallet :on-card-read])) 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 :card-read-in-progress?] (boolean on-card-read)) true (assoc-in [:hardwallet :return-to-step] nil)) - :hardwallet/get-application-info pairing} - (when on-card-connected + :hardwallet/get-application-info {:pairing pairing + :on-success on-card-read}} + (when (and on-card-connected + (not login?)) (dispatch-on-card-connected-event on-card-connected)) (when setup-running? (navigation/navigate-to-cofx :hardwallet-setup nil))))) @@ -545,7 +652,9 @@ (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)} + {:db (-> db + (assoc-in [:hardwallet :card-connected?] false) + (assoc-in [:hardwallet :card-read-in-progress?] false))} (when (or setup-running? on-card-connected) (navigation/navigate-to-cofx :hardwallet-connect nil))))) @@ -683,7 +792,7 @@ :keycard-paired-on paired-on} encryption-public-key {:seed-backed-up? true - :login? false}) + :login? true}) (navigation/navigate-to-cofx :hardwallet-success nil)))) (fx/defn on-generate-and-load-key-success @@ -692,9 +801,9 @@ whisper-private-key whisper-address wallet-address + instance-uid encryption-public-key]} (js->clj data :keywordize-keys true) - whisper-public-key' (str "0x" whisper-public-key) - keycard-instance-uid (get-in db [:hardwallet :application-info :instance-uid])] + whisper-public-key' (str "0x" whisper-public-key)] (fx/merge cofx {:db (-> db (assoc-in [:hardwallet :whisper-public-key] whisper-public-key') @@ -702,7 +811,9 @@ (assoc-in [:hardwallet :whisper-address] whisper-address) (assoc-in [:hardwallet :wallet-address] wallet-address) (assoc-in [:hardwallet :encryption-public-key] encryption-public-key) - (assoc-in [:hardwallet :keycard-instance-uid] keycard-instance-uid) + (assoc-in [:hardwallet :keycard-instance-uid] instance-uid) + (update :hardwallet dissoc :recovery-phrase) + (update-in [:hardwallet :secrets] dissoc :pin :puk :password) (assoc :node/on-ready :create-keycard-account) (assoc :accounts/new-installation-id (random-guid-generator)) (update-in [:hardwallet :secrets] dissoc :mnemonic))} @@ -716,3 +827,39 @@ (assoc-in [:hardwallet :return-to-step] :recovery-phrase) (assoc-in [:hardwallet :setup-error] error))} (process-error code))) + +(fx/defn on-get-keys-success + [{:keys [db] :as cofx} data] + (let [{:keys [whisper-public-key + whisper-private-key + wallet-address + encryption-public-key]} (js->clj data :keywordize-keys true) + whisper-public-key' (str "0x" whisper-public-key) + {:keys [photo-path name]} (get-in db [:accounts/accounts wallet-address]) + password encryption-public-key] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :status] nil) + (assoc-in [:hardwallet :whisper-public-key] whisper-public-key') + (assoc-in [:hardwallet :whisper-private-key] whisper-private-key) + (assoc-in [:hardwallet :wallet-address] wallet-address) + (assoc-in [:hardwallet :encryption-public-key] encryption-public-key) + (update :accounts/login assoc + :password password + :address wallet-address + :photo-path photo-path + :name name))} + (accounts.login/user-login true)))) + +(fx/defn on-get-keys-error + [{:keys [db] :as cofx} error] + (log/debug "[hardwallet] get keys error: " error) + (let [tag-was-lost? (= "Tag was lost." (:error error))] + (if tag-was-lost? + {:utils/show-popup {:title (i18n/label :t/error) + :content (i18n/label :t/tag-was-lost)}} + (fx/merge cofx + {:hardwallet/get-application-info {:pairing (get-pairing db)} + :db (update-in db [:hardwallet :pin] merge {:status :error + :error-label :t/pin-mismatch})} + (navigation/navigate-to-cofx :enter-pin nil))))) diff --git a/src/status_im/hardwallet/fx.cljs b/src/status_im/hardwallet/fx.cljs index e0594280f3..ce75ef6986 100644 --- a/src/status_im/hardwallet/fx.cljs +++ b/src/status_im/hardwallet/fx.cljs @@ -1,8 +1,9 @@ (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.utils.datetime :as utils.datetime])) + [status-im.utils.datetime :as utils.datetime] + [status-im.native-module.core :as statusgo] + [status-im.react-native.js-dependencies :as js-dependencies])) (re-frame/reg-fx :hardwallet/get-application-info @@ -68,6 +69,10 @@ :hardwallet/unpair-and-delete card/unpair-and-delete) +(re-frame/reg-fx + :hardwallet/get-keys + card/get-keys) + ;TODO remove when keycard login will be ready (re-frame/reg-fx :hardwallet/persist-pairing @@ -98,3 +103,7 @@ (.. js-dependencies/react-native -AsyncStorage (removeItem "status-keycard-pairing")))) + +(re-frame/reg-fx + :hardwallet/login-with-keycard + statusgo/login-with-keycard) diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 5878044157..02b600d834 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -28,6 +28,10 @@ (defn verify [address password callback] (native-module/verify address password callback)) +(defn login-with-keycard + [{:keys [whisper-private-key encryption-public-key on-result]}] + (native-module/login-with-keycard whisper-private-key encryption-public-key on-result)) + (defn set-soft-input-mode [mode] (native-module/set-soft-input-mode mode)) diff --git a/src/status_im/native_module/impl/module.cljs b/src/status_im/native_module/impl/module.cljs index ef8726db8b..f96a32b4bf 100644 --- a/src/status_im/native_module/impl/module.cljs +++ b/src/status_im/native_module/impl/module.cljs @@ -75,6 +75,10 @@ (when (and @node-started status) (.verify status address password on-result))) +(defn login-with-keycard [whisper-private-key encryption-public-key on-result] + (when (and @node-started status) + (.loginWithKeycard status whisper-private-key encryption-public-key on-result))) + (defn set-soft-input-mode [mode] (when status (.setSoftInputMode status mode))) diff --git a/src/status_im/ui/screens/accounts/styles.cljs b/src/status_im/ui/screens/accounts/styles.cljs index 99c9c5a607..45fe8ba030 100644 --- a/src/status_im/ui/screens/accounts/styles.cljs +++ b/src/status_im/ui/screens/accounts/styles.cljs @@ -37,7 +37,7 @@ (def account-badge-text-view {:margin-left 16 - :margin-right 21 + :margin-right 31 :flex-shrink 1}) (def account-badge-text diff --git a/src/status_im/ui/screens/accounts/subs.cljs b/src/status_im/ui/screens/accounts/subs.cljs index c8ad0708cf..e92a7b0c46 100644 --- a/src/status_im/ui/screens/accounts/subs.cljs +++ b/src/status_im/ui/screens/accounts/subs.cljs @@ -10,6 +10,11 @@ (fn [db] (:accounts/accounts db))) +(re-frame/reg-sub + :accounts/login + (fn [db] + (:accounts/login db))) + (re-frame/reg-sub :account/account (fn [db] diff --git a/src/status_im/ui/screens/accounts/views.cljs b/src/status_im/ui/screens/accounts/views.cljs index d5c154bd83..a402aeb61e 100644 --- a/src/status_im/ui/screens/accounts/views.cljs +++ b/src/status_im/ui/screens/accounts/views.cljs @@ -14,14 +14,18 @@ [status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.screens.privacy-policy.views :as privacy-policy])) -(defn account-view [{:keys [address photo-path name public-key]}] +(defn account-view [{:keys [address photo-path name public-key keycard-instance-uid]}] [react/touchable-highlight {:on-press #(re-frame/dispatch [:accounts.login.ui/account-selected address photo-path name])} [react/view styles/account-view [photos/photo photo-path {:size styles/account-image-size}] [react/view styles/account-badge-text-view - [react/text {:style styles/account-badge-text - :numberOfLines 1} - name] + [react/view {:flex-direction :row} + [react/text {:style styles/account-badge-text + :numberOfLines 1} + name] + (when keycard-instance-uid + [icons/icon :icons/hardwallet {:color colors/blue + :container-style {:margin-left 7}}])] [react/text {:style styles/account-badge-pub-key-text :ellipsize-mode :middle :numberOfLines 1} diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index e1460dc26d..5c9253fb11 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -176,4 +176,7 @@ #(case view-id :keycard-settings (hardwallet/settings-screen-did-load %) :reset-card (hardwallet/reset-card-screen-did-load %) + :enter-pin (hardwallet/enter-pin-screen-did-load %) + :hardwallet-connect (hardwallet/hardwallet-connect-screen-did-load %) + :accounts (hardwallet/accounts-screen-did-load %) nil)))) diff --git a/src/status_im/ui/screens/hardwallet/connect/subs.cljs b/src/status_im/ui/screens/hardwallet/connect/subs.cljs index c3e7232cc5..0743f57f8c 100644 --- a/src/status_im/ui/screens/hardwallet/connect/subs.cljs +++ b/src/status_im/ui/screens/hardwallet/connect/subs.cljs @@ -4,4 +4,9 @@ (re-frame/reg-sub :hardwallet/nfc-enabled? (fn [db] - (get-in db [:hardwallet :nfc-enabled?]))) \ No newline at end of file + (get-in db [:hardwallet :nfc-enabled?]))) + +(re-frame/reg-sub + :hardwallet/card-read-in-progress? + (fn [db] + (get-in db [:hardwallet :card-read-in-progress?] false))) diff --git a/src/status_im/ui/screens/hardwallet/connect/views.cljs b/src/status_im/ui/screens/hardwallet/connect/views.cljs index d195886fdd..c58adf205c 100644 --- a/src/status_im/ui/screens/hardwallet/connect/views.cljs +++ b/src/status_im/ui/screens/hardwallet/connect/views.cljs @@ -11,15 +11,20 @@ [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors])) -(defn nfc-enabled [] - [react/view styles/nfc-enabled-container - [react/view - [react/image {:source (:hold-card-animation resources/ui) - :style styles/phone-nfc-on-image}]] - [react/view styles/turn-nfc-text-container - [react/text {:style styles/status-hardwallet-text - :number-of-lines 2} - (i18n/label :t/hold-card)]]]) +(defview nfc-enabled [] + (letsubs [card-read-in-progress? [:hardwallet/card-read-in-progress?]] + [react/view styles/nfc-enabled-container + [react/view + [react/image {:source (:hold-card-animation resources/ui) + :style styles/phone-nfc-on-image}]] + [react/view styles/turn-nfc-text-container + [react/text {:style styles/status-hardwallet-text + :number-of-lines 2} + (i18n/label :t/hold-card)]] + (when card-read-in-progress? + [react/view {:margin-top 35} + [react/activity-indicator {:animating true + :size :large}]])])) (defn nfc-disabled [] [react/view styles/nfc-disabled-container diff --git a/src/status_im/ui/screens/hardwallet/login/styles.cljs b/src/status_im/ui/screens/hardwallet/login/styles.cljs new file mode 100644 index 0000000000..e38facec8f --- /dev/null +++ b/src/status_im/ui/screens/hardwallet/login/styles.cljs @@ -0,0 +1,45 @@ +(ns status-im.ui.screens.hardwallet.login.styles + (:require [status-im.ui.components.colors :as colors])) + +(def container + {:flex 1 + :background-color colors/white}) + +(def inner-container + {:flex-direction :column + :flex 1 + :align-items :center + :justify-content :space-between}) + +(def login-view + {:flex 1 + :margin-horizontal 16}) + +(def login-badge-container + {:margin-top 24}) + +(def processing-view + {:flex 1 + :align-items :center + :justify-content :center}) + +(def sign-you-in + {:margin-top 16 + :font-size 13 + :color colors/black}) + +(def bottom-button-container + {:flex-direction :row + :margin-horizontal 12 + :margin-vertical 15 + :align-items :center}) + +(def login-badge + {:align-items :center}) + +(def login-badge-image-size 56) + +(def login-badge-name + {:font-size 15 + :color colors/black + :margin-top 8}) diff --git a/src/status_im/ui/screens/hardwallet/login/views.cljs b/src/status_im/ui/screens/hardwallet/login/views.cljs new file mode 100644 index 0000000000..705c599732 --- /dev/null +++ b/src/status_im/ui/screens/hardwallet/login/views.cljs @@ -0,0 +1,48 @@ +(ns status-im.ui.screens.hardwallet.login.views + (:require-macros [status-im.utils.views :refer [defview letsubs]]) + (:require [status-im.ui.screens.hardwallet.pin.views :as pin.views] + [status-im.ui.screens.hardwallet.connect.views :as connect.views] + [status-im.ui.screens.hardwallet.components :as components] + [status-im.ui.screens.hardwallet.login.styles :as styles] + [status-im.ui.screens.hardwallet.settings.views :as settings.views] + [status-im.ui.components.react :as react] + [status-im.ui.components.styles :as components.styles] + [status-im.ui.components.status-bar.view :as status-bar] + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.ui.components.common.common :as components.common] + [status-im.ui.screens.chat.photos :as photos] + [status-im.i18n :as i18n] + [status-im.ui.components.toolbar.actions :as toolbar.actions] + [status-im.ui.components.react :as react.components] + [re-frame.core :as re-frame])) + +(defview hardwallet-login [] + (letsubs [{:keys [photo-path name processing]} [:get :accounts/login] + nfc-enabled? [:hardwallet/nfc-enabled?]] + [react/keyboard-avoiding-view styles/container + [status-bar/status-bar] + [toolbar/toolbar + nil + [toolbar/nav-button + (toolbar.actions/back #(re-frame/dispatch [:navigate-to-clean :accounts]))] + [toolbar/content-title (i18n/label :t/sign-in-to-status)]] + [components.common/separator] + [react/view styles/login-view + [react/view styles/login-badge-container + [react/view styles/login-badge + [photos/photo photo-path {:size styles/login-badge-image-size}] + [react/view + [react/text {:style styles/login-badge-name + :numberOfLines 1} + name]]] + [react/view + (if nfc-enabled? + [connect.views/nfc-enabled] + [connect.views/nfc-disabled])]]] + (when processing + [react/view styles/processing-view + [react.components/activity-indicator {:animating true}] + [react/i18n-text {:style styles/sign-you-in :key :sign-you-in}]]) + (when-not processing + [react/view {:style styles/bottom-button-container} + [react/view {:style {:flex 1}}]])])) \ No newline at end of file diff --git a/src/status_im/ui/screens/hardwallet/pin/styles.cljs b/src/status_im/ui/screens/hardwallet/pin/styles.cljs index c38ffb5418..0b463dd50d 100644 --- a/src/status_im/ui/screens/hardwallet/pin/styles.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/styles.cljs @@ -2,6 +2,10 @@ (:require-macros [status-im.utils.styles :refer [defstyle]]) (:require [status-im.ui.components.colors :as colors])) +(def container + {:flex 1 + :background-color colors/white}) + (defstyle pin-container {:flex 1 :flex-direction :column diff --git a/src/status_im/ui/screens/hardwallet/pin/subs.cljs b/src/status_im/ui/screens/hardwallet/pin/subs.cljs index fe0f622ee0..22803eb5aa 100644 --- a/src/status_im/ui/screens/hardwallet/pin/subs.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/subs.cljs @@ -6,6 +6,11 @@ (fn [db] (get-in db [:hardwallet :pin :original]))) +(re-frame/reg-sub + :hardwallet/login-pin + (fn [db] + (get-in db [:hardwallet :pin :login]))) + (re-frame/reg-sub :hardwallet/pin-confirmation (fn [db] diff --git a/src/status_im/ui/screens/hardwallet/pin/views.cljs b/src/status_im/ui/screens/hardwallet/pin/views.cljs index e4b6e2ed49..9a17fcd424 100644 --- a/src/status_im/ui/screens/hardwallet/pin/views.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/views.cljs @@ -6,7 +6,10 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.react :as react] [status-im.ui.screens.hardwallet.pin.styles :as styles] - [status-im.ui.screens.hardwallet.components :as components])) + [status-im.ui.screens.hardwallet.components :as components] + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.ui.components.status-bar.view :as status-bar] + [status-im.ui.components.toolbar.actions :as actions])) (defn numpad-button [n step enabled?] [react/touchable-highlight @@ -98,22 +101,40 @@ [pin-indicators pin])) [numpad step enabled?]]]])) -(defview main [] - (letsubs [original [:hardwallet/original-pin] - confirmation [:hardwallet/pin-confirmation] - enter-step [:hardwallet/pin-enter-step] +(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]] - (case enter-step - :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 + [react/view {:flex 1 + :background-color colors/white} + [status-bar/status-bar] + [toolbar/toolbar nil toolbar/default-nav-back nil] + (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 + :original :t/create-pin + :confirmation :t/repeat-pin + :t/current-pin) + :description-label (case step + :current :t/current-pin-description + :login :t/login-pin-description + :t/new-pin-description) + :step step + :status status + :error-label error-label}])])) \ No newline at end of file diff --git a/src/status_im/ui/screens/hardwallet/settings/views.cljs b/src/status_im/ui/screens/hardwallet/settings/views.cljs index 0d83242d2c..ed94eed0a0 100644 --- a/src/status_im/ui/screens/hardwallet/settings/views.cljs +++ b/src/status_im/ui/screens/hardwallet/settings/views.cljs @@ -13,46 +13,6 @@ [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} diff --git a/src/status_im/ui/screens/hardwallet/setup/views.cljs b/src/status_im/ui/screens/hardwallet/setup/views.cljs index c278ecb2de..78ddf0b741 100644 --- a/src/status_im/ui/screens/hardwallet/setup/views.cljs +++ b/src/status_im/ui/screens/hardwallet/setup/views.cljs @@ -378,7 +378,7 @@ :no-slots [no-slots] :card-already-linked [card-already-linked] :pairing [pairing] - :pin [pin.views/main] + :pin [pin.views/enter-pin] :recovery-phrase [recovery-phrase] :recovery-phrase-confirm-word1 [recovery-phrase-confirm-word step] :recovery-phrase-confirm-word2 [recovery-phrase-confirm-word step] diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 12dca21040..5851442c76 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -53,7 +53,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.hardwallet.settings.views :refer [keycard-settings 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]] @@ -61,6 +61,7 @@ [status-im.ui.screens.accounts.create.views :refer [create-account]] [status-im.ui.screens.hardwallet.authentication-method.views :refer [hardwallet-authentication-method]] [status-im.ui.screens.hardwallet.connect.views :refer [hardwallet-connect]] + [status-im.ui.screens.hardwallet.pin.views :refer [enter-pin]] [status-im.ui.screens.hardwallet.setup.views :refer [hardwallet-setup]] [status-im.ui.screens.hardwallet.success.views :refer [hardwallet-success]] [status-im.ui.screens.profile.seed.views :refer [backup-seed]] @@ -144,10 +145,12 @@ config/hardwallet-enabled? (assoc :hardwallet-authentication-method hardwallet-authentication-method :hardwallet-connect hardwallet-connect + :enter-pin enter-pin :hardwallet-setup hardwallet-setup :hardwallet-success hardwallet-success))) (cond-> {:headerMode "none"} - (#{:intro :login :progress} view-id) + ; add view-id here if you'd like that view to be first view when app is started + (#{:intro :login :progress :accounts} view-id) (assoc :initialRouteName (name view-id))))} :chat-stack {:screen @@ -155,23 +158,28 @@ (stack-screens {:main-stack {:screens - {:home (main-tabs/get-main-tab :home) - :chat chat - :profile profile.contact/profile - :new add-new - :new-chat new-chat - :qr-scanner qr-scanner - :new-group new-group - :add-participants-toggle-list add-participants-toggle-list - :contact-toggle-list contact-toggle-list - :group-chat-profile profile.group-chat/group-chat-profile - :new-public-chat new-public-chat - :open-dapp open-dapp - :dapp-description dapp-description - :browser browser - :stickers stickers/packs - :stickers-pack stickers/pack - :login login} + (cond-> + {:home (main-tabs/get-main-tab :home) + :chat chat + :profile profile.contact/profile + :new add-new + :new-chat new-chat + :qr-scanner qr-scanner + :new-group new-group + :add-participants-toggle-list add-participants-toggle-list + :contact-toggle-list contact-toggle-list + :group-chat-profile profile.group-chat/group-chat-profile + :new-public-chat new-public-chat + :open-dapp open-dapp + :dapp-description dapp-description + :browser browser + :stickers stickers/packs + :stickers-pack stickers/pack + :login login} + + config/hardwallet-enabled? + (assoc :hardwallet-connect hardwallet-connect + :enter-pin enter-pin)) :config {:headerMode "none" :initialRouteName "home"}} diff --git a/test/cljs/status_im/test/sign_in/flow.cljs b/test/cljs/status_im/test/sign_in/flow.cljs index 3cdcf413ad..ba54721111 100644 --- a/test/cljs/status_im/test/sign_in/flow.cljs +++ b/test/cljs/status_im/test/sign_in/flow.cljs @@ -233,3 +233,19 @@ (is (contains? efx :utils/show-popup))) (testing "Logout." (is (= [:accounts.logout.ui/logout-confirmed] (:dispatch efx))))))) + +(deftest login + (testing "login with keycard" + (let [wpk "c56c7ac797c27b3790ce02c2459e9957c5d20d7a2c55320535526ce9e4dcbbef" + epk "04f43da85ff1c333f3e7277b9ac4df92c9120fbb251f1dede7d41286e8c055acfeb845f6d2654821afca25da119daff9043530b296ee0e28e202ba92ec5842d617" + db {:hardwallet {:encryption-public-key epk + :whisper-private-key wpk + :wallet-address "83278851e290d2488b6add2a257259f5741a3b7d" + :whisper-public-key "0x04491c1272149d7fa668afa45968c9914c0661641ace7dbcbc585c15070257840a0b4b1f71ce66c2147e281e1a44d6231b4731a26f6cc0a49e9616bbc7fc2f1a93" + :whisper-address "b8bec30855ff20c2ddab32282e2b2c8c8baca70d"}} + result (login.core/login {:db db})] + (is (= (-> result (get :hardwallet/login-with-keycard) keys count) + 3)) + (is (= (get-in result [:hardwallet/login-with-keycard :whisper-private-key wpk]))) + (is (= (get-in result [:hardwallet/login-with-keycard :encryption-public-key epk]))) + (is (fn? (get-in result [:hardwallet/login-with-keycard :on-result])))))) diff --git a/translations/en.json b/translations/en.json index 7fb76b11da..0a0c0a218e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -838,13 +838,17 @@ "pin-unblocked-description": "Your new PIN {{pin}}", "current-pin": "Enter PIN", "current-pin-description": "Enter your PIN to proceed", + "login-pin-description": "Enter your PIN code to login\nto your account", "new-pin-description": "Enter new PIN code", "change-pin": "Change PIN", + "enter-pin": "Enter PIN", + "enter-pin-description": "Enter your PIN code to login\n to your account", "create-pin": "Create a PIN", "create-pin-description": "You'll need your card + this PIN to log in and to confirm transactions", "repeat-pin": "Repeat new PIN", "puk-mismatch": "PUK code does not match", "pin-mismatch": "PIN does not match", + "tag-was-lost": "Tag was lost", "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", @@ -862,6 +866,14 @@ "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.", + "no-account-on-card": "No account on this card", + "no-account-on-card-text": "The card you are presenting does not hold any account. Please try again with the right card", + "wrong-card": "Wrong card", + "wrong-card-text": "You have tapped a card that does not correspond to the account you selected. Please try again.", + "account-not-listed": "Account not listed", + "account-not-listed-text": "The account on this card is not listed on your phone. Would you like to login with this new account?", + "wrong-card-text": "You have tapped a card that does not correspond to the account you selected. Please try again.", + "no-pairing-on-device": "Card is not paired to this device", "help": "help", "help-capitalized": "Help", "pairing-card": "Pairing card",