Propagate signed membership information

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-07-19 17:51:06 +02:00
parent e89628ce3a
commit 165f1a46c7
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
39 changed files with 527 additions and 185 deletions

3
.env
View File

@ -12,9 +12,8 @@ TESTFAIRY_TOKEN=969f6c921cb435cea1d41d1ea3f5b247d6026d55
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0
GROUP_CHATS_ENABLED=1
CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=1
HARDWALLET_ENABLED=0
PFS_ENCRYPTION_ENABLED=0
GROUP_CHATS_ENABLED=0

View File

@ -11,7 +11,7 @@ POW_TIME=1
DEFAULT_NETWORK=mainnet_rpc
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=0
GROUP_CHATS_ENABLED=1
MAINNET_WARNING_ENABLED=1
CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=0

View File

@ -10,7 +10,7 @@ POW_TIME=1
DEFAULT_NETWORK=mainnet_rpc
INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=0
GROUP_CHATS_ENABLED=1
MAINNET_WARNING_ENABLED=1
EXTENSIONS=1
PFS_ENCRYPTION_ENABLED=0

View File

@ -13795,7 +13795,7 @@
}
},
"web3": {
"version": "github:status-im/web3.js#ff09ce57e9574a1c42138832a7d6bf6c2e699048",
"version": "git+https://github.com/status-im/web3.js.git#b1c2e2b75f6a190b320dda4be7931d3680ecb727",
"requires": {
"bignumber.js": "github:status-im/bignumber.js#cc066a0a3d6bfe0c436c9957f4ea8344bf963c89",
"crypto-js": "3.1.8",

View File

@ -85,7 +85,7 @@
"string_decoder": "0.10.31",
"text-encoding": "^0.6.4",
"url": "0.10.3",
"web3": "github:status-im/web3.js#feature/shhext",
"web3": "https://github.com/status-im/web3.js.git#feature/chat-api",
"web3-utils": "1.0.0-beta.36"
},
"devDependencies": {

View File

@ -8456,7 +8456,7 @@
},
"react-native-tab-view": {
"version": "0.0.77",
"resolved": "http://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz",
"integrity": "sha512-9vjD4Ly1Zlum1Y4g23ODpi/F3gYIUIsKWrsZO/Oh5cuX1eiB1DRVn11nY1z+j/hsQfhfyW6nDlmySyDvYQvYCA==",
"requires": {
"prop-types": "15.6.0"
@ -10505,7 +10505,7 @@
}
},
"web3": {
"version": "git+https://github.com/status-im/web3.js.git#ff09ce57e9574a1c42138832a7d6bf6c2e699048",
"version": "git+https://github.com/status-im/web3.js.git#b1c2e2b75f6a190b320dda4be7931d3680ecb727",
"requires": {
"bignumber.js": "github:status-im/bignumber.js#cc066a0a3d6bfe0c436c9957f4ea8344bf963c89",
"crypto-js": "3.1.8",

View File

@ -68,7 +68,7 @@
"string_decoder": "0.10.31",
"text-encoding": "^0.6.4",
"url": "0.10.3",
"web3": "https://github.com/status-im/web3.js.git#feature/shhext",
"web3": "https://github.com/status-im/web3.js.git#feature/chat-api",
"web3-utils": "1.0.0-beta.36"
}
}

View File

@ -16,7 +16,7 @@ dependencies {
implementation 'com.instabug.library:instabug:3+'
implementation 'status-im:function:0.0.1'
String statusGoVersion = 'v0.15.0'
String statusGoVersion = 'v0.16.0-1-gf3880f8f'
final String statusGoGroup = 'status-im', statusGoName = 'status-go'
// Check if the local status-go jar exists, and compile against that if it does

View File

@ -693,6 +693,47 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
return android.text.format.DateFormat.is24HourFormat(this.reactContext.getApplicationContext());
}
@ReactMethod
public void verifyGroupMembershipSignatures(final String signaturePairs, final Callback callback) {
Log.d(TAG, "verifyGroupMembershipSignatures");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String result = Statusgo.VerifyGroupMembershipSignatures(signaturePairs);
callback.invoke(result);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void signGroupMembership(final String content, final Callback callback) {
Log.d(TAG, "signGroupMembership");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String result = Statusgo.SignGroupMembership(content);
callback.invoke(result);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
@Override
public @Nullable
Map<String, Object> getConstants() {

View File

@ -37,7 +37,8 @@ ExternalProject_Add(StatusGo_ep
PREFIX ${StatusGo_PREFIX}
SOURCE_DIR ${StatusGo_SOURCE_DIR}
GIT_REPOSITORY https://github.com/status-im/status-go.git
GIT_TAG v0.15.0
GIT_TAG f3880f8fe1f11e2cd59382c34dd826ebbf9662cf
BUILD_BYPRODUCTS ${StatusGo_STATIC_LIB}
CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/${CONFIGURE_SCRIPT} ${GO_ROOT_PATH} ${StatusGo_ROOT} ${StatusGo_SOURCE_DIR}
BUILD_COMMAND ""

View File

@ -193,6 +193,25 @@ void RCTStatus::signMessage(QString rpcParams, double callbackId) {
}, rpcParams, callbackId);
}
void RCTStatus::signGroupMembership(QString content, double callbackId) {
Q_D(RCTStatus);
qDebug() << "call of RCTStatus::signGroupMembership with param callbackId: " << callbackId;
QtConcurrent::run([&](QString content, double callbackId) {
const char* result = SignGroupMembership(content.toUtf8().data());
qDebug() << "RCTStatus::signGroupMembership SignGroupMembership result: " << statusGoResultError(result);
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
}, content, callbackId);
}
void RCTStatus::verifyGroupMembershipSignatures(QString signatures, double callbackId) {
Q_D(RCTStatus);
qDebug() << "call of RCTStatus::verifyGroupMembershipSignatures with param callbackId: " << callbackId;
QtConcurrent::run([&](QString signatures, double callbackId) {
const char* result = VerifyGroupMembershipSignatures(signatures.toUtf8().data());
qDebug() << "RCTStatus::verifyGroupMembershipSignatures VerifyGroupMembershipSignatures result: " << statusGoResultError(result);
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
}, signatures, callbackId);
}
void RCTStatus::setAdjustResize() {
}

View File

@ -41,6 +41,8 @@ public:
Q_INVOKABLE void login(QString address, QString password, double callbackId);
Q_INVOKABLE void sendTransaction(QString txArgsJSON, QString password, double callbackId);
Q_INVOKABLE void signMessage(QString rpcParams, double callbackId);
Q_INVOKABLE void signGroupMembership(QString content, double callbackId);
Q_INVOKABLE void verifyGroupMembershipSignatures(QString signatures, double callbackId);
Q_INVOKABLE void setAdjustResize();
Q_INVOKABLE void setAdjustPan();

View File

@ -253,6 +253,30 @@ RCT_EXPORT_METHOD(signMessage:(NSString *)message
callback(@[[NSString stringWithUTF8String: result]]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - SignGroupMembership
//////////////////////////////////////////////////////////////////// signGroupMembership
RCT_EXPORT_METHOD(signGroupMembership:(NSString *)content
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"SignGroupMembership() method called");
#endif
char * result = SignGroupMembership((char *) [content UTF8String]);
callback(@[[NSString stringWithUTF8String: result]]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - VerifyGroupMembershipSignatures
//////////////////////////////////////////////////////////////////// verifyGroupMembershipSignatures
RCT_EXPORT_METHOD(verifyGroupMembershipSignatures:(NSString *)content
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"VerifyGroupMembershipSignatures() method called");
#endif
char * result = VerifyGroupMembershipSignatures((char *) [content UTF8String]);
callback(@[[NSString stringWithUTF8String: result]]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - only android methods
////////////////////////////////////////////////////////////////////

View File

@ -25,7 +25,7 @@
<artifactItem>
<groupId>status-im</groupId>
<artifactId>status-go-ios-simulator</artifactId>
<version>v0.15.0</version>
<version>v0.16.0-1-gf3880f8f</version>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>./</outputDirectory>

View File

@ -213,7 +213,7 @@
(fx/merge cofx
(add-public-chat topic)
(navigate-to-chat topic {:modal? modal?
:navigation-replace? true})
:navigation-reset? true})
(public-chat/join-public-chat topic)))
(fx/defn disable-chat-cooldown

View File

@ -32,40 +32,6 @@
(seq removed-participants)
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
(fx/defn handle-group-admin-update [{:keys [now db random-id-generator] :as cofx}
{:keys [chat-name participants]} chat-id signature]
(let [me (:current-public-key db)
system-message-id (random-id-generator)]
;; we have to check if we already have a chat, or it's a new one
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
;; update for existing group chat
(when (and (= signature group-admin) ;; make sure that admin is the one making changes
(not= (set contacts) (set participants))) ;; make sure it's actually changing something
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
admin-name (or (get-in db [:contacts/contacts group-admin :name])
group-admin)]
(if (removed me) ;; we were removed
(fx/merge cofx
(models.message/receive
(models.message/system-message chat-id system-message-id now
(str admin-name " " (i18n/label :t/removed-from-chat))))
(models.chat/upsert-chat {:chat-id chat-id
:removed-from-at now
:is-active false})
(transport.utils/unsubscribe-from-chat chat-id))
(fx/merge cofx
(models.message/receive
(models.message/system-message chat-id system-message-id now
(prepare-system-message admin-name
added
removed
(:contacts/contacts db))))
(group/participants-added chat-id added)
(group/participants-removed chat-id removed)))))
;; first time we hear about chat -> create it if we are among participants
(when (get (set participants) me)
(models.chat/add-group-chat cofx chat-id chat-name signature participants)))))
(fx/defn handle-group-leave
[{:keys [db random-id-generator now] :as cofx} chat-id signature]
(let [me (:current-public-key db)
@ -77,9 +43,9 @@
(get-in db [:chats chat-id])) ;; chat is present
(fx/merge cofx
(models.message/receive
(models.message/system-message chat-id system-message-id now
(str participant-leaving-name " " (i18n/label :t/left))))
#_(models.message/receive
(models.message/system-message chat-id random-id now
(str participant-leaving-name " " (i18n/label :t/left))))
(group/participants-removed chat-id #{signature})))))
(defn- group-name-from-contacts [selected-contacts all-contacts username]
@ -87,20 +53,3 @@
(map (comp :name (partial get all-contacts)))
(cons username)
(string/join ", ")))
(fx/defn send-group-update [cofx group-update chat-id]
(transport/send group-update chat-id cofx))
(fx/defn start-group-chat
"Starts a new group chat"
[{:keys [db random-id-generator] :as cofx} group-name]
(let [my-public-key (:current-public-key db)
chat-id (random-id-generator)
selected-contacts (conj (:group/selected-contacts db)
my-public-key)
group-update (transport.message/GroupMembershipUpdate. chat-id group-name my-public-key selected-contacts nil nil nil)]
(fx/merge cofx
{:db (assoc db :group/selected-contacts #{})}
(models.chat/navigate-to-chat chat-id {})
(group-chat/handle-membership-update group-update my-public-key)
(send-group-update group-update chat-id))))

View File

@ -150,15 +150,6 @@
(not (= constants/system from))
(not (:outgoing message)))))))
(fx/defn receive
[{:keys [now] :as cofx} {:keys [chat-id message-id] :as message}]
(fx/merge cofx
(chat-model/upsert-chat {:chat-id chat-id
;; We activate a chat again on new messages
:is-active true
:timestamp now})
(add-received-message false message)))
(fx/defn update-group-messages [cofx chat->message chat-id]
(fx/merge cofx
(re-index-message-groups chat-id)
@ -185,9 +176,19 @@
:accumulated []}
messages)))
(defn valid-chat-id? [cofx {:keys [chat-id from message-type]}]
"Validate chat-id and message-type"
(case message-type
:group-user-message (get-in cofx [:db :chats chat-id :contacts from])
:public-group-user-message (get-in cofx [:db :chats chat-id :public?])
:user-message (or (= (get-in cofx [:db :current-public-key]) from)
(= chat-id from))
false))
(fx/defn receive-many
[{:keys [now] :as cofx} messages]
(let [deduped-messages (filter-messages cofx messages)
(let [valid-messages (filter (partial valid-chat-id? cofx) messages)
deduped-messages (filter-messages cofx valid-messages)
chat->message (group-by :chat-id deduped-messages)
chat-ids (keys chat->message)
chats-fx-fns (map #(chat-model/upsert-chat {:chat-id %

View File

@ -92,3 +92,37 @@
:default false}
:public? {:type :bool
:default false}}})
(def v6 {:name :chat
:primaryKey :chat-id
:properties {:chat-id :string
:name :string
:color {:type :string
:default default-chat-color}
:group-chat {:type :bool
:indexed true}
:group-admin {:type :string
:optional true}
:is-active :bool
:timestamp :int
:contacts {:type "string[]"}
:removed-at {:type :int
:optional true}
:removed-from-at {:type :int
:optional true}
:deleted-at-clock-value {:type :int
:optional true}
:added-to-at {:type :int
:optional true}
:updated-at {:type :int
:optional true}
:message-overhead {:type :int
:default 0}
:membership-version {:type :int
:optional true}
:membership-signature {:type :string
:optional true}
:debug? {:type :bool
:default false}
:public? {:type :bool
:default false}}})

View File

@ -132,6 +132,16 @@
browser/v8
dapp-permissions/v9])
(def v13 [chat/v6
transport/v6
contact/v1
message/v7
mailserver/v11
user-status/v1
local-storage/v1
browser/v8
dapp-permissions/v9])
;; put schemas ordered by version
(def schemas [{:schema v1
:schemaVersion 1
@ -168,4 +178,7 @@
:migration migrations/v11}
{:schema v12
:schemaVersion 12
:migration migrations/v12}])
:migration migrations/v12}
{:schema v13
:schemaVersion 13
:migration migrations/v13}])

View File

@ -84,3 +84,5 @@
new-content {:text content}]
(aset message "content" (pr-str new-content)))))))
(defn v13 [old-realm new-realm]
(log/debug "migrating v13 account database"))

View File

@ -17,6 +17,7 @@
[status-im.chat.commands.input :as commands.input]
[status-im.data-store.core :as data-store]
[status-im.fleet.core :as fleet]
[status-im.group-chats.core :as group-chats]
[status-im.hardwallet.core :as hardwallet]
[status-im.i18n :as i18n]
[status-im.init.core :as init]
@ -504,11 +505,6 @@
(fn [cofx [_ topic modal?]]
(chat/start-public-chat cofx topic modal?)))
(handlers/register-handler-fx
:chat.ui/start-group-chat
(fn [cofx [_ group-name]]
(chat.group/start-group-chat cofx group-name)))
(handlers/register-handler-fx
:chat.ui/remove-chat
(fn [cofx [_ chat-id]]
@ -844,3 +840,32 @@
:browser.ui/open-modal-chat-button-pressed
(fn [cofx [_ host]]
(browser/open-chat-from-browser cofx host)))
;; group-chats module
(handlers/register-handler-fx
:group-chats.ui/create-pressed
[(re-frame/inject-cofx :random-guid-generator)]
(fn [cofx [_ chat-name]]
(group-chats/create cofx chat-name)))
(handlers/register-handler-fx
:group-chats.ui/name-changed
(fn [cofx [_ chat-name]]
(group-chats/handle-name-changed cofx chat-name)))
(handlers/register-handler-fx
:group-chats.ui/save-pressed
(fn [cofx _]
(group-chats/save cofx)))
(handlers/register-handler-fx
:group-chats.callback/sign-success
[(re-frame/inject-cofx :random-guid-generator)]
(fn [cofx [_ group-update]]
(group-chats/handle-sign-success cofx group-update)))
(handlers/register-handler-fx
:group-chats.callback/verify-signature-success
(fn [cofx [_ group-update sender-signature]]
(group-chats/handle-membership-update cofx group-update sender-signature)))

View File

@ -1,5 +1,9 @@
(ns status-im.group-chats.core
(:require [status-im.utils.config :as config]
(:require [clojure.string :as string]
[clojure.spec.alpha :as spec]
[re-frame.core :as re-frame]
[status-im.utils.config :as config]
[status-im.native-module.core :as native-module]
[status-im.transport.utils :as transport.utils]
[status-im.transport.db :as transport.db]
[status-im.transport.utils :as transport.utils]
@ -9,60 +13,199 @@
[status-im.utils.fx :as fx]
[status-im.chat.models :as models.chat]))
(defn wrap-group-message [cofx chat-id message]
(defn- parse-response [response-js]
(-> response-js
js/JSON.parse
(js->clj :keywordize-keys true)))
(defn signature-material [{:keys [chat-id admin participants]}]
(apply str
(concat (sort participants)
admin
chat-id)))
(defn signature-pairs [{:keys [admin signature] :as payload}]
(js/JSON.stringify (clj->js [[(signature-material payload)
signature
(subs admin 2)]])))
(defn valid-chat-id?
;; We need to make sure the chat-id ends with the admin pk (and it's not the same).
;; this is due to prevent an attack whereby a non-admin user would
;; send out a message with identical chat-id and themselves as admin to other members,
;; who would then have to trust the first of the two messages received, possibly
;; resulting in a situation where some of the members in the chat trust a different admin.
[chat-id admin]
(and (string/ends-with? chat-id admin)
(not= chat-id admin)))
(defn wrap-group-message
"Wrap a group message in a membership update"
[cofx chat-id message]
(when-let [chat (get-in cofx [:db :chats chat-id])]
(transport/GroupMembershipUpdate.
chat-id
(:name chat)
(:group-admin chat)
(:contacts chat)
nil
nil
message)))
(transport/map->GroupMembershipUpdate.
{:chat-id chat-id
:chat-name (:name chat)
:admin (:group-admin chat)
:participants (:contacts chat)
:signature (:membership-signature chat)
:version (:membership-version chat)
:message message})))
(fx/defn update-membership
"Upsert chat when version is greater or not existing"
[cofx previous-chat {:keys [chat-id
chat-name
participants
leaves
admin
signature
version]}]
(when
(or
(nil? previous-chat)
(< (:membership-version previous-chat)
version))
(defn update-membership [cofx previous-chat {:keys [chat-id chat-name participants leaves signature version]}]
(when (< (:membership-version previous-chat)
version)
(models.chat/upsert-chat cofx
{:chat-id chat-id
:membership-version version})))
{:chat-id chat-id
:name chat-name
:is-active (get previous-chat :is-active true)
:group-chat true
:group-admin admin
:contacts participants
:membership-signature signature
:membership-version version})))
(defn send-membership-update [cofx payload chat-id]
(defn send-membership-update
"Send a membership update to all participants but the sender"
[cofx payload chat-id]
(let [{:keys [participants]} payload
{:keys [current-public-key web3]} (:db cofx)]
(fx/merge
cofx
{:shh/send-group-message {:web3 web3
:src current-public-key
:dsts (disj participants current-public-key)
{:shh/send-group-message {:web3 web3
:src current-public-key
:dsts (disj participants current-public-key)
:success-event [:transport/set-message-envelope-hash
chat-id
(transport.utils/message-id (:message payload))
:group-user-message]
:payload payload}})))
:payload payload}})))
(defn handle-group-leave [payload chat-id cofx]
(defn send-group-leave [payload chat-id cofx]
(transport.protocol/send cofx
{:chat-id chat-id
:payload payload
:success-event [:group/unsubscribe-from-chat chat-id]}))
(fx/defn handle-membership-update [cofx {:keys [chat-id chat-name participants leaves message signature version] :as membership-update} sender-signature]
(when config/group-chats-enabled?
(let [chat-fx (if-let [previous-chat (get-in cofx [:db :chats chat-id])]
(update-membership cofx previous-chat membership-update)
(models.chat/upsert-chat
cofx
{:chat-id chat-id
:name chat-name
:is-active true
:group-chat true
:group-admin signature
:contacts participants
:membership-version version}))]
(if message
(fx/merge cofx
chat-fx
#(protocol.message/receive message chat-id sender-signature nil %))
chat-fx))))
(fx/defn handle-membership-update-received
"Verify signatures in status-go and act if successful"
[cofx membership-update signature]
{:group-chats/verify-membership-signature [[membership-update signature]]})
(fx/defn handle-membership-update
"Upsert chat and receive message if valid"
;; Care needs to be taken here as chat-id is not coming from a whisper filter
;; so can be manipulated by the sending user.
[cofx {:keys [chat-id
chat-name
participants
signature
leaves
message
admin
version] :as membership-update}
sender-signature]
(when (and config/group-chats-enabled?
(valid-chat-id? chat-id admin))
(let [previous-chat (get-in cofx [:db :chats chat-id])]
(fx/merge cofx
(update-membership previous-chat membership-update)
#(when (and message
;; don't allow anything but group messages
(instance? transport.protocol/Message message)
(= :group-user-message (:message-type message)))
(protocol.message/receive message chat-id sender-signature nil %))))))
(defn handle-sign-success
"Upsert chat and send signed payload to group members"
[{:keys [db] :as cofx} {:keys [chat-id] :as group-update}]
(let [my-public-key (:current-public-key db)]
(fx/merge cofx
(models.chat/navigate-to-chat chat-id {:navigation-reset? true})
(handle-membership-update group-update my-public-key)
#(protocol.message/send group-update chat-id %))))
(defn handle-sign-response
"Callback to dispatch on sign response"
[payload response-js]
(let [{:keys [error signature]} (parse-response response-js)]
(if error
(re-frame/dispatch [:group-chats.callback/sign-failed error])
(re-frame/dispatch [:group-chats.callback/sign-success (assoc payload :signature signature)]))))
(defn handle-verify-signature-response
"Callback to dispatch on verify signature response"
[payload sender-signature response-js]
(let [{:keys [error]} (parse-response response-js)]
(if error
(re-frame/dispatch [:group-chats.callback/verify-signature-failed error])
(re-frame/dispatch [:group-chats.callback/verify-signature-success payload sender-signature]))))
(defn sign-membership [payload]
(native-module/sign-group-membership (signature-material payload)
(partial handle-sign-response payload)))
(defn verify-membership-signature [signatures]
(doseq [[payload sender-signature] signatures]
(native-module/verify-group-membership-signatures (signature-pairs payload)
(partial handle-verify-signature-response payload sender-signature))))
(fx/defn create
"Format group update message and sign membership"
[{:keys [db random-guid-generator] :as cofx} group-name]
(let [my-public-key (:current-public-key db)
chat-id (str (random-guid-generator) my-public-key)
selected-contacts (conj (:group/selected-contacts db)
my-public-key)
group-update (transport/map->GroupMembershipUpdate
{:chat-id chat-id
:chat-name group-name
:admin my-public-key
:participants selected-contacts
:version 1})]
{:group-chats/sign-membership group-update
:db (assoc db :group/selected-contacts #{})}))
(defn- valid-name? [name]
(spec/valid? :profile/name name))
(fx/defn update-name [{:keys [db]} name]
{:db (-> db
(assoc-in [:group-chat-profile/profile :valid-name?] (valid-name? name))
(assoc-in [:group-chat-profile/profile :name] name))})
(fx/defn handle-name-changed
"Store name in profile scratchpad"
[cofx new-chat-name]
(update-name cofx new-chat-name))
(fx/defn save
"Save chat from edited profile"
[{:keys [db] :as cofx}]
(let [current-chat-id (get-in cofx [:db :current-chat-id])
new-name (get-in cofx [:db :group-chat-profile/profile :name])]
(when (valid-name? new-name)
(fx/merge cofx
{:db (assoc db :group-chat-profile/editing? false)}
(models.chat/upsert-chat {:chat-id current-chat-id
:name new-name})))))
(re-frame/reg-fx
:group-chats/sign-membership
sign-membership)
(re-frame/reg-fx
:group-chats/verify-membership-signature
(fn [signatures]
(verify-membership-signature signatures)))

View File

@ -59,3 +59,7 @@
(defn is24Hour []
(native-module/is24Hour))
(def verify-group-membership-signatures native-module/verify-group-membership-signatures)
(def sign-group-membership native-module/sign-group-membership)

View File

@ -134,6 +134,14 @@
(fn [UUID]
(callback (string/upper-case UUID))))))
(defn verify-group-membership-signatures [signature-pairs callback]
(when status
(call-module #(.verifyGroupMembershipSignatures status signature-pairs callback))))
(defn sign-group-membership [content callback]
(when status
(call-module #(.signGroupMembership status content callback))))
(defn is24Hour []
(when status
(.-is24Hour status)))

View File

@ -54,6 +54,7 @@
(defn- get-base-node-config [config]
(assoc config
:BackupDisabledDataDir (utils.platform/no-backup-directory)
:Name "StatusIM"))
(defn- pick-nodes
@ -94,7 +95,6 @@
:MinimumPoW 0.001
:EnableNTPSync true}
:RequireTopics (get-topics network)
:BackupDisabledDataDir (utils.platform/no-backup-directory)
:InstallationID installation-id
:PFSEnabled (or config/pfs-encryption-enabled?
config/group-chats-enabled?))

View File

@ -10,7 +10,7 @@
(extend-type transport.protocol/GroupMembershipUpdate
message/StatusMessage
(receive [this _ signature _ cofx]
(group-chats/handle-membership-update cofx this signature)))
(group-chats/handle-membership-update-received cofx this signature)))
(extend-type transport.protocol/GroupLeave
message/StatusMessage

View File

@ -12,4 +12,4 @@
(extend-type transport/GroupLeave
message/StatusMessage
(send [this chat-id cofx]
(group-chats/handle-group-leave this chat-id cofx)))
(group-chats/send-group-leave this chat-id cofx)))

View File

@ -65,8 +65,8 @@
(deftype GroupMembershipUpdateHandler []
Object
(tag [this v] "g5")
(rep [this {:keys [chat-id chat-name admin participants signature message]}]
#js [chat-id chat-name admin participants signature message]))
(rep [this {:keys [chat-id chat-name admin participants leaves version signature message]}]
#js [chat-id chat-name admin participants leaves version signature message]))
(def writer (transit/writer :json
{:handlers
@ -110,8 +110,8 @@
(v1.protocol/MessagesSeen. message-ids))
"c6" (fn [[name profile-image address fcm-token]]
(v1.contact/ContactUpdate. name profile-image address fcm-token))
"g5" (fn [[chat-id chat-name admin participants signature message]]
(v1/GroupMembershipUpdate. chat-id chat-name admin participants nil signature message))}})) ; removed group chat handlers for https://github.com/status-im/status-react/issues/4506
"g5" (fn [[chat-id chat-name admin participants leaves version signature message]]
(v1/GroupMembershipUpdate. chat-id chat-name admin participants leaves version signature message))}})) ; removed group chat handlers for https://github.com/status-im/status-react/issues/4506
(defn serialize
"Serializes a record implementing the StatusMessage protocol using the custom writers"

View File

@ -2,7 +2,7 @@
(:require [status-im.transport.message.core :as message]))
(defrecord GroupMembershipUpdate
[chat-id chat-name admin participants leaves signature message]
[chat-id chat-name admin participants leaves version signature message]
message/StatusMessage)
(defrecord GroupLeave

View File

@ -9,7 +9,7 @@
[status-im.react-native.resources :as resources]))
(defn default-chat-icon [name styles]
(when name
(when-not (string/blank? name)
[react/view (:default-chat-icon styles)
[react/text {:style (:default-chat-icon-text styles)}
(string/capitalize (first name))]]))

View File

@ -14,8 +14,7 @@
(fn [{:keys [db] :as cofx} [_ chat-id]]
(fx/merge cofx
{:db (assoc db
:new-chat-name (get-in db [:chats chat-id :name])
:group/group-type :chat-group)}
:new-chat-name (get-in db [:chats chat-id :name]))}
(navigation/navigate-to-cofx :group-chat-profile nil))))
(handlers/register-handler-fx
@ -32,9 +31,9 @@
(assoc-in [:chats current-chat-id :contacts] participants)
(assoc :selected-participants #{}))
:data-store/tx [(chats-store/add-chat-contacts-tx current-chat-id selected-participants)]}
(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've added " (apply str (interpose ", " added-participants-names)))))
#_(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've added " (apply str (interpose ", " added-participants-names)))))
#_(transport/send (protocol/GroupAdminUpdate. nil participants current-chat-id) current-chat-id)))))
(handlers/register-handler-fx
@ -49,14 +48,7 @@
(fx/merge cofx
{:db (assoc-in db [:chats current-chat-id :contacts] participants)
:data-store/tx [(chats-store/remove-chat-contacts-tx current-chat-id removed-participants)]}
(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've removed " (apply str (interpose ", " removed-participants-names)))))
#_(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've removed " (apply str (interpose ", " removed-participants-names)))))
#_(transport/send (protocol/GroupAdminUpdate. nil participants current-chat-id) current-chat-id)))))
(handlers/register-handler-fx
:set-group-chat-name
(fn [{{:keys [current-chat-id] :as db} :db} [_ new-chat-name]]
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
:data-store/tx [(chats-store/save-chat-tx {:chat-id current-chat-id
:name new-chat-name})]}))

View File

@ -33,7 +33,7 @@
toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/group-chat)]
(when save-btn-enabled?
(let [handler #(re-frame/dispatch [:chat.ui/start-group-chat group-name])]
(let [handler #(re-frame/dispatch [:group-chats.ui/create-pressed group-name])]
(if platform/android?
[toolbar/actions [{:icon :icons/ok
:icon-opts {:color :blue

View File

@ -12,12 +12,6 @@
(update :navigation-stack conj view-id)
(assoc :view-id view-id)))
(defn- replace-top-element [stack view-id]
(let [stack' (if (> 2 (count stack))
(list :home)
(pop stack))]
(conj stack' view-id)))
;; public fns
(fx/defn navigate-to-clean

View File

@ -49,11 +49,6 @@
(fn [cofx _]
(profile.models/start-editing-group-chat-profile cofx)))
(handlers/register-handler-fx
:group-chat-profile/save-profile
(fn [cofx _]
(profile.models/save-group-chat-profile cofx)))
(handlers/register-handler-fx
:my-profile/enter-two-random-words
(fn [cofx _]

View File

@ -30,7 +30,7 @@
[toolbar/toolbar {}
nil
[toolbar/content-title ""]
[toolbar/default-done {:handler #(re-frame/dispatch [:group-chat-profile/save-profile])
[toolbar/default-done {:handler #(re-frame/dispatch [:group-chats.ui/save-pressed])
:icon :icons/ok
:icon-opts {:color colors/blue
:accessibility-label :done-button}}]])
@ -47,7 +47,7 @@
:accessibility-label :clear-history-button}
{:label (i18n/label :t/delete-chat)
:icon :icons/arrow-left
:action #(re-frame/dispatch [:group-chat.ui/leave-group-pressed chat-id])
:action #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])
:accessibility-label :delete-chat-button}]))
(defn contact-actions [contact]
@ -99,7 +99,7 @@
{:contact shown-chat
:editing? editing?
:allow-icon-change? false
:on-change-text-event :set-group-chat-name}]
:on-change-text-event :group-chats.ui/name-changed}]
[list/action-list (actions admin? (:chat-id current-chat))
{:container-style styles/action-container
:action-style styles/action

View File

@ -73,10 +73,6 @@
(defn start-editing-group-chat-profile [{:keys [db]}]
{:db (assoc db :group-chat-profile/editing? true)})
(defn save-group-chat-profile [{:keys [db]}]
(-> {:db db}
(update :db dissoc :group-chat-profile/editing?)))
(defn enter-two-random-words [{:keys [db]}]
(let [{:keys [mnemonic]} (:account/account db)
shuffled-mnemonic (shuffle (map-indexed vector (clojure.string/split mnemonic #" ")))]

View File

@ -13,7 +13,7 @@
#{:data-store/tx :data-store/base-tx :chat-received-message/add-fx
:shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post
:shh/generate-sym-key-from-password :confirm-messages-processed
:utils/dispatch-later})
:group-chats/verify-membership-signature :utils/dispatch-later})
(defn- safe-merge [fx new-fx]
(if (:merging-fx-with-common-keys fx)

View File

@ -48,12 +48,13 @@
:current-chat-id "chat-id"
:chats {"chat-id" {:messages {}}}}]
(testing "a message coming from you!"
(let [actual (message/receive {:db db}
{:from "me"
:message-id "id"
:chat-id "chat-id"
:content "b"
:clock-value 1})
(let [actual (message/receive-many {:db db}
[{:from "me"
:message-type :user-message
:message-id "id"
:chat-id "chat-id"
:content "b"
:clock-value 1}])
message (get-in actual [:db :chats "chat-id" :messages "id"])
status (get-in actual [:db :chats "chat-id" :message-statuses "id" "me" :status])]
(testing "it adds the message"
@ -69,34 +70,119 @@
(testing "it marks it as sent"
(is (= :sent status)))))))
(deftest receive-group-chats
(let [cofx {:db {:chats {"chat-id" {:contacts #{"present"}}}
:account/account {:public-key "a"}
:current-chat-id "chat-id"
:view-id :chat}}
valid-message {:chat-id "chat-id"
:from "present"
:message-type :group-user-message
:message-id "1"
:clock-value 1
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "present"
:message-type :group-user-message
:message-id "1"
:clock-value 1
:timestamp 0}
bad-from-message {:chat-id "chat-id"
:from "not-present"
:message-type :group-user-message
:message-id "1"
:clock-value 1
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-many cofx [valid-message]) [:db :chats "chat-id" :messages "1"])))
(testing "a message from someone not in the list of participants"
(is (= cofx (message/receive-many cofx [bad-from-message]))))
(testing "a message with non existing chat-id"
(is (= cofx (message/receive-many cofx [bad-chat-id-message]))))))
(deftest receive-public-chats
(let [cofx {:db {:chats {"chat-id" {:public? true}}
:account/account {:public-key "a"}
:current-chat-id "chat-id"
:view-id :chat}}
valid-message {:chat-id "chat-id"
:from "anyone"
:message-type :public-group-user-message
:message-id "1"
:clock-value 1
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "present"
:message-type :public-group-user-message
:message-id "1"
:clock-value 1
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-many cofx [valid-message]) [:db :chats "chat-id" :messages "1"])))
(testing "a message with non existing chat-id"
(is (= cofx (message/receive-many cofx [bad-chat-id-message]))))))
(deftest receive-one-to-one
(let [cofx {:db {:chats {"matching" {}}
:account/account {:public-key "a"}
:current-public-key "me"
:current-chat-id "chat-id"
:view-id :chat}}
valid-message {:chat-id "matching"
:from "matching"
:message-type :user-message
:message-id "1"
:clock-value 1
:timestamp 0}
own-message {:chat-id "matching"
:from "me"
:message-type :user-message
:message-id "1"
:clock-value 1
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "not-matching"
:message-type :user-message
:message-id "1"
:clock-value 1
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-many cofx [valid-message]) [:db :chats "matching" :messages "1"])))
(testing "our own message"
(is (get-in (message/receive-many cofx [own-message]) [:db :chats "matching" :messages "1"])))
(testing "a message with non matching chat-id"
(is (= cofx (message/receive-many cofx [bad-chat-id-message]))))))
(deftest receive-send-seen
(let [cofx {:db {:chats {"chat-id" {}}
:account/account {:public-key "a"}
:current-chat-id "chat-id"
:view-id :chat}}
message {:chat-id "chat-id"
:from "a"
:message-type :user-message
:from "chat-id"
:message-id "1"
:clock-value 0
:clock-value 1
:timestamp 0}
extract-seen (comp :payload :message first :shh/post)]
(testing "it send a seen message when the chat is 1-to-1 and is open"
(is (instance? protocol/MessagesSeen
(extract-seen (message/receive cofx message))))
(is (= #{"1"} (:message-ids (extract-seen (message/receive cofx message))))))
(extract-seen (message/receive-many cofx [message]))))
(is (= #{"1"} (:message-ids (extract-seen (message/receive-many cofx [message]))))))
(testing "it does not send any when the chat is a group-chat"
(is (nil? (extract-seen
(message/receive
(message/receive-many
(assoc-in cofx [:db :chats "chat-id" :group-chat] true)
message)))))
[message])))))
(testing "it does not send any when we are in a different chat"
(is (nil? (extract-seen
(message/receive (assoc-in cofx [:db :current-chat-id] :different)
message)))))
(message/receive-many (assoc-in cofx [:db :current-chat-id] :different)
[message])))))
(testing "it does not send any when we are not in a chat view"
(is (nil? (extract-seen
(message/receive (assoc-in cofx [:db :view-id] :home)
message)))))))
(message/receive-many (assoc-in cofx [:db :view-id] :home)
[message])))))))
(deftest delete-message
(let [timestamp (time/now)

View File

@ -3,7 +3,7 @@
[status-im.utils.config :as config]
[status-im.group-chats.core :as group-chats]))
(def chat-id "0xba")
(def random-id "685a9351-417e-587c-8bc1-191ac2a57ef8")
(def chat-name "chat-name")
(def member-1 "member-1")
@ -11,6 +11,8 @@
(def admin member-1)
(def chat-id (str random-id admin))
(def invitation-m1 {:id "m-1"
:user member-1})
(def invitation-m2 {:id "m-2"
@ -42,6 +44,18 @@
(testing "it sets the right version"
(is (= 1
(:membership-version actual))))))
(testing "a chat with the wrong id"
(let [bad-chat-id (str random-id member-2)
actual (->
(group-chats/handle-membership-update
{:db {}}
(assoc initial-message :chat-id bad-chat-id)
admin)
:db
:chats
(get bad-chat-id))]
(testing "it does not create a chat"
(is (not actual)))))
(testing "an already existing chat"
(let [cofx {:db {:chats {chat-id {:contacts [invitation-m1
invitation-m2]
@ -51,7 +65,7 @@
(testing "the message is an older version"
(let [actual (group-chats/handle-membership-update cofx initial-message admin)]
(testing "it noops"
(is (not actual)))))
(is (= actual cofx)))))
(testing "the message is a more recent version"
(testing "it sets the right participants")))
(testing "a leave from a member is received"