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 <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2019-01-29 10:42:53 +01:00
parent ac758f5348
commit 13b04f17eb
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
12 changed files with 118 additions and 119 deletions

View File

@ -1,42 +1,66 @@
(ns status-im.contact-recovery.core (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 (:require
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.data-store.contact-recovery :as data-store.contact-recovery] [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.utils.fx :as fx]
[status-im.accounts.db :as accounts.db] [status-im.accounts.db :as accounts.db]
[status-im.contact.core :as models.contact])) [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] (defn prompt-dismissed! [public-key]
(re-frame/dispatch [:contact-recovery.ui/prompt-dismissed public-key])) (re-frame/dispatch [:contact-recovery.ui/prompt-dismissed public-key]))
(defn prompt-accepted! [public-key] (defn prompt-accepted! [public-key]
(re-frame/dispatch [:contact-recovery.ui/prompt-accepted public-key])) (re-frame/dispatch [:contact-recovery.ui/prompt-accepted public-key]))
(defn show-contact-recovery-fx (defn handle-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" "Check that a contact-recovery for the given user is not already in process, if not
[{:keys [db] :as cofx} public-key] fetch from db and check"
(let [my-public-key (accounts.db/current-public-key cofx) [{:keys [db now] :as cofx} public-key]
pfs? (get-in db [:account/account :settings :pfs?])] (let [my-public-key (accounts.db/current-public-key cofx)]
(when (and (not= public-key my-public-key) (when (and (not= public-key my-public-key)
pfs?
(not (get-in db [:contact-recovery/pop-up public-key]))) (not (get-in db [:contact-recovery/pop-up public-key])))
{:db (update db :contact-recovery/pop-up conj 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] (fx/defn prompt-dismissed [{:keys [db]} public-key]
{:db (update db :contact-recovery/pop-up disj public-key)}) {:db (update db :contact-recovery/pop-up disj public-key)})
(defn show-contact-recovery-message? [public-key] (defn notified-recently?
(not (data-store.contact-recovery/get-contact-recovery-by-id public-key))) "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] (defn handle-recovery-fx [now public-key]
(when (show-contact-recovery-message? public-key) (when-not (notified-recently? now public-key)
(re-frame/dispatch [:contact-recovery.callback/show-contact-recovery-message 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 (re-frame/reg-fx
:contact-recovery/show-contact-recovery-message :contact-recovery/handle-recovery
show-contact-recovery-message-fx) (fn [[now public-key]]
(handle-recovery-fx now public-key)))
(fx/defn save-contact-recovery [{:keys [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 {:data-store/tx [(data-store.contact-recovery/save-contact-recovery-tx {:timestamp now
@ -46,18 +70,18 @@
(fx/merge (fx/merge
cofx cofx
(prompt-dismissed public-key) (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] (fx/defn handle-recovery [{:keys [db] :as cofx} public-key]
(let [pfs? (get-in db [:account/account :settings :pfs?]) (let [contact (models.contact/build-contact cofx public-key)
contact (models.contact/build-contact cofx public-key)
popup {:ui/show-confirmation {:title (i18n/label :t/contact-recovery-title {:name (:name contact)}) popup {:ui/show-confirmation {:title (i18n/label :t/contact-recovery-title {:name (:name contact)})
:content (i18n/label :t/contact-recovery-content {: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) :cancel-button-text (i18n/label :t/cancel)
:on-cancel #(prompt-dismissed! public-key) :on-cancel #(prompt-dismissed! public-key)
:on-accept #(prompt-accepted! public-key)}}] :on-accept #(prompt-accepted! public-key)}}]
(when pfs? (if config/show-contact-recovery-pop-up?
(fx/merge cofx (fx/merge cofx popup)
popup)))) (prompt-accepted cofx public-key))))

View File

@ -2,7 +2,9 @@
(:require [status-im.data-store.realm.core :as core])) (:require [status-im.data-store.realm.core :as core]))
(defn get-contact-recovery-by-id [public-key] (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 (defn save-contact-recovery-tx
"Returns tx function for saving a contact-recovery" "Returns tx function for saving a contact-recovery"

View File

@ -1568,10 +1568,7 @@
:contact-recovery.ui/prompt-accepted :contact-recovery.ui/prompt-accepted
[(re-frame/inject-cofx :random-id-generator)] [(re-frame/inject-cofx :random-id-generator)]
(fn [cofx [_ public-key]] (fn [cofx [_ public-key]]
(fx/merge (contact-recovery/prompt-accepted cofx public-key)))
cofx
(contact/add-contact public-key)
(contact-recovery/prompt-accepted public-key))))
(handlers/register-handler-fx (handlers/register-handler-fx
:contact-recovery.ui/prompt-dismissed :contact-recovery.ui/prompt-dismissed
@ -1579,10 +1576,10 @@
(contact-recovery/prompt-dismissed cofx public-key))) (contact-recovery/prompt-dismissed cofx public-key)))
(handlers/register-handler-fx (handlers/register-handler-fx
:contact-recovery.callback/show-contact-recovery-message :contact-recovery.callback/handle-recovery
[(re-frame/inject-cofx :random-id-generator)] [(re-frame/inject-cofx :random-id-generator)]
(fn [cofx [_ public-key]] (fn [cofx [_ public-key]]
(contact-recovery/show-contact-recovery-message cofx public-key))) (contact-recovery/handle-recovery cofx public-key)))
(handlers/register-handler-fx (handlers/register-handler-fx
:stickers/load-sticker-pack-success :stickers/load-sticker-pack-success

View File

@ -86,8 +86,6 @@
:data-store/tx [(data-store.installations/save updated-installation)]})) :data-store/tx [(data-store.installations/save updated-installation)]}))
(defn handle-bundles-added [{:keys [db] :as cofx} bundle] (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) (let [installation-id (:installationID bundle)
new-installation {:installation-id installation-id new-installation {:installation-id installation-id
:has-bundle? true}] :has-bundle? true}]
@ -100,7 +98,7 @@
(upsert-installation new-installation) (upsert-installation new-installation)
#(when-not (or (get-in % [:db :pairing/prompt-user-pop-up]) #(when-not (or (get-in % [:db :pairing/prompt-user-pop-up])
(= :installations (:view-id db))) (= :installations (:view-id db)))
(prompt-user-on-new-installation %)))))))) (prompt-user-on-new-installation %))))))
(defn sync-installation-account-message [{:keys [db]}] (defn sync-installation-account-message [{:keys [db]}]
(let [account (-> db (let [account (-> db
@ -191,7 +189,6 @@
(fx/defn send-sync-installation [cofx payload] (fx/defn send-sync-installation [cofx payload]
(let [{:keys [web3]} (:db cofx) (let [{:keys [web3]} (:db cofx)
current-public-key (accounts.db/current-public-key cofx)] current-public-key (accounts.db/current-public-key cofx)]
{:shh/send-direct-message {:shh/send-direct-message
[{:web3 web3 [{:web3 web3
:src current-public-key :src current-public-key
@ -199,10 +196,8 @@
:payload payload}]})) :payload payload}]}))
(fx/defn send-installation-message-fx [cofx payload] (fx/defn send-installation-message-fx [cofx payload]
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])] (when (has-paired-installations? cofx)
(when (and (config/pairing-enabled? dev-mode?) (protocol/send payload nil cofx)))
(has-paired-installations? cofx))
(protocol/send payload nil cofx))))
(fx/defn sync-public-chat [cofx chat-id] (fx/defn sync-public-chat [cofx chat-id]
(let [sync-message (transport.pairing/SyncInstallation. {} {} {:public? true (let [sync-message (transport.pairing/SyncInstallation. {} {} {:public? true
@ -230,9 +225,7 @@
contacts)) contacts))
(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account chat]} sender] (defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account chat]} sender]
(let [dev-mode? (get-in db [:account/account :dev-mode?])] (when (= sender (accounts.db/current-public-key cofx))
(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)) (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))
new-account (merge-account (:account/account db) account)] new-account (merge-account (:account/account db) account)]
(fx/merge cofx (fx/merge cofx
@ -242,18 +235,16 @@
:data-store/base-tx [(data-store.accounts/save-account-tx 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))]} :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]}
#(when (:public? chat) #(when (:public? chat)
(models.chat/start-public-chat % (:chat-id chat) {:dont-navigate? true}))))))) (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] (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 (= sender (accounts.db/current-public-key cofx))
(when (and (config/pairing-enabled? dev-mode?)
(= sender (accounts.db/current-public-key cofx))
(not= (get-in db [:account/account :installation-id]) installation-id)) (not= (get-in db [:account/account :installation-id]) installation-id))
(let [installation {:installation-id installation-id (let [installation {:installation-id installation-id
:name name :name name
:device-type device-type :device-type device-type
:last-paired timestamp}] :last-paired timestamp}]
(upsert-installation cofx installation))))) (upsert-installation cofx installation))))
(fx/defn set-name [{:keys [db] :as cofx} installation-name] (fx/defn set-name [{:keys [db] :as cofx} installation-name]
(let [new-account (assoc (get-in cofx [:db :account/account]) :installation-name installation-name)] (let [new-account (assoc (get-in cofx [:db :account/account]) :installation-name installation-name)]

View File

@ -71,6 +71,6 @@
"mailserver.request.completed" (mailserver/handle-request-completed cofx event) "mailserver.request.completed" (mailserver/handle-request-completed cofx event)
"mailserver.request.expired" (when (accounts.db/logged-in? cofx) "mailserver.request.expired" (when (accounts.db/logged-in? cofx)
(mailserver/resend-request cofx {:request-id (:hash event)})) (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) "discovery.summary" (summary cofx event)
(log/debug "Event " type " not handled")))) (log/debug "Event " type " not handled"))))

