From 13b04f17ebd4bfa69ee792db6ba2d603499669df Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 29 Jan 2019 10:42:53 +0100 Subject: [PATCH] Enable pairing & contact recovery This PR enables pairing outside of dev-mode and contact-recovery, which is useful in the case a new device is added or re-installed. Signed-off-by: Andrea Maria Piana --- src/status_im/contact_recovery/core.cljs | 68 +++++++++++----- .../data_store/contact_recovery.cljs | 4 +- src/status_im/events.cljs | 11 +-- src/status_im/pairing/core.cljs | 81 +++++++++---------- src/status_im/signals/core.cljs | 2 +- src/status_im/transport/message/protocol.cljs | 6 +- .../desktop/main/tabs/profile/views.cljs | 9 +-- src/status_im/ui/screens/pairing/views.cljs | 4 +- .../ui/screens/profile/user/views.cljs | 12 ++- src/status_im/utils/config.cljs | 4 +- .../status_im/test/contact-recovery/core.cljs | 33 +++----- translations/en.json | 3 +- 12 files changed, 118 insertions(+), 119 deletions(-) diff --git a/src/status_im/contact_recovery/core.cljs b/src/status_im/contact_recovery/core.cljs index 4b2d1747e7..44849e8648 100644 --- a/src/status_im/contact_recovery/core.cljs +++ b/src/status_im/contact_recovery/core.cljs @@ -1,42 +1,66 @@ (ns status-im.contact-recovery.core + "This namespace handles the case where a user has just recovered their account + and is not able to decrypt messages, as the encryption is device-to-device. + Upon receiving this message, an empty message is sent back carrying device information + which will tell the other peer to target this device as well" (:require [status-im.i18n :as i18n] [re-frame.core :as re-frame] [status-im.data-store.contact-recovery :as data-store.contact-recovery] + [status-im.utils.config :as config] [status-im.utils.fx :as fx] [status-im.accounts.db :as accounts.db] [status-im.contact.core :as models.contact])) +;; How long do we wait until we process a contact-recovery again? +(def contact-recovery-interval-ms (* 6 60 60 1000)) + (defn prompt-dismissed! [public-key] (re-frame/dispatch [:contact-recovery.ui/prompt-dismissed public-key])) (defn prompt-accepted! [public-key] (re-frame/dispatch [:contact-recovery.ui/prompt-accepted public-key])) -(defn show-contact-recovery-fx - "Check that a pop up for that given user is not already shown, if not proceed fetching from the db whether we should be showing it" - [{:keys [db] :as cofx} public-key] - (let [my-public-key (accounts.db/current-public-key cofx) - pfs? (get-in db [:account/account :settings :pfs?])] +(defn handle-contact-recovery-fx + "Check that a contact-recovery for the given user is not already in process, if not + fetch from db and check" + [{:keys [db now] :as cofx} public-key] + (let [my-public-key (accounts.db/current-public-key cofx)] (when (and (not= public-key my-public-key) - pfs? (not (get-in db [:contact-recovery/pop-up public-key]))) {:db (update db :contact-recovery/pop-up conj public-key) - :contact-recovery/show-contact-recovery-message public-key}))) + :contact-recovery/handle-recovery [now public-key]}))) (fx/defn prompt-dismissed [{:keys [db]} public-key] {:db (update db :contact-recovery/pop-up disj public-key)}) -(defn show-contact-recovery-message? [public-key] - (not (data-store.contact-recovery/get-contact-recovery-by-id public-key))) +(defn notified-recently? + "We don't want to notify the user each time, so we wait an interval before + sending a message again" + [now public-key] + (let [{:keys [timestamp]} (data-store.contact-recovery/get-contact-recovery-by-id public-key)] + (and timestamp + (< contact-recovery-interval-ms (- now timestamp))))) -(defn show-contact-recovery-message-fx [public-key] - (when (show-contact-recovery-message? public-key) - (re-frame/dispatch [:contact-recovery.callback/show-contact-recovery-message public-key]))) +(defn handle-recovery-fx [now public-key] + (when-not (notified-recently? now public-key) + (re-frame/dispatch [:contact-recovery.callback/handle-recovery public-key]))) + +(fx/defn notify-user + "Send an empty message to the user, which will carry device information" + [cofx public-key] + (let [{:keys [web3]} (:db cofx) + current-public-key (accounts.db/current-public-key cofx)] + {:shh/send-direct-message + [{:web3 web3 + :src current-public-key + :dst public-key + :payload ""}]})) (re-frame/reg-fx - :contact-recovery/show-contact-recovery-message - show-contact-recovery-message-fx) + :contact-recovery/handle-recovery + (fn [[now public-key]] + (handle-recovery-fx now public-key))) (fx/defn save-contact-recovery [{:keys [now]} public-key] {:data-store/tx [(data-store.contact-recovery/save-contact-recovery-tx {:timestamp now @@ -46,18 +70,18 @@ (fx/merge cofx (prompt-dismissed public-key) - (save-contact-recovery public-key))) + (save-contact-recovery public-key) + (notify-user public-key))) -(fx/defn show-contact-recovery-message [{:keys [db] :as cofx} public-key] - (let [pfs? (get-in db [:account/account :settings :pfs?]) - contact (models.contact/build-contact cofx public-key) +(fx/defn handle-recovery [{:keys [db] :as cofx} public-key] + (let [contact (models.contact/build-contact cofx public-key) popup {:ui/show-confirmation {:title (i18n/label :t/contact-recovery-title {:name (:name contact)}) :content (i18n/label :t/contact-recovery-content {:name (:name contact)}) - :confirm-button-text (i18n/label :t/add-to-contacts) + :confirm-button-text (i18n/label :t/notify) :cancel-button-text (i18n/label :t/cancel) :on-cancel #(prompt-dismissed! public-key) :on-accept #(prompt-accepted! public-key)}}] - (when pfs? - (fx/merge cofx - popup)))) + (if config/show-contact-recovery-pop-up? + (fx/merge cofx popup) + (prompt-accepted cofx public-key)))) diff --git a/src/status_im/data_store/contact_recovery.cljs b/src/status_im/data_store/contact_recovery.cljs index 743b69ec3b..34ed020376 100644 --- a/src/status_im/data_store/contact_recovery.cljs +++ b/src/status_im/data_store/contact_recovery.cljs @@ -2,7 +2,9 @@ (:require [status-im.data-store.realm.core :as core])) (defn get-contact-recovery-by-id [public-key] - (core/single (core/get-by-field @core/account-realm :contact-recovery :id public-key))) + (-> @core/account-realm + (core/get-by-field :contact-recovery :id public-key) + (core/single-clj :contact-recovery))) (defn save-contact-recovery-tx "Returns tx function for saving a contact-recovery" diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index aaad93951a..8672763d73 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1568,10 +1568,7 @@ :contact-recovery.ui/prompt-accepted [(re-frame/inject-cofx :random-id-generator)] (fn [cofx [_ public-key]] - (fx/merge - cofx - (contact/add-contact public-key) - (contact-recovery/prompt-accepted public-key)))) + (contact-recovery/prompt-accepted cofx public-key))) (handlers/register-handler-fx :contact-recovery.ui/prompt-dismissed @@ -1579,10 +1576,10 @@ (contact-recovery/prompt-dismissed cofx public-key))) (handlers/register-handler-fx - :contact-recovery.callback/show-contact-recovery-message + :contact-recovery.callback/handle-recovery [(re-frame/inject-cofx :random-id-generator)] (fn [cofx [_ public-key]] - (contact-recovery/show-contact-recovery-message cofx public-key))) + (contact-recovery/handle-recovery cofx public-key))) (handlers/register-handler-fx :stickers/load-sticker-pack-success @@ -1609,4 +1606,4 @@ (handlers/register-handler-fx :stickers/select-pack (fn [{:keys [db]} [_ id]] - {:db (assoc db :stickers/selected-pack id)})) \ No newline at end of file + {:db (assoc db :stickers/selected-pack id)})) diff --git a/src/status_im/pairing/core.cljs b/src/status_im/pairing/core.cljs index a4ec32bc98..d25087127a 100644 --- a/src/status_im/pairing/core.cljs +++ b/src/status_im/pairing/core.cljs @@ -86,21 +86,19 @@ :data-store/tx [(data-store.installations/save updated-installation)]})) (defn handle-bundles-added [{:keys [db] :as cofx} bundle] - (let [dev-mode? (get-in db [:account/account :dev-mode?])] - (when (config/pairing-enabled? dev-mode?) - (let [installation-id (:installationID bundle) - new-installation {:installation-id installation-id - :has-bundle? true}] - (when - (and (= (:identity bundle) - (accounts.db/current-public-key cofx)) - (not= (get-in db [:account/account :installation-id]) installation-id) - (not (get-in db [:pairing/installations installation-id]))) - (fx/merge cofx - (upsert-installation new-installation) - #(when-not (or (get-in % [:db :pairing/prompt-user-pop-up]) - (= :installations (:view-id db))) - (prompt-user-on-new-installation %)))))))) + (let [installation-id (:installationID bundle) + new-installation {:installation-id installation-id + :has-bundle? true}] + (when + (and (= (:identity bundle) + (accounts.db/current-public-key cofx)) + (not= (get-in db [:account/account :installation-id]) installation-id) + (not (get-in db [:pairing/installations installation-id]))) + (fx/merge cofx + (upsert-installation new-installation) + #(when-not (or (get-in % [:db :pairing/prompt-user-pop-up]) + (= :installations (:view-id db))) + (prompt-user-on-new-installation %)))))) (defn sync-installation-account-message [{:keys [db]}] (let [account (-> db @@ -191,18 +189,15 @@ (fx/defn send-sync-installation [cofx payload] (let [{:keys [web3]} (:db cofx) current-public-key (accounts.db/current-public-key cofx)] - {:shh/send-direct-message - [{:web3 web3 - :src current-public-key - :dst current-public-key + [{:web3 web3 + :src current-public-key + :dst current-public-key :payload payload}]})) (fx/defn send-installation-message-fx [cofx payload] - (let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])] - (when (and (config/pairing-enabled? dev-mode?) - (has-paired-installations? cofx)) - (protocol/send payload nil cofx)))) + (when (has-paired-installations? cofx) + (protocol/send payload nil cofx))) (fx/defn sync-public-chat [cofx chat-id] (let [sync-message (transport.pairing/SyncInstallation. {} {} {:public? true @@ -230,30 +225,26 @@ contacts)) (defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account chat]} sender] - (let [dev-mode? (get-in db [:account/account :dev-mode?])] - (when (and (config/pairing-enabled? dev-mode?) - (= sender (accounts.db/current-public-key cofx))) - (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts)) - new-account (merge-account (:account/account db) account)] - (fx/merge cofx - {:db (assoc db - :contacts/contacts new-contacts - :account/account new-account) - :data-store/base-tx [(data-store.accounts/save-account-tx new-account)] - :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]} - #(when (:public? chat) - (models.chat/start-public-chat % (:chat-id chat) {:dont-navigate? true}))))))) + (when (= sender (accounts.db/current-public-key cofx)) + (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts)) + new-account (merge-account (:account/account db) account)] + (fx/merge cofx + {:db (assoc db + :contacts/contacts new-contacts + :account/account new-account) + :data-store/base-tx [(data-store.accounts/save-account-tx new-account)] + :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]} + #(when (:public? chat) + (models.chat/start-public-chat % (:chat-id chat) {:dont-navigate? true})))))) (defn handle-pair-installation [{:keys [db] :as cofx} {:keys [name installation-id device-type]} timestamp sender] - (let [dev-mode? (get-in db [:account/account :dev-mode?])] - (when (and (config/pairing-enabled? dev-mode?) - (= sender (accounts.db/current-public-key cofx)) - (not= (get-in db [:account/account :installation-id]) installation-id)) - (let [installation {:installation-id installation-id - :name name - :device-type device-type - :last-paired timestamp}] - (upsert-installation cofx installation))))) + (when (and (= sender (accounts.db/current-public-key cofx)) + (not= (get-in db [:account/account :installation-id]) installation-id)) + (let [installation {:installation-id installation-id + :name name + :device-type device-type + :last-paired timestamp}] + (upsert-installation cofx installation)))) (fx/defn set-name [{:keys [db] :as cofx} installation-name] (let [new-account (assoc (get-in cofx [:db :account/account]) :installation-name installation-name)] diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 6ba8ed7e10..c58369ec03 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -71,6 +71,6 @@ "mailserver.request.completed" (mailserver/handle-request-completed cofx event) "mailserver.request.expired" (when (accounts.db/logged-in? cofx) (mailserver/resend-request cofx {:request-id (:hash event)})) - "messages.decrypt.failed" (contact-recovery/show-contact-recovery-fx cofx (:sender event)) + "messages.decrypt.failed" (contact-recovery/handle-contact-recovery-fx cofx (:sender event)) "discovery.summary" (summary cofx event) (log/debug "Event " type " not handled")))) diff --git a/src/status_im/transport/message/protocol.cljs b/src/status_im/transport/message/protocol.cljs index d78133459d..a2c215c01e 100644 --- a/src/status_im/transport/message/protocol.cljs +++ b/src/status_im/transport/message/protocol.cljs @@ -90,8 +90,7 @@ (defrecord Message [content content-type message-type clock-value timestamp] StatusMessage (send [this chat-id {:keys [message-id] :as cofx}] - (let [dev-mode? (get-in cofx [:db :account/account :dev-mode?]) - current-public-key (accounts.db/current-public-key cofx) + (let [current-public-key (accounts.db/current-public-key cofx) params {:chat-id chat-id :payload this :success-event [:transport/message-sent @@ -104,8 +103,7 @@ :user-message (fx/merge cofx - #(when (config/pairing-enabled? dev-mode?) - (send-direct-message % current-public-key nil this)) + (send-direct-message current-public-key nil this) (send-with-pubkey params))))) (receive [this chat-id signature _ cofx] {:chat-received-message/add-fx diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs index 98f87cbc57..0b2c53e1d4 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs @@ -203,11 +203,10 @@ installation-id [:pairing/installation-id] installation-name [:pairing/installation-name]] [react/scroll-view - (when (config/pairing-enabled? true) - (installations-section - installation-id - installation-name - installations))])) + (installations-section + installation-id + installation-name + installations)])) (views/defview backup-recovery-phrase [] [profile.recovery/backup-seed]) diff --git a/src/status_im/ui/screens/pairing/views.cljs b/src/status_im/ui/screens/pairing/views.cljs index d614e1ac67..f877997774 100644 --- a/src/status_im/ui/screens/pairing/views.cljs +++ b/src/status_im/ui/screens/pairing/views.cljs @@ -140,8 +140,8 @@ (if utils.platform/ios? ;; On IOS switches seems to be broken, they take up value of dev-mode? (so if dev mode is on they all show to be on). ;; Replacing therefore with checkbox until I have more time to investigate - (checkbox.views/plain-checkbox {:checked? enabled? - :on-value-change (partial toggle-enabled! installation-id enabled?)}) + (checkbox.views/checkbox {:checked? enabled? + :on-value-change (partial toggle-enabled! installation-id enabled?)}) [react/switch {:on-tint-color colors/blue :value enabled? :on-value-change (partial toggle-enabled! installation-id enabled?)}])]]]) diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index 8504949a24..0c81cd8f99 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -127,13 +127,11 @@ {:label-kw :t/backup-your-recovery-phrase :action-fn #(re-frame/dispatch [:navigate-to :backup-seed]) :icon-content [components.common/counter {:size 22} 1]}]) - (when (config/pairing-enabled? dev-mode?) - [profile.components/settings-item-separator]) - (when (config/pairing-enabled? dev-mode?) - [profile.components/settings-item - {:label-kw :t/devices - :action-fn #(re-frame/dispatch [:navigate-to :installations]) - :accessibility-label :pairing-settings-button}]) + [profile.components/settings-item-separator] + [profile.components/settings-item + {:label-kw :t/devices + :action-fn #(re-frame/dispatch [:navigate-to :installations]) + :accessibility-label :pairing-settings-button}] [profile.components/settings-item-separator] [profile.components/settings-switch-item {:label-kw :t/web3-opt-in diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index c276e7e407..6cfd3488cf 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -18,9 +18,7 @@ (def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1"))) (def rpc-networks-only? (enabled? (get-config :RPC_NETWORKS_ONLY "1"))) (def group-chats-publish-to-topic? (enabled? (get-config :GROUP_CHATS_PUBLISH_TO_TOPIC "0"))) -(defn pairing-enabled? [dev-mode?] - (and (enabled? (get-config :PAIRING_ENABLED "0")) - (or dev-mode? platform/desktop?))) +(def show-contact-recovery-pop-up? (enabled? (get-config :SHOW_CONTACT_RECOVERY_POPUP))) (def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED))) (def mainnet-warning-enabled? (enabled? (get-config :MAINNET_WARNING_ENABLED 0))) (def pfs-encryption-enabled? (enabled? (get-config :PFS_ENCRYPTION_ENABLED "0"))) diff --git a/test/cljs/status_im/test/contact-recovery/core.cljs b/test/cljs/status_im/test/contact-recovery/core.cljs index 76e1c9667a..1afa2540d8 100644 --- a/test/cljs/status_im/test/contact-recovery/core.cljs +++ b/test/cljs/status_im/test/contact-recovery/core.cljs @@ -1,38 +1,29 @@ (ns status-im.test.contact-recovery.core (:require [cljs.test :refer-macros [deftest is testing]] + [status-im.utils.config :as config] [status-im.contact-recovery.core :as contact-recovery])) (deftest show-contact-recovery-fx (let [public-key "pk"] - (testing "pfs is not enabled" - (testing "no pop up is displayed" - (let [actual (contact-recovery/show-contact-recovery-fx {:db {:contact-recovery/pop-up #{}}} public-key)] - (testing "it does nothing" - (is (not (:db actual))) - (is (not (:contact-recovery/show-contact-recovery-message actual))))))) - (testing "no pop up is displayed" - (let [cofx {:db {:contact-recovery/pop-up #{} + (testing "no contact-recovery in place" + (let [cofx {:now "now" + :db {:contact-recovery/pop-up #{} :account/account {:settings {:pfs? true}}}} - actual (contact-recovery/show-contact-recovery-fx cofx public-key)] + actual (contact-recovery/handle-contact-recovery-fx cofx public-key)] (testing "it sets the pop up as displayed" (is (get-in actual [:db :contact-recovery/pop-up public-key]))) (testing "it adds an fx for fetching the contact" - (is (= public-key (:contact-recovery/show-contact-recovery-message actual)))))) - (testing "pop up is already displayed" - (let [actual (contact-recovery/show-contact-recovery-fx {:db {:contact-recovery/pop-up #{public-key}}} public-key)] + (is (= ["now" public-key] (:contact-recovery/handle-recovery actual)))))) + (testing "contact recovery is in place" + (let [actual (contact-recovery/handle-contact-recovery-fx {:db {:contact-recovery/pop-up #{public-key}}} public-key)] (testing "it does nothing" (is (not (:db actual))) (is (not (:contact-recovery/show-contact-recovery-message actual)))))))) (deftest show-contact-recovery-message (let [public-key "pk"] - (testing "pfs is enabled" - (let [cofx {:db {:account/account {:settings {:pfs? true}}}} - actual (contact-recovery/show-contact-recovery-message cofx public-key)] + (with-redefs [config/show-contact-recovery-pop-up? true] + (let [cofx {:db {}} + actual (contact-recovery/handle-recovery cofx public-key)] (testing "it shows a pop up" - (is (:ui/show-confirmation actual))))) - (testing "pfs is not enabled" - (let [cofx {:db {:account/account {:settings {}}}} - actual (contact-recovery/show-contact-recovery-message cofx public-key)] - (testing "it shows a pop up" - (is (not (:ui/show-confirmation actual)))))))) + (is (:ui/show-confirmation actual))))))) diff --git a/translations/en.json b/translations/en.json index 1d9c359c7b..8442a4ab65 100644 --- a/translations/en.json +++ b/translations/en.json @@ -15,9 +15,10 @@ "syncing-disabled": "Syncing disabled", "sync-all-devices": "Sync all devices", "syncing-devices": "Syncing...", + "notify": "Notify", "paired-devices": "Paired devices", "contact-recovery-title": "{{name}} has sent you a message", - "contact-recovery-content": "{{name}} has sent you a message but did not include this device.\nThis might happen if you have more than 3 devices, you haven't paired your devices correctly or you just recovered your account.\nPlease make sure your devices are paired correctly and click Add to contacts to notify the user of this device.", + "contact-recovery-content": "{{name}} has sent you a message but did not include this device.\nThis might happen if you have more than 3 devices, you haven't paired your devices correctly or you just recovered your account.\nPlease make sure your devices are paired correctly and click Notify to let the user know of this device.", "pairing-maximum-number-reached-title": "Max number of devices reached", "pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.", "pairing-new-installation-detected-title": "New device detected",