Skip to content

Commit f8e73fb

Browse files
committed
fix(communities): stop publishing the community when not control node
Part of status-im/status-app#21514 The asynchronous community token-metadata handler reused `Subscription.Community` to notify the UI after loading token data, but that event is interpreted by the messenger as a request to publish the full community description. On non-control nodes, the missing community private key caused `SendPublic` to fall back to the client identity, producing the flood of externally self-signed descriptions. Use a dedicated UI-only subscription event for token-metadata updates and reject description publication from devices that are not the community control node.
1 parent 355353e commit f8e73fb

4 files changed

Lines changed: 113 additions & 4 deletions

File tree

protocol/communities/manager.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ func (m *Manager) SetMediaServerProperties() {
419419
type Subscription struct {
420420
archivetypes.HistoryArchiveSignals
421421
Community *Community
422+
CommunityTokensMetadataLoaded *Community
422423
CommunityEventsMessage *CommunityEventsMessage
423424
AcceptedRequestsToJoin []types3.HexBytes
424425
RejectedRequestsToJoin []types3.HexBytes
@@ -4512,7 +4513,7 @@ func (m *Manager) handleCommunityTokensMetadataAsync(communityID string) {
45124513
default:
45134514
}
45144515

4515-
m.publish(&Subscription{Community: community})
4516+
m.publish(&Subscription{CommunityTokensMetadataLoaded: community})
45164517
}()
45174518
}
45184519

protocol/communities/manager_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,27 @@ func (s *ManagerSuite) SetupTest() {
9494
s.archiveManager = t
9595
}
9696

97+
func (s *ManagerSuite) TestCommunityTokensMetadataLoadedDoesNotPublishCommunity() {
98+
community, err := s.manager.CreateCommunity(&requests.CreateCommunity{
99+
Name: "status",
100+
Description: "status community description",
101+
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
102+
}, false)
103+
s.Require().NoError(err)
104+
105+
subscription := s.manager.Subscribe()
106+
s.manager.handleCommunityTokensMetadataAsync(community.IDString())
107+
108+
select {
109+
case event := <-subscription:
110+
s.Require().Nil(event.Community)
111+
s.Require().NotNil(event.CommunityTokensMetadataLoaded)
112+
s.Require().Equal(community.IDString(), event.CommunityTokensMetadataLoaded.IDString())
113+
case <-time.After(2 * time.Second):
114+
s.FailNow("community token metadata completion was not signaled")
115+
}
116+
}
117+
97118
func intToBig(n int64) *hexutil.Big {
98119
return (*hexutil.Big)(big.NewInt(n))
99120
}

protocol/messenger_communities.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ func (m *Messenger) publishOrg(org *communities.Community, shouldRekey bool) err
125125
if org == nil {
126126
return nil
127127
}
128+
if !org.IsControlNode() {
129+
return communities.ErrNotControlNode
130+
}
128131

129132
m.logger.Debug("publishing community",
130133
zap.String("communityID", org.IDString()),
@@ -408,15 +411,18 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti
408411
return
409412
}
410413
if sub.Community != nil {
411-
if sub.Community == nil {
412-
continue
413-
}
414414
// NOTE: because we use a pointer here, there's a race condition where the community would be updated before it's compared to the previous one.
415415
// This results in keys not being propagated as the copy would not see any changes
416416
communityCopy := sub.Community.CreateDeepCopy()
417417

418418
publishOrgAndDistributeEncryptionKeys(communityCopy)
419419
}
420+
if sub.CommunityTokensMetadataLoaded != nil && m.config.messengerSignalsHandler != nil {
421+
communityCopy := sub.CommunityTokensMetadataLoaded.CreateDeepCopy()
422+
response := &MessengerResponse{}
423+
response.AddCommunity(communityCopy)
424+
m.config.messengerSignalsHandler.MessengerResponse(response)
425+
}
420426

421427
if sub.CommunityEventsMessage != nil {
422428
err := m.publishCommunityEvents(sub.Community, sub.CommunityEventsMessage)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package protocol
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/status-im/status-go/internal/crypto"
9+
"github.com/status-im/status-go/pkg/messaging"
10+
"github.com/status-im/status-go/pkg/pubsub"
11+
protocolcommon "github.com/status-im/status-go/protocol/common"
12+
"github.com/status-im/status-go/protocol/communities"
13+
"github.com/status-im/status-go/protocol/protobuf"
14+
)
15+
16+
type publishOrgTimeSource struct{}
17+
18+
func (publishOrgTimeSource) GetCurrentTime() uint64 {
19+
return 0
20+
}
21+
22+
func TestPublishOrgRejectsNonControlNode(t *testing.T) {
23+
controlNode, err := crypto.GenerateKey()
24+
require.NoError(t, err)
25+
member, err := crypto.GenerateKey()
26+
require.NoError(t, err)
27+
28+
community, err := communities.New(communities.Config{
29+
ControlNode: &controlNode.PublicKey,
30+
ID: &controlNode.PublicKey,
31+
MemberIdentity: member,
32+
CommunityDescription: &protobuf.CommunityDescription{},
33+
}, publishOrgTimeSource{}, &communities.NoopDescriptionEncryptor{}, nil)
34+
require.NoError(t, err)
35+
require.False(t, community.IsControlNode())
36+
37+
require.ErrorIs(t, (&Messenger{}).publishOrg(community, false), communities.ErrNotControlNode)
38+
}
39+
40+
func TestPublishOrgAllowsControlNode(t *testing.T) {
41+
messagingEnv, err := messaging.NewTestMessagingEnvironment()
42+
require.NoError(t, err)
43+
require.NoError(t, messagingEnv.Setup(t))
44+
45+
m, err := newTestMessenger(t, messagingEnv, testMessengerConfig{})
46+
require.NoError(t, err)
47+
48+
controlNode, err := crypto.GenerateKey()
49+
require.NoError(t, err)
50+
community, err := communities.New(communities.Config{
51+
PrivateKey: controlNode,
52+
ControlNode: &controlNode.PublicKey,
53+
ControlDevice: true,
54+
ID: &controlNode.PublicKey,
55+
MemberIdentity: controlNode,
56+
CommunityDescription: &protobuf.CommunityDescription{},
57+
}, publishOrgTimeSource{}, &communities.NoopDescriptionEncryptor{}, nil)
58+
require.NoError(t, err)
59+
require.True(t, community.IsControlNode())
60+
61+
messageEvents, unsubscribe := pubsub.Subscribe[protocolcommon.MessageEvent](m.sender.Publisher(), 1)
62+
defer unsubscribe()
63+
64+
require.NoError(t, m.publishOrg(community, false))
65+
66+
select {
67+
case event := <-messageEvents:
68+
require.NotNil(t, event.ScheduledMessage)
69+
require.Nil(t, event.ScheduledMessage.Recipient)
70+
rawMessage := event.ScheduledMessage.RawMessage
71+
require.NotNil(t, rawMessage)
72+
require.NotEmpty(t, rawMessage.ID)
73+
require.Equal(t, protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION, rawMessage.MessageType)
74+
require.Equal(t, community.IDString(), rawMessage.ContentTopic)
75+
require.Equal(t, []byte(community.ID()), rawMessage.CommunityID)
76+
require.NotNil(t, rawMessage.Sender)
77+
require.True(t, crypto.IsPubKeyEqual(&rawMessage.Sender.PublicKey, community.ControlNode()))
78+
default:
79+
require.Fail(t, "community description was not scheduled for publication")
80+
}
81+
}

0 commit comments

Comments
 (0)