View File

@ -90,8 +90,7 @@
(defrecord Message [content content-type message-type clock-value timestamp] (defrecord Message [content content-type message-type clock-value timestamp]
StatusMessage StatusMessage
(send [this chat-id {:keys [message-id] :as cofx}] (send [this chat-id {:keys [message-id] :as cofx}]
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?]) (let [current-public-key (accounts.db/current-public-key cofx)
current-public-key (accounts.db/current-public-key cofx)
params {:chat-id chat-id params {:chat-id chat-id
:payload this :payload this
:success-event [:transport/message-sent :success-event [:transport/message-sent
@ -104,8 +103,7 @@
:user-message :user-message
(fx/merge cofx (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))))) (send-with-pubkey params)))))
(receive [this chat-id signature _ cofx] (receive [this chat-id signature _ cofx]
{:chat-received-message/add-fx {:chat-received-message/add-fx

View File

@ -203,11 +203,10 @@
installation-id [:pairing/installation-id] installation-id [:pairing/installation-id]
installation-name [:pairing/installation-name]] installation-name [:pairing/installation-name]]
[react/scroll-view [react/scroll-view
(when (config/pairing-enabled? true)
(installations-section (installations-section
installation-id installation-id
installation-name installation-name
installations))])) installations)]))
(views/defview backup-recovery-phrase [] (views/defview backup-recovery-phrase []
[profile.recovery/backup-seed]) [profile.recovery/backup-seed])

View File

@ -140,7 +140,7 @@
(if utils.platform/ios? (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). ;; 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 ;; Replacing therefore with checkbox until I have more time to investigate
(checkbox.views/plain-checkbox {:checked? enabled? (checkbox.views/checkbox {:checked? enabled?
:on-value-change (partial toggle-enabled! installation-id enabled?)}) :on-value-change (partial toggle-enabled! installation-id enabled?)})
[react/switch {:on-tint-color colors/blue [react/switch {:on-tint-color colors/blue
:value enabled? :value enabled?

View File

@ -127,13 +127,11 @@
{:label-kw :t/backup-your-recovery-phrase {:label-kw :t/backup-your-recovery-phrase
:action-fn #(re-frame/dispatch [:navigate-to :backup-seed]) :action-fn #(re-frame/dispatch [:navigate-to :backup-seed])
:icon-content [components.common/counter {:size 22} 1]}]) :icon-content [components.common/counter {:size 22} 1]}])
(when (config/pairing-enabled? dev-mode?) [profile.components/settings-item-separator]
[profile.components/settings-item-separator])
(when (config/pairing-enabled? dev-mode?)
[profile.components/settings-item [profile.components/settings-item
{:label-kw :t/devices {:label-kw :t/devices
:action-fn #(re-frame/dispatch [:navigate-to :installations]) :action-fn #(re-frame/dispatch [:navigate-to :installations])
:accessibility-label :pairing-settings-button}]) :accessibility-label :pairing-settings-button}]
[profile.components/settings-item-separator] [profile.components/settings-item-separator]
[profile.components/settings-switch-item [profile.components/settings-switch-item
{:label-kw :t/web3-opt-in {:label-kw :t/web3-opt-in

View File

@ -18,9 +18,7 @@
(def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1"))) (def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1")))
(def rpc-networks-only? (enabled? (get-config :RPC_NETWORKS_ONLY "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"))) (def group-chats-publish-to-topic? (enabled? (get-config :GROUP_CHATS_PUBLISH_TO_TOPIC "0")))
(defn pairing-enabled? [dev-mode?] (def show-contact-recovery-pop-up? (enabled? (get-config :SHOW_CONTACT_RECOVERY_POPUP)))
(and (enabled? (get-config :PAIRING_ENABLED "0"))
(or dev-mode? platform/desktop?)))
(def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED))) (def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED)))
(def mainnet-warning-enabled? (enabled? (get-config :MAINNET_WARNING_ENABLED 0))) (def mainnet-warning-enabled? (enabled? (get-config :MAINNET_WARNING_ENABLED 0)))
(def pfs-encryption-enabled? (enabled? (get-config :PFS_ENCRYPTION_ENABLED "0"))) (def pfs-encryption-enabled? (enabled? (get-config :PFS_ENCRYPTION_ENABLED "0")))

View File

@ -1,38 +1,29 @@
(ns status-im.test.contact-recovery.core (ns status-im.test.contact-recovery.core
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.config :as config]
[status-im.contact-recovery.core :as contact-recovery])) [status-im.contact-recovery.core :as contact-recovery]))
(deftest show-contact-recovery-fx (deftest show-contact-recovery-fx
(let [public-key "pk"] (let [public-key "pk"]
(testing "pfs is not enabled" (testing "no contact-recovery in place"
(testing "no pop up is displayed" (let [cofx {:now "now"
(let [actual (contact-recovery/show-contact-recovery-fx {:db {:contact-recovery/pop-up #{}}} public-key)] :db {:contact-recovery/pop-up #{}
(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 #{}
:account/account {:settings {:pfs? true}}}} :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" (testing "it sets the pop up as displayed"
(is (get-in actual [:db :contact-recovery/pop-up public-key]))) (is (get-in actual [:db :contact-recovery/pop-up public-key])))
(testing "it adds an fx for fetching the contact" (testing "it adds an fx for fetching the contact"
(is (= public-key (:contact-recovery/show-contact-recovery-message actual)))))) (is (= ["now" public-key] (:contact-recovery/handle-recovery actual))))))
(testing "pop up is already displayed" (testing "contact recovery is in place"
(let [actual (contact-recovery/show-contact-recovery-fx {:db {:contact-recovery/pop-up #{public-key}}} public-key)] (let [actual (contact-recovery/handle-contact-recovery-fx {:db {:contact-recovery/pop-up #{public-key}}} public-key)]
(testing "it does nothing" (testing "it does nothing"
(is (not (:db actual))) (is (not (:db actual)))
(is (not (:contact-recovery/show-contact-recovery-message actual)))))))) (is (not (:contact-recovery/show-contact-recovery-message actual))))))))
(deftest show-contact-recovery-message (deftest show-contact-recovery-message
(let [public-key "pk"] (let [public-key "pk"]
(testing "pfs is enabled" (with-redefs [config/show-contact-recovery-pop-up? true]
(let [cofx {:db {:account/account {:settings {:pfs? true}}}} (let [cofx {:db {}}
actual (contact-recovery/show-contact-recovery-message cofx public-key)] actual (contact-recovery/handle-recovery cofx public-key)]
(testing "it shows a pop up" (testing "it shows a pop up"
(is (:ui/show-confirmation actual))))) (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))))))))

View File

@ -15,9 +15,10 @@
"syncing-disabled": "Syncing disabled", "syncing-disabled": "Syncing disabled",
"sync-all-devices": "Sync all devices", "sync-all-devices": "Sync all devices",
"syncing-devices": "Syncing...", "syncing-devices": "Syncing...",
"notify": "Notify",
"paired-devices": "Paired devices", "paired-devices": "Paired devices",
"contact-recovery-title": "{{name}} has sent you a message", "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-title": "Max number of devices reached",
"pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.", "pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.",
"pairing-new-installation-detected-title": "New device detected", "pairing-new-installation-detected-title": "New device detected",