Biometrics in new settings (#18258)
* feat: added biometrics setting to new-settings * fix: fix renaming issues from status-im2 * ref: addressed @cammellos' review comments * fix: open password settings in a modal * ref: addressed review comments * fix: disabling biometric clears auth-method from keychain * chore: quo/overlay seqs the childrend so need to add keys * fix: don't pass the password unmasked between events to avoid leaks
This commit is contained in:
parent
4c4ba97308
commit
fb13c3016d
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -24,11 +24,17 @@
|
|||
|
||||
(defn get-label-by-type
|
||||
[biometric-type]
|
||||
(case biometric-type
|
||||
(condp = biometric-type
|
||||
:fingerprint (i18n/label :t/biometric-fingerprint)
|
||||
:FaceID (i18n/label :t/biometric-faceid)
|
||||
(i18n/label :t/biometric-touchid)))
|
||||
|
||||
(defn get-icon-by-type
|
||||
[biometric-type]
|
||||
(condp = biometric-type
|
||||
:FaceID :i/face-id
|
||||
:i/touch-id))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:biometric/get-supported-biometric-type
|
||||
(fn []
|
||||
|
@ -126,3 +132,26 @@
|
|||
{:events [:biometric/authenticate]}
|
||||
[_ opts]
|
||||
{:biometric/authenticate opts})
|
||||
|
||||
(rf/reg-event-fx
|
||||
:biometric/on-enable-success
|
||||
(fn [{:keys [db]} [password]]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:db (assoc db :auth-method constants/auth-method-biometric)
|
||||
:dispatch [:keychain/save-password-and-auth-method
|
||||
{:key-uid key-uid
|
||||
:masked-password password}]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:biometric/enable
|
||||
(fn [_ [password]]
|
||||
{:dispatch [:biometric/authenticate
|
||||
{:on-success #(rf/dispatch [:biometric/on-enable-success password])
|
||||
:on-fail #(rf/dispatch [:biometric/show-message %])}]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:biometric/disable
|
||||
(fn [{:keys [db]}]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:db (assoc db :auth-method constants/auth-method-none)
|
||||
:keychain/clear-user-password key-uid})))
|
||||
|
|
|
@ -131,6 +131,7 @@
|
|||
:keychain/clear-user-password
|
||||
(fn [key-uid]
|
||||
(keychain/reset-credentials (password-migration-key-name key-uid))
|
||||
(keychain/reset-credentials (str key-uid "-auth"))
|
||||
(keychain/reset-credentials key-uid)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
@ -142,6 +143,11 @@
|
|||
(.then #(when on-success (on-success)))
|
||||
(.catch #(when on-error (on-error %))))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
:keychain/save-password-and-auth-method
|
||||
(fn [_ [opts]]
|
||||
{:keychain/save-password-and-auth-method opts}))
|
||||
|
||||
;; NOTE: migrating the plaintext password in the keychain
|
||||
;; with the hashed one. Added due to the sync onboarding
|
||||
;; flow, where the password arrives already hashed.
|
||||
|
|
|
@ -6,3 +6,8 @@
|
|||
(fn [{:keys [db]} [callback]]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:fx [[:keychain/get-user-password [key-uid callback]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:standard-auth/reset-login-password
|
||||
(fn [{:keys [db]}]
|
||||
{:db (update db :profile/login dissoc :password :error)}))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
(ns status-im.common.standard-authentication.standard-auth.authorize
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[react-native.touch-id :as biometric]
|
||||
[status-im.common.standard-authentication.enter-password.view :as enter-password]
|
||||
[taoensso.timbre :as log]
|
||||
|
@ -18,11 +17,11 @@
|
|||
auth-button-label theme blur? auth-button-icon-left]}]
|
||||
(let [handle-auth-success (fn [biometric?]
|
||||
(fn [entered-password]
|
||||
(let [sha3-pwd (if biometric?
|
||||
(str (security/safe-unmask-data entered-password))
|
||||
(native-module/sha3 (str (security/safe-unmask-data
|
||||
entered-password))))]
|
||||
(on-auth-success sha3-pwd))))
|
||||
(let [sha3-masked-password (if biometric?
|
||||
entered-password
|
||||
(security/hash-masked-password
|
||||
entered-password))]
|
||||
(on-auth-success sha3-masked-password))))
|
||||
password-login (fn [{:keys [on-press-biometrics]}]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:on-close on-close
|
||||
|
@ -53,10 +52,12 @@
|
|||
(password-login {:on-press-biometrics
|
||||
#(on-press-biometrics
|
||||
on-press-biometrics)}))}))]
|
||||
(biometric/get-supported-type
|
||||
(fn [biometric-type]
|
||||
(if (and biometric-auth? biometric-type)
|
||||
(biometrics-login biometrics-login)
|
||||
(do
|
||||
(reset-password)
|
||||
(password-login {})))))))
|
||||
(if biometric-auth?
|
||||
(biometric/get-supported-type
|
||||
(fn [biometric-type]
|
||||
(if biometric-type
|
||||
(biometrics-login biometrics-login)
|
||||
(do
|
||||
(reset-password)
|
||||
(password-login {})))))
|
||||
(password-login {}))))
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.common.standard-authentication.standard-auth.authorize :as authorize]
|
||||
[status-im.constants :as constants]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- view-internal
|
||||
|
@ -15,7 +16,7 @@
|
|||
#(reset! reset-slider? true)
|
||||
200))
|
||||
auth-method (rf/sub [:auth-method])
|
||||
biometric-auth? (= auth-method "biometric")]
|
||||
biometric-auth? (= auth-method constants/auth-method-biometric)]
|
||||
(fn [{:keys [track-text
|
||||
customization-color
|
||||
auth-button-label
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
:blur? true
|
||||
:action :arrow}
|
||||
{:title (i18n/label :t/password)
|
||||
:on-press not-implemented/alert
|
||||
:on-press #(rf/dispatch [:open-modal :settings-password])
|
||||
:image-props :i/password
|
||||
:image :icon
|
||||
:blur? true
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.style)
|
||||
|
||||
(defn navigation
|
||||
[top-inset]
|
||||
{:padding-top top-inset})
|
||||
|
||||
(def header
|
||||
{:padding-horizontal 20
|
||||
:padding-bottom 8
|
||||
:padding-top 12})
|
|
@ -0,0 +1,86 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.view
|
||||
(:require [quo.core :as quo]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.biometric.events :as biometric]
|
||||
[status-im.common.not-implemented :as not-implemented]
|
||||
[status-im.common.standard-authentication.standard-auth.authorize :as authorize]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.settings.screens.password.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- on-press-biometric-enable
|
||||
[button-label theme]
|
||||
(fn []
|
||||
(authorize/authorize
|
||||
{:biometric-auth? false
|
||||
:blur? true
|
||||
:theme theme
|
||||
:auth-button-label (i18n/label :t/biometric-enable-button {:bio-type-label button-label})
|
||||
:on-close (fn [] (rf/dispatch [:standard-auth/reset-login-password]))
|
||||
:on-auth-success (fn [password]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:standard-auth/reset-login-password])
|
||||
(rf/dispatch [:biometric/enable password]))})))
|
||||
|
||||
(defn- get-biometric-item
|
||||
[theme]
|
||||
(let [auth-method (rf/sub [:auth-method])
|
||||
biometric-type (rf/sub [:biometric/supported-type])
|
||||
label (biometric/get-label-by-type biometric-type)
|
||||
icon (biometric/get-icon-by-type biometric-type)
|
||||
supported? (boolean biometric-type)
|
||||
enabled? (= auth-method constants/auth-method-biometric)
|
||||
biometric-on? (and supported? enabled?)
|
||||
press-handler (if biometric-on?
|
||||
(fn [] (rf/dispatch [:biometric/disable]))
|
||||
(on-press-biometric-enable label theme))]
|
||||
{:title label
|
||||
:image-props icon
|
||||
:image :icon
|
||||
:blur? true
|
||||
:action :selector
|
||||
:action-props {:disabled? (not supported?)
|
||||
:on-change press-handler
|
||||
:checked? biometric-on?}
|
||||
:on-press press-handler}))
|
||||
|
||||
(defn- get-change-password-item
|
||||
[]
|
||||
{:title (i18n/label :t/change-password)
|
||||
:on-press not-implemented/alert
|
||||
:blur? true
|
||||
:image :icon
|
||||
:image-props :i/password
|
||||
:action :arrow})
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [theme]}]
|
||||
(let [insets (safe-area/get-insets)]
|
||||
[quo/overlay {:type :shell}
|
||||
[rn/view
|
||||
{:key :navigation
|
||||
:style (style/navigation (:top insets))}
|
||||
[quo/page-nav
|
||||
{:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press #(rf/dispatch [:navigate-back])}]]
|
||||
[rn/view
|
||||
{:key :header
|
||||
:style style/header}
|
||||
[quo/text
|
||||
{:accessibility-label :password-settings-label
|
||||
:weight :semi-bold
|
||||
:number-of-lines 1
|
||||
:size :heading-1}
|
||||
(i18n/label :t/password)]]
|
||||
[quo/category
|
||||
{:key :category
|
||||
:data [(get-biometric-item theme)
|
||||
(get-change-password-item)]
|
||||
:blur? true
|
||||
:list-type :settings}]]))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
|
@ -11,6 +11,7 @@
|
|||
[status-im.contexts.syncing.utils :as sync-utils]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(rf/defn local-pairing-update-role
|
||||
|
@ -106,7 +107,8 @@
|
|||
config-map (.stringify js/JSON
|
||||
(clj->js {:senderConfig {:keyUID key-uid
|
||||
:keystorePath ""
|
||||
:password sha3-pwd
|
||||
:password (security/safe-unmask-data
|
||||
sha3-pwd)
|
||||
:deviceType platform/os}
|
||||
:serverConfig {:timeout 0}}))]
|
||||
(native-module/get-connection-string-for-bootstrapping-another-device
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.responsiveness :refer [iphone-11-Pro-20-pixel-from-width]]
|
||||
[utils.security.core :as security]
|
||||
[utils.string]))
|
||||
|
||||
(defn keypair-string
|
||||
|
@ -119,9 +120,8 @@
|
|||
:track-text (i18n/label :t/slide-to-create-account)
|
||||
:customization-color @account-color
|
||||
:on-auth-success (fn [entered-password]
|
||||
(prn entered-password)
|
||||
(rf/dispatch [:wallet/derive-address-and-add-account
|
||||
{:sha3-pwd entered-password
|
||||
{:sha3-pwd (security/safe-unmask-data entered-password)
|
||||
:emoji @emoji
|
||||
:color @account-color
|
||||
:path @derivation-path
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
[status-im.contexts.profile.edit.name.view :as edit-name]
|
||||
[status-im.contexts.profile.edit.view :as edit-profile]
|
||||
[status-im.contexts.profile.profiles.view :as profiles]
|
||||
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
|
||||
[status-im.contexts.profile.settings.view :as settings]
|
||||
[status-im.contexts.shell.activity-center.view :as activity-center]
|
||||
[status-im.contexts.shell.jump-to.view :as shell]
|
||||
|
@ -367,7 +368,13 @@
|
|||
:options (merge
|
||||
options/dark-screen
|
||||
{:modalPresentationStyle :overCurrentContext})
|
||||
:component scan-profile-qr-page/view}]
|
||||
:component scan-profile-qr-page/view}
|
||||
|
||||
;; Settings
|
||||
|
||||
{:name :settings-password
|
||||
:options options/transparent-screen-options
|
||||
:component settings-password/view}]
|
||||
|
||||
(when js/goog.DEBUG
|
||||
[{:name :dev-component-preview
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
"change-log-level": "Confirm and restart the app to change log level to {{log-level}}",
|
||||
"change-logging-enabled": "Are you sure you want to {{enable}} logging?",
|
||||
"change-passcode": "Change Passcode",
|
||||
"change-password": "Change Password",
|
||||
"change-password": "Change password",
|
||||
"change-pin": "Change 6-digit passcode",
|
||||
"change-puk": "Change 12-digit PUK",
|
||||
"change-pairing": "Change pairing code",
|
||||
|
|
Loading…
Reference in New Issue