mirror of
https://github.com/status-im/status-react.git
synced 2025-02-23 16:18:32 +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
|
||||||
- React/Core (0.55.4):
|
- React/Core (0.55.4):
|
||||||
- yoga (= 0.55.4.React)
|
- yoga (= 0.55.4.React)
|
||||||
- RNKeychain (3.0.0-rc.3):
|
- RNKeychain (3.0.0):
|
||||||
- React
|
- React
|
||||||
- yoga (0.55.4.React)
|
- yoga (0.55.4.React)
|
||||||
|
|
||||||
@ -83,6 +83,6 @@ SPEC CHECKSUMS:
|
|||||||
RNKeychain: 627c6095cef215dd3d9804a9a9cf45ab96aa3997
|
RNKeychain: 627c6095cef215dd3d9804a9a9cf45ab96aa3997
|
||||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 7d2c351f8b90d3ad2b11fdcfde727875f4c2eebf
|
PODFILE CHECKSUM: e5ecd57d8448253eb06b209c4e1c5230a2634a1d
|
||||||
|
|
||||||
COCOAPODS: 1.4.0
|
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": {
|
"react-native-keychain": {
|
||||||
"version": "3.0.0-rc.3",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-3.0.0-rc.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-3.0.0.tgz",
|
||||||
"integrity": "sha512-ijWfHmxTPKnrHtPJiDbKW3D6lRH8O9wbCNEE3xlxEg1WZT+VhP6iiF+HUansNYuxL7Hh7k41GSFfvr3xumfmXA=="
|
"integrity": "sha512-0incABt1+aXsZvG34mDV57KKanSB+iMHmxvWv+N6lgpNLaSoqrCxazjbZdeqD4qJ7Z+Etp5CLf/4v1aI+sNLBw=="
|
||||||
},
|
},
|
||||||
"react-native-level-fs": {
|
"react-native-level-fs": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
"react-native-image-crop-picker": "0.18.1",
|
"react-native-image-crop-picker": "0.18.1",
|
||||||
"react-native-image-resizer": "1.0.0",
|
"react-native-image-resizer": "1.0.0",
|
||||||
"react-native-invertible-scroll-view": "1.1.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-level-fs": "3.0.0",
|
||||||
"react-native-os": "1.1.0",
|
"react-native-os": "1.1.0",
|
||||||
"react-native-qrcode": "0.2.6",
|
"react-native-qrcode": "0.2.6",
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
:transaction-moved-title :photos-access-error :hash
|
:transaction-moved-title :photos-access-error :hash
|
||||||
:removed-from-chat :done :remove-from-contacts :delete-chat :new-group-chat
|
:removed-from-chat :done :remove-from-contacts :delete-chat :new-group-chat
|
||||||
:edit-chats :wallet :wallet-exchange :wallet-request :sign-in
|
: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
|
:sign-later-text :datetime-ago :no-hashtags-discovered-body :contacts
|
||||||
:search-chat :got-it :delete-group-confirmation :public-chats
|
:search-chat :got-it :delete-group-confirmation :public-chats
|
||||||
:not-applicable :move-to-internal-failure-message :active-online
|
:not-applicable :move-to-internal-failure-message :active-online
|
||||||
|
@ -385,6 +385,8 @@
|
|||||||
:sign-in-to-status "Sign in to Status"
|
:sign-in-to-status "Sign in to Status"
|
||||||
:sign-in "Sign in"
|
:sign-in "Sign in"
|
||||||
:sign-in-to-another "Sign in to another account"
|
: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"
|
:wrong-password "Wrong password"
|
||||||
:enter-password "Enter password"
|
:enter-password "Enter password"
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
[status-im.ui.components.colors :as colors]
|
[status-im.ui.components.colors :as colors]
|
||||||
[status-im.ui.components.tooltip.views :as tooltip]))
|
[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
|
[react/view
|
||||||
(when label
|
(when label
|
||||||
[react/text {:style styles/label}
|
[react/text {:style styles/label}
|
||||||
@ -15,6 +15,7 @@
|
|||||||
{:style (merge styles/input style)
|
{:style (merge styles/input style)
|
||||||
:placeholder-text-color colors/gray
|
:placeholder-text-color colors/gray
|
||||||
:auto-focus true
|
:auto-focus true
|
||||||
|
:value text
|
||||||
:auto-capitalize :none}
|
:auto-capitalize :none}
|
||||||
(dissoc props :style :height))]
|
(dissoc props :style :height))]
|
||||||
(when content content)]
|
(when content content)]
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
(:require status-im.ui.screens.accounts.login.navigation
|
(:require status-im.ui.screens.accounts.login.navigation
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[status-im.utils.handlers :as handlers]
|
[status-im.utils.handlers :as handlers]
|
||||||
|
[status-im.utils.handlers-macro :as handlers-macro]
|
||||||
[status-im.ui.screens.accounts.login.models :as models]))
|
[status-im.ui.screens.accounts.login.models :as models]))
|
||||||
|
|
||||||
;;;; FX
|
;;;; FX
|
||||||
@ -10,8 +11,8 @@
|
|||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
:login
|
:login
|
||||||
(fn [[address password]]
|
(fn [[address password save-password]]
|
||||||
(models/login! address password)))
|
(models/login! address password save-password)))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
:clear-web-data
|
:clear-web-data
|
||||||
@ -29,27 +30,36 @@
|
|||||||
(fn [cofx [_ address photo-path name]]
|
(fn [cofx [_ address photo-path name]]
|
||||||
(models/open-login address photo-path name cofx)))
|
(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
|
(handlers/register-handler-fx
|
||||||
:login-account-internal
|
:login-account-internal
|
||||||
(fn [cofx [_ address password]]
|
(fn [cofx [_ address password save-password]]
|
||||||
(models/login-account-internal address password cofx)))
|
(models/login-account-internal address password save-password cofx)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:start-node
|
:start-node
|
||||||
(fn [cofx [_ address password]]
|
(fn [cofx [_ address password save-password]]
|
||||||
(models/start-node address password cofx)))
|
(models/start-node address password save-password cofx)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:login-account
|
:login-account
|
||||||
(fn [cofx [_ address password]]
|
(fn [cofx [_ address password save-password]]
|
||||||
(models/login-account address password cofx)))
|
(models/login-account address password save-password cofx)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:login-handler
|
:login-handler
|
||||||
(fn [cofx [_ login-result address]]
|
(fn [cofx [_ login-result address password save-password]]
|
||||||
(models/login-handler login-result address cofx)))
|
(models/login-handler login-result address password save-password cofx)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:change-account-handler
|
:change-account-handler
|
||||||
(fn [cofx [_ address]]
|
(fn [cofx [_ address]]
|
||||||
(models/change-account-handler address cofx)))
|
(models/change-account-handler address cofx)))
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
[status-im.utils.config :as config]
|
[status-im.utils.config :as config]
|
||||||
[status-im.utils.keychain.core :as keychain]
|
[status-im.utils.keychain.core :as keychain]
|
||||||
[status-im.utils.notifications :as notifications]
|
[status-im.utils.notifications :as notifications]
|
||||||
|
[taoensso.timbre :as log]
|
||||||
[status-im.utils.platform :as platform]
|
[status-im.utils.platform :as platform]
|
||||||
[status-im.utils.universal-links.core :as universal-links]))
|
[status-im.utils.universal-links.core :as universal-links]))
|
||||||
|
|
||||||
@ -14,8 +15,10 @@
|
|||||||
|
|
||||||
(defn stop-node! [] (status/stop-node))
|
(defn stop-node! [] (status/stop-node))
|
||||||
|
|
||||||
(defn login! [address password]
|
(defn login! [address password save-password]
|
||||||
(status/login address password #(re-frame/dispatch [:login-handler % address])))
|
(status/login address
|
||||||
|
password
|
||||||
|
#(re-frame/dispatch [:login-handler % address password save-password])))
|
||||||
|
|
||||||
(defn clear-web-data! []
|
(defn clear-web-data! []
|
||||||
(status/clear-web-data))
|
(status/clear-web-data))
|
||||||
@ -34,22 +37,27 @@
|
|||||||
|
|
||||||
;;;; Handlers
|
;;;; Handlers
|
||||||
|
|
||||||
(defn open-login [address photo-path name {db :db}]
|
(defn navigate-to-login [address photo-path name password {db :db}]
|
||||||
{:db (update db
|
{:db (update db
|
||||||
:accounts/login assoc
|
:accounts/login assoc
|
||||||
:address address
|
:address address
|
||||||
:photo-path photo-path
|
:photo-path photo-path
|
||||||
|
:password password
|
||||||
:name name)
|
:name name)
|
||||||
|
:can-save-user-password [#(re-frame/dispatch [:set-in [:accounts/login :can-save-password] %])]
|
||||||
:dispatch [:navigate-to :login]})
|
: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
|
{: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
|
(wrap-with-login-account-fx
|
||||||
(assoc db :node/after-start nil)
|
(assoc db :node/after-start nil)
|
||||||
address password))
|
address password save-password))
|
||||||
|
|
||||||
(defn- add-custom-bootnodes [config network all-bootnodes]
|
(defn- add-custom-bootnodes [config network all-bootnodes]
|
||||||
(let [bootnodes (as-> all-bootnodes $
|
(let [bootnodes (as-> all-bootnodes $
|
||||||
@ -77,17 +85,17 @@
|
|||||||
:network network
|
:network network
|
||||||
:config config}))
|
: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)]
|
(let [{:keys [network config]} (get-network-by-address db address)]
|
||||||
{:initialize-geth-fx config
|
{:initialize-geth-fx config
|
||||||
:db (assoc db
|
:db (assoc db
|
||||||
:network network
|
: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
|
(wrap-with-initialize-geth-fx
|
||||||
(assoc db :node/after-stop nil)
|
(assoc db :node/after-stop nil)
|
||||||
address password))
|
address password save-password))
|
||||||
|
|
||||||
(defn- wrap-with-stop-node-fx [db address password]
|
(defn- wrap-with-stop-node-fx [db address password]
|
||||||
{:db (assoc db :node/after-stop [:start-node address password])
|
{:db (assoc db :node/after-stop [:start-node address password])
|
||||||
@ -98,7 +106,7 @@
|
|||||||
(and config/bootnodes-settings-enabled?
|
(and config/bootnodes-settings-enabled?
|
||||||
use-custom-bootnodes)))
|
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
|
(let [{use-custom-bootnodes :use-custom-bootnodes
|
||||||
account-network :network} (get-network-by-address db address)
|
account-network :network} (get-network-by-address db address)
|
||||||
db' (-> db
|
db' (-> db
|
||||||
@ -113,17 +121,21 @@
|
|||||||
|
|
||||||
:else
|
:else
|
||||||
wrap-with-stop-node-fx)]
|
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)
|
(let [data (types/json->clj login-result)
|
||||||
error (:error data)
|
error (:error data)
|
||||||
success (zero? (count error))
|
success (zero? (count error))
|
||||||
db' (assoc-in db [:accounts/login :processing] false)]
|
db' (assoc-in db [:accounts/login :processing] false)]
|
||||||
(if success
|
(if success
|
||||||
|
(merge
|
||||||
{:db db
|
{:db db
|
||||||
:clear-web-data nil
|
:clear-web-data nil
|
||||||
:change-account [address]}
|
:change-account [address]}
|
||||||
|
(when save-password
|
||||||
|
{:save-user-password [address password]}))
|
||||||
{:db (assoc-in db' [:accounts/login :error] error)})))
|
{:db (assoc-in db' [:accounts/login :error] error)})))
|
||||||
|
|
||||||
(defn change-account-handler [address {{:keys [view-id] :as db} :db :as cofx}]
|
(defn change-account-handler [address {{:keys [view-id] :as db} :db :as cofx}]
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
(defmethod nav/preload-data! :login
|
(defmethod nav/preload-data! :login
|
||||||
[db]
|
[db]
|
||||||
(update db :accounts/login dissoc :error :password))
|
(update db :accounts/login dissoc :error))
|
||||||
|
@ -38,3 +38,15 @@
|
|||||||
|
|
||||||
(def password-container
|
(def password-container
|
||||||
{:margin-top 24})
|
{: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-macros [status-im.utils.views :refer [defview letsubs]])
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[status-im.ui.screens.accounts.styles :as ast]
|
[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.text-input.view :as text-input]
|
||||||
[status-im.ui.components.status-bar.view :as status-bar]
|
[status-im.ui.components.status-bar.view :as status-bar]
|
||||||
[status-im.ui.components.toolbar.view :as toolbar]
|
[status-im.ui.components.toolbar.view :as toolbar]
|
||||||
@ -14,6 +16,7 @@
|
|||||||
[status-im.chat.views.photos :as photos]
|
[status-im.chat.views.photos :as photos]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[cljs.spec.alpha :as spec]
|
[cljs.spec.alpha :as spec]
|
||||||
|
[status-im.utils.platform :as platform]
|
||||||
[status-im.ui.screens.accounts.db :as db]))
|
[status-im.ui.screens.accounts.db :as db]))
|
||||||
|
|
||||||
(defn login-toolbar [can-navigate-back?]
|
(defn login-toolbar [can-navigate-back?]
|
||||||
@ -23,9 +26,9 @@
|
|||||||
[toolbar/nav-button act/default-back])
|
[toolbar/nav-button act/default-back])
|
||||||
[toolbar/content-title (i18n/label :t/sign-in-to-status)]])
|
[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)
|
(.blur password-text-input)
|
||||||
(re-frame/dispatch [:login-account address password]))
|
(re-frame/dispatch [:login-account address password save-password]))
|
||||||
|
|
||||||
(defn- error-key [error]
|
(defn- error-key [error]
|
||||||
;; TODO Improve selection logic when status-go provide an error code
|
;; TODO Improve selection logic when status-go provide an error code
|
||||||
@ -49,14 +52,24 @@
|
|||||||
name]]])
|
name]]])
|
||||||
|
|
||||||
(defview login []
|
(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?]
|
can-navigate-back? [:can-navigate-back?]
|
||||||
password-text-input (atom nil)]
|
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}
|
[react/keyboard-avoiding-view {:style ast/accounts-view}
|
||||||
[status-bar/status-bar]
|
[status-bar/status-bar]
|
||||||
[login-toolbar can-navigate-back?]
|
[login-toolbar can-navigate-back?]
|
||||||
[components.common/separator]
|
[components.common/separator]
|
||||||
[react/view styles/login-view
|
[react/view styles/login-view
|
||||||
|
;; 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
|
[react/view styles/login-badge-container
|
||||||
[account-login-badge photo-path name]
|
[account-login-badge photo-path name]
|
||||||
[react/view {:style styles/password-container
|
[react/view {:style styles/password-container
|
||||||
@ -66,12 +79,22 @@
|
|||||||
:placeholder (i18n/label :t/password)
|
:placeholder (i18n/label :t/password)
|
||||||
:ref #(reset! password-text-input %)
|
:ref #(reset! password-text-input %)
|
||||||
:auto-focus can-navigate-back? ;;this needed because keyboard overlays testfairy alert
|
:auto-focus can-navigate-back? ;;this needed because keyboard overlays testfairy alert
|
||||||
:on-submit-editing #(login-account @password-text-input address password)
|
:on-submit-editing #(login-account @password-text-input address password save-password)
|
||||||
:on-change-text #(do
|
:on-change-text #(do
|
||||||
(re-frame/dispatch [:set-in [:accounts/login :password] %])
|
(re-frame/dispatch [:set-in [:accounts/login :password] %])
|
||||||
(re-frame/dispatch [:set-in [:accounts/login :error] ""]))
|
(re-frame/dispatch [:set-in [:accounts/login :error] ""]))
|
||||||
:secure-text-entry true
|
:secure-text-entry true
|
||||||
:error (when (pos? (count error)) (i18n/label (error-key error)))}]]]]
|
: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
|
(when processing
|
||||||
[react/view styles/processing-view
|
[react/view styles/processing-view
|
||||||
[components/activity-indicator {:animating true}]
|
[components/activity-indicator {:animating true}]
|
||||||
@ -87,4 +110,4 @@
|
|||||||
{:forward? true
|
{:forward? true
|
||||||
:label (i18n/label :t/sign-in)
|
:label (i18n/label :t/sign-in)
|
||||||
:disabled? (not (spec/valid? ::db/password password))
|
: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)
|
(when-not (str/blank? pubkey)
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
(add-account account)
|
(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]}]
|
(defn load-accounts [{:keys [db all-accounts]}]
|
||||||
(let [accounts (->> all-accounts
|
(let [accounts (->> all-accounts
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
status-im.protocol.handlers
|
status-im.protocol.handlers
|
||||||
[status-im.ui.screens.accounts.models :as accounts.models]
|
[status-im.ui.screens.accounts.models :as accounts.models]
|
||||||
status-im.ui.screens.accounts.login.events
|
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.accounts.recover.events
|
||||||
[status-im.ui.screens.contacts.events :as contacts]
|
[status-im.ui.screens.contacts.events :as contacts]
|
||||||
status-im.ui.screens.add-new.new-chat.events
|
status-im.ui.screens.add-new.new-chat.events
|
||||||
@ -214,18 +215,17 @@
|
|||||||
(re-frame/dispatch [:initialize-app encryption-key]))
|
(re-frame/dispatch [:initialize-app encryption-key]))
|
||||||
:on-accept handle-reset-data})
|
:on-accept handle-reset-data})
|
||||||
|
|
||||||
(defn initialize-views [{{:accounts/keys [accounts] :as db} :db}]
|
(defn initialize-views [cofx]
|
||||||
{:db (if (empty? accounts)
|
(let [{{:accounts/keys [accounts] :as db} :db} cofx
|
||||||
(assoc db :view-id :intro :navigation-stack (list :intro))
|
{:keys [address photo-path name]} (first (sort-by :last-sign-in > (vals accounts)))
|
||||||
(let [{:keys [address photo-path name]} (first (sort-by :last-sign-in > (vals accounts)))]
|
default-fx {:handle-initial-push-notification-fx db}]
|
||||||
(-> db
|
(if (nil? address)
|
||||||
(assoc :view-id :login
|
(handlers-macro/merge-fx cofx
|
||||||
:navigation-stack (list :login))
|
default-fx
|
||||||
(update :accounts/login assoc
|
(navigation/navigate-to-clean :intro))
|
||||||
:address address
|
(handlers-macro/merge-fx cofx
|
||||||
:photo-path photo-path
|
default-fx
|
||||||
:name name))))
|
(login/open-login address photo-path name)))))
|
||||||
:handle-initial-push-notification-fx db})
|
|
||||||
|
|
||||||
(defn initialize-db
|
(defn initialize-db
|
||||||
"Initialize db to the initial state"
|
"Initialize db to the initial state"
|
||||||
@ -289,7 +289,8 @@
|
|||||||
(fn [{:keys [db] :as cofx} _]
|
(fn [{:keys [db] :as cofx} _]
|
||||||
(let [{:transport/keys [chats]} db]
|
(let [{:transport/keys [chats]} db]
|
||||||
(handlers-macro/merge-fx cofx
|
(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)
|
(navigation/navigate-to-clean nil)
|
||||||
(transport/stop-whisper)))))
|
(transport/stop-whisper)))))
|
||||||
|
|
||||||
|
@ -13,6 +13,81 @@
|
|||||||
(defn- string->js-array [s]
|
(defn- string->js-array [s]
|
||||||
(.parse js/JSON (.-password 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
|
;; 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
|
;; some IOS devices. We check naively that there are no more than key-bytes/2
|
||||||
;; identical characters.
|
;; identical characters.
|
||||||
@ -77,3 +152,36 @@
|
|||||||
|
|
||||||
(defn set-username []
|
(defn set-username []
|
||||||
(when platform/desktop? (.setUsername rn/keychain 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.utils.config :as config]
|
||||||
[status-im.ui.screens.accounts.login.models :as models]))
|
[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
|
(deftest login-account
|
||||||
(let [mainnet-account {:network "mainnet_rpc"
|
(let [mainnet-account {:network "mainnet_rpc"
|
||||||
:networks {"mainnet_rpc" {:config {:NetworkId 1}}}}
|
:networks {"mainnet_rpc" {:config {:NetworkId 1}}}}
|
||||||
@ -13,38 +24,60 @@
|
|||||||
initial-db {:db {:network "mainnet_rpc"
|
initial-db {:db {:network "mainnet_rpc"
|
||||||
:accounts/accounts accounts}}]
|
:accounts/accounts accounts}}]
|
||||||
|
|
||||||
(testing "status-go has not started"
|
(testing "save password"
|
||||||
(let [actual (models/login-account "testnet" "password" initial-db)]
|
(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"
|
(testing "it starts status-node if it has not started"
|
||||||
(is (= {:NetworkId 3}
|
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
||||||
(:initialize-geth-fx
|
|
||||||
actual))))
|
|
||||||
(testing "it logins the user after the node started"
|
(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"
|
(testing "status-go has started & the user is on mainnet"
|
||||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||||
actual (models/login-account "mainnet" "password" db)]
|
actual (models/login-account "mainnet" "password" false db)]
|
||||||
(testing "it does not start status-node if it has already started"
|
(testing "it does not start status-node if it has already started"
|
||||||
(is (not (:initialize-geth-fx actual))))
|
(is (not (:initialize-geth-fx actual))))
|
||||||
(testing "it logs in the user"
|
(testing "it logs in the user"
|
||||||
(is (= ["mainnet" "password"] (:login actual))))))
|
(is (= ["mainnet" "password" false] (:login actual))))))
|
||||||
|
|
||||||
(testing "the user has selected a different network"
|
(testing "the user has selected a different network"
|
||||||
(testing "status-go has started"
|
(testing "status-go has started"
|
||||||
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
(let [db (assoc-in initial-db [:db :status-node-started?] true)
|
||||||
actual (models/login-account "testnet" "password" db)]
|
actual (models/login-account "testnet" "password" false db)]
|
||||||
(testing "it dispatches start-node"
|
(testing "it dispatches start-node"
|
||||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password"])))
|
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password" true])))
|
||||||
|
(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 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"
|
(testing "it stops status-node"
|
||||||
(is (contains? actual :stop-node)))))
|
(is (contains? actual :stop-node)))))
|
||||||
|
|
||||||
(testing "status-go has not started"
|
(testing "status-go has not started"
|
||||||
(let [actual (models/login-account "testnet" "password" initial-db)]
|
(let [actual (models/login-account "testnet" "password" false initial-db)]
|
||||||
(testing "it starts status-node"
|
(testing "it starts status-node"
|
||||||
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
(is (= {:NetworkId 3} (:initialize-geth-fx actual))))
|
||||||
(testing "it logins the user after the node started"
|
(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" false] (get-in actual [:db :node/after-start])))))))
|
||||||
|
|
||||||
(testing "custom bootnodes"
|
(testing "custom bootnodes"
|
||||||
(let [custom-bootnodes {"a" {:id "a"
|
(let [custom-bootnodes {"a" {:id "a"
|
||||||
@ -63,37 +96,37 @@
|
|||||||
bootnodes-db
|
bootnodes-db
|
||||||
[:db :accounts/accounts "mainnet" :settings]
|
[:db :accounts/accounts "mainnet" :settings]
|
||||||
{:bootnodes {"mainnet_rpc" true}})
|
{:bootnodes {"mainnet_rpc" true}})
|
||||||
actual (models/login-account "mainnet" "password" bootnodes-enabled-db)]
|
actual (models/login-account "mainnet" "password" false bootnodes-enabled-db)]
|
||||||
(testing "status-node has started"
|
(testing "status-node has started"
|
||||||
(let [db (assoc-in bootnodes-enabled-db [:db :status-node-started?] true)
|
(let [db (assoc-in bootnodes-enabled-db [:db :status-node-started?] true)
|
||||||
actual (models/login-account "mainnet" "password" db)]
|
actual (models/login-account "mainnet" "password" false db)]
|
||||||
(testing "it dispatches start-node"
|
(testing "it dispatches start-node"
|
||||||
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password"])))
|
(is (get-in actual [:db :node/after-stop] [:start-node "testnet" "password" false])))
|
||||||
(testing "it stops status-node"
|
(testing "it stops status-node"
|
||||||
(is (contains? actual :stop-node)))))
|
(is (contains? actual :stop-node)))))
|
||||||
(testing "status-node has not started"
|
(testing "status-node has not started"
|
||||||
(let [actual (models/login-account "mainnet" "password" bootnodes-enabled-db)]
|
(let [actual (models/login-account "mainnet" "password" false bootnodes-enabled-db)]
|
||||||
(testing "it adds bootnodes to the config"
|
(testing "it adds bootnodes to the config"
|
||||||
(is (= {:ClusterConfig {:Enabled true
|
(is (= {:ClusterConfig {:Enabled true
|
||||||
:BootNodes ["address-a" "address-b"]}
|
:BootNodes ["address-a" "address-b"]}
|
||||||
:NetworkId 1} (:initialize-geth-fx actual))))
|
:NetworkId 1} (:initialize-geth-fx actual))))
|
||||||
(testing "it logins the user after the node started"
|
(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]))))))))
|
||||||
|
|
||||||
(testing "custom bootnodes not enabled"
|
(testing "custom bootnodes not enabled"
|
||||||
(testing "status-node has started"
|
(testing "status-node has started"
|
||||||
(let [db (assoc-in bootnodes-db [:db :status-node-started?] true)
|
(let [db (assoc-in bootnodes-db [:db :status-node-started?] true)
|
||||||
actual (models/login-account "mainnet" "password" db)]
|
actual (models/login-account "mainnet" "password" false db)]
|
||||||
(testing "it does not start status-node if it has already started"
|
(testing "it does not start status-node if it has already started"
|
||||||
(is (not (:initialize-geth-fx actual))))
|
(is (not (:initialize-geth-fx actual))))
|
||||||
(testing "it logs in the user"
|
(testing "it logs in the user"
|
||||||
(is (= ["mainnet" "password"] (:login actual))))))
|
(is (= ["mainnet" "password" false] (:login actual))))))
|
||||||
(testing "status-node has not started"
|
(testing "status-node has not started"
|
||||||
(let [actual (models/login-account "mainnet" "password" bootnodes-db)]
|
(let [actual (models/login-account "mainnet" "password" false bootnodes-db)]
|
||||||
(testing "it starts status-node without custom bootnodes"
|
(testing "it starts status-node without custom bootnodes"
|
||||||
(is (= {:NetworkId 1} (:initialize-geth-fx actual))))
|
(is (= {:NetworkId 1} (:initialize-geth-fx actual))))
|
||||||
(testing "it logins the user after the node started"
|
(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?
|
(deftest restart-node?
|
||||||
(testing "custom bootnodes is toggled off"
|
(testing "custom bootnodes is toggled off"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user