diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index da57f654ef..6f867ab0f1 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1328,12 +1328,19 @@ updatePublicGroupData user gInfo -- TODO [relays] owner: set owners on updating link data (multi-owner) groupLinkData :: GroupInfo -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData) -groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}} GroupLink {groupLinkId} groupRelays = +groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}, membership = GroupMember {memberId}, groupKeys} GroupLink {groupLinkId} groupRelays = let direct = not $ useRelays' gInfo relays = mapMaybe (\GroupRelay {relayLink} -> relayLink) groupRelays publicGroupData_ = PublicGroupData <$> publicMemberCount userData = encodeShortLinkData $ GroupShortLinkData {groupProfile, publicGroupData = publicGroupData_} - userLinkData = UserContactLinkData UserContactData {direct, owners = [], relays, userData} + owners = case groupKeys of + Just GroupKeys {groupRootKey = GRKPrivate rootPrivKey, memberPrivKey} -> + let ownerId = unMemberId memberId + ownerKey = C.publicKey memberPrivKey + authOwnerSig = C.sign' rootPrivKey (ownerId <> C.encodePubKey ownerKey) + in [OwnerAuth {ownerId, ownerKey, authOwnerSig}] + _ -> [] + userLinkData = UserContactLinkData UserContactData {direct, owners, relays, userData} crClientData = encodeJSON $ CRDataGroup groupLinkId in (userLinkData, crClientData) diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index aedbb5e1e9..0685d609e6 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -253,6 +253,7 @@ chatGroupTests = do it "should update channel profile (signed)" testChannelUpdateProfileSigned it "should preserve working link after profile update" testChannelLinkAfterProfileUpdate it "should preserve working link after welcome message update" testChannelLinkAfterWelcomeUpdate + it "should preserve owner key in link data after profile update" testChannelOwnerKeyAfterLinkUpdate it "should update channel preferences (signed)" testChannelUpdatePrefsSigned it "should change member role (signed)" testChannelChangeRoleSigned it "should block member for all (signed)" testChannelBlockMemberSigned @@ -8844,6 +8845,56 @@ testChannelLinkAfterWelcomeUpdate ps = bob <# "#team> hi" [cath, dan] *<# "#team> hi [>>]" +testChannelOwnerKeyAfterLinkUpdate :: HasCallStack => TestParams -> IO () +testChannelOwnerKeyAfterLinkUpdate ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> + withNewTestChat ps "dan" danProfile $ \dan -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice bob + memberJoinChannel "team" [bob] [alice] shortLink fullLink cath + + -- Owner updates channel profile - triggers rebuilding link data. + alice ##> "/gp team my_team My team description" + alice <## "changed to #my_team (My team description)" + concurrentlyN_ + [ do + bob <## "alice updated group #team: (signed)" + bob <## "changed to #my_team (My team description)", + do + cath <## "alice updated group #team: (signed)" + cath <## "changed to #my_team (My team description)" + ] + + threadDelay 100000 + + -- Late subscriber joins via the same channel link after profile update. + alice ##> "/show link #my_team" + (shortLink', fullLink') <- getGroupLinks alice "my_team" GRMember False + shortLink' `shouldBe` shortLink + fullLink' `shouldBe` fullLink + memberJoinChannel "my_team" [bob] [alice] shortLink' fullLink' dan + + -- Verify owner member record in late subscriber's DB has a public key. + ownerKeyPresent <- withCCTransaction dan $ \db -> + DB.query_ db "SELECT COUNT(1) FROM group_members WHERE member_role = 'owner' AND member_pub_key IS NOT NULL" :: IO [[Int]] + ownerKeyPresent `shouldBe` [[1]] + + -- Verify signed event is received by late subscriber. + alice ##> "/gp my_team team" + alice <## "changed to #team" + concurrentlyN_ + [ do + bob <## "alice updated group #my_team: (signed)" + bob <## "changed to #team", + do + cath <## "alice updated group #my_team: (signed)" + cath <## "changed to #team", + do + dan <## "alice updated group #my_team: (signed)" + dan <## "changed to #team" + ] + testChannelUpdatePrefsSigned :: HasCallStack => TestParams -> IO () testChannelUpdatePrefsSigned ps = withNewTestChat ps "alice" aliceProfile $ \alice ->