From 02545cc3d02cc3058371e90450c4f2a521a92a81 Mon Sep 17 00:00:00 2001 From: Igor Mandrigin Date: Thu, 23 Aug 2018 16:27:52 +0200 Subject: [PATCH] Implement "Save Password" for iOS. Update react-native-keychain to 3.0.0 release. Signed-off-by: Igor Mandrigin --- ios/Podfile.lock | 4 +- mobile_files/package-lock.json | 6 +- mobile_files/package.json | 2 +- src/status_im/i18n.cljs | 2 +- src/status_im/translations/en.cljs | 2 + .../ui/components/text_input/view.cljs | 3 +- .../ui/screens/accounts/login/events.cljs | 30 ++- .../ui/screens/accounts/login/models.cljs | 46 +++-- .../ui/screens/accounts/login/navigation.cljs | 2 +- .../ui/screens/accounts/login/styles.cljs | 12 ++ .../ui/screens/accounts/login/views.cljs | 61 ++++-- src/status_im/ui/screens/accounts/models.cljs | 2 +- src/status_im/ui/screens/events.cljs | 27 +-- src/status_im/utils/keychain/core.cljs | 108 +++++++++++ .../ui/screens/accounts/login/models.cljs | 181 +++++++++++------- 15 files changed, 345 insertions(+), 143 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5b6c6d3f05..0d7d93fcad 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/mobile_files/package-lock.json b/mobile_files/package-lock.json index 54b1127f76..0d9a5c03e3 100644 --- a/mobile_files/package-lock.json +++ b/mobile_files/package-lock.json @@ -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", diff --git a/mobile_files/package.json b/mobile_files/package.json index 4ad49403b0..5cba42d477 100644 --- a/mobile_files/package.json +++ b/mobile_files/package.json @@ -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", diff --git a/src/status_im/i18n.cljs b/src/status_im/i18n.cljs index 85d15e5021..18d5fb87cd 100644 --- a/src/status_im/i18n.cljs +++ b/src/status_im/i18n.cljs @@ -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 diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index addc0f18c1..7364b74d19 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -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" diff --git a/src/status_im/ui/components/text_input/view.cljs b/src/status_im/ui/components/text_input/view.cljs index 68901d4b2a..ccce3341f7 100644 --- a/src/status_im/ui/components/text_input/view.cljs +++ b/src/status_im/ui/components/text_input/view.cljs @@ -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)] diff --git a/src/status_im/ui/screens/accounts/login/events.cljs b/src/status_im/ui/screens/accounts/login/events.cljs index e07947a416..4f5af8a7a9 100644 --- a/src/status_im/ui/screens/accounts/login/events.cljs +++ b/src/status_im/ui/screens/accounts/login/events.cljs @@ -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))) + diff --git a/src/status_im/ui/screens/accounts/login/models.cljs b/src/status_im/ui/screens/accounts/login/models.cljs index a94d38288e..d012609837 100644 --- a/src/status_im/ui/screens/accounts/login/models.cljs +++ b/src/status_im/ui/screens/accounts/login/models.cljs @@ -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}] diff --git a/src/status_im/ui/screens/accounts/login/navigation.cljs b/src/status_im/ui/screens/accounts/login/navigation.cljs index e66cf451fd..18e00f7b10 100644 --- a/src/status_im/ui/screens/accounts/login/navigation.cljs +++ b/src/status_im/ui/screens/accounts/login/navigation.cljs @@ -3,4 +3,4 @@ (defmethod nav/preload-data! :login [db] - (update db :accounts/login dissoc :error :password)) + (update db :accounts/login dissoc :error)) diff --git a/src/status_im/ui/screens/accounts/login/styles.cljs b/src/status_im/ui/screens/accounts/login/styles.cljs index 9f3f3e1d67..5eac6e995d 100644 --- a/src/status_im/ui/screens/accounts/login/styles.cljs +++ b/src/status_im/ui/screens/accounts/login/styles.cljs @@ -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}) diff --git a/src/status_im/ui/screens/accounts/login/views.cljs b/src/status_im/ui/screens/accounts/login/views.cljs index 548f4f3354..e1ba2a6429 100644 --- a/src/status_im/ui/screens/accounts/login/views.cljs +++ b/src/status_im/ui/screens/accounts/login/views.cljs @@ -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)}]])])) diff --git a/src/status_im/ui/screens/accounts/models.cljs b/src/status_im/ui/screens/accounts/models.cljs index 8908dd0989..252e4c7d54 100644 --- a/src/status_im/ui/screens/accounts/models.cljs +++ b/src/status_im/ui/screens/accounts/models.cljs @@ -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 diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 9467a196af..bfdc1710e6 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -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))))) diff --git a/src/status_im/utils/keychain/core.cljs b/src/status_im/utils/keychain/core.cljs index 87062f0742..7ca40cf65c 100644 --- a/src/status_im/utils/keychain/core.cljs +++ b/src/status_im/utils/keychain/core.cljs @@ -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))) diff --git a/test/cljs/status_im/test/ui/screens/accounts/login/models.cljs b/test/cljs/status_im/test/ui/screens/accounts/login/models.cljs index dd5c756b0c..c60d88a38f 100644 --- a/test/cljs/status_im/test/ui/screens/accounts/login/models.cljs +++ b/test/cljs/status_im/test/ui/screens/accounts/login/models.cljs @@ -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"