diff --git a/modules/react-native-status/nodejs/status.cpp b/modules/react-native-status/nodejs/status.cpp index 0a71ed1f96..335921a1d2 100644 --- a/modules/react-native-status/nodejs/status.cpp +++ b/modules/react-native-status/nodejs/status.cpp @@ -833,6 +833,37 @@ void _SaveAccountAndLogin(const FunctionCallbackInfo& args) { } +void _CreateAccountAndLogin(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.Length() != 1) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong number of arguments for SaveAccountAndLogin"))); + return; + } + + // Check the argument types + + if (!args[0]->IsString()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong argument type for 'settingsJSON'"))); + return; + } + + String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked()); + char *arg0 = *arg0Obj; + + // Call exported Go function, which returns a C string + char *c = CreateAccountAndLogin(arg0); + + Local ret = String::NewFromUtf8(isolate, c).ToLocalChecked(); + args.GetReturnValue().Set(ret); + delete c; + +} + void _GenerateAlias(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); @@ -1929,6 +1960,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "hashMessage", _HashMessage); NODE_SET_METHOD(exports, "resetChainData", _ResetChainData); NODE_SET_METHOD(exports, "saveAccountAndLogin", _SaveAccountAndLogin); + NODE_SET_METHOD(exports, "createAccountAndLogin", _CreateAccountAndLogin); NODE_SET_METHOD(exports, "generateAlias", _GenerateAlias); NODE_SET_METHOD(exports, "validateMnemonic", _ValidateMnemonic); NODE_SET_METHOD(exports, "multiformatSerializePublicKey", _MultiformatSerializePublicKey); diff --git a/src/react_native/keychain.cljs b/src/react_native/keychain.cljs new file mode 100644 index 0000000000..c2decc6e0e --- /dev/null +++ b/src/react_native/keychain.cljs @@ -0,0 +1,78 @@ +(ns react-native.keychain + (:require ["react-native-keychain" :as react-native-keychain] + [clojure.string :as string] + [taoensso.timbre :as log])) + +;; ******************************************************************************** +;; 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 ^js react-native-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. + #js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")}) + +(def keychain-secure-hardware + ;; (Android) Requires storing the encryption key for the entry in secure hardware + ;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention) + "SECURE_HARDWARE") + +;; Android only +(defn secure-hardware-available? + [callback] + (-> (.getSecurityLevel ^js react-native-keychain) + (.then (fn [level] (callback (= level keychain-secure-hardware)))))) + +;; iOS only +(defn device-encrypted? + [callback] + (-> (.canImplyAuthentication + ^js react-native-keychain + (clj->js + {:authenticationType + (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")})) + (.then callback))) + +(defn save-credentials + "Stores the credentials for the address to the Keychain" + [server username password callback] + (-> (.setInternetCredentials ^js react-native-keychain + (string/lower-case server) + username + password + keychain-secure-hardware + keychain-restricted-availability) + (.then callback))) + +(defn get-credentials + "Gets the credentials for a specified server from the Keychain" + [server callback] + (-> (.getInternetCredentials ^js react-native-keychain (string/lower-case server)) + (.then callback))) + +(defn reset-credentials + [server] + (-> (.resetInternetCredentials ^js react-native-keychain (string/lower-case server)) + (.then #(when-not % (log/error (str "Error while clearing saved password.")))))) diff --git a/src/react_native/touch_id.cljs b/src/react_native/touch_id.cljs new file mode 100644 index 0000000000..22dfe24069 --- /dev/null +++ b/src/react_native/touch_id.cljs @@ -0,0 +1,19 @@ +(ns react-native.touch-id + (:require ["react-native-touch-id" :default touchid])) + +;; currently, for android, react-native-touch-id +;; is not returning supported biometric type +;; defaulting to :fingerprint +(def android-default-support :fingerprint) + +(defn get-supported-type + [callback] + (-> (.isSupported ^js touchid) + (.then #(callback (or (keyword %) android-default-support))) + (.catch #(callback nil)))) + +(defn authenticate + [{:keys [on-success on-fail reason options]}] + (-> (.authenticate ^js touchid reason (clj->js options)) + (.then #(when on-success (on-success))) + (.catch #(when on-fail (on-fail (aget % "code")))))) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 21f4c7b096..a40c846f07 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -23,7 +23,6 @@ status-im.log-level.core status-im.mailserver.constants [status-im.mailserver.core :as mailserver] - [status-im.multiaccounts.biometric.core :as biometric] status-im.multiaccounts.login.core status-im.multiaccounts.logout.core [status-im.multiaccounts.model :as multiaccounts.model] @@ -63,7 +62,8 @@ [react-native.platform :as platform] status-im2.contexts.chat.home.events status-im2.contexts.communities.home.events - status-im.ui.components.invite.events)) + status-im.ui.components.invite.events + [status-im2.common.biometric.events :as biometric])) (re-frame/reg-fx :dismiss-keyboard @@ -124,33 +124,26 @@ [(get-in db [:profile/profile :appearance]) (:view-id db) true]}))) -(def authentication-options - {:reason (i18n/label :t/biometric-auth-reason-login)}) - -(defn- on-biometric-auth-result - [{:keys [bioauth-success bioauth-code bioauth-message]}] - (when-not bioauth-success - (if (= bioauth-code "USER_FALLBACK") - (re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) - (utils/show-confirmation - {:title (i18n/label :t/biometric-auth-confirm-title) - :content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message)) - :confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again) - :cancel-button-text (i18n/label :t/biometric-auth-confirm-logout) - :on-accept #(biometric/authenticate nil - on-biometric-auth-result - authentication-options) - :on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))) +(defn- on-biometric-auth-fail + [{:keys [code]}] + (if (= code "USER_FALLBACK") + (re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) + (utils/show-confirmation + {:title (i18n/label :t/biometric-auth-confirm-title) + :content (i18n/label :t/biometric-auth-confirm-message) + :confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again) + :cancel-button-text (i18n/label :t/biometric-auth-confirm-logout) + :on-accept #(biometric/authenticate nil {:on-fail on-biometric-auth-fail}) + :on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])}))) (rf/defn on-return-from-background [{:keys [db now] :as cofx}] (let [new-account? (get db :onboarding-2/new-account?) app-in-background-since (get db :app-in-background-since) signed-up? (get-in db [:profile/profile :signed-up?]) - biometric-auth? (= (:auth-method db) "biometric") requires-bio-auth (and signed-up? - biometric-auth? + (= (:auth-method db) "biometric") (some? app-in-background-since) (>= (- now app-in-background-since) constants/ms-in-bg-for-require-bioauth))] @@ -163,7 +156,7 @@ #(when-let [chat-id (:current-chat-id db)] {:dispatch [:chat/mark-all-as-read chat-id]}) #(when requires-bio-auth - (biometric/authenticate % on-biometric-auth-result authentication-options))))) + (biometric/authenticate % {:on-fail on-biometric-auth-fail}))))) (rf/defn on-going-in-background [{:keys [db now]}] @@ -274,53 +267,3 @@ cofx (navigation/open-modal :buy-crypto nil) (wallet/keep-watching-history))) - -;; Information Box - -(def closable-information-boxes - "[{:id information box id - :global? true/false (close information box across all profiles)}]" - []) - -(defn information-box-id-hash - [id public-key global?] - (if global? - (hash id) - (hash (str public-key id)))) - -(rf/defn close-information-box - {:events [:close-information-box]} - [{:keys [db]} id global?] - (let [public-key (get-in db [:profile/profile :public-key]) - hash (information-box-id-hash id public-key global?)] - {::async-storage/set! {hash true} - :db (assoc-in db [:information-box-states id] true)})) - -(rf/defn information-box-states-loaded - {:events [:information-box-states-loaded]} - [{:keys [db]} hashes states] - {:db (assoc db - :information-box-states - (reduce - (fn [acc [id hash]] - (assoc acc id (get states hash))) - {} - hashes))}) - -(rf/defn load-information-box-states - {:events [:load-information-box-states]} - [{:keys [db]}] - (let [public-key (get-in db [:profile/profile :public-key]) - {:keys [keys hashes]} (reduce (fn [acc {:keys [id global?]}] - (let [hash (information-box-id-hash - id - public-key - global?)] - (-> acc - (assoc-in [:hashes id] hash) - (update :keys #(conj % hash))))) - {} - closable-information-boxes)] - {::async-storage/get {:keys keys - :cb #(re-frame/dispatch - [:information-box-states-loaded hashes %])}})) diff --git a/src/status_im/integration_test.cljs b/src/status_im/integration_test.cljs index da11a09c85..ef874f0052 100644 --- a/src/status_im/integration_test.cljs +++ b/src/status_im/integration_test.cljs @@ -26,13 +26,10 @@ [] (rf/dispatch [:app-started])) -(defn generate-and-derive-addresses! - [] - (rf/dispatch [:generate-and-derive-addresses])) - (defn create-multiaccount! [] - (rf/dispatch [:create-multiaccount password])) + (rf/dispatch [:profile.create/create-and-login + {:display-name account-name :password password :color "blue"}])) (defn assert-app-initialized [] @@ -82,15 +79,12 @@ (initialize-app!) ; initialize app (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) ; generate 5 new keys - (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] ; wait for the keys - (create-multiaccount!) ; create a multiaccount - (rf-test/wait-for ; wait for login - [::transport/messenger-started] - (assert-messenger-started) - (logout!) - (rf-test/wait-for [::logout/logout-method])))))) + (create-multiaccount!) ; create a multiaccount + (rf-test/wait-for ; wait for login + [::transport/messenger-started] + (assert-messenger-started) + (logout!) + (rf-test/wait-for [::logout/logout-method]))))) (deftest create-community-test (log/info "====== create-community-test ==================") @@ -98,22 +92,19 @@ (initialize-app!) ; initialize app (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) ; generate 5 new keys - (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] - (create-multiaccount!) ; create a multiaccount - (rf-test/wait-for ; wait for login - [::transport/messenger-started] - (assert-messenger-started) - (rf/dispatch-sync [:legacy-only-for-e2e/open-create-community]) - (doseq [[k v] (dissoc community :membership)] - (rf/dispatch-sync [:status-im.communities.core/create-field k v])) - (rf/dispatch [:status-im.communities.core/create-confirmation-pressed]) - (rf-test/wait-for - [:status-im.communities.core/community-created] - (assert-community-created) - (logout!) - (rf-test/wait-for [::logout/logout-method]))))))) + (create-multiaccount!) ; create a multiaccount + (rf-test/wait-for ; wait for login + [::transport/messenger-started] + (assert-messenger-started) + (rf/dispatch-sync [:legacy-only-for-e2e/open-create-community]) + (doseq [[k v] (dissoc community :membership)] + (rf/dispatch-sync [:status-im.communities.core/create-field k v])) + (rf/dispatch [:status-im.communities.core/create-confirmation-pressed]) + (rf-test/wait-for + [:status-im.communities.core/community-created] + (assert-community-created) + (logout!) + (rf-test/wait-for [::logout/logout-method])))))) (deftest create-wallet-account-test (log/info "====== create-wallet-account-test ==================") @@ -121,19 +112,16 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) ; generate 5 new keys - (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] - (create-multiaccount!) ; create a multiaccount - (rf-test/wait-for ; wait for login - [::transport/messenger-started] - (assert-messenger-started) - (create-new-account!) ; create a new account - (rf-test/wait-for - [:wallet.accounts/account-stored] - (assert-new-account-created) ; assert account was created - (logout!) - (rf-test/wait-for [::logout/logout-method]))))))) + (create-multiaccount!) ; create a multiaccount + (rf-test/wait-for ; wait for login + [::transport/messenger-started] + (assert-messenger-started) + (create-new-account!) ; create a new account + (rf-test/wait-for + [:wallet.accounts/account-stored] + (assert-new-account-created) ; assert account was created + (logout!) + (rf-test/wait-for [::logout/logout-method])))))) (deftest back-up-seed-phrase-test (log/info "========= back-up-seed-phrase-test ==================") @@ -141,30 +129,27 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) + (create-multiaccount!) (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] - (create-multiaccount!) - (rf-test/wait-for - [::transport/messenger-started] - (assert-messenger-started) - (rf/dispatch-sync [:set-in [:my-profile/seed :step] :12-words]) ; display seed phrase to user - (rf/dispatch-sync [:my-profile/enter-two-random-words]) ; begin prompting user for seed words - (let [{:keys [mnemonic]} @(rf/subscribe [:profile/profile]) - seed @(rf/subscribe [:my-profile/seed]) - word1 (second (:first-word seed)) - word2 (second (:second-word seed))] - (is (= 12 (count (string/split mnemonic #" ")))) ; assert 12-word seed phrase - (rf/dispatch-sync [:set-in [:my-profile/seed :word] word1]) - (rf/dispatch-sync [:my-profile/set-step :second-word]) - (rf/dispatch-sync [:set-in [:my-profile/seed :word] word2]) - ;; TODO: refactor (defn next-handler) & (defn enter-word) to improve test coverage? - (rf/dispatch [:my-profile/finish]) - (rf-test/wait-for - [:my-profile/finish-success] - (is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed - (logout!) - (rf-test/wait-for [::logout/logout-method])))))))) + [::transport/messenger-started] + (assert-messenger-started) + (rf/dispatch-sync [:set-in [:my-profile/seed :step] :12-words]) ; display seed phrase to user + (rf/dispatch-sync [:my-profile/enter-two-random-words]) ; begin prompting user for seed words + (let [{:keys [mnemonic]} @(rf/subscribe [:profile/profile]) + seed @(rf/subscribe [:my-profile/seed]) + word1 (second (:first-word seed)) + word2 (second (:second-word seed))] + (is (= 12 (count (string/split mnemonic #" ")))) ; assert 12-word seed phrase + (rf/dispatch-sync [:set-in [:my-profile/seed :word] word1]) + (rf/dispatch-sync [:my-profile/set-step :second-word]) + (rf/dispatch-sync [:set-in [:my-profile/seed :word] word2]) + ;; TODO: refactor (defn next-handler) & (defn enter-word) to improve test coverage? + (rf/dispatch [:my-profile/finish]) + (rf-test/wait-for + [:my-profile/finish-success] + (is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed + (logout!) + (rf-test/wait-for [::logout/logout-method]))))))) (def multiaccount-name "Narrow Frail Lemming") (def multiaccount-mnemonic @@ -180,20 +165,17 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) + (create-multiaccount!) (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] ; wait for the keys - (create-multiaccount!) + [::transport/messenger-started] + (assert-messenger-started) + (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat (rf-test/wait-for - [::transport/messenger-started] - (assert-messenger-started) - (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat - (rf-test/wait-for - [:chat/one-to-one-chat-created] - (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) - (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) - (logout!) - (rf-test/wait-for [::logout/logout-method]))))))) + [:chat/one-to-one-chat-created] + (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) + (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) + (logout!) + (rf-test/wait-for [::logout/logout-method])))))) (deftest delete-chat-test (log/info "========= delete-chat-test ==================") @@ -201,23 +183,20 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) + (create-multiaccount!) (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] ; wait for the keys - (create-multiaccount!) + [::transport/messenger-started] + (assert-messenger-started) + (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat (rf-test/wait-for - [::transport/messenger-started] - (assert-messenger-started) - (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat - (rf-test/wait-for - [:chat/one-to-one-chat-created] - (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) - (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) - (is @(rf/subscribe [:chats/chat chat-id])) - (rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id]) - (rf/dispatch-sync [:chat.ui/remove-chat chat-id]) - (logout!) - (rf-test/wait-for [::logout/logout-method]))))))) + [:chat/one-to-one-chat-created] + (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) + (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) + (is @(rf/subscribe [:chats/chat chat-id])) + (rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id]) + (rf/dispatch-sync [:chat.ui/remove-chat chat-id]) + (logout!) + (rf-test/wait-for [::logout/logout-method])))))) (deftest mute-chat-test (log/info "========= mute-chat-test ==================") @@ -225,29 +204,26 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) + (create-multiaccount!) (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] ; wait for the keys - (create-multiaccount!) + [::transport/messenger-started] + (assert-messenger-started) + (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat (rf-test/wait-for - [::transport/messenger-started] - (assert-messenger-started) - (rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat + [:chat/one-to-one-chat-created] + (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) + (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) + (is @(rf/subscribe [:chats/chat chat-id])) + (rf/dispatch-sync [:chat.ui/mute chat-id true constants/mute-till-unmuted]) (rf-test/wait-for - [:chat/one-to-one-chat-created] - (rf/dispatch-sync [:chat/navigate-to-chat chat-id]) - (is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) - (is @(rf/subscribe [:chats/chat chat-id])) - (rf/dispatch-sync [:chat.ui/mute chat-id true constants/mute-till-unmuted]) + [:chat/mute-successfully] + (is @(rf/subscribe [:chats/muted chat-id])) + (rf/dispatch-sync [:chat.ui/mute chat-id false]) (rf-test/wait-for [:chat/mute-successfully] - (is @(rf/subscribe [:chats/muted chat-id])) - (rf/dispatch-sync [:chat.ui/mute chat-id false]) - (rf-test/wait-for - [:chat/mute-successfully] - (is (not @(rf/subscribe [:chats/muted chat-id]))) - (logout!) - (rf-test/wait-for [::logout/logout-method]))))))))) + (is (not @(rf/subscribe [:chats/muted chat-id]))) + (logout!) + (rf-test/wait-for [::logout/logout-method])))))))) (deftest add-contact-test (log/info "========= add-contact-test ==================") @@ -260,27 +236,24 @@ (initialize-app!) (rf-test/wait-for [:profile/get-profiles-overview-success] - (generate-and-derive-addresses!) + (create-multiaccount!) (rf-test/wait-for - [:multiaccount-generate-and-derive-addresses-success] - (create-multiaccount!) + [::transport/messenger-started] + (assert-messenger-started) + ;; search for contact using compressed key + (rf/dispatch [:contacts/set-new-identity compressed-key]) (rf-test/wait-for - [::transport/messenger-started] - (assert-messenger-started) - ;; search for contact using compressed key - (rf/dispatch [:contacts/set-new-identity compressed-key]) + [:contacts/set-new-identity-success] + (let [new-identity @(rf/subscribe [:contacts/new-identity])] + (is (= public-key (:public-key new-identity))) + (is (= :valid (:state new-identity)))) + ;; click 'view profile' button + (rf/dispatch [:chat.ui/show-profile public-key]) (rf-test/wait-for - [:contacts/set-new-identity-success] - (let [new-identity @(rf/subscribe [:contacts/new-identity])] - (is (= public-key (:public-key new-identity))) - (is (= :valid (:state new-identity)))) - ;; click 'view profile' button - (rf/dispatch [:chat.ui/show-profile public-key]) + [:contacts/build-contact] (rf-test/wait-for - [:contacts/build-contact] - (rf-test/wait-for - [:contacts/contact-built] - (let [contact @(rf/subscribe [:contacts/current-contact])] - (is (= three-words-name (:primary-name contact)))) - (logout!) - (rf-test/wait-for [::logout/logout-method])))))))))) + [:contacts/contact-built] + (let [contact @(rf/subscribe [:contacts/current-contact])] + (is (= three-words-name (:primary-name contact)))) + (logout!) + (rf-test/wait-for [::logout/logout-method]))))))))) diff --git a/src/status_im/keycard/core.cljs b/src/status_im/keycard/core.cljs index 341e6526e5..016e91e8fd 100644 --- a/src/status_im/keycard/core.cljs +++ b/src/status_im/keycard/core.cljs @@ -14,7 +14,6 @@ [status-im.keycard.sign :as sign] status-im.keycard.unpair [status-im.keycard.wallet :as wallet] - [status-im.multiaccounts.recover.core :as multiaccounts.recover] [status-im.multiaccounts.update.core :as multiaccounts.update] [utils.re-frame :as rf] [utils.datetime :as datetime] @@ -589,7 +588,8 @@ (when (and (= card-state :profile/profile) (= flow :import)) (if (common/find-multiaccount-by-key-uid db key-uid) - (multiaccounts.recover/show-existing-multiaccount-alert key-uid) + ;; reimplement + ;;(multiaccounts.recover/show-existing-multiaccount-alert key-uid) (if paired? (load-recovery-pin-screen) (recovery/load-pair-screen)))) @@ -681,3 +681,11 @@ {:events [:keycard.callback/stop-nfc-failure]} [{:keys [db]} _] (log/debug "[keycard] nfc failed stopping")) ;; leave current value on :nfc-running + +(rf/defn init + {:events [:keycard/init]} + [_] + {:keycard/register-card-events nil + :keycard/check-nfc-support nil + :keycard/check-nfc-enabled nil + :keycard/retrieve-pairings nil}) diff --git a/src/status_im/keycard/recovery.cljs b/src/status_im/keycard/recovery.cljs index b3356e958a..3f80ff3bf4 100644 --- a/src/status_im/keycard/recovery.cljs +++ b/src/status_im/keycard/recovery.cljs @@ -139,7 +139,6 @@ {:db (-> db (update :keycard dissoc :flow) (dissoc :restored-account?))} - (multiaccounts.create/prepare-intro-wizard) (if (pos? (count accs)) (navigation/navigate-to :get-your-keys nil) (navigation/set-stack-root :onboarding [:get-your-keys]))))) diff --git a/src/status_im/multiaccounts/biometric/core.cljs b/src/status_im/multiaccounts/biometric/core.cljs deleted file mode 100644 index 814f4f2f3c..0000000000 --- a/src/status_im/multiaccounts/biometric/core.cljs +++ /dev/null @@ -1,229 +0,0 @@ -(ns status-im.multiaccounts.biometric.core - (:require ["react-native-touch-id" :default touchid] - [quo.design-system.colors :as colors] - [re-frame.core :as re-frame] - [utils.i18n :as i18n] - [native-module.core :as native-module] - [status-im.popover.core :as popover] - [utils.re-frame :as rf] - [status-im.utils.keychain.core :as keychain] - [status-im.utils.platform :as platform] - [taoensso.timbre :as log])) - -;; currently, for android, react-native-touch-id -;; is not returning supported biometric type -;; defaulting to :fingerprint -(def android-default-support :fingerprint) - -;;; android blacklist based on device info: - -(def deviceinfo (native-module/get-device-model-info)) - -;; {:model ? -;; :brand "Xiaomi" -;; :build-id "13D15" -;; :device-id "goldfish" -;; more info on https://github.com/react-native-community/react-native-device-info - -(def android-device-blacklisted? - (cond - (= (:brand deviceinfo) "bannedbrand") true - :else false)) - -;; biometric auth config -;; https://github.com/naoufal/react-native-touch-id#authenticatereason-config -(defn- authenticate-options - [ios-fallback-label] - (clj->js (merge - {:unifiedErrors true} - (when platform/ios? - {:passcodeFallback false - :fallbackLabel (or ios-fallback-label "")}) - (when platform/android? - {:title (i18n/label :t/biometric-auth-android-title) - :imageColor colors/blue - :imageErrorColor colors/red - :sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc) - :sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc) - :cancelText (i18n/label :cancel)})))) - -(defn get-label - [supported-biometric-auth] - (case supported-biometric-auth - :fingerprint (i18n/label :t/biometric-fingerprint) - :FaceID (i18n/label :t/biometric-faceid) - (i18n/label :t/biometric-touchid))) - -(defn- get-error-message - "must return an error message for the user" - [touchid-error-code] - (cond - ;; no message if user canceled or falled back to password - (= touchid-error-code "USER_CANCELED") nil - (= touchid-error-code "USER_FALLBACK") nil - ;; add here more specific errors if needed - ;; https://github.com/naoufal/react-native-touch-id#unified-errors - :else (i18n/label :t/biometric-auth-error - {:code touchid-error-code}))) - -(def success-result - {:bioauth-success true}) - -(defn- generate-error-result - [touchid-error-obj] - (let [code (aget touchid-error-obj "code")] - {:bioauth-success false - :bioauth-code code - :bioauth-message (get-error-message code)})) - -(defn- do-get-supported - [callback] - (-> (.isSupported touchid) - (.then #(callback (or (keyword %) android-default-support))) - (.catch #(callback nil)))) - -(defn get-supported - [callback] - (log/debug "[biometric] get-supported") - (cond platform/ios? (do-get-supported callback) - platform/android? (if android-device-blacklisted? - (callback nil) - (do-get-supported callback)) - :else (callback nil))) - -(defn authenticate-fx - ([cb] - (authenticate-fx cb nil)) - ([cb {:keys [reason ios-fallback-label]}] - (log/debug "[biometric] authenticate-fx") - (-> (.authenticate touchid reason (authenticate-options ios-fallback-label)) - (.then #(cb success-result)) - (.catch #(cb (generate-error-result %)))))) - -(re-frame/reg-fx - :biometric/get-supported-biometric-auth - (fn [] - (let [callback #(re-frame/dispatch [:init.callback/get-supported-biometric-auth-success %])] - ;;NOTE: if we can't save user password, we can't support biometrics - (keychain/can-save-user-password? - (fn [can-save?] - (if can-save? - (get-supported callback) - (callback nil))))))) - -(rf/defn set-supported-biometric-auth - {:events [:init.callback/get-supported-biometric-auth-success]} - [{:keys [db]} supported-biometric-auth] - {:db (assoc db :supported-biometric-auth supported-biometric-auth)}) - -(rf/defn authenticate - [_ cb options] - {:biometric-auth/authenticate [cb options]}) - -(re-frame/reg-fx - :biometric-auth/authenticate - (fn [[cb options]] - (authenticate-fx #(cb %) options))) - -(re-frame/reg-fx - :biometric/enable-and-save-password - (fn [{:keys [key-uid - masked-password - on-success - on-error]}] - (-> (keychain/save-user-password! - key-uid - masked-password) - (.then - (fn [_] - (keychain/save-auth-method! - key-uid - keychain/auth-method-biometric))) - (.then - (fn [_] - (when on-success - (on-success)))) - (.catch (fn [error] - (when on-error - (on-error error))))))) - -(rf/defn update-biometric - [{db :db :as cofx} biometric-auth?] - (let [key-uid (or (get-in db [:profile/profile :key-uid]) - (get-in db [:profile/login :key-uid]))] - (rf/merge cofx - (keychain/save-auth-method - key-uid - (if biometric-auth? - keychain/auth-method-biometric - keychain/auth-method-none)) - #(when-not biometric-auth? - {:keychain/clear-user-password key-uid})))) - -(rf/defn biometric-auth-switched - {:events [:multiaccounts.ui/biometric-auth-switched]} - [cofx biometric-auth?] - (if biometric-auth? - (authenticate - cofx - #(re-frame/dispatch [:biometric-init-done %]) - {}) - (update-biometric cofx false))) - -(rf/defn show-message - [cofx bioauth-message bioauth-code] - (let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code) - (i18n/label :t/grant-face-id-permissions)) - bioauth-message)] - (when content - {:utils/show-popup - {:title (i18n/label :t/biometric-auth-login-error-title) - :content content}}))) - -(rf/defn biometric-init-done - {:events [:biometric-init-done]} - [cofx {:keys [bioauth-success bioauth-message bioauth-code]}] - (if bioauth-success - (if (= keychain/auth-method-password - (get-in cofx [:db :auth-method])) - (update-biometric cofx true) - (popover/show-popover cofx {:view :enable-biometric})) - (show-message cofx bioauth-message bioauth-code))) - -(rf/defn biometric-auth - {:events [:biometric-authenticate]} - [cofx] - (authenticate - cofx - #(re-frame/dispatch [:biometric-auth-done %]) - {:reason (i18n/label :t/biometric-auth-reason-login) - :ios-fallback-label (i18n/label :t/biometric-auth-login-ios-fallback-label)})) - -(rf/defn enable - {:events [:biometric/enable]} - [cofx] - (rf/merge - cofx - (popover/hide-popover) - (authenticate #(re-frame/dispatch [:biometric/setup-done %]) {}))) - -(rf/defn disable - {:events [:biometric/disable]} - [{:keys [db] :as cofx}] - (rf/merge - cofx - {:db (-> db - (assoc :auth-method keychain/auth-method-none) - (assoc-in [:profile/login :save-password?] false))} - (popover/hide-popover))) - -(rf/defn setup-done - {:events [:biometric/setup-done]} - [{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}] - (log/debug "[biometric] setup-done" - "bioauth-success" bioauth-success - "bioauth-message" bioauth-message - "bioauth-code" bioauth-code) - (if bioauth-success - {:db (assoc db :auth-method keychain/auth-method-biometric-prepare)} - (show-message cofx bioauth-message bioauth-code))) diff --git a/src/status_im/multiaccounts/create/core.cljs b/src/status_im/multiaccounts/create/core.cljs index 79c71ec8cd..61e8fc9a0a 100644 --- a/src/status_im/multiaccounts/create/core.cljs +++ b/src/status_im/multiaccounts/create/core.cljs @@ -41,43 +41,6 @@ (fn [cofx _] (assoc cofx :signing-phrase (signing-phrase/generate)))) -(re-frame/reg-fx - ::store-multiaccount - (fn [[id key-uid hashed-password callback]] - (native-module/multiaccount-store-derived - id - key-uid - [constants/path-wallet-root - constants/path-eip1581 - constants/path-whisper - constants/path-default-wallet] - hashed-password - callback))) - -(rf/defn create-multiaccount - {:events [:create-multiaccount]} - [{:keys [db]} key-code] - (let [{:keys [selected-id]} (:intro-wizard db)] - {::store-multiaccount - [selected-id - (some - (fn [{:keys [id key-uid]}] - (when (= id selected-id) - key-uid)) - (get-in db [:intro-wizard :multiaccounts])) - (ethereum/sha3 (security/safe-unmask-data key-code)) - (fn [result] - (let [derived-data (normalize-derived-data-keys (types/json->clj result)) - public-key (get-in derived-data [constants/path-whisper-keyword :public-key])] - (native-module/gfycat-identicon-async - public-key - (fn [name _] - (let [derived-whisper (derived-data constants/path-whisper-keyword) - derived-data-extended (assoc-in derived-data - [constants/path-whisper-keyword] - (assoc derived-whisper :name name))] - (re-frame/dispatch [::store-multiaccount-success key-code derived-data-extended]))))))]})) - (re-frame/reg-fx :multiaccount-generate-and-derive-addresses (fn [] @@ -116,10 +79,6 @@ (dissoc :recovered-account?)) :multiaccount-generate-and-derive-addresses nil}) -(rf/defn prepare-intro-wizard - [{:keys [db]}] - {:db (assoc db :intro-wizard {})}) - (rf/defn save-multiaccount-and-login-with-keycard [_ args] {:keycard/save-multiaccount-and-login args}) @@ -255,27 +214,3 @@ settings (node/get-new-config db) accounts-data))))) - -(rf/defn store-multiaccount-success - {:events [::store-multiaccount-success] - :interceptors [(re-frame/inject-cofx :random-guid-generator) - (re-frame/inject-cofx ::get-signing-phrase)]} - [{:keys [db] :as cofx} password derived] - (rf/merge cofx - {:db (dissoc db :intro-wizard)} - (on-multiaccount-created (assoc (let [{:keys [selected-id multiaccounts]} (:intro-wizard db)] - (some #(when (= selected-id (:id %)) %) multiaccounts)) - :derived derived - :recovered (get-in db [:intro-wizard :recovering?])) - password - {:save-mnemonic? true}))) - -(rf/defn on-key-selected - {:events [:intro-wizard/on-key-selected]} - [{:keys [db]} id] - {:db (assoc-in db [:intro-wizard :selected-id] id)}) - -(rf/defn on-key-storage-selected - {:events [:intro-wizard/on-key-storage-selected]} - [{:keys [db]} storage-type] - {:db (assoc-in db [:intro-wizard :selected-storage-type] storage-type)}) diff --git a/src/status_im/multiaccounts/key_storage/core.cljs b/src/status_im/multiaccounts/key_storage/core.cljs deleted file mode 100644 index ccc1d347e8..0000000000 --- a/src/status_im/multiaccounts/key_storage/core.cljs +++ /dev/null @@ -1,21 +0,0 @@ -(ns status-im.multiaccounts.key-storage.core - (:require [clojure.string :as string] - [re-frame.core :as re-frame] - [status-im.ethereum.core :as ethereum] - [native-module.core :as native-module] - [status-im.utils.types :as types] - [utils.security.core :as security])) - -(re-frame/reg-fx - :key-storage/delete-imported-key - (fn [{:keys [key-uid address password on-success on-error]}] - (let [hashed-pass (ethereum/sha3 (security/safe-unmask-data password))] - (native-module/delete-imported-key - key-uid - (string/lower-case (subs address 2)) - hashed-pass - (fn [result] - (let [{:keys [error]} (types/json->clj result)] - (if-not (string/blank? error) - (on-error error) - (on-success)))))))) diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index c8d489c4c8..a3e933d4a6 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -17,17 +17,14 @@ [status-im.fleet.core :as fleet] [utils.i18n :as i18n] [status-im.mobile-sync-settings.core :as mobile-network] - [status-im.multiaccounts.biometric.core :as biometric] [status-im.multiaccounts.core :as multiaccounts] [native-module.core :as native-module] [status-im.notifications.core :as notifications] - [status-im.popover.core :as popover] [status-im.signing.eip1559 :as eip1559] [status-im.transport.core :as transport] [status-im.ui.components.react :as react] [status-im2.config :as config] [utils.re-frame :as rf] - [status-im.utils.keychain.core :as keychain] [status-im.utils.mobile-sync :as utils.mobile-sync] [status-im.utils.platform :as platform] [status-im.utils.types :as types] @@ -46,7 +43,7 @@ [taoensso.timbre :as log] [status-im2.contexts.shell.jump-to.utils :as shell.utils] [utils.security.core :as security] - [status-im.keycard.common :as keycard.common])) + [status-im2.common.keychain.events :as keychain])) (re-frame/reg-fx ::initialize-transactions-management-enabled @@ -56,22 +53,6 @@ :transactions-management-enabled? callback)))) -(re-frame/reg-fx - ::login - (fn [[key-uid _ hashed-password]] - (native-module/login-account {:keyUid key-uid - :password hashed-password - :wakuV2Nameserver "1.1.1.1" - :openseaAPIKey config/opensea-api-key - - :poktToken config/POKT_TOKEN - :infuraToken config/INFURA_TOKEN - - :alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN - :alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN - :alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN - :alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN}))) - (re-frame/reg-fx ::export-db (fn [[key-uid account-data hashed-password callback]] @@ -177,27 +158,8 @@ (wallet/request-current-block-update)) (prices/update-prices))) -(rf/defn login-local-paired-user - {:events [:multiaccounts.login/local-paired-user]} - [{:keys [db]}] - (let [{:keys [key-uid name password]} (get-in db [:syncing :profile/profile])] - {::login [key-uid - (types/clj->json {:name name - :key-uid key-uid}) - password]})) -(rf/defn login - {:events [:multiaccounts.login.ui/password-input-submitted]} - [{:keys [db]}] - (let [{:keys [key-uid password name]} (:profile/login db)] - {:db (-> db - (assoc-in [:profile/login :processing] true) - (dissoc :intro-wizard :recovered-account?) - (update :keycard dissoc :flow)) - ::login [key-uid - (types/clj->json {:name name - :key-uid key-uid}) - (ethereum/sha3 (security/safe-unmask-data password))]})) + (rf/defn export-db-submitted {:events [:multiaccounts.login.ui/export-db-submitted]} @@ -392,7 +354,7 @@ (rf/defn get-node-config-callback {:events [::get-node-config-callback]} - [{:keys [db] :as cofx} node-config-json] + [{:keys [db]} node-config-json] (let [node-config (types/json->clj node-config-json)] {:db (assoc-in db [:profile/profile :wakuv2-config] @@ -414,14 +376,11 @@ (get db :onboarding-2/new-account?) {:dispatch [:onboarding-2/finalize-setup]} - (get db :tos/accepted?) + :else (rf/merge cofx (multiaccounts/switch-theme nil :shell-stack) - (navigation/init-root :shell-stack)) - - :else - {:dispatch [:init-root :tos]}))) + (navigation/init-root :shell-stack))))) (rf/defn get-settings-callback {:events [::get-settings-callback]} @@ -448,7 +407,6 @@ #(do (re-frame/dispatch [:chats-list/load-success %]) (rf/dispatch [:communities/get-user-requests-to-join]) (re-frame/dispatch [::get-chats-callback]))}) - (initialize-wallet-connect) (get-node-config) (communities/fetch) (contract-communities/fetch-contract-communities) @@ -530,11 +488,9 @@ (defn get-new-auth-method [auth-method save-password?] (when save-password? - (when-not (or (= keychain/auth-method-biometric auth-method) - (= keychain/auth-method-password auth-method)) - (if (= auth-method keychain/auth-method-biometric-prepare) - keychain/auth-method-biometric - keychain/auth-method-password)))) + (when-not (= keychain/auth-method-biometric auth-method) + (when (= auth-method keychain/auth-method-biometric-prepare) + keychain/auth-method-biometric)))) (rf/defn login-only-events [{:keys [db] :as cofx} key-uid password save-password?] @@ -549,8 +505,6 @@ [{:method "settings_getSettings" :on-success #(re-frame/dispatch [::get-settings-callback %])}]} (notifications/load-notification-preferences) - (when save-password? - (keychain/save-user-password key-uid password)) (keychain/save-auth-method key-uid (or new-auth-method auth-method keychain/auth-method-none))))) @@ -560,14 +514,13 @@ db {:keys [creating?]} (:profile/login db) first-account? (and creating? (empty? profiles-overview)) - tos-accepted? (get db :tos/accepted?) {:networks/keys [current-network networks]} db network-id (str (get-in networks [current-network :config :NetworkId]))] (shell.utils/change-selected-stack-id :communities-stack true nil) (rf/merge cofx {:db (-> db (dissoc :profile/login) - (assoc :tos/next-root :enable-notifications :chats/loading? false) + (assoc :chats/loading? false) (assoc-in [:profile/profile :multiaccounts/first-account] first-account?)) ::get-tokens [network-id wallet-accounts recovered-account?]} @@ -586,7 +539,7 @@ (boolean (get-in cofx [:db :keycard :flow]))) (defn on-login-update-db - [db login-only? now] + [db now] (-> db (dissoc :connectivity/ui-status-properties) (update :keycard dissoc :from-key-storage-and-migration?) @@ -595,10 +548,6 @@ :card-read-in-progress? :pin :profile/profile) - (assoc :tos-accept-next-root - (if login-only? - :shell-stack - :onboarding-notification)) (assoc :logged-in-since now) (assoc :view-id :home))) @@ -620,7 +569,7 @@ "login-only?" login-only? "recovered-account?" recovered-account?) (rf/merge cofx - {:db (on-login-update-db db login-only? now) + {:db (on-login-update-db db now) :json-rpc/call [{:method "web3_clientVersion" :on-success #(re-frame/dispatch [::initialize-web3-client-version %])}]} @@ -636,151 +585,3 @@ (if login-only? (login-only-events key-uid password save-password?) (create-only-events recovered-account?))))) - -(rf/defn open-login-callback - {:events [:multiaccounts.login.callback/get-user-password-success]} - [{:keys [db] :as cofx} password] - (let [key-uid (get-in db [:profile/login :key-uid]) - keycard-account? (boolean (get-in db - [:profile/profiles-overview - key-uid - :keycard-pairing])) - goto-key-storage? (:goto-key-storage? db)] - (if password - (rf/merge - cofx - {:db (update-in db - [:profile/login] - assoc - :password password - :save-password? true) - :set-root :progress} - login) - (rf/merge - cofx - {:db (dissoc db :goto-key-storage?)} - (when keycard-account? - {:db (-> db - (assoc-in [:keycard :pin :status] nil) - (assoc-in [:keycard :pin :login] []))}) - #(if keycard-account? - {:set-root :multiaccounts-keycard} - {:set-root :profiles}) - #(when goto-key-storage? - (navigation/navigate-to % :actions-not-logged-in nil)))))) - -(rf/defn get-credentials - [{:keys [db] :as cofx} key-uid] - (let [keycard-multiaccount? (boolean (get-in db - [:profile/profiles-overview key-uid :keycard-pairing]))] - (log/debug "[login] get-credentials" - "keycard-multiacc?" - keycard-multiaccount?) - (if keycard-multiaccount? - (keychain/get-keycard-keys cofx key-uid) - (keychain/get-user-password cofx key-uid)))) - -(rf/defn get-auth-method-success - "Auth method: nil - not supported, \"none\" - not selected, \"password\", \"biometric\", \"biometric-prepare\"" - {:events [:multiaccounts.login/get-auth-method-success]} - [{:keys [db] :as cofx} auth-method] - (let [key-uid (get-in db [:profile/login :key-uid]) - keycard-profile? (boolean (get-in db [:profile/profiles-overview key-uid :keycard-pairing]))] - (rf/merge - cofx - {:db (assoc db :auth-method auth-method)} - #(cond - (= auth-method keychain/auth-method-biometric) - (biometric/biometric-auth %) - (= auth-method keychain/auth-method-password) - (get-credentials % key-uid) - (and keycard-profile? (get-in db [:keycard :card-connected?])) - (keycard.common/get-application-info % nil)) - (open-login-callback nil)))) - -(rf/defn biometric-auth-done - {:events [:biometric-auth-done]} - [{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}] - (let [key-uid (get-in db [:profile/login :key-uid]) - auth-method (get db :auth-method)] - (log/debug "[biometric] biometric-auth-done" - "bioauth-success" bioauth-success - "bioauth-message" bioauth-message - "bioauth-code" bioauth-code) - (if bioauth-success - (get-credentials cofx key-uid) - (rf/merge cofx - {:db (assoc-in db - [:profile/login :save-password?] - (= auth-method keychain/auth-method-biometric))} - (when-not (= auth-method keychain/auth-method-biometric) - (keychain/save-auth-method key-uid keychain/auth-method-none)) - (biometric/show-message bioauth-message bioauth-code) - (open-login-callback nil))))) - -(rf/defn save-password - {:events [:multiaccounts/save-password]} - [{:keys [db] :as cofx} save-password?] - (let [bioauth-supported? (boolean (get db :supported-biometric-auth)) - previous-auth-method (get db :auth-method)] - (log/debug "[login] save-password" - "save-password?" save-password? - "bioauth-supported?" bioauth-supported? - "previous-auth-method" previous-auth-method) - (rf/merge - cofx - {:db (cond-> db - (not= previous-auth-method - keychain/auth-method-biometric-prepare) - (assoc :auth-method keychain/auth-method-none) - (or save-password? - (not bioauth-supported?) - (and (not save-password?) - bioauth-supported? - (= previous-auth-method keychain/auth-method-none))) - (assoc-in [:profile/login :save-password?] save-password?))} - (when bioauth-supported? - (if save-password? - (popover/show-popover {:view :secure-with-biometric}) - (when-not (= previous-auth-method keychain/auth-method-none) - (popover/show-popover {:view :disable-password-saving}))))))) - -(rf/defn welcome-lets-go - {:events [:welcome-lets-go]} - [_] - {:set-root :shell-stack}) - -(rf/defn hide-keycard-banner - {:events [:hide-keycard-banner]} - [{:keys [db]}] - {:db (assoc db :keycard/banner-hidden true) - ::async-storage/set! {:keycard-banner-hidden true}}) - -(rf/defn get-keycard-banner-preference-cb - {:events [:get-keycard-banner-preference-cb]} - [{:keys [db]} {:keys [keycard-banner-hidden]}] - {:db (assoc db :keycard/banner-hidden keycard-banner-hidden)}) - -(rf/defn get-keycard-banner-preference - {:events [:get-keycard-banner-preference]} - [_] - {::async-storage/get {:keys [:keycard-banner-hidden] - :cb #(re-frame/dispatch [:get-keycard-banner-preference-cb %])}}) - -(rf/defn get-opted-in-to-new-terms-of-service-cb - {:events [:get-opted-in-to-new-terms-of-service-cb]} - [{:keys [db]} {:keys [new-terms-of-service-accepted]}] - {:db (assoc db :tos/accepted? new-terms-of-service-accepted)}) - -(rf/defn get-opted-in-to-new-terms-of-service - "New TOS sprint https://github.com/status-im/status-mobile/pull/12240" - {:events [:get-opted-in-to-new-terms-of-service]} - [{:keys [db]}] - {::async-storage/get {:keys [:new-terms-of-service-accepted] - :cb #(re-frame/dispatch [:get-opted-in-to-new-terms-of-service-cb %])}}) - -(rf/defn hide-terms-of-services-opt-in-screen - {:events [:hide-terms-of-services-opt-in-screen]} - [{:keys [db]}] - {::async-storage/set! {:new-terms-of-service-accepted true} - :db (assoc db :tos/accepted? true)}) diff --git a/src/status_im/multiaccounts/login/core_test.cljs b/src/status_im/multiaccounts/login/core_test.cljs deleted file mode 100644 index c9763c7a07..0000000000 --- a/src/status_im/multiaccounts/login/core_test.cljs +++ /dev/null @@ -1,62 +0,0 @@ -(ns status-im.multiaccounts.login.core-test - (:require [cljs.test :as test] - [status-im.multiaccounts.biometric.core :as biometric] - [status-im.multiaccounts.login.core :as login] - [utils.re-frame :as rf] - [status-im.utils.keychain.core :as keychain])) - -(test/deftest save-password-test - (test/testing "check save password, biometric unavailable" - (let [initial-cofx {:db {:auth-method keychain/auth-method-none - :supported-biometric-auth false}} - {:keys [db]} (login/save-password initial-cofx true)] - (test/is (= false (contains? db :popover/popover))) - (test/is (= true (get-in db [:profile/login :save-password?]))) - (test/testing "uncheck save password" - (let [{:keys [db]} (login/save-password {:db db} false)] - (test/is (= false (contains? db :popover/popover))) - (test/is (= false (get-in db [:profile/login :save-password?]))))))) - (test/testing "check save password, biometric available" - (let [initial-cofx {:db {:auth-method keychain/auth-method-none - :supported-biometric-auth true}} - {:keys [db]} (login/save-password initial-cofx true)] - (test/is (= true (get-in db [:profile/login :save-password?]))) - (test/testing "enable biometric auth" - (let [{:keys [db] :as res} (biometric/enable {:db db})] - (test/is (contains? res :biometric-auth/authenticate)) - (test/is (= false (contains? db :popover/popover))) - (test/testing "biometric auth successfully enabled" - (let [{:keys [db]} (biometric/setup-done - {:db db} - {:bioauth-success true - :bioauth-message nil - :bioauth-code nil})] - (test/is (= keychain/auth-method-biometric-prepare - (:auth-method db))))) - (test/testing "biometric auth canceled" - (let [{:keys [db]} (biometric/setup-done - {:db db} - {:bioauth-success false - :bioauth-message nil - :bioauth-code "USER_CANCELED"})] - (test/is (= nil db) "no db changes"))))))) - (test/testing (str "check save password, enable biometric auth," - "uncheck save password") - (let [initial-cofx {:db {:auth-method keychain/auth-method-none - :supported-biometric-auth true}} - {:keys [db]} (login/save-password (rf/merge - initial-cofx - (login/save-password true) - (biometric/enable) - (biometric/setup-done - {:bioauth-success true - :bioauth-message nil - :bioauth-code nil})) - false)] - (test/is (= true (get-in db [:profile/login :save-password?]))) - ;; case 2 from https://github.com/status-im/status-mobile/issues/9573 - (test/is (= keychain/auth-method-biometric-prepare (:auth-method db))) - (test/testing "disable biometric" - (let [{:keys [db]} (biometric/disable {:db db})] - (test/is (= false (get-in db [:profile/login :save-password?]))) - (test/is (= keychain/auth-method-none (:auth-method db)))))))) diff --git a/src/status_im/multiaccounts/login/data_test.cljs b/src/status_im/multiaccounts/login/data_test.cljs deleted file mode 100644 index fe4aeb3e75..0000000000 --- a/src/status_im/multiaccounts/login/data_test.cljs +++ /dev/null @@ -1,121 +0,0 @@ -(ns status-im.multiaccounts.login.data-test) - -(def all-contacts - [{:last-updated 1547185503000 - :tags #{} - :address "2f88d65f3cb52605a54a833ae118fb1363acccd2" - :name "Darkviolet Lightgreen Halcyon" - :added? true - :last-online 0 - :public-key - "0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"} - {:last-updated 1547271764000 - :address "b267ff8336ac10b3a1986c04a70ff91fb03d0b78" - :name "rv" - :added? true - :last-online 0 - :public-key - "0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"}]) - -(def chats - [{:updated-at nil - :tags #{} - :color "#51d0f0" - :contacts #{} - :last-clock-value 154990121501242 - :admins #{} - :members-joined #{} - :name "status" - :membership-updates () - :unviewed-messages-count 0 - :last-message-content-type "text/plain" - :last-message-content {:chat-id "status" :text "darn typos...! "} - :debug? false - :group-chat true - :public? true - :chat-id "status" - :timestamp 1547361080397 - :deleted-at-clock-value nil} - {:updated-at nil - :tags #{} - :color "#d37ef4" - :contacts - #{"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"} - :last-clock-value 154727176928001 - :admins #{} - :members-joined #{} - :name "rv" - :membership-updates () - :unviewed-messages-count 0 - :last-message-content-type "text/plain" - :last-message-content - {:chat-id - "0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c" - :text "Hey"} - :debug? false - :group-chat false - :public? false - :chat-id - "0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f" - :timestamp 1547271770816 - :deleted-at-clock-value nil} - {:updated-at nil - :tags #{} - :color "#7cda00" - :contacts - #{"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"} - :last-clock-value 154718689430301 - :admins #{} - :members-joined #{} - :name "Darkviolet Lightgreen Halcyon" - :membership-updates () - :unviewed-messages-count 0 - :last-message-content-type "text/plain" - :last-message-content - {:chat-id - "0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c" - :text "Djndjd"} - :debug? false - :group-chat false - :public? false - :chat-id - "0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24" - :timestamp 1547186895328 - :deleted-at-clock-value nil}]) - -(def multiaccount - {:last-updated 0 - :address "7540c34d6c4082391f12468580a9a4e0724c6755" - :mnemonic "tumble gorilla neglect dumb budget involve tennis ocean diary eagle lady ring" - :custom-bootnodes {} - :signing-phrase "bull exam weed" - :signed-up? true - :name "name" - :last-request nil - :wallet/visible-tokens {:mainnet #{:SNT} - :goerli #{:STT} - :xdai #{}} - :preview-privacy? true - :fleet :eth.prod - :wallet-set-up-passed? false - :public-key - "0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c" - :keycard-key-uid nil - :installation-id "618ec020-13c8-5505-8aa6-9c5444317e7f"}) - -(def multiaccounts - {"address" multiaccount}) - -(defn get-chats [_ _] chats) - -(def transport - {"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24" - {:resend? nil} - "0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f" - {:resend? nil} - "status" - {:resend? nil}}) - -(def topics - {"0xf8946aac" {:chat-ids #{:discovery-topic} - :last-request 1547319670}}) diff --git a/src/status_im/multiaccounts/login/flow_test.cljs b/src/status_im/multiaccounts/login/flow_test.cljs deleted file mode 100644 index 88d3a74159..0000000000 --- a/src/status_im/multiaccounts/login/flow_test.cljs +++ /dev/null @@ -1,59 +0,0 @@ -(ns status-im.multiaccounts.login.flow-test - "The main purpose of these tests is to signal that some steps of the sign in - flow has been changed. Such changes should be reflected in both these tests - and documents which describe the whole \"sign in\" flow." - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.ethereum.core :as ethereum] - [status-im.multiaccounts.login.core :as login.core] - [status-im.multiaccounts.login.data-test :as data])) - -(deftest on-password-input-submitted - (testing - "handling :multiaccounts.login.ui/password-input-submitted event" - (let [cofx {:db {:profile/login {:key-uid "key-uid" - :password "password" - :name "user"}}} - efx (login.core/login cofx)] - (testing "Change multiaccount." - (is (= (::login.core/login efx) - ["key-uid" "{\"name\":\"user\",\"key-uid\":\"key-uid\"}" - (ethereum/sha3 "password")]))) - (testing "start activity indicator" - (is (= (get-in efx [:db :profile/login :processing]) true)))))) - -(deftest login-success - (testing ":accounts.login.callback/login-success event received." - (let [db {:profile/login {:address "address" - :password "password"} - :profile/profile data/multiaccount} - cofx {:db db} - efx (login.core/multiaccount-login-success cofx) - json-rpc (into #{} (map :method (:json-rpc/call efx)))] - ;; TODO: Account is now cleared only after all sign in fx are executed. - ;; (testing ":accounts/login cleared." - ;; (is (not (contains? new-db :profile/login)))) - (testing "Check the rest of effects." - (is (json-rpc "web3_clientVersion")))))) - -;;TODO re-enable when keycard is fixed -#_(deftest login - (testing "login with keycard" - (let - [wpk "c56c7ac797c27b3790ce02c2459e9957c5d20d7a2c55320535526ce9e4dcbbef" - epk - "04f43da85ff1c333f3e7277b9ac4df92c9120fbb251f1dede7d41286e8c055acfeb845f6d2654821afca25da119daff9043530b296ee0e28e202ba92ec5842d617" - db - {:keycard - {:profile/profile - {:encryption-public-key epk - :whisper-private-key wpk - :wallet-address "83278851e290d2488b6add2a257259f5741a3b7d" - :whisper-public-key - "0x04491c1272149d7fa668afa45968c9914c0661641ace7dbcbc585c15070257840a0b4b1f71ce66c2147e281e1a44d6231b4731a26f6cc0a49e9616bbc7fc2f1a93" - :whisper-address "b8bec30855ff20c2ddab32282e2b2c8c8baca70d"}}} - result (login.core/login {:db db})] - (is (= (-> result (get :keycard/login-with-keycard) keys count) - 3)) - (is (= (get-in result [:keycard/login-with-keycard :whisper-private-key wpk]))) - (is (= (get-in result [:keycard/login-with-keycard :encryption-public-key epk]))) - (is (fn? (get-in result [:keycard/login-with-keycard :on-result])))))) diff --git a/src/status_im/multiaccounts/logout/core.cljs b/src/status_im/multiaccounts/logout/core.cljs index a1fe427106..faaea55589 100644 --- a/src/status_im/multiaccounts/logout/core.cljs +++ b/src/status_im/multiaccounts/logout/core.cljs @@ -5,9 +5,24 @@ [native-module.core :as native-module] [status-im.notifications.core :as notifications] [utils.re-frame :as rf] - [status-im.utils.keychain.core :as keychain] [status-im.wallet.core :as wallet] - [status-im2.events :as init])) + [status-im2.common.keychain.events :as keychain] + [status-im2.db :as db])) + +(re-frame/reg-fx + ::logout + (fn [] + (native-module/logout))) + +(rf/defn initialize-app-db + [{{:keys [keycard] + :biometric/keys [supported-type] + :network/keys [type]} + :db}] + {:db (assoc db/app-db + :network/type type + :keycard (dissoc keycard :secrets :pin :application-info) + :biometric/supported-type supported-type)}) (rf/defn logout-method {:events [::logout-method]} @@ -21,12 +36,11 @@ ::logout nil ::multiaccounts/webview-debug-changed false :keychain/clear-user-password key-uid - :profile/get-profiles-overview #(re-frame/dispatch - [:profile/get-profiles-overview-success - %])} + :profile/get-profiles-overview #(rf/dispatch + [:profile/get-profiles-overview-success %])} (keychain/save-auth-method key-uid auth-method) (wallet/clear-timeouts) - (init/initialize-app-db)))) + (initialize-app-db)))) (rf/defn logout {:events [:logout :multiaccounts.logout.ui/logout-confirmed @@ -49,17 +63,3 @@ :confirm-button-text (i18n/label :t/logout) :on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) :on-cancel nil}}) - -(rf/defn biometric-logout - {:events [:biometric-logout]} - [cofx] - (rf/merge cofx - (logout-method {:auth-method keychain/auth-method-biometric-prepare - :logout? false}) - (fn [{:keys [db]}] - {:db (assoc-in db [:profile/login :save-password?] true)}))) - -(re-frame/reg-fx - ::logout - (fn [] - (native-module/logout))) diff --git a/src/status_im/multiaccounts/recover/core.cljs b/src/status_im/multiaccounts/recover/core.cljs index a57a29ef02..33a30190df 100644 --- a/src/status_im/multiaccounts/recover/core.cljs +++ b/src/status_im/multiaccounts/recover/core.cljs @@ -1,86 +1,10 @@ (ns status-im.multiaccounts.recover.core - (:require [clojure.string :as string] - [re-frame.core :as re-frame] - [status-im.bottom-sheet.events :as bottom-sheet] + (:require [re-frame.core :as re-frame] [status-im2.constants :as constants] - [status-im.ethereum.core :as ethereum] - [status-im.ethereum.mnemonic :as mnemonic] - [utils.i18n :as i18n] - [status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.create.core :as multiaccounts.create] [native-module.core :as native-module] - [status-im.popover.core :as popover] - [utils.re-frame :as rf] [status-im.utils.types :as types] - [status-im2.navigation.events :as navigation] - [taoensso.timbre :as log] - [utils.security.core :as security])) - -(defn existing-account? - [multiaccounts key-uid] - {:pre [(not (nil? key-uid))]} - (contains? multiaccounts key-uid)) - -(defn check-phrase-warnings - [recovery-phrase] - (cond (string/blank? recovery-phrase) :t/required-field)) - -(rf/defn set-phrase - {:events [:multiaccounts.recover/passphrase-input-changed]} - [{:keys [db]} masked-recovery-phrase] - (let [recovery-phrase (security/safe-unmask-data masked-recovery-phrase)] - {:db (update db - :intro-wizard assoc - :passphrase (string/lower-case recovery-phrase) - :passphrase-error nil - :next-button-disabled? (or (empty? recovery-phrase) - (not (mnemonic/valid-length? recovery-phrase))))})) - -(rf/defn validate-phrase-for-warnings - [{:keys [db]}] - (let [recovery-phrase (get-in db [:intro-wizard :passphrase])] - {:db (update db - :intro-wizard assoc - :passphrase-error (check-phrase-warnings recovery-phrase))})) - -(rf/defn on-store-multiaccount-success - {:events [::store-multiaccount-success] - :interceptors [(re-frame/inject-cofx :random-guid-generator) - (re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]} - [{:keys [db] :as cofx} result password] - (let [{:keys [error]} (types/json->clj result)] - (if error - {:utils/show-popup {:title (i18n/label :t/multiaccount-exists-title) - :content (i18n/label :t/multiaccount-exists-title) - :on-dismiss #(re-frame/dispatch [:pop-to-root :multiaccounts])}} - (let [{:keys [key-uid] :as multiaccount} (get-in db [:intro-wizard :root-key]) - keycard-multiaccount? (boolean (get-in db - [:profile/profiles-overview key-uid - :keycard-pairing]))] - (if keycard-multiaccount? - ;; trying to recover multiaccount created with keycard - {:db (-> db - (update :intro-wizard assoc - :processing? false - :passphrase-error :recover-keycard-multiaccount-not-supported) - (update :intro-wizard - dissoc - :passphrase-valid?))} - (let [multiaccount (assoc multiaccount :derived (get-in db [:intro-wizard :derived]))] - (multiaccounts.create/on-multiaccount-created cofx - multiaccount - password - {}))))))) - -(rf/defn store-multiaccount - {:events [::recover-multiaccount-confirmed]} - [{:keys [db]} password] - (let [{:keys [root-key]} (:intro-wizard db) - {:keys [id key-uid]} root-key - callback #(re-frame/dispatch [::store-multiaccount-success % password]) - hashed-password (ethereum/sha3 (security/safe-unmask-data password))] - {:db (assoc-in db [:intro-wizard :processing?] true) - ::multiaccounts.create/store-multiaccount [id key-uid hashed-password callback]})) + [taoensso.timbre :as log])) (re-frame/reg-fx ::import-multiaccount @@ -110,68 +34,3 @@ (update derived-data constants/path-whisper-keyword assoc :name name)] (re-frame/dispatch [success-event root-data derived-data-extended])))))))))))) -(rf/defn show-existing-multiaccount-alert - [_ key-uid] - {:utils/show-confirmation - {:title (i18n/label :t/multiaccount-exists-title) - :content (i18n/label :t/multiaccount-exists-content) - :confirm-button-text (i18n/label :t/unlock) - :on-accept #(do - (re-frame/dispatch [:pop-to-root :multiaccounts]) - (re-frame/dispatch - [:profile/profile-selected key-uid])) - :on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}) - -(rf/defn on-import-multiaccount-success - {:events [::import-multiaccount-success]} - [{:keys [db] :as cofx} {:keys [key-uid] :as root-data} derived-data] - (let [multiaccounts (:profile/profiles-overview db)] - (rf/merge - cofx - {:db (update db - :intro-wizard - assoc - :root-key root-data - :derived derived-data - :step :recovery-success)} - (when (existing-account? multiaccounts key-uid) - (show-existing-multiaccount-alert key-uid)) - (navigation/navigate-to :recover-multiaccount-success nil)))) - -(rf/defn enter-phrase-pressed - {:events [::enter-phrase-pressed]} - [{:keys [db] :as cofx}] - (rf/merge - cofx - {:db (-> db - (assoc :intro-wizard - {:step :enter-phrase - :recovering? true - :next-button-disabled? true - :weak-password? true - :encrypt-with-password? true - :forward-action :multiaccounts.recover/enter-phrase-next-pressed} - :recovered-account? true) - (update :keycard dissoc :flow))} - (bottom-sheet/hide-bottom-sheet-old) - (navigation/navigate-to :recover-multiaccount-enter-phrase nil))) - -(rf/defn proceed-to-import-mnemonic - {:events [:multiaccounts.recover/phrase-validated]} - [{:keys [db] :as cofx} phrase-warnings] - (let [{:keys [password passphrase]} (:intro-wizard db)] - (if-not (string/blank? (:error (types/json->clj phrase-warnings))) - (popover/show-popover cofx {:view :custom-seed-phrase}) - (when (mnemonic/valid-length? passphrase) - {::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase) - :password (when password - (security/safe-unmask-data password)) - :success-event ::import-multiaccount-success}})))) - -(rf/defn seed-phrase-next-pressed - {:events [:multiaccounts.recover/enter-phrase-next-pressed]} - [{:keys [db] :as cofx}] - (let [{:keys [passphrase]} (:intro-wizard db)] - {::multiaccounts/validate-mnemonic [passphrase - #(re-frame/dispatch [:multiaccounts.recover/phrase-validated - %])]})) diff --git a/src/status_im/multiaccounts/recover/core_test.cljs b/src/status_im/multiaccounts/recover/core_test.cljs deleted file mode 100644 index a0c2bf4b4f..0000000000 --- a/src/status_im/multiaccounts/recover/core_test.cljs +++ /dev/null @@ -1,85 +0,0 @@ -(ns status-im.multiaccounts.recover.core-test - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.multiaccounts.create.core :as multiaccounts.create] - [status-im.multiaccounts.recover.core :as models] - [utils.security.core :as security])) - -;;;; helpers - - -(deftest check-phrase-warnings - (is (= :t/required-field (models/check-phrase-warnings "")))) - -;;;; handlers - -(deftest set-phrase - (is - (= {:db {:intro-wizard - {:passphrase - "game buzz method pretty olympic fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} - (models/set-phrase - {:db {}} - (security/mask-data - "game buzz method pretty olympic fat quit display velvet unveil marine crater")))) - (is - (= {:db {:intro-wizard - {:passphrase - "game buzz method pretty olympic fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} - (models/set-phrase - {:db {}} - (security/mask-data - "Game buzz method pretty Olympic fat quit DISPLAY velvet unveil marine crater")))) - (is - (= {:db {:intro-wizard {:passphrase - "game buzz method pretty zeus fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} - (models/set-phrase {:db {}} - (security/mask-data - "game buzz method pretty zeus fat quit display velvet unveil marine crater")))) - (is - (= - {:db {:intro-wizard - {:passphrase - " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater " - :passphrase-error nil - :next-button-disabled? false}}} - (models/set-phrase - {:db {}} - (security/mask-data - " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater ")))) - (is - (= {:db {:intro-wizard {:passphrase - "game buzz method pretty 1234 fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} - (models/set-phrase - {:db {}} - (security/mask-data - "game buzz method pretty 1234 fat quit display velvet unveil marine crater"))))) - -(deftest store-multiaccount - (let [new-cofx (models/store-multiaccount - {:db {:intro-wizard - {:passphrase - "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}} - (security/mask-data "thisisapaswoord"))] - (is (::multiaccounts.create/store-multiaccount new-cofx)))) - -(deftest on-import-multiaccount-success - (testing "importing a new multiaccount" - (let [res (models/on-import-multiaccount-success - {:db {:profile/profiles-overview {:acc1 {}}}} - {:key-uid :acc2} - nil)] - (is (nil? (:utils/show-confirmation res))))) - (testing "importing an existing multiaccount" - (let [res (models/on-import-multiaccount-success - {:db {:profile/profiles-overview {:acc1 {}}}} - {:key-uid :acc1} - nil)] - (is (contains? res :utils/show-confirmation))))) diff --git a/src/status_im/multiaccounts/reset_password/core.cljs b/src/status_im/multiaccounts/reset_password/core.cljs index 4af6133662..3a8c5c99ee 100644 --- a/src/status_im/multiaccounts/reset_password/core.cljs +++ b/src/status_im/multiaccounts/reset_password/core.cljs @@ -5,7 +5,6 @@ [native-module.core :as native-module] [status-im.popover.core :as popover] [utils.re-frame :as rf] - [status-im.utils.keychain.core :as keychain] [status-im.utils.types :as types] [utils.security.core :as security])) @@ -35,19 +34,13 @@ (rf/defn password-reset-success {:events [::password-reset-success]} [{:keys [db] :as cofx}] - (let [{:keys [key-uid]} (:profile/profile db) - auth-method (get db :auth-method keychain/auth-method-none) - new-password (get-in db [:multiaccount/reset-password-form-vals :new-password])] - (rf/merge cofx - {:db (dissoc - db - :multiaccount/reset-password-form-vals - :multiaccount/reset-password-errors - :multiaccount/reset-password-next-enabled? - :multiaccount/resetting-password?)} - ;; update password in keychain if biometrics are enabled - (when (= auth-method keychain/auth-method-biometric) - (keychain/save-user-password key-uid new-password))))) + (rf/merge cofx + {:db (dissoc + db + :multiaccount/reset-password-form-vals + :multiaccount/reset-password-errors + :multiaccount/reset-password-next-enabled? + :multiaccount/resetting-password?)})) (defn change-db-password-cb [res] diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 83050cf588..1431ee27e4 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -88,7 +88,7 @@ (assoc-in [:syncing :pairing-status] :connected) received-account? - (assoc-in [:syncing :profile/profile] multiaccount-data) + (assoc-in [:syncing :profile] multiaccount-data) error-on-pairing? (assoc-in [:syncing :pairing-status] :error) @@ -105,7 +105,7 @@ {:dispatch [:syncing/clear-states]} (and completed-pairing? receiver?) - {:dispatch [:multiaccounts.login/local-paired-user]} + {:dispatch [:profile.login/local-paired-user]} (and error-on-pairing? (some? error)) {:dispatch [:toasts/upsert diff --git a/src/status_im/ui/screens/biometric/views.cljs b/src/status_im/ui/screens/biometric/views.cljs deleted file mode 100644 index 2ba6222baa..0000000000 --- a/src/status_im/ui/screens/biometric/views.cljs +++ /dev/null @@ -1,97 +0,0 @@ -(ns status-im.ui.screens.biometric.views - (:require [quo.core :as quo] - [quo.design-system.colors :as colors] - [re-frame.core :as re-frame] - [utils.i18n :as i18n] - [status-im.multiaccounts.biometric.core :as biometric] - [status-im.ui.components.icons.icons :as icons] - [status-im.ui.components.react :as react])) - -(defn get-supported-biometric-auth - [] - @(re-frame/subscribe [:supported-biometric-auth])) - -(defn get-bio-type-label - [] - (biometric/get-label (get-supported-biometric-auth))) - -(defn biometric-popover - [{:keys [title-label description-label description-text - ok-button-label cancel-button-label on-cancel on-confirm]}] - (let [supported-biometric-auth (get-supported-biometric-auth) - bio-type-label (get-bio-type-label)] - [react/view - {:margin-top 24 - :align-items :center - :padding-horizontal 24} - [react/view - {:width 32 - :height 32 - :background-color colors/blue-light - :border-radius 16 - :align-items :center - :justify-content :center} - [icons/icon - (if (= supported-biometric-auth :FaceID) - :main-icons/faceid - :main-icons/print) - {:color colors/blue}]] - - [react/text - {:style {:typography :title-bold - :margin-top 16}} - (str (i18n/label title-label {:bio-type-label bio-type-label}))] - (vec - (concat - [react/nested-text - {:style {:margin-top 8 - :color colors/gray - :text-align :center}}] - (if description-label - [(i18n/label description-label {:bio-type-label bio-type-label})] - description-text))) - [react/view {:padding-vertical 16} - [react/view {:padding-vertical 8} - [quo/button {:on-press #(re-frame/dispatch [on-confirm])} - (i18n/label ok-button-label - {:bio-type-label bio-type-label})]] - [quo/button - {:type :secondary - :on-press #(re-frame/dispatch [(or on-cancel :hide-popover)])} - (or cancel-button-label - (i18n/label :t/cancel))]]])) - -(defn disable-password-saving-popover - [] - (let [bio-label-type (get-bio-type-label)] - [biometric-popover - {:title-label :t/biometric-disable-password-title - :ok-button-label :t/continue - :on-confirm :biometric/disable - - :description-text - [[{:style {:color colors/gray}} - (i18n/label :t/biometric-disable-password-description)] - [{} - (i18n/label :t/biometric-disable-bioauth - {:bio-type-label bio-label-type})]]}])) - -(defn enable-biometric-popover - [] - [biometric-popover - {:title-label :t/biometric-secure-with - :description-label :t/to-enable-biometric - :ok-button-label :t/biometric-enable-button - :on-confirm :biometric-logout}]) - -(defn secure-with-biometric-popover - [] - (let [keycard-account? @(re-frame/subscribe - [:multiaccounts.login/keycard-account?])] - [biometric-popover - {:title-label :t/biometric-secure-with - :ok-button-label :t/biometric-enable-button - :on-confirm :biometric/enable - :description-label (if keycard-account? - :t/biometric-enable-keycard - :t/biometric-enable)}])) diff --git a/src/status_im/ui/screens/keycard/pin/views.cljs b/src/status_im/ui/screens/keycard/pin/views.cljs index 0fa0b9f74b..e5383e1df3 100644 --- a/src/status_im/ui/screens/keycard/pin/views.cljs +++ b/src/status_im/ui/screens/keycard/pin/views.cljs @@ -94,9 +94,10 @@ [react/view {:style {:flex-direction :row}} [checkbox/checkbox - {:checked? save-password? - :style {:margin-right 10} - :on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}] + {:checked? save-password? + :style {:margin-right 10}}] + ;; should be reimplemented + ;;:on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}] [react/text (i18n/label :t/keycard-dont-ask-card)]]))) (defn bezier-easing diff --git a/src/status_im/ui/screens/keycard/views.cljs b/src/status_im/ui/screens/keycard/views.cljs index 33455e8471..865cef740d 100644 --- a/src/status_im/ui/screens/keycard/views.cljs +++ b/src/status_im/ui/screens/keycard/views.cljs @@ -9,7 +9,6 @@ [utils.i18n :as i18n] [status-im.keycard.login :as keycard.login] [status-im.multiaccounts.core :as multiaccounts] - [status-im.multiaccounts.create.core :as multiaccounts.create] [status-im.react-native.resources :as resources] [status-im.ui.components.icons.icons :as icons] [status-im.ui.components.react :as react] @@ -444,7 +443,6 @@ {:events [:multiaccounts.create.ui/get-new-key]} [{:keys [db] :as cofx}] (rf/merge cofx - (multiaccounts.create/prepare-intro-wizard) (bottom-sheet/hide-bottom-sheet-old) (navigation/navigate-to :get-your-keys nil))) diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index 3321b2e014..41c20a30d2 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -6,7 +6,6 @@ [reagent.core :as reagent] [status-im.ui.components.animation :as anim] [status-im.ui.components.react :as react] - [status-im.ui.screens.biometric.views :as biometric] [status-im.ui.screens.keycard.frozen-card.view :as frozen-card] [status-im.ui.screens.keycard.views :as keycard.views] [status-im.ui.screens.profile.user.views :as profile.user] @@ -142,15 +141,6 @@ (= :share-chat-key view) [profile.user/share-chat-key] - (= :enable-biometric view) - [biometric/enable-biometric-popover] - - (= :secure-with-biometric view) - [biometric/secure-with-biometric-popover] - - (= :disable-password-saving view) - [biometric/disable-password-saving-popover] - (= :transaction-data view) [signing/transaction-data] diff --git a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs index 6ba4e5e5c8..e22a33f61e 100644 --- a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs @@ -3,7 +3,6 @@ [re-frame.core :as re-frame] [status-im2.constants :as constants] [utils.i18n :as i18n] - [status-im.multiaccounts.biometric.core :as biometric] [status-im.multiaccounts.reset-password.core :as reset-password] [status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.ui.components.common.common :as components.common] @@ -34,9 +33,7 @@ profile-pictures-visibility]} [:profile/profile] has-picture [:profile/has-picture] - supported-biometric-auth [:supported-biometric-auth] keycard? [:keycard-multiaccount?] - auth-method [:auth-method] profile-pictures-show-to [:multiaccount/profile-pictures-show-to]] [react/scroll-view {:padding-vertical 8} [quo/list-header (i18n/label :t/security)] @@ -48,18 +45,6 @@ :chevron (boolean mnemonic) :accessory (when mnemonic [components.common/counter {:size 22} 1]) :on-press #(re-frame/dispatch [:navigate-to :backup-seed])}] - (when supported-biometric-auth - [quo/list-item - {:size :small - :title (str (i18n/label :t/lock-app-with) - " " - (biometric/get-label supported-biometric-auth)) - :active (= auth-method "biometric") - :accessibility-label :biometric-auth-settings-switch - :accessory :switch - :on-press #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched - ((complement boolean) - (= auth-method "biometric"))])}]) [separator] [quo/list-header (i18n/label :t/privacy)] [quo/list-item diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index abfce835ef..2fef1734ba 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -58,7 +58,6 @@ [status-im.ui.screens.rpc-usage-info :as rpc-usage-info] [status-im.ui.screens.stickers.views :as stickers] [status-im.ui.screens.sync-settings.views :as sync-settings] - [status-im.ui.screens.terms-of-service.views :as terms-of-service] [status-im.ui.screens.wakuv2-settings.edit-node.views :as edit-wakuv2-node] [status-im.ui.screens.wakuv2-settings.views :as wakuv2-settings] [status-im.ui.screens.wallet.account-settings.views :as account-settings] @@ -349,9 +348,6 @@ :options {:topBar {:title {:text (i18n/label :t/principles)}} :insets {:top? true}} :component about-app/principles} - {:name :force-accept-tos - :options {:insets {:top? true}} - :component terms-of-service/force-accept-tos} {:name :manage-dapps-permissions ;;TODO dynamic title :options {:insets {:top? true}} diff --git a/src/status_im/ui/screens/terms_of_service/views.cljs b/src/status_im/ui/screens/terms_of_service/views.cljs deleted file mode 100644 index da2ec59488..0000000000 --- a/src/status_im/ui/screens/terms_of_service/views.cljs +++ /dev/null @@ -1,112 +0,0 @@ -(ns status-im.ui.screens.terms-of-service.views - (:require [quo.core :as quo] - [quo.design-system.colors :as colors] - [quo.design-system.spacing :as spacing] - [quo.design-system.typography :as typography] - [re-frame.core :as re-frame] - [status-im2.constants :refer [docs-link]] - [utils.i18n :as i18n] - [status-im.react-native.resources :as resources] - [status-im.ui.components.icons.icons :as icons] - [status-im.ui.components.react :as react] - [status-im.ui.components.toolbar :as toolbar]) - (:require-macros [status-im.utils.views :refer [defview letsubs]])) - -(defn principles-item - [] - [react/nested-text {} - (i18n/label :t/wc-new-tos-based-on-principles-prefix) - [{:style (merge {:color colors/blue} - typography/font-medium) - :on-press #(re-frame/dispatch [:open-modal :principles])} - " " - (i18n/label :t/principles)]]) - -(def changes - [[principles-item] - :wc-how-to-use-status-app - :wc-brand-guide - :wc-disclaimer - :wc-dispute]) - -(defn change-list-item - [label] - [react/view - {:flex-direction :row - :align-items :center - :margin-horizontal (:base spacing/spacing) - :margin-vertical (:tiny spacing/spacing)} - [icons/icon :main-icons/checkmark-circle - {:color colors/blue - :container-style {:margin-top 1.2 - :margin-right (:tiny spacing/spacing)}}] - [react/view {:style {:padding-right (:xx-large spacing/spacing)}} - (if (keyword? label) - [react/text (i18n/label label)] - label)]]) - -(defview force-accept-tos - [] - (letsubs [next-root [:tos-accept-next-root]] - [react/scroll-view - [react/view - {:style (merge {:align-items :center} - (:x-large spacing/padding-horizontal))} - [react/image - {:source (resources/get-image :status-logo) - :style {:margin-vertical (:base spacing/spacing) - :width 32 - :height 32}}] - [quo/text - {:size :x-large - :align :center - :weight :bold - :style {:margin-bottom (:base spacing/spacing)}} - (i18n/label :t/updates-to-tos)] - [quo/text - {:color :secondary - :align :center} - (i18n/label :t/updates-to-tos-desc)]] - - [quo/separator {:style {:margin-top (:base spacing/spacing)}}] - [quo/list-item - {:title [quo/text - {:color :link - :weight :medium} - (i18n/label :t/terms-of-service)] - :accessibility-label :tos - :chevron true - :on-press #(re-frame/dispatch [:open-modal :terms-of-service])}] - [quo/separator] - - [quo/list-header (i18n/label :t/what-changed)] - (for [c changes] - ^{:key c} - [change-list-item c]) - - [quo/separator {:style {:margin-vertical (:base spacing/spacing)}}] - - [react/view {:style (:base spacing/padding-horizontal)} - [quo/text {:weight :medium} (i18n/label :t/status-is-open-source)] - [quo/text {:color :secondary} (i18n/label :t/build-yourself)] - [quo/text - {:color :link - :weight :medium - :on-press #(.openURL ^js react/linking docs-link)} - docs-link]] - - [quo/separator {:style {:margin-vertical (:base spacing/spacing)}}] - - [toolbar/toolbar - {:size :large - :center - [react/view {:padding-horizontal 8} - [quo/button - {:type :primary - :on-press #(do - (re-frame/dispatch [:hide-terms-of-services-opt-in-screen]) - (re-frame/dispatch [:init-root next-root]))} - (i18n/label :t/accept-and-continue)]]}]])) - -(comment - (re-frame/dispatch [:navigate-to :force-accept-tos])) diff --git a/src/status_im/utils/keychain/core.cljs b/src/status_im/utils/keychain/core.cljs index d8539a61f4..231307aa6d 100644 --- a/src/status_im/utils/keychain/core.cljs +++ b/src/status_im/utils/keychain/core.cljs @@ -1,213 +1,45 @@ (ns status-im.utils.keychain.core - (:require ["react-native-keychain" :as react-native-keychain] - [clojure.string :as string] + (:require [react-native.keychain :as keychain] [re-frame.core :as re-frame] - [native-module.core :as native-module] [utils.re-frame :as rf] - [status-im.utils.platform :as platform] [taoensso.timbre :as log] - [utils.security.core :as security])) + [oops.core :as oops])) -(defn- check-conditions - [callback & checks] - (if (= (count checks) 0) - (callback true) - (let [current-check-fn (first checks) - process-check-result (fn [callback-success callback-fail] - (fn [current-check-passed?] - (if current-check-passed? - (callback-success) - (callback-fail))))] - (current-check-fn (process-check-result - #(apply (partial check-conditions callback) (rest checks)) - #(callback false)))))) - -;; ******************************************************************************** -;; 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 react-native-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. - #js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")}) - -(def keychain-secure-hardware - ;; (Android) Requires storing the encryption key for the entry in secure hardware - ;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention) - "SECURE_HARDWARE") - -;; These helpers check if the device is okay to use for password storage -;; They resolve callback with `true` if the check is passed, with `false` otherwise. -;; Android only -(defn- device-not-rooted? - [callback] - (native-module/rooted-device? (fn [rooted?] (callback (not rooted?))))) - -;; Android only -(defn- secure-hardware-available? - [callback] - (-> (.getSecurityLevel react-native-keychain) - (.then (fn [level] (callback (= level keychain-secure-hardware)))))) - -;; iOS only -(defn- device-encrypted? - [callback] - (-> (.canImplyAuthentication - react-native-keychain - (clj->js - {:authenticationType - (enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")})) - (.then callback))) +(def auth-method-biometric "biometric") +(def auth-method-biometric-prepare "biometric-prepare") +(def auth-method-none "none") (defn- whisper-key-name [address] (str address "-whisper")) -(defn can-save-user-password? - [callback] - (log/debug "[keychain] can-save-user-password?") - (cond - platform/ios? - (check-conditions callback device-encrypted?) - - platform/android? - (check-conditions callback secure-hardware-available? device-not-rooted?) - - :else - (callback false))) - -(defn save-credentials - "Stores the credentials for the address to the Keychain" - [server username password callback] - (log/debug "[keychain] save-credentials") - (-> (.setInternetCredentials react-native-keychain - (string/lower-case server) - username - password - keychain-secure-hardware - keychain-restricted-availability) - (.then callback))) - -(defn get-credentials - "Gets the credentials for a specified server from the Keychain" - [server callback] - (log/debug "[keychain] get-credentials") - (-> (.getInternetCredentials react-native-keychain (string/lower-case server)) - (.then callback))) - -(def auth-method-password "password") -(def auth-method-biometric "biometric") -(def auth-method-biometric-prepare "biometric-prepare") -(def auth-method-none "none") - -(re-frame/reg-fx - :keychain/get-auth-method - (fn [[key-uid callback]] - (can-save-user-password? - (fn [can-save?] - (if can-save? - (get-credentials (str key-uid "-auth") - #(callback (if % - (.-password ^js %) - auth-method-none))) - (callback nil)))))) - -(re-frame/reg-fx - :keychain/get-user-password - (fn [[key-uid callback]] - (get-credentials key-uid #(if % (callback (security/mask-data (.-password ^js %))) (callback nil))))) - (re-frame/reg-fx :keychain/get-keycard-keys (fn [[key-uid callback]] - (get-credentials + (keychain/get-credentials key-uid - (fn [^js encryption-key-data] + (fn [encryption-key-data] (if encryption-key-data - (get-credentials + (keychain/get-credentials (whisper-key-name key-uid) - (fn [^js whisper-key-data] + (fn [whisper-key-data] (if whisper-key-data - (callback [(.-password encryption-key-data) - (.-password whisper-key-data)]) + (callback [(oops/oget encryption-key-data "password") + (oops/oget whisper-key-data "password")]) (callback nil)))) (callback nil)))))) -(defn save-user-password! - [key-uid password] - (save-credentials - key-uid - key-uid - (security/safe-unmask-data 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."))))) - -(defn save-auth-method! - [key-uid method] - (save-credentials - (str key-uid "-auth") - key-uid - method - #(when-not % - (log/error - (str "Error while saving auth method." - " " - "The app will continue to work normally, " - "but you will have to login again next time you launch it."))))) - -(re-frame/reg-fx - :keychain/save-user-password - (fn [[key-uid masked-password]] - (save-user-password! key-uid masked-password))) - -(re-frame/reg-fx - :keychain/save-auth-method - (fn [[key-uid method]] - (log/debug "[keychain] :keychain/save-auth-method" - "key-uid" - key-uid - "method" - method) - (when-not (empty? key-uid) ; key-uid may be nil after restore from local pairing - (save-auth-method! key-uid method)))) - (re-frame/reg-fx :keychain/save-keycard-keys (fn [[key-uid encryption-public-key whisper-private-key]] - (save-credentials + (keychain/save-credentials key-uid key-uid encryption-public-key #(when-not % (log/error (str "Error while saving encryption-public-key")))) - (save-credentials + (keychain/save-credentials (whisper-key-name key-uid) key-uid whisper-private-key @@ -215,19 +47,6 @@ (log/error (str "Error while saving whisper-private-key")))))) -(re-frame/reg-fx - :keychain/clear-user-password - (fn [key-uid] - (-> (.resetInternetCredentials react-native-keychain (string/lower-case key-uid)) - (.then #(when-not % (log/error (str "Error while clearing saved password."))))))) - -(rf/defn get-user-password - [_ key-uid] - {:keychain/get-user-password - [key-uid - #(re-frame/dispatch - [:multiaccounts.login.callback/get-user-password-success % key-uid])]}) - (rf/defn get-keycard-keys [_ key-uid] {:keychain/get-keycard-keys @@ -235,16 +54,8 @@ #(re-frame/dispatch [:multiaccounts.login.callback/get-keycard-keys-success key-uid %])]}) -(rf/defn save-user-password - [_ key-uid password] - {:keychain/save-user-password [key-uid password]}) - (rf/defn save-keycard-keys [_ key-uid encryption-public-key whisper-private-key] {:keychain/save-keycard-keys [key-uid encryption-public-key whisper-private-key]}) -(rf/defn save-auth-method - [{:keys [db]} key-uid method] - {:db (assoc db :auth-method method) - :keychain/save-auth-method [key-uid method]}) diff --git a/src/status_im/utils/test.cljs b/src/status_im/utils/test.cljs index 1e2fec692d..b57d0427bd 100644 --- a/src/status_im/utils/test.cljs +++ b/src/status_im/utils/test.cljs @@ -36,16 +36,21 @@ (fn [json callback] (callback (.multiAccountStoreDerivedAccounts native-status json))) + :getNodeConfig (fn [] (types/clj->json {:WakuV2Config ""})) + + :backupDisabledDataDir (fn [] "./smth") + :keystoreDir (fn [] "") + :logFileDirectory (fn [] "./smth") :clearCookies identity - :clearStorageAPIs identity - :setBlankPreviewFlag identity :callPrivateRPC (fn [payload callback] (callback (.callPrivateRPC native-status payload))) + :createAccountAndLogin (fn [request] (.createAccountAndLogin native-status request)) + :saveAccountAndLogin (fn [multiaccount-data password settings config accounts-data] (.saveAccountAndLogin native-status multiaccount-data password settings config accounts-data)) diff --git a/src/status_im/wallet/accounts/core.cljs b/src/status_im/wallet/accounts/core.cljs index 4de8b89c98..50b8d9ea2c 100644 --- a/src/status_im/wallet/accounts/core.cljs +++ b/src/status_im/wallet/accounts/core.cljs @@ -333,6 +333,20 @@ (update-in [:wallet :accounts] dissoc deleted-address))} (navigation/pop-to-root :shell-stack)))) +(re-frame/reg-fx + :key-storage/delete-imported-key + (fn [{:keys [key-uid address password on-success on-error]}] + (let [hashed-pass (ethereum/sha3 (security/safe-unmask-data password))] + (native-module/delete-imported-key + key-uid + (string/lower-case (subs address 2)) + hashed-pass + (fn [result] + (let [{:keys [error]} (types/json->clj result)] + (if-not (string/blank? error) + (on-error error) + (on-success)))))))) + (rf/defn delete-account-key {:events [:wallet.accounts/delete-key]} [{:keys [db] :as cofx} account password on-error] diff --git a/src/status_im2/common/biometric/events.cljs b/src/status_im2/common/biometric/events.cljs new file mode 100644 index 0000000000..9de9141175 --- /dev/null +++ b/src/status_im2/common/biometric/events.cljs @@ -0,0 +1,76 @@ +(ns status-im2.common.biometric.events + (:require [react-native.platform :as platform] + [react-native.touch-id :as touch-id] + [native-module.core :as native-module] + [status-im2.common.keychain.events :as keychain] + [re-frame.core :as re-frame] + [utils.re-frame :as rf] + [utils.i18n :as i18n])) + +(def android-device-blacklisted? + (= (:brand (native-module/get-device-model-info)) "bannedbrand")) + +(defn get-supported-type + [callback] + (cond platform/ios? (touch-id/get-supported-type callback) + platform/android? (if android-device-blacklisted? + (callback nil) + (touch-id/get-supported-type callback)) + :else (callback nil))) + +(defn get-label-by-type + [biometric-type] + (case biometric-type + :fingerprint (i18n/label :t/biometric-fingerprint) + :FaceID (i18n/label :t/biometric-faceid) + (i18n/label :t/biometric-touchid))) + +(re-frame/reg-fx + :biometric/get-supported-biometric-type + (fn [] + ;;NOTE: if we can't save user password, we can't use biometric + (keychain/can-save-user-password? + (fn [can-save?] + (when can-save? + (get-supported-type #(rf/dispatch [:biometric/get-supported-biometric-type-success %]))))))) + +(rf/defn get-supported-biometric-auth-success + {:events [:biometric/get-supported-biometric-type-success]} + [{:keys [db]} supported-type] + {:db (assoc db :biometric/supported-type supported-type)}) + +(rf/defn show-message + [_ code] + (let [content (if (#{"NOT_AVAILABLE" "NOT_ENROLLED"} code) + (i18n/label :t/grant-face-id-permissions) + (when-not (or (= code "USER_CANCELED") (= code "USER_FALLBACK")) + (i18n/label :t/biometric-auth-error {:code code})))] + (when content + {:utils/show-popup + {:title (i18n/label :t/biometric-auth-login-error-title) + :content content}}))) + +(re-frame/reg-fx + :biometric/authenticate + (fn [options] + (touch-id/authenticate + (merge + {:reason (i18n/label :t/biometric-auth-reason-login) + :options (merge + {:unifiedErrors true} + (when platform/ios? + {:passcodeFallback false + :fallbackLabel (i18n/label :t/biometric-auth-login-ios-fallback-label)}) + (when platform/android? + {:title (i18n/label :t/biometric-auth-android-title) + :imageColor :blue + :imageErrorColor :red + :sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc) + :sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc) + :cancelText (i18n/label :cancel)}))} + options)))) + +(rf/defn authenticate + {:events [:biometric/authenticate]} + [_ opts] + {:biometric/authenticate opts}) diff --git a/src/status_im2/common/keychain/events.cljs b/src/status_im2/common/keychain/events.cljs new file mode 100644 index 0000000000..71bfa2a878 --- /dev/null +++ b/src/status_im2/common/keychain/events.cljs @@ -0,0 +1,110 @@ +(ns status-im2.common.keychain.events + (:require [taoensso.timbre :as log] + [react-native.platform :as platform] + [react-native.keychain :as keychain] + [re-frame.core :as re-frame] + [utils.re-frame :as rf] + [oops.core :as oops] + [native-module.core :as native-module] + [utils.security.core :as security])) + +(defn- check-conditions + [callback & checks] + (if (= (count checks) 0) + (callback true) + (let [current-check-fn (first checks) + process-check-result (fn [callback-success callback-fail] + (fn [current-check-passed?] + (if current-check-passed? + (callback-success) + (callback-fail))))] + (current-check-fn (process-check-result + #(apply (partial check-conditions callback) (rest checks)) + #(callback false)))))) + +;; These helpers check if the device is okay to use for password storage +;; They resolve callback with `true` if the check is passed, with `false` otherwise. +;; Android only +(defn- device-not-rooted? + [callback] + (native-module/rooted-device? (fn [rooted?] (callback (not rooted?))))) + +(defn can-save-user-password? + [callback] + (log/debug "[keychain] can-save-user-password?") + (cond + platform/ios? + (check-conditions callback keychain/device-encrypted?) + + platform/android? + (check-conditions callback keychain/secure-hardware-available? device-not-rooted?) + + :else + (callback false))) + +(def auth-method-biometric "biometric") +(def auth-method-biometric-prepare "biometric-prepare") +(def auth-method-none "none") + +(defn save-auth-method! + [key-uid method] + (keychain/save-credentials + (str key-uid "-auth") + key-uid + method + #(when-not % + (log/error + (str "Error while saving auth method." + " " + "The app will continue to work normally, " + "but you will have to login again next time you launch it."))))) + +(re-frame/reg-fx + :keychain/save-auth-method + (fn [[key-uid method]] + (when-not (empty? key-uid) ; key-uid may be nil after restore from local pairing + (save-auth-method! key-uid method)))) + +(rf/defn save-auth-method + [{:keys [db]} key-uid method] + {:db (assoc db :auth-method method) + :keychain/save-auth-method [key-uid method]}) + +(re-frame/reg-fx + :keychain/get-auth-method + (fn [[key-uid callback]] + (can-save-user-password? + (fn [can-save?] + (if can-save? + (keychain/get-credentials + (str key-uid "-auth") + #(callback (if % (oops/oget % "password") auth-method-none))) + (callback nil)))))) + +(defn save-user-password! + [key-uid password] + (keychain/save-credentials key-uid key-uid (security/safe-unmask-data password) #())) + +(re-frame/reg-fx + :keychain/get-user-password + (fn [[key-uid callback]] + (keychain/get-credentials + key-uid + #(if % (callback (security/mask-data (oops/oget % "password"))) (callback nil))))) + +(rf/defn get-user-password + [_ key-uid callback] + {:keychain/get-user-password [key-uid callback]}) + +(re-frame/reg-fx + :keychain/clear-user-password + (fn [key-uid] + (keychain/reset-credentials key-uid))) + +(re-frame/reg-fx + :keychain/save-password-and-auth-method + (fn [{:keys [key-uid masked-password on-success on-error]}] + (-> (save-user-password! key-uid masked-password) + (.then #(save-auth-method! key-uid auth-method-biometric)) + (.then #(when on-success (on-success))) + (.catch #(when on-error (on-error %)))))) diff --git a/src/status_im2/contexts/onboarding/enable_biometrics/view.cljs b/src/status_im2/contexts/onboarding/enable_biometrics/view.cljs index 800055f968..677be11671 100644 --- a/src/status_im2/contexts/onboarding/enable_biometrics/view.cljs +++ b/src/status_im2/contexts/onboarding/enable_biometrics/view.cljs @@ -5,13 +5,13 @@ [react-native.safe-area :as safe-area] [status-im2.contexts.onboarding.enable-biometrics.style :as style] [status-im2.contexts.onboarding.common.navigation-bar.view :as navigation-bar] - [status-im.multiaccounts.biometric.core :as biometric] [utils.i18n :as i18n] [utils.re-frame :as rf] [status-im2.common.resources :as resources] [status-im2.common.parallax.view :as parallax] [status-im2.contexts.onboarding.common.background.view :as background] - [status-im2.common.parallax.whitelist :as whitelist])) + [status-im2.common.parallax.whitelist :as whitelist] + [status-im2.common.biometric.events :as biometric])) (defn page-title [] @@ -24,9 +24,9 @@ (defn enable-biometrics-buttons [insets] - (let [supported-biometric (rf/sub [:supported-biometric-auth]) - bio-type-label (biometric/get-label supported-biometric) - profile-color (:color (rf/sub [:onboarding-2/profile]))] + (let [supported-biometric-type (rf/sub [:biometric/supported-type]) + bio-type-label (biometric/get-label-by-type supported-biometric-type) + profile-color (:color (rf/sub [:onboarding-2/profile]))] [rn/view {:style (style/buttons insets)} [quo/button {:accessibility-label :enable-biometrics-button diff --git a/src/status_im2/contexts/onboarding/events.cljs b/src/status_im2/contexts/onboarding/events.cljs index 4ce815c0d3..26eb0ef75f 100644 --- a/src/status_im2/contexts/onboarding/events.cljs +++ b/src/status_im2/contexts/onboarding/events.cljs @@ -1,21 +1,15 @@ (ns status-im2.contexts.onboarding.events - (:require - [clojure.string :as string] - [native-module.core :as native-module] - [re-frame.core :as re-frame] - [status-im.ethereum.core :as ethereum] - [status-im.utils.types :as types] - [status-im2.config :as config] - [status-im2.constants :as constants] - [taoensso.timbre :as log] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [utils.security.core :as security])) - -(re-frame/reg-fx - :multiaccount/create-account-and-login - (fn [request] - (native-module/create-account-and-login request))) + (:require [native-module.core :as native-module] + [re-frame.core :as re-frame] + [status-im.utils.types :as types] + [status-im2.constants :as constants] + [taoensso.timbre :as log] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [utils.security.core :as security] + [status-im2.contexts.profile.create.events :as profile.create] + [status-im2.contexts.profile.recover.events :as profile.recover] + [status-im2.common.biometric.events :as biometric])) (re-frame/reg-fx :multiaccount/validate-mnemonic @@ -28,11 +22,6 @@ (when on-error (on-error error)) (on-success mnemonic keyUID))))))) -(re-frame/reg-fx - :multiaccount/restore-account-and-login - (fn [request] - (native-module/restore-account-and-login request))) - (rf/defn profile-data-set {:events [:onboarding-2/profile-data-set]} [{:keys [db]} onboarding-data] @@ -42,82 +31,34 @@ (rf/defn enable-biometrics {:events [:onboarding-2/enable-biometrics]} [_] - {:biometric-auth/authenticate [#(rf/dispatch [:onboarding-2/biometrics-done %]) {}]}) - -(rf/defn show-biometrics-message - [cofx bioauth-message bioauth-code] - (let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code) - (i18n/label :t/grant-face-id-permissions)) - bioauth-message)] - (when content - {:utils/show-popup - {:title (i18n/label :t/biometric-auth-login-error-title) - :content content}}))) + {:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done]) + :on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}}) (rf/defn biometrics-done {:events [:onboarding-2/biometrics-done]} - [{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}] - (if bioauth-success - {:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric) - :dispatch [:onboarding-2/create-account-and-login]} - (show-biometrics-message cofx bioauth-message bioauth-code))) + [{:keys [db]}] + {:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric) + :dispatch [:onboarding-2/create-account-and-login]}) -(defn strip-file-prefix - [path] - (when path - (string/replace-first path "file://" ""))) +(rf/defn biometrics-fail + {:events [:onboarding-2/biometrics-fail]} + [cofx code] + (biometric/show-message cofx code)) (rf/defn create-account-and-login {:events [:onboarding-2/create-account-and-login]} - [{:keys [db]}] - (let [{:keys [display-name - seed-phrase - password - image-path - color]} (:onboarding-2/profile db) - log-enabled? (boolean (not-empty config/log-level)) - effect (if seed-phrase - :multiaccount/restore-account-and-login - :multiaccount/create-account-and-login) - request {:displayName display-name - :deviceName (native-module/get-installation-name) - :password (ethereum/sha3 (security/safe-unmask-data - password)) - :mnemonic (when seed-phrase - (security/safe-unmask-data seed-phrase)) - :imagePath (strip-file-prefix image-path) - :customizationColor color - :backupDisabledDataDir (native-module/backup-disabled-data-dir) - :rootKeystoreDir (native-module/keystore-dir) - ;; Temporary fix until https://github.com/status-im/status-go/issues/3024 is - ;; resolved - :wakuV2Nameserver "1.1.1.1" - :logLevel (when log-enabled? config/log-level) - :logEnabled log-enabled? - :logFilePath (native-module/log-file-directory) - :openseaAPIKey config/opensea-api-key - :verifyTransactionURL config/verify-transaction-url - :verifyENSURL config/verify-ens-url - :verifyENSContractAddress config/verify-ens-contract-address - :verifyTransactionChainID config/verify-transaction-chain-id - :upstreamConfig config/default-network-rpc-url - :networkId config/default-network-id - :poktToken config/POKT_TOKEN - :infuraToken config/INFURA_TOKEN - - :alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN - :alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN - :alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN - :alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN - - :currentNetwork config/default-network - :previewPrivacy config/blank-preview?}] - {effect request - :dispatch [:navigate-to :generating-keys] - :db (-> db - (dissoc :profile/login) - (dissoc :auth-method) - (assoc :onboarding-2/new-account? true))})) + [{:keys [db] :as cofx}] + (let [{:keys [display-name seed-phrase password image-path color] :as profile} + (:onboarding-2/profile db)] + (rf/merge cofx + {:dispatch [:navigate-to :generating-keys] + :db (-> db + (dissoc :profile/login) + (dissoc :auth-method) + (assoc :onboarding-2/new-account? true))} + (if seed-phrase + (profile.recover/recover-profile-and-login profile) + (profile.create/create-profile-and-login profile))))) (rf/defn on-delete-profile-success {:events [:onboarding-2/on-delete-profile-success]} @@ -173,13 +114,11 @@ [{:keys [db]}] (let [masked-password (get-in db [:onboarding-2/profile :password]) key-uid (get-in db [:profile/profile :key-uid]) - biometric-enabled? (= - constants/auth-method-biometric - (get-in db [:onboarding-2/profile :auth-method]))] - + biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method]) + constants/auth-method-biometric)] (cond-> {:dispatch [:navigate-to :identifiers]} biometric-enabled? - (assoc :biometric/enable-and-save-password + (assoc :keychain/save-password-and-auth-method {:key-uid key-uid :masked-password masked-password :on-success #(log/debug "successfully saved biometric") diff --git a/src/status_im2/contexts/onboarding/intro/view.cljs b/src/status_im2/contexts/onboarding/intro/view.cljs index 78d4b2a54f..f1757de28b 100644 --- a/src/status_im2/contexts/onboarding/intro/view.cljs +++ b/src/status_im2/contexts/onboarding/intro/view.cljs @@ -18,18 +18,13 @@ (reset! scan-sync-code/dismiss-animations reset-top-animation-fn)) :animations-duration constants/onboarding-modal-animation-duration :animations-delay constants/onboarding-modal-animation-delay - :top-card {:on-press (fn [] - (debounce/dispatch-and-chill [:open-modal - :sign-in-intro] - 2000) - (rf/dispatch [:hide-terms-of-services-opt-in-screen])) + :top-card {:on-press #(debounce/dispatch-and-chill [:open-modal + :sign-in-intro] + 2000) :heading (i18n/label :t/sign-in) :animated-heading (i18n/label :t/sign-in-by-syncing) :accessibility-label :already-use-status-button} - :bottom-card {:on-press (fn [] - (rf/dispatch [:navigate-to :new-to-status]) - (rf/dispatch - [:hide-terms-of-services-opt-in-screen])) + :bottom-card {:on-press #(rf/dispatch [:navigate-to :new-to-status]) :heading (i18n/label :t/new-to-status) :accessibility-label :new-to-status-button}} [quo/text diff --git a/src/status_im2/contexts/profile/config.cljs b/src/status_im2/contexts/profile/config.cljs new file mode 100644 index 0000000000..96582da28b --- /dev/null +++ b/src/status_im2/contexts/profile/config.cljs @@ -0,0 +1,42 @@ +(ns status-im2.contexts.profile.config + (:require [status-im2.config :as config] + [native-module.core :as native-module] + [clojure.string :as string])) + +(defn login + [] + {;; Temporary fix until https://github.com/status-im/status-go/issues/3024 is + ;; resolved + :wakuV2Nameserver "1.1.1.1" + :openseaAPIKey config/opensea-api-key + :poktToken config/POKT_TOKEN + :infuraToken config/INFURA_TOKEN + :alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN + :alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN + :alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN + :alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN}) + +(defn create + [] + (let [log-enabled? (boolean (not-empty config/log-level))] + (merge (login) + {:deviceName (native-module/get-installation-name) + :backupDisabledDataDir (native-module/backup-disabled-data-dir) + :rootKeystoreDir (native-module/keystore-dir) + + :logLevel (when log-enabled? config/log-level) + :logEnabled log-enabled? + :logFilePath (native-module/log-file-directory) + :verifyTransactionURL config/verify-transaction-url + :verifyENSURL config/verify-ens-url + :verifyENSContractAddress config/verify-ens-contract-address + :verifyTransactionChainID config/verify-transaction-chain-id + :upstreamConfig config/default-network-rpc-url + :networkId config/default-network-id + :currentNetwork config/default-network + :previewPrivacy config/blank-preview?}))) + +(defn strip-file-prefix + [path] + (when path + (string/replace-first path "file://" ""))) diff --git a/src/status_im2/contexts/profile/create/events.cljs b/src/status_im2/contexts/profile/create/events.cljs new file mode 100644 index 0000000000..e1437492ba --- /dev/null +++ b/src/status_im2/contexts/profile/create/events.cljs @@ -0,0 +1,23 @@ +(ns status-im2.contexts.profile.create.events + (:require [utils.re-frame :as rf] + [native-module.core :as native-module] + [status-im2.contexts.profile.config :as profile.config] + [status-im.ethereum.core :as ethereum] + [utils.security.core :as security] + [re-frame.core :as re-frame])) + +(re-frame/reg-fx + ::create-profile-and-login + (fn [request] + ;;"node.login" signal will be triggered as a callback + (native-module/create-account-and-login request))) + +(rf/defn create-profile-and-login + {:events [:profile.create/create-and-login]} + [_ {:keys [display-name password image-path color]}] + {::create-profile-and-login + (merge (profile.config/create) + {:displayName display-name + :password (ethereum/sha3 (security/safe-unmask-data password)) + :imagePath (profile.config/strip-file-prefix image-path) + :customizationColor color})}) diff --git a/src/status_im2/contexts/profile/events.cljs b/src/status_im2/contexts/profile/events.cljs index 8fb8190202..27ed335375 100644 --- a/src/status_im2/contexts/profile/events.cljs +++ b/src/status_im2/contexts/profile/events.cljs @@ -1,6 +1,15 @@ (ns status-im2.contexts.profile.events (:require [utils.re-frame :as rf] - [clojure.string :as string])) + [clojure.string :as string] + [re-frame.core :as re-frame] + [native-module.core :as native-module] + [status-im2.navigation.events :as navigation] + [status-im2.contexts.profile.login.events :as login])) + +(re-frame/reg-fx + :profile/get-profiles-overview + (fn [callback] + (native-module/open-accounts callback))) (rf/defn profile-selected {:events [:profile/profile-selected]} @@ -11,6 +20,12 @@ (assoc :key-uid key-uid) (dissoc :error :password)))}) +(rf/defn init-profiles-overview + [{:keys [db] :as cofx} profiles key-uid] + (rf/merge cofx + {:db (assoc db :profile/profiles-overview profiles)} + (profile-selected key-uid))) + (defn rpc->profiles-overview [{:keys [customizationColor keycard-pairing] :as profile}] (-> profile @@ -18,18 +33,24 @@ (assoc :customization-color (keyword customizationColor)) (assoc :keycard-pairing (when-not (string/blank? keycard-pairing) keycard-pairing)))) -(rf/defn init-profiles-overview - [{:keys [db] :as cofx} profiles] - (let [profiles (reduce - (fn [acc {:keys [key-uid] :as profile}] - (assoc acc key-uid (rpc->profiles-overview profile))) - {} - profiles) - {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] - (rf/merge cofx - {:db (assoc db :profile/profiles-overview profiles) - :keychain/get-auth-method - [key-uid #(rf/dispatch [:multiaccounts.login/get-auth-method-success % key-uid])] - :dispatch-n [[:get-opted-in-to-new-terms-of-service] - [:load-information-box-states]]} - (profile-selected key-uid)))) +(defn reduce-profiles + [profiles] + (reduce + (fn [acc {:keys [key-uid] :as profile}] + (assoc acc key-uid (rpc->profiles-overview profile))) + {} + profiles)) + +(rf/defn get-profiles-overview-success + {:events [:profile/get-profiles-overview-success]} + [cofx profiles-overview] + (if (seq profiles-overview) + (let [profiles (reduce-profiles profiles-overview) + {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] + (rf/merge cofx + (navigation/init-root :profiles) + (init-profiles-overview profiles key-uid) + ;;we check if biometric is available, and try to login with it, + ;;if succeed "node.login" signal will be triggered + (login/login-with-biometric-if-available key-uid))) + (navigation/init-root cofx :intro))) diff --git a/src/status_im2/contexts/profile/login/events.cljs b/src/status_im2/contexts/profile/login/events.cljs index 7dcc916014..656d5372a7 100644 --- a/src/status_im2/contexts/profile/login/events.cljs +++ b/src/status_im2/contexts/profile/login/events.cljs @@ -1,3 +1,71 @@ -(ns status-im2.contexts.profile.login.events) +(ns status-im2.contexts.profile.login.events + (:require [utils.re-frame :as rf] + [status-im.ethereum.core :as ethereum] + [utils.security.core :as security] + [re-frame.core :as re-frame] + [native-module.core :as native-module] + [status-im2.navigation.events :as navigation] + [status-im2.common.keychain.events :as keychain] + [status-im2.common.biometric.events :as biometric] + [status-im2.contexts.profile.config :as profile.config])) +(re-frame/reg-fx + ::login + (fn [[key-uid hashed-password]] + ;;"node.login" signal will be triggered as a callback + (native-module/login-account + (assoc (profile.config/login) :keyUid key-uid :password hashed-password)))) +(rf/defn login + {:events [:profile.login/login]} + [{:keys [db]}] + (let [{:keys [key-uid password]} (:profile/login db)] + {:db (assoc-in db [:profile/login :processing] true) + ::login [key-uid (ethereum/sha3 (security/safe-unmask-data password))]})) + +(rf/defn login-local-paired-user + {:events [:profile.login/local-paired-user]} + [{:keys [db]}] + (let [{:keys [key-uid password]} (get-in db [:syncing :profile])] + {::login [key-uid password]})) + +(rf/defn login-with-biometric-if-available + {:events [:profile.login/login-with-biometric-if-available]} + [_ key-uid] + {:keychain/get-auth-method [key-uid + #(rf/dispatch [:profile.login/get-auth-method-success % key-uid])]}) + +(rf/defn get-auth-method-success + {:events [:profile.login/get-auth-method-success]} + [{:keys [db]} auth-method] + (merge {:db (assoc db :auth-method auth-method)} + (when (= auth-method keychain/auth-method-biometric) + {:biometric/authenticate + {:on-success #(rf/dispatch [:profile.login/biometric-success]) + :on-faile #(rf/dispatch [:profile.login/biometric-auth-fail])}}))) + +(rf/defn biometric-auth-success + {:events [:profile.login/biometric-success]} + [{:keys [db] :as cofx}] + (let [key-uid (get-in db [:profile/login :key-uid])] + (keychain/get-user-password cofx + key-uid + #(rf/dispatch [:profile.login/get-user-password-success %])))) + +;; result of :keychain/get-auth-method above +(rf/defn get-user-password-success + {:events [:profile.login/get-user-password-success]} + [{:keys [db] :as cofx} password] + (when password + (rf/merge + cofx + {:db (assoc-in db [:profile/login :password] password)} + (navigation/init-root :progress) + (login)))) + +(rf/defn biometric-auth-fail + {:events [:profile.login/biometric-auth-fail]} + [{:keys [db] :as cofx} code] + (rf/merge cofx + (navigation/init-root :profiles) + (biometric/show-message code))) diff --git a/src/status_im2/contexts/onboarding/profiles/style.cljs b/src/status_im2/contexts/profile/profiles/style.cljs similarity index 96% rename from src/status_im2/contexts/onboarding/profiles/style.cljs rename to src/status_im2/contexts/profile/profiles/style.cljs index 90e08ee1bf..a4ed76ee77 100644 --- a/src/status_im2/contexts/onboarding/profiles/style.cljs +++ b/src/status_im2/contexts/profile/profiles/style.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.onboarding.profiles.style +(ns status-im2.contexts.profile.profiles.style (:require [quo2.foundations.colors :as colors])) ;; Profiles Section diff --git a/src/status_im2/contexts/onboarding/profiles/view.cljs b/src/status_im2/contexts/profile/profiles/view.cljs similarity index 88% rename from src/status_im2/contexts/onboarding/profiles/view.cljs rename to src/status_im2/contexts/profile/profiles/view.cljs index ab18d27273..eaa1a5d32d 100644 --- a/src/status_im2/contexts/onboarding/profiles/view.cljs +++ b/src/status_im2/contexts/profile/profiles/view.cljs @@ -1,11 +1,11 @@ -(ns status-im2.contexts.onboarding.profiles.view +(ns status-im2.contexts.profile.profiles.view (:require [native-module.core :as native-module] [quo2.core :as quo] [react-native.core :as rn] [reagent.core :as reagent] [status-im2.common.confirmation-drawer.view :as confirmation-drawer] [status-im2.contexts.onboarding.common.background.view :as background] - [status-im2.contexts.onboarding.profiles.style :as style] + [status-im2.contexts.profile.profiles.style :as style] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.re-frame :as rf] @@ -15,10 +15,6 @@ [react-native.safe-area :as safe-area] [clojure.string :as string])) -(defn login-multiaccount - [] - (rf/dispatch [:multiaccounts.login.ui/password-input-submitted])) - (defn new-account-options [] [quo/action-drawer @@ -76,8 +72,10 @@ :shell? true}])) (defn profile-card - [{:keys [name key-uid customization-color keycard-pairing last-index set-hide-profiles]} - index] + [{:keys [name key-uid customization-color keycard-pairing]} + index + _ + {:keys [last-index set-hide-profiles]}] (let [last-item? (= last-index index) profile-picture (rf/sub [:profile/login-profiles-picture key-uid])] [quo/profile-card @@ -101,11 +99,7 @@ (defn profiles-section [{:keys [set-hide-profiles]}] - (let [multiaccounts (vals (rf/sub [:profile/profiles-overview])) - profiles-data (map #(assoc % - :last-index (- (count multiaccounts) 1) - :set-hide-profiles set-hide-profiles) - multiaccounts)] + (let [profiles (vals (rf/sub [:profile/profiles-overview]))] [rn/view {:style style/profiles-container} [rn/view @@ -123,9 +117,11 @@ :accessibility-label :show-new-account-options} :main-icons/add]] [rn/flat-list - {:data (sort-by :timestamp > profiles-data) + {:data (sort-by :timestamp > profiles) :key-fn :key-uid :content-container-style {:padding-bottom 20} + :render-data {:last-index (- (count profiles) 1) + :set-hide-profiles set-hide-profiles} :render-fn profile-card}]])) (defn forget-password-doc @@ -175,19 +171,23 @@ [quo/text {:size :paragraph-2} (i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]]) +(defn- get-error-message + [error] + (if (and (some? error) + (or (= error "file is not a database") + (string/starts-with? error "failed to set ") + (string/starts-with? error "Failed"))) + (i18n/label :t/oops-wrong-password) + error)) + (defn login-section [{:keys [set-show-profiles]}] (let [{:keys [error processing password]} (rf/sub [:profile/login]) {:keys [key-uid name customization-color]} (rf/sub [:profile/login-profile]) sign-in-enabled? (rf/sub [:sign-in-enabled?]) profile-picture (rf/sub [:profile/login-profiles-picture key-uid]) - error (if (and (some? error) - (or (= error "file is not a database") - (string/starts-with? error - "failed to set ") - (string/starts-with? error "Failed"))) - (i18n/label :t/oops-wrong-password) - error)] + error (get-error-message error) + login-multiaccount #(rf/dispatch [:profile.login/login])] [rn/keyboard-avoiding-view {:style style/login-container :keyboardVerticalOffset (- (safe-area/get-bottom))} @@ -257,7 +257,7 @@ :style {:margin-bottom (+ (safe-area/get-bottom) 12)}} (i18n/label :t/log-in)]])) -(defn views +(defn view [] (let [show-profiles? (reagent/atom false) set-show-profiles #(reset! show-profiles? true) diff --git a/src/status_im2/contexts/profile/recover/events.cljs b/src/status_im2/contexts/profile/recover/events.cljs new file mode 100644 index 0000000000..428dc73c92 --- /dev/null +++ b/src/status_im2/contexts/profile/recover/events.cljs @@ -0,0 +1,24 @@ +(ns status-im2.contexts.profile.recover.events + (:require [utils.security.core :as security] + [status-im.ethereum.core :as ethereum] + [status-im2.contexts.profile.config :as profile.config] + [utils.re-frame :as rf] + [re-frame.core :as re-frame] + [native-module.core :as native-module])) + +(re-frame/reg-fx + ::restore-profile-and-login + (fn [request] + ;;"node.login" signal will be triggered as a callback + (native-module/restore-account-and-login request))) + +(rf/defn recover-profile-and-login + {:events [:profile.recover/recover-and-login]} + [_ {:keys [display-name password image-path color seed-phrase]}] + {::restore-profile-and-login + (merge (profile.config/create) + {:displayName display-name + :mnemonic (security/safe-unmask-data seed-phrase) + :password (ethereum/sha3 (security/safe-unmask-data password)) + :imagePath (profile.config/strip-file-prefix image-path) + :customizationColor color})}) diff --git a/src/status_im2/events.cljs b/src/status_im2/events.cljs index ba3003daa8..c152c6ab20 100644 --- a/src/status_im2/events.cljs +++ b/src/status_im2/events.cljs @@ -1,62 +1,30 @@ (ns status-im2.events - (:require [re-frame.core :as re-frame] - [status-im2.common.json-rpc.events] + (:require [status-im2.common.json-rpc.events] [status-im2.common.toasts.events] [status-im2.contexts.add-new-contact.events] status-im2.contexts.onboarding.events [status-im.bottom-sheet.events] [status-im2.db :as db] [utils.re-frame :as rf] - [utils.datetime :as datetime] status-im2.contexts.shell.share.events status-im2.contexts.syncing.events status-im2.contexts.chat.events status-im2.common.password-authentication.events status-im2.contexts.communities.overview.events status-im2.contexts.chat.photo-selector.events - [status-im2.contexts.profile.events :as profile.events] status-im2.common.theme.events - [status-im2.navigation.events :as navigation] - [native-module.core :as native-module])) - -(re-frame/reg-cofx :now (fn [coeffects _] (assoc coeffects :now (datetime/timestamp)))) - -(rf/defn initialize-app-db - "Initialize db to initial state" - [{{:keys [keycard supported-biometric-auth goto-key-storage?] - :network/keys [type] - :keycard/keys [banner-hidden]} - :db}] - {:db (assoc db/app-db - :network/type type - :keycard/banner-hidden banner-hidden - :keycard (dissoc keycard :secrets :pin :application-info) - :supported-biometric-auth supported-biometric-auth - :goto-key-storage? goto-key-storage?)}) - -(re-frame/reg-fx - :profile/get-profiles-overview - (fn [callback] - (native-module/open-accounts callback))) - -(rf/defn get-profiles-overview-success - {:events [:profile/get-profiles-overview-success]} - [cofx profiles-overview] - (if (seq profiles-overview) - (profile.events/init-profiles-overview cofx profiles-overview) - (navigation/init-root cofx :intro))) + status-im2.contexts.profile.events + [status-im.keycard.core :as keycard])) (rf/defn start-app {:events [:app-started]} [cofx] (rf/merge cofx - {:theme/init-theme nil - :biometric/get-supported-biometric-auth nil + {:db db/app-db + :theme/init-theme nil :network/listen-to-network-info nil - :keycard/register-card-events nil - :keycard/check-nfc-support nil - :keycard/check-nfc-enabled nil - :keycard/retrieve-pairings nil - :profile/get-profiles-overview #(re-frame/dispatch + :biometric/get-supported-biometric-type nil + ;;app starting flow continues in get-profiles-overview + :profile/get-profiles-overview #(rf/dispatch [:profile/get-profiles-overview-success %])} - (initialize-app-db))) + (keycard/init))) diff --git a/src/status_im2/navigation/roots.cljs b/src/status_im2/navigation/roots.cljs index 228b8cc4f0..3c743cd0a1 100644 --- a/src/status_im2/navigation/roots.cljs +++ b/src/status_im2/navigation/roots.cljs @@ -64,15 +64,6 @@ {:root {:stack {:children [{:component {:name :onboarding-notification :id :onboarding-notification :options (options/statusbar-and-navbar-root)}}] - :options (merge (options/default-root) - (options/statusbar-and-navbar-root) - {:topBar (assoc (options/topbar-options) :visible false)})}}} - - ;; TERMS OF SERVICE - :tos - {:root {:stack {:children [{:component {:name :force-accept-tos - :id :force-accept-tos - :options (get-screen-options :force-accept-tos)}}] :options (merge (options/default-root) (options/statusbar-and-navbar-root) {:topBar (assoc (options/topbar-options) :visible false)})}}}}) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index 44395e5e67..942ed1a9cb 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -19,7 +19,7 @@ [status-im2.contexts.onboarding.sign-in.view :as sign-in] [status-im2.contexts.onboarding.generating-keys.view :as generating-keys] [status-im2.contexts.onboarding.enter-seed-phrase.view :as enter-seed-phrase] - [status-im2.contexts.onboarding.profiles.view :as profiles] + [status-im2.contexts.profile.profiles.view :as profiles] [status-im2.contexts.quo-preview.main :as quo.preview] [status-im2.contexts.shell.jump-to.view :as shell] [status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page] @@ -143,7 +143,7 @@ {:name :profiles :options {:theme :dark :layout options/onboarding-layout} - :component profiles/views} + :component profiles/view} {:name :new-to-status :options {:theme :dark diff --git a/src/status_im2/subs/general.cljs b/src/status_im2/subs/general.cljs index 206abbaf6b..d7eef36516 100644 --- a/src/status_im2/subs/general.cljs +++ b/src/status_im2/subs/general.cljs @@ -228,12 +228,6 @@ (fn [network] (tokens/native-currency network))) -(re-frame/reg-sub - :information-box-closed? - :<- [:information-box-states] - (fn [states [_ id]] - (get states id))) - (re-frame/reg-sub :connectivity/state :<- [:network-status] diff --git a/src/status_im2/subs/onboarding.cljs b/src/status_im2/subs/onboarding.cljs deleted file mode 100644 index f2515786b8..0000000000 --- a/src/status_im2/subs/onboarding.cljs +++ /dev/null @@ -1,80 +0,0 @@ -(ns status-im2.subs.onboarding - (:require [quo2.theme :as theme] - [re-frame.core :as re-frame] - [status-im.multiaccounts.recover.core :as recover] - [status-im2.constants :as constants] - [utils.image-server :as image-server])) - -(re-frame/reg-sub - :intro-wizard - :<- [:intro-wizard-state] - :<- [:dimensions/window] - (fn [[wizard-state {:keys [width height]}]] - (assoc wizard-state :view-height height :view-width width))) - -(re-frame/reg-sub - :intro-wizard/choose-key - :<- [:intro-wizard] - (fn [wizard-state] - (select-keys wizard-state [:multiaccounts :selected-id :view-height]))) - -(re-frame/reg-sub - :intro-wizard/select-key-storage - :<- [:intro-wizard] - (fn [wizard-state] - (select-keys wizard-state [:selected-storage-type :recovering?]))) - -(re-frame/reg-sub - :intro-wizard/enter-phrase - :<- [:intro-wizard] - (fn [wizard-state] - (select-keys wizard-state - [:processing? - :passphrase-word-count - :next-button-disabled? - :passphrase-error]))) - -(re-frame/reg-sub - :intro-wizard/recovery-success - :<- [:intro-wizard] - (fn [wizard-state] - {:pubkey (get-in wizard-state [:derived constants/path-whisper-keyword :public-key]) - :compressed-key (get-in wizard-state [:derived constants/path-whisper-keyword :compressed-key]) - :name (get-in wizard-state [:derived constants/path-whisper-keyword :name]) - :processing? (:processing? wizard-state)})) - -(re-frame/reg-sub - :intro-wizard/recover-existing-account? - :<- [:intro-wizard] - :<- [:profile/profiles-overview] - (fn [[intro-wizard multiaccounts]] - (recover/existing-account? (:root-key intro-wizard) multiaccounts))) - -(re-frame/reg-sub - :profile/login-profiles-picture - :<- [:profile/profiles-overview] - :<- [:mediaserver/port] - (fn [[multiaccounts port] [_ target-key-uid]] - (let [image-name (-> multiaccounts - (get-in [target-key-uid :images]) - first - :type)] - (when image-name - (image-server/get-account-image-uri {:port port - :image-name image-name - :key-uid target-key-uid - :theme (theme/get-theme) - :ring? true}))))) - -(defn login-profile-keycard-pairing - "Compute the keycard-pairing value of the multiaccount selected for login" - [db _] - (when-let [acc-to-login (-> db :profile/login)] - (-> db - :profile/profiles-overview - (get (:key-uid acc-to-login)) - :keycard-pairing))) - -(re-frame/reg-sub - :intro-wizard/acc-to-login-keycard-pairing - login-profile-keycard-pairing) diff --git a/src/status_im2/subs/onboarding_test.cljs b/src/status_im2/subs/onboarding_test.cljs deleted file mode 100644 index 803be44456..0000000000 --- a/src/status_im2/subs/onboarding_test.cljs +++ /dev/null @@ -1,38 +0,0 @@ -(ns status-im2.subs.onboarding-test - (:require [cljs.test :as t] - [quo2.theme :as theme] - [re-frame.db :as rf-db] - status-im2.subs.onboarding - [test-helpers.unit :as h] - [utils.image-server :as image-server] - [utils.re-frame :as rf])) - -(def key-uid "0x1") -(def port "mediaserver-port") -(def cur-theme :current-theme) - -(h/deftest-sub :profile/login-profiles-picture - [sub-name] - (with-redefs [image-server/get-account-image-uri identity - theme/get-theme (constantly cur-theme)] - (t/testing "nil when no images" - (swap! rf-db/app-db assoc :profile/profiles-overview {key-uid {}}) - (t/is (nil? (rf/sub [sub-name key-uid])))) - - (t/testing "nil when no key-uid" - (swap! rf-db/app-db assoc :profile/profiles-overview {key-uid {}}) - (t/is (nil? (rf/sub [sub-name "0x2"])))) - - (t/testing "result from image-server/get-account-image-uri" - (swap! - rf-db/app-db - assoc - :profile/profiles-overview {key-uid {:images [{:type "large"} - {:type "thumbnail"}]}} - :mediaserver/port port) - (t/is (= (rf/sub [sub-name key-uid]) - {:port port - :image-name "large" - :key-uid key-uid - :theme cur-theme - :ring? true}))))) diff --git a/src/status_im2/subs/profile.cljs b/src/status_im2/subs/profile.cljs index 871ed2534b..cad7b94092 100644 --- a/src/status_im2/subs/profile.cljs +++ b/src/status_im2/subs/profile.cljs @@ -23,6 +23,22 @@ (fn [{:keys [customization-color]}] (or customization-color constants/profile-default-color))) +(re-frame/reg-sub + :profile/login-profiles-picture + :<- [:profile/profiles-overview] + :<- [:mediaserver/port] + (fn [[multiaccounts port] [_ target-key-uid]] + (let [image-name (-> multiaccounts + (get-in [target-key-uid :images]) + first + :type)] + (when image-name + (image-server/get-account-image-uri {:port port + :image-name image-name + :key-uid target-key-uid + :theme (theme/get-theme) + :ring? true}))))) + (re-frame/reg-sub :multiaccount/public-key :<- [:profile/profile] diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index 8289b88ca9..fc916b841e 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -15,7 +15,6 @@ status-im2.subs.mailservers status-im2.subs.profile status-im2.subs.networks - status-im2.subs.onboarding status-im2.subs.pairing status-im2.subs.search status-im2.subs.shell @@ -66,7 +65,7 @@ (reg-root-key-sub :networks/manage :networks/manage) (reg-root-key-sub :get-pairing-installations :pairing/installations) (reg-root-key-sub :tooltips :tooltips) -(reg-root-key-sub :supported-biometric-auth :supported-biometric-auth) +(reg-root-key-sub :biometric/supported-type :biometric/supported-type) (reg-root-key-sub :connectivity/ui-status-properties :connectivity/ui-status-properties) (reg-root-key-sub :logged-in-since :logged-in-since) (reg-root-key-sub :app-state :app-state) @@ -231,8 +230,6 @@ (reg-root-key-sub :auth-method :auth-method) -(reg-root-key-sub :tos-accept-next-root :tos-accept-next-root) - ;; keycard (reg-root-key-sub :keycard/banner-hidden :keycard/banner-hidden) @@ -290,7 +287,5 @@ (reg-root-key-sub :messenger/started? :messenger/started?) -(reg-root-key-sub :information-box-states :information-box-states) - ; Messages home view -> tabs (reg-root-key-sub :messages-home/selected-tab :messages-home/selected-tab) diff --git a/src/status_im2/subs/subs_test.cljs b/src/status_im2/subs/subs_test.cljs index 95138390e8..69afaff14f 100644 --- a/src/status_im2/subs/subs_test.cljs +++ b/src/status_im2/subs/subs_test.cljs @@ -1,6 +1,5 @@ (ns status-im2.subs.subs-test (:require [cljs.test :refer [deftest is testing]] - [status-im2.subs.onboarding :as onboarding] [status-im2.subs.wallet.transactions :as wallet.transactions])) (def transactions @@ -23,20 +22,3 @@ (testing "Check if transactions are sorted by date" (is (= (wallet.transactions/group-transactions-by-date transactions) grouped-transactions)))) - -(deftest login-profile-keycard-pairing - (testing "returns nil when no :profile/login" - (let [res (onboarding/login-profile-keycard-pairing - {:profile/login nil - :profile/profiles-overview - {"0x1" {:keycard-pairing "keycard-pairing-code"}}} - {})] - (is (nil? res)))) - - (testing "returns :keycard-pairing when :profile/login is present" - (let [res (onboarding/login-profile-keycard-pairing - {:profile/login {:key-uid "0x1"} - :profile/profiles-overview - {"0x1" {:keycard-pairing "keycard-pairing-code"}}} - {})] - (is (= res "keycard-pairing-code"))))) diff --git a/src/utils/re_frame.cljs b/src/utils/re_frame.cljs index 07463a37b7..f8cc08f6d3 100644 --- a/src/utils/re_frame.cljs +++ b/src/utils/re_frame.cljs @@ -2,11 +2,14 @@ (:require-macros utils.re-frame) (:require [re-frame.core :as re-frame] [re-frame.interceptor :as interceptor] - [taoensso.timbre :as log]) + [taoensso.timbre :as log] + [utils.datetime :as datetime]) (:refer-clojure :exclude [merge reduce])) (def handler-nesting-level (atom 0)) +(re-frame/reg-cofx :now (fn [coeffects _] (assoc coeffects :now (datetime/timestamp)))) + (def debug-handlers-names "Interceptor which logs debug information to js/console for each event." (interceptor/->interceptor