From 751ff10eacd3915952c5279f81b2d3804b972be4 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:27:28 +0100 Subject: [PATCH] directory: fix group link verification (#7242) * add logs * more logs * fix * fix --- src/Simplex/Chat/Library/Commands.hs | 21 +++++++++++---------- src/Simplex/Chat/Store/Groups.hs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 58f22f8a18..c664b64a5c 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -4344,8 +4344,9 @@ processChatCommand cxt nm = \case (Nothing, Nothing) -> pure () _ -> throwChatError CEInvalidConnReq let ov = verifyLinkOwner rootKey owners l' sig_ + glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners planDomain = case nl of CTName ni -> Just (nameDomain ni); _ -> Nothing - plan0 <- groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_ ov + plan0 <- groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_ ov glOwners -- a joined channel is found by link but not by name (its domain is not verified locally, -- e.g. an un-upgraded relay dropped the claim); refresh its profile from the fresh link -- data and mark it verified, so the check below passes and future by-name lookups match @@ -4452,7 +4453,7 @@ processChatCommand cxt nm = \case groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli case groupLinkId of Nothing -> contactRequestPlan user cReq Nothing Nothing - Just _ -> groupJoinRequestPlan user cReq Nothing Nothing Nothing + Just _ -> groupJoinRequestPlan user cReq Nothing Nothing Nothing [] contactRequestPlan :: User -> ConnReqContact -> Maybe ContactShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan contactRequestPlan user (CRContactUri crData) cld ov = do let cReqSchemas = contactCReqSchemas crData @@ -4474,10 +4475,10 @@ processChatCommand cxt nm = \case | contactDeleted ct -> plan $ CAPOk cld ov | otherwise -> plan $ CAPKnown ct -- TODO [short links] RcvGroupMsgConnection branch is deprecated? (old group link protocol?) - Just (RcvGroupMsgConnection _ gInfo _) -> groupPlan gInfo Nothing Nothing Nothing + Just (RcvGroupMsgConnection _ gInfo _) -> groupPlan gInfo Nothing Nothing Nothing [] Just _ -> throwCmdError "found connection entity is not RcvDirectMsgConnection or RcvGroupMsgConnection" - groupJoinRequestPlan :: User -> ConnReqContact -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan - groupJoinRequestPlan user (CRContactUri crData) linkInfo gld ov = do + groupJoinRequestPlan :: User -> ConnReqContact -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> [GroupLinkOwner] -> CM ConnectionPlan + groupJoinRequestPlan user (CRContactUri crData) linkInfo gld ov glOwners = do let cReqSchemas = contactCReqSchemas crData cReqHashes = bimap contactCReqHash contactCReqHash cReqSchemas plan p = pure $ CPGroupLink p @@ -4494,13 +4495,13 @@ processChatCommand cxt nm = \case | not (contactReady ct) && contactActive ct -> plan $ GLPConnectingProhibit gInfo_ | otherwise -> plan $ GLPOk linkInfo gld ov (Nothing, Just _) -> throwCmdError "found connection entity is not RcvDirectMsgConnection" - (Just gInfo, _) -> groupPlan gInfo linkInfo gld ov - groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan - groupPlan gInfo@GroupInfo {membership} linkInfo gld ov - | memberStatus membership == GSMemRejected = plan $ GLPKnown gInfo False ov (ListDef []) + (Just gInfo, _) -> groupPlan gInfo linkInfo gld ov glOwners + groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> [GroupLinkOwner] -> CM ConnectionPlan + groupPlan gInfo@GroupInfo {membership} linkInfo gld ov glOwners + | memberStatus membership == GSMemRejected = plan $ GLPKnown gInfo False ov (ListDef glOwners) | not (memberActive membership) && not (memberRemoved membership) = plan $ GLPConnectingProhibit $ Just gInfo - | memberActive membership = plan $ GLPKnown gInfo False ov (ListDef []) + | memberActive membership = plan $ GLPKnown gInfo False ov (ListDef glOwners) | otherwise = plan $ GLPOk linkInfo gld ov where plan p = pure $ CPGroupLink p diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index b10b7ad239..fe16dd4731 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -2859,10 +2859,19 @@ getGroupInfoViaUserTarget db cxt user@User {userId} target = fmap eitherToMaybe _ -> Left $ SEInternalError "no conn req or group ID" getGroupViaShortLinkToConnect :: DB.Connection -> StoreCxt -> User -> ShortLinkContact -> ExceptT StoreError IO (Maybe (ConnReqContact, GroupInfo)) -getGroupViaShortLinkToConnect db cxt user@User {userId} shortLink = - liftIO (maybeFirstRow id $ DB.query db "SELECT group_id, conn_full_link_to_connect FROM groups WHERE user_id = ? AND conn_short_link_to_connect = ?" (userId, shortLink)) >>= \case +getGroupViaShortLinkToConnect db cxt user@User {userId, userContactId} shortLink = + liftIO (maybeFirstRow id $ DB.query db q (userContactId, userId, shortLink, GSMemRejected, GSMemRemoved, GSMemLeft, GSMemGroupDeleted)) >>= \case Just (gId :: Int64, Just cReq) -> Just . (cReq,) <$> getGroupInfo db cxt user gId _ -> pure Nothing + where + q = + [sql| + SELECT g.group_id, g.conn_full_link_to_connect + FROM groups g + JOIN group_members mu ON mu.group_id = g.group_id AND mu.contact_id = ? + WHERE g.user_id = ? AND g.conn_short_link_to_connect = ? + AND mu.member_status NOT IN (?,?,?,?) + |] getGroupInfoByGroupLinkHash :: DB.Connection -> StoreCxt -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo) getGroupInfoByGroupLinkHash db cxt user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do