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

View File

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

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?}
{}))))
(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?}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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-in-background-since
::hardwallet
::auth-method
:multiaccount/multiaccount
:navigation/view-id
:navigation/navigation-stack

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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