mirror of
https://github.com/status-im/status-react.git
synced 2025-02-22 15:48:50 +00:00
Implement "Save Password" for iOS.
Update react-native-keychain to 3.0.0 release. Signed-off-by: Igor Mandrigin <i@mandrigin.ru>
This commit is contained in:
parent
cf0a49b3b1
commit
02545cc3d0
@ -38,7 +38,7 @@ PODS:
|
||||
- React
|
||||
- React/Core (0.55.4):
|
||||
- yoga (= 0.55.4.React)
|
||||
- RNKeychain (3.0.0-rc.3):
|
||||
- RNKeychain (3.0.0):
|
||||
- React
|
||||
- yoga (0.55.4.React)
|
||||
|
||||
@ -83,6 +83,6 @@ SPEC CHECKSUMS:
|
||||
RNKeychain: 627c6095cef215dd3d9804a9a9cf45ab96aa3997
|
||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||
|
||||
PODFILE CHECKSUM: 7d2c351f8b90d3ad2b11fdcfde727875f4c2eebf
|
||||
PODFILE CHECKSUM: e5ecd57d8448253eb06b209c4e1c5230a2634a1d
|
||||
|
||||
COCOAPODS: 1.4.0
|
||||
|
6
mobile_files/package-lock.json
generated
6
mobile_files/package-lock.json
generated
@ -6739,9 +6739,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-keychain": {
|
||||
"version": "3.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-3.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-ijWfHmxTPKnrHtPJiDbKW3D6lRH8O9wbCNEE3xlxEg1WZT+VhP6iiF+HUansNYuxL7Hh7k41GSFfvr3xumfmXA=="
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-3.0.0.tgz",
|
||||
"integrity": "sha512-0incABt1+aXsZvG34mDV57KKanSB+iMHmxvWv+N6lgpNLaSoqrCxazjbZdeqD4qJ7Z+Etp5CLf/4v1aI+sNLBw=="
|
||||
},
|
||||
"react-native-level-fs": {
|
||||
"version": "3.0.0",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"react-native-image-crop-picker": "0.18.1",
|
||||
"react-native-image-resizer": "1.0.0",
|
||||
"react-native-invertible-scroll-view": "1.1.0",
|
||||
"react-native-keychain": "3.0.0-rc.3",
|
||||
"react-native-keychain": "^3.0.0",
|
||||
"react-native-level-fs": "3.0.0",
|
||||
"react-native-os": "1.1.0",
|
||||
"react-native-qrcode": "0.2.6",
|
||||
|
@ -174,7 +174,7 @@
|
||||
:transaction-moved-title :photos-access-error :hash
|
||||
:removed-from-chat :done :remove-from-contacts :delete-chat :new-group-chat
|
||||
:edit-chats :wallet :wallet-exchange :wallet-request :sign-in
|
||||
:datetime-yesterday :create-new-account :sign-in-to-status :dapp-profile
|
||||
:datetime-yesterday :create-new-account :sign-in-to-status :save-password :save-password-unavailable :dapp-profile
|
||||
:sign-later-text :datetime-ago :no-hashtags-discovered-body :contacts
|
||||
:search-chat :got-it :delete-group-confirmation :public-chats
|
||||
:not-applicable :move-to-internal-failure-message :active-online
|
||||
|
@ -385,6 +385,8 @@
|
||||
:sign-in-to-status "Sign in to Status"
|
||||
:sign-in "Sign in"
|
||||
:sign-in-to-another "Sign in to another account"
|
||||
:save-password "Save password until logout"
|
||||
:save-password-unavailable "Set device passcode to save password"
|
||||
:wrong-password "Wrong password"
|
||||
:enter-password "Enter password"
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.tooltip.views :as tooltip]))
|
||||
|
||||
(defn text-input-with-label [{:keys [label content error style height container] :as props}]
|
||||
(defn text-input-with-label [{:keys [label content error style height container text] :as props}]
|
||||
[react/view
|
||||
(when label
|
||||
[react/text {:style styles/label}
|
||||
@ -15,6 +15,7 @@
|
||||
{:style (merge styles/input style)
|
||||
:placeholder-text-color colors/gray
|
||||
:auto-focus true
|
||||
:value text
|
||||
:auto-capitalize :none}
|
||||
(dissoc props :style :height))]
|
||||
(when content content)]
|
||||
|
@ -2,6 +2,7 @@
|
||||
(:require status-im.ui.screens.accounts.login.navigation
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]
|
||||
[status-im.ui.screens.accounts.login.models :as models]))
|
||||
|
||||
;;;; FX
|
||||
@ -10,8 +11,8 @@
|
||||
|
||||
(re-frame/reg-fx
|
||||
:login
|
||||
(fn [[address password]]
|
||||
(models/login! address password)))
|
||||
(fn [[address password save-password]]
|
||||
(models/login! address password save-password)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:clear-web-data
|
||||
@ -29,27 +30,36 @@
|
||||
(fn [cofx [_ address photo-path name]]
|
||||
(models/open-login address photo-path name cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:do-login
|
||||
(fn [cofx [_ address photo-path name password]]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models/navigate-to-login address photo-path name password)
|
||||
;; models/login-account takes care about empty password
|
||||
(models/login-account address password (not (empty? password))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:login-account-internal
|
||||
(fn [cofx [_ address password]]
|
||||
(models/login-account-internal address password cofx)))
|
||||
(fn [cofx [_ address password save-password]]
|
||||
(models/login-account-internal address password save-password cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:start-node
|
||||
(fn [cofx [_ address password]]
|
||||
(models/start-node address password cofx)))
|
||||
(fn [cofx [_ address password save-password]]
|
||||
(models/start-node address password save-password cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:login-account
|
||||
(fn [cofx [_ address password]]
|
||||
(models/login-account address password cofx)))
|
||||
(fn [cofx [_ address password save-password]]
|
||||
(models/login-account address password save-password cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:login-handler
|
||||
(fn [cofx [_ login-result address]]
|
||||
(models/login-handler login-result address cofx)))
|
||||
(fn [cofx [_ login-result address password save-password]]
|
||||
(models/login-handler login-result address password save-password cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:change-account-handler
|
||||
(fn [cofx [_ address]]
|
||||
(models/change-account-handler address cofx)))
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.utils.keychain.core :as keychain]
|
||||
[status-im.utils.notifications :as notifications]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.universal-links.core :as universal-links]))
|
||||
|
||||
@ -14,8 +15,10 @@
|
||||
|
||||
(defn stop-node! [] (status/stop-node))
|
||||
|
||||
(defn login! [address password]
|
||||
(status/login address password #(re-frame/dispatch [:login-handler % address])))
|
||||
(defn login! [address password save-password]
|
||||
(status/login address
|
||||
password
|
||||
#(re-frame/dispatch [:login-handler % address password save-password])))
|
||||
|
||||
(defn clear-web-data! []
|
||||
(status/clear-web-data))
|
||||
@ -34,22 +37,27 @@
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(defn open-login [address photo-path name {db :db}]
|
||||
(defn navigate-to-login [address photo-path name password {db :db}]
|
||||
{:db (update db
|
||||
:accounts/login assoc
|
||||
:address address
|
||||
:photo-path photo-path
|
||||
:password password
|
||||
:name name)
|
||||
:can-save-user-password [#(re-frame/dispatch [:set-in [:accounts/login :can-save-password] %])]
|
||||
:dispatch [:navigate-to :login]})
|
||||
|
||||
(defn wrap-with-login-account-fx [db address password]
|
||||
(defn wrap-with-login-account-fx [db address password save-password]
|
||||
{:db db
|
||||
:login [address password]})
|
||||
:login [address password save-password]})
|
||||
|
||||
(defn login-account-internal [address password {db :db}]
|
||||
(defn open-login [address photo-path name cofx]
|
||||
{:get-user-password [address #(re-frame/dispatch [:do-login address photo-path name %])]})
|
||||
|
||||
(defn login-account-internal [address password save-password {db :db}]
|
||||
(wrap-with-login-account-fx
|
||||
(assoc db :node/after-start nil)
|
||||
address password))
|
||||
address password save-password))
|
||||
|
||||
(defn- add-custom-bootnodes [config network all-bootnodes]
|
||||
(let [bootnodes (as-> all-bootnodes $
|
||||
@ -77,17 +85,17 @@
|
||||
:network network
|
||||
:config config}))
|
||||
|
||||
(defn- wrap-with-initialize-geth-fx [db address password]
|
||||
(defn- wrap-with-initialize-geth-fx [db address password save-password]
|
||||
(let [{:keys [network config]} (get-network-by-address db address)]
|
||||
{:initialize-geth-fx config
|
||||
:db (assoc db
|
||||
:network network
|
||||
:node/after-start [:login-account-internal address password])}))
|
||||
:node/after-start [:login-account-internal address password save-password])}))
|
||||
|
||||
(defn start-node [address password {db :db}]
|
||||
(defn start-node [address password save-password {db :db}]
|
||||
(wrap-with-initialize-geth-fx
|
||||
(assoc db :node/after-stop nil)
|
||||
address password))
|
||||
address password save-password))
|
||||
|
||||
(defn- wrap-with-stop-node-fx [db address password]
|
||||
{:db (assoc db :node/after-stop [:start-node address password])
|
||||
@ -98,7 +106,7 @@
|
||||
(and config/bootnodes-settings-enabled?
|
||||
use-custom-bootnodes)))
|
||||
|
||||
(defn login-account [address password {{:keys [network status-node-started?] :as db} :db}]
|
||||
(defn login-account [address password save-password {{:keys [network status-node-started?] :as db} :db}]
|
||||
(let [{use-custom-bootnodes :use-custom-bootnodes
|
||||
account-network :network} (get-network-by-address db address)
|
||||
db' (-> db
|
||||
@ -113,17 +121,21 @@
|
||||
|
||||
:else
|
||||
wrap-with-stop-node-fx)]
|
||||
(wrap-fn db' address password)))
|
||||
(when-not (empty? password)
|
||||
(wrap-fn db' address password save-password))))
|
||||
|
||||
(defn login-handler [login-result address {db :db}]
|
||||
(defn login-handler [login-result address password save-password {db :db}]
|
||||
(let [data (types/json->clj login-result)
|
||||
error (:error data)
|
||||
success (zero? (count error))
|
||||
db' (assoc-in db [:accounts/login :processing] false)]
|
||||
(if success
|
||||
{:db db
|
||||
:clear-web-data nil
|
||||
:change-account [address]}
|
||||
(merge
|
||||
{:db db
|
||||
:clear-web-data nil
|
||||
:change-account [address]}
|
||||
(when save-password
|
||||
{:save-user-password [address password]}))
|
||||
{:db (assoc-in db' [:accounts/login :error] error)})))
|
||||
|
||||
(defn change-account-handler [address {{:keys [view-id] :as db} :db :as cofx}]
|
||||
|
@ -3,4 +3,4 @@
|
||||
|
||||
(defmethod nav/preload-data! :login
|
||||
[db]
|
||||
(update db :accounts/login dissoc :error :password))
|
||||
(update db :accounts/login dissoc :error))
|
||||
|
@ -38,3 +38,15 @@
|
||||
|
||||
(def password-container
|
||||
{:margin-top 24})
|
||||
|
||||
(def save-password-checkbox-container
|
||||
{:margin-top 0
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def save-password-unavailable
|
||||
{:margin-top 8
|
||||
:width "100%"
|
||||
:text-align :center
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
@ -2,6 +2,8 @@
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [clojure.string :as string]
|
||||
[status-im.ui.screens.accounts.styles :as ast]
|
||||
[status-im.ui.screens.profile.components.views :as profile.components]
|
||||
[status-im.ui.components.checkbox.view :as checkbox]
|
||||
[status-im.ui.components.text-input.view :as text-input]
|
||||
[status-im.ui.components.status-bar.view :as status-bar]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
@ -14,6 +16,7 @@
|
||||
[status-im.chat.views.photos :as photos]
|
||||
[re-frame.core :as re-frame]
|
||||
[cljs.spec.alpha :as spec]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.screens.accounts.db :as db]))
|
||||
|
||||
(defn login-toolbar [can-navigate-back?]
|
||||
@ -23,9 +26,9 @@
|
||||
[toolbar/nav-button act/default-back])
|
||||
[toolbar/content-title (i18n/label :t/sign-in-to-status)]])
|
||||
|
||||
(defn login-account [password-text-input address password]
|
||||
(defn login-account [password-text-input address password save-password]
|
||||
(.blur password-text-input)
|
||||
(re-frame/dispatch [:login-account address password]))
|
||||
(re-frame/dispatch [:login-account address password save-password]))
|
||||
|
||||
(defn- error-key [error]
|
||||
;; TODO Improve selection logic when status-go provide an error code
|
||||
@ -49,29 +52,49 @@
|
||||
name]]])
|
||||
|
||||
(defview login []
|
||||
(letsubs [{:keys [address photo-path name password error processing]} [:get :accounts/login]
|
||||
(letsubs [{:keys [address photo-path name password error processing save-password can-save-password]} [:get :accounts/login]
|
||||
can-navigate-back? [:can-navigate-back?]
|
||||
password-text-input (atom nil)]
|
||||
;; due to async nature of UI, this function can be called when
|
||||
;; :accounts/login is already removed
|
||||
;; (e.g. when the data is removed but we didn't navigate to the next screen)
|
||||
;; in that case, we will show a basic progress bar.
|
||||
[react/keyboard-avoiding-view {:style ast/accounts-view}
|
||||
[status-bar/status-bar]
|
||||
[login-toolbar can-navigate-back?]
|
||||
[components.common/separator]
|
||||
[react/view styles/login-view
|
||||
[react/view styles/login-badge-container
|
||||
[account-login-badge photo-path name]
|
||||
[react/view {:style styles/password-container
|
||||
:important-for-accessibility :no-hide-descendants}
|
||||
[text-input/text-input-with-label
|
||||
{:label (i18n/label :t/password)
|
||||
:placeholder (i18n/label :t/password)
|
||||
:ref #(reset! password-text-input %)
|
||||
:auto-focus can-navigate-back? ;;this needed because keyboard overlays testfairy alert
|
||||
:on-submit-editing #(login-account @password-text-input address password)
|
||||
:on-change-text #(do
|
||||
(re-frame/dispatch [:set-in [:accounts/login :password] %])
|
||||
(re-frame/dispatch [:set-in [:accounts/login :error] ""]))
|
||||
:secure-text-entry true
|
||||
:error (when (pos? (count error)) (i18n/label (error-key error)))}]]]]
|
||||
;; so, if this component is rendered with no data
|
||||
(if (or (empty? address) (empty? photo-path))
|
||||
;; we will show an activiy indicator
|
||||
[react/view styles/processing-view
|
||||
[components/activity-indicator {:animating true}]]
|
||||
;; otherwise, we will render it properly
|
||||
[react/view styles/login-badge-container
|
||||
[account-login-badge photo-path name]
|
||||
[react/view {:style styles/password-container
|
||||
:important-for-accessibility :no-hide-descendants}
|
||||
[text-input/text-input-with-label
|
||||
{:label (i18n/label :t/password)
|
||||
:placeholder (i18n/label :t/password)
|
||||
:ref #(reset! password-text-input %)
|
||||
:auto-focus can-navigate-back? ;;this needed because keyboard overlays testfairy alert
|
||||
:on-submit-editing #(login-account @password-text-input address password save-password)
|
||||
:on-change-text #(do
|
||||
(re-frame/dispatch [:set-in [:accounts/login :password] %])
|
||||
(re-frame/dispatch [:set-in [:accounts/login :error] ""]))
|
||||
:secure-text-entry true
|
||||
:text password
|
||||
:error (when (pos? (count error)) (i18n/label (error-key error)))}]]
|
||||
(when platform/ios?
|
||||
[react/view {:style styles/save-password-checkbox-container}
|
||||
[profile.components/settings-switch-item
|
||||
{:label-kw (if can-save-password
|
||||
:t/save-password
|
||||
:t/save-password-unavailable)
|
||||
:active? can-save-password
|
||||
:value save-password
|
||||
:action-fn #(re-frame/dispatch [:set-in [:accounts/login :save-password] %])}]])])]
|
||||
(when processing
|
||||
[react/view styles/processing-view
|
||||
[components/activity-indicator {:animating true}]
|
||||
@ -87,4 +110,4 @@
|
||||
{:forward? true
|
||||
:label (i18n/label :t/sign-in)
|
||||
:disabled? (not (spec/valid? ::db/password password))
|
||||
:on-press #(login-account @password-text-input address password)}]])]))
|
||||
:on-press #(login-account @password-text-input address password save-password)}]])]))
|
||||
|
@ -71,7 +71,7 @@
|
||||
(when-not (str/blank? pubkey)
|
||||
(handlers-macro/merge-fx cofx
|
||||
(add-account account)
|
||||
(login.models/login-account normalized-address password)))))
|
||||
(login.models/login-account normalized-address password false)))))
|
||||
|
||||
(defn load-accounts [{:keys [db all-accounts]}]
|
||||
(let [accounts (->> all-accounts
|
||||
|
@ -5,6 +5,7 @@
|
||||
status-im.protocol.handlers
|
||||
[status-im.ui.screens.accounts.models :as accounts.models]
|
||||
status-im.ui.screens.accounts.login.events
|
||||
[status-im.ui.screens.accounts.login.models :as login]
|
||||
status-im.ui.screens.accounts.recover.events
|
||||
[status-im.ui.screens.contacts.events :as contacts]
|
||||
status-im.ui.screens.add-new.new-chat.events
|
||||
@ -214,18 +215,17 @@
|
||||
(re-frame/dispatch [:initialize-app encryption-key]))
|
||||
:on-accept handle-reset-data})
|
||||
|
||||
(defn initialize-views [{{:accounts/keys [accounts] :as db} :db}]
|
||||
{:db (if (empty? accounts)
|
||||
(assoc db :view-id :intro :navigation-stack (list :intro))
|
||||
(let [{:keys [address photo-path name]} (first (sort-by :last-sign-in > (vals accounts)))]
|
||||
(-> db
|
||||
(assoc :view-id :login
|
||||
:navigation-stack (list :login))
|
||||
(update :accounts/login assoc
|
||||
:address address
|
||||
:photo-path photo-path
|
||||
:name name))))
|
||||
:handle-initial-push-notification-fx db})
|
||||
(defn initialize-views [cofx]
|
||||
(let [{{:accounts/keys [accounts] :as db} :db} cofx
|
||||
{:keys [address photo-path name]} (first (sort-by :last-sign-in > (vals accounts)))
|
||||
default-fx {:handle-initial-push-notification-fx db}]
|
||||
(if (nil? address)
|
||||
(handlers-macro/merge-fx cofx
|
||||
default-fx
|
||||
(navigation/navigate-to-clean :intro))
|
||||
(handlers-macro/merge-fx cofx
|
||||
default-fx
|
||||
(login/open-login address photo-path name)))))
|
||||
|
||||
(defn initialize-db
|
||||
"Initialize db to the initial state"
|
||||
@ -289,7 +289,8 @@
|
||||
(fn [{:keys [db] :as cofx} _]
|
||||
(let [{:transport/keys [chats]} db]
|
||||
(handlers-macro/merge-fx cofx
|
||||
{:dispatch [:initialize-keychain]}
|
||||
{:dispatch [:initialize-keychain]
|
||||
:clear-user-password [(get-in db [:account/account :address])]}
|
||||
(navigation/navigate-to-clean nil)
|
||||
(transport/stop-whisper)))))
|
||||
|
||||
|
@ -13,6 +13,81 @@
|
||||
(defn- string->js-array [s]
|
||||
(.parse js/JSON (.-password s)))
|
||||
|
||||
;; ********************************************************************************
|
||||
;; Storing / Retrieving a user password to/from Keychain
|
||||
;; ********************************************************************************
|
||||
;;
|
||||
;; We are using set/get/reset internet credentials there because they are bound
|
||||
;; to an address (`server`) property.
|
||||
|
||||
|
||||
(defn enum-val [enum-name value-name]
|
||||
(get-in (js->clj rn/keychain) [enum-name value-name]))
|
||||
|
||||
;; We need a more strict access mode for keychain entries that save user password.
|
||||
;; iOS
|
||||
;; see this article for more details:
|
||||
;; https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility?language=objc
|
||||
(def keychain-restricted-availability
|
||||
;; From Apple's documentation:
|
||||
;; > The kSecAttrAccessible attribute enables you to control item availability
|
||||
;; > relative to the lock state of the device.
|
||||
;; > It also lets you specify eligibility for restoration to a new device.
|
||||
;; > If the attribute ends with the string ThisDeviceOnly,
|
||||
;; > the item can be restored to the same device that created a backup,
|
||||
;; > but it isn’t migrated when restoring another device’s backup data.
|
||||
;; > ...
|
||||
;; > For extremely sensitive data
|
||||
;; > THAT YOU NEVER WANT STORED IN iCloud,
|
||||
;; > 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")})
|
||||
|
||||
;; Stores the password for the address to the Keychain
|
||||
(defn save-user-password [address password callback]
|
||||
(if-not platform/ios?
|
||||
(callback) ;; no-op on Androids (for now)
|
||||
(-> (.setInternetCredentials rn/keychain address address password
|
||||
(clj->js keychain-restricted-availability))
|
||||
(.then callback))))
|
||||
|
||||
(defn handle-callback [callback result]
|
||||
(if result
|
||||
(callback (.-password result))
|
||||
(callback "")))
|
||||
|
||||
;; Gets the password for a specified address from the Keychain
|
||||
(defn get-user-password [address callback]
|
||||
(if-not platform/ios?
|
||||
(callback "") ;; no-op on Androids (for now)
|
||||
(-> (.getInternetCredentials rn/keychain address)
|
||||
(.then (partial handle-callback callback)))))
|
||||
|
||||
;; 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-not platform/ios?
|
||||
(callback)
|
||||
(-> (.resetInternetCredentials rn/keychain address)
|
||||
(.then callback))))
|
||||
|
||||
;; Resolves to `false` if the device doesn't have neither a passcode nor a biometry auth.
|
||||
(defn can-save-user-password [callback]
|
||||
(if-not platform/ios?
|
||||
(callback false)
|
||||
(-> (.canImplyAuthentication
|
||||
rn/keychain
|
||||
(clj->js
|
||||
{:authenticationType
|
||||
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
|
||||
(.then callback))))
|
||||
|
||||
;; ********************************************************************************
|
||||
;; Storing / Retrieving the realm encryption key to/from the Keychain
|
||||
;; ********************************************************************************
|
||||
|
||||
|
||||
;; Smoke test key to make sure is ok, we noticed some non-random keys on
|
||||
;; some IOS devices. We check naively that there are no more than key-bytes/2
|
||||
;; identical characters.
|
||||
@ -77,3 +152,36 @@
|
||||
|
||||
(defn set-username []
|
||||
(when platform/desktop? (.setUsername rn/keychain username)))
|
||||
|
||||
;;;; Effects
|
||||
|
||||
(re-frame/reg-fx
|
||||
:save-user-password
|
||||
(fn [[address password]]
|
||||
(save-user-password
|
||||
address
|
||||
password
|
||||
#(when-not %
|
||||
(log/error
|
||||
(str "Error while saving password."
|
||||
" "
|
||||
"The app will continue to work normally, "
|
||||
"but you will have to login again next time you launch it."))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:get-user-password
|
||||
(fn [[address callback]]
|
||||
(get-user-password address callback)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:clear-user-password
|
||||
(fn [[address]]
|
||||
(clear-user-password
|
||||
address
|
||||
#(when-not %
|
||||
(log/error (str "Error while clearing saved password."))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:can-save-user-password
|
||||
(fn [[callback]]
|
||||
(can-save-user-password callback)))
|
||||
|
@ -3,6 +3,17 @@
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.ui.screens.accounts.login.models :as models]))
|
||||
|
||||
(deftest login-account-internal
|
||||
(let [initial-db {:db {:node/after-start "something"}}]
|
||||
(defn test-func [save-password]
|
||||
(testing (str "login-account-interal test, save password: " save-password)
|
||||
(let [actual (models/login-account-internal "address" "password" save-password initial-db)]
|
||||
(testing "it resets :node/after-start"
|
||||
(is (= (get-in actual [:db :node/after-start]) nil))
|
||||
(testing "it causes :login effect and preserves save-password flag"
|
||||
(is (= ["address" "password" save-password] (get-in actual [:login]))))))))
|
||||
(map test-func [true false])))
|
||||
|
||||
(deftest login-account
|
||||
(let [mainnet-account {:network "mainnet_rpc"
|
||||
:networks {"mainnet_rpc" {:config {:NetworkId 1}}}}
|
||||
@ -13,87 +24,109 @@
|
||||
initial-db {:db {:network "mainnet_rpc"
|
||||
:accounts/accounts accounts}}]
|
||||
|
||||
(testing "status-go has not started"
|
||||
(let [actual (models/login-account "testnet" "password" initial-db)]
|
||||
(testing "it starts status-node if it has not started"
|
||||
(is (= {:NetworkId 3}
|
||||
(:initialize-geth-fx
|
||||
actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "testnet" "password"] (get-in actual [:db :node/after-start]))))))
|
||||
|
||||
(testing "status-go has started & the user is on mainnet"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" db)]
|
||||
(testing "it does not start status-node if it has already started"
|
||||
(is (not (:initialize-geth-fx actual))))
|
||||
(testing "it logs in the user"
|
||||
(is (= ["mainnet" "password"] (:login actual))))))
|
||||
|
||||
(testing "the user has selected a different network"
|
||||
(testing "status-go has started"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "testnet" "password" db)]
|
||||
(testing "it dispatches start-node"
|
||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password"])))
|
||||
(testing "it stops status-node"
|
||||
(is (contains? actual :stop-node)))))
|
||||
|
||||
(testing "status-go has not started"
|
||||
(let [actual (models/login-account "testnet" "password" initial-db)]
|
||||
(testing "it starts status-node"
|
||||
(testing "save password"
|
||||
(testing "save password: status-go not started"
|
||||
(let [actual (models/login-account "testnet" "password" true initial-db)]
|
||||
(testing "it starts status-node if it has not started"
|
||||
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "testnet" "password"] (get-in actual [:db :node/after-start])))))))
|
||||
(is (= [:login-account-internal "testnet" "password" true] (get-in actual [:db :node/after-start]))))))
|
||||
(testing "status-go has started & the user is on mainnet"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" false db)]
|
||||
(testing "it does not start status-node if it has already started"
|
||||
(is (not (:initialize-geth-fx actual))))
|
||||
(testing "it logs in the user"
|
||||
(is (= ["mainnet" "password" false] (:login actual))))))
|
||||
|
||||
(testing "custom bootnodes"
|
||||
(let [custom-bootnodes {"a" {:id "a"
|
||||
:name "name-a"
|
||||
:address "address-a"}
|
||||
"b" {:id "b"
|
||||
:name "name-b"
|
||||
:address "address-b"}}
|
||||
bootnodes-db (assoc-in
|
||||
initial-db
|
||||
[:db :accounts/accounts "mainnet" :bootnodes]
|
||||
{"mainnet_rpc" custom-bootnodes})]
|
||||
(testing "the user has selected a different network"
|
||||
(testing "status-go has started"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "testnet" "password" false db)]
|
||||
(testing "it dispatches start-node"
|
||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password" true])))
|
||||
(testing "it stops status-node"
|
||||
(is (contains? actual :stop-node))))))
|
||||
|
||||
(testing "custom bootnodes enabled"
|
||||
(let [bootnodes-enabled-db (assoc-in
|
||||
bootnodes-db
|
||||
[:db :accounts/accounts "mainnet" :settings]
|
||||
{:bootnodes {"mainnet_rpc" true}})
|
||||
actual (models/login-account "mainnet" "password" bootnodes-enabled-db)]
|
||||
(testing "status-go has not started"
|
||||
(let [actual (models/login-account "testnet" "password" false initial-db)]
|
||||
(testing "it starts status-node if it has not started"
|
||||
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "testnet" "password" false] (get-in actual [:db :node/after-start]))))))
|
||||
|
||||
(testing "status-go has started & the user is on mainnet"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" false db)]
|
||||
(testing "it does not start status-node if it has already started"
|
||||
(is (not (:initialize-geth-fx actual))))
|
||||
(testing "it logs in the user"
|
||||
(is (= ["mainnet" "password" false] (:login actual))))))
|
||||
|
||||
(testing "the user has selected a different network"
|
||||
(testing "status-go has started"
|
||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "testnet" "password" false db)]
|
||||
(testing "it dispatches start-node"
|
||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password" false])))
|
||||
(testing "it stops status-node"
|
||||
(is (contains? actual :stop-node)))))
|
||||
|
||||
(testing "status-go has not started"
|
||||
(let [actual (models/login-account "testnet" "password" false initial-db)]
|
||||
(testing "it starts status-node"
|
||||
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "testnet" "password" false] (get-in actual [:db :node/after-start])))))))
|
||||
|
||||
(testing "custom bootnodes"
|
||||
(let [custom-bootnodes {"a" {:id "a"
|
||||
:name "name-a"
|
||||
:address "address-a"}
|
||||
"b" {:id "b"
|
||||
:name "name-b"
|
||||
:address "address-b"}}
|
||||
bootnodes-db (assoc-in
|
||||
initial-db
|
||||
[:db :accounts/accounts "mainnet" :bootnodes]
|
||||
{"mainnet_rpc" custom-bootnodes})]
|
||||
|
||||
(testing "custom bootnodes enabled"
|
||||
(let [bootnodes-enabled-db (assoc-in
|
||||
bootnodes-db
|
||||
[:db :accounts/accounts "mainnet" :settings]
|
||||
{:bootnodes {"mainnet_rpc" true}})
|
||||
actual (models/login-account "mainnet" "password" false bootnodes-enabled-db)]
|
||||
(testing "status-node has started"
|
||||
(let [db (assoc-in bootnodes-enabled-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" false db)]
|
||||
(testing "it dispatches start-node"
|
||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password" false])))
|
||||
(testing "it stops status-node"
|
||||
(is (contains? actual :stop-node)))))
|
||||
(testing "status-node has not started"
|
||||
(let [actual (models/login-account "mainnet" "password" false bootnodes-enabled-db)]
|
||||
(testing "it adds bootnodes to the config"
|
||||
(is (= {:ClusterConfig {:Enabled true
|
||||
:BootNodes ["address-a" "address-b"]}
|
||||
:NetworkId 1} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "mainnet" "password" false] (get-in actual [:db :node/after-start]))))))))
|
||||
|
||||
(testing "custom bootnodes not enabled"
|
||||
(testing "status-node has started"
|
||||
(let [db (assoc-in bootnodes-enabled-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" db)]
|
||||
(testing "it dispatches start-node"
|
||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password"])))
|
||||
(testing "it stops status-node"
|
||||
(is (contains? actual :stop-node)))))
|
||||
(let [db (assoc-in bootnodes-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" false db)]
|
||||
(testing "it does not start status-node if it has already started"
|
||||
(is (not (:initialize-geth-fx actual))))
|
||||
(testing "it logs in the user"
|
||||
(is (= ["mainnet" "password" false] (:login actual))))))
|
||||
(testing "status-node has not started"
|
||||
(let [actual (models/login-account "mainnet" "password" bootnodes-enabled-db)]
|
||||
(testing "it adds bootnodes to the config"
|
||||
(is (= {:ClusterConfig {:Enabled true
|
||||
:BootNodes ["address-a" "address-b"]}
|
||||
:NetworkId 1} (:initialize-geth-fx actual))))
|
||||
(let [actual (models/login-account "mainnet" "password" false bootnodes-db)]
|
||||
(testing "it starts status-node without custom bootnodes"
|
||||
(is (= {:NetworkId 1} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "mainnet" "password"] (get-in actual [:db :node/after-start]))))))))
|
||||
|
||||
(testing "custom bootnodes not enabled"
|
||||
(testing "status-node has started"
|
||||
(let [db (assoc-in bootnodes-db [:db :status-node-started?] true)
|
||||
actual (models/login-account "mainnet" "password" db)]
|
||||
(testing "it does not start status-node if it has already started"
|
||||
(is (not (:initialize-geth-fx actual))))
|
||||
(testing "it logs in the user"
|
||||
(is (= ["mainnet" "password"] (:login actual))))))
|
||||
(testing "status-node has not started"
|
||||
(let [actual (models/login-account "mainnet" "password" bootnodes-db)]
|
||||
(testing "it starts status-node without custom bootnodes"
|
||||
(is (= {:NetworkId 1} (:initialize-geth-fx actual))))
|
||||
(testing "it logins the user after the node started"
|
||||
(is (= [:login-account-internal "mainnet" "password"] (get-in actual [:db :node/after-start])))))))))))
|
||||
(is (= [:login-account-internal "mainnet" "password" false] (get-in actual [:db :node/after-start]))))))))))))
|
||||
|
||||
(deftest restart-node?
|
||||
(testing "custom bootnodes is toggled off"
|
||||
|
Loading…
x
Reference in New Issue
Block a user