diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 8f2da97a42..ac8e4b0d27 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2831,8 +2831,7 @@ processChatCommand cxt nm = \case withFastStore' $ \db -> do updateGroupMemberStatus db userId fromMember GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted - forM_ (groupMemberKey g) $ \_ -> - when (maxVersion peerChatVRange >= groupMemberKeyVersion) $ setMemberKeyStatus db KSSent (groupMemberId' fromMember) + when (isJust (groupMemberKey g) && maxVersion peerChatVRange >= groupMemberKeyVersion) $ setMemberKeyStatus db KSSent (groupMemberId' fromMember) -- MFAll is default for new groups unless (enableNtfs == MFAll) $ updateGroupSettings db user groupId chatSettings {enableNtfs} void (withAgent $ \a -> joinConnection a nm (aUserId user) agentConnId (enableNtfs /= MFNone) connRequest dm PQSupportOff subMode) @@ -4248,9 +4247,8 @@ processChatCommand cxt nm = \case groupSize = Just currentMemCount } (msg, _) <- sendDirectContactMessage user ct $ XGrpInv groupInv - forM_ (groupMemberKey gInfo) $ \_ -> - when (m `supportsVersion` groupMemberKeyVersion) $ - withStore' $ \db -> setMemberKeyStatus db KSSent groupMemberId + when (isJust (groupMemberKey gInfo) && m `supportsVersion` groupMemberKeyVersion) $ + withStore' $ \db -> setMemberKeyStatus db KSSent groupMemberId let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole timed_ <- contactCITimed ct ci <- saveSndChatItem' user (CDDirectSnd ct) msg content Nothing Nothing Nothing timed_ False diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 746d36c861..119c6169af 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2635,8 +2635,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = createMemberConnectionAsync db user hostId connIds connChatVersion peerChatVRange subMode updateGroupMemberStatusById db userId hostId GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted - forM_ (groupMemberKey gInfo) $ \_ -> - when (maxVersion peerChatVRange >= groupMemberKeyVersion) $ setMemberKeyStatus db KSSent hostId + when (isJust (groupMemberKey gInfo) && maxVersion peerChatVRange >= groupMemberKeyVersion) $ setMemberKeyStatus db KSSent hostId joinAgentConnectionAsync cmdId False acId True connRequest dm subMode toView $ CEvtUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct) else do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 8b1cd9f4e7..80c74f44fc 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -652,8 +652,9 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile { createPreparedGroup :: DB.Connection -> TVar ChaChaDRG -> StoreCxt -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> GroupMemberRole -> Maybe Int64 -> Maybe SimplexDomain -> ExceptT StoreError IO (GroupInfo, Maybe GroupMember) createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays userMemberRole publicMemberCount_ verifiedDomain = do currentTs <- liftIO getCurrentTime + (memberPubKey, memberPrivKey) <- liftIO $ atomically $ C.generateKeyPair gVar let prepared = Just (connLinkToConnect, welcomeSharedMsgId) - (groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing useRelays Nothing publicMemberCount_ currentTs + (groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing useRelays Nothing publicMemberCount_ (Just memberPrivKey) currentTs hostMemberId_ <- if useRelays then pure Nothing @@ -663,8 +664,7 @@ createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile b then liftIO $ MemberId <$> encodedRandomBytes gVar 12 else pure $ MemberId $ encodeUtf8 groupLDN <> "_user_unknown_id" let userMember = MemberIdRole userMemberId userMemberRole - -- TODO [member keys] user key must be included here. Should key be added when group is prepared? - membership <- createContactMemberInv_ db user groupId hostMemberId_ user userMember GCUserMember GSMemUnknown IBUnknown Nothing Nothing currentTs (vr cxt) + membership <- createContactMemberInv_ db user groupId hostMemberId_ user userMember GCUserMember GSMemUnknown IBUnknown Nothing (Just memberPubKey) currentTs (vr cxt) hostMember_ <- forM hostMemberId_ $ getGroupMember db cxt user groupId forM_ hostMember_ $ \hostMember -> when business $ liftIO $ setGroupBusinessChatInfo groupId membership hostMember @@ -857,16 +857,14 @@ createGroupInvitedViaLink :: DB.Connection -> StoreCxt -> User -> Connection -> createGroupInvitedViaLink db cxt user conn memberKeys GroupLinkInvitation {fromMember, fromMemberKey, fromMemberName, invitedMember, groupProfile, accepted, business} = do let fromMemberProfile = profileFromName fromMemberName initialStatus = maybe GSMemAccepted (acceptanceToStatus $ memberAdmission groupProfile) accepted - r@(_, host) <- createGroupViaLink' db cxt user conn memberKeys fromMember fromMemberProfile invitedMember groupProfile business initialStatus - forM_ fromMemberKey $ \(MemberKey k) -> liftIO $ setMemberPubKey db (groupMemberId' host) k - pure r + createGroupViaLink' db cxt user conn memberKeys fromMember fromMemberProfile ((\(MemberKey k) -> k) <$> fromMemberKey) invitedMember groupProfile business initialStatus createGroupRejectedViaLink :: DB.Connection -> StoreCxt -> User -> Connection -> C.KeyPairEd25519 -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember) createGroupRejectedViaLink db cxt user conn memberKeys GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do let fromMemberProfile = profileFromName $ nameFromMemberId memberId - createGroupViaLink' db cxt user conn memberKeys fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected + createGroupViaLink' db cxt user conn memberKeys fromMember fromMemberProfile Nothing invitedMember groupProfile Nothing GSMemRejected -createGroupViaLink' :: DB.Connection -> StoreCxt -> User -> Connection -> C.KeyPairEd25519 -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember) +createGroupViaLink' :: DB.Connection -> StoreCxt -> User -> Connection -> C.KeyPairEd25519 -> MemberIdRole -> Profile -> Maybe C.PublicKeyEd25519 -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember) createGroupViaLink' db cxt @@ -875,18 +873,17 @@ createGroupViaLink' memberKeys fromMember fromMemberProfile + fromMemberPubKey_ invitedMember groupProfile business membershipStatus = do currentTs <- liftIO getCurrentTime - (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing business False Nothing Nothing currentTs + (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing business False Nothing Nothing (Just (snd memberKeys)) currentTs hostMemberId <- insertHost_ currentTs groupId liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId) -- using IBUnknown since host is created without contact - -- TODO [member keys] this is currently not used with public groups. If it needs to be used, member keys need to be added - membership <- createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId Nothing currentTs (vr cxt) - liftIO $ setUserMemberKey db groupId (groupMemberId' membership) (snd memberKeys) + _membership <- createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId (Just (fst memberKeys)) currentTs (vr cxt) liftIO $ setViaGroupLinkUri db groupId connId (,) <$> getGroupInfo db cxt user groupId <*> getGroupMemberById db cxt user hostMemberId where @@ -900,16 +897,16 @@ createGroupViaLink' [sql| INSERT INTO group_members ( group_id, index_in_group, member_id, member_role, member_category, member_status, member_relations_vector, invited_by, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_id, contact_profile_id, member_pub_key, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted, Binary B.empty, fromInvitedBy userContactId IBUnknown) - :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs) + :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, fromMemberPubKey_, currentTs, currentTs) ) insertedRowId db -createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> Bool -> Maybe RelayStatus -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (GroupId, Text) -createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus publicMemberCount_ currentTs = ExceptT $ do +createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> Bool -> Maybe RelayStatus -> Maybe Int64 -> Maybe C.PrivateKeyEd25519 -> UTCTime -> ExceptT StoreError IO (GroupId, Text) +createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus publicMemberCount_ memberPrivKey_ currentTs = ExceptT $ do let GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} = groupProfile (groupType_, groupLink_, publicGroupId_) = case publicGroup of Just PublicGroupProfile {groupType, groupLink, publicGroupId} -> (Just groupType, Just groupLink, Just publicGroupId) @@ -935,10 +932,10 @@ createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus p INSERT INTO groups (group_profile_id, local_display_name, user_id, enable_ntfs, created_at, updated_at, chat_ts, user_member_profile_sent_at, conn_full_link_to_connect, conn_short_link_to_connect, welcome_shared_msg_id, - business_chat, business_member_id, customer_member_id, use_relays, relay_own_status, public_member_count) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + business_chat, business_member_id, customer_member_id, use_relays, relay_own_status, public_member_count, member_priv_key) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. toPreparedGroupRow prepared :. businessChatInfoRow business :. (BI useRelays, relayOwnStatus, publicMemberCount_)) + ((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. toPreparedGroupRow prepared :. businessChatInfoRow business :. (BI useRelays, relayOwnStatus, publicMemberCount_, memberPrivKey_)) groupId <- insertedRowId db pure (groupId, localDisplayName) @@ -1949,7 +1946,7 @@ createRelayRequestGroup db cxt user@User {userId} GroupRelayInvitation {fromMemb groupPreferences = Nothing, memberAdmission = Nothing } - (groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just relayStatus) Nothing currentTs + (groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just relayStatus) Nothing Nothing currentTs -- Store relay request data for recovery liftIO $ setRelayRequestData_ groupId currentTs ownerMemberId <- insertOwner_ currentTs groupId @@ -2190,6 +2187,7 @@ createBusinessRequestGroup pure (groupInfo, clientMember) where insertGroup_ currentTs = do + (memberPubKey, memberPrivKey) <- liftIO $ atomically $ C.generateKeyPair gVar liftIO $ DB.execute db @@ -2202,14 +2200,13 @@ createBusinessRequestGroup [sql| INSERT INTO groups (group_profile_id, local_display_name, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat) - VALUES (?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, member_priv_key) + VALUES (?,?,?,?,?,?,?,?,?,?) |] - (groupProfileId, ldn, userId, BI True, currentTs, currentTs, currentTs, currentTs, BCCustomer) + (groupProfileId, ldn, userId, BI True, currentTs, currentTs, currentTs, currentTs, BCCustomer, memberPrivKey) groupId <- liftIO $ insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 - -- TODO [member keys] we could support member keys in business groups to allow binding agreements (though identity keys would be better for it. - membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing Nothing currentTs (vr cxt) + membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing (Just memberPubKey) currentTs (vr cxt) pure (groupId, membership) VersionRange minV maxV = cReqChatVRange insertClientMember_ currentTs groupId membership =