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"
|
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"
|
|
|
|
|
|
|
|
"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"
|
|
|
|
"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"
|
2023-06-21 11:20:43 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
2020-11-18 09:16:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const signatureLength = 65
|
|
|
|
|
|
|
|
type Config struct {
|
2023-07-10 15:35:15 +00:00
|
|
|
PrivateKey *ecdsa.PrivateKey
|
|
|
|
CommunityDescription *protobuf.CommunityDescription
|
|
|
|
CommunityDescriptionProtocolMessage []byte // community in a wrapped & signed (by owner) protocol message
|
|
|
|
ID *ecdsa.PublicKey
|
|
|
|
Joined bool
|
|
|
|
Requested bool
|
|
|
|
Verified bool
|
|
|
|
Spectated bool
|
|
|
|
Muted bool
|
|
|
|
MuteTill time.Time
|
|
|
|
Logger *zap.Logger
|
|
|
|
RequestedToJoinAt uint64
|
|
|
|
RequestsToJoin []*RequestToJoin
|
|
|
|
MemberIdentity *ecdsa.PublicKey
|
|
|
|
SyncedAt uint64
|
|
|
|
EventsData *EventsData
|
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 {
|
|
|
|
config *Config
|
|
|
|
mutex sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(config Config) (*Community, error) {
|
2021-01-11 10:32:51 +00:00
|
|
|
if config.MemberIdentity == nil {
|
|
|
|
return nil, errors.New("no member identity")
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
community := &Community{config: &config}
|
|
|
|
community.initialize()
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
2021-10-04 13:02:25 +00:00
|
|
|
Color string `json:"color"`
|
|
|
|
Emoji string `json:"emoji"`
|
2021-06-01 12:13:17 +00:00
|
|
|
Description string `json:"description"`
|
2021-01-11 10:32:51 +00:00
|
|
|
Members map[string]*protobuf.CommunityMember `json:"members"`
|
|
|
|
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
|
|
|
CanPost bool `json:"canPost"`
|
2021-05-23 13:34:17 +00:00
|
|
|
Position int `json:"position"`
|
|
|
|
CategoryID string `json:"categoryID"`
|
|
|
|
}
|
|
|
|
|
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
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-02-20 11:57:33 +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"`
|
|
|
|
BanList []string `json:"banList"`
|
|
|
|
TokenPermissions map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissions"`
|
|
|
|
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
2023-03-28 14:40:00 +00:00
|
|
|
ActiveMembersCount uint64 `json:"activeMembersCount"`
|
2021-08-05 13:27:47 +00:00
|
|
|
}{
|
|
|
|
ID: o.ID(),
|
|
|
|
Verified: o.config.Verified,
|
|
|
|
Chats: make(map[string]CommunityChat),
|
|
|
|
Categories: make(map[string]CommunityCategory),
|
2022-06-24 13:40:12 +00:00
|
|
|
Tags: o.Tags(),
|
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
|
2022-05-27 09:14:40 +00:00
|
|
|
communityItem.Encrypted = o.config.CommunityDescription.Encrypted
|
2021-08-05 13:27:47 +00:00
|
|
|
}
|
|
|
|
for id, c := range o.config.CommunityDescription.Chats {
|
|
|
|
canPost, err := o.CanPost(o.config.MemberIdentity, id, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
chat := CommunityChat{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Identity.DisplayName,
|
2021-10-04 13:02:25 +00:00
|
|
|
Color: c.Identity.Color,
|
|
|
|
Emoji: c.Identity.Emoji,
|
2021-08-05 13:27:47 +00:00
|
|
|
Description: c.Identity.Description,
|
|
|
|
Permissions: c.Permissions,
|
|
|
|
Members: c.Members,
|
|
|
|
CanPost: canPost,
|
|
|
|
CategoryID: c.CategoryId,
|
|
|
|
Position: int(c.Position),
|
|
|
|
}
|
|
|
|
communityItem.Chats[id] = chat
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
|
|
|
|
communityItem.TokenPermissions = o.config.CommunityDescription.TokenPermissions
|
2021-08-05 13:27:47 +00:00
|
|
|
communityItem.MembersCount = len(o.config.CommunityDescription.Members)
|
|
|
|
communityItem.Link = fmt.Sprintf("https://join.status.im/c/0x%x", o.ID())
|
2022-05-24 19:20:13 +00:00
|
|
|
communityItem.IntroMessage = o.config.CommunityDescription.IntroMessage
|
|
|
|
communityItem.OutroMessage = o.config.CommunityDescription.OutroMessage
|
2022-06-23 07:12:15 +00:00
|
|
|
communityItem.BanList = o.config.CommunityDescription.BanList
|
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)
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) MarshalJSON() ([]byte, error) {
|
2021-01-11 10:32:51 +00:00
|
|
|
if o.config.MemberIdentity == nil {
|
|
|
|
return nil, errors.New("member identity not set")
|
|
|
|
}
|
|
|
|
communityItem := struct {
|
2023-03-02 16:27:48 +00:00
|
|
|
ID types.HexBytes `json:"id"`
|
2023-06-14 14:15:46 +00:00
|
|
|
MemberRole protobuf.CommunityMember_Roles `json:"memberRole"`
|
2023-07-21 09:41:26 +00:00
|
|
|
IsControlNode bool `json:"isControlNode"`
|
2023-03-02 16:27:48 +00:00
|
|
|
Verified bool `json:"verified"`
|
|
|
|
Joined bool `json:"joined"`
|
|
|
|
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]images.IdentityImage `json:"images"`
|
|
|
|
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
|
|
|
Members map[string]*protobuf.CommunityMember `json:"members"`
|
|
|
|
CanRequestAccess bool `json:"canRequestAccess"`
|
2023-06-14 14:15:46 +00:00
|
|
|
CanManageUsers bool `json:"canManageUsers"` //TODO: we can remove this
|
|
|
|
CanDeleteMessageForEveryone bool `json:"canDeleteMessageForEveryone"` //TODO: we can remove this
|
2023-03-02 16:27:48 +00:00
|
|
|
CanJoin bool `json:"canJoin"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
RequestedToJoinAt uint64 `json:"requestedToJoinAt,omitempty"`
|
|
|
|
IsMember bool `json:"isMember"`
|
|
|
|
Muted bool `json:"muted"`
|
2023-06-17 08:19:05 +00:00
|
|
|
MuteTill time.Time `json:"muteTill,omitempty"`
|
2023-03-02 16:27:48 +00:00
|
|
|
CommunityAdminSettings CommunityAdminSettings `json:"adminSettings"`
|
|
|
|
Encrypted bool `json:"encrypted"`
|
|
|
|
BanList []string `json:"banList"`
|
|
|
|
TokenPermissions map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissions"`
|
2023-02-20 11:57:33 +00:00
|
|
|
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
2023-03-28 14:40:00 +00:00
|
|
|
ActiveMembersCount uint64 `json:"activeMembersCount"`
|
2020-11-18 09:16:51 +00:00
|
|
|
}{
|
2022-12-02 11:34:02 +00:00
|
|
|
ID: o.ID(),
|
2023-06-14 14:15:46 +00:00
|
|
|
MemberRole: o.MemberRole(o.MemberIdentity()),
|
2023-07-21 09:41:26 +00:00
|
|
|
IsControlNode: o.IsControlNode(),
|
2022-12-02 11:34:02 +00:00
|
|
|
Verified: o.config.Verified,
|
|
|
|
Chats: make(map[string]CommunityChat),
|
|
|
|
Categories: make(map[string]CommunityCategory),
|
|
|
|
Joined: o.config.Joined,
|
|
|
|
Spectated: o.config.Spectated,
|
|
|
|
CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity),
|
|
|
|
CanJoin: o.canJoin(),
|
|
|
|
CanManageUsers: o.CanManageUsers(o.config.MemberIdentity),
|
|
|
|
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.config.MemberIdentity),
|
|
|
|
RequestedToJoinAt: o.RequestedToJoinAt(),
|
|
|
|
IsMember: o.isMember(),
|
|
|
|
Muted: o.config.Muted,
|
2023-06-17 08:19:05 +00:00
|
|
|
MuteTill: o.config.MuteTill,
|
2022-12-02 11:34:02 +00:00
|
|
|
Tags: o.Tags(),
|
|
|
|
Encrypted: o.Encrypted(),
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
if o.config.CommunityDescription != nil {
|
2021-05-23 13:34:17 +00:00
|
|
|
for id, c := range o.config.CommunityDescription.Categories {
|
|
|
|
category := CommunityCategory{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Name,
|
|
|
|
Position: int(c.Position),
|
|
|
|
}
|
2022-05-27 09:14:40 +00:00
|
|
|
communityItem.Encrypted = o.config.CommunityDescription.Encrypted
|
2021-05-23 13:34:17 +00:00
|
|
|
communityItem.Categories[id] = category
|
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
for id, c := range o.config.CommunityDescription.Chats {
|
|
|
|
canPost, err := o.CanPost(o.config.MemberIdentity, id, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
chat := CommunityChat{
|
|
|
|
ID: id,
|
|
|
|
Name: c.Identity.DisplayName,
|
2021-10-04 13:02:25 +00:00
|
|
|
Emoji: c.Identity.Emoji,
|
|
|
|
Color: c.Identity.Color,
|
2021-06-01 12:13:17 +00:00
|
|
|
Description: c.Identity.Description,
|
2021-01-11 10:32:51 +00:00
|
|
|
Permissions: c.Permissions,
|
|
|
|
Members: c.Members,
|
|
|
|
CanPost: canPost,
|
2021-05-23 13:34:17 +00:00
|
|
|
CategoryID: c.CategoryId,
|
|
|
|
Position: int(c.Position),
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
communityItem.Chats[id] = chat
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
communityItem.TokenPermissions = o.config.CommunityDescription.TokenPermissions
|
2021-01-11 10:32:51 +00:00
|
|
|
communityItem.Members = o.config.CommunityDescription.Members
|
|
|
|
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
2022-05-24 19:20:13 +00:00
|
|
|
communityItem.IntroMessage = o.config.CommunityDescription.IntroMessage
|
|
|
|
communityItem.OutroMessage = o.config.CommunityDescription.OutroMessage
|
2022-06-23 07:12:15 +00:00
|
|
|
communityItem.BanList = o.config.CommunityDescription.BanList
|
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-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
|
|
|
|
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
|
|
|
|
}
|
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 ""
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
if o != nil &&
|
|
|
|
o.config != nil &&
|
|
|
|
o.config.CommunityDescription != nil {
|
|
|
|
var result []CommunityTag
|
|
|
|
for _, t := range o.config.CommunityDescription.Tags {
|
|
|
|
result = append(result, CommunityTag{
|
|
|
|
Name: t,
|
|
|
|
Emoji: requests.TagsEmojies[t],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
var indices []uint32
|
|
|
|
for _, t := range o.config.CommunityDescription.Tags {
|
|
|
|
i := uint32(0)
|
|
|
|
for k := range requests.TagsEmojies {
|
|
|
|
if k == t {
|
|
|
|
indices = append(indices, i)
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) initialize() {
|
|
|
|
if o.config.CommunityDescription == nil {
|
|
|
|
o.config.CommunityDescription = &protobuf.CommunityDescription{}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-27 13:27:24 +00:00
|
|
|
type DeployState uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
Failed DeployState = iota
|
|
|
|
InProgress
|
|
|
|
Deployed
|
|
|
|
)
|
|
|
|
|
|
|
|
type CommunityToken struct {
|
2023-02-20 11:57:33 +00:00
|
|
|
TokenType protobuf.CommunityTokenType `json:"tokenType"`
|
|
|
|
CommunityID string `json:"communityId"`
|
|
|
|
Address string `json:"address"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Symbol string `json:"symbol"`
|
|
|
|
Description string `json:"description"`
|
2023-06-21 11:20:43 +00:00
|
|
|
Supply *bigint.BigInt `json:"supply"`
|
2023-02-20 11:57:33 +00:00
|
|
|
InfiniteSupply bool `json:"infiniteSupply"`
|
|
|
|
Transferable bool `json:"transferable"`
|
|
|
|
RemoteSelfDestruct bool `json:"remoteSelfDestruct"`
|
|
|
|
ChainID int `json:"chainId"`
|
|
|
|
DeployState DeployState `json:"deployState"`
|
|
|
|
Base64Image string `json:"image"`
|
2023-06-14 07:47:54 +00:00
|
|
|
Decimals int `json:"decimals"`
|
2023-01-27 13:27:24 +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
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
// `CommunityAdminEventChanges contain additional changes that don't live on
|
|
|
|
// a `Community` but still have to be propagated to other admin and control nodes
|
2023-07-18 15:06:12 +00:00
|
|
|
type CommunityEventChanges struct {
|
2023-06-14 14:15:46 +00:00
|
|
|
*CommunityChanges
|
|
|
|
// `RejectedRequestsToJoin` is a map of signer keys to requests to join
|
|
|
|
RejectedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"rejectedRequestsToJoin"`
|
|
|
|
// `AcceptedRequestsToJoin` is a map of signer keys to requests to join
|
|
|
|
AcceptedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"acceptedRequestsToJoin"`
|
|
|
|
}
|
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToCreateChannelCommunityEvent(chatID, chat))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
changes := o.emptyCommunityChanges()
|
2020-11-18 09:16:51 +00:00
|
|
|
changes.ChatsAdded[chatID] = chat
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2021-06-01 12:13:17 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToEditChannelCommunityEvent(chatID, chat))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
2021-06-01 12:13:17 +00:00
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
changes.ChatsModified[chatID] = &CommunityChatChanges{
|
|
|
|
ChatModified: chat,
|
|
|
|
}
|
|
|
|
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
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
|
|
|
changes := o.deleteChat(chatID)
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
return changes, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) InviteUserToOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityInvitation, 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
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
_, err := o.AddMember(pk, []protobuf.CommunityMember_Roles{})
|
2022-07-01 13:54:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
response := &protobuf.CommunityInvitation{}
|
2023-07-10 15:35:15 +00:00
|
|
|
wrappedCommunity, err := o.toProtocolMessageBytes()
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-10 15:35:15 +00:00
|
|
|
response.WrappedCommunityDescription = wrappedCommunity
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
grant, err := o.buildGrant(pk, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
response.Grant = grant
|
|
|
|
response.PublicKey = crypto.CompressPubkey(pk)
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) InviteUserToChat(pk *ecdsa.PublicKey, chatID string) (*protobuf.CommunityInvitation, 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
|
|
|
}
|
|
|
|
memberKey := common.PubkeyToHex(pk)
|
|
|
|
|
|
|
|
if _, ok := o.config.CommunityDescription.Members[memberKey]; !ok {
|
|
|
|
o.config.CommunityDescription.Members[memberKey] = &protobuf.CommunityMember{}
|
|
|
|
}
|
|
|
|
|
|
|
|
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{}
|
|
|
|
|
|
|
|
o.increaseClock()
|
|
|
|
|
|
|
|
response := &protobuf.CommunityInvitation{}
|
2023-07-10 15:35:15 +00:00
|
|
|
wrappedCommunity, err := o.toProtocolMessageBytes()
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-10 15:35:15 +00:00
|
|
|
response.WrappedCommunityDescription = wrappedCommunity
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
grant, err := o.buildGrant(pk, chatID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
response.Grant = grant
|
|
|
|
response.ChatId = chatID
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
|
|
|
|
for _, k := range o.config.CommunityDescription.BanList {
|
|
|
|
if k == key {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) IsMemberInChat(pk *ecdsa.PublicKey, chatID string) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
return o.getChatMember(pk, chatID) != nil
|
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) removeMemberFromOrg(pk *ecdsa.PublicKey) {
|
2020-11-18 09:16:51 +00:00
|
|
|
if !o.hasMember(pk) {
|
2022-09-14 12:39:55 +00:00
|
|
|
return
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2022-09-14 12:39:55 +00:00
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
key := common.PubkeyToHex(pk)
|
|
|
|
|
|
|
|
// Remove from org
|
|
|
|
delete(o.config.CommunityDescription.Members, key)
|
|
|
|
|
|
|
|
// Remove from chats
|
|
|
|
for _, chat := range o.config.CommunityDescription.Chats {
|
|
|
|
delete(chat.Members, key)
|
|
|
|
}
|
2022-09-14 12:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) RemoveOurselvesFromOrg(pk *ecdsa.PublicKey) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
o.removeMemberFromOrg(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-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2022-09-14 12:39:55 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
if !isControlNode && o.IsPrivilegedMember(pk) {
|
2023-06-23 07:02:12 +00:00
|
|
|
return nil, ErrCannotRemoveOwnerOrAdmin
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToKickCommunityMemberCommunityEvent(common.PubkeyToHex(pk)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-14 12:39:55 +00:00
|
|
|
o.removeMemberFromOrg(pk)
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
return o.config.CommunityDescription, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
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-07-21 09:41:26 +00:00
|
|
|
if !o.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2023-02-20 11:57:33 +00:00
|
|
|
}
|
|
|
|
o.config.CommunityDescription.CommunityTokensMetadata = append(o.config.CommunityDescription.CommunityTokensMetadata, token)
|
|
|
|
o.increaseClock()
|
|
|
|
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2022-06-23 07:12:15 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToUnbanCommunityMemberCommunityEvent(common.PubkeyToHex(pk)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
o.unbanUserFromCommunity(pk)
|
2022-06-23 07:12:15 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
2022-06-23 07:12:15 +00:00
|
|
|
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
func (o *Community) BanUserFromCommunity(pk *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2021-03-19 09:15:45 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
if !isControlNode && o.IsPrivilegedMember(pk) {
|
2023-06-23 07:02:12 +00:00
|
|
|
return nil, ErrCannotBanOwnerOrAdmin
|
2021-03-19 09:15:45 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToBanCommunityMemberCommunityEvent(common.PubkeyToHex(pk)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
o.banUserFromCommunity(pk)
|
2021-03-19 09:15:45 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
2021-03-19 09:15:45 +00:00
|
|
|
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
func (o *Community) AddRoleToMember(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
|
|
|
addRole := 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
|
|
|
member.Roles = append(member.Roles, role)
|
|
|
|
updated = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:49:26 +00:00
|
|
|
member := o.getMember(pk)
|
|
|
|
if member != nil {
|
|
|
|
addRole(member)
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range o.chats() {
|
|
|
|
chatMember := o.getChatMember(pk, channelID)
|
|
|
|
if chatMember != nil {
|
|
|
|
addRole(member)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
if updated {
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
return o.config.CommunityDescription, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Join() {
|
|
|
|
o.config.Joined = true
|
2023-07-04 11:51:21 +00:00
|
|
|
o.config.Spectated = false
|
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 {
|
|
|
|
return o.config.CommunityDescription.Encrypted
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) Joined() bool {
|
|
|
|
return o.config.Joined
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
return o.config.MemberIdentity
|
|
|
|
}
|
|
|
|
|
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-07-18 15:06:12 +00:00
|
|
|
func (o *Community) UpdateCommunityDescription(description *protobuf.CommunityDescription, rawMessage []byte, allowEqualClock bool) (*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
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
response := o.emptyCommunityChanges()
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
// allowEqualClock == true only if this was a description from the handling request to join sent by an admin
|
|
|
|
if allowEqualClock {
|
|
|
|
if description.Clock < o.config.CommunityDescription.Clock {
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
} else if description.Clock <= o.config.CommunityDescription.Clock {
|
2020-11-18 09:16:51 +00:00
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-07-13 17:49:19 +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(o.config.CommunityDescription, description)
|
|
|
|
response.Community = o
|
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
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
// If we are not admin, fuggetaboutit
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() && !o.HasPermissionToSendCommunityEvents() {
|
2020-11-18 09:16:51 +00:00
|
|
|
return ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the org is ens name only, then reject if not present
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-10 14:11:37 +00:00
|
|
|
// ValidateRequestToJoin validates a request, checks that the right permissions are applied
|
|
|
|
func (o *Community) ValidateEditSharedAddresses(signer *ecdsa.PublicKey, request *protobuf.CommunityEditRevealedAccounts) error {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
// If we are not owner, fuggetaboutit
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() {
|
2023-07-18 15:06:12 +00:00
|
|
|
return ErrNotOwner
|
2023-07-10 14:11:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(request.RevealedAccounts) == 0 {
|
|
|
|
return errors.New("no addresses were shared")
|
|
|
|
}
|
|
|
|
|
|
|
|
if request.Clock < o.config.CommunityDescription.Members[common.PubkeyToHex(signer)].LastUpdateClock {
|
|
|
|
return errors.New("edit request is older than the last one we have. Ignore")
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
return o.config.PrivateKey != nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
func (o *Community) IsOwnerWithoutCommunityKey() bool {
|
|
|
|
return o.config.PrivateKey == nil && o.IsMemberOwner(o.config.MemberIdentity)
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
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-07-26 12:16:50 +00:00
|
|
|
func (o *Community) HasPermissionToSendCommunityEvents() bool {
|
2023-08-02 18:59:26 +00:00
|
|
|
return !o.IsControlNode() && o.hasRoles(o.config.MemberIdentity, manageCommunityRoles())
|
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 manageUsersRole() map[protobuf.CommunityMember_Roles]bool {
|
|
|
|
roles := manageCommunityRoles()
|
2021-03-31 16:23:45 +00:00
|
|
|
roles[protobuf.CommunityMember_ROLE_MANAGE_USERS] = true
|
|
|
|
return roles
|
|
|
|
}
|
|
|
|
|
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-08-02 18:59:26 +00:00
|
|
|
func moderateContentRole() map[protobuf.CommunityMember_Roles]bool {
|
|
|
|
roles := manageCommunityRoles()
|
|
|
|
roles[protobuf.CommunityMember_ROLE_MODERATE_CONTENT] = 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
|
|
|
|
} else if o.CanManageUsers(pubKey) {
|
|
|
|
return protobuf.CommunityMember_ROLE_MANAGE_USERS
|
|
|
|
} else if o.CanDeleteMessageForEveryone(pubKey) {
|
|
|
|
return protobuf.CommunityMember_ROLE_MODERATE_CONTENT
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
if chat.Permissions.Access != protobuf.CommunityPermissions_ON_REQUEST {
|
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Permissions.EnsOnly && len(request.EnsName) == 0 {
|
|
|
|
return ErrCantRequestAccess
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) OnRequest() bool {
|
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_ON_REQUEST
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) InvitationOnly() bool {
|
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_INVITATION_ONLY
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
func (o *Community) AcceptRequestToJoinAutomatically() bool {
|
|
|
|
// 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.
|
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_NO_MEMBERSHIP
|
|
|
|
}
|
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.
|
|
|
|
if o.config.CommunityDescription.Permissions.Access != protobuf.CommunityPermissions_ON_REQUEST && o.config.CommunityDescription.Permissions.Access != protobuf.CommunityPermissions_NO_MEMBERSHIP {
|
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())
|
|
|
|
}
|
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
2021-07-22 17:41:49 +00:00
|
|
|
func (o *Community) DefaultFilters() []string {
|
|
|
|
cID := o.IDString()
|
2023-01-06 16:33:09 +00:00
|
|
|
uncompressedPubKey := common.PubkeyToHex(o.config.ID)[2:]
|
2022-03-09 16:07:38 +00:00
|
|
|
updatesChannelID := o.StatusUpdatesChannelID()
|
2022-03-08 13:54:53 +00:00
|
|
|
mlChannelID := o.MagnetlinkMessageChannelID()
|
2022-07-08 10:25:46 +00:00
|
|
|
memberUpdateChannelID := o.MemberUpdateChannelID()
|
2023-01-06 16:33:09 +00:00
|
|
|
return []string{cID, uncompressedPubKey, updatesChannelID, mlChannelID, memberUpdateChannelID}
|
2021-07-22 17:41:49 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) PrivateKey() *ecdsa.PrivateKey {
|
|
|
|
return o.config.PrivateKey
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (o *Community) marshaledDescription() ([]byte, error) {
|
|
|
|
return proto.Marshal(o.config.CommunityDescription)
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-11-18 09:16:51 +00:00
|
|
|
// This should not happen, as we can only serialize on our side if we
|
|
|
|
// created the community
|
2023-07-10 15:35:15 +00:00
|
|
|
if !o.IsControlNode() && len(o.config.CommunityDescriptionProtocolMessage) == 0 {
|
2023-07-21 09:41:26 +00:00
|
|
|
return nil, ErrNotControlNode
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
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-07-10 15:35:15 +00:00
|
|
|
return o.config.CommunityDescriptionProtocolMessage, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// serialize and sign
|
|
|
|
payload, err := o.marshaledDescription()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
func (o *Community) TokenPermissions() map[string]*protobuf.CommunityTokenPermission {
|
|
|
|
return o.config.CommunityDescription.TokenPermissions
|
|
|
|
}
|
|
|
|
|
2023-03-24 15:43:53 +00:00
|
|
|
func (o *Community) HasTokenPermissions() bool {
|
2023-06-23 10:49:26 +00:00
|
|
|
return o.config.CommunityDescription.TokenPermissions != nil && len(o.config.CommunityDescription.TokenPermissions) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) ChannelHasTokenPermissions(chatID string) bool {
|
|
|
|
if !o.HasTokenPermissions() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tokenPermission := range o.TokenPermissions() {
|
|
|
|
if includes(tokenPermission.ChatIds, chatID) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2023-03-24 15:43:53 +00:00
|
|
|
}
|
|
|
|
|
2023-07-13 17:49:19 +00:00
|
|
|
func TokenPermissionsByType(permissions map[string]*protobuf.CommunityTokenPermission, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
|
|
|
|
result := make([]*protobuf.CommunityTokenPermission, 0)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) TokenPermissionsByType(permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.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-06-12 15:17:37 +00:00
|
|
|
func (o *Community) ChannelTokenPermissionsByType(channelID string, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
|
|
|
|
permissions := make([]*protobuf.CommunityTokenPermission, 0)
|
|
|
|
for _, tokenPermission := range o.TokenPermissions() {
|
|
|
|
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-07-17 16:40:09 +00:00
|
|
|
func (o *Community) updateEncrypted() {
|
|
|
|
o.config.CommunityDescription.Encrypted = len(o.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)) > 0
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
func (o *Community) AddTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents || (allowedToSendEvents && permission.Type == protobuf.CommunityTokenPermission_BECOME_ADMIN) {
|
2023-06-14 14:15:46 +00:00
|
|
|
return nil, ErrNotEnoughPermissions
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
changes, err := o.addTokenPermission(permission)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityTokenPermissionChangeCommunityEvent(permission))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-17 16:40:09 +00:00
|
|
|
o.updateEncrypted()
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) UpdateTokenPermission(permissionID string, tokenPermission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents || (allowedToSendEvents && tokenPermission.Type == protobuf.CommunityTokenPermission_BECOME_ADMIN) {
|
2023-06-14 14:15:46 +00:00
|
|
|
return nil, ErrNotEnoughPermissions
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
changes, err := o.updateTokenPermission(tokenPermission)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityTokenPermissionChangeCommunityEvent(tokenPermission))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-17 16:40:09 +00:00
|
|
|
o.updateEncrypted()
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) DeleteTokenPermission(permissionID string) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
permission, exists := o.config.CommunityDescription.TokenPermissions[permissionID]
|
2023-03-02 16:27:48 +00:00
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
if !exists {
|
2023-03-02 16:27:48 +00:00
|
|
|
return nil, ErrTokenPermissionNotFound
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents || (allowedToSendEvents && permission.Type == protobuf.CommunityTokenPermission_BECOME_ADMIN) {
|
2023-06-14 14:15:46 +00:00
|
|
|
return nil, ErrNotEnoughPermissions
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
changes, err := o.deleteTokenPermission(permissionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityTokenPermissionDeleteCommunityEvent(permission))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-17 16:40:09 +00:00
|
|
|
o.updateEncrypted()
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
extractedPublicKey, err := crypto.SigToPub(crypto.Keccak256(payload), signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !common.IsPubKeyEqual(o.config.ID, extractedPublicKey) {
|
|
|
|
return nil, ErrInvalidGrant
|
|
|
|
}
|
|
|
|
|
|
|
|
return grant, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CanPost(pk *ecdsa.PublicKey, chatID string, grantBytes []byte) (bool, error) {
|
|
|
|
if o.config.CommunityDescription.Chats == nil {
|
|
|
|
o.config.Logger.Debug("canPost, no-chats")
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
|
|
|
if !ok {
|
|
|
|
o.config.Logger.Debug("canPost, no chat with id", zap.String("chat-id", chatID))
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// creator can always post
|
|
|
|
if common.IsPubKeyEqual(pk, o.config.ID) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
// if banned cannot post
|
|
|
|
if o.isBanned(pk) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
// If both the chat & the org have no permissions, the user is allowed to post
|
|
|
|
if o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_NO_MEMBERSHIP && chat.Permissions.Access == protobuf.CommunityPermissions_NO_MEMBERSHIP {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Permissions.Access != protobuf.CommunityPermissions_NO_MEMBERSHIP {
|
|
|
|
if chat.Members == nil {
|
|
|
|
o.config.Logger.Debug("canPost, no members in chat", zap.String("chat-id", chatID))
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ok := chat.Members[common.PubkeyToHex(pk)]
|
|
|
|
// If member, we stop here
|
|
|
|
if ok {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not a member, and not grant, we return
|
|
|
|
if !ok && grantBytes == nil {
|
|
|
|
o.config.Logger.Debug("canPost, not a member in chat", zap.String("chat-id", chatID))
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise we verify the grant
|
|
|
|
return o.canPostWithGrant(pk, chatID, grantBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Chat has no membership, check org permissions
|
|
|
|
if o.config.CommunityDescription.Members == nil {
|
|
|
|
o.config.Logger.Debug("canPost, no members in org", zap.String("chat-id", chatID))
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If member, they can post
|
|
|
|
_, ok = o.config.CommunityDescription.Members[common.PubkeyToHex(pk)]
|
|
|
|
if ok {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not a member and no grant, can't post
|
|
|
|
if !ok && grantBytes == nil {
|
|
|
|
o.config.Logger.Debug("canPost, not a member in org", zap.String("chat-id", chatID), zap.String("pubkey", common.PubkeyToHex(pk)))
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.canPostWithGrant(pk, chatID, grantBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) canPostWithGrant(pk *ecdsa.PublicKey, chatID string, grantBytes []byte) (bool, error) {
|
|
|
|
grant, err := o.VerifyGrantSignature(grantBytes)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
// If the clock is lower or equal is invalid
|
|
|
|
if grant.Clock <= o.config.CommunityDescription.Clock {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if grant.MemberId == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
grantPk, err := crypto.DecompressPubkey(grant.MemberId)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !common.IsPubKeyEqual(grantPk, pk) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if chatID != grant.ChatId {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
if o.config.PrivateKey != nil {
|
|
|
|
grant := &protobuf.Grant{
|
|
|
|
CommunityId: o.ID(),
|
|
|
|
MemberId: crypto.CompressPubkey(key),
|
|
|
|
ChatId: chatID,
|
|
|
|
Clock: o.config.CommunityDescription.Clock,
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_ON_REQUEST
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if o.IsControlNode() {
|
2021-01-11 10:32:51 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !o.hasMember(pk) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
roles := manageUsersRole()
|
|
|
|
return o.hasRoles(pk, roles)
|
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-07-26 12:16:50 +00:00
|
|
|
if o.IsControlNode() {
|
2022-12-02 11:34:02 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !o.hasMember(pk) {
|
|
|
|
return false
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2023-08-02 18:59:26 +00:00
|
|
|
roles := moderateContentRole()
|
|
|
|
return o.hasRoles(pk, roles)
|
2022-12-02 11:34:02 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (o *Community) isMember() bool {
|
|
|
|
return o.hasMember(o.config.MemberIdentity)
|
|
|
|
}
|
|
|
|
|
2022-02-09 21:58:33 +00:00
|
|
|
func (o *Community) CanMemberIdentityPost(chatID string) (bool, error) {
|
|
|
|
return o.CanPost(o.config.MemberIdentity, chatID, nil)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Permissions.Access == protobuf.CommunityPermissions_NO_MEMBERSHIP {
|
|
|
|
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 {
|
|
|
|
return o.config.CommunityDescription.Clock + 1
|
|
|
|
}
|
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-02 18:59:26 +00:00
|
|
|
roles := manageUsersRole()
|
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
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (o *Community) AddMember(publicKey *ecdsa.PublicKey, roles []protobuf.CommunityMember_Roles) (*CommunityChanges, error) {
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() && !o.HasPermissionToSendCommunityEvents() {
|
2023-06-14 14:15:46 +00:00
|
|
|
return nil, ErrNotAdmin
|
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 {
|
2022-09-21 10:50:56 +00:00
|
|
|
o.config.CommunityDescription.Members[memberKey] = &protobuf.CommunityMember{Roles: roles}
|
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()
|
2023-06-14 14:15:46 +00:00
|
|
|
return changes, nil
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
|
|
|
|
2023-07-13 17:49:19 +00:00
|
|
|
func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey, roles []protobuf.CommunityMember_Roles) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() && !o.HasPermissionToSendCommunityEvents() {
|
2023-07-13 17:49:19 +00:00
|
|
|
return nil, ErrNotAuthorized
|
|
|
|
}
|
|
|
|
|
|
|
|
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{
|
|
|
|
Roles: roles,
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-09 09:58:05 +00:00
|
|
|
func (o *Community) ChatIDs() (chatIDs []string) {
|
|
|
|
for id := range o.config.CommunityDescription.Chats {
|
|
|
|
chatIDs = append(chatIDs, o.IDString()+id)
|
|
|
|
}
|
|
|
|
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-10 14:11:37 +00:00
|
|
|
func (o *Community) AddMemberRevealedAccounts(memberID string, accounts []*protobuf.RevealedAccount, clock uint64) (*CommunityChanges, error) {
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !o.IsControlNode() && !o.HasPermissionToSendCommunityEvents() {
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := o.config.CommunityDescription.Members[memberID]; !ok {
|
|
|
|
return nil, ErrMemberNotFound
|
|
|
|
}
|
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
o.config.CommunityDescription.Members[memberID].RevealedAccounts = accounts
|
2023-07-10 14:11:37 +00:00
|
|
|
o.config.CommunityDescription.Members[memberID].LastUpdateClock = clock
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
o.increaseClock()
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
2023-06-06 18:33:09 +00:00
|
|
|
changes.MemberWalletsAdded[memberID] = o.config.CommunityDescription.Members[memberID].RevealedAccounts
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (o *Community) AddMemberWithRevealedAccounts(dbRequest *RequestToJoin, roles []protobuf.CommunityMember_Roles, accounts []*protobuf.RevealedAccount) (*CommunityChanges, error) {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
return nil, ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
|
|
|
changes := o.addMemberWithRevealedAccounts(dbRequest.PublicKey, roles, accounts, dbRequest.Clock)
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
acceptedRequestsToJoin := make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
acceptedRequestsToJoin[dbRequest.PublicKey] = dbRequest.ToCommunityRequestToJoinProtobuf()
|
|
|
|
|
|
|
|
adminChanges := &CommunityEventChanges{
|
|
|
|
CommunityChanges: changes,
|
|
|
|
AcceptedRequestsToJoin: acceptedRequestsToJoin,
|
|
|
|
}
|
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityRequestToJoinAcceptCommunityEvent(adminChanges))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
2023-07-17 16:40:09 +00:00
|
|
|
func (o *Community) CreateDeepCopy() *Community {
|
2023-07-18 15:06:12 +00:00
|
|
|
return &Community{
|
2023-06-14 14:15:46 +00:00
|
|
|
config: &Config{
|
2023-07-10 15:35:15 +00:00
|
|
|
PrivateKey: o.config.PrivateKey,
|
|
|
|
CommunityDescription: proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription),
|
|
|
|
CommunityDescriptionProtocolMessage: o.config.CommunityDescriptionProtocolMessage,
|
|
|
|
ID: o.config.ID,
|
|
|
|
Joined: o.config.Joined,
|
|
|
|
Requested: o.config.Requested,
|
|
|
|
Verified: o.config.Verified,
|
|
|
|
Spectated: o.config.Spectated,
|
|
|
|
Muted: o.config.Muted,
|
|
|
|
Logger: o.config.Logger,
|
|
|
|
RequestedToJoinAt: o.config.RequestedToJoinAt,
|
|
|
|
RequestsToJoin: o.config.RequestsToJoin,
|
|
|
|
MemberIdentity: o.config.MemberIdentity,
|
|
|
|
SyncedAt: o.config.SyncedAt,
|
|
|
|
EventsData: o.config.EventsData,
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) banUserFromCommunity(pk *ecdsa.PublicKey) {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, u := range o.config.CommunityDescription.BanList {
|
|
|
|
if u == key {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.BanList = append(o.config.CommunityDescription.BanList, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
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-06-23 10:49:26 +00:00
|
|
|
chat.Members = o.config.CommunityDescription.Members
|
|
|
|
|
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-07-18 15:06:12 +00:00
|
|
|
func (o *Community) addCommunityMember(pk *ecdsa.PublicKey, member *protobuf.CommunityMember) {
|
|
|
|
|
|
|
|
if o.config.CommunityDescription.Members == nil {
|
|
|
|
o.config.CommunityDescription.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
}
|
|
|
|
|
|
|
|
memberKey := common.PubkeyToHex(pk)
|
|
|
|
o.config.CommunityDescription.Members[memberKey] = member
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) addTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
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
|
|
|
|
|
|
|
if _, exists := o.config.CommunityDescription.TokenPermissions[permission.Id]; exists {
|
|
|
|
return nil, ErrTokenPermissionAlreadyExists
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
|
|
|
o.config.CommunityDescription.TokenPermissions[permission.Id] = permission
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
|
|
|
|
if changes.TokenPermissionsAdded == nil {
|
|
|
|
changes.TokenPermissionsAdded = make(map[string]*protobuf.CommunityTokenPermission)
|
|
|
|
}
|
|
|
|
changes.TokenPermissionsAdded[permission.Id] = permission
|
|
|
|
|
|
|
|
return changes, nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (o *Community) updateTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
|
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
|
|
|
if _, ok := o.config.CommunityDescription.TokenPermissions[permission.Id]; !ok {
|
|
|
|
return nil, ErrTokenPermissionNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
o.config.CommunityDescription.TokenPermissions[permission.Id] = permission
|
|
|
|
|
|
|
|
if changes.TokenPermissionsModified == nil {
|
|
|
|
changes.TokenPermissionsModified = make(map[string]*protobuf.CommunityTokenPermission)
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
changes.TokenPermissionsModified[permission.Id] = o.config.CommunityDescription.TokenPermissions[permission.Id]
|
|
|
|
|
|
|
|
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-07-13 17:49:19 +00:00
|
|
|
changes.TokenPermissionsRemoved[permissionID] = permission
|
2023-07-18 15:06:12 +00:00
|
|
|
return changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Community) addMemberWithRevealedAccounts(memberKey string, roles []protobuf.CommunityMember_Roles, accounts []*protobuf.RevealedAccount, clock uint64) *CommunityChanges {
|
|
|
|
changes := o.emptyCommunityChanges()
|
2023-06-14 14:15:46 +00:00
|
|
|
|
|
|
|
if o.config.CommunityDescription.Members == nil {
|
|
|
|
o.config.CommunityDescription.Members = make(map[string]*protobuf.CommunityMember)
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
if _, ok := o.config.CommunityDescription.Members[memberKey]; !ok {
|
|
|
|
o.config.CommunityDescription.Members[memberKey] = &protobuf.CommunityMember{Roles: roles}
|
|
|
|
changes.MembersAdded[memberKey] = o.config.CommunityDescription.Members[memberKey]
|
|
|
|
}
|
|
|
|
|
|
|
|
o.config.CommunityDescription.Members[memberKey].RevealedAccounts = accounts
|
|
|
|
o.config.CommunityDescription.Members[memberKey].LastUpdateClock = clock
|
|
|
|
changes.MemberWalletsAdded[memberKey] = o.config.CommunityDescription.Members[memberKey].RevealedAccounts
|
|
|
|
|
|
|
|
return changes
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (o *Community) DeclineRequestToJoin(dbRequest *RequestToJoin) error {
|
|
|
|
o.mutex.Lock()
|
|
|
|
defer o.mutex.Unlock()
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
isControlNode := o.IsControlNode()
|
|
|
|
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if !isControlNode && !allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
return ErrNotAdmin
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if allowedToSendEvents {
|
2023-07-18 15:06:12 +00:00
|
|
|
rejectedRequestsToJoin := make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
rejectedRequestsToJoin[dbRequest.PublicKey] = dbRequest.ToCommunityRequestToJoinProtobuf()
|
|
|
|
|
|
|
|
adminChanges := &CommunityEventChanges{
|
|
|
|
CommunityChanges: o.emptyCommunityChanges(),
|
|
|
|
RejectedRequestsToJoin: rejectedRequestsToJoin,
|
|
|
|
}
|
|
|
|
err := o.addNewCommunityEvent(o.ToCommunityRequestToJoinRejectCommunityEvent(adminChanges))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if isControlNode {
|
2023-07-18 15:06:12 +00:00
|
|
|
// typically, community's clock is increased implicitly when making changes
|
|
|
|
// to it, however in this scenario there are no changes in the community, yet
|
|
|
|
// we need to increase the clock to ensure the owner event is processed by other
|
|
|
|
// nodes.
|
|
|
|
o.increaseClock()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|