2020-11-18 09:16:51 +00:00
|
|
|
package communities
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"encoding/json"
|
2021-01-11 10:32:51 +00:00
|
|
|
"errors"
|
2021-08-05 13:27:47 +00:00
|
|
|
"fmt"
|
2024-02-13 10:23:11 +00:00
|
|
|
"math"
|
|
|
|
"math/big"
|
2020-11-18 09:16:51 +00:00
|
|
|
"sync"
|
2023-06-17 08:19:05 +00:00
|
|
|
"time"
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"go.uber.org/zap"
|
2024-02-22 10:25:13 +00:00
|
|
|
slices "golang.org/x/exp/slices"
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-10-30 18:34:21 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
|
2023-11-10 16:33:37 +00:00
|
|
|
"github.com/status-im/status-go/api/multiformat"
|
2024-07-01 18:52:57 +00:00
|
|
|
utils "github.com/status-im/status-go/common"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2021-01-11 10:32:51 +00:00
|
|
|
"github.com/status-im/status-go/images"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
2023-11-15 15:58:15 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common/shard"
|
2023-08-15 17:42:40 +00:00
|
|
|
community_token "github.com/status-im/status-go/protocol/communities/token"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2022-06-24 13:40:12 +00:00
|
|
|
"github.com/status-im/status-go/protocol/requests"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/v1"
|
2024-06-24 09:37:44 +00:00
|
|
|
"github.com/status-im/status-go/server"
|
2020-11-18 09:16:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const signatureLength = 65
|
|
|
|
|
2024-04-17 14:53:51 +00:00
|
|
|
// GrantExpirationTime interval of 7 days
|
|
|
|
var GrantExpirationTime = 168 * time.Hour
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
type Config struct {
|
2023-07-10 15:35:15 +00:00
|
|
|
PrivateKey *ecdsa.PrivateKey
|
2023-07-05 17:35:22 +00:00
|
|
|
ControlNode *ecdsa.PublicKey
|
2023-09-21 11:16:05 +00:00
|
|
|
ControlDevice bool // whether this device is control node
|
2023-07-10 15:35:15 +00:00
|
|
|
CommunityDescription *protobuf.CommunityDescription
|
|
|
|
CommunityDescriptionProtocolMessage []byte // community in a wrapped & signed (by owner) protocol message
|
|
|
|
ID *ecdsa.PublicKey
|
|
|
|
Joined bool
|
2024-01-09 18:36:47 +00:00
|
|
|
JoinedAt int64
|
2023-07-10 15:35:15 +00:00
|
|
|
Requested bool
|
|
|
|
Verified bool
|
|
|
|
Spectated bool
|
|
|
|
Muted bool
|
|
|
|
MuteTill time.Time
|
|
|
|
Logger *zap.Logger
|
|
|
|
RequestedToJoinAt uint64
|
|
|
|
RequestsToJoin []*RequestToJoin
|
2024-06-27 15:29:03 +00:00
|
|
|
MemberIdentity *ecdsa.PrivateKey
|
2023-07-10 15:35:15 +00:00
|
|
|
EventsData *EventsData
|
2023-11-15 15:58:15 +00:00
|
|
|
Shard *shard.Shard
|
2023-10-30 18:34:21 +00:00
|
|
|
PubsubTopicPrivateKey *ecdsa.PrivateKey
|
2024-01-21 10:55:14 +00:00
|
|
|
LastOpenedAt int64
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type EventsData struct {
|
|
|
|
EventsBaseCommunityDescription []byte
|
|
|
|
Events []CommunityEvent
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Community struct {
|
2024-07-01 18:52:57 +00:00
|
|
|
config *Config
|
|
|
|
mutex sync.Mutex
|
|
|
|
timesource common.TimeSource
|
|
|
|
encryptor DescriptionEncryptor
|
|
|
|
mediaServer server.MediaServerInterface
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
func New(config Config, timesource common.TimeSource, encryptor DescriptionEncryptor, mediaServer server.MediaServerInterface) (*Community, error) {
|
2021-01-11 10:32:51 +00:00
|
|
|
if config.MemberIdentity == nil {
|
|
|
|
return nil, errors.New("no member identity")
|
|
|
|
}
|
|
|
|
|
2023-09-28 15:37:03 +00:00
|
|
|
if timesource == nil {
|
|
|
|
return nil, errors.New("no timesource")
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
if config.Logger == nil {
|
|
|
|
logger, err := zap.NewDevelopment()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
config.Logger = logger
|
|
|
|
}
|
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
if config.CommunityDescription == nil {
|
|
|
|
config.CommunityDescription = &protobuf.CommunityDescription{}
|
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
return &Community{
|
|
|
|
config: &config,
|
|
|
|
timesource: timesource,
|
|
|
|
encryptor: encryptor,
|
|
|
|
mediaServer: mediaServer,
|
|
|
|
}, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 14:21:38 +00:00
|
|
|
type CommunityAdminSettings struct {
|
|
|
|
PinMessageAllMembersEnabled bool `json:"pinMessageAllMembersEnabled"`
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
type CommunityChat struct {
|
2024-03-06 21:46:41 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
Emoji string `json:"emoji"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Members map[string]*protobuf.CommunityMember `json:"members"`
|
|
|
|
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
|
|
|
CanPost bool `json:"canPost"`
|
2024-03-19 17:14:24 +00:00
|
|
|
CanView bool `json:"canView"`
|
2024-05-13 19:06:21 +00:00
|
|
|
CanPostReactions bool `json:"canPostReactions"`
|
2024-03-06 21:46:41 +00:00
|
|
|
ViewersCanPostReactions bool `json:"viewersCanPostReactions"`
|
|
|
|
Position int `json:"position"`
|
|
|
|
CategoryID string `json:"categoryID"`
|
2024-02-29 09:51:38 +00:00
|
|
|
TokenGated bool `json:"tokenGated"`
|
2024-02-28 11:48:37 +00:00
|
|
|
HideIfPermissionsNotMet bool `json:"hideIfPermissionsNotMet"`
|
2024-06-27 15:29:03 +00:00
|
|
|
MissingEncryptionKey bool `json:"missingEncryptionKey"`
|
2021-05-23 13:34:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityCategory struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Position int `json:"position"` // Position is used to sort the categories
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2022-06-24 13:40:12 +00:00
|
|
|
type CommunityTag struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Emoji string `json:"emoji"`
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
type CommunityMemberState uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
CommunityMemberBanned CommunityMemberState = iota
|
|
|
|
CommunityMemberBanPending
|
|
|
|
CommunityMemberUnbanPending
|
|
|
|
CommunityMemberKickPending
|
2024-03-19 13:40:23 +00:00
|
|
|
CommunityMemberBanWithAllMessagesDelete
|
2023-10-04 20:47:22 +00:00
|
|
|
)
|
|
|
|
|
2021-08-05 13:27:47 +00:00
|
|
|
func (o *Community) MarshalPublicAPIJSON() ([]byte, error) {
|
|
|
|
if o.config.MemberIdentity == nil {
|
|
|
|
return nil, errors.New("member identity not set")
|
|
|
|
}
|
|
|
|
communityItem := struct {
|
2023-08-17 17:14:23 +00:00
|
|
|
ID types.HexBytes `json:"id"`
|
|
|
|
Verified bool `json:"verified"`
|
|
|
|
Chats map[string]CommunityChat `json:"chats"`
|
|
|
|
Categories map[string]CommunityCategory `json:"categories"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
IntroMessage string `json:"introMessage"`
|
|
|
|
OutroMessage string `json:"outroMessage"`
|
|
|
|
Tags []CommunityTag `json:"tags"`
|
|
|
|
Images map[string]images.IdentityImage `json:"images"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
MembersCount int `json:"membersCount"`
|
|
|
|
EnsName string `json:"ensName"`
|
|
|
|
Link string `json:"link"`
|
|
|
|
CommunityAdminSettings CommunityAdminSettings `json:"adminSettings"`
|
|
|
|
Encrypted bool `json:"encrypted"`
|
|
|
|
TokenPermissions map[string]*CommunityTokenPermission `json:"tokenPermissions"`
|
|
|
|
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
|
|
|
ActiveMembersCount uint64 `json:"activeMembersCount"`
|
2023-10-12 19:21:49 +00:00
|
|
|
PubsubTopic string `json:"pubsubTopic"`
|
2023-10-30 18:34:21 +00:00
|
|
|
PubsubTopicKey string `json:"pubsubTopicKey"`
|
2023-11-15 15:58:15 +00:00
|
|
|
Shard *shard.Shard `json:"shard"`
|
2021-08-05 13:27:47 +00:00
|
|
|
}{
|
2023-10-30 18:34:21 +00:00
|
|
|
ID: o.ID(),
|
|
|
|
Verified: o.config.Verified,
|
|
|
|
Chats: make(map[string]CommunityChat),
|
|
|
|
Categories: make(map[string]CommunityCategory),
|
|
|
|
Tags: o.Tags(),
|
|
|
|
PubsubTopic: o.PubsubTopic(),
|
|
|
|
PubsubTopicKey: o.PubsubTopicKey(),
|
|
|
|
Shard: o.Shard(),
|
2021-08-05 13:27:47 +00:00
|
|
|
}
|
2023-10-12 19:21:49 +00:00
|
|
|
|
2021-08-05 13:27:47 +00:00
|
|
|
if o.config.CommunityDescription != nil {
|
|
|
|
for id, c := range o.config.CommunityDescription.Categories {
|
|
|
|
category := CommunityCategory{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Name,
|
|
|
|
Position: int(c.Position),
|
|
|
|
}
|
|
|
|
communityItem.Categories[id] = category
|
2023-10-11 15:10:10 +00:00
|
|
|
communityItem.Encrypted = o.Encrypted()
|
2021-08-05 13:27:47 +00:00
|
|
|
}
|
|
|
|
for id, c := range o.config.CommunityDescription.Chats {
|
2024-05-13 19:06:21 +00:00
|
|
|
// NOTE: Here `CanPost` is only set for ChatMessage and Emoji reactions. But it can be different for pin/etc.
|
2024-03-01 17:15:38 +00:00
|
|
|
// Consider adding more properties to `CommunityChat` to reflect that.
|
2024-06-27 15:29:03 +00:00
|
|
|
canPost, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
2021-08-05 13:27:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-27 15:29:03 +00:00
|
|
|
canPostReactions, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
2024-05-13 19:06:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-27 15:29:03 +00:00
|
|
|
canView := o.CanView(o.MemberIdentity(), id)
|
2024-03-19 17:14:24 +00:00
|
|
|
|
2021-08-05 13:27:47 +00:00
|
|
|
chat := CommunityChat{
|
2024-03-06 21:46:41 +00:00
|
|
|
ID: id,
|
|
|
|
Name: c.Identity.DisplayName,
|
|
|
|
Color: c.Identity.Color,
|
|
|
|
Emoji: c.Identity.Emoji,
|
|
|
|
Description: c.Identity.Description,
|
|
|
|
Permissions: c.Permissions,
|
|
|
|
Members: c.Members,
|
|
|
|
CanPost: canPost,
|
2024-03-19 17:14:24 +00:00
|
|
|
CanView: canView,
|
2024-05-13 19:06:21 +00:00
|
|
|
CanPostReactions: canPostReactions,
|
2024-03-06 21:46:41 +00:00
|
|
|
ViewersCanPostReactions: c.ViewersCanPostReactions,
|
2024-02-29 09:51:38 +00:00
|
|
|
TokenGated: o.channelEncrypted(id),
|
2024-03-06 21:46:41 +00:00
|
|
|
CategoryID: c.CategoryId,
|
2024-02-28 11:48:37 +00:00
|
|
|
HideIfPermissionsNotMet: c.HideIfPermissionsNotMet,
|
2024-03-06 21:46:41 +00:00
|
|
|
Position: int(c.Position),
|
2021-08-05 13:27:47 +00:00
|
|
|
}
|
|
|
|
communityItem.Chats[id] = chat
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
communityItem.TokenPermissions = o.tokenPermissions()
|
2021-08-05 13:27:47 +00:00
|
|
|
communityItem.MembersCount = len(o.config.CommunityDescription.Members)
|
2023-10-12 19:21:49 +00:00
|
|
|
|
2021-08-05 13:27:47 +00:00
|
|
|
communityItem.Link = fmt.Sprintf("https://join.status.im/c/0x%x", o.ID())
|
2023-10-12 19:21:49 +00:00
|
|
|
if o.Shard() != nil {
|
|
|
|
communityItem.Link = fmt.Sprintf("%s/%d/%d", communityItem.Link, o.Shard().Cluster, o.Shard().Index)
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:20:13 +00:00
|
|
|
communityItem.IntroMessage = o.config.CommunityDescription.IntroMessage
|
|
|
|
communityItem.OutroMessage = o.config.CommunityDescription.OutroMessage
|
2023-02-20 11:57:33 +00:00
|
|
|
communityItem.CommunityTokensMetadata = o.config.CommunityDescription.CommunityTokensMetadata
|
2023-03-28 14:40:00 +00:00
|
|
|
communityItem.ActiveMembersCount = o.config.CommunityDescription.ActiveMembersCount
|
2022-05-24 19:20:13 +00:00
|
|
|
|
2021-08-05 13:27:47 +00:00
|
|
|
if o.config.CommunityDescription.Identity != nil {
|
|
|
|
communityItem.Name = o.Name()
|
|
|
|
communityItem.Color = o.config.CommunityDescription.Identity.Color
|
|
|
|
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
|
|
|
for t, i := range o.config.CommunityDescription.Identity.Images {
|
|
|
|
if communityItem.Images == nil {
|
|
|
|
communityItem.Images = make(map[string]images.IdentityImage)
|
|
|
|
}
|
|
|
|
communityItem.Images[t] = images.IdentityImage{Name: t, Payload: i.Payload}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 14:21:38 +00:00
|
|
|
communityItem.CommunityAdminSettings = CommunityAdminSettings{
|
|
|
|
PinMessageAllMembersEnabled: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.AdminSettings != nil {
|
|
|
|
communityItem.CommunityAdminSettings.PinMessageAllMembersEnabled = o.config.CommunityDescription.AdminSettings.PinMessageAllMembersEnabled
|
|
|
|
}
|
2021-08-05 13:27:47 +00:00
|
|
|
}
|
|
|
|
return json.Marshal(communityItem)
|
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
func (o *Community) MarshalJSON() ([]byte, error) {
|
2024-06-24 09:37:44 +00:00
|
|
|
if o.config.MemberIdentity == nil {
|
|
|
|
return nil, errors.New("member identity not set")
|
|
|
|
}
|
|
|
|
|
|
|
|
type Image struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
}
|
|
|
|
communityItem := struct {
|
|
|
|
ID types.HexBytes `json:"id"`
|
|
|
|
MemberRole protobuf.CommunityMember_Roles `json:"memberRole"`
|
|
|
|
IsControlNode bool `json:"isControlNode"`
|
|
|
|
Verified bool `json:"verified"`
|
|
|
|
Joined bool `json:"joined"`
|
|
|
|
JoinedAt int64 `json:"joinedAt"`
|
|
|
|
Spectated bool `json:"spectated"`
|
|
|
|
RequestedAccessAt int `json:"requestedAccessAt"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
IntroMessage string `json:"introMessage"`
|
|
|
|
OutroMessage string `json:"outroMessage"`
|
|
|
|
Tags []CommunityTag `json:"tags"`
|
|
|
|
Chats map[string]CommunityChat `json:"chats"`
|
|
|
|
Categories map[string]CommunityCategory `json:"categories"`
|
|
|
|
Images map[string]Image `json:"images"`
|
|
|
|
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
|
|
|
Members map[string]*protobuf.CommunityMember `json:"members"`
|
|
|
|
CanRequestAccess bool `json:"canRequestAccess"`
|
|
|
|
CanManageUsers bool `json:"canManageUsers"` //TODO: we can remove this
|
|
|
|
CanDeleteMessageForEveryone bool `json:"canDeleteMessageForEveryone"` //TODO: we can remove this
|
|
|
|
CanJoin bool `json:"canJoin"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
RequestedToJoinAt uint64 `json:"requestedToJoinAt,omitempty"`
|
|
|
|
IsMember bool `json:"isMember"`
|
|
|
|
Muted bool `json:"muted"`
|
|
|
|
MuteTill time.Time `json:"muteTill,omitempty"`
|
|
|
|
CommunityAdminSettings CommunityAdminSettings `json:"adminSettings"`
|
|
|
|
Encrypted bool `json:"encrypted"`
|
|
|
|
PendingAndBannedMembers map[string]CommunityMemberState `json:"pendingAndBannedMembers"`
|
|
|
|
TokenPermissions map[string]*CommunityTokenPermission `json:"tokenPermissions"`
|
|
|
|
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
|
|
|
ActiveMembersCount uint64 `json:"activeMembersCount"`
|
|
|
|
PubsubTopic string `json:"pubsubTopic"`
|
|
|
|
PubsubTopicKey string `json:"pubsubTopicKey"`
|
|
|
|
Shard *shard.Shard `json:"shard"`
|
|
|
|
LastOpenedAt int64 `json:"lastOpenedAt"`
|
|
|
|
Clock uint64 `json:"clock"`
|
|
|
|
}{
|
|
|
|
ID: o.ID(),
|
|
|
|
Clock: o.Clock(),
|
|
|
|
MemberRole: o.MemberRole(o.MemberIdentity()),
|
|
|
|
IsControlNode: o.IsControlNode(),
|
|
|
|
Verified: o.config.Verified,
|
|
|
|
Chats: make(map[string]CommunityChat),
|
|
|
|
Categories: make(map[string]CommunityCategory),
|
|
|
|
Joined: o.config.Joined,
|
|
|
|
JoinedAt: o.config.JoinedAt,
|
|
|
|
Spectated: o.config.Spectated,
|
2024-06-27 15:29:03 +00:00
|
|
|
CanRequestAccess: o.CanRequestAccess(o.MemberIdentity()),
|
2024-06-24 09:37:44 +00:00
|
|
|
CanJoin: o.canJoin(),
|
2024-06-27 15:29:03 +00:00
|
|
|
CanManageUsers: o.CanManageUsers(o.MemberIdentity()),
|
|
|
|
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.MemberIdentity()),
|
2024-06-24 09:37:44 +00:00
|
|
|
RequestedToJoinAt: o.RequestedToJoinAt(),
|
|
|
|
IsMember: o.isMember(),
|
|
|
|
Muted: o.config.Muted,
|
|
|
|
MuteTill: o.config.MuteTill,
|
|
|
|
Tags: o.Tags(),
|
|
|
|
Encrypted: o.Encrypted(),
|
|
|
|
PubsubTopic: o.PubsubTopic(),
|
|
|
|
PubsubTopicKey: o.PubsubTopicKey(),
|
|
|
|
Shard: o.Shard(),
|
|
|
|
LastOpenedAt: o.config.LastOpenedAt,
|
|
|
|
}
|
|
|
|
if o.config.CommunityDescription != nil {
|
|
|
|
for id, c := range o.config.CommunityDescription.Categories {
|
|
|
|
category := CommunityCategory{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Name,
|
|
|
|
Position: int(c.Position),
|
|
|
|
}
|
|
|
|
communityItem.Encrypted = o.Encrypted()
|
|
|
|
communityItem.Categories[id] = category
|
|
|
|
}
|
|
|
|
for id, c := range o.config.CommunityDescription.Chats {
|
|
|
|
// NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
|
|
|
|
// Consider adding more properties to `CommunityChat` to reflect that.
|
2024-06-27 15:29:03 +00:00
|
|
|
canPost, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
2024-06-24 09:37:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-27 15:29:03 +00:00
|
|
|
canPostReactions, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
2024-06-24 09:37:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-27 15:29:03 +00:00
|
|
|
canView := o.CanView(o.MemberIdentity(), id)
|
2024-06-24 09:37:44 +00:00
|
|
|
|
|
|
|
chat := CommunityChat{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Identity.DisplayName,
|
|
|
|
Emoji: c.Identity.Emoji,
|
|
|
|
Color: c.Identity.Color,
|
|
|
|
Description: c.Identity.Description,
|
|
|
|
Permissions: c.Permissions,
|
|
|
|
CanPost: canPost,
|
|
|
|
CanView: canView,
|
|
|
|
CanPostReactions: canPostReactions,
|
|
|
|
ViewersCanPostReactions: c.ViewersCanPostReactions,
|
|
|
|
TokenGated: o.channelEncrypted(id),
|
|
|
|
CategoryID: c.CategoryId,
|
|
|
|
HideIfPermissionsNotMet: c.HideIfPermissionsNotMet,
|
|
|
|
Position: int(c.Position),
|
2024-07-04 15:54:48 +00:00
|
|
|
MissingEncryptionKey: o.HasMissingEncryptionKey(id),
|
2024-06-24 09:37:44 +00:00
|
|
|
}
|
2024-07-01 18:52:57 +00:00
|
|
|
|
|
|
|
if chat.TokenGated {
|
|
|
|
chat.Members = c.Members
|
|
|
|
}
|
2024-06-24 09:37:44 +00:00
|
|
|
communityItem.Chats[id] = chat
|
|
|
|
}
|
|
|
|
communityItem.TokenPermissions = o.tokenPermissions()
|
|
|
|
communityItem.PendingAndBannedMembers = o.PendingAndBannedMembers()
|
|
|
|
communityItem.Members = o.config.CommunityDescription.Members
|
|
|
|
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
|
|
|
communityItem.IntroMessage = o.config.CommunityDescription.IntroMessage
|
|
|
|
communityItem.OutroMessage = o.config.CommunityDescription.OutroMessage
|
|
|
|
|
|
|
|
// update token meta image to url rather than base64 image
|
|
|
|
var tokenMetadata []*protobuf.CommunityTokenMetadata
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
if !utils.IsNil(o.mediaServer) {
|
|
|
|
for _, m := range o.config.CommunityDescription.CommunityTokensMetadata {
|
|
|
|
copyM := proto.Clone(m).(*protobuf.CommunityTokenMetadata)
|
|
|
|
copyM.Image = o.mediaServer.MakeCommunityDescriptionTokenImageURL(o.IDString(), copyM.GetSymbol())
|
|
|
|
tokenMetadata = append(tokenMetadata, copyM)
|
2024-06-24 09:37:44 +00:00
|
|
|
}
|
2024-07-01 18:52:57 +00:00
|
|
|
communityItem.CommunityTokensMetadata = tokenMetadata
|
2024-06-24 09:37:44 +00:00
|
|
|
}
|
2023-03-28 14:40:00 +00:00
|
|
|
communityItem.ActiveMembersCount = o.config.CommunityDescription.ActiveMembersCount
|
2022-05-24 19:20:13 +00:00
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
if o.config.CommunityDescription.Identity != nil {
|
2021-03-31 16:23:45 +00:00
|
|
|
communityItem.Name = o.Name()
|
2021-01-11 10:32:51 +00:00
|
|
|
communityItem.Color = o.config.CommunityDescription.Identity.Color
|
|
|
|
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
2024-07-01 18:52:57 +00:00
|
|
|
|
|
|
|
if !utils.IsNil(o.mediaServer) {
|
|
|
|
for t := range o.config.CommunityDescription.Identity.Images {
|
|
|
|
if communityItem.Images == nil {
|
|
|
|
communityItem.Images = make(map[string]Image)
|
|
|
|
}
|
|
|
|
communityItem.Images[t] = Image{Uri: o.mediaServer.MakeCommunityImageURL(o.IDString(), t)}
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 14:21:38 +00:00
|
|
|
communityItem.CommunityAdminSettings = CommunityAdminSettings{
|
|
|
|
PinMessageAllMembersEnabled: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.AdminSettings != nil {
|
|
|
|
communityItem.CommunityAdminSettings.PinMessageAllMembersEnabled = o.config.CommunityDescription.AdminSettings.PinMessageAllMembersEnabled
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
return json.Marshal(communityItem)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) Identity() *protobuf.ChatIdentity {
|
|
|
|
return o.config.CommunityDescription.Identity
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) Permissions() *protobuf.CommunityPermissions {
|
|
|
|
return o.config.CommunityDescription.Permissions
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) AdminSettings() *protobuf.CommunityAdminSettings {
|
|
|
|
return o.config.CommunityDescription.AdminSettings
|
|
|
|
}
|
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
func (o *Community) Name() string {
|
2021-04-19 12:09:46 +00:00
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil &&
|
|
|
|
o.config.CommunityDescription.Identity != nil {
|
|
|
|
return o.config.CommunityDescription.Identity.DisplayName
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (o *Community) DescriptionText() string {
|
2021-04-19 12:09:46 +00:00
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil &&
|
|
|
|
o.config.CommunityDescription.Identity != nil {
|
|
|
|
return o.config.CommunityDescription.Identity.Description
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-11-15 15:58:15 +00:00
|
|
|
func (o *Community) Shard() *shard.Shard {
|
2023-10-12 19:21:49 +00:00
|
|
|
if o != nil && o.config != nil {
|
|
|
|
return o.config.Shard
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-15 19:50:12 +00:00
|
|
|
func (o *Community) CommunityShard() CommunityShard {
|
|
|
|
return CommunityShard{
|
|
|
|
CommunityID: o.IDString(),
|
|
|
|
Shard: o.Shard(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:20:13 +00:00
|
|
|
func (o *Community) IntroMessage() string {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
return o.config.CommunityDescription.IntroMessage
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-02-20 11:57:33 +00:00
|
|
|
func (o *Community) CommunityTokensMetadata() []*protobuf.CommunityTokenMetadata {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
return o.config.CommunityDescription.CommunityTokensMetadata
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-24 13:40:12 +00:00
|
|
|
func (o *Community) Tags() []CommunityTag {
|
2024-03-26 20:02:12 +00:00
|
|
|
if o == nil ||
|
|
|
|
o.config == nil ||
|
|
|
|
o.config.CommunityDescription == nil {
|
|
|
|
return nil
|
2022-06-24 13:40:12 +00:00
|
|
|
}
|
2024-03-26 20:02:12 +00:00
|
|
|
|
|
|
|
result := make([]CommunityTag, 0, len(o.config.CommunityDescription.Tags))
|
|
|
|
for _, t := range o.config.CommunityDescription.Tags {
|
|
|
|
result = append(result, CommunityTag{
|
|
|
|
Name: t,
|
|
|
|
Emoji: requests.TagEmoji(t),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return result
|
2022-06-24 13:40:12 +00:00
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) TagsRaw() []string {
|
|
|
|
return o.config.CommunityDescription.Tags
|
|
|
|
}
|
|
|
|
|
2023-07-04 13:48:52 +00:00
|
|
|
func (o *Community) TagsIndices() []uint32 {
|
2024-03-26 20:02:12 +00:00
|
|
|
var indices []uint32
|
2023-07-04 13:48:52 +00:00
|
|
|
for _, t := range o.config.CommunityDescription.Tags {
|
2024-03-26 20:02:12 +00:00
|
|
|
indices = append(indices, requests.TagIndex(t))
|
2023-07-04 13:48:52 +00:00
|
|
|
}
|
|
|
|
return indices
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:20:13 +00:00
|
|
|
func (o *Community) OutroMessage() string {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
return o.config.CommunityDescription.OutroMessage
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-06-08 12:46:52 +00:00
|
|
|
func (o *Community) Color() string {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil &&
|
|
|
|
o.config.CommunityDescription.Identity != nil {
|
|
|
|
return o.config.CommunityDescription.Identity.Color
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-03-20 10:36:32 +00:00
|
|
|
func (o *Community) Members() map[string]*protobuf.CommunityMember {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
return o.config.CommunityDescription.Members
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
func (o *Community) UpdateMemberLastUpdateClock(publicKey string, clock uint64) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
if member, exists := o.config.CommunityDescription.Members[publicKey]; exists {
|
|
|
|
member.LastUpdateClock = clock
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 12:09:46 +00:00
|
|
|
func (o *Community) MembersCount() int {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
return len(o.config.CommunityDescription.Members)
|
|
|
|
}
|
|
|
|
return 0
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
2022-05-27 09:14:40 +00:00
|
|
|
func (o *Community) GetMemberPubkeys() []*ecdsa.PublicKey {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
pubkeys := make([]*ecdsa.PublicKey, len(o.config.CommunityDescription.Members))
|
|
|
|
i := 0
|
|
|
|
for hex := range o.config.CommunityDescription.Members {
|
|
|
|
pubkeys[i], _ = common.HexToPubkey(hex)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return pubkeys
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-17 16:40:09 +00:00
|
|
|
|
2022-03-08 15:25:00 +00:00
|
|
|
type CommunitySettings struct {
|
|
|
|
CommunityID string `json:"communityId"`
|
|
|
|
HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled"`
|
2022-06-01 07:55:48 +00:00
|
|
|
Clock uint64 `json:"clock"`
|
2022-03-08 15:25:00 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) emptyCommunityChanges() *CommunityChanges {
|
2023-07-13 15:25:43 +00:00
|
|
|
changes := EmptyCommunityChanges()
|
2021-01-11 10:32:51 +00:00
|
|
|
changes.Community = o
|
|
|
|
return changes
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CreateChat(chatID string, chat *protobuf.CommunityChat) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
err := o.createChat(chatID, chat)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-16 12:54:55 +00:00
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
changes.ChatsAdded[chatID] = chat
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
2023-08-16 12:54:55 +00:00
|
|
|
} else {
|
|
|
|
err := o.addNewCommunityEvent(o.ToCreateChannelCommunityEvent(chatID, chat))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2021-06-01 12:13:17 +00:00
|
|
|
func (o *Community) EditChat(chatID string, chat *protobuf.CommunityChat) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
err := o.editChat(chatID, chat)
|
2021-06-01 12:13:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
changes.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
ChatModified: chat,
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
|
|
|
err := o.addNewCommunityEvent(o.ToEditChannelCommunityEvent(chatID, chat))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-01 12:13:17 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) DeleteChat(chatID string) (*CommunityChanges, error) {
|
2020-11-18 09:16:51 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
changes := o.deleteChat(chatID)
|
|
|
|
|
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToDeleteChannelCommunityEvent(chatID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
return changes, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) getMember(pk *ecdsa.PublicKey) *protobuf.CommunityMember {
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
key := common.PubkeyToHex(pk)
|
2021-01-11 10:32:51 +00:00
|
|
|
member := o.config.CommunityDescription.Members[key]
|
|
|
|
return member
|
|
|
|
}
|
|
|
|
|
2023-07-10 14:11:37 +00:00
|
|
|
func (o *Community) GetMember(pk *ecdsa.PublicKey) *protobuf.CommunityMember {
|
|
|
|
return o.getMember(pk)
|
|
|
|
}
|
|
|
|
|
2024-03-07 17:30:23 +00:00
|
|
|
func (o *Community) GetChat(chatID string) (*protobuf.CommunityChat, error) {
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return chat, nil
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
func (o *Community) getChatMember(pk *ecdsa.PublicKey, chatID string) *protobuf.CommunityMember {
|
|
|
|
if !o.hasMember(pk) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
return chat.Members[key]
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) hasMember(pk *ecdsa.PublicKey) bool {
|
|
|
|
|
|
|
|
member := o.getMember(pk)
|
|
|
|
return member != nil
|
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
func (o *Community) IsBanned(pk *ecdsa.PublicKey) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return o.isBanned(pk)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) isBanned(pk *ecdsa.PublicKey) bool {
|
2024-02-22 10:25:13 +00:00
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
banned := slices.Contains(o.config.CommunityDescription.BanList, key)
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.BannedMembers != nil && !banned {
|
|
|
|
_, banned = o.config.CommunityDescription.BannedMembers[key]
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
2024-02-22 10:25:13 +00:00
|
|
|
|
|
|
|
return banned
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
func (o *Community) rolesOf(pk *ecdsa.PublicKey) []protobuf.CommunityMember_Roles {
|
|
|
|
member := o.getMember(pk)
|
|
|
|
if member == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return member.Roles
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func (o *Community) memberHasRoles(member *protobuf.CommunityMember, roles map[protobuf.CommunityMember_Roles]bool) bool {
|
2021-01-11 10:32:51 +00:00
|
|
|
for _, r := range member.Roles {
|
2023-08-02 18:59:26 +00:00
|
|
|
if roles[r] {
|
2021-01-11 10:32:51 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func (o *Community) hasRoles(pk *ecdsa.PublicKey, roles map[protobuf.CommunityMember_Roles]bool) bool {
|
2023-07-12 08:52:32 +00:00
|
|
|
if pk == nil || o.config == nil || o.config.ID == nil {
|
2023-06-14 14:15:46 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
member := o.getMember(pk)
|
|
|
|
if member == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
return o.memberHasRoles(member, roles)
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return o.hasMember(pk)
|
|
|
|
}
|
|
|
|
|
2024-07-04 15:54:48 +00:00
|
|
|
func (o *Community) isMemberInChat(pk *ecdsa.PublicKey, chatID string) bool {
|
|
|
|
return o.getChatMember(pk, chatID) != nil
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) IsMemberInChat(pk *ecdsa.PublicKey, chatID string) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2024-07-04 15:54:48 +00:00
|
|
|
return o.isMemberInChat(pk, chatID)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-27 15:29:03 +00:00
|
|
|
// Uses bloom filter members list to estimate presence in the channel.
|
|
|
|
// False positive rate is 0.1%.
|
|
|
|
func (o *Community) IsMemberLikelyInChat(chatID string) bool {
|
|
|
|
if o.IsControlNode() || o.IsPrivilegedMember(o.MemberIdentity()) || !o.channelEncrypted(chatID) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// For communities controlled by clients that haven't updated to newer version yet we assume no membership.
|
|
|
|
if chat.MembersList == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := verifyMembershipWithBloomFilter(chat.MembersList, o.config.MemberIdentity, o.ControlNode(), chatID, o.Clock())
|
|
|
|
if err != nil {
|
|
|
|
o.config.Logger.Error("failed to estimate membership", zap.Error(err))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) RemoveUserFromChat(pk *ecdsa.PublicKey, chatID string) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
if !o.hasMember(pk) {
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
delete(chat.Members, key)
|
|
|
|
|
2023-07-28 12:14:59 +00:00
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2022-09-14 12:39:55 +00:00
|
|
|
func (o *Community) RemoveOurselvesFromOrg(pk *ecdsa.PublicKey) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
2024-07-05 08:38:12 +00:00
|
|
|
_ = o.RemoveMembersFromOrg([]string{common.PubkeyToHex(pk)})
|
2023-06-14 14:15:46 +00:00
|
|
|
o.increaseClock()
|
2022-09-14 12:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) RemoveUserFromOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2022-09-14 12:39:55 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !o.IsControlNode() && o.IsPrivilegedMember(pk) {
|
2023-06-23 07:02:12 +00:00
|
|
|
return nil, ErrCannotRemoveOwnerOrAdmin
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
pkStr := common.PubkeyToHex(pk)
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2024-07-05 08:38:12 +00:00
|
|
|
_ = o.RemoveMembersFromOrg([]string{pkStr})
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToKickCommunityMemberCommunityEvent(common.PubkeyToHex(pk)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
func (o *Community) RemoveMembersFromOrg(membersToRemove []string) *CommunityChanges {
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
|
|
|
|
if len(membersToRemove) == 0 {
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, pk := range membersToRemove {
|
|
|
|
member, exists := o.config.CommunityDescription.Members[pk]
|
|
|
|
if exists {
|
|
|
|
changes.MembersRemoved[pk] = member
|
|
|
|
delete(o.config.CommunityDescription.Members, pk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(changes.MembersRemoved) == 0 {
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
|
|
|
|
for chatID, chat := range o.config.CommunityDescription.Chats {
|
|
|
|
chatMembersToRemove := make(map[string]*protobuf.CommunityMember)
|
|
|
|
for _, pk := range membersToRemove {
|
|
|
|
chatMember, exists := chat.Members[pk]
|
|
|
|
if exists {
|
|
|
|
chatMembersToRemove[pk] = chatMember
|
|
|
|
delete(chat.Members, pk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
changes.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
ChatModified: chat,
|
|
|
|
MembersRemoved: chatMembersToRemove,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
func (o *Community) RemoveAllUsersFromOrg() *CommunityChanges {
|
|
|
|
o.increaseClock()
|
|
|
|
|
2024-06-27 15:29:03 +00:00
|
|
|
myPublicKey := common.PubkeyToHex(o.MemberIdentity())
|
2023-10-31 14:20:40 +00:00
|
|
|
member := o.config.CommunityDescription.Members[myPublicKey]
|
|
|
|
|
|
|
|
membersToRemove := o.config.CommunityDescription.Members
|
|
|
|
delete(membersToRemove, myPublicKey)
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
changes.MembersRemoved = membersToRemove
|
|
|
|
|
|
|
|
o.config.CommunityDescription.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
o.config.CommunityDescription.Members[myPublicKey] = member
|
|
|
|
|
|
|
|
for chatID, chat := range o.config.CommunityDescription.Chats {
|
|
|
|
chatMembersToRemove := chat.Members
|
|
|
|
delete(chatMembersToRemove, myPublicKey)
|
|
|
|
|
|
|
|
chat.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
chat.Members[myPublicKey] = member
|
|
|
|
|
|
|
|
changes.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
ChatModified: chat,
|
|
|
|
MembersRemoved: chatMembersToRemove,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
|
2023-02-20 11:57:33 +00:00
|
|
|
func (o *Community) AddCommunityTokensMetadata(token *protobuf.CommunityTokenMetadata) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
2023-08-04 10:28:46 +00:00
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2023-08-04 10:28:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
o.config.CommunityDescription.CommunityTokensMetadata = append(o.config.CommunityDescription.CommunityTokensMetadata, token)
|
|
|
|
|
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2023-08-04 10:28:46 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToAddTokenMetadataCommunityEvent(token))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-02-20 11:57:33 +00:00
|
|
|
}
|
2023-08-04 10:28:46 +00:00
|
|
|
|
2023-02-20 11:57:33 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2024-05-21 16:00:32 +00:00
|
|
|
func containsToken(tokens []*protobuf.CommunityTokenMetadata, symbol string) bool {
|
|
|
|
for _, token := range tokens {
|
|
|
|
if token.Symbol == symbol {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) UpsertCommunityTokensMetadata(token *protobuf.CommunityTokenMetadata) (bool, error) {
|
|
|
|
if containsToken(o.config.CommunityDescription.CommunityTokensMetadata, token.Symbol) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := o.AddCommunityTokensMetadata(token)
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
|
2022-06-23 07:12:15 +00:00
|
|
|
func (o *Community) UnbanUserFromCommunity(pk *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2022-06-23 07:12:15 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2023-10-04 20:47:22 +00:00
|
|
|
o.unbanUserFromCommunity(pk)
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToUnbanCommunityMemberCommunityEvent(common.PubkeyToHex(pk)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-23 07:12:15 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
func (o *Community) BanUserFromCommunity(pk *ecdsa.PublicKey, communityBanInfo *protobuf.CommunityBanInfo) (*protobuf.CommunityDescription, error) {
|
2021-03-19 09:15:45 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !o.IsControlNode() && o.IsPrivilegedMember(pk) {
|
2023-06-23 07:02:12 +00:00
|
|
|
return nil, ErrCannotBanOwnerOrAdmin
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2024-02-22 10:25:13 +00:00
|
|
|
o.banUserFromCommunity(pk, communityBanInfo)
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2024-02-29 17:54:17 +00:00
|
|
|
pkStr := common.PubkeyToHex(pk)
|
|
|
|
err := o.addNewCommunityEvent(o.ToBanCommunityMemberCommunityEvent(pkStr))
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-29 17:54:17 +00:00
|
|
|
if communityBanInfo.DeleteAllMessages {
|
|
|
|
err := o.addNewCommunityEvent(o.ToDeleteAllMemberMessagesEvent(pkStr))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
func (o *Community) setRoleToMember(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles, setter func(member *protobuf.CommunityMember, role protobuf.CommunityMember_Roles) bool) (*protobuf.CommunityDescription, error) {
|
2022-12-02 11:34:02 +00:00
|
|
|
updated := false
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
member := o.getMember(pk)
|
|
|
|
if member != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
updated = setter(member, role)
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range o.chats() {
|
|
|
|
chatMember := o.getChatMember(pk, channelID)
|
|
|
|
if chatMember != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
_ = setter(member, role)
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
if updated {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
func (o *Community) SetRoleToMember(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) (*protobuf.CommunityDescription, error) {
|
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
|
|
|
}
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
setRole := func(member *protobuf.CommunityMember, role protobuf.CommunityMember_Roles) bool {
|
|
|
|
if len(member.Roles) == 1 && member.Roles[0] == role {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
member.Roles = []protobuf.CommunityMember_Roles{role}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.setRoleToMember(pk, role, setRole)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deprecated: roles are mutually exclusive, use SetRoleToMember instead.
|
|
|
|
func (o *Community) AddRoleToMember(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) (*protobuf.CommunityDescription, error) {
|
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
|
|
|
}
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
addRole := func(member *protobuf.CommunityMember, role protobuf.CommunityMember_Roles) bool {
|
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
|
|
|
roles[role] = true
|
|
|
|
if !o.memberHasRoles(member, roles) {
|
|
|
|
member.Roles = append(member.Roles, role)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.setRoleToMember(pk, role, addRole)
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
func (o *Community) RemoveRoleFromMember(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2022-12-02 11:34:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updated := false
|
2023-06-23 10:49:26 +00:00
|
|
|
removeRole := func(member *protobuf.CommunityMember) {
|
2022-12-02 11:34:02 +00:00
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
|
|
|
roles[role] = true
|
2023-08-02 18:59:26 +00:00
|
|
|
if o.memberHasRoles(member, roles) {
|
2022-12-02 11:34:02 +00:00
|
|
|
var newRoles []protobuf.CommunityMember_Roles
|
|
|
|
for _, r := range member.Roles {
|
|
|
|
if r != role {
|
|
|
|
newRoles = append(newRoles, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
member.Roles = newRoles
|
|
|
|
updated = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
member := o.getMember(pk)
|
|
|
|
if member != nil {
|
|
|
|
removeRole(member)
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range o.chats() {
|
|
|
|
chatMember := o.getChatMember(pk, channelID)
|
|
|
|
if chatMember != nil {
|
|
|
|
removeRole(member)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
if updated {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2021-05-18 19:32:15 +00:00
|
|
|
func (o *Community) Edit(description *protobuf.CommunityDescription) {
|
|
|
|
o.config.CommunityDescription.Identity.DisplayName = description.Identity.DisplayName
|
|
|
|
o.config.CommunityDescription.Identity.Description = description.Identity.Description
|
|
|
|
o.config.CommunityDescription.Identity.Color = description.Identity.Color
|
2022-07-04 13:56:00 +00:00
|
|
|
o.config.CommunityDescription.Tags = description.Tags
|
2021-10-04 13:02:25 +00:00
|
|
|
o.config.CommunityDescription.Identity.Emoji = description.Identity.Emoji
|
2021-05-18 19:32:15 +00:00
|
|
|
o.config.CommunityDescription.Identity.Images = description.Identity.Images
|
2022-05-24 19:20:13 +00:00
|
|
|
o.config.CommunityDescription.IntroMessage = description.IntroMessage
|
|
|
|
o.config.CommunityDescription.OutroMessage = description.OutroMessage
|
2022-05-10 14:21:38 +00:00
|
|
|
if o.config.CommunityDescription.AdminSettings == nil {
|
|
|
|
o.config.CommunityDescription.AdminSettings = &protobuf.CommunityAdminSettings{}
|
|
|
|
}
|
2022-06-21 17:43:49 +00:00
|
|
|
o.config.CommunityDescription.Permissions = description.Permissions
|
2022-05-10 14:21:38 +00:00
|
|
|
o.config.CommunityDescription.AdminSettings.PinMessageAllMembersEnabled = description.AdminSettings.PinMessageAllMembersEnabled
|
2021-05-18 19:32:15 +00:00
|
|
|
}
|
|
|
|
|
2024-05-01 17:27:31 +00:00
|
|
|
func (o *Community) EditPermissionAccess(permissionAccess protobuf.CommunityPermissions_Access) {
|
|
|
|
o.config.CommunityDescription.Permissions.Access = permissionAccess
|
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Join() {
|
|
|
|
o.config.Joined = true
|
2024-01-09 18:36:47 +00:00
|
|
|
o.config.JoinedAt = time.Now().Unix()
|
2023-07-04 11:51:21 +00:00
|
|
|
o.config.Spectated = false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-01-21 10:55:14 +00:00
|
|
|
func (o *Community) UpdateLastOpenedAt(timestamp int64) {
|
|
|
|
o.config.LastOpenedAt = timestamp
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Leave() {
|
|
|
|
o.config.Joined = false
|
2022-09-20 19:57:39 +00:00
|
|
|
o.config.Spectated = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) Spectate() {
|
|
|
|
o.config.Spectated = true
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-05-27 09:14:40 +00:00
|
|
|
func (o *Community) Encrypted() bool {
|
2023-10-11 15:10:10 +00:00
|
|
|
return len(o.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)) > 0
|
2022-05-27 09:14:40 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Joined() bool {
|
|
|
|
return o.config.Joined
|
|
|
|
}
|
|
|
|
|
2024-01-09 18:36:47 +00:00
|
|
|
func (o *Community) JoinedAt() int64 {
|
|
|
|
return o.config.JoinedAt
|
|
|
|
}
|
|
|
|
|
2024-01-21 10:55:14 +00:00
|
|
|
func (o *Community) LastOpenedAt() int64 {
|
|
|
|
return o.config.LastOpenedAt
|
|
|
|
}
|
|
|
|
|
2022-09-20 19:57:39 +00:00
|
|
|
func (o *Community) Spectated() bool {
|
|
|
|
return o.config.Spectated
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (o *Community) Verified() bool {
|
|
|
|
return o.config.Verified
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) Muted() bool {
|
|
|
|
return o.config.Muted
|
|
|
|
}
|
|
|
|
|
2023-06-17 08:19:05 +00:00
|
|
|
func (o *Community) MuteTill() time.Time {
|
|
|
|
return o.config.MuteTill
|
|
|
|
}
|
|
|
|
|
2022-02-11 20:59:43 +00:00
|
|
|
func (o *Community) MemberIdentity() *ecdsa.PublicKey {
|
2024-06-27 15:29:03 +00:00
|
|
|
return &o.config.MemberIdentity.PublicKey
|
2022-02-11 20:59:43 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
// UpdateCommunityDescription will update the community to the new community description and return a list of changes
|
2023-10-31 14:20:40 +00:00
|
|
|
func (o *Community) UpdateCommunityDescription(description *protobuf.CommunityDescription, rawMessage []byte, newControlNode *ecdsa.PublicKey) (*CommunityChanges, error) {
|
2020-11-18 09:16:51 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-13 17:49:19 +00:00
|
|
|
// This is done in case tags are updated and a client sends unknown tags
|
|
|
|
description.Tags = requests.RemoveUnknownAndDeduplicateTags(description.Tags)
|
|
|
|
|
|
|
|
err := ValidateCommunityDescription(description)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-18 16:59:23 +00:00
|
|
|
// Enables processing of identical clocks. Identical descriptions may be reprocessed upon subsequent receipt of the previously missing encryption key.
|
|
|
|
if description.Clock < o.config.CommunityDescription.Clock {
|
2024-06-10 14:20:21 +00:00
|
|
|
return nil, ErrInvalidCommunityDescriptionClockOutdated
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
originCommunity := o.CreateDeepCopy()
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
o.config.CommunityDescription = description
|
2023-07-10 15:35:15 +00:00
|
|
|
o.config.CommunityDescriptionProtocolMessage = rawMessage
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
if newControlNode != nil {
|
|
|
|
o.setControlNode(newControlNode)
|
|
|
|
}
|
|
|
|
|
2024-06-10 14:20:21 +00:00
|
|
|
response := o.emptyCommunityChanges()
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
// We only calculate changes if we joined/spectated the community or we requested access, otherwise not interested
|
|
|
|
if o.config.Joined || o.config.Spectated || o.config.RequestedToJoinAt > 0 {
|
|
|
|
response = EvaluateCommunityChanges(originCommunity, o)
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
func (o *Community) UpdateChatFirstMessageTimestamp(chatID string, timestamp uint32) (*CommunityChanges, error) {
|
2023-07-21 09:41:26 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2022-09-02 08:36:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.Identity.FirstMessageTimestamp = timestamp
|
|
|
|
|
|
|
|
communityChanges := o.emptyCommunityChanges()
|
|
|
|
communityChanges.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
FirstMessageTimestampModified: timestamp,
|
|
|
|
}
|
|
|
|
return communityChanges, nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
// ValidateRequestToJoin validates a request, checks that the right permissions are applied
|
|
|
|
func (o *Community) ValidateRequestToJoin(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoin) error {
|
2020-11-18 09:16:51 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
if o.IsControlNode() {
|
|
|
|
// TODO: Enable this once mobile supports revealed addresses.
|
|
|
|
// if len(request.RevealedAccounts) == 0 {
|
|
|
|
// return errors.New("no addresses revealed")
|
|
|
|
// }
|
|
|
|
} else if o.HasPermissionToSendCommunityEvents() {
|
2023-10-25 13:16:49 +00:00
|
|
|
if o.AutoAccept() {
|
2023-10-12 21:42:03 +00:00
|
|
|
return errors.New("auto-accept community requests can only be processed by the control node")
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-18 09:16:51 +00:00
|
|
|
return ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Permissions.EnsOnly && len(request.EnsName) == 0 {
|
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(request.ChatId) != 0 {
|
2021-01-11 10:32:51 +00:00
|
|
|
return o.validateRequestToJoinWithChatID(request)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
err := o.validateRequestToJoinWithoutChatID(request)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
if o.isBanned(signer) {
|
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
timeNow := uint64(time.Now().Unix())
|
|
|
|
requestTimeOutClock, err := AddTimeoutToRequestToJoinClock(request.Clock)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if timeNow >= requestTimeOutClock {
|
|
|
|
return errors.New("request is expired")
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-10 14:11:37 +00:00
|
|
|
// ValidateRequestToJoin validates a request, checks that the right permissions are applied
|
2024-07-05 13:43:03 +00:00
|
|
|
func (o *Community) ValidateEditSharedAddresses(signer string, request *protobuf.CommunityEditSharedAddresses) error {
|
2023-07-10 14:11:37 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
if len(request.RevealedAccounts) == 0 {
|
|
|
|
return errors.New("no addresses were shared")
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
member, exists := o.config.CommunityDescription.Members[signer]
|
2024-03-22 14:25:37 +00:00
|
|
|
if !exists {
|
|
|
|
return errors.New("signer is not a community member")
|
|
|
|
}
|
|
|
|
|
|
|
|
if request.Clock < member.LastUpdateClock {
|
2024-07-05 13:43:03 +00:00
|
|
|
return ErrEditSharedAddressesRequestOutdated
|
2023-07-10 14:11:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
// We treat control node as an owner with community key
|
2023-07-21 09:41:26 +00:00
|
|
|
func (o *Community) IsControlNode() bool {
|
2023-09-21 11:16:05 +00:00
|
|
|
return o.config.PrivateKey != nil && o.config.PrivateKey.PublicKey.Equal(o.ControlNode()) && o.config.ControlDevice
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
func (o *Community) IsOwner() bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return o.IsMemberOwner(o.MemberIdentity())
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-04 10:28:46 +00:00
|
|
|
func (o *Community) IsTokenMaster() bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return o.IsMemberTokenMaster(o.MemberIdentity())
|
2023-08-04 10:28:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 15:02:56 +00:00
|
|
|
func (o *Community) IsAdmin() bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return o.IsMemberAdmin(o.MemberIdentity())
|
2023-08-08 15:02:56 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
func (o *Community) GetTokenMasterMembers() []*ecdsa.PublicKey {
|
|
|
|
tokenMasterMembers := make([]*ecdsa.PublicKey, 0)
|
|
|
|
members := o.GetMemberPubkeys()
|
|
|
|
for _, member := range members {
|
|
|
|
if o.IsMemberTokenMaster(member) {
|
|
|
|
tokenMasterMembers = append(tokenMasterMembers, member)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tokenMasterMembers
|
|
|
|
}
|
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
func (o *Community) GetPrivilegedMembers() []*ecdsa.PublicKey {
|
|
|
|
privilegedMembers := make([]*ecdsa.PublicKey, 0)
|
|
|
|
members := o.GetMemberPubkeys()
|
|
|
|
for _, member := range members {
|
|
|
|
if o.IsPrivilegedMember(member) {
|
|
|
|
privilegedMembers = append(privilegedMembers, member)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return privilegedMembers
|
|
|
|
}
|
|
|
|
|
2023-09-20 08:37:46 +00:00
|
|
|
func (o *Community) GetFilteredPrivilegedMembers(skipMembers map[string]struct{}) map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey {
|
|
|
|
privilegedMembers := make(map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey)
|
|
|
|
privilegedMembers[protobuf.CommunityMember_ROLE_TOKEN_MASTER] = []*ecdsa.PublicKey{}
|
|
|
|
privilegedMembers[protobuf.CommunityMember_ROLE_ADMIN] = []*ecdsa.PublicKey{}
|
|
|
|
privilegedMembers[protobuf.CommunityMember_ROLE_OWNER] = []*ecdsa.PublicKey{}
|
|
|
|
|
|
|
|
members := o.GetMemberPubkeys()
|
|
|
|
for _, member := range members {
|
|
|
|
if len(skipMembers) > 0 {
|
|
|
|
if _, exist := skipMembers[common.PubkeyToHex(member)]; exist {
|
|
|
|
delete(skipMembers, common.PubkeyToHex(member))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memberRole := o.MemberRole(member)
|
|
|
|
if memberRole == protobuf.CommunityMember_ROLE_OWNER || memberRole == protobuf.CommunityMember_ROLE_ADMIN ||
|
|
|
|
memberRole == protobuf.CommunityMember_ROLE_TOKEN_MASTER {
|
|
|
|
|
|
|
|
privilegedMembers[memberRole] = append(privilegedMembers[memberRole], member)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return privilegedMembers
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
func (o *Community) HasPermissionToSendCommunityEvents() bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return !o.IsControlNode() && o.hasRoles(o.MemberIdentity(), manageCommunityRoles())
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
func (o *Community) hasPermissionToSendCommunityEvent(event protobuf.CommunityEvent_EventType) bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return !o.IsControlNode() && canRolesPerformEvent(o.rolesOf(o.MemberIdentity()), event)
|
2023-08-16 12:54:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) hasPermissionToSendTokenPermissionCommunityEvent(event protobuf.CommunityEvent_EventType, permissionType protobuf.CommunityTokenPermission_Type) bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
roles := o.rolesOf(o.MemberIdentity())
|
2023-08-16 12:54:55 +00:00
|
|
|
return !o.IsControlNode() && canRolesPerformEvent(roles, event) && canRolesModifyPermission(roles, permissionType)
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) IsMemberOwner(publicKey *ecdsa.PublicKey) bool {
|
2023-08-02 18:59:26 +00:00
|
|
|
return o.hasRoles(publicKey, ownerRole())
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
func (o *Community) IsMemberTokenMaster(publicKey *ecdsa.PublicKey) bool {
|
2023-08-02 18:59:26 +00:00
|
|
|
return o.hasRoles(publicKey, tokenMasterRole())
|
2023-07-28 18:18:27 +00:00
|
|
|
}
|
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
func (o *Community) IsMemberAdmin(publicKey *ecdsa.PublicKey) bool {
|
2023-08-02 18:59:26 +00:00
|
|
|
return o.hasRoles(publicKey, adminRole())
|
2021-03-31 16:23:45 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
func (o *Community) IsPrivilegedMember(publicKey *ecdsa.PublicKey) bool {
|
2023-08-02 18:59:26 +00:00
|
|
|
return o.hasRoles(publicKey, manageCommunityRoles())
|
2023-07-26 12:16:50 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func manageCommunityRoles() map[protobuf.CommunityMember_Roles]bool {
|
2023-07-26 12:16:50 +00:00
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
|
|
|
roles[protobuf.CommunityMember_ROLE_OWNER] = true
|
|
|
|
roles[protobuf.CommunityMember_ROLE_ADMIN] = true
|
2023-07-26 16:01:19 +00:00
|
|
|
roles[protobuf.CommunityMember_ROLE_TOKEN_MASTER] = true
|
2023-07-26 12:16:50 +00:00
|
|
|
return roles
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func ownerRole() map[protobuf.CommunityMember_Roles]bool {
|
2023-06-14 14:15:46 +00:00
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
|
|
|
roles[protobuf.CommunityMember_ROLE_OWNER] = true
|
|
|
|
return roles
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func adminRole() map[protobuf.CommunityMember_Roles]bool {
|
2021-03-31 16:23:45 +00:00
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
2023-06-14 14:15:46 +00:00
|
|
|
roles[protobuf.CommunityMember_ROLE_ADMIN] = true
|
|
|
|
return roles
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
func tokenMasterRole() map[protobuf.CommunityMember_Roles]bool {
|
2023-07-28 18:18:27 +00:00
|
|
|
roles := make(map[protobuf.CommunityMember_Roles]bool)
|
|
|
|
roles[protobuf.CommunityMember_ROLE_TOKEN_MASTER] = true
|
|
|
|
return roles
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) MemberRole(pubKey *ecdsa.PublicKey) protobuf.CommunityMember_Roles {
|
|
|
|
if o.IsMemberOwner(pubKey) {
|
|
|
|
return protobuf.CommunityMember_ROLE_OWNER
|
2023-07-31 08:46:59 +00:00
|
|
|
} else if o.IsMemberTokenMaster(pubKey) {
|
|
|
|
return protobuf.CommunityMember_ROLE_TOKEN_MASTER
|
2023-06-14 14:15:46 +00:00
|
|
|
} else if o.IsMemberAdmin(pubKey) {
|
|
|
|
return protobuf.CommunityMember_ROLE_ADMIN
|
|
|
|
}
|
|
|
|
|
|
|
|
return protobuf.CommunityMember_ROLE_NONE
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error {
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[request.ChatId]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
// If chat is no permissions, access should not have been requested
|
2023-10-25 13:03:26 +00:00
|
|
|
if chat.Permissions.Access != protobuf.CommunityPermissions_MANUAL_ACCEPT {
|
2020-11-18 09:16:51 +00:00
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Permissions.EnsOnly && len(request.EnsName) == 0 {
|
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-25 13:16:49 +00:00
|
|
|
func (o *Community) ManualAccept() bool {
|
2023-10-25 13:03:26 +00:00
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_MANUAL_ACCEPT
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 13:16:49 +00:00
|
|
|
func (o *Community) AutoAccept() bool {
|
2022-07-01 13:54:02 +00:00
|
|
|
// We no longer have the notion of "no membership", but for historical reasons
|
|
|
|
// we use `NO_MEMBERSHIP` to determine wether requests to join should be automatically
|
|
|
|
// accepted or not.
|
2023-10-25 13:03:26 +00:00
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_AUTO_ACCEPT
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
func (o *Community) validateRequestToJoinWithoutChatID(request *protobuf.CommunityRequestToJoin) error {
|
|
|
|
// Previously, requests to join a community where only necessary when the community
|
|
|
|
// permissions were indeed set to `ON_REQUEST`.
|
|
|
|
// Now, users always have to request access but can get accepted automatically
|
|
|
|
// (if permissions are set to NO_MEMBERSHIP).
|
|
|
|
//
|
|
|
|
// Hence, not only do we check whether the community permissions are ON_REQUEST but
|
|
|
|
// also NO_MEMBERSHIP.
|
2023-10-25 13:03:26 +00:00
|
|
|
if o.config.CommunityDescription.Permissions.Access != protobuf.CommunityPermissions_MANUAL_ACCEPT && o.config.CommunityDescription.Permissions.Access != protobuf.CommunityPermissions_AUTO_ACCEPT {
|
2020-11-18 09:16:51 +00:00
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) ID() types.HexBytes {
|
2020-11-18 09:16:51 +00:00
|
|
|
return crypto.CompressPubkey(o.config.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) IDString() string {
|
|
|
|
return types.EncodeHex(o.ID())
|
|
|
|
}
|
|
|
|
|
2023-11-10 16:33:37 +00:00
|
|
|
func (o *Community) UncompressedIDString() string {
|
|
|
|
return types.EncodeHex(crypto.FromECDSAPub(o.config.ID))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) SerializedID() (string, error) {
|
|
|
|
return multiformat.SerializeLegacyKey(o.UncompressedIDString())
|
|
|
|
}
|
|
|
|
|
2021-07-22 17:41:49 +00:00
|
|
|
func (o *Community) StatusUpdatesChannelID() string {
|
|
|
|
return o.IDString() + "-ping"
|
|
|
|
}
|
|
|
|
|
2022-03-08 13:54:53 +00:00
|
|
|
func (o *Community) MagnetlinkMessageChannelID() string {
|
|
|
|
return o.IDString() + "-magnetlinks"
|
|
|
|
}
|
|
|
|
|
2022-07-08 10:25:46 +00:00
|
|
|
func (o *Community) MemberUpdateChannelID() string {
|
|
|
|
return o.IDString() + "-memberUpdate"
|
|
|
|
}
|
|
|
|
|
2023-05-22 21:38:02 +00:00
|
|
|
func (o *Community) PubsubTopic() string {
|
2023-11-15 15:58:15 +00:00
|
|
|
return o.Shard().PubsubTopic()
|
2023-05-22 21:38:02 +00:00
|
|
|
}
|
|
|
|
|
2023-10-30 18:34:21 +00:00
|
|
|
func (o *Community) PubsubTopicPrivateKey() *ecdsa.PrivateKey {
|
|
|
|
return o.config.PubsubTopicPrivateKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) SetPubsubTopicPrivateKey(privKey *ecdsa.PrivateKey) {
|
|
|
|
o.config.PubsubTopicPrivateKey = privKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) PubsubTopicKey() string {
|
|
|
|
if o.config.PubsubTopicPrivateKey == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return hexutil.Encode(crypto.FromECDSAPub(&o.config.PubsubTopicPrivateKey.PublicKey))
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) PrivateKey() *ecdsa.PrivateKey {
|
|
|
|
return o.config.PrivateKey
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
func (o *Community) setPrivateKey(pk *ecdsa.PrivateKey) {
|
|
|
|
if pk != nil {
|
|
|
|
o.config.PrivateKey = pk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
func (o *Community) SetResendAccountsClock(clock uint64) {
|
|
|
|
o.config.CommunityDescription.ResendAccountsClock = clock
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
func (o *Community) ControlNode() *ecdsa.PublicKey {
|
2023-10-19 16:52:57 +00:00
|
|
|
if o.config.ControlNode == nil {
|
|
|
|
return o.config.ID
|
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
return o.config.ControlNode
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) setControlNode(pubKey *ecdsa.PublicKey) {
|
|
|
|
if pubKey != nil {
|
|
|
|
o.config.ControlNode = pubKey
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) PublicKey() *ecdsa.PublicKey {
|
|
|
|
return o.config.ID
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (o *Community) Description() *protobuf.CommunityDescription {
|
|
|
|
return o.config.CommunityDescription
|
|
|
|
}
|
|
|
|
|
2024-03-20 19:18:18 +00:00
|
|
|
func (o *Community) EncryptedDescription() (*protobuf.CommunityDescription, error) {
|
|
|
|
clone := proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription)
|
|
|
|
if o.encryptor != nil {
|
|
|
|
err := encryptDescription(o.encryptor, o, clone)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return clone, nil
|
|
|
|
}
|
|
|
|
|
2024-01-08 15:57:57 +00:00
|
|
|
func (o *Community) DescriptionProtocolMessage() []byte {
|
|
|
|
return o.config.CommunityDescriptionProtocolMessage
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) marshaledDescription() ([]byte, error) {
|
2023-11-29 17:21:21 +00:00
|
|
|
clone := proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription)
|
|
|
|
|
2023-09-26 16:37:23 +00:00
|
|
|
// This is only workaround to lower the size of the message that goes over the wire,
|
|
|
|
// see https://github.com/status-im/status-desktop/issues/12188
|
2024-06-27 09:36:18 +00:00
|
|
|
dehydrateChannelsMembers(clone)
|
2023-11-29 17:21:21 +00:00
|
|
|
|
2024-06-27 15:29:03 +00:00
|
|
|
err := generateBloomFiltersForChannels(clone, o.PrivateKey())
|
|
|
|
if err != nil {
|
|
|
|
o.config.Logger.Error("failed to generate bloom filters", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
if o.encryptor != nil {
|
|
|
|
err := encryptDescription(o.encryptor, o, clone)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return proto.Marshal(clone)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) MarshaledDescription() ([]byte, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return o.marshaledDescription()
|
|
|
|
}
|
|
|
|
|
2023-07-10 15:35:15 +00:00
|
|
|
func (o *Community) toProtocolMessageBytes() ([]byte, error) {
|
2023-07-21 09:38:34 +00:00
|
|
|
// If we are not a control node, use the received serialized version
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() {
|
2023-09-26 16:37:23 +00:00
|
|
|
// This should not happen, as we can only serialize on our side if we
|
|
|
|
// created the community
|
|
|
|
if len(o.config.CommunityDescriptionProtocolMessage) == 0 {
|
|
|
|
return nil, ErrNotControlNode
|
|
|
|
}
|
|
|
|
|
2023-07-10 15:35:15 +00:00
|
|
|
return o.config.CommunityDescriptionProtocolMessage, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
// serialize
|
2020-11-18 09:16:51 +00:00
|
|
|
payload, err := o.marshaledDescription()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
// sign
|
2020-11-18 09:16:51 +00:00
|
|
|
return protocol.WrapMessageV1(payload, protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION, o.config.PrivateKey)
|
|
|
|
}
|
|
|
|
|
2023-07-10 15:35:15 +00:00
|
|
|
// ToProtocolMessageBytes returns the community in a wrapped & signed protocol message
|
|
|
|
func (o *Community) ToProtocolMessageBytes() ([]byte, error) {
|
2020-11-18 09:16:51 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
2023-07-10 15:35:15 +00:00
|
|
|
return o.toProtocolMessageBytes()
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
func dehydrateChannelsMembers(description *protobuf.CommunityDescription) {
|
2023-11-29 17:21:21 +00:00
|
|
|
// To save space, we don't attach members for channels without permissions,
|
|
|
|
// otherwise the message will hit waku msg size limit.
|
|
|
|
for channelID, channel := range description.Chats {
|
2024-07-15 17:53:20 +00:00
|
|
|
if !channelHasPermissions(ChatID(description.ID, channelID), description.TokenPermissions) {
|
2023-11-29 17:21:21 +00:00
|
|
|
channel.Members = map[string]*protobuf.CommunityMember{} // clean members
|
2023-09-25 11:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
2023-09-25 11:26:17 +00:00
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
func hydrateChannelsMembers(description *protobuf.CommunityDescription) {
|
2023-09-25 11:26:17 +00:00
|
|
|
for channelID, channel := range description.Chats {
|
2024-07-15 17:53:20 +00:00
|
|
|
if !channelHasPermissions(ChatID(description.ID, channelID), description.TokenPermissions) {
|
2023-09-25 11:26:17 +00:00
|
|
|
channel.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
for pubKey, member := range description.Members {
|
|
|
|
channel.Members[pubKey] = member
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-13 10:23:11 +00:00
|
|
|
func upgradeTokenPermissions(description *protobuf.CommunityDescription) {
|
|
|
|
|
|
|
|
floatToWeiIntFunc := func(floatStr string, decimals uint64) string {
|
|
|
|
bigfloat := new(big.Float)
|
|
|
|
bigfloat.SetString(floatStr)
|
|
|
|
|
|
|
|
multiplier := big.NewFloat(math.Pow(10, float64(decimals)))
|
|
|
|
bigfloat.Mul(bigfloat, multiplier)
|
|
|
|
|
|
|
|
result := new(big.Int)
|
|
|
|
bigfloat.Int(result)
|
|
|
|
return result.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, permission := range description.TokenPermissions {
|
|
|
|
for _, criteria := range permission.TokenCriteria {
|
|
|
|
if criteria.AmountInWei != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// set AmountInWei if missing
|
|
|
|
// Amount format (deprecated): "0.123"
|
|
|
|
// AmountInWei format: "123000..000"
|
|
|
|
if criteria.Type == protobuf.CommunityTokenType_ERC20 {
|
|
|
|
criteria.AmountInWei = floatToWeiIntFunc(criteria.Amount, criteria.Decimals)
|
|
|
|
} else {
|
|
|
|
criteria.AmountInWei = criteria.Amount
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Chats() map[string]*protobuf.CommunityChat {
|
2023-01-13 17:12:46 +00:00
|
|
|
// Why are we checking here for nil, it should be the responsibility of the caller
|
|
|
|
if o == nil {
|
2023-06-23 10:49:26 +00:00
|
|
|
return make(map[string]*protobuf.CommunityChat)
|
2022-06-08 12:46:52 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
return o.chats()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) chats() map[string]*protobuf.CommunityChat {
|
|
|
|
response := make(map[string]*protobuf.CommunityChat)
|
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
if o.config != nil && o.config.CommunityDescription != nil {
|
2022-06-08 12:46:52 +00:00
|
|
|
for k, v := range o.config.CommunityDescription.Chats {
|
|
|
|
response[k] = v
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2022-06-08 12:46:52 +00:00
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2022-06-08 12:46:52 +00:00
|
|
|
func (o *Community) Images() map[string]*protobuf.IdentityImage {
|
|
|
|
response := make(map[string]*protobuf.IdentityImage)
|
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
// Why are we checking here for nil, it should be the responsibility of the caller
|
|
|
|
if o == nil {
|
2022-06-08 12:46:52 +00:00
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
if o.config != nil && o.config.CommunityDescription != nil && o.config.CommunityDescription.Identity != nil {
|
2022-06-08 12:46:52 +00:00
|
|
|
for k, v := range o.config.CommunityDescription.Identity.Images {
|
|
|
|
response[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
2021-05-23 13:34:17 +00:00
|
|
|
|
2022-06-08 12:46:52 +00:00
|
|
|
func (o *Community) Categories() map[string]*protobuf.CommunityCategory {
|
2021-05-23 13:34:17 +00:00
|
|
|
response := make(map[string]*protobuf.CommunityCategory)
|
2022-06-08 12:46:52 +00:00
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
if o == nil {
|
2022-06-08 12:46:52 +00:00
|
|
|
return response
|
2021-05-23 13:34:17 +00:00
|
|
|
}
|
2022-06-08 12:46:52 +00:00
|
|
|
|
2023-01-13 17:12:46 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
if o.config != nil && o.config.CommunityDescription != nil {
|
2022-06-08 12:46:52 +00:00
|
|
|
for k, v := range o.config.CommunityDescription.Categories {
|
|
|
|
response[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func (o *Community) tokenPermissions() map[string]*CommunityTokenPermission {
|
|
|
|
result := make(map[string]*CommunityTokenPermission, len(o.config.CommunityDescription.TokenPermissions))
|
|
|
|
for _, tokenPermission := range o.config.CommunityDescription.TokenPermissions {
|
|
|
|
result[tokenPermission.Id] = NewCommunityTokenPermission(tokenPermission)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Non-privileged members should not see pending permissions
|
|
|
|
if o.config.EventsData == nil || !o.IsPrivilegedMember(o.MemberIdentity()) {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
processedPermissions := make(map[string]*struct{})
|
|
|
|
for _, event := range o.config.EventsData.Events {
|
|
|
|
if event.TokenPermission == nil || processedPermissions[event.TokenPermission.Id] != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
processedPermissions[event.TokenPermission.Id] = &struct{}{} // first permission event wins
|
|
|
|
|
|
|
|
switch event.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE:
|
2023-08-23 14:23:53 +00:00
|
|
|
eventsTokenPermission := NewCommunityTokenPermission(event.TokenPermission)
|
|
|
|
if result[event.TokenPermission.Id] != nil {
|
|
|
|
eventsTokenPermission.State = TokenPermissionUpdatePending
|
2023-08-17 17:14:23 +00:00
|
|
|
} else {
|
2023-08-23 14:23:53 +00:00
|
|
|
eventsTokenPermission.State = TokenPermissionAdditionPending
|
2023-08-17 17:14:23 +00:00
|
|
|
}
|
2023-08-23 14:23:53 +00:00
|
|
|
result[eventsTokenPermission.Id] = eventsTokenPermission
|
2023-08-17 17:14:23 +00:00
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
|
|
|
|
tokenPermission := result[event.TokenPermission.Id]
|
|
|
|
if tokenPermission != nil {
|
|
|
|
tokenPermission.State = TokenPermissionRemovalPending
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
func (o *Community) PendingAndBannedMembers() map[string]CommunityMemberState {
|
|
|
|
result := make(map[string]CommunityMemberState)
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
if o.config.CommunityDescription.BannedMembers != nil {
|
2024-03-19 13:40:23 +00:00
|
|
|
for bannedMemberID, banInfo := range o.config.CommunityDescription.BannedMembers {
|
|
|
|
state := CommunityMemberBanned
|
|
|
|
if banInfo.DeleteAllMessages {
|
|
|
|
state = CommunityMemberBanWithAllMessagesDelete
|
|
|
|
}
|
|
|
|
result[bannedMemberID] = state
|
2024-02-22 10:25:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
for _, bannedMemberID := range o.config.CommunityDescription.BanList {
|
2024-02-22 10:25:13 +00:00
|
|
|
if _, exists := result[bannedMemberID]; !exists {
|
|
|
|
result[bannedMemberID] = CommunityMemberBanned
|
|
|
|
}
|
2023-10-04 20:47:22 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:00:29 +00:00
|
|
|
if o.config.EventsData == nil {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
processedEvents := make(map[string]bool)
|
|
|
|
for _, event := range o.config.EventsData.Events {
|
|
|
|
if processedEvents[event.MemberToAction] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch event.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
|
|
|
|
result[event.MemberToAction] = CommunityMemberKickPending
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN:
|
|
|
|
result[event.MemberToAction] = CommunityMemberBanPending
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
|
|
|
|
result[event.MemberToAction] = CommunityMemberUnbanPending
|
|
|
|
default:
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
processedEvents[event.MemberToAction] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func (o *Community) TokenPermissions() map[string]*CommunityTokenPermission {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return o.tokenPermissions()
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
}
|
|
|
|
|
2023-03-24 15:43:53 +00:00
|
|
|
func (o *Community) HasTokenPermissions() bool {
|
2023-08-17 17:14:23 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return len(o.tokenPermissions()) > 0
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 17:53:20 +00:00
|
|
|
func channelHasPermissions(chatID string, permissions map[string]*protobuf.CommunityTokenPermission) bool {
|
|
|
|
for _, p := range permissions {
|
|
|
|
if includes(p.ChatIds, chatID) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
func channelEncrypted(chatID string, permissions map[string]*protobuf.CommunityTokenPermission) bool {
|
2024-05-15 08:43:15 +00:00
|
|
|
hasPermission := false
|
|
|
|
viewableByEveryone := false
|
2023-10-26 15:09:43 +00:00
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
for _, p := range permissions {
|
2024-05-15 08:43:15 +00:00
|
|
|
if !includes(p.ChatIds, chatID) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
hasPermission = true
|
|
|
|
|
|
|
|
if p.Type == protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL &&
|
|
|
|
len(p.TokenCriteria) == 0 {
|
|
|
|
viewableByEveryone = true
|
|
|
|
break
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-15 08:43:15 +00:00
|
|
|
return hasPermission && !viewableByEveryone
|
2023-03-24 15:43:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
func (o *Community) channelEncrypted(channelID string) bool {
|
|
|
|
return channelEncrypted(o.ChatID(channelID), o.config.CommunityDescription.TokenPermissions)
|
|
|
|
}
|
|
|
|
|
2024-05-15 08:43:15 +00:00
|
|
|
func (o *Community) ChannelEncrypted(channelID string) bool {
|
2023-11-29 17:21:21 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
2024-05-15 08:43:15 +00:00
|
|
|
return o.channelEncrypted(channelID)
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
2024-07-04 15:54:48 +00:00
|
|
|
func (o *Community) HasMissingEncryptionKey(channelID string) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
return o.channelEncrypted(channelID) &&
|
|
|
|
!o.isMemberInChat(o.MemberIdentity(), channelID) &&
|
|
|
|
o.IsMemberLikelyInChat(channelID)
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func TokenPermissionsByType(permissions map[string]*CommunityTokenPermission, permissionType protobuf.CommunityTokenPermission_Type) []*CommunityTokenPermission {
|
|
|
|
result := make([]*CommunityTokenPermission, 0)
|
2023-07-13 17:49:19 +00:00
|
|
|
for _, tokenPermission := range permissions {
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
if tokenPermission.Type == permissionType {
|
2023-07-13 17:49:19 +00:00
|
|
|
result = append(result, tokenPermission)
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-13 17:49:19 +00:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func (o *Community) tokenPermissionByID(ID string) *CommunityTokenPermission {
|
|
|
|
return o.tokenPermissions()[ID]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) TokenPermissionByID(ID string) *CommunityTokenPermission {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
2023-08-17 15:19:18 +00:00
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
return o.tokenPermissionByID(ID)
|
2023-08-17 15:19:18 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func (o *Community) TokenPermissionsByType(permissionType protobuf.CommunityTokenPermission_Type) []*CommunityTokenPermission {
|
|
|
|
return TokenPermissionsByType(o.tokenPermissions(), permissionType)
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func (o *Community) ChannelTokenPermissionsByType(channelID string, permissionType protobuf.CommunityTokenPermission_Type) []*CommunityTokenPermission {
|
|
|
|
permissions := make([]*CommunityTokenPermission, 0)
|
|
|
|
for _, tokenPermission := range o.tokenPermissions() {
|
2023-06-12 15:17:37 +00:00
|
|
|
if tokenPermission.Type == permissionType && includes(tokenPermission.ChatIds, channelID) {
|
|
|
|
permissions = append(permissions, tokenPermission)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return permissions
|
|
|
|
}
|
|
|
|
|
|
|
|
func includes(channelIDs []string, channelID string) bool {
|
|
|
|
for _, id := range channelIDs {
|
|
|
|
if id == channelID {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:19:18 +00:00
|
|
|
func (o *Community) UpsertTokenPermission(tokenPermission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
2023-03-02 16:27:48 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2023-08-17 17:14:23 +00:00
|
|
|
changes, err := o.upsertTokenPermission(tokenPermission)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
2023-08-17 17:14:23 +00:00
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.hasPermissionToSendTokenPermissionCommunityEvent(protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE, tokenPermission.Type) {
|
|
|
|
existed := o.tokenPermissionByID(tokenPermission.Id) != nil
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityTokenPermissionChangeCommunityEvent(tokenPermission))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-17 17:14:23 +00:00
|
|
|
|
|
|
|
permission := NewCommunityTokenPermission(tokenPermission)
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
if existed {
|
|
|
|
permission.State = TokenPermissionUpdatePending
|
|
|
|
changes.TokenPermissionsModified[tokenPermission.Id] = permission
|
|
|
|
} else {
|
|
|
|
permission.State = TokenPermissionAdditionPending
|
|
|
|
changes.TokenPermissionsAdded[tokenPermission.Id] = permission
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes, nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
return nil, ErrNotAuthorized
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) DeleteTokenPermission(permissionID string) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
tokenPermission, exists := o.config.CommunityDescription.TokenPermissions[permissionID]
|
2023-06-14 14:15:46 +00:00
|
|
|
if !exists {
|
2023-03-02 16:27:48 +00:00
|
|
|
return nil, ErrTokenPermissionNotFound
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2023-08-17 17:14:23 +00:00
|
|
|
changes, err := o.deleteTokenPermission(permissionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
2023-08-17 17:14:23 +00:00
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.hasPermissionToSendTokenPermissionCommunityEvent(protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE, tokenPermission.Type) {
|
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityTokenPermissionDeleteCommunityEvent(tokenPermission))
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-17 17:14:23 +00:00
|
|
|
|
|
|
|
permission := NewCommunityTokenPermission(tokenPermission)
|
|
|
|
permission.State = TokenPermissionRemovalPending
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
changes.TokenPermissionsModified[permission.Id] = permission
|
|
|
|
|
|
|
|
return changes, nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
return nil, ErrNotAuthorized
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) VerifyGrantSignature(data []byte) (*protobuf.Grant, error) {
|
|
|
|
if len(data) <= signatureLength {
|
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
|
|
|
signature := data[:signatureLength]
|
|
|
|
payload := data[signatureLength:]
|
|
|
|
grant := &protobuf.Grant{}
|
|
|
|
err := proto.Unmarshal(payload, grant)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if grant.Clock == 0 {
|
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
|
|
|
if grant.MemberId == nil {
|
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
|
|
|
if !bytes.Equal(grant.CommunityId, o.ID()) {
|
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
2024-04-17 14:53:51 +00:00
|
|
|
if grant.Expires < uint64(time.Now().UnixMilli()) {
|
|
|
|
return nil, ErrGrantExpired
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
extractedPublicKey, err := crypto.SigToPub(crypto.Keccak256(payload), signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:56:54 +00:00
|
|
|
if !common.IsPubKeyEqual(o.ControlNode(), extractedPublicKey) {
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
|
|
|
|
|
|
|
return grant, nil
|
|
|
|
}
|
|
|
|
|
2024-03-19 17:14:24 +00:00
|
|
|
func (o *Community) CanView(pk *ecdsa.PublicKey, chatID string) bool {
|
2020-11-18 09:16:51 +00:00
|
|
|
if o.config.CommunityDescription.Chats == nil {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: no-chats")
|
|
|
|
return false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: no chat with id", zap.String("chat-id", chatID))
|
|
|
|
return false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 14:26:18 +00:00
|
|
|
// community creator can always post, return immediately
|
2023-12-04 09:56:54 +00:00
|
|
|
if common.IsPubKeyEqual(pk, o.ControlNode()) {
|
2024-03-19 17:14:24 +00:00
|
|
|
return true
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
if o.isBanned(pk) {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: user is banned", zap.String("chat-id", chatID))
|
|
|
|
return false
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
if o.config.CommunityDescription.Members == nil {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: no members in org", zap.String("chat-id", chatID))
|
|
|
|
return false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 14:26:18 +00:00
|
|
|
// If community member, also check chat membership next
|
2020-11-18 09:16:51 +00:00
|
|
|
_, ok = o.config.CommunityDescription.Members[common.PubkeyToHex(pk)]
|
2023-10-25 14:26:18 +00:00
|
|
|
if !ok {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: not a community member", zap.String("chat-id", chatID))
|
|
|
|
return false
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 14:26:18 +00:00
|
|
|
if chat.Members == nil {
|
2024-03-19 17:14:24 +00:00
|
|
|
o.config.Logger.Debug("Community.CanView: no members in chat", zap.String("chat-id", chatID))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_, isChatMember := chat.Members[common.PubkeyToHex(pk)]
|
|
|
|
return isChatMember
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CanPost(pk *ecdsa.PublicKey, chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
|
|
|
|
hasAccessToChat := o.CanView(pk, chatID)
|
|
|
|
if !hasAccessToChat {
|
2020-11-18 09:16:51 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2024-03-19 17:14:24 +00:00
|
|
|
chat := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
member := chat.Members[common.PubkeyToHex(pk)]
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2024-03-01 17:15:38 +00:00
|
|
|
switch messageType {
|
|
|
|
case protobuf.ApplicationMetadataMessage_PIN_MESSAGE:
|
|
|
|
pinAllowed := o.IsPrivilegedMember(pk) || o.AllowsAllMembersToPinMessage()
|
2024-03-19 17:14:24 +00:00
|
|
|
return pinAllowed, nil
|
2024-03-01 17:15:38 +00:00
|
|
|
|
|
|
|
case protobuf.ApplicationMetadataMessage_EMOJI_REACTION:
|
2024-03-08 19:46:59 +00:00
|
|
|
isPoster := member.GetChannelRole() == protobuf.CommunityMember_CHANNEL_ROLE_POSTER
|
|
|
|
isViewer := member.GetChannelRole() == protobuf.CommunityMember_CHANNEL_ROLE_VIEWER
|
2024-03-01 17:15:38 +00:00
|
|
|
return isPoster || (isViewer && chat.ViewersCanPostReactions), nil
|
|
|
|
|
|
|
|
default:
|
2024-03-19 17:14:24 +00:00
|
|
|
return member.GetChannelRole() == protobuf.CommunityMember_CHANNEL_ROLE_POSTER, nil
|
2024-03-01 17:15:38 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
func (o *Community) BuildGrant(key *ecdsa.PublicKey, chatID string) ([]byte, error) {
|
|
|
|
return o.buildGrant(key, chatID)
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) buildGrant(key *ecdsa.PublicKey, chatID string) ([]byte, error) {
|
2023-06-14 14:15:46 +00:00
|
|
|
bytes := make([]byte, 0)
|
2023-07-05 17:35:22 +00:00
|
|
|
if o.IsControlNode() {
|
2023-06-14 14:15:46 +00:00
|
|
|
grant := &protobuf.Grant{
|
|
|
|
CommunityId: o.ID(),
|
|
|
|
MemberId: crypto.CompressPubkey(key),
|
|
|
|
ChatId: chatID,
|
|
|
|
Clock: o.config.CommunityDescription.Clock,
|
2024-04-17 14:53:51 +00:00
|
|
|
Expires: uint64(time.Now().Add(GrantExpirationTime).UnixMilli()),
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
marshaledGrant, err := proto.Marshal(grant)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
signatureMaterial := crypto.Keccak256(marshaledGrant)
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
signature, err := crypto.Sign(signatureMaterial, o.config.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
bytes = append(signature, marshaledGrant...)
|
|
|
|
}
|
|
|
|
return bytes, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) increaseClock() {
|
|
|
|
o.config.CommunityDescription.Clock = o.nextClock()
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) Clock() uint64 {
|
|
|
|
return o.config.CommunityDescription.Clock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CanRequestAccess(pk *ecdsa.PublicKey) bool {
|
|
|
|
if o.hasMember(pk) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Permissions == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-10-25 13:03:26 +00:00
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_MANUAL_ACCEPT
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 10:14:53 +00:00
|
|
|
return o.IsPrivilegedMember(pk)
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
2022-12-02 11:34:02 +00:00
|
|
|
|
|
|
|
func (o *Community) CanDeleteMessageForEveryone(pk *ecdsa.PublicKey) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 10:14:53 +00:00
|
|
|
return o.IsPrivilegedMember(pk)
|
2022-12-02 11:34:02 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) isMember() bool {
|
2024-06-27 15:29:03 +00:00
|
|
|
return o.hasMember(o.MemberIdentity())
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2024-03-01 17:15:38 +00:00
|
|
|
func (o *Community) CanMemberIdentityPost(chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
|
2024-06-27 15:29:03 +00:00
|
|
|
return o.CanPost(o.MemberIdentity(), chatID, messageType)
|
2022-02-09 21:58:33 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
// CanJoin returns whether a user can join the community, only if it's
|
|
|
|
func (o *Community) canJoin() bool {
|
|
|
|
if o.config.Joined {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if o.IsControlNode() {
|
2021-01-11 10:32:51 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-10-25 13:03:26 +00:00
|
|
|
if o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_AUTO_ACCEPT {
|
2021-01-11 10:32:51 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.isMember()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) RequestedToJoinAt() uint64 {
|
|
|
|
return o.config.RequestedToJoinAt
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) nextClock() uint64 {
|
2023-09-28 15:37:03 +00:00
|
|
|
// lamport timestamp
|
|
|
|
clock := o.config.CommunityDescription.Clock
|
|
|
|
timestamp := o.timesource.GetCurrentTime()
|
|
|
|
if clock == 0 || clock < timestamp {
|
|
|
|
clock = timestamp
|
|
|
|
} else {
|
|
|
|
clock = clock + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return clock
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
|
2021-03-31 16:23:45 +00:00
|
|
|
func (o *Community) CanManageUsersPublicKeys() ([]*ecdsa.PublicKey, error) {
|
|
|
|
var response []*ecdsa.PublicKey
|
2023-08-16 10:14:53 +00:00
|
|
|
roles := manageCommunityRoles()
|
2021-03-31 16:23:45 +00:00
|
|
|
for pkString, member := range o.config.CommunityDescription.Members {
|
2023-08-02 18:59:26 +00:00
|
|
|
if o.memberHasRoles(member, roles) {
|
2021-03-31 16:23:45 +00:00
|
|
|
pk, err := common.HexToPubkey(pkString)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response = append(response, pk)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (o *Community) AddRequestToJoin(request *RequestToJoin) {
|
|
|
|
o.config.RequestsToJoin = append(o.config.RequestsToJoin, request)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) RequestsToJoin() []*RequestToJoin {
|
|
|
|
return o.config.RequestsToJoin
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:22:22 +00:00
|
|
|
func (o *Community) AddMember(publicKey *ecdsa.PublicKey, roles []protobuf.CommunityMember_Roles, lastUpdateClock uint64) (*CommunityChanges, error) {
|
2023-08-16 12:54:55 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memberKey := common.PubkeyToHex(publicKey)
|
2023-06-14 14:15:46 +00:00
|
|
|
changes := o.emptyCommunityChanges()
|
2022-07-01 13:54:02 +00:00
|
|
|
|
|
|
|
if o.config.CommunityDescription.Members == nil {
|
|
|
|
o.config.CommunityDescription.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := o.config.CommunityDescription.Members[memberKey]; !ok {
|
2024-07-05 13:22:22 +00:00
|
|
|
o.config.CommunityDescription.Members[memberKey] = &protobuf.CommunityMember{Roles: roles, LastUpdateClock: lastUpdateClock}
|
2023-06-14 14:15:46 +00:00
|
|
|
changes.MembersAdded[memberKey] = o.config.CommunityDescription.Members[memberKey]
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
o.increaseClock()
|
2024-07-05 13:22:22 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
return changes, nil
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
|
|
|
|
2024-03-01 17:15:38 +00:00
|
|
|
func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey,
|
|
|
|
roles []protobuf.CommunityMember_Roles, channelRole protobuf.CommunityMember_ChannelRole) (*CommunityChanges, error) {
|
|
|
|
|
2023-07-13 17:49:19 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2023-07-13 17:49:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memberKey := common.PubkeyToHex(publicKey)
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Members == nil {
|
|
|
|
chat.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
}
|
|
|
|
chat.Members[memberKey] = &protobuf.CommunityMember{
|
2024-03-01 17:15:38 +00:00
|
|
|
Roles: roles,
|
|
|
|
ChannelRole: channelRole,
|
2023-07-13 17:49:19 +00:00
|
|
|
}
|
|
|
|
changes.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
ChatModified: chat,
|
|
|
|
MembersAdded: map[string]*protobuf.CommunityMember{
|
|
|
|
memberKey: chat.Members[memberKey],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-07-28 12:14:59 +00:00
|
|
|
if o.IsControlNode() {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
2023-07-13 17:49:19 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (o *Community) PopulateChannelsWithAllMembers() {
|
|
|
|
members := o.Members()
|
|
|
|
for _, channel := range o.Chats() {
|
|
|
|
channel.Members = members
|
|
|
|
}
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
2023-07-26 14:12:53 +00:00
|
|
|
func (o *Community) PopulateChatWithAllMembers(chatID string) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
|
|
|
if !o.IsControlNode() {
|
|
|
|
return o.emptyCommunityChanges(), ErrNotControlNode
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.populateChatWithAllMembers(chatID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) populateChatWithAllMembers(chatID string) (*CommunityChanges, error) {
|
|
|
|
result := o.emptyCommunityChanges()
|
|
|
|
|
|
|
|
chat, exists := o.chats()[chatID]
|
|
|
|
if !exists {
|
|
|
|
return result, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
membersAdded := make(map[string]*protobuf.CommunityMember)
|
|
|
|
for pubKey, member := range o.Members() {
|
|
|
|
if chat.Members[pubKey] == nil {
|
|
|
|
membersAdded[pubKey] = member
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
MembersAdded: membersAdded,
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.Members = o.Members()
|
2023-07-28 12:14:59 +00:00
|
|
|
o.increaseClock()
|
|
|
|
|
2023-07-26 14:12:53 +00:00
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2024-06-27 09:36:18 +00:00
|
|
|
func ChatID(communityID, channelID string) string {
|
|
|
|
return communityID + channelID
|
|
|
|
}
|
|
|
|
|
2023-11-03 16:42:50 +00:00
|
|
|
func (o *Community) ChatID(channelID string) string {
|
2024-06-27 09:36:18 +00:00
|
|
|
return ChatID(o.IDString(), channelID)
|
2023-11-03 16:42:50 +00:00
|
|
|
}
|
|
|
|
|
2022-03-09 09:58:05 +00:00
|
|
|
func (o *Community) ChatIDs() (chatIDs []string) {
|
2023-11-03 16:42:50 +00:00
|
|
|
for channelID := range o.config.CommunityDescription.Chats {
|
|
|
|
chatIDs = append(chatIDs, o.ChatID(channelID))
|
2022-03-09 09:58:05 +00:00
|
|
|
}
|
|
|
|
return chatIDs
|
|
|
|
}
|
|
|
|
|
2022-05-10 14:21:38 +00:00
|
|
|
func (o *Community) AllowsAllMembersToPinMessage() bool {
|
|
|
|
return o.config.CommunityDescription.AdminSettings != nil && o.config.CommunityDescription.AdminSettings.PinMessageAllMembersEnabled
|
|
|
|
}
|
|
|
|
|
2023-07-17 16:40:09 +00:00
|
|
|
func (o *Community) CreateDeepCopy() *Community {
|
2023-07-18 15:06:12 +00:00
|
|
|
return &Community{
|
2024-01-23 16:56:51 +00:00
|
|
|
encryptor: o.encryptor,
|
2023-06-14 14:15:46 +00:00
|
|
|
config: &Config{
|
2023-07-10 15:35:15 +00:00
|
|
|
PrivateKey: o.config.PrivateKey,
|
2023-10-18 15:04:02 +00:00
|
|
|
ControlNode: o.config.ControlNode,
|
2023-09-21 11:16:05 +00:00
|
|
|
ControlDevice: o.config.ControlDevice,
|
2023-07-10 15:35:15 +00:00
|
|
|
CommunityDescription: proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription),
|
|
|
|
CommunityDescriptionProtocolMessage: o.config.CommunityDescriptionProtocolMessage,
|
|
|
|
ID: o.config.ID,
|
|
|
|
Joined: o.config.Joined,
|
2024-01-09 18:36:47 +00:00
|
|
|
JoinedAt: o.config.JoinedAt,
|
2023-07-10 15:35:15 +00:00
|
|
|
Requested: o.config.Requested,
|
|
|
|
Verified: o.config.Verified,
|
|
|
|
Spectated: o.config.Spectated,
|
|
|
|
Muted: o.config.Muted,
|
2024-02-12 23:26:32 +00:00
|
|
|
MuteTill: o.config.MuteTill,
|
2023-07-10 15:35:15 +00:00
|
|
|
Logger: o.config.Logger,
|
|
|
|
RequestedToJoinAt: o.config.RequestedToJoinAt,
|
|
|
|
RequestsToJoin: o.config.RequestsToJoin,
|
|
|
|
MemberIdentity: o.config.MemberIdentity,
|
|
|
|
EventsData: o.config.EventsData,
|
2024-02-12 23:26:32 +00:00
|
|
|
Shard: o.config.Shard,
|
|
|
|
PubsubTopicPrivateKey: o.config.PubsubTopicPrivateKey,
|
2024-01-21 10:55:14 +00:00
|
|
|
LastOpenedAt: o.config.LastOpenedAt,
|
2023-06-14 14:15:46 +00:00
|
|
|
},
|
2023-09-28 15:37:03 +00:00
|
|
|
timesource: o.timesource,
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-28 14:40:00 +00:00
|
|
|
func (o *Community) SetActiveMembersCount(activeMembersCount uint64) (updated bool, err error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return false, ErrNotControlNode
|
2023-03-28 14:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if activeMembersCount == o.config.CommunityDescription.ActiveMembersCount {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.ActiveMembersCount = activeMembersCount
|
|
|
|
o.increaseClock()
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
type sortSlice []sorterHelperIdx
|
|
|
|
type sorterHelperIdx struct {
|
|
|
|
pos int32
|
|
|
|
catID string
|
|
|
|
chatID string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d sortSlice) Len() int {
|
|
|
|
return len(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d sortSlice) Swap(i, j int) {
|
|
|
|
d[i], d[j] = d[j], d[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d sortSlice) Less(i, j int) bool {
|
|
|
|
return d[i].pos < d[j].pos
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
|
|
|
func (o *Community) unbanUserFromCommunity(pk *ecdsa.PublicKey) {
|
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
for i, v := range o.config.CommunityDescription.BanList {
|
|
|
|
if v == key {
|
|
|
|
o.config.CommunityDescription.BanList =
|
|
|
|
append(o.config.CommunityDescription.BanList[:i], o.config.CommunityDescription.BanList[i+1:]...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2024-02-22 10:25:13 +00:00
|
|
|
|
|
|
|
if o.config.CommunityDescription.BannedMembers != nil {
|
|
|
|
delete(o.config.CommunityDescription.BannedMembers, key)
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
func (o *Community) banUserFromCommunity(pk *ecdsa.PublicKey, communityBanInfo *protobuf.CommunityBanInfo) {
|
2023-06-14 14:15:46 +00:00
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
if o.hasMember(pk) {
|
|
|
|
// Remove from org
|
|
|
|
delete(o.config.CommunityDescription.Members, key)
|
|
|
|
|
|
|
|
// Remove from chats
|
|
|
|
for _, chat := range o.config.CommunityDescription.Chats {
|
|
|
|
delete(chat.Members, key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
if o.config.CommunityDescription.BannedMembers == nil {
|
|
|
|
o.config.CommunityDescription.BannedMembers = make(map[string]*protobuf.CommunityBanInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := o.config.CommunityDescription.BannedMembers[key]; !exists {
|
|
|
|
o.config.CommunityDescription.BannedMembers[key] = communityBanInfo
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
for _, u := range o.config.CommunityDescription.BanList {
|
|
|
|
if u == key {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.BanList = append(o.config.CommunityDescription.BanList, key)
|
|
|
|
}
|
|
|
|
|
2024-02-29 17:54:17 +00:00
|
|
|
func (o *Community) deleteBannedMemberAllMessages(pk *ecdsa.PublicKey) error {
|
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.BannedMembers == nil {
|
|
|
|
return ErrBannedMemberNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := o.config.CommunityDescription.BannedMembers[key]; !exists {
|
|
|
|
return ErrBannedMemberNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.BannedMembers[key].DeleteAllMessages = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) editChat(chatID string, chat *protobuf.CommunityChat) error {
|
|
|
|
err := validateCommunityChat(o.config.CommunityDescription, chat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Chats == nil {
|
|
|
|
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
|
|
|
|
}
|
|
|
|
if _, exists := o.config.CommunityDescription.Chats[chatID]; !exists {
|
|
|
|
return ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.Chats[chatID] = chat
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) createChat(chatID string, chat *protobuf.CommunityChat) error {
|
|
|
|
err := validateCommunityChat(o.config.CommunityDescription, chat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Chats == nil {
|
|
|
|
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
|
|
|
|
}
|
|
|
|
if _, ok := o.config.CommunityDescription.Chats[chatID]; ok {
|
|
|
|
return ErrChatAlreadyExists
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range o.config.CommunityDescription.Chats {
|
|
|
|
if chat.Identity.DisplayName == c.Identity.DisplayName {
|
|
|
|
return ErrInvalidCommunityDescriptionDuplicatedName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets the chat position to be the last within its category
|
|
|
|
chat.Position = 0
|
|
|
|
for _, c := range o.config.CommunityDescription.Chats {
|
|
|
|
if c.CategoryId == chat.CategoryId {
|
|
|
|
chat.Position++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 17:01:02 +00:00
|
|
|
chat.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
for pubKey, member := range o.config.CommunityDescription.Members {
|
|
|
|
chat.Members[pubKey] = member
|
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
o.config.CommunityDescription.Chats[chatID] = chat
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) deleteChat(chatID string) *CommunityChanges {
|
|
|
|
if o.config.CommunityDescription.Chats == nil {
|
|
|
|
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
|
|
|
|
}
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
|
|
|
|
if chat, exists := o.config.CommunityDescription.Chats[chatID]; exists {
|
|
|
|
tmpCatID := chat.CategoryId
|
|
|
|
chat.CategoryId = ""
|
|
|
|
o.SortCategoryChats(changes, tmpCatID)
|
|
|
|
changes.ChatsRemoved[chatID] = chat
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(o.config.CommunityDescription.Chats, chatID)
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:19:18 +00:00
|
|
|
func (o *Community) upsertTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
|
|
|
existed := o.tokenPermissionByID(permission.Id) != nil
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
if o.config.CommunityDescription.TokenPermissions == nil {
|
|
|
|
o.config.CommunityDescription.TokenPermissions = make(map[string]*protobuf.CommunityTokenPermission)
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
o.config.CommunityDescription.TokenPermissions[permission.Id] = permission
|
|
|
|
|
2023-08-17 15:19:18 +00:00
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
if existed {
|
2023-08-17 17:14:23 +00:00
|
|
|
changes.TokenPermissionsModified[permission.Id] = NewCommunityTokenPermission(permission)
|
2023-08-17 15:19:18 +00:00
|
|
|
} else {
|
2023-08-17 17:14:23 +00:00
|
|
|
changes.TokenPermissionsAdded[permission.Id] = NewCommunityTokenPermission(permission)
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
|
|
|
return changes, nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (o *Community) deleteTokenPermission(permissionID string) (*CommunityChanges, error) {
|
2023-07-13 17:49:19 +00:00
|
|
|
permission, exists := o.config.CommunityDescription.TokenPermissions[permissionID]
|
|
|
|
if !exists {
|
|
|
|
return nil, ErrTokenPermissionNotFound
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
delete(o.config.CommunityDescription.TokenPermissions, permissionID)
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
2023-07-26 14:13:31 +00:00
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
changes.TokenPermissionsRemoved[permissionID] = NewCommunityTokenPermission(permission)
|
2024-05-17 16:15:39 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
func (o *Community) DeclineRequestToJoin(dbRequest *RequestToJoin) (adminEventCreated bool, err error) {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT)) {
|
2023-10-04 19:02:17 +00:00
|
|
|
return adminEventCreated, ErrNotAuthorized
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if o.IsControlNode() {
|
2024-07-05 08:38:12 +00:00
|
|
|
o.RemoveMembersFromOrg([]string{dbRequest.PublicKey})
|
2023-08-16 12:54:55 +00:00
|
|
|
o.increaseClock()
|
|
|
|
} else {
|
2024-02-19 09:52:22 +00:00
|
|
|
err = o.addNewCommunityEvent(o.ToCommunityRequestToJoinRejectCommunityEvent(dbRequest.PublicKey, dbRequest.ToCommunityRequestToJoinProtobuf()))
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
2023-10-04 19:02:17 +00:00
|
|
|
return adminEventCreated, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-10-04 19:02:17 +00:00
|
|
|
|
|
|
|
adminEventCreated = true
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
return adminEventCreated, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-08-08 18:33:29 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (o *Community) validateEvent(event *CommunityEvent, signer *ecdsa.PublicKey) error {
|
|
|
|
err := event.Validate()
|
2023-08-08 18:33:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
eventSender := o.getMember(signer)
|
|
|
|
if eventSender == nil {
|
2023-08-08 18:33:29 +00:00
|
|
|
return ErrMemberNotFound
|
|
|
|
}
|
|
|
|
|
2023-10-04 20:47:22 +00:00
|
|
|
eventTargetRoles := []protobuf.CommunityMember_Roles{}
|
|
|
|
eventTargetPk, err := common.HexToPubkey(event.MemberToAction)
|
|
|
|
if err == nil {
|
|
|
|
eventTarget := o.getMember(eventTargetPk)
|
|
|
|
if eventTarget != nil {
|
|
|
|
eventTargetRoles = eventTarget.Roles
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !RolesAuthorizedToPerformEvent(eventSender.Roles, eventTargetRoles, event) {
|
2023-08-08 18:33:29 +00:00
|
|
|
return ErrNotAuthorized
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-08-15 17:42:40 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (o *Community) ValidateEvent(event *CommunityEvent, signer *ecdsa.PublicKey) error {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
return o.validateEvent(event, signer)
|
|
|
|
}
|
|
|
|
|
2023-08-15 17:42:40 +00:00
|
|
|
func (o *Community) MemberCanManageToken(member *ecdsa.PublicKey, token *community_token.CommunityToken) bool {
|
2023-07-05 17:35:22 +00:00
|
|
|
return o.IsMemberOwner(member) || o.IsControlNode() || (o.IsMemberTokenMaster(member) &&
|
2023-08-15 17:42:40 +00:00
|
|
|
token.PrivilegesLevel != community_token.OwnerLevel && token.PrivilegesLevel != community_token.MasterLevel)
|
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
|
|
|
|
func CommunityDescriptionTokenOwnerChainID(description *protobuf.CommunityDescription) uint64 {
|
|
|
|
if description == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// We look in TokenPermissions for a token that grants BECOME_TOKEN_OWNER rights
|
|
|
|
// There should be only one, and it's only a single chainID
|
|
|
|
for _, p := range description.TokenPermissions {
|
|
|
|
if p.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER && len(p.TokenCriteria) != 0 {
|
|
|
|
|
|
|
|
for _, criteria := range p.TokenCriteria {
|
|
|
|
for chainID := range criteria.ContractAddresses {
|
|
|
|
return chainID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func HasTokenOwnership(description *protobuf.CommunityDescription) bool {
|
|
|
|
return uint64(0) != CommunityDescriptionTokenOwnerChainID(description)
|
|
|
|
}
|