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:
Lungu Cristian 2024-01-09 10:25:40 +02:00 committed by GitHub
parent 4c4ba97308
commit fb13c3016d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 168 additions and 21 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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})))

View File

@ -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.

View File

@ -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)}))

View File

@ -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 {}))))

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",