Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2019-09-15 17:20:10 +02:00
parent da4fb01700
commit b3d04bb68c
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
37 changed files with 504 additions and 443 deletions

View File

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

24
ICONS.md Normal file
View File

@ -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 ...}]
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

@ -7,7 +7,7 @@
[status-im.multiaccounts.logout.core :as multiaccounts.logout] [status-im.multiaccounts.logout.core :as multiaccounts.logout]
[status-im.multiaccounts.recover.core :as multiaccounts.recover] [status-im.multiaccounts.recover.core :as multiaccounts.recover]
[status-im.multiaccounts.update.core :as multiaccounts.update] [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.bootnodes.core :as bootnodes]
[status-im.browser.core :as browser] [status-im.browser.core :as browser]
[status-im.browser.permissions :as browser.permissions] [status-im.browser.permissions :as browser.permissions]
@ -120,20 +120,6 @@
:on-accept open-chaos-unicorn-day-link}}) :on-accept open-chaos-unicorn-day-link}})
(multiaccounts/switch-chaos-mode chaos-mode?))))) (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 (handlers/register-handler-fx
:multiaccounts.ui/notifications-enabled :multiaccounts.ui/notifications-enabled
(fn [cofx [_ desktop-notifications?]] (fn [cofx [_ desktop-notifications?]]
@ -175,14 +161,6 @@
(fn [cofx [_ address photo-path name public-key]] (fn [cofx [_ address photo-path name public-key]]
(multiaccounts.login/open-login 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 ;; multiaccounts logout module
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -1,6 +1,5 @@
(ns status-im.init.core (ns status-im.init.core
(:require [re-frame.core :as re-frame] (: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.multiaccounts.login.core :as multiaccounts.login]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.network.net-info :as network] [status-im.network.net-info :as network]
@ -33,11 +32,6 @@
:view-id view-id :view-id view-id
:push-notifications/stored stored)}) :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 (fx/defn initialize-views
[cofx] [cofx]
(let [{{:multiaccounts/keys [multiaccounts] :as db} :db} cofx] (let [{{:multiaccounts/keys [multiaccounts] :as db} :db} cofx]
@ -75,7 +69,7 @@
(fx/defn start-app [cofx] (fx/defn start-app [cofx]
(fx/merge cofx (fx/merge cofx
{::get-supported-biometric-auth nil {:get-supported-biometric-auth nil
::init-keystore nil ::init-keystore nil
::restore-native-settings nil ::restore-native-settings nil
::open-multiaccounts #(re-frame/dispatch [::initialize-multiaccounts %]) ::open-multiaccounts #(re-frame/dispatch [::initialize-multiaccounts %])
@ -101,8 +95,3 @@
::init-keystore ::init-keystore
(fn [] (fn []
(status/init-keystore))) (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 %]))))

View File

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

View File

@ -66,15 +66,6 @@
(multiaccounts.update/multiaccount-update {:chaos-mode? chaos-mode?} (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?] (fx/defn enable-notifications [cofx desktop-notifications?]
(multiaccounts.update/multiaccount-update cofx (multiaccounts.update/multiaccount-update cofx
{:desktop-notifications? desktop-notifications?} {:desktop-notifications? desktop-notifications?}

View File

@ -1,6 +1,5 @@
(ns status-im.multiaccounts.login.core (ns status-im.multiaccounts.login.core
(:require [re-frame.core :as re-frame] (: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.chaos-mode.core :as chaos-mode]
[status-im.chat.models :as chat-model] [status-im.chat.models :as chat-model]
[status-im.chat.models.loading :as chat.loading] [status-im.chat.models.loading :as chat.loading]
@ -27,7 +26,9 @@
[status-im.utils.universal-links.core :as universal-links] [status-im.utils.universal-links.core :as universal-links]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet] [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 rpc-endpoint "https://goerli.infura.io/v3/f315575765b14720b32382a61a89341a")
(def contract-address "0xfbf4c8e2B41fAfF8c616a0E49Fb4365a5355Ffaf") (def contract-address "0xfbf4c8e2B41fAfF8c616a0E49Fb4365a5355Ffaf")
@ -49,7 +50,11 @@
(resolve default-nodes))))) (resolve default-nodes)))))
(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/defn initialize-wallet [cofx]
(fx/merge cofx (fx/merge cofx
(wallet/initialize-tokens) (wallet/initialize-tokens)
@ -98,10 +103,6 @@
[{:keys [db]} node-version] [{:keys [db]} node-version]
{:db (assoc db :web3-node-version 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 (fx/defn handle-close-app-confirmed
{:events [::close-app-confirmed]} {:events [::close-app-confirmed]}
[_] [_]
@ -156,7 +157,12 @@
(fx/defn login-only-events (fx/defn login-only-events
[{:keys [db] :as cofx} address password save-password?] [{: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 (fx/merge cofx
{:db (assoc db :chats/loading? true) {:db (assoc db :chats/loading? true)
::json-rpc/call ::json-rpc/call
@ -174,7 +180,9 @@
:params [["multiaccount" "current-network" "networks"]] :params [["multiaccount" "current-network" "networks"]]
:on-success #(re-frame/dispatch [::get-config-callback % stored-pns])}]} :on-success #(re-frame/dispatch [::get-config-callback % stored-pns])}]}
(when save-password? (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) (navigation/navigate-to-cofx :home nil)
(when platform/desktop? (when platform/desktop?
(chat-model/update-dock-badge-label))))) (chat-model/update-dock-badge-label)))))
@ -263,12 +271,6 @@
(navigation/navigate-to-cofx :multiaccounts nil) (navigation/navigate-to-cofx :multiaccounts nil)
(navigation/navigate-to-cofx :keycard-login-pin 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 (fx/defn open-login
[{:keys [db] :as cofx} address photo-path name public-key] [{:keys [db] :as cofx} address photo-path name public-key]
(let [keycard-multiaccount? (get-in db [:multiaccounts/multiaccounts address :keycard-key-uid])] (let [keycard-multiaccount? (get-in db [:multiaccounts/multiaccounts address :keycard-key-uid])]
@ -284,31 +286,41 @@
:password))} :password))}
(if keycard-multiaccount? (if keycard-multiaccount?
(open-keycard-login) (open-keycard-login)
(get-user-password address))))) (keychain/get-auth-method address)))))
(fx/defn open-login-callback (fx/defn open-login-callback
{:events [::biometric-auth-done]} {:events [:multiaccounts.login.callback/get-user-password-success]}
[{:keys [db] :as cofx} password {:keys [bioauth-success bioauth-notrequired bioauth-message]}] [{:keys [db] :as cofx} password]
(if (and password (if password
(or bioauth-success bioauth-notrequired))
(fx/merge cofx (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) (navigation/navigate-to-cofx :progress nil)
login) 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 (fx/merge cofx
(when bioauth-message {:db (assoc db :auth-method auth-method)}
{:utils/show-popup {:title (i18n/label :t/biometric-auth-login-error-title) :content bioauth-message}}) #(case auth-method
(navigation/navigate-to-cofx :login nil)))) "biometric"
(biometric/biometric-auth %)
"password"
(keychain/get-user-password % address)
(fx/defn do-biometric-auth ;;nil or "none" or "biometric-prepare"
[{:keys [db] :as cofx} password] (open-login-callback % nil)))))
(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)}))
(re-frame/reg-fx (fx/defn biometric-auth-done
::login {:events [:biometric-auth-done]}
(fn [[account-data hashed-password]] [{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(status/login account-data (let [address (get-in db [:multiaccounts/login :address])]
hashed-password))) (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)))))

View File

@ -6,19 +6,23 @@
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.transport.core :as transport] [status-im.transport.core :as transport]
[status-im.utils.fx :as fx] [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 (fx/defn logout
{:events [:logout]} {:events [:logout]}
[{:keys [db] :as cofx}] [cofx]
(fx/merge cofx (logout-method cofx "none"))
{::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)))
(fx/defn show-logout-confirmation [_] (fx/defn show-logout-confirmation [_]
{:ui/show-confirmation {:ui/show-confirmation
@ -28,6 +32,14 @@
:on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) :on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
:on-cancel nil}}) :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 (re-frame/reg-fx
::logout ::logout
(fn [] (fn []

View File

@ -1,6 +1,5 @@
(ns status-im.popover.core (ns status-im.popover.core
(:require [status-im.utils.fx :as fx] (:require [status-im.utils.fx :as fx]))
[status-im.utils.handlers :as handlers]))
(fx/defn show-popover (fx/defn show-popover
{:events [:show-popover]} {:events [:show-popover]}

View File

@ -183,6 +183,8 @@
(reg-root-key-sub :keycard :hardwallet) (reg-root-key-sub :keycard :hardwallet)
(reg-root-key-sub :auth-method :auth-method)
;;GENERAL ============================================================================================================== ;;GENERAL ==============================================================================================================
(re-frame/reg-sub (re-frame/reg-sub

View File

@ -45,9 +45,9 @@
Spec: https://www.figma.com/file/cb4p8AxLtTF3q1L6JYDnKN15/Index?node-id=858%3A0" 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)] (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 (when accessibility-label
{:accessibility-label accessibility-label})) {:accessibility-label accessibility-label}))
[react/view {:style (style-container type disabled?)} [react/view {:style (style-container type disabled?)}

View File

@ -57,7 +57,7 @@
:disabled false}]]} :disabled false}]]}
{:type :divider}]) {: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 [{:container-margin-top 8
:type :section-header :type :section-header
:title :t/dev-mode-settings} :title :t/dev-mode-settings}
@ -117,47 +117,25 @@
#(re-frame/dispatch #(re-frame/dispatch
[:multiaccounts.ui/chaos-mode-switched (not chaos-mode?)]) [:multiaccounts.ui/chaos-mode-switched (not chaos-mode?)])
:disabled false}]]} :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}]]) [react/view {:height 24}]])
(defn- flat-list-data [network-name current-log-level current-fleet (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? (if dev-mode?
(into (into
(normal-mode-settings-data (normal-mode-settings-data
network-name current-log-level current-fleet dev-mode?) network-name current-log-level current-fleet dev-mode?)
(dev-mode-settings-data (dev-mode-settings-data
settings chaos-mode? supported-biometric-auth)) settings chaos-mode?))
;; else ;; else
(normal-mode-settings-data (normal-mode-settings-data
network-name current-log-level current-fleet dev-mode?))) network-name current-log-level current-fleet dev-mode?)))
(views/defview advanced-settings [] (views/defview advanced-settings []
(views/letsubs [{:keys (views/letsubs [{:keys [chaos-mode? dev-mode? settings]} [:multiaccount]
[chaos-mode?
dev-mode?
settings]
:as current-multiaccount} [:multiaccount]
settings [:multiaccount-settings]
network-name [:network-name] network-name [:network-name]
current-log-level [:settings/current-log-level] current-log-level [:settings/current-log-level]
current-fleet [:settings/current-fleet] current-fleet [:settings/current-fleet]]
supported-biometric-auth [:supported-biometric-auth]]
[react/view {:flex 1 :background-color colors/white} [react/view {:flex 1 :background-color colors/white}
[status-bar/status-bar] [status-bar/status-bar]
[toolbar/simple-toolbar [toolbar/simple-toolbar
@ -166,7 +144,7 @@
{:data (flat-list-data {:data (flat-list-data
network-name current-log-level network-name current-log-level
current-fleet dev-mode? settings current-fleet dev-mode? settings
chaos-mode? supported-biometric-auth) chaos-mode?)
:key-fn (fn [_ i] (str i)) :key-fn (fn [_ i] (str i))
:render-fn list/flat-list-generic-render-fn}]])) :render-fn list/flat-list-generic-render-fn}]]))

View File

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

View File

@ -281,6 +281,7 @@
::app-state ::app-state
::app-in-background-since ::app-in-background-since
::hardwallet ::hardwallet
::auth-method
:multiaccount/multiaccount :multiaccount/multiaccount
:navigation/view-id :navigation/view-id
:navigation/navigation-stack :navigation/navigation-stack

View File

@ -20,8 +20,8 @@
[status-im.utils.http :as http] [status-im.utils.http :as http]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.i18n :as i18n] [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]}] (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %)) (let [on-success #(re-frame/dispatch (success-event-creator %))
@ -131,13 +131,13 @@
:content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message)) :content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message))
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again) :confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout) :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])})))) :on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])}))))
(fx/defn on-return-from-background [{:keys [db now] :as cofx}] (fx/defn on-return-from-background [{:keys [db now] :as cofx}]
(let [app-in-background-since (get db :app-in-background-since) (let [app-in-background-since (get db :app-in-background-since)
signed-up? (get-in db [:multiaccount :signed-up?]) 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 requires-bio-auth (and
signed-up? signed-up?
biometric-auth? biometric-auth?
@ -149,7 +149,7 @@
(mailserver/process-next-messages-request) (mailserver/process-next-messages-request)
(hardwallet/return-back-from-nfc-settings) (hardwallet/return-back-from-nfc-settings)
#(when requires-bio-auth #(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/defn on-going-in-background [{:keys [db now] :as cofx}]
(fx/merge cofx (fx/merge cofx

View File

@ -68,3 +68,11 @@
:text-align :center :text-align :center
:flex-direction :row :flex-direction :row
:align-items :center}) :align-items :center})
(def biometric-button
{:justify-content :center
:align-items :center
:height 40
:width 40
:border-radius 20
:margin-left 16})

View File

@ -15,7 +15,8 @@
[status-im.ui.screens.multiaccounts.styles :as ast] [status-im.ui.screens.multiaccounts.styles :as ast]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.security :as security] [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]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn login-toolbar [can-navigate-back?] (defn login-toolbar [can-navigate-back?]
@ -46,11 +47,13 @@
(utils/get-shortened-address public-key)]]]) (utils/get-shortened-address public-key)]]])
(defview login [] (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?] can-navigate-back? [:can-navigate-back?]
password-text-input (atom nil) password-text-input (atom nil)
sign-in-enabled? [:sign-in-enabled?] 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} [react/keyboard-avoiding-view {:style ast/multiaccounts-view}
[status-bar/status-bar] [status-bar/status-bar]
[login-toolbar can-navigate-back?] [login-toolbar can-navigate-back?]
@ -59,6 +62,8 @@
[multiaccount-login-badge multiaccount] [multiaccount-login-badge multiaccount]
[react/view {:style styles/password-container [react/view {:style styles/password-container
:important-for-accessibility :no-hide-descendants} :important-for-accessibility :no-hide-descendants}
[react/view {:flex-direction :row :align-items :center}
[react/view {:flex 1}
[text-input/text-input-with-label [text-input/text-input-with-label
{:placeholder (i18n/label :t/enter-your-password) {:placeholder (i18n/label :t/enter-your-password)
:ref #(reset! password-text-input %) :ref #(reset! password-text-input %)
@ -72,19 +77,23 @@
(re-frame/dispatch [:set-in [:multiaccounts/login :error] ""])) (re-frame/dispatch [:set-in [:multiaccounts/login :error] ""]))
:secure-text-entry true :secure-text-entry true
:error (when (not-empty error) error)}]] :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? (when-not platform/desktop?
;; saving passwords is unavailable on 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, ;; 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. ;; 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 [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 [react/view {:style {:flex-direction :row
:align-items :center :align-items :center
:justify-content :flex-start}} :justify-content :flex-start
:margin-top 19}}
[checkbox/checkbox {:checked? save-password? [checkbox/checkbox {:checked? save-password?
:style {:padding-left 0 :padding-right 10} :style {:margin-left 3 :margin-right 10}
:on-value-change #(re-frame/dispatch [:set-in [:multiaccounts/login :save-password?] %])}] :on-value-change #(re-frame/dispatch [:set-in [:multiaccounts/login :save-password?] %])}]
[react/text (i18n/label :t/save-password)]]))]] [react/text (i18n/label :t/save-password)]]))]]
(when processing (when processing

View File

@ -7,7 +7,8 @@
[status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase] [status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase]
[status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.profile.user.views :as profile.user] [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 (defn hide-panel-anim
[bottom-anim-value alpha-value window-height] [bottom-anim-value alpha-value window-height]
@ -102,6 +103,9 @@
(= :custom-seed-phrase view) (= :custom-seed-phrase view)
[multiaccounts.recover/custom-seed-phrase] [multiaccounts.recover/custom-seed-phrase]
(= :enable-biometric view)
[biometric/enable-biometric-popover]
:else :else
[view])]]]]])))}))) [view])]]]]])))})))

View File

@ -8,10 +8,11 @@
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar] [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])) (: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 [{:type :section-header
:title :t/security :title :t/security
:container-margin-top 6} :container-margin-top 6}
@ -26,6 +27,18 @@
:accessories :accessories
[(when show-backup-seed? [components.common/counter {:size 22} 1]) [(when show-backup-seed? [components.common/counter {:size 22} 1])
:chevron]} :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 ;; TODO - uncomment when implemented
;; {:type :small ;; {:type :small
;; :title :t/change-password ;; :title :t/change-password
@ -69,7 +82,10 @@
(views/defview privacy-and-security [] (views/defview privacy-and-security []
(views/letsubs [{:keys [seed-backed-up? mnemonic]} [:multiaccount] (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?) (let [show-backup-seed? (and (not seed-backed-up?)
(not (string/blank? mnemonic)))] (not (string/blank? mnemonic)))]
[react/view {:flex 1 :background-color colors/white} [react/view {:flex 1 :background-color colors/white}
@ -77,6 +93,7 @@
[toolbar/simple-toolbar [toolbar/simple-toolbar
(i18n/label :t/privacy-and-security)] (i18n/label :t/privacy-and-security)]
[list/flat-list [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)) :key-fn (fn [_ i] (str i))
:render-fn list/flat-list-generic-render-fn}]]))) :render-fn list/flat-list-generic-render-fn}]])))

View File

@ -5,7 +5,9 @@
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.security :as security] [status-im.utils.security :as security]
[status-im.native-module.core :as status] [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] (defn- check-conditions [callback & checks]
(if (= (count checks) 0) (if (= (count checks) 0)
@ -28,7 +30,9 @@
;; to an address (`server`) property. ;; to an address (`server`) property.
(defn enum-val [enum-name value-name] (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. ;; We need a more strict access mode for keychain entries that save user password.
;; iOS ;; iOS
@ -48,9 +52,9 @@
;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. ;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.
;; That is exactly what we use there. ;; That is exactly what we use there.
;; Note that the password won't be stored if the device isn't locked by a passcode. ;; 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 ;; (Android) Requires storing the encryption key for the entry in secure hardware
;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention) ;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention)
"SECURE_HARDWARE") "SECURE_HARDWARE")
@ -70,55 +74,54 @@
(defn- device-encrypted? [callback] (defn- device-encrypted? [callback]
(-> (.canImplyAuthentication (-> (.canImplyAuthentication
rn/keychain rn/keychain
(clj->js #js {:authenticationType (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")})
{:authenticationType
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
(.then callback))) (.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] (defn can-save-user-password? [callback]
(cond (cond
platform/ios? (check-conditions callback platform/ios?
device-encrypted?) (check-conditions callback device-encrypted?)
platform/android? (check-conditions platform/android?
callback (check-conditions callback secure-hardware-available? device-not-rooted?)
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 (re-frame/reg-fx
:keychain/save-user-password :keychain/save-user-password
(fn [[address password]] (fn [[address password]]
(save-user-password (save-credentials
address
address address
(security/safe-unmask-data password) (security/safe-unmask-data password)
#(when-not % #(when-not %
@ -129,26 +132,41 @@
"but you will have to login again next time you launch it.")))))) "but you will have to login again next time you launch it."))))))
(re-frame/reg-fx (re-frame/reg-fx
:keychain/get-user-password :keychain/save-auth-method
(fn [[address callback]] (fn [[address method]]
(get-user-password address callback))) (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 (re-frame/reg-fx
:keychain/clear-user-password :keychain/clear-user-password
(fn [address] (fn [address]
(clear-user-password (when platform/mobile?
address (-> (.resetInternetCredentials rn/keychain (string/lower-case address))
#(when-not % (.then #(when-not % (log/error (str "Error while clearing saved password."))))))))
(log/error (str "Error while clearing saved password."))))))
(re-frame/reg-fx (fx/defn get-auth-method
:keychain/can-save-user-password? [_ address]
(fn [_] {:keychain/get-auth-method
(can-save-user-password? #(re-frame/dispatch [:keychain.callback/can-save-user-password?-success %])))) [address #(re-frame/dispatch [:multiaccounts.login/get-auth-method-success % address])]})
(handlers/register-handler-fx (fx/defn get-user-password
:keychain.callback/can-save-user-password?-success [_ address]
(fn [{:keys [db]} [_ can-save-user-password?]] {:keychain/get-user-password
{:db (assoc-in db [address #(re-frame/dispatch [:multiaccounts.login.callback/get-user-password-success % address])]})
[:multiaccounts/login :can-save-password?]
can-save-user-password?)})) (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]})

View File

@ -44,7 +44,9 @@
:clearTimeout js/clearTimeout :clearTimeout js/clearTimeout
:clearInterval js/clearInterval}) :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 react-navigation #js {:NavigationActions #js {}})
(def desktop-menu #js {}) (def desktop-menu #js {})
(def desktop-config #js {}) (def desktop-config #js {})

View File

@ -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-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": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ",
"empty-chat-description-public-share-this": "share this chat.", "empty-chat-description-public-share-this": "share this chat.",
"enable": "enable", "enable": "Enable",
"encrypt-with-password": "Encrypt with password", "encrypt-with-password": "Encrypt with password",
"ens-10-SNT": "10 SNT", "ens-10-SNT": "10 SNT",
"ens-add-username": "Add username", "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-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-2": "some words are misspelled.",
"custom-seed-phrase-text-3": " If so, you'll end up creating a", "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"
} }