[#9354] Unlock keycard account without a card

To make it work `encryption-public-key` and `whisper-private-key` are
stored on the devices when a user chooses this option. The former key is
used for multiaccount's database encryption, the latter is needed for a
messaging. In case if a user wants to sign a transaction the card is
still needed, we don't store wallet's keys on the device.

Other things were fixed/added:
- A user can enable biometric auth for a regular account when chooses
  to save the password on the device (if biometric auth is available).
  This is done for feature parity between keycard and "on device"
  accounts.
- The option to create/restore an account on a keycard is not shown on
  the devices which do not support NFC. Currently, the app just crashes
  if the user continues a flow which is not supported by the device.
This commit is contained in:
Roman Volosovskyi 2019-11-28 11:57:58 +02:00
parent cd0486227d
commit 5bb6997d1d
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
22 changed files with 450 additions and 157 deletions

View File

@ -686,7 +686,7 @@
(handlers/register-handler-fx
:hardwallet.callback/check-nfc-support-success
(fn [cofx [_ supported?]]
(hardwallet/set-nfc-support cofx supported?)))
(hardwallet/set-nfc-supported cofx supported?)))
(handlers/register-handler-fx
:hardwallet.callback/on-card-connected

View File

@ -2,20 +2,16 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.multiaccounts.create.core :as multiaccounts.create]
[status-im.multiaccounts.login.core :as multiaccounts.login]
[status-im.multiaccounts.logout.core :as multiaccounts.logout]
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.mnemonic :as mnemonic]
[status-im.i18n :as i18n]
[status-im.node.core :as node]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.config :as config]
[status-im.utils.datetime :as utils.datetime]
[status-im.utils.fx :as fx]
[status-im.utils.platform :as platform]
[status-im.utils.types :as types]
[status-im.wallet.core :as wallet]
[taoensso.timbre :as log]
status-im.hardwallet.fx
[status-im.ui.components.react :as react]
@ -23,7 +19,9 @@
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.multiaccounts.recover.core :as recover]
[status-im.ethereum.eip55 :as eip55]))
[status-im.ethereum.eip55 :as eip55]
[status-im.utils.keychain.core :as keychain]
[status-im.hardwallet.nfc :as nfc]))
(def default-pin "000000")
@ -59,6 +57,11 @@
(:keycard-pairing
(find-multiaccount-by-key-uid db key-uid))))))
(re-frame/reg-fx
:hardwallet/set-nfc-supported
(fn [supported?]
(nfc/set-nfc-supported? supported?)))
(fx/defn listen-to-hardware-back-button
[{:keys [db]}]
(when-not (get-in db [:hardwallet :back-button-listener])
@ -164,10 +167,10 @@
remove-instance-uid? (assoc :keycard-instance-uid nil))
{}))
(defn hardwallet-supported? [{:keys [db]}]
(defn hardwallet-supported? []
(and config/hardwallet-enabled?
platform/android?
(get-in db [:hardwallet :nfc-supported?])))
(nfc/nfc-supported?)))
(fx/defn unauthorized-operation
[{:keys [db] :as cofx}]
@ -628,7 +631,7 @@
pairing (get-in db [:multiaccounts/multiaccounts multiaccount-address :keycard-pairing])
pin (string/join (get-in db [:hardwallet :pin :login]))]
(when (and pairing
(not (empty? pin)))
(seq pin))
{:db (-> db
(assoc-in [:hardwallet :pin :status] :verifying))
:hardwallet/get-keys {:pairing pairing
@ -723,9 +726,9 @@
(when on-card-read
(dispatch-event on-card-read)))))))
(fx/defn set-nfc-support
[{:keys [db]} supported?]
{:db (assoc-in db [:hardwallet :nfc-supported?] supported?)})
(fx/defn set-nfc-supported
[_ supported?]
{:hardwallet/set-nfc-supported supported?})
(fx/defn keycard-option-pressed
{:events [:onboarding.ui/keycard-option-pressed]}
@ -1774,13 +1777,14 @@
(let [account-data (js->clj data :keywordize-keys true)]
(fx/merge cofx
{:db (-> db
(assoc-in [:hardwallet :multiaccount] (-> account-data
(update :address #(str "0x" %))
(update :whisper-address #(str "0x" %))
(update :wallet-address #(str "0x" %))
(update :public-key #(str "0x" %))
(update :whisper-public-key #(str "0x" %))
(update :wallet-public-key #(str "0x" %))
(assoc-in [:hardwallet :multiaccount]
(-> account-data
(update :address ethereum/normalized-hex)
(update :whisper-address ethereum/normalized-hex)
(update :wallet-address ethereum/normalized-hex)
(update :public-key ethereum/normalized-hex)
(update :whisper-public-key ethereum/normalized-hex)
(update :wallet-public-key ethereum/normalized-hex)
(update :instance-uid #(get-in db [:hardwallet :multiaccount :instance-uid] %))))
(assoc-in [:hardwallet :multiaccount-wallet-address] (:wallet-address account-data))
(assoc-in [:hardwallet :multiaccount-whisper-public-key] (:whisper-public-key account-data))
@ -1808,29 +1812,66 @@
(fx/defn on-get-keys-success
[{:keys [db] :as cofx} data]
(let [{:keys [address whisper-address encryption-public-key whisper-private-key] :as account-data} (js->clj data :keywordize-keys true)
address (str "0x" address)
(let [{:keys [address encryption-public-key whisper-private-key] :as account-data} (js->clj data :keywordize-keys true)
address (ethereum/normalized-hex address)
{:keys [photo-path name]} (get-in db [:multiaccounts/multiaccounts address])
key-uid (get-in db [:hardwallet :application-info :key-uid])
multiaccount-data (types/clj->json {:name name :address address :photo-path photo-path})]
(fx/merge cofx
{:db (-> db
multiaccount-data (types/clj->json {:name name :address address :photo-path photo-path})
save-keys? (get-in db [:multiaccounts/login :save-password?])]
(fx/merge
cofx
{:db
(-> db
(assoc-in [:hardwallet :pin :status] nil)
(assoc-in [:hardwallet :pin :login] [])
(assoc-in [:hardwallet :multiaccount] (update account-data :whisper-public-key #(str "0x" %)))
(assoc-in [:hardwallet :multiaccount]
(update account-data :whisper-public-key ethereum/normalized-hex))
(assoc-in [:hardwallet :flow] nil)
(update :multiaccounts/login assoc
:password encryption-public-key
:address address
:photo-path photo-path
:name name))
:hardwallet/get-application-info {:pairing (get-pairing db key-uid)}
:hardwallet/login-with-keycard {:multiaccount-data multiaccount-data
:password encryption-public-key
:chat-key whisper-private-key}}
(when save-keys?
(keychain/save-hardwallet-keys address encryption-public-key whisper-private-key))
(clear-on-card-connected)
(clear-on-card-read))))
(fx/defn on-hardwallet-keychain-keys
{:events [:multiaccounts.login.callback/get-hardwallet-keys-success]}
[{:keys [db] :as cofx} address [encryption-public-key whisper-private-key :as creds]]
(if (nil? creds)
(navigation/navigate-to-cofx cofx :keycard-login-pin nil)
(let [{:keys [photo-path name]} (get-in db [:multiaccounts/multiaccounts address])
multiaccount-data (types/clj->json {:name name
:address address
:photo-path photo-path})
account-data {:address address
:encryption-public-key encryption-public-key
:whisper-private-key whisper-private-key}]
{:db
(-> db
(assoc-in [:hardwallet :pin :status] nil)
(assoc-in [:hardwallet :pin :login] [])
(assoc-in [:hardwallet :multiaccount]
(update account-data :whisper-public-key ethereum/normalized-hex))
(assoc-in [:hardwallet :flow] nil)
(update :multiaccounts/login assoc
:password encryption-public-key
:address address
:photo-path photo-path
:name name
:save-password? true))
:hardwallet/login-with-keycard
{:multiaccount-data multiaccount-data
:password encryption-public-key
:chat-key whisper-private-key}})))
(fx/defn on-get-keys-error
[{:keys [db] :as cofx} error]
(log/debug "[hardwallet] get keys error: " error)

View File

@ -0,0 +1,9 @@
(ns status-im.hardwallet.nfc)
(def is-nfc-supported? (atom nil))
(defn set-nfc-supported? [supported?]
(reset! is-nfc-supported? supported?))
(defn nfc-supported? []
@is-nfc-supported?)

View File

@ -11,7 +11,8 @@
[status-im.react-native.js-dependencies :as js-dependencies]
[re-frame.core :as re-frame]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.keychain.core :as keychain]))
[status-im.utils.keychain.core :as keychain]
[taoensso.timbre :as log]))
;; currently, for android, react-native-touch-id
;; is not returning supported biometric type
@ -81,6 +82,7 @@
(.catch #(callback nil))))
(defn get-supported [callback]
(log/debug "[biometric] get-supported")
(cond platform/ios? (do-get-supported callback)
platform/android? (if android-device-blacklisted?
(callback nil)
@ -91,6 +93,7 @@
([cb]
(authenticate-fx cb nil))
([cb {:keys [reason ios-fallback-label]}]
(log/debug "[biometric] authenticate-fx")
(-> (.authenticate js-dependencies/touchid reason (authenticate-options ios-fallback-label))
(.then #(cb success-result))
(.catch #(cb (generate-error-result %))))))
@ -115,9 +118,14 @@
(authenticate-fx #(cb %) options)))
(fx/defn update-biometric [{db :db :as cofx} biometric-auth?]
(let [address (get-in db [:multiaccount :address])]
(let [address (or (get-in db [:multiaccount :address])
(get-in db [:multiaccounts/login :address]))]
(fx/merge cofx
(keychain/save-auth-method address (if biometric-auth? "biometric" "none"))
(keychain/save-auth-method
address
(if biometric-auth?
keychain/auth-method-biometric
keychain/auth-method-none))
#(when-not biometric-auth?
{:keychain/clear-user-password address}))))
@ -143,9 +151,10 @@
(fx/defn biometric-init-done
{:events [:biometric-init-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
[cofx {:keys [bioauth-success bioauth-message bioauth-code]}]
(if bioauth-success
(if (= "password" (get-in cofx [:db :auth-method]))
(if (= keychain/auth-method-password
(get-in cofx [:db :auth-method]))
(update-biometric cofx true)
(popover/show-popover cofx {:view :enable-biometric}))
(show-message cofx bioauth-message bioauth-code)))
@ -158,3 +167,32 @@
#(re-frame/dispatch [:biometric-auth-done %])
{:reason (i18n/label :t/biometric-auth-reason-login)
:ios-fallback-label (i18n/label :t/biometric-auth-login-ios-fallback-label)}))
(fx/defn enable
{:events [:biometric/enable]}
[cofx]
(fx/merge
cofx
(popover/hide-popover)
(authenticate #(re-frame/dispatch [:biometric/setup-done %]) {})))
(fx/defn disable
{:events [:biometric/disable]}
[{:keys [db] :as cofx}]
(fx/merge
cofx
{:db (-> db
(assoc :auth-method keychain/auth-method-none)
(assoc-in [:multiaccounts/login :save-password?] false))}
(popover/hide-popover)))
(fx/defn setup-done
{:events [:biometric/setup-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(log/debug "[biometric] setup-done"
"bioauth-success" bioauth-success
"bioauth-message" bioauth-message
"bioauth-code" bioauth-code)
(if bioauth-success
{:db (assoc db :auth-method keychain/auth-method-biometric-prepare)}
(show-message cofx bioauth-message bioauth-code)))

View File

@ -5,6 +5,7 @@
[status-im.ethereum.core :as ethereum]
[taoensso.timbre :as log]
[status-im.i18n :as i18n]
[status-im.hardwallet.nfc :as nfc]
[status-im.multiaccounts.db :as db]
[status-im.native-module.core :as status]
[status-im.node.core :as node]
@ -30,14 +31,16 @@
(defn decrement-step [step]
(let [inverted (map-invert step-kw-to-num)]
(if (and (= step :create-code)
(not platform/android?))
(or (not platform/android?)
(not (nfc/nfc-supported?))))
:choose-key
(inverted (dec (step-kw-to-num step))))))
(defn inc-step [step]
(let [inverted (map-invert step-kw-to-num)]
(if (and (= step :choose-key)
(not platform/android?))
(or (not platform/android?)
(not (nfc/nfc-supported?))))
:create-code
(inverted (inc (step-kw-to-num step))))))
@ -139,6 +142,8 @@
{:events [:intro-wizard/step-forward-pressed]}
[{:keys [db] :as cofx} {:keys [skip?] :as opts}]
(let [{:keys [step selected-storage-type processing? weak-password?]} (:intro-wizard db)]
(log/debug "[multiaccount.create] intro-step-forward"
"step" step)
(cond (confirm-failure? db)
(on-confirm-failure cofx)

View File

@ -31,7 +31,9 @@
[status-im.ui.screens.db :refer [app-db]]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.utils.identicon :as identicon]
[status-im.ethereum.eip55 :as eip55]))
[status-im.ethereum.eip55 :as eip55]
[status-im.popover.core :as popover]
[status-im.hardwallet.nfc :as nfc]))
(def rpc-endpoint "https://goerli.infura.io/v3/f315575765b14720b32382a61a89341a")
(def contract-address "0xfbf4c8e2B41fAfF8c616a0E49Fb4365a5355Ffaf")
@ -171,19 +173,24 @@
(when-not platform/desktop?
(initialize-wallet)))))
(defn get-new-auth-method [auth-method save-password?]
(if save-password?
(when-not (or (= keychain/auth-method-biometric auth-method)
(= keychain/auth-method-password auth-method))
(if (= auth-method keychain/auth-method-biometric-prepare)
keychain/auth-method-biometric
keychain/auth-method-password))
(when (and auth-method
(not= auth-method keychain/auth-method-none))
keychain/auth-method-none)))
(fx/defn login-only-events
[{:keys [db] :as cofx} address password save-password?]
(let [auth-method (:auth-method db)
new-auth-method (if save-password?
(when-not (or (= "biometric" auth-method)
(= "password" auth-method))
(if (= auth-method "biometric-prepare")
"biometric"
"password"))
(when (and auth-method
(not= auth-method
"none"))
"none"))]
new-auth-method (get-new-auth-method auth-method save-password?)]
(log/debug "[login] login-only-events"
"auth-method" auth-method
"new-auth-method" new-auth-method)
(fx/merge cofx
{:db (assoc db :chats/loading? true)
::json-rpc/call
@ -202,8 +209,7 @@
:on-success #(re-frame/dispatch [::get-config-callback %])}]}
(when save-password?
(keychain/save-user-password address password))
(when new-auth-method
(keychain/save-auth-method address new-auth-method))
(keychain/save-auth-method address (or new-auth-method auth-method))
(navigation/navigate-to-cofx :home nil)
(when platform/desktop?
(chat-model/update-dock-badge-label)))))
@ -291,7 +297,6 @@
(fx/defn open-login
[{:keys [db] :as cofx} address photo-path name public-key]
(let [keycard-multiaccount? (boolean (get-in db [:multiaccounts/multiaccounts address :keycard-pairing]))]
(fx/merge cofx
{:db (-> db
(update :multiaccounts/login assoc
@ -303,43 +308,95 @@
(update :multiaccounts/login dissoc
:error
:password))}
(if keycard-multiaccount?
(open-keycard-login)
(keychain/get-auth-method address)))))
(keychain/get-auth-method address)))
(fx/defn open-login-callback
{:events [:multiaccounts.login.callback/get-user-password-success]}
[{:keys [db] :as cofx} password]
(let [address (get-in db [:multiaccounts/login :address])
keycard-account? (boolean (get-in db [:multiaccounts/multiaccounts
address
:keycard-pairing]))]
(if password
(fx/merge cofx
{:db (update-in db [:multiaccounts/login] assoc :password password :save-password? true)}
(fx/merge
cofx
{:db (update-in db [:multiaccounts/login] assoc
:password password
:save-password? true)}
(navigation/navigate-to-cofx :progress nil)
login)
(navigation/navigate-to-cofx cofx :login nil)))
(navigation/navigate-to-cofx
cofx
(if keycard-account? :keycard-login-pin :login)
nil))))
(fx/defn get-credentials
[{:keys [db] :as cofx} address]
(let [keycard-multiaccount? (boolean (get-in db [:multiaccounts/multiaccounts address :keycard-pairing]))]
(log/debug "[login] get-credentials"
"keycard-multiacc?" keycard-multiaccount?)
(if keycard-multiaccount?
(keychain/get-hardwallet-keys cofx address)
(keychain/get-user-password cofx address))))
(fx/defn get-auth-method-success
"Auth method: nil - not supported, \"none\" - not selected, \"password\", \"biometric\", \"biometric-prepare\""
{:events [:multiaccounts.login/get-auth-method-success]}
[{:keys [db] :as cofx} auth-method]
(let [address (get-in db [:multiaccounts/login :address])]
(let [address (get-in db [:multiaccounts/login :address])
keycard-multiaccount? (boolean (get-in db [:multiaccounts/multiaccounts address :keycard-pairing]))]
(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)}
#(case auth-method
"biometric"
keychain/auth-method-biometric
(biometric/biometric-auth %)
"password"
(keychain/get-user-password % address)
keychain/auth-method-password
(get-credentials % address)
;;nil or "none" or "biometric-prepare"
(open-login-callback % nil)))))
(if keycard-multiaccount?
(open-keycard-login %)
(open-login-callback % nil))))))
(fx/defn biometric-auth-done
{:events [:biometric-auth-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(let [address (get-in db [:multiaccounts/login :address])]
(log/debug "[biometric] biometric-auth-done"
"bioauth-success" bioauth-success
"bioauth-message" bioauth-message
"bioauth-code" bioauth-code)
(if bioauth-success
(keychain/get-user-password cofx address)
(get-credentials cofx address)
(fx/merge cofx
{:db (assoc-in db [:multiaccounts/login :save-password?] true)}
(biometric/show-message bioauth-message bioauth-code)
(open-login-callback nil)))))
(fx/defn save-password
{:events [:multiaccounts/save-password]}
[{:keys [db] :as cofx} save-password?]
(let [bioauth-supported? (boolean (get db :supported-biometric-auth))
previous-auth-method (get db :auth-method)]
(log/debug "[login] save-password"
"save-password?" save-password?
"bioauth-supported?" bioauth-supported?
"previous-auth-method" previous-auth-method)
(fx/merge
cofx
{:db (cond-> (assoc db :auth-method keychain/auth-method-none)
(or save-password?
(not bioauth-supported?)
(and (not save-password?)
bioauth-supported?
(= previous-auth-method keychain/auth-method-none)))
(assoc-in [:multiaccounts/login :save-password?] save-password?))}
(when bioauth-supported?
(if save-password?
(popover/show-popover {:view :secure-with-biometric})
(when-not (= previous-auth-method keychain/auth-method-none)
(popover/show-popover {:view :disable-password-saving})))))))

View File

@ -22,7 +22,7 @@
(fx/defn logout
{:events [:logout]}
[cofx]
(logout-method cofx "none"))
(logout-method cofx keychain/auth-method-none))
(fx/defn show-logout-confirmation [_]
{:ui/show-confirmation
@ -34,9 +34,9 @@
(fx/defn biometric-logout
{:events [:biometric-logout]}
[{:keys [db] :as cofx}]
[cofx]
(fx/merge cofx
(logout-method "biometric-prepare")
(logout-method keychain/auth-method-biometric-prepare)
(fn [{:keys [db]}]
{:db (assoc-in db [:multiaccounts/login :save-password?] true)})))

View File

@ -4,9 +4,9 @@
[status-im.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.mnemonic :as mnemonic]
[status-im.hardwallet.nfc :as nfc]
[status-im.i18n :as i18n]
[status-im.multiaccounts.create.core :as multiaccounts.create]
[status-im.multiaccounts.db :as db]
[status-im.native-module.core :as status]
[status-im.popover.core :as popover]
[status-im.ui.screens.navigation :as navigation]
@ -14,8 +14,7 @@
[status-im.utils.security :as security]
[status-im.utils.types :as types]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]
[status-im.ethereum.eip55 :as eip55]))
[status-im.utils.utils :as utils]))
(defn existing-account?
[root-key multiaccounts]
@ -225,7 +224,8 @@
assoc :step :select-key-storage
:forward-action :multiaccounts.recover/select-storage-next-pressed
:selected-storage-type :default)}
(if platform/android?
(if (and platform/android?
(nfc/nfc-supported?))
(navigation/navigate-to-cofx :recover-multiaccount-select-storage nil)
(select-storage-next-pressed))))

View File

@ -165,8 +165,7 @@
(defn login-with-keycard
[{:keys [multiaccount-data password chat-key]}]
(log/debug "[native-module] login-with-keycard"
"password" password)
(log/debug "[native-module] login-with-keycard")
(clear-web-data)
(.loginWithKeycard (status) multiaccount-data password chat-key))

View File

@ -14,6 +14,8 @@
(fx/defn status-node-started
[{db :db :as cofx} {:keys [error]}]
(log/debug "[signals] status-node-started"
"error" error)
(if error
{:db (-> db
(update :multiaccounts/login dissoc :processing)

View File

@ -8,17 +8,73 @@
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.i18n :as i18n]))
(views/defview enable-biometric-popover []
(views/letsubs [supported-biometric-auth [:supported-biometric-auth]]
(let [bio-type-label (biometric/get-label supported-biometric-auth)]
[react/view {:padding 24 :align-items :center}
[react/view {:margin-bottom 16 :width 32 :height 32 :background-color colors/blue-light
:border-radius 16 :align-items :center :justify-content :center}
(defn get-supported-biometric-auth []
@(re-frame/subscribe [:supported-biometric-auth]))
(defn get-bio-type-label []
(biometric/get-label (get-supported-biometric-auth)))
(defn biometric-popover
[{:keys [title-label description-label description-text
ok-button-label cancel-button-label on-cancel on-confirm]}]
(let [supported-biometric-auth (get-supported-biometric-auth)
bio-type-label (get-bio-type-label)]
[react/view {:padding 24
:align-items :center}
[react/view {:margin-bottom 16
:width 32
:height 32
:background-color colors/blue-light
:border-radius 16
:align-items :center
:justify-content :center}
[icons/icon (if (= supported-biometric-auth :FaceID) :faceid :print)]]
[react/text {:style {:typography :title-bold}} (str (i18n/label :t/enable) " " bio-type-label)]
[react/text {:style {:margin-bottom 25 :margin-top 10 :text-align :center}}
(i18n/label :t/to-enable-biometric {:bio-type-label bio-type-label})]
[button/button {:label (i18n/label :t/ok-save-pass) :style {:margin-bottom 16}
:on-press #(re-frame/dispatch [:biometric-logout])}]
[button/button {:label :t/cancel :type :secondary
:on-press #(re-frame/dispatch [:hide-popover])}]])))
[react/text {:style {:typography :title-bold}}
(str (i18n/label title-label {:bio-type-label bio-type-label}))]
(vec
(concat
[react/nested-text {:style {:margin-bottom 25
:margin-top 10
:text-align :center}}]
(if description-label
[(i18n/label description-label {:bio-type-label bio-type-label})]
description-text)))
[button/button {:label (i18n/label ok-button-label
{:bio-type-label bio-type-label})
:style {:margin-bottom 16}
:on-press #(re-frame/dispatch [on-confirm])}]
[button/button {:label (or cancel-button-label :t/cancel)
:type :secondary
:on-press #(re-frame/dispatch [(or on-cancel :hide-popover)])}]]))
(defn disable-password-saving-popover []
(let [bio-label-type (get-bio-type-label)]
[biometric-popover
{:title-label :t/biometric-disable-password-title
:ok-button-label :t/continue
:on-confirm :biometric/disable
:description-text
[[{:style {:color colors/gray}}
(i18n/label :t/biometric-disable-password-description)]
[{} (i18n/label :t/biometric-disable-bioauth
{:bio-type-label bio-label-type})]]}]))
(defn enable-biometric-popover []
[biometric-popover
{:title-label :t/enable
:description-label :t/to-enable-biometric
:ok-button-label :t/biometric-enable-button
:on-confirm :biometric-logout}])
(defn secure-with-biometric-popover []
(let [bio-label-type (get-bio-type-label)]
[biometric-popover
{:title-label :t/biometric-secure-with
:ok-button-label :t/biometric-enable-button
:on-confirm :biometric/enable
:description-text
[[{:style {:color colors/gray}} (i18n/label :t/biometric-enable)]
[{} (i18n/label :t/biometric-sign-in {:bio-type-label bio-label-type})]]}]))

View File

@ -54,8 +54,7 @@
:registry {}
:stickers/packs-owned #{}
:stickers/packs-pending #{}
:hardwallet {:nfc-supported? false
:nfc-enabled? false
:hardwallet {:nfc-enabled? false
:pin {:original []
:confirmation []
:current []

View File

@ -7,8 +7,9 @@
[status-im.ui.components.react :as react]
[status-im.ui.screens.hardwallet.pin.styles :as styles]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.actions :as toolbar.actions]))
[status-im.ui.components.toolbar.actions :as toolbar.actions]
[status-im.ui.components.checkbox.view :as checkbox]
[status-im.utils.platform :as platform]))
(defn numpad-button [n step enabled? small-screen?]
[react/touchable-highlight
@ -68,8 +69,21 @@
(repeat (- 12 (count puk))
nil))))])
(defn pin-view [{:keys [pin title-label description-label step status error-label
retry-counter small-screen?]}]
(defn save-password []
(let [{:keys [save-password?]} @(re-frame/subscribe [:multiaccounts/login])
auth-method @(re-frame/subscribe [:auth-method])]
(when-not (and platform/android? (not auth-method))
[react/view
{:style {:flex-direction :row}}
[checkbox/checkbox
{:checked? save-password?
:style {:margin-right 10}
:on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}]
[react/text (i18n/label :t/hardwallet-dont-ask-card)]])))
(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
@ -81,8 +95,7 @@
[react/text {:style styles/create-pin-text
:number-of-lines 2}
(i18n/label description-label)])
[react/view {:margin-top 40
:height (if small-screen? 18 22)}
[react/view {:flex 1}
(case status
:verifying [react/view styles/waiting-indicator-container
[react/activity-indicator {:animating true
@ -94,6 +107,8 @@
[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])

View File

@ -2,6 +2,7 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react]
[status-im.ui.components.checkbox.view :as checkbox]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.screens.keycard.styles :as styles]
@ -370,8 +371,7 @@
retry-counter [:hardwallet/retry-counter]]
[react/view styles/container
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
{:transparent? true}
(when multiple-multiaccounts?
[toolbar/nav-button
(actions/back
@ -385,12 +385,11 @@
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center
:margin-top (if small-screen? 28 46)}
[react/view {:flex-direction :column
:flex 1
:justify-content :center
: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
@ -436,7 +435,8 @@
:small-screen? small-screen?
:status status
:error-label error-label
:step enter-step}]
: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])}

View File

@ -92,7 +92,7 @@
:margin-top 19}}
[checkbox/checkbox {:checked? save-password?
:style {:margin-left 3 :margin-right 10}
:on-value-change #(re-frame/dispatch [:set-in [:multiaccounts/login :save-password?] %])}]
:on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}]
[react/text (i18n/label :t/save-password)]]))]]
(when processing
[react/view styles/processing-view

View File

@ -1,23 +1,13 @@
(ns status-im.ui.screens.multiaccounts.recover.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
[status-im.hardwallet.core :as hardwallet]
[status-im.hardwallet.nfc :as nfc]
[status-im.i18n :as i18n]
[status-im.ui.components.styles :as components.styles]
[status-im.utils.config :as config]
[status-im.ui.components.common.common :as components.common]
[status-im.utils.security :as security]
[status-im.ui.components.colors :as colors]
[status-im.utils.gfycat.core :as gfy]
[status-im.utils.identicon :as identicon]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.screens.intro.views :as intro.views]
[status-im.utils.utils :as utils]
[status-im.constants :as constants]
[status-im.ui.components.list-item.views :as list-item]
[status-im.utils.platform :as platform]
[status-im.react-native.resources :as resources]
@ -68,7 +58,8 @@
:icon :main-icons/text
:on-press #(re-frame/dispatch [::multiaccounts.recover/enter-phrase-pressed])}]
(when (and config/hardwallet-enabled?
platform/android?)
platform/android?
(nfc/nfc-supported?))
[list-item/list-item
{:theme :action
:title :t/recover-with-keycard
@ -86,6 +77,9 @@
:style {:width 24 :height 24}}]]
:on-press #(re-frame/dispatch [::hardwallet/recover-with-keycard-pressed])}])]])
(def bottom-sheet
(defn bottom-sheet []
{:content bottom-sheet-view
:content-height (if platform/android? 130 65)})
:content-height (if (and platform/android?
(nfc/nfc-supported?))
130
65)})

View File

@ -106,6 +106,12 @@
(= :enable-biometric view)
[biometric/enable-biometric-popover]
(= :secure-with-biometric view)
[biometric/secure-with-biometric-popover]
(= :disable-password-saving view)
[biometric/disable-password-saving-popover]
:else
[view])]]]]])))})))

View File

@ -31,12 +31,12 @@
:title (str (i18n/label :t/lock-app-with) " " (biometric/get-label supported-biometric-auth))
:container-margin-bottom 8
:accessibility-label :biometric-auth-settings-switch
:disabled? (or (not (some? supported-biometric-auth)) keycard?)
:disabled? (not (some? supported-biometric-auth))
:accessories [[react/switch
{:track-color #js {:true colors/blue :false nil}
:value (boolean biometric-auth?)
:on-value-change #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched %])
:disabled (or (not (some? supported-biometric-auth)) keycard?)}]]
:disabled (not supported-biometric-auth)}]]
:on-press #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched
((complement boolean) biometric-auth?)])}
;; TODO - uncomment when implemented

View File

@ -78,7 +78,7 @@
(merge home.sheet/group-chat-actions)
(= view :recover-sheet)
(merge recover.views/bottom-sheet))
(merge (recover.views/bottom-sheet)))
height-atom (reagent/atom (if (:content-height opts) (:content-height opts) nil))]
[bottom-sheet-comp opts height-atom])))

View File

@ -77,7 +77,11 @@
#js {:authenticationType (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")})
(.then callback)))
(defn- whisper-key-name [address]
(str address "-whisper"))
(defn can-save-user-password? [callback]
(log/debug "[keychain] can-save-user-password?")
(cond
platform/ios?
(check-conditions callback device-encrypted?)
@ -91,6 +95,7 @@
(defn save-credentials
"Stores the credentials for the address to the Keychain"
[server username password callback]
(log/debug "[keychain] save-credentials")
(-> (.setInternetCredentials rn/keychain (string/lower-case server) username password
keychain-secure-hardware keychain-restricted-availability)
(.then callback)))
@ -98,18 +103,27 @@
(defn get-credentials
"Gets the credentials for a specified server from the Keychain"
[server callback]
(log/debug "[keychain] get-credentials")
(if platform/mobile?
(-> (.getInternetCredentials rn/keychain (string/lower-case server))
(.then callback))
(callback))) ;; no-op for Desktop
(def ^:const auth-method-password "password")
(def ^:const auth-method-biometric "biometric")
(def ^:const auth-method-biometric-prepare "biometric-prepare")
(def ^:const auth-method-none "none")
(re-frame/reg-fx
:keychain/get-auth-method
(fn [[address callback]]
(can-save-user-password?
(fn [can-save?]
(if can-save?
(get-credentials (str address "-auth") #(callback (if % (.-password %) "none")))
(get-credentials (str address "-auth")
#(callback (if %
(.-password %)
auth-method-none)))
(callback nil))))))
(re-frame/reg-fx
@ -117,6 +131,22 @@
(fn [[address callback]]
(get-credentials address #(if % (callback (security/mask-data (.-password %))) (callback nil)))))
(re-frame/reg-fx
:keychain/get-hardwallet-keys
(fn [[address callback]]
(get-credentials
address
(fn [encryption-key-data]
(if encryption-key-data
(get-credentials
(whisper-key-name address)
(fn [whisper-key-data]
(if whisper-key-data
(callback [(.-password encryption-key-data)
(.-password whisper-key-data)])
(callback nil))))
(callback nil))))))
(re-frame/reg-fx
:keychain/save-user-password
(fn [[address password]]
@ -134,6 +164,8 @@
(re-frame/reg-fx
:keychain/save-auth-method
(fn [[address method]]
(log/debug "[keychain] :keychain/save-auth-method"
"method" method)
(save-credentials
(str address "-auth")
address
@ -145,6 +177,24 @@
"The app will continue to work normally, "
"but you will have to login again next time you launch it."))))))
(re-frame/reg-fx
:keychain/save-hardwallet-keys
(fn [[address encryption-public-key whisper-private-key]]
(save-credentials
address
address
encryption-public-key
#(when-not %
(log/error
(str "Error while saving encryption-public-key"))))
(save-credentials
(whisper-key-name address)
address
whisper-private-key
#(when-not %
(log/error
(str "Error while saving whisper-private-key"))))))
(re-frame/reg-fx
:keychain/clear-user-password
(fn [address]
@ -160,12 +210,26 @@
(fx/defn get-user-password
[_ address]
{:keychain/get-user-password
[address #(re-frame/dispatch [:multiaccounts.login.callback/get-user-password-success % address])]})
[address
#(re-frame/dispatch
[:multiaccounts.login.callback/get-user-password-success % address])]})
(fx/defn get-hardwallet-keys
[_ address]
{:keychain/get-hardwallet-keys
[address
#(re-frame/dispatch
[:multiaccounts.login.callback/get-hardwallet-keys-success address %])]})
(fx/defn save-user-password
[cofx address password]
[_ address password]
{:keychain/save-user-password [address password]})
(fx/defn save-hardwallet-keys
[_ address encryption-public-key whisper-private-key]
{:keychain/save-hardwallet-keys [address
encryption-public-key
whisper-private-key]})
(fx/defn save-auth-method
[{:keys [db]} address method]
{:db (assoc db :auth-method method)

View File

@ -68,6 +68,13 @@
"biometric-auth-reason-login": "Login in Status",
"biometric-auth-reason-verify": "Verify authentication",
"biometric-auth-setting-label": "Use biometric authentication",
"biometric-secure-with": "Secure with {{bio-type-label}}",
"biometric-sign-in": "{{bio-type-label}} sign in",
"biometric-enable": "If you don't want to use your Keycard each time to access the app, enable ",
"biometric-disable-bioauth": "disable {{bio-type-label}}",
"biometric-disable-password-title": "Disable password saving",
"biometric-disable-password-description": "If you disable this, you will also ",
"biometric-enable-button": "Enable {{bio-type-label}}",
"blank-keycard-text": "You can proceed with your keycard once you've generated your keys and name",
"blank-keycard-title": "Looks like youve tapped \na blank keycard",
"block": "Block",
@ -497,6 +504,7 @@
"group-info": "Group info",
"gwei": "Gwei",
"hash": "Hash",
"hardwallet-dont-ask-card": "Don't ask for card to sign in",
"help": "help",
"help-capitalized": "Help",
"help-center": "Help Center",