Show popup when device is not targeted.

When someone is sending a pfs message to us but did not include our own
device, a pop up is shown propmting the user to connect with the user.

The reason for receiving messages that are not targeting our devices are
various:

1) The account was just recovered (which means it is a new installation
id)
2) More than 3 devices are in use (we only keep max 3 devices in sync)
3) The sender has used an old bundle which does not include the current
device

Eventually we will reduce the likelihood of this scenario happening, but
we can't dismiss it completely.

It's only enabled when PFS is enabled.

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-12-27 13:12:54 +01:00
parent ad99ed2a28
commit b56fd2e29f
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
11 changed files with 215 additions and 51 deletions

View File

@ -0,0 +1,63 @@
(ns status-im.contact-recovery.core
(: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.fx :as fx]
[status-im.accounts.db :as accounts.db]
[status-im.contact.core :as models.contact]))
(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?])]
(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})))
(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 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])))
(re-frame/reg-fx
:contact-recovery/show-contact-recovery-message
show-contact-recovery-message-fx)
(fx/defn save-contact-recovery [{:keys [now]} public-key]
{:data-store/tx [(data-store.contact-recovery/save-contact-recovery-tx {:timestamp now
:id public-key})]})
(fx/defn prompt-accepted [cofx public-key]
(fx/merge
cofx
(prompt-dismissed public-key)
(save-contact-recovery 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)
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)
: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))))

View File

@ -0,0 +1,14 @@
(ns status-im.data-store.contact-recovery
(: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)))
(defn save-contact-recovery-tx
"Returns tx function for saving a contact-recovery"
[contact-recovery]
(fn [realm]
(core/create realm
:contact-recovery
contact-recovery
true)))

View File

@ -0,0 +1,7 @@
(ns status-im.data-store.realm.schemas.account.contact-recovery)
(def v1 {:name :contact-recovery
:primaryKey :id
:properties {:id :string
:timestamp {:type :int
:optional true}}})

View File

@ -13,6 +13,7 @@
[status-im.data-store.realm.schemas.account.request :as request] [status-im.data-store.realm.schemas.account.request :as request]
[status-im.data-store.realm.schemas.account.membership-update :as membership-update] [status-im.data-store.realm.schemas.account.membership-update :as membership-update]
[status-im.data-store.realm.schemas.account.installation :as installation] [status-im.data-store.realm.schemas.account.installation :as installation]
[status-im.data-store.realm.schemas.account.contact-recovery :as contact-recovery]
[status-im.data-store.realm.schemas.account.migrations :as migrations] [status-im.data-store.realm.schemas.account.migrations :as migrations]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
@ -343,6 +344,20 @@
browser/v8 browser/v8
dapp-permissions/v9]) dapp-permissions/v9])
(def v32 [chat/v13
transport/v7
contact/v3
message/v9
mailserver/v11
mailserver-topic/v1
user-status/v2
membership-update/v1
installation/v2
local-storage/v1
browser/v8
dapp-permissions/v9
contact-recovery/v1])
;; put schemas ordered by version ;; put schemas ordered by version
(def schemas [{:schema v1 (def schemas [{:schema v1
:schemaVersion 1 :schemaVersion 1
@ -436,4 +451,7 @@
:migration migrations/v30} :migration migrations/v30}
{:schema v31 {:schema v31
:schemaVersion 31 :schemaVersion 31
:migration (constantly nil)}
{:schema v32
:schemaVersion 32
:migration (constantly nil)}]) :migration (constantly nil)}])

View File

@ -16,6 +16,7 @@
[status-im.chat.models.loading :as chat.loading] [status-im.chat.models.loading :as chat.loading]
[status-im.chat.models.message :as chat.message] [status-im.chat.models.message :as chat.message]
[status-im.contact.core :as contact] [status-im.contact.core :as contact]
[status-im.contact-recovery.core :as contact-recovery]
[status-im.data-store.core :as data-store] [status-im.data-store.core :as data-store]
[status-im.extensions.core :as extensions] [status-im.extensions.core :as extensions]
[status-im.extensions.registry :as extensions.registry] [status-im.extensions.registry :as extensions.registry]
@ -1404,3 +1405,25 @@
:pairing.callback/disable-installation-success :pairing.callback/disable-installation-success
(fn [cofx [_ installation-id]] (fn [cofx [_ installation-id]]
(pairing/disable cofx installation-id))) (pairing/disable cofx installation-id)))
;; Contact recovery module
(handlers/register-handler-fx
: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))))
(handlers/register-handler-fx
:contact-recovery.ui/prompt-dismissed
(fn [cofx [_ public-key]]
(contact-recovery/prompt-dismissed cofx public-key)))
(handlers/register-handler-fx
:contact-recovery.callback/show-contact-recovery-message
[(re-frame/inject-cofx :random-id-generator)]
(fn [cofx [_ public-key]]
(contact-recovery/show-contact-recovery-message cofx public-key)))

View File

@ -4,6 +4,7 @@
[status-im.init.core :as init] [status-im.init.core :as init]
[status-im.node.core :as node] [status-im.node.core :as node]
[status-im.pairing.core :as pairing] [status-im.pairing.core :as pairing]
[status-im.contact-recovery.core :as contact-recovery]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.transport.message.core :as transport.message] [status-im.transport.message.core :as transport.message]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
@ -75,5 +76,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))
"discovery.summary" (summary cofx event) "discovery.summary" (summary cofx event)
(log/debug "Event " type " not handled")))) (log/debug "Event " type " not handled"))))

View File

@ -35,19 +35,36 @@
:resend? resend? :resend? resend?
:now now}))}) :now now}))})
(defn send-public-message
"Sends the payload to topic"
[{:keys [db] :as cofx} chat-id success-event payload]
(let [{:keys [web3]} db]
{:shh/send-public-message [{:web3 web3
:success-event success-event
:src (accounts.db/current-public-key cofx)
:chat chat-id
:payload payload}]}))
(fx/defn send-with-sym-key (fx/defn send-with-sym-key
"Sends the payload using symetric key and topic from db (looked up by `chat-id`)" "Sends the payload using symetric key and topic from db (looked up by `chat-id`)"
[{:keys [db] :as cofx} {:keys [payload chat-id success-event]}] [{:keys [db] :as cofx} {:keys [payload chat-id success-event]}]
;; we assume that the chat contains the contact public-key ;; we assume that the chat contains the contact public-key
(let [{:keys [web3]} db (let [{:keys [web3]} db
{:keys [sym-key-id topic]} (get-in db [:transport/chats chat-id])] {:keys [sym-key-id topic]} (get-in db [:transport/chats chat-id])
{:shh/post [{:web3 web3 pfs? (get-in db [:account/account :settings :pfs?])]
:success-event success-event (if pfs?
:message (merge {:sig (accounts.db/current-public-key cofx) (send-public-message
:symKeyID sym-key-id cofx
:payload payload chat-id
:topic topic} success-event
whisper-opts)}]})) payload)
{:shh/post [{:web3 web3
:success-event success-event
:message (merge {:sig (accounts.db/current-public-key cofx)
:symKeyID sym-key-id
:payload payload
:topic topic}
whisper-opts)}]})))
(fx/defn send-direct-message (fx/defn send-direct-message
"Sends the payload using to dst" "Sends the payload using to dst"
@ -59,33 +76,28 @@
:dst dst :dst dst
:payload payload}]})) :payload payload}]}))
(defn send-public-message
"Sends the payload to topic"
[{:keys [db] :as cofx} chat-id success-event payload]
(let [{:keys [web3]} db]
{:shh/send-public-message [{:web3 web3
:success-event success-event
:src (accounts.db/current-public-key cofx)
:chat chat-id
:payload payload}]}))
(fx/defn send-with-pubkey (fx/defn send-with-pubkey
"Sends the payload using asymetric key (account `:public-key` in db) and fixed discovery topic" "Sends the payload using asymetric key (account `:public-key` in db) and fixed discovery topic"
[{:keys [db] :as cofx} {:keys [payload chat-id success-event]}] [{:keys [db] :as cofx} {:keys [payload chat-id success-event]}]
(let [{:keys [web3]} db] (let [{:keys [web3]} db]
{:shh/post [{:web3 web3 (let [pfs? (get-in db [:account/account :settings :pfs?])]
:success-event success-event (if pfs?
:message (merge {:sig (accounts.db/current-public-key cofx) (send-direct-message cofx
:pubKey chat-id chat-id
:payload payload success-event
:topic (transport.utils/get-topic constants/contact-discovery)} payload)
whisper-opts)}]})) {:shh/post [{:web3 web3
:success-event success-event
:message (merge {:sig (accounts.db/current-public-key cofx)
:pubKey chat-id
:payload payload
:topic (transport.utils/get-topic constants/contact-discovery)}
whisper-opts)}]}))))
(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 [dev-mode? (get-in cofx [:db :account/account :dev-mode?])
pfs? (get-in cofx [:db :account/account :settings :pfs?])
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
@ -95,25 +107,13 @@
message-type]}] message-type]}]
(case message-type (case message-type
:public-group-user-message :public-group-user-message
(if pfs? (send-with-sym-key cofx params)
(send-public-message
cofx
chat-id
(:success-event params)
this)
(send-with-sym-key cofx params))
:user-message :user-message
(if pfs? (fx/merge cofx
(send-direct-message #(when (config/pairing-enabled? dev-mode?)
cofx (send-direct-message % current-public-key nil this))
chat-id (send-with-pubkey params)))))
(:success-event params)
this)
(fx/merge cofx
#(when (config/pairing-enabled? dev-mode?)
(send-direct-message % current-public-key nil this))
(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
[(assoc (into {} this) [(assoc (into {} this)
@ -134,14 +134,8 @@
(defrecord MessagesSeen [message-ids] (defrecord MessagesSeen [message-ids]
StatusMessage StatusMessage
(send [this chat-id cofx] (send [this chat-id cofx]
(let [pfs? (get-in cofx [:db :account/account :settings :pfs?])] (send-with-pubkey cofx {:chat-id chat-id
(if pfs? :payload this}))
(send-direct-message cofx
chat-id
nil
this)
(send-with-pubkey cofx {:chat-id chat-id
:payload this}))))
(receive [this chat-id signature _ cofx] (receive [this chat-id signature _ cofx]
(chat/receive-seen cofx chat-id signature this)) (chat/receive-seen cofx chat-id signature this))
(validate [this] (validate [this]

View File

@ -24,6 +24,7 @@
:navigation-stack '() :navigation-stack '()
:contacts/contacts {} :contacts/contacts {}
:pairing/installations {} :pairing/installations {}
:contact-recovery/pop-up #{}
:qr-codes {} :qr-codes {}
:group/selected-contacts #{} :group/selected-contacts #{}
:chats {} :chats {}

View File

@ -0,0 +1,38 @@
(ns status-im.test.contact-recovery.core
(:require [cljs.test :refer-macros [deftest is testing]]
[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 #{}
:account/account {:settings {:pfs? true}}}}
actual (contact-recovery/show-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)]
(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)]
(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))))))))

View File

@ -56,6 +56,7 @@
[status-im.test.utils.fx] [status-im.test.utils.fx]
[status-im.test.accounts.recover.core] [status-im.test.accounts.recover.core]
[status-im.test.hardwallet.core] [status-im.test.hardwallet.core]
[status-im.test.contact-recovery.core]
[status-im.test.ui.screens.currency-settings.models] [status-im.test.ui.screens.currency-settings.models]
[status-im.test.ui.screens.wallet.db])) [status-im.test.ui.screens.wallet.db]))
@ -124,5 +125,6 @@
'status-im.test.ui.screens.currency-settings.models 'status-im.test.ui.screens.currency-settings.models
'status-im.test.ui.screens.wallet.db 'status-im.test.ui.screens.wallet.db
'status-im.test.browser.core 'status-im.test.browser.core
'status-im.test.contact-recovery.core
'status-im.test.extensions.ethereum 'status-im.test.extensions.ethereum
'status-im.test.browser.permissions) 'status-im.test.browser.permissions)

View File

@ -15,6 +15,8 @@
"sync-all-devices": "Sync all devices", "sync-all-devices": "Sync all devices",
"syncing-devices": "Syncing...", "syncing-devices": "Syncing...",
"paired-devices": "Paired devices", "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.",
"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",