diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index c55f2934e2..238ffe7c01 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3771,7 +3771,10 @@ processChatCommand cxt nm = \case Nothing -> connect' groupLinkId Nothing (Just $ Just gInfo) Just conn@Connection {connStatus, xContactId} -> case connStatus of ConnPrepared -> joinPreparedConn' xContactId conn (Just $ Just gInfo) - _ -> connect' groupLinkId xContactId (Just $ Just gInfo) -- why not "already connected" for host member? + -- the connection is only advanced past ConnPrepared once the join succeeded (see joinContact), + -- so retrying a join that failed reuses it above, and reaching here means it already joined - + -- connecting again would create a second connection for the same member. + _ -> throwCmdError "group is already being joined" Nothing -> withFastStore' (\db -> getConnReqContactXContactId db cxt user cReqHash1 cReqHash2) >>= \case Right ct@Contact {activeConn} -> case groupLinkId of diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 37827ab2ad..7702caec9d 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -294,6 +294,7 @@ chatGroupTests = do it "subscriber should update profile in channel (signed)" testChannelSubscriberProfileUpdate it "should report relay results when one relay deleted its address" testChannelCreateDeletedRelay it "should deliver support scope messages via relay" testChannelSupportScope + it "connecting to channel twice fails" testChannelConnectTwice it "should add relay to existing channel" testChannelAddRelay it "should remove relay from channel" testChannelRemoveRelay it "should remove left relay from channel" testChannelRemoveLeftRelay @@ -8900,6 +8901,21 @@ setupRelay owner relay = do owner <## "ok" pure relaySLink +testChannelConnectTwice :: HasCallStack => TestParams -> IO () +testChannelConnectTwice ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice bob + memberJoinChannel "team" [bob] [alice] shortLink fullLink cath + -- connecting again reuses the owner members the first connection created, + -- and is rejected before it can create a second connection to the relay + cath ##> "/_connect group #1" + cath <## "bad chat command: group is already being joined" + alice #> "#team hi" + bob <# "#team> hi" + cath <# "#team> hi [>>]" + testChannelMemberSecurityCode :: HasCallStack => TestParams -> IO () testChannelMemberSecurityCode ps = withNewTestChat ps "alice" aliceProfile $ \alice -> diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index a012a1224c..21673b0a53 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -146,6 +146,7 @@ shortLinkTests = do it "prepare business chat using address short link data and connect" testShortLinkAddressPrepareBusiness it "connect to business address with request message" testBusinessAddressRequestMessage it "prepare group using group short link data and connect" testShortLinkPrepareGroup + it "prepare group and connect twice" testShortLinkPrepareGroupConnectTwice it "prepare group using group short link data and connect, host rejects" testShortLinkPrepareGroupReject it "connect to group with welcome message via short link" testGroupShortLinkWelcome it "retry connecting to group via short link" testShortLinkGroupRetry @@ -3757,6 +3758,37 @@ testShortLinkPrepareGroup = testChat3 aliceProfile bobProfile cathProfile test bob <## "group link: ok to connect directly" void $ getTermLine bob +testShortLinkPrepareGroupConnectTwice :: HasCallStack => TestParams -> IO () +testShortLinkPrepareGroupConnectTwice = testChat3 aliceProfile bobProfile cathProfile test + where + test alice bob cath = do + createGroup2 "team" alice cath + alice ##> "/create link #team" + (shortLink, fullLink) <- getGroupLinks alice "team" GRMember True + bob ##> ("/_connect plan 1 " <> shortLink) + bob <## "group link: ok to connect directly" + groupSLinkData <- getTermLine bob + bob ##> ("/_prepare group 1 " <> fullLink <> " " <> shortLink <> " " <> groupSLinkData) + bob <## "#team: group is prepared" + bob ##> "/_connect group #1" + bob <## "#team: connection started" + alice <## "bob (Bob): accepting request to join group #team..." + concurrentlyN_ + [ alice <## "#team: bob joined the group", + do + bob <## "#team: joining the group..." + bob <## "#team: you joined the group" + bob <## "#team: member cath (Catherine) is connected", + do + cath <## "#team: alice added bob (Bob) to the group (connecting...)" + cath <## "#team: new member bob is connected" + ] + -- connecting again is rejected before it can create a second connection to the host member + bob ##> "/_connect group #1" + bob <## "bad chat command: group is already being joined" + alice #> "#team 1" + [bob, cath] *<# "#team alice> 1" + testShortLinkPrepareGroupReject :: HasCallStack => TestParams -> IO () testShortLinkPrepareGroupReject = testChatCfg3 cfg aliceProfile bobProfile cathProfile test where