core: ignore relay join requests in groups without relays (#7875)

This commit is contained in:
Evgeny @ SimpleX Chat authored and GitHub committed 2026-10-26 18:28:09 +00:00
1 parent 3de946b7d4
commit ec8884f465
2 files changed
+44 -13

No files matched your search

+16 -12
View File
@@ -1688,22 +1688,26 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
User {userChatRelay} = user
-- TODO [relays] owner, relays: TBC how to communicate member rejection rules from owner to relays
memberJoinRequestViaRelay :: InvitationId -> VersionRangeChat -> Maybe SignedMsg -> Profile -> MemberId -> MemberKey -> Maybe MemberId -> CM ()
memberJoinRequestViaRelay invId chatVRange signedMsg_ p joiningMemberId joiningMemberKey@(MemberKey joiningKey) viaRelay = do
memberJoinRequestViaRelay invId chatVRange signedMsg_ p@Profile {displayName} joiningMemberId joiningMemberKey@(MemberKey joiningKey) viaRelay = do
(_ucl, gLinkInfo_) <- withStore $ \db -> getUserContactLinkById db userId uclId
case gLinkInfo_ of
Just GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
g@(GIK gInfo _) <- withStore $ \db -> getGroupInfoKeys db cxt user groupId
existing_ <- withStore' $ \db -> eitherToMaybe <$> runExceptT (getGroupMemberByMemberId db cxt user gInfo joiningMemberId)
let signed = memberSigned gInfo joiningMemberId joiningKey signedMsg_
case existing_ of
Just rosterMem
-- a privileged memberId's key is owner-authoritative (the roster); the joiner must prove
-- possession of that exact key, otherwise this is an attempt to impersonate it
| isRosterRole (memberRole' rosterMem) ->
if verifyKey gInfo rosterMem signed
then acceptJoin g signed (Just rosterMem) (memberRole' rosterMem)
else messageError "memberJoinRequestViaRelay: rejected join claiming privileged memberId (key mismatch or invalid signature)"
_ -> acceptJoin g signed Nothing gLinkMemRole
if
| not (useRelays' gInfo) ->
messageWarning $ "memberJoinRequestViaRelay (group " <> groupName' gInfo <> "): ignored relay join request from " <> displayName <> " (group does not use relays)"
| otherwise -> do
existing_ <- withStore' $ \db -> eitherToMaybe <$> runExceptT (getGroupMemberByMemberId db cxt user gInfo joiningMemberId)
let signed = memberSigned gInfo joiningMemberId joiningKey signedMsg_
case existing_ of
Just rosterMem
-- a privileged memberId's key is owner-authoritative (the roster); the joiner must prove
-- possession of that exact key, otherwise this is an attempt to impersonate it
| isRosterRole (memberRole' rosterMem) ->
if verifyKey gInfo rosterMem signed
then acceptJoin g signed (Just rosterMem) (memberRole' rosterMem)
else messageError "memberJoinRequestViaRelay: rejected join claiming privileged memberId (key mismatch or invalid signature)"
_ -> acceptJoin g signed Nothing gLinkMemRole
Nothing ->
messageError "memberJoinRequestViaRelay: no group link info for relay link"
where
+28 -1
View File
@@ -39,7 +39,7 @@ import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId)
import Simplex.Chat.Messages.Batch (encodeBinaryBatch, encodeFwdElement)
import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText)
import Simplex.Chat.Options
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XGrpRelayInv, XInfo, XMsgUpdate, XMsgNew, XMsgDel), FwdSender (FwdMember, FwdChannel), GrpMsgForward (GrpMsgForward), MsgContainer (..), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), chatMsgToBody, mcSimple, msgContentText)
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XGrpRelayInv, XInfo, XMember, XMsgUpdate, XMsgNew, XMsgDel), FwdSender (FwdMember, FwdChannel), GrpMsgForward (GrpMsgForward), MsgContainer (..), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), chatMsgToBody, mcSimple, msgContentText)
import Simplex.Chat.Types
import Simplex.Chat.Types.MemberRelations (MemberRelation (..), getRelation, setRelation)
import Simplex.Chat.Types.Shared (GroupMemberRole (..), GroupAcceptance (..))
@@ -135,6 +135,7 @@ chatGroupTests = do
it "demotion does not remove group link" testGroupLinkDemotedAdmin
it "host profile received" testGroupLinkHostProfileReceived
it "existing contact merged" testGroupLinkExistingContactMerged
it "ignore relay join request in group without relays" testGroupLinkIgnoreRelayJoinRequest
describe "group links - member screening" $ do
it "reject member - blocked name" testGLinkRejectBlockedName
it "accept member - only host approval" testGLinkApproveMember
@@ -3629,6 +3630,32 @@ testGroupLinkExistingContactMerged =
bob #> "#team hi there"
alice <# "#team bob> hi there"
testGroupLinkIgnoreRelayJoinRequest :: HasCallStack => TestParams -> IO ()
testGroupLinkIgnoreRelayJoinRequest ps =
withNewTestChatOpts ps (testOpts {coreOptions = testCoreOpts {logLevel = CLLWarning}}) "alice" aliceProfile $ \alice ->
withNewTestChat ps "bob" bobProfile $ \bob -> do
alice ##> "/g team"
alice <## "group #team is created"
alice <## "to add members use /a team <name> or /create link #team"
alice ##> "/create link #team"
(gShortLink, _gLink) <- getGroupLinks alice "team" GRMember True
sendMemberJoinRequest bob gShortLink
alice <## "warning: memberJoinRequestViaRelay (group team): ignored relay join request from bob (group does not use relays)"
alice ##> "/ms team"
alice <## "alice (Alice): owner, you, created group"
sendMemberJoinRequest :: TestCC -> String -> IO ()
sendMemberJoinRequest cc groupLink =
withCCUser cc $ \user -> do
gLink :: ShortLinkContact <- either fail pure $ strDecode (B.pack groupLink)
(memberKey, _) <- atomically . C.generateKeyPair =<< C.newRandom
let joinGroup = do
(_, _, cReq) <- getShortLinkConnReq NRMInteractive user gLink
(connId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff
dm <- encodeConnInfo $ XMember bobProfile (MemberId "member") (MemberKey memberKey) Nothing
withAgent $ \a -> joinConnection a NRMInteractive (aUserId user) connId True cReq dm PQSupportOff SMSubscribe
runExceptT joinGroup `runReaderT` chatController cc >>= either (fail . show) (\_ -> pure ())
testGLinkRejectBlockedName :: HasCallStack => TestParams -> IO ()
testGLinkRejectBlockedName =
testChatCfg2 cfg aliceProfile bobProfile $