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
|
(defn get-label-by-type
|
||||||
[biometric-type]
|
[biometric-type]
|
||||||
(case biometric-type
|
(condp = biometric-type
|
||||||
:fingerprint (i18n/label :t/biometric-fingerprint)
|
:fingerprint (i18n/label :t/biometric-fingerprint)
|
||||||
:FaceID (i18n/label :t/biometric-faceid)
|
:FaceID (i18n/label :t/biometric-faceid)
|
||||||
(i18n/label :t/biometric-touchid)))
|
(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
|
(re-frame/reg-fx
|
||||||
:biometric/get-supported-biometric-type
|
:biometric/get-supported-biometric-type
|
||||||
(fn []
|
(fn []
|
||||||
|
@ -126,3 +132,26 @@
|
||||||
{:events [:biometric/authenticate]}
|
{:events [:biometric/authenticate]}
|
||||||
[_ opts]
|
[_ opts]
|
||||||
{: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
|
:keychain/clear-user-password
|
||||||
(fn [key-uid]
|
(fn [key-uid]
|
||||||
(keychain/reset-credentials (password-migration-key-name key-uid))
|
(keychain/reset-credentials (password-migration-key-name key-uid))
|
||||||
|
(keychain/reset-credentials (str key-uid "-auth"))
|
||||||
(keychain/reset-credentials key-uid)))
|
(keychain/reset-credentials key-uid)))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
|
@ -142,6 +143,11 @@
|
||||||
(.then #(when on-success (on-success)))
|
(.then #(when on-success (on-success)))
|
||||||
(.catch #(when on-error (on-error %))))))
|
(.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
|
;; NOTE: migrating the plaintext password in the keychain
|
||||||
;; with the hashed one. Added due to the sync onboarding
|
;; with the hashed one. Added due to the sync onboarding
|
||||||
;; flow, where the password arrives already hashed.
|
;; flow, where the password arrives already hashed.
|
||||||
|
|
|
@ -6,3 +6,8 @@
|
||||||
(fn [{:keys [db]} [callback]]
|
(fn [{:keys [db]} [callback]]
|
||||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||||
{:fx [[:keychain/get-user-password [key-uid callback]]]})))
|
{: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
|
(ns status-im.common.standard-authentication.standard-auth.authorize
|
||||||
(:require
|
(:require
|
||||||
[native-module.core :as native-module]
|
|
||||||
[react-native.touch-id :as biometric]
|
[react-native.touch-id :as biometric]
|
||||||
[status-im.common.standard-authentication.enter-password.view :as enter-password]
|
[status-im.common.standard-authentication.enter-password.view :as enter-password]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
|
@ -18,11 +17,11 @@
|
||||||
auth-button-label theme blur? auth-button-icon-left]}]
|
auth-button-label theme blur? auth-button-icon-left]}]
|
||||||
(let [handle-auth-success (fn [biometric?]
|
(let [handle-auth-success (fn [biometric?]
|
||||||
(fn [entered-password]
|
(fn [entered-password]
|
||||||
(let [sha3-pwd (if biometric?
|
(let [sha3-masked-password (if biometric?
|
||||||
(str (security/safe-unmask-data entered-password))
|
entered-password
|
||||||
(native-module/sha3 (str (security/safe-unmask-data
|
(security/hash-masked-password
|
||||||
entered-password))))]
|
entered-password))]
|
||||||
(on-auth-success sha3-pwd))))
|
(on-auth-success sha3-masked-password))))
|
||||||
password-login (fn [{:keys [on-press-biometrics]}]
|
password-login (fn [{:keys [on-press-biometrics]}]
|
||||||
(rf/dispatch [:show-bottom-sheet
|
(rf/dispatch [:show-bottom-sheet
|
||||||
{:on-close on-close
|
{:on-close on-close
|
||||||
|
@ -53,10 +52,12 @@
|
||||||
(password-login {:on-press-biometrics
|
(password-login {:on-press-biometrics
|
||||||
#(on-press-biometrics
|
#(on-press-biometrics
|
||||||
on-press-biometrics)}))}))]
|
on-press-biometrics)}))}))]
|
||||||
|
(if biometric-auth?
|
||||||
(biometric/get-supported-type
|
(biometric/get-supported-type
|
||||||
(fn [biometric-type]
|
(fn [biometric-type]
|
||||||
(if (and biometric-auth? biometric-type)
|
(if biometric-type
|
||||||
(biometrics-login biometrics-login)
|
(biometrics-login biometrics-login)
|
||||||
(do
|
(do
|
||||||
(reset-password)
|
(reset-password)
|
||||||
(password-login {})))))))
|
(password-login {})))))
|
||||||
|
(password-login {}))))
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[status-im.common.standard-authentication.standard-auth.authorize :as authorize]
|
[status-im.common.standard-authentication.standard-auth.authorize :as authorize]
|
||||||
|
[status-im.constants :as constants]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn- view-internal
|
(defn- view-internal
|
||||||
|
@ -15,7 +16,7 @@
|
||||||
#(reset! reset-slider? true)
|
#(reset! reset-slider? true)
|
||||||
200))
|
200))
|
||||||
auth-method (rf/sub [:auth-method])
|
auth-method (rf/sub [:auth-method])
|
||||||
biometric-auth? (= auth-method "biometric")]
|
biometric-auth? (= auth-method constants/auth-method-biometric)]
|
||||||
(fn [{:keys [track-text
|
(fn [{:keys [track-text
|
||||||
customization-color
|
customization-color
|
||||||
auth-button-label
|
auth-button-label
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
:blur? true
|
:blur? true
|
||||||
:action :arrow}
|
:action :arrow}
|
||||||
{:title (i18n/label :t/password)
|
{:title (i18n/label :t/password)
|
||||||
:on-press not-implemented/alert
|
:on-press #(rf/dispatch [:open-modal :settings-password])
|
||||||
:image-props :i/password
|
:image-props :i/password
|
||||||
:image :icon
|
:image :icon
|
||||||
:blur? true
|
: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]
|
[status-im.contexts.syncing.utils :as sync-utils]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[utils.re-frame :as rf]
|
[utils.re-frame :as rf]
|
||||||
|
[utils.security.core :as security]
|
||||||
[utils.transforms :as transforms]))
|
[utils.transforms :as transforms]))
|
||||||
|
|
||||||
(rf/defn local-pairing-update-role
|
(rf/defn local-pairing-update-role
|
||||||
|
@ -106,7 +107,8 @@
|
||||||
config-map (.stringify js/JSON
|
config-map (.stringify js/JSON
|
||||||
(clj->js {:senderConfig {:keyUID key-uid
|
(clj->js {:senderConfig {:keyUID key-uid
|
||||||
:keystorePath ""
|
:keystorePath ""
|
||||||
:password sha3-pwd
|
:password (security/safe-unmask-data
|
||||||
|
sha3-pwd)
|
||||||
:deviceType platform/os}
|
:deviceType platform/os}
|
||||||
:serverConfig {:timeout 0}}))]
|
:serverConfig {:timeout 0}}))]
|
||||||
(native-module/get-connection-string-for-bootstrapping-another-device
|
(native-module/get-connection-string-for-bootstrapping-another-device
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]
|
[utils.re-frame :as rf]
|
||||||
[utils.responsiveness :refer [iphone-11-Pro-20-pixel-from-width]]
|
[utils.responsiveness :refer [iphone-11-Pro-20-pixel-from-width]]
|
||||||
|
[utils.security.core :as security]
|
||||||
[utils.string]))
|
[utils.string]))
|
||||||
|
|
||||||
(defn keypair-string
|
(defn keypair-string
|
||||||
|
@ -119,9 +120,8 @@
|
||||||
:track-text (i18n/label :t/slide-to-create-account)
|
:track-text (i18n/label :t/slide-to-create-account)
|
||||||
:customization-color @account-color
|
:customization-color @account-color
|
||||||
:on-auth-success (fn [entered-password]
|
:on-auth-success (fn [entered-password]
|
||||||
(prn entered-password)
|
|
||||||
(rf/dispatch [:wallet/derive-address-and-add-account
|
(rf/dispatch [:wallet/derive-address-and-add-account
|
||||||
{:sha3-pwd entered-password
|
{:sha3-pwd (security/safe-unmask-data entered-password)
|
||||||
:emoji @emoji
|
:emoji @emoji
|
||||||
:color @account-color
|
:color @account-color
|
||||||
:path @derivation-path
|
:path @derivation-path
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
[status-im.contexts.profile.edit.name.view :as edit-name]
|
[status-im.contexts.profile.edit.name.view :as edit-name]
|
||||||
[status-im.contexts.profile.edit.view :as edit-profile]
|
[status-im.contexts.profile.edit.view :as edit-profile]
|
||||||
[status-im.contexts.profile.profiles.view :as profiles]
|
[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.profile.settings.view :as settings]
|
||||||
[status-im.contexts.shell.activity-center.view :as activity-center]
|
[status-im.contexts.shell.activity-center.view :as activity-center]
|
||||||
[status-im.contexts.shell.jump-to.view :as shell]
|
[status-im.contexts.shell.jump-to.view :as shell]
|
||||||
|
@ -367,7 +368,13 @@
|
||||||
:options (merge
|
:options (merge
|
||||||
options/dark-screen
|
options/dark-screen
|
||||||
{:modalPresentationStyle :overCurrentContext})
|
{: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
|
(when js/goog.DEBUG
|
||||||
[{:name :dev-component-preview
|
[{:name :dev-component-preview
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
"change-log-level": "Confirm and restart the app to change log level to {{log-level}}",
|
"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-logging-enabled": "Are you sure you want to {{enable}} logging?",
|
||||||
"change-passcode": "Change Passcode",
|
"change-passcode": "Change Passcode",
|
||||||
"change-password": "Change Password",
|
"change-password": "Change password",
|
||||||
"change-pin": "Change 6-digit passcode",
|
"change-pin": "Change 6-digit passcode",
|
||||||
"change-puk": "Change 12-digit PUK",
|
"change-puk": "Change 12-digit PUK",
|
||||||
"change-pairing": "Change pairing code",
|
"change-pairing": "Change pairing code",
|
||||||
|
|
Loading…
Reference in New Issue