diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 581f8f6b3f..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,124 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [0.9.28 - Unreleased] -### Added -- Allow numbers in chat topics -### Fixed -Fix app freeze on recover with wrong password -### Changed - -## [0.9.27] -### Added -- Added BRLN coin #5582 -- Added SuperRare token support #5496 -- Setting to remember login passwords iOS -- ENS username resolution in Chat for stateofus.eth usernames (testnet only) - -### Fixed -- Improve contact code validation #5612 #5613 -- Frequent logouts on iOS -- Peepeth incompatibilities on Android #5726 -- Realm vulnerability PR -- Fixed how node version is shown on the about screen #5688 -- Transaction fixes #5694 #5709 - -### Changed -- Updated Bounties.network address -- Update status-go to v0.13.1 - -## [0.9.26] -### Added -- Allow user to remove custom avatar -### Fixed - -### Changed -- Improved validation in account recovery. We now show a warning if any words are not from our mnemonic dictionary. - -## [0.9.25] -### Added -- Add MOKSHA token on Rinkeby - -### Changed -- Update REP contract address -- Update mailservers on Rinkeby network -- Update url of Bounties Network DApp - -## [0.9.24 - 2018-08-01] -### Fixed -- Security patch for DApp browser - -## [0.9.23 - 2018-07-23] -### Added -- Added dismiss button to "Add to contacts" bar - -### Fixed -- Partially fixed issue with 0 fiat in main Wallet screen. We are now explicitly showing an error when app can't fetch -asset prices, instead of silently failing. - -### Changed - -## 0.9.23 - 2018-07-23 -### Added -- iPad support. Status is now displayed at full native resolution on iPad's -- Deep links support -- Persist browser history -- Added refresh button -- Added more dapps -- Allow for setting custom network id -- Sound for Push Notifications, tap on PN opens corresponding chat - -### Fixed -- Fixed Sign in: Cannot paste text within password field [#3931] -- Fixed chat message layout for right-to-left languages -- Fixed parsing of messages containing multiple dots (elipsis) -- Fixed Webview: Screen cut off when using ERC dEX DApp [#3131] -- Fixed links targeting new tabs [#4413] -- Fixed blinking token amounts in transaction history -- Fixed/corrected fiat values in /send and /request messages -- Automatically converts recovery phrase to lowercase during account recovery -- Fixed unread messages counter -- Fixed incoming messages timestamp -- Fixed gas validation: Showing message when total amount being sent plus the max gas cost exceed the balance [#3441] - -### Changed -- Removed Mixpanel integration - -## [0.9.22] - 2018-07-09 -### Added -- Added Farsi public #status channel -- Spam moderation -- Collectibles support (CryptoKitties, CryptoStrikers and Etheremon) -- Added more dapps -- Universal and deep links for public chats, browsing dapps, viewing profiles - -### Fixed -- Fixed mailservers connectivity issue -- Clear chat action correctly clear the unread messages counter -- Gracefully handle realm decryption failures by showing a pop up asking the user to reset the data - -### Changed -- Downgraded React Native to 0.53.3 for improved performance and decreased battery consumption -- When joining a chat only download one day of history - -## [0.9.21] - 2018-06-25 -### Added -- Mainnet is enabled by default on new installations. Upgrades will need to manually switch. -- Chat now supports sending SNT and other tokens in 1-1 chats -- New chat UI for /send and /receive commands -- Updated chat message styling -- Updated toolbar style -- Updated DApp list -- New help links on the Profile tab. Read our Beta FAQ, file a bug, or suggest and vote on features. -- Performance improvements when fetching old messages -- Added 5 new default public chats for our friends speaking Korean, Chinese, Japanese, Russian and Spanish. - -### Fixed -- Prevented empty commands from being sent in chat -- Fixed minimum amounts in transactions -- Fixed an issue where contacts names may not be shown -- Removed log files from release builds -- Fixed an issue with transactions when navigating back diff --git a/ICONS.md b/ICONS.md new file mode 100644 index 0000000000..e9a4c24307 --- /dev/null +++ b/ICONS.md @@ -0,0 +1,24 @@ +# new icons + +## android +1. copy files to corresponding directories at `/Users/romanvolosovskyi/clj/status-react/android/app/src/main/res` (one of `drawable-hdpi`, `drawable-mdpi`, `drawable-xhdpi`, `drawable-xxhdpi`, `drawable-xxxhdpi` for corresponding resolution) +If you only have 3 pngs 1x, 2x and 3x put them in mdpi, xhdpi and xxhdpi +3. if necessary, rename file so that filename contains only lower case chars, and dashes instead of hyphens, e.g. `"Icon-Name.png"` should be renamed to `"icon_name.png"`. +4. In the app `icon_name.png` still can be accessed as `icon-name`, so in order to use can add the next code: + ```clojure + ;; icon_name.png + [vector-icons/icon :icon-name {:color ...}] + ``` + +## ios +1. open xcode (on macos run `open ios/StatusIm.xcworkspace` from project's root dir) +2. go to `StatusIm/StatusIm/Images.xcassets` in xcode +![](https://notes.status.im/uploads/upload_be25e49db97cb114ff4aa0c9d94422fa.png) +3. add images there +4. if necessary, rename file so that filename contains only lower case chars, e.g. `"Icon-Name.png"` should be renamed to `"icon-name"`. +5. **IMPORTANT** there is no need to replace hyphens with dashes, and if you do so you will need to use names with dashes in both android and ios versions. So use dashes for android resources names and hyphens for ios. +6. And now `"icon-name"` can be added in app the same way as it was added for android version + ```clojure + ;; icon-name + [vector-icons/icon :icon-name {:color ...}] + ``` diff --git a/android/app/src/main/res/drawable-mdpi/faceid.png b/android/app/src/main/res/drawable-mdpi/faceid.png new file mode 100644 index 0000000000..9f817b0add Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/faceid.png differ diff --git a/android/app/src/main/res/drawable-mdpi/print.png b/android/app/src/main/res/drawable-mdpi/print.png new file mode 100644 index 0000000000..9556c28bba Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/print.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/faceid.png b/android/app/src/main/res/drawable-xhdpi/faceid.png new file mode 100644 index 0000000000..cf0cae9d2a Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/faceid.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/print.png b/android/app/src/main/res/drawable-xhdpi/print.png new file mode 100644 index 0000000000..6103fe4ee0 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/print.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/faceid.png b/android/app/src/main/res/drawable-xxhdpi/faceid.png new file mode 100644 index 0000000000..6c4dd55ac6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/faceid.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/print.png b/android/app/src/main/res/drawable-xxhdpi/print.png new file mode 100644 index 0000000000..874c7babc3 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/print.png differ diff --git a/ios/StatusIm/Images.xcassets/faceid.imageset/Contents.json b/ios/StatusIm/Images.xcassets/faceid.imageset/Contents.json new file mode 100644 index 0000000000..a982e92186 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/faceid.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "faceid@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "faceid@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "faceid@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@1x.png b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@1x.png new file mode 100644 index 0000000000..9f817b0add Binary files /dev/null and b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@1x.png differ diff --git a/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@2x.png b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@2x.png new file mode 100644 index 0000000000..cf0cae9d2a Binary files /dev/null and b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@2x.png differ diff --git a/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@3x.png b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@3x.png new file mode 100644 index 0000000000..6c4dd55ac6 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/faceid.imageset/faceid@3x.png differ diff --git a/ios/StatusIm/Images.xcassets/print.imageset/Contents.json b/ios/StatusIm/Images.xcassets/print.imageset/Contents.json new file mode 100644 index 0000000000..dc19ddb708 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/print.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "print@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "print@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "print@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/print.imageset/print@1x.png b/ios/StatusIm/Images.xcassets/print.imageset/print@1x.png new file mode 100644 index 0000000000..9556c28bba Binary files /dev/null and b/ios/StatusIm/Images.xcassets/print.imageset/print@1x.png differ diff --git a/ios/StatusIm/Images.xcassets/print.imageset/print@2x.png b/ios/StatusIm/Images.xcassets/print.imageset/print@2x.png new file mode 100644 index 0000000000..6103fe4ee0 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/print.imageset/print@2x.png differ diff --git a/ios/StatusIm/Images.xcassets/print.imageset/print@3x.png b/ios/StatusIm/Images.xcassets/print.imageset/print@3x.png new file mode 100644 index 0000000000..874c7babc3 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/print.imageset/print@3x.png differ diff --git a/src/status_im/biometric_auth/core.cljs b/src/status_im/biometric_auth/core.cljs deleted file mode 100644 index 82a443d176..0000000000 --- a/src/status_im/biometric_auth/core.cljs +++ /dev/null @@ -1,93 +0,0 @@ -(ns status-im.biometric-auth.core - (:require [re-frame.core :as re-frame] - [status-im.utils.platform :as platform] - [status-im.i18n :as i18n] - [status-im.utils.fx :as fx] - [status-im.ui.components.colors :as colors] - [status-im.native-module.core :as status] - [status-im.react-native.js-dependencies :as rn])) - -;; currently, for android, react-native-touch-id -;; is not returning supported biometric type -;; defaulting to :fingerprint -(def android-default-support :fingerprint) - -;;; android blacklist based on device info: - -(def deviceinfo (status/get-device-model-info)) - -;; {:model ? -;; :brand "Xiaomi" -;; :build-id "13D15" -;; :device-id "goldfish" -;; more info on https://github.com/react-native-community/react-native-device-info - -(def android-device-blacklisted? - (cond - (= (:brand deviceinfo) "bannedbrand") true - :else false)) - -;; biometric auth config -;; https://github.com/naoufal/react-native-touch-id#authenticatereason-config -(defn- authenticate-options [ios-fallback-label] - (clj->js (merge - {:unifiedErrors true} - (when platform/ios? - {:passcodeFallback false - :fallbackLabel (or ios-fallback-label "")}) - (when platform/android? - {:title (i18n/label :t/biometric-auth-android-title) - :imageColor colors/blue - :imageErrorColor colors/red - :sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc) - :sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc) - :cancelText (i18n/label :cancel)})))) - -(defn- get-error-message - "must return an error message for the user" - [touchid-error-code] - (cond - ;; no message if user canceled or falled back to password - (= touchid-error-code "USER_CANCELED") nil - (= touchid-error-code "USER_FALLBACK") nil - ;; add here more specific errors if needed - ;; https://github.com/naoufal/react-native-touch-id#unified-errors - :else (i18n/label :t/biometric-auth-error {:code touchid-error-code}))) - -(def success-result - {:bioauth-success true}) - -(defn- generate-error-result [touchid-error-obj] - (let [code (aget touchid-error-obj "code")] - {:bioauth-success false - :bioauth-code code - :bioauth-message (get-error-message code)})) - -(defn- do-get-supported [callback] - (-> (.isSupported rn/touchid) - (.then #(callback (or (keyword %) android-default-support))) - (.catch #(callback nil)))) - -(defn get-supported [callback] - (cond platform/ios? (do-get-supported callback) - platform/android? (if android-device-blacklisted? - (callback nil) - (do-get-supported callback)) - :else (callback nil))) - -(defn authenticate - ([cb] - (authenticate cb nil)) - ([cb {:keys [reason ios-fallback-label]}] - (-> (.authenticate rn/touchid reason (authenticate-options ios-fallback-label)) - (.then #(cb success-result)) - (.catch #(cb (generate-error-result %)))))) - -(fx/defn authenticate-fx - [_ cb options] - {:biometric-auth/authenticate [cb options]}) - -(re-frame/reg-fx - :biometric-auth/authenticate - (fn [[cb options]] - (authenticate #(cb %) options))) \ No newline at end of file diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 1d5e3928fc..2de2165ece 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -7,7 +7,7 @@ [status-im.multiaccounts.logout.core :as multiaccounts.logout] [status-im.multiaccounts.recover.core :as multiaccounts.recover] [status-im.multiaccounts.update.core :as multiaccounts.update] - [status-im.biometric-auth.core :as biomentric-auth] + status-im.multiaccounts.biometric.core [status-im.bootnodes.core :as bootnodes] [status-im.browser.core :as browser] [status-im.browser.permissions :as browser.permissions] @@ -120,20 +120,6 @@ :on-accept open-chaos-unicorn-day-link}}) (multiaccounts/switch-chaos-mode chaos-mode?))))) -(handlers/register-handler-fx - :multiaccounts.ui/biometric-auth-switched - (fn [cofx [_ biometric-auth?]] - (if biometric-auth? - (biomentric-auth/authenticate-fx - cofx - (fn [{:keys [bioauth-success bioauth-message]}] - (when bioauth-success - (re-frame/dispatch [:multiaccounts.ui/switch-biometric-auth true])) - (when bioauth-message - (utils/show-popup (i18n/label :t/biometric-auth-reason-verify) bioauth-message))) - {:reason (i18n/label :t/biometric-auth-reason-verify)}) - (multiaccounts/switch-biometric-auth cofx false)))) - (handlers/register-handler-fx :multiaccounts.ui/notifications-enabled (fn [cofx [_ desktop-notifications?]] @@ -175,14 +161,6 @@ (fn [cofx [_ address photo-path name public-key]] (multiaccounts.login/open-login cofx address photo-path name public-key))) -(handlers/register-handler-fx - :multiaccounts.login.callback/get-user-password-success - (fn [{:keys [db] :as cofx} [_ password address]] - (let [biometric-auth? (get-in db [:multiaccounts/multiaccounts address :settings :biometric-auth?])] - (if (and password biometric-auth?) - (multiaccounts.login/do-biometric-auth cofx password) - (multiaccounts.login/open-login-callback cofx password {:bioauth-notrequired true}))))) - ;; multiaccounts logout module (handlers/register-handler-fx diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index d489f8aff8..4d5ae7476c 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -1,6 +1,5 @@ (ns status-im.init.core (:require [re-frame.core :as re-frame] - [status-im.biometric-auth.core :as biometric-auth] [status-im.multiaccounts.login.core :as multiaccounts.login] [status-im.native-module.core :as status] [status-im.network.net-info :as network] @@ -33,11 +32,6 @@ :view-id view-id :push-notifications/stored stored)}) -(fx/defn set-supported-biometric-auth - {:events [:init.callback/get-supported-biometric-auth-success]} - [{:keys [db]} supported-biometric-auth] - {:db (assoc db :supported-biometric-auth supported-biometric-auth)}) - (fx/defn initialize-views [cofx] (let [{{:multiaccounts/keys [multiaccounts] :as db} :db} cofx] @@ -75,7 +69,7 @@ (fx/defn start-app [cofx] (fx/merge cofx - {::get-supported-biometric-auth nil + {:get-supported-biometric-auth nil ::init-keystore nil ::restore-native-settings nil ::open-multiaccounts #(re-frame/dispatch [::initialize-multiaccounts %]) @@ -100,9 +94,4 @@ (re-frame/reg-fx ::init-keystore (fn [] - (status/init-keystore))) - -(re-frame/reg-fx - ::get-supported-biometric-auth - (fn [] - (biometric-auth/get-supported #(re-frame/dispatch [:init.callback/get-supported-biometric-auth-success %])))) + (status/init-keystore))) \ No newline at end of file diff --git a/src/status_im/multiaccounts/biometric/core.cljs b/src/status_im/multiaccounts/biometric/core.cljs new file mode 100644 index 0000000000..bb08343306 --- /dev/null +++ b/src/status_im/multiaccounts/biometric/core.cljs @@ -0,0 +1,160 @@ +(ns status-im.multiaccounts.biometric.core + (:require + [status-im.utils.fx :as fx] + [status-im.utils.types :as types] + [clojure.string :as string] + [status-im.popover.core :as popover] + [status-im.native-module.core :as status] + [status-im.utils.platform :as platform] + [status-im.ui.components.colors :as colors] + [status-im.i18n :as i18n] + [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])) + +;; currently, for android, react-native-touch-id +;; is not returning supported biometric type +;; defaulting to :fingerprint +(def android-default-support :fingerprint) + +;;; android blacklist based on device info: + +(def deviceinfo (status/get-device-model-info)) + +;; {:model ? +;; :brand "Xiaomi" +;; :build-id "13D15" +;; :device-id "goldfish" +;; more info on https://github.com/react-native-community/react-native-device-info + +(def android-device-blacklisted? + (cond + (= (:brand deviceinfo) "bannedbrand") true + :else false)) + +;; biometric auth config +;; https://github.com/naoufal/react-native-touch-id#authenticatereason-config +(defn- authenticate-options [ios-fallback-label] + (clj->js (merge + {:unifiedErrors true} + (when platform/ios? + {:passcodeFallback false + :fallbackLabel (or ios-fallback-label "")}) + (when platform/android? + {:title (i18n/label :t/biometric-auth-android-title) + :imageColor colors/blue + :imageErrorColor colors/red + :sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc) + :sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc) + :cancelText (i18n/label :cancel)})))) + +(defn get-label [supported-biometric-auth] + (case supported-biometric-auth + :fingerprint "Fingerprint" + :FaceID "Face ID" + "Touch ID")) + +(defn- get-error-message + "must return an error message for the user" + [touchid-error-code] + (cond + ;; no message if user canceled or falled back to password + (= touchid-error-code "USER_CANCELED") nil + (= touchid-error-code "USER_FALLBACK") nil + ;; add here more specific errors if needed + ;; https://github.com/naoufal/react-native-touch-id#unified-errors + :else (i18n/label :t/biometric-auth-error {:code touchid-error-code}))) + +(def success-result + {:bioauth-success true}) + +(defn- generate-error-result [touchid-error-obj] + (let [code (aget touchid-error-obj "code")] + {:bioauth-success false + :bioauth-code code + :bioauth-message (get-error-message code)})) + +(defn- do-get-supported [callback] + (-> (.isSupported js-dependencies/touchid) + (.then #(callback (or (keyword %) android-default-support))) + (.catch #(callback nil)))) + +(defn get-supported [callback] + (cond platform/ios? (do-get-supported callback) + platform/android? (if android-device-blacklisted? + (callback nil) + (do-get-supported callback)) + :else (callback nil))) + +(defn authenticate-fx + ([cb] + (authenticate-fx cb nil)) + ([cb {:keys [reason ios-fallback-label]}] + (-> (.authenticate js-dependencies/touchid reason (authenticate-options ios-fallback-label)) + (.then #(cb success-result)) + (.catch #(cb (generate-error-result %)))))) + +(re-frame/reg-fx + :get-supported-biometric-auth + (fn [] + (get-supported #(re-frame/dispatch [:init.callback/get-supported-biometric-auth-success %])))) + +(fx/defn set-supported-biometric-auth + {:events [:init.callback/get-supported-biometric-auth-success]} + [{:keys [db]} supported-biometric-auth] + {:db (assoc db :supported-biometric-auth supported-biometric-auth)}) + +(fx/defn authenticate + [_ cb options] + {:biometric-auth/authenticate [cb options]}) + +(re-frame/reg-fx + :biometric-auth/authenticate + (fn [[cb options]] + (authenticate-fx #(cb %) options))) + +(fx/defn update-biometric [{db :db :as cofx} biometric-auth?] + (let [address (get-in db [:multiaccount :address])] + (fx/merge cofx + (keychain/save-auth-method address (if biometric-auth? "biometric" "none")) + #(when-not biometric-auth? + {:keychain/clear-user-password address})))) + +(fx/defn biometric-auth-switched + {:events [:multiaccounts.ui/biometric-auth-switched]} + [cofx biometric-auth?] + (if biometric-auth? + (authenticate + cofx + #(re-frame/dispatch [:biometric-init-done %]) + {}) + (update-biometric cofx false))) + +(fx/defn show-message + [cofx bioauth-message bioauth-code] + (let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code) + (i18n/label :t/grant-face-id-permissions)) + bioauth-message)] + (when content + {:utils/show-popup + {:title (i18n/label :t/biometric-auth-login-error-title) + :content content}}))) + +(fx/defn biometric-init-done + {:events [:biometric-init-done]} + [{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}] + (if bioauth-success + (if (= "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))) + +(fx/defn biometric-auth + {:events [:biometric-authenticate]} + [cofx] + (authenticate + cofx + #(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)})) \ No newline at end of file diff --git a/src/status_im/multiaccounts/core.cljs b/src/status_im/multiaccounts/core.cljs index 317f3e8f09..cebd0c3505 100644 --- a/src/status_im/multiaccounts/core.cljs +++ b/src/status_im/multiaccounts/core.cljs @@ -66,15 +66,6 @@ (multiaccounts.update/multiaccount-update {:chaos-mode? chaos-mode?} {})))) -(fx/defn switch-biometric-auth - {:events [:multiaccounts.ui/switch-biometric-auth]} - [{:keys [db] :as cofx} biometric-auth?] - (when (:multiaccount db) - (let [settings (get-in db [:multiaccount :settings])] - (multiaccounts.update/update-settings cofx - (assoc settings :biometric-auth? biometric-auth?) - {})))) - (fx/defn enable-notifications [cofx desktop-notifications?] (multiaccounts.update/multiaccount-update cofx {:desktop-notifications? desktop-notifications?} diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 52d75ff04d..5f7eeb2bb9 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -1,6 +1,5 @@ (ns status-im.multiaccounts.login.core (:require [re-frame.core :as re-frame] - [status-im.biometric-auth.core :as biometric-auth] [status-im.chaos-mode.core :as chaos-mode] [status-im.chat.models :as chat-model] [status-im.chat.models.loading :as chat.loading] @@ -27,7 +26,9 @@ [status-im.utils.universal-links.core :as universal-links] [status-im.utils.utils :as utils] [status-im.wallet.core :as wallet] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.ui.screens.db :refer [app-db]] + [status-im.multiaccounts.biometric.core :as biometric])) (def rpc-endpoint "https://goerli.infura.io/v3/f315575765b14720b32382a61a89341a") (def contract-address "0xfbf4c8e2B41fAfF8c616a0E49Fb4365a5355Ffaf") @@ -49,7 +50,11 @@ (resolve default-nodes))))) (resolve default-nodes)))) -;;;; Handlers +(re-frame/reg-fx + ::login + (fn [[account-data hashed-password]] + (status/login account-data hashed-password))) + (fx/defn initialize-wallet [cofx] (fx/merge cofx (wallet/initialize-tokens) @@ -98,10 +103,6 @@ [{:keys [db]} node-version] {:db (assoc db :web3-node-version node-version)}) -(fx/defn save-user-password - [cofx address password] - {:keychain/save-user-password [address password]}) - (fx/defn handle-close-app-confirmed {:events [::close-app-confirmed]} [_] @@ -156,25 +157,32 @@ (fx/defn login-only-events [{:keys [db] :as cofx} address password save-password?] - (let [stored-pns (:push-notifications/stored db)] + (let [stored-pns (:push-notifications/stored db) + 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"))] (fx/merge cofx {:db (assoc db :chats/loading? true) ::json-rpc/call - [{:method "mailservers_getMailserverTopics" + [{:method "mailservers_getMailserverTopics" :on-success #(re-frame/dispatch [::protocol/initialize-protocol {:mailserver-topics (or % {})}])} - {:method "mailservers_getChatRequestRanges" + {:method "mailservers_getChatRequestRanges" :on-success #(re-frame/dispatch [::protocol/initialize-protocol {:mailserver-ranges (or % {})}])} - {:method "browsers_getBrowsers" + {:method "browsers_getBrowsers" :on-success #(re-frame/dispatch [::initialize-browsers %])} - {:method "permissions_getDappPermissions" + {:method "permissions_getDappPermissions" :on-success #(re-frame/dispatch [::initialize-dapp-permissions %])} - {:method "mailservers_getMailservers" + {:method "mailservers_getMailservers" :on-success #(re-frame/dispatch [::protocol/initialize-protocol {:mailservers (or % [])}])} - {:method "settings_getConfigs" - :params [["multiaccount" "current-network" "networks"]] + {:method "settings_getConfigs" + :params [["multiaccount" "current-network" "networks"]] :on-success #(re-frame/dispatch [::get-config-callback % stored-pns])}]} (when save-password? - (save-user-password address password)) + (keychain/save-user-password address password)) + (when new-auth-method + (keychain/save-auth-method address new-auth-method)) (navigation/navigate-to-cofx :home nil) (when platform/desktop? (chat-model/update-dock-badge-label))))) @@ -263,12 +271,6 @@ (navigation/navigate-to-cofx :multiaccounts nil) (navigation/navigate-to-cofx :keycard-login-pin nil))))) -(fx/defn get-user-password - [_ address] - {:keychain/can-save-user-password? nil - :keychain/get-user-password [address - #(re-frame/dispatch [:multiaccounts.login.callback/get-user-password-success % address])]}) - (fx/defn open-login [{:keys [db] :as cofx} address photo-path name public-key] (let [keycard-multiaccount? (get-in db [:multiaccounts/multiaccounts address :keycard-key-uid])] @@ -284,31 +286,41 @@ :password))} (if keycard-multiaccount? (open-keycard-login) - (get-user-password address))))) + (keychain/get-auth-method address))))) (fx/defn open-login-callback - {:events [::biometric-auth-done]} - [{:keys [db] :as cofx} password {:keys [bioauth-success bioauth-notrequired bioauth-message]}] - (if (and password - (or bioauth-success bioauth-notrequired)) + {:events [:multiaccounts.login.callback/get-user-password-success]} + [{:keys [db] :as cofx} password] + (if password (fx/merge cofx - {:db (assoc-in db [:multiaccounts/login :password] password)} + {: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))) + +(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])] (fx/merge cofx - (when bioauth-message - {:utils/show-popup {:title (i18n/label :t/biometric-auth-login-error-title) :content bioauth-message}}) - (navigation/navigate-to-cofx :login nil)))) + {:db (assoc db :auth-method auth-method)} + #(case auth-method + "biometric" + (biometric/biometric-auth %) + "password" + (keychain/get-user-password % address) -(fx/defn do-biometric-auth - [{:keys [db] :as cofx} password] - (biometric-auth/authenticate-fx cofx - #(re-frame/dispatch [::biometric-auth-done password %]) - {:reason (i18n/label :t/biometric-auth-reason-login) - :ios-fallback-label (i18n/label :t/biometric-auth-login-ios-fallback-label)})) + ;;nil or "none" or "biometric-prepare" + (open-login-callback % nil))))) -(re-frame/reg-fx - ::login - (fn [[account-data hashed-password]] - (status/login account-data - hashed-password))) +(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])] + (if bioauth-success + (keychain/get-user-password 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))))) \ No newline at end of file diff --git a/src/status_im/multiaccounts/logout/core.cljs b/src/status_im/multiaccounts/logout/core.cljs index ee25e7ca81..3406e91fc8 100644 --- a/src/status_im/multiaccounts/logout/core.cljs +++ b/src/status_im/multiaccounts/logout/core.cljs @@ -6,19 +6,23 @@ [status-im.native-module.core :as status] [status-im.transport.core :as transport] [status-im.utils.fx :as fx] - [clojure.string :as string])) + [status-im.utils.keychain.core :as keychain])) + +(fx/defn logout-method [{:keys [db] :as cofx} auth-method] + (let [address (get-in db [:multiaccount :address])] + (fx/merge cofx + {::logout nil + :keychain/clear-user-password address + ::init/open-multiaccounts #(re-frame/dispatch [::init/initialize-multiaccounts %])} + (keychain/save-auth-method address auth-method) + (transport/stop-whisper) + (chaos-mode/stop-checking) + (init/initialize-app-db)))) (fx/defn logout {:events [:logout]} - [{:keys [db] :as cofx}] - (fx/merge cofx - {::logout nil - ;;TODO sort out this mess with lower case addresses - :keychain/clear-user-password (string/lower-case (get-in db [:multiaccount :address])) - ::init/open-multiaccounts #(re-frame/dispatch [::init/initialize-multiaccounts %])} - (transport/stop-whisper) - (chaos-mode/stop-checking) - (init/initialize-app-db))) + [cofx] + (logout-method cofx "none")) (fx/defn show-logout-confirmation [_] {:ui/show-confirmation @@ -28,6 +32,14 @@ :on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) :on-cancel nil}}) +(fx/defn biometric-logout + {:events [:biometric-logout]} + [{:keys [db] :as cofx}] + (fx/merge cofx + (logout-method "biometric-prepare") + (fn [{:keys [db]}] + {:db (assoc-in db [:multiaccounts/login :save-password?] true)}))) + (re-frame/reg-fx ::logout (fn [] diff --git a/src/status_im/popover/core.cljs b/src/status_im/popover/core.cljs index 9d8a79ce92..424f390f34 100644 --- a/src/status_im/popover/core.cljs +++ b/src/status_im/popover/core.cljs @@ -1,6 +1,5 @@ (ns status-im.popover.core - (:require [status-im.utils.fx :as fx] - [status-im.utils.handlers :as handlers])) + (:require [status-im.utils.fx :as fx])) (fx/defn show-popover {:events [:show-popover]} diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index cde20315e3..4af8ea51b5 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -183,6 +183,8 @@ (reg-root-key-sub :keycard :hardwallet) +(reg-root-key-sub :auth-method :auth-method) + ;;GENERAL ============================================================================================================== (re-frame/reg-sub diff --git a/src/status_im/ui/components/button.cljs b/src/status_im/ui/components/button.cljs index 555f305d53..f77bae494d 100644 --- a/src/status_im/ui/components/button.cljs +++ b/src/status_im/ui/components/button.cljs @@ -45,9 +45,9 @@ Spec: https://www.figma.com/file/cb4p8AxLtTF3q1L6JYDnKN15/Index?node-id=858%3A0" - [{:keys [label type disabled? on-press accessibility-label] :or {type :main}}] + [{:keys [label type disabled? on-press accessibility-label style] :or {type :main}}] (let [label (utils.label/stringify label)] - [react/touchable-opacity (merge {:on-press on-press :disabled disabled? :active-pacity 0.5} + [react/touchable-opacity (merge {:on-press on-press :disabled disabled? :active-pacity 0.5 :style style} (when accessibility-label {:accessibility-label accessibility-label})) [react/view {:style (style-container type disabled?)} diff --git a/src/status_im/ui/screens/advanced_settings/views.cljs b/src/status_im/ui/screens/advanced_settings/views.cljs index ae10012898..b94f03c0e8 100644 --- a/src/status_im/ui/screens/advanced_settings/views.cljs +++ b/src/status_im/ui/screens/advanced_settings/views.cljs @@ -57,7 +57,7 @@ :disabled false}]]} {:type :divider}]) -(defn- dev-mode-settings-data [settings chaos-mode? supported-biometric-auth] +(defn- dev-mode-settings-data [settings chaos-mode?] [{:container-margin-top 8 :type :section-header :title :t/dev-mode-settings} @@ -117,47 +117,25 @@ #(re-frame/dispatch [:multiaccounts.ui/chaos-mode-switched (not chaos-mode?)]) :disabled false}]]} - {:type :small - :title :t/biometric-auth-setting-label - :container-margin-bottom 8 - :accessibility-label :biometric-auth-settings-switch - :disabled? (not (some? supported-biometric-auth)) - :accessories - [[react/switch - {:track-color #js {:true colors/blue :false nil} - :value (boolean (:biometric-auth? settings)) - :on-value-change - #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched %]) - :disabled (not (some? supported-biometric-auth))}]] - :on-press - #(re-frame/dispatch - [:multiaccounts.ui/biometric-auth-switched - ((complement boolean) (:biometric-auth? settings))])} [react/view {:height 24}]]) (defn- flat-list-data [network-name current-log-level current-fleet - dev-mode? settings chaos-mode? supported-biometric-auth] + dev-mode? settings chaos-mode?] (if dev-mode? (into (normal-mode-settings-data network-name current-log-level current-fleet dev-mode?) (dev-mode-settings-data - settings chaos-mode? supported-biometric-auth)) + settings chaos-mode?)) ;; else (normal-mode-settings-data network-name current-log-level current-fleet dev-mode?))) (views/defview advanced-settings [] - (views/letsubs [{:keys - [chaos-mode? - dev-mode? - settings] - :as current-multiaccount} [:multiaccount] - settings [:multiaccount-settings] - network-name [:network-name] - current-log-level [:settings/current-log-level] - current-fleet [:settings/current-fleet] - supported-biometric-auth [:supported-biometric-auth]] + (views/letsubs [{:keys [chaos-mode? dev-mode? settings]} [:multiaccount] + network-name [:network-name] + current-log-level [:settings/current-log-level] + current-fleet [:settings/current-fleet]] [react/view {:flex 1 :background-color colors/white} [status-bar/status-bar] [toolbar/simple-toolbar @@ -166,7 +144,7 @@ {:data (flat-list-data network-name current-log-level current-fleet dev-mode? settings - chaos-mode? supported-biometric-auth) + chaos-mode?) :key-fn (fn [_ i] (str i)) :render-fn list/flat-list-generic-render-fn}]])) diff --git a/src/status_im/ui/screens/biometric/views.cljs b/src/status_im/ui/screens/biometric/views.cljs new file mode 100644 index 0000000000..d820b218aa --- /dev/null +++ b/src/status_im/ui/screens/biometric/views.cljs @@ -0,0 +1,24 @@ +(ns status-im.ui.screens.biometric.views + (:require-macros [status-im.utils.views :as views]) + (:require [status-im.ui.components.react :as react] + [status-im.ui.components.button :as button] + [re-frame.core :as re-frame] + [status-im.multiaccounts.biometric.core :as biometric] + [status-im.ui.components.colors :as colors] + [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} + [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])}]]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index a25df24510..a8fc9d8266 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -281,6 +281,7 @@ ::app-state ::app-in-background-since ::hardwallet + ::auth-method :multiaccount/multiaccount :navigation/view-id :navigation/navigation-stack diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index defafa7ec7..dc4a16d19a 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -20,8 +20,8 @@ [status-im.utils.http :as http] [status-im.utils.utils :as utils] [status-im.i18n :as i18n] - [status-im.biometric-auth.core :as biometric-auth] - [status-im.constants :as const])) + [status-im.constants :as const] + [status-im.multiaccounts.biometric.core :as biometric])) (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (let [on-success #(re-frame/dispatch (success-event-creator %)) @@ -131,13 +131,13 @@ :content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message)) :confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again) :cancel-button-text (i18n/label :t/biometric-auth-confirm-logout) - :on-accept #(biometric-auth/authenticate on-biometric-auth-result authentication-options) + :on-accept #(biometric/authenticate on-biometric-auth-result authentication-options) :on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))) (fx/defn on-return-from-background [{:keys [db now] :as cofx}] (let [app-in-background-since (get db :app-in-background-since) signed-up? (get-in db [:multiaccount :signed-up?]) - biometric-auth? (get-in db [:multiaccount :settings :biometric-auth?]) + biometric-auth? (= (:auth-method db) "biometric") requires-bio-auth (and signed-up? biometric-auth? @@ -149,7 +149,7 @@ (mailserver/process-next-messages-request) (hardwallet/return-back-from-nfc-settings) #(when requires-bio-auth - (biometric-auth/authenticate-fx % on-biometric-auth-result authentication-options))))) + (biometric/authenticate % on-biometric-auth-result authentication-options))))) (fx/defn on-going-in-background [{:keys [db now] :as cofx}] (fx/merge cofx diff --git a/src/status_im/ui/screens/multiaccounts/login/styles.cljs b/src/status_im/ui/screens/multiaccounts/login/styles.cljs index cd13468f4c..1b04d50131 100644 --- a/src/status_im/ui/screens/multiaccounts/login/styles.cljs +++ b/src/status_im/ui/screens/multiaccounts/login/styles.cljs @@ -68,3 +68,11 @@ :text-align :center :flex-direction :row :align-items :center}) + +(def biometric-button + {:justify-content :center + :align-items :center + :height 40 + :width 40 + :border-radius 20 + :margin-left 16}) diff --git a/src/status_im/ui/screens/multiaccounts/login/views.cljs b/src/status_im/ui/screens/multiaccounts/login/views.cljs index 84b34f7626..594160a36c 100644 --- a/src/status_im/ui/screens/multiaccounts/login/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/login/views.cljs @@ -15,7 +15,8 @@ [status-im.ui.screens.multiaccounts.styles :as ast] [status-im.utils.platform :as platform] [status-im.utils.security :as security] - [status-im.utils.utils :as utils]) + [status-im.utils.utils :as utils] + [status-im.ui.components.icons.vector-icons :as icons]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn login-toolbar [can-navigate-back?] @@ -46,11 +47,13 @@ (utils/get-shortened-address public-key)]]]) (defview login [] - (letsubs [{:keys [error processing save-password? can-save-password?] :as multiaccount} [:multiaccounts/login] + (letsubs [{:keys [error processing save-password?] :as multiaccount} [:multiaccounts/login] can-navigate-back? [:can-navigate-back?] password-text-input (atom nil) sign-in-enabled? [:sign-in-enabled?] - view-id [:view-id]] + auth-method [:auth-method] + view-id [:view-id] + supported-biometric-auth [:supported-biometric-auth]] [react/keyboard-avoiding-view {:style ast/multiaccounts-view} [status-bar/status-bar] [login-toolbar can-navigate-back?] @@ -59,32 +62,38 @@ [multiaccount-login-badge multiaccount] [react/view {:style styles/password-container :important-for-accessibility :no-hide-descendants} - [text-input/text-input-with-label - {:placeholder (i18n/label :t/enter-your-password) - :ref #(reset! password-text-input %) - :auto-focus (= view-id :login) - :accessibility-label :password-input - :on-submit-editing (when sign-in-enabled? - #(login-multiaccount @password-text-input)) - :on-change-text #(do - (re-frame/dispatch [:set-in [:multiaccounts/login :password] - (security/mask-data %)]) - (re-frame/dispatch [:set-in [:multiaccounts/login :error] ""])) - :secure-text-entry true - :error (when (not-empty error) error)}]] - + [react/view {:flex-direction :row :align-items :center} + [react/view {:flex 1} + [text-input/text-input-with-label + {:placeholder (i18n/label :t/enter-your-password) + :ref #(reset! password-text-input %) + :auto-focus (= view-id :login) + :accessibility-label :password-input + :on-submit-editing (when sign-in-enabled? + #(login-multiaccount @password-text-input)) + :on-change-text #(do + (re-frame/dispatch [:set-in [:multiaccounts/login :password] + (security/mask-data %)]) + (re-frame/dispatch [:set-in [:multiaccounts/login :error] ""])) + :secure-text-entry true + :error (when (not-empty error) error)}]] + (when (and supported-biometric-auth (= auth-method "biometric")) + [react/touchable-highlight {:on-press #(re-frame/dispatch [:biometric-authenticate])} + [react/view {:style styles/biometric-button} + [icons/icon (if (= supported-biometric-auth :FaceID) :faceid :print)]]])]] (when-not platform/desktop? ;; saving passwords is unavailable on Desktop - (if (and platform/android? (not can-save-password?)) + (if (and platform/android? (not auth-method)) ;; on Android, there is much more reasons for the password save to be unavailable, ;; so we don't show the checkbox whatsoever but put a label explaining why it happenned. [react/i18n-text {:style styles/save-password-unavailable-android - :key :save-password-unavailable-android}] + :key :save-password-unavailable-android}] [react/view {:style {:flex-direction :row :align-items :center - :justify-content :flex-start}} - [checkbox/checkbox {:checked? save-password? - :style {:padding-left 0 :padding-right 10} + :justify-content :flex-start + :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?] %])}] [react/text (i18n/label :t/save-password)]]))]] (when processing diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index caf23e9db0..bb7a074f53 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -7,7 +7,8 @@ [status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase] [status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.profile.user.views :as profile.user] - [status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover])) + [status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover] + [status-im.ui.screens.biometric.views :as biometric])) (defn hide-panel-anim [bottom-anim-value alpha-value window-height] @@ -102,6 +103,9 @@ (= :custom-seed-phrase view) [multiaccounts.recover/custom-seed-phrase] + (= :enable-biometric view) + [biometric/enable-biometric-popover] + :else [view])]]]]])))}))) diff --git a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs index 1f1e77dbcb..79833638f5 100644 --- a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs @@ -8,10 +8,11 @@ [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.components.status-bar.view :as status-bar] - [status-im.ui.components.toolbar.view :as toolbar]) + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.multiaccounts.biometric.core :as biometric]) (:require-macros [status-im.utils.views :as views])) -(defn- list-data [show-backup-seed? settings] +(defn- list-data [show-backup-seed? settings supported-biometric-auth biometric-auth? keycard?] [{:type :section-header :title :t/security :container-margin-top 6} @@ -26,6 +27,18 @@ :accessories [(when show-backup-seed? [components.common/counter {:size 22} 1]) :chevron]} + {:type :small + :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?) + :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?)}]] + :on-press #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched + ((complement boolean) biometric-auth?)])} ;; TODO - uncomment when implemented ;; {:type :small ;; :title :t/change-password @@ -69,7 +82,10 @@ (views/defview privacy-and-security [] (views/letsubs [{:keys [seed-backed-up? mnemonic]} [:multiaccount] - settings [:multiaccount-settings]] + settings [:multiaccount-settings] + supported-biometric-auth [:supported-biometric-auth] + auth-method [:auth-method] + {:keys [keycard-key-uid]} [:multiaccount]] (let [show-backup-seed? (and (not seed-backed-up?) (not (string/blank? mnemonic)))] [react/view {:flex 1 :background-color colors/white} @@ -77,6 +93,7 @@ [toolbar/simple-toolbar (i18n/label :t/privacy-and-security)] [list/flat-list - {:data (list-data show-backup-seed? settings) + {:data (list-data show-backup-seed? settings supported-biometric-auth + (= auth-method "biometric") (boolean keycard-key-uid)) :key-fn (fn [_ i] (str i)) - :render-fn list/flat-list-generic-render-fn}]]))) + :render-fn list/flat-list-generic-render-fn}]]))) \ No newline at end of file diff --git a/src/status_im/utils/keychain/core.cljs b/src/status_im/utils/keychain/core.cljs index f994e07ed7..36e431148f 100644 --- a/src/status_im/utils/keychain/core.cljs +++ b/src/status_im/utils/keychain/core.cljs @@ -5,7 +5,9 @@ [status-im.utils.platform :as platform] [status-im.utils.security :as security] [status-im.native-module.core :as status] - [status-im.utils.handlers :as handlers])) + [status-im.utils.fx :as fx] + [goog.object :as object] + [clojure.string :as string])) (defn- check-conditions [callback & checks] (if (= (count checks) 0) @@ -28,7 +30,9 @@ ;; to an address (`server`) property. (defn enum-val [enum-name value-name] - (get-in (js->clj rn/keychain) [enum-name value-name])) + (-> rn/keychain + (object/get enum-name) + (object/get value-name))) ;; We need a more strict access mode for keychain entries that save user password. ;; iOS @@ -48,9 +52,9 @@ ;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. ;; That is exactly what we use there. ;; Note that the password won't be stored if the device isn't locked by a passcode. - {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")}) + #js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")}) -(def keychain-secure-hardware +(def ^:const keychain-secure-hardware ;; (Android) Requires storing the encryption key for the entry in secure hardware ;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention) "SECURE_HARDWARE") @@ -70,55 +74,54 @@ (defn- device-encrypted? [callback] (-> (.canImplyAuthentication rn/keychain - (clj->js - {:authenticationType - (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")})) + #js {:authenticationType (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}) (.then callback))) -;; Stores the password for the address to the Keychain -(defn save-user-password [address password callback] - (-> (.setInternetCredentials rn/keychain address address password keychain-secure-hardware (clj->js keychain-restricted-availability)) - (.then callback))) - -(defn handle-callback [callback result] - (if result - (callback (security/mask-data (.-password result))) - (callback nil))) - -;; Gets the password for a specified address from the Keychain -(defn get-user-password [address callback] - (if (or platform/ios? platform/android?) - (-> (.getInternetCredentials rn/keychain address) - (.then (partial handle-callback callback))) - (callback))) ;; no-op for Desktop - -;; Clears the password for a specified address from the Keychain -;; (example of usage is logout or signing in w/o "save-password") -(defn clear-user-password [address callback] - (if (or platform/ios? platform/android?) - (-> (.resetInternetCredentials rn/keychain address) - (.then callback)) - (callback true))) ;; no-op for Desktop - -;; Resolves to `false` if the device doesn't have neither a passcode nor a biometry auth. (defn can-save-user-password? [callback] (cond - platform/ios? (check-conditions callback - device-encrypted?) + platform/ios? + (check-conditions callback device-encrypted?) - platform/android? (check-conditions - callback - secure-hardware-available? - device-not-rooted?) + platform/android? + (check-conditions callback secure-hardware-available? device-not-rooted?) - :else (callback false))) + :else + (callback false))) -;;;; Effects +(defn save-credentials + "Stores the credentials for the address to the Keychain" + [server username password callback] + (-> (.setInternetCredentials rn/keychain (string/lower-case server) username password + keychain-secure-hardware keychain-restricted-availability) + (.then callback))) + +(defn get-credentials + "Gets the credentials for a specified server from the Keychain" + [server callback] + (if platform/mobile? + (-> (.getInternetCredentials rn/keychain (string/lower-case server)) + (.then callback)) + (callback))) ;; no-op for Desktop + +(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"))) + (callback nil)))))) + +(re-frame/reg-fx + :keychain/get-user-password + (fn [[address callback]] + (get-credentials address #(if % (callback (security/mask-data (.-password %))) (callback nil))))) (re-frame/reg-fx :keychain/save-user-password (fn [[address password]] - (save-user-password + (save-credentials + address address (security/safe-unmask-data password) #(when-not % @@ -129,26 +132,41 @@ "but you will have to login again next time you launch it.")))))) (re-frame/reg-fx - :keychain/get-user-password - (fn [[address callback]] - (get-user-password address callback))) + :keychain/save-auth-method + (fn [[address method]] + (save-credentials + (str address "-auth") + address + method + #(when-not % + (log/error + (str "Error while saving auth method." + " " + "The app will continue to work normally, " + "but you will have to login again next time you launch it.")))))) (re-frame/reg-fx :keychain/clear-user-password (fn [address] - (clear-user-password - address - #(when-not % - (log/error (str "Error while clearing saved password.")))))) + (when platform/mobile? + (-> (.resetInternetCredentials rn/keychain (string/lower-case address)) + (.then #(when-not % (log/error (str "Error while clearing saved password.")))))))) -(re-frame/reg-fx - :keychain/can-save-user-password? - (fn [_] - (can-save-user-password? #(re-frame/dispatch [:keychain.callback/can-save-user-password?-success %])))) +(fx/defn get-auth-method + [_ address] + {:keychain/get-auth-method + [address #(re-frame/dispatch [:multiaccounts.login/get-auth-method-success % address])]}) -(handlers/register-handler-fx - :keychain.callback/can-save-user-password?-success - (fn [{:keys [db]} [_ can-save-user-password?]] - {:db (assoc-in db - [:multiaccounts/login :can-save-password?] - can-save-user-password?)})) +(fx/defn get-user-password + [_ address] + {:keychain/get-user-password + [address #(re-frame/dispatch [:multiaccounts.login.callback/get-user-password-success % address])]}) + +(fx/defn save-user-password + [cofx address password] + {:keychain/save-user-password [address password]}) + +(fx/defn save-auth-method + [{:keys [db]} address method] + {:db (assoc db :auth-method method) + :keychain/save-auth-method [address method]}) \ No newline at end of file diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index 0a11242d5e..89a8d326ae 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -44,7 +44,9 @@ :clearTimeout js/clearTimeout :clearInterval js/clearInterval}) -(def keychain #js {:setGenericPassword (constantly (.resolve js/Promise true))}) +(def keychain #js {:setGenericPassword (constantly (.resolve js/Promise true)) + "ACCESSIBLE" {} + "ACCESS_CONTROL" {}}) (def react-navigation #js {:NavigationActions #js {}}) (def desktop-menu #js {}) (def desktop-config #js {}) diff --git a/translations/en.json b/translations/en.json index fbf6962afb..4fb0d33d0a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -341,7 +341,7 @@ "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", "empty-chat-description-public": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ", "empty-chat-description-public-share-this": "share this chat.", - "enable": "enable", + "enable": "Enable", "encrypt-with-password": "Encrypt with password", "ens-10-SNT": "10 SNT", "ens-add-username": "Add username", @@ -1100,5 +1100,9 @@ "custom-seed-phrase-text-1": "This looks like a custom seed phrase and doesn't match the Status dictionary. This could also mean ", "custom-seed-phrase-text-2": "some words are misspelled.", "custom-seed-phrase-text-3": " If so, you'll end up creating a", - "custom-seed-phrase-text-4": " new account" + "custom-seed-phrase-text-4": " new account", + "to-enable-biometric": "To enable {{bio-type-label}}, your must save your password on the unlock screen", + "ok-save-pass": "OK, save password", + "lock-app-with": "Lock app with", + "grant-face-id-permissions": "To grant the required Face ID permission, please go to your system settings and make sure that Status > Face ID is selected" }