feat: enable biometrics for standard-auth (#17992)

This commit is contained in:
Jamie Caprani 2023-12-07 16:44:16 +00:00 committed by GitHub
parent 6093d56c90
commit 55a99da2f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 143 additions and 149 deletions

View File

@ -46,6 +46,7 @@
[status-im.wallet.core :as wallet]
status-im.wallet.custom-tokens.core
[status-im2.common.biometric.events :as biometric]
status-im2.common.standard-authentication.events
[status-im2.common.theme.core :as theme]
[status-im2.common.universal-links :as universal-links]
[status-im2.constants :as constants]

View File

@ -1,7 +1,7 @@
(ns status-im2.common.standard-authentication.core
(:require
status-im2.common.standard-authentication.standard-auth.button.view
status-im2.common.standard-authentication.password-input.view
status-im2.common.standard-authentication.standard-auth.slide-button.view))
(def button status-im2.common.standard-authentication.standard-auth.button.view/view)
(def password-input status-im2.common.standard-authentication.password-input.view/view)
(def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view)

View File

@ -9,7 +9,7 @@
[utils.re-frame :as rf]))
(defn view
[{:keys [on-enter-password button-label button-icon-left]}]
[{:keys [on-enter-password on-press-biometrics button-label button-icon-left]}]
(let [{:keys [key-uid customization-color] :as profile} (rf/sub [:profile/profile-with-image])
{:keys [error processing password]} (rf/sub [:profile/login])
sign-in-enabled? (rf/sub [:sign-in-enabled?])]
@ -32,20 +32,21 @@
:customization-color customization-color
:size 24}]]
[password-input/view
{:processing processing
:error error
:default-password password
:sign-in-enabled? sign-in-enabled?}]
[rn/view {:style style/enter-password-button}
[quo/button
{:size 40
:type :primary
:customization-color (or customization-color :primary)
:accessibility-label :login-button
:icon-left button-icon-left
:disabled? (or (not sign-in-enabled?) processing)
:on-press (fn []
(rf/dispatch [:set-in [:profile/login :key-uid] key-uid])
(rf/dispatch [:profile.login/verify-database-password password
#(on-enter-password password)]))}
button-label]]]]]))
{:on-press-biometrics on-press-biometrics
:processing processing
:error error
:default-password password
:sign-in-enabled? sign-in-enabled?}]
[quo/button
{:size 40
:container-style style/enter-password-button
:type :primary
:customization-color (or customization-color :primary)
:accessibility-label :login-button
:icon-left button-icon-left
:disabled? (or (not sign-in-enabled?) processing)
:on-press (fn []
(rf/dispatch [:set-in [:profile/login :key-uid] key-uid])
(rf/dispatch [:profile.login/verify-database-password password
#(on-enter-password password)]))}
button-label]]]]))

View File

@ -0,0 +1,8 @@
(ns status-im2.common.standard-authentication.events
(:require
[utils.re-frame :as rf]))
(rf/reg-event-fx :standard-auth/on-biometric-success
(fn [{:keys [db]} [callback]]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:keychain/get-user-password [key-uid callback]]]})))

View File

@ -4,3 +4,7 @@
{:margin-top 8
:flex-direction :row
:align-items :center})
(def auth-button
{:align-self :flex-end
:margin-left 8})

View File

@ -27,21 +27,30 @@
(rf/dispatch [:set-in [:profile/login :error] ""]))
(defn- view-internal
[{:keys [default-password theme shell?]}]
[{:keys [default-password theme shell? on-press-biometrics]}]
(let [{:keys [error processing]} (rf/sub [:profile/login])
error-message (get-error-message error)
error? (boolean (seq error-message))]
[:<>
[quo/input
{:type :password
:blur? true
:disabled? processing
:placeholder (i18n/label :t/type-your-password)
:auto-focus true
:error? error?
:label (i18n/label :t/profile-password)
:on-change-text on-change-password
:default-value (security/safe-unmask-data default-password)}]
[rn/view {:style {:flex-direction :row}}
[quo/input
{:container-style {:flex 1}
:type :password
:blur? true
:disabled? processing
:placeholder (i18n/label :t/type-your-password)
:auto-focus true
:error? error?
:label (i18n/label :t/profile-password)
:on-change-text on-change-password
:default-value (security/safe-unmask-data default-password)}]
(when on-press-biometrics
[quo/button
{:container-style style/auth-button
:on-press on-press-biometrics
:icon-only? true
:type :outline}
:i/face-id])]
(when error?
[rn/view {:style style/error-message}
[quo/info-message
@ -60,10 +69,7 @@
:shell? shell?}]))}
[rn/text
{:style {:text-decoration-line :underline
:color (colors/theme-colors
(colors/custom-color :danger 50)
(colors/custom-color :danger 60)
theme)}
:color (colors/resolve-color :danger theme)}
:size :paragraph-2
:suppress-highlighting true}
(i18n/label :t/forgot-password)]]])]))

View File

@ -1,10 +1,12 @@
(ns status-im2.common.standard-authentication.standard-auth.authorize
(:require
[native-module.core :as native-module]
[react-native.touch-id :as biometric]
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn reset-password
[]
@ -12,33 +14,49 @@
(rf/dispatch [:set-in [:profile/login :error] ""]))
(defn authorize
[{:keys [on-enter-password biometric-auth? on-auth-success on-auth-fail on-close
[{:keys [biometric-auth? on-auth-success on-auth-fail on-close
auth-button-label theme blur? auth-button-icon-left]}]
(biometric/get-supported-type
(fn [biometric-type]
(if (and biometric-auth? biometric-type)
(biometric/authenticate
{:reason (i18n/label :t/biometric-auth-confirm-message)
:on-success (fn [response]
(when on-auth-success (on-auth-success response))
(log/info "response" response))
:on-fail (fn [error]
(log/error "Authentication Failed. Error:" error)
(when on-auth-fail (on-auth-fail error))
(rf/dispatch [:show-bottom-sheet
{:theme theme
:shell? blur?
:content (fn []
[enter-password/view
{:on-enter-password on-enter-password}])}]))})
(do
(reset-password)
(rf/dispatch [:show-bottom-sheet
{:on-close on-close
:theme theme
:shell? blur?
:content (fn []
[enter-password/view
{:on-enter-password on-enter-password
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])}]))))))
(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))))
password-login (fn [{:keys [on-press-biometrics]}]
(rf/dispatch [:show-bottom-sheet
{:on-close on-close
:theme theme
:shell? blur?
:content (fn []
[enter-password/view
{:on-enter-password (handle-auth-success
false)
:on-press-biometrics on-press-biometrics
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])}]))
; biometrics-login recursively passes itself as a parameter because if the user
; fails biometric auth they will be shown the password bottom sheet with an option
; to retrigger biometric auth, so they can endlessly repeat this cycle.
biometrics-login (fn [on-press-biometrics]
(rf/dispatch [:dismiss-keyboard])
(biometric/authenticate
{:reason (i18n/label :t/biometric-auth-confirm-message)
:on-success (fn [_response]
(on-close)
(rf/dispatch [:standard-auth/on-biometric-success
(handle-auth-success true)]))
:on-fail (fn [error]
(on-close)
(log/error "Authentication Failed. Error:" error)
(when on-auth-fail (on-auth-fail error))
(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 {})))))))

View File

@ -1,44 +0,0 @@
(ns status-im2.common.standard-authentication.standard-auth.button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
customization-color
auth-button-label
on-enter-password
on-auth-success
on-press
on-auth-fail
auth-button-icon-left
size
button-label
theme
blur?
container-style
icon-left]}]
[quo/button
{:size size
:container-style container-style
:customization-color customization-color
:icon-left icon-left
:on-press (if on-press
on-press
#(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}))}
button-label])))
(def view (quo.theme/with-theme view-internal))

View File

@ -4,17 +4,21 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]
[utils.re-frame :as rf]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
track-text
(let [reset-slider? (reagent/atom false)
on-close (fn []
(js/setTimeout
#(reset! reset-slider? true)
200))
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method "biometric")]
(fn [{:keys [track-text
customization-color
auth-button-label
on-enter-password
on-auth-success
on-auth-fail
auth-button-icon-left
@ -32,7 +36,6 @@
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail

View File

@ -7,7 +7,6 @@
[status-im2.common.parallax.view :as parallax]
[status-im2.common.parallax.whitelist :as whitelist]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
[status-im2.navigation.state :as state]
[utils.i18n :as i18n]
@ -31,13 +30,13 @@
(rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)]
[rn/view {:style (style/buttons insets)}
[standard-auth/button
[quo/button
{:size 40
:accessibility-label :enable-biometrics-button
:icon-left :i/face-id
:customization-color profile-color
:on-press #(rf/dispatch [:onboarding/enable-biometrics])
:button-label (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})}]
:on-press #(rf/dispatch [:onboarding/enable-biometrics])}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[quo/button
{:accessibility-label :maybe-later-button
:background :blur

View File

@ -133,13 +133,18 @@
{:db (dissoc db :onboarding/profile)
:dispatch [:navigate-to-within-stack [:create-profile :new-to-status]]})
(rf/reg-event-fx :onboarding/set-auth-method
(fn [{:keys [db]} [auth-method]]
{:db (assoc db :auth-method auth-method)}))
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding/finalize-setup]}
[{:keys [db]}]
(let [masked-password (get-in db [:onboarding/profile :password])
key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding/profile :syncing?])
biometric-enabled? (= (get-in db [:onboarding/profile :auth-method])
auth-method (get-in db [:onboarding/profile :auth-method])
biometric-enabled? (= auth-method
constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding/generated-keys? true)}
biometric-enabled?
@ -149,9 +154,9 @@
masked-password
(security/hash-masked-password masked-password))
:on-success (fn []
(if syncing?
(rf/dispatch [:onboarding/navigate-to-enable-notifications])
(log/error "successfully saved biometrics")))
(rf/dispatch [:onboarding/set-auth-method auth-method])
(when syncing?
(rf/dispatch [:onboarding/navigate-to-enable-notifications])))
:on-error #(log/error "failed to save biometrics"
{:key-uid key-uid
:error %})}))))

View File

@ -7,7 +7,7 @@
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.common.confirmation-drawer.view :as confirmation-drawer]
[status-im2.common.standard-authentication.password-input.view :as password-input]
[status-im2.common.standard-authentication.core :as standard-authentication]
[status-im2.config :as config]
[status-im2.constants :as constants]
[status-im2.contexts.onboarding.common.background.view :as background]
@ -205,7 +205,7 @@
:customization-color (or customization-color :primary)
:profile-picture profile-picture
:card-style style/login-profile-card}]
[password-input/view
[standard-authentication/password-input
{:shell? true
:default-password password}]]
[quo/button

View File

@ -12,7 +12,6 @@
[status-im2.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
@ -97,7 +96,7 @@
(rf/defn preparations-for-connection-string
{:events [:syncing/get-connection-string]}
[{:keys [db] :as cofx} entered-password on-valid-connection-string]
[{:keys [db] :as cofx} sha3-pwd on-valid-connection-string]
(let [error (get-in db [:profile/login :error])
handle-connection (fn [response]
(when (sync-utils/valid-connection-string? response)
@ -105,8 +104,7 @@
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
(rf/dispatch [:hide-bottom-sheet])))]
(when-not (and error (string/blank? error))
(let [key-uid (get-in db [:profile/login :key-uid])
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data entered-password)))
(let [key-uid (get-in db [:profile/profile :key-uid])
config-map (.stringify js/JSON
(clj->js {:senderConfig {:keyUID key-uid
:keystorePath ""

View File

@ -53,7 +53,7 @@
(reset! code nil)
(reset! timestamp nil)
(reset! valid-for-ms code-valid-for-ms))
on-enter-password (fn [entered-password]
on-auth-success (fn [entered-password]
(rf/dispatch [:syncing/get-connection-string entered-password
set-code]))]
(fn []
@ -124,8 +124,7 @@
:size :size-40
:track-text (i18n/label :t/slide-to-reveal-code)
:customization-color customization-color
:on-enter-password on-enter-password
:biometric-auth? false
:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/reveal-sync-code)
:auth-button-icon-left :i/reveal}]])]]
[rn/view {:style style/sync-code}

View File

@ -43,8 +43,8 @@
:bottom-action-props {:customization-color @account-color
:disabled? (string/blank? @account-name)
:on-press #(rf/dispatch [:wallet/add-account
nil
{:type :watch
{:sha3-pwd nil
:type :watch
:account-name @account-name
:emoji @account-emoji
:color @account-color}

View File

@ -118,14 +118,14 @@
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
:on-enter-password (fn [entered-password]
:on-auth-success (fn [entered-password]
(prn entered-password)
(rf/dispatch [:wallet/derive-address-and-add-account
entered-password
{:emoji @emoji
{:sha3-pwd entered-password
:emoji @emoji
:color @account-color
:path @derivation-path
:account-name @account-name}]))
:biometric-auth? false
:auth-button-label (i18n/label :t/confirm)
:container-style (style/slide-button-container bottom)}]])))

View File

@ -3,7 +3,6 @@
[camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske]
[clojure.string :as string]
[native-module.core :as native-module]
[quo.foundations.colors :as colors]
[react-native.background-timer :as background-timer]
[status-im2.common.data-store.wallet :as data-store]
@ -15,7 +14,6 @@
[utils.i18n :as i18n]
[utils.number]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as types]))
(rf/reg-event-fx :wallet/show-account-created-toast
@ -143,9 +141,8 @@
{:db (dissoc db :wallet/scanned-address :wallet/send-address)})
(rf/reg-event-fx :wallet/create-derived-addresses
(fn [{:keys [db]} [password {:keys [path]} on-success]]
(let [{:keys [wallet-root-address]} (:profile/profile db)
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data password)))]
(fn [{:keys [db]} [{:keys [sha3-pwd path]} on-success]]
(let [{:keys [wallet-root-address]} (:profile/profile db)]
{:fx [[:json-rpc/call
[{:method "wallet_getDerivedAddresses"
:params [sha3-pwd wallet-root-address [path]]
@ -162,11 +159,10 @@
(rf/reg-event-fx :wallet/add-account
(fn [{:keys [db]}
[password {:keys [emoji account-name color type] :or {type :generated}}
[{:keys [sha3-pwd emoji account-name color type] :or {type :generated}}
{:keys [public-key address path]}]]
(let [lowercase-address (if address (string/lower-case address) address)
key-uid (get-in db [:profile/profile :key-uid])
sha3-pwd (native-module/sha3 (security/safe-unmask-data password))
account-config {:key-uid (when (= type :generated) key-uid)
:wallet false
:chat false
@ -185,11 +181,11 @@
(rf/reg-event-fx
:wallet/derive-address-and-add-account
(fn [_ [password account-details]]
(fn [_ [account-details]]
(let [on-success (fn [derived-address-details]
(rf/dispatch [:wallet/add-account password account-details
(rf/dispatch [:wallet/add-account account-details
(first derived-address-details)]))]
{:fx [[:dispatch [:wallet/create-derived-addresses password account-details on-success]]]})))
{:fx [[:dispatch [:wallet/create-derived-addresses account-details on-success]]]})))
(rf/defn get-ethereum-chains
{:events [:wallet/get-ethereum-chains]}