core: do not connect to a group whose member is already connected

Connecting to a prepared group whose host or relay member already had a
connection created a second one for the same member -

  UNIQUE constraint failed: connections.group_member_id

reachable by connecting again from a second view of the same group. joinContact
advances the connection past ConnPrepared only after the agent call returns, so
a join that failed is resumed by the ConnPrepared branch above and reaching this
one means the join already succeeded; there is no case where connecting again is
what the caller wants. Replaces a connect' call the code already questioned.

The two tests reach this error only because owner members are now re-used, so
they cover both halves: without either change they fail on a constraint instead.
This commit is contained in:
Narasimha-sc
2026-08-20 14:03:37 +00:00
parent be8d51b642
commit 66d6466a9e
3 changed files with 52 additions and 1 deletions
+4 -1
View File
@@ -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
+16
View File
@@ -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 ->
+32
View File
@@ -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