github.com/status-im/status-go@v1.1.0/protocol/communities/community_event.go (about)

     1  package communities
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/golang/protobuf/proto"
    10  
    11  	"github.com/status-im/status-go/eth-node/crypto"
    12  	"github.com/status-im/status-go/protocol/protobuf"
    13  )
    14  
    15  type CommunityEvent struct {
    16  	CommunityEventClock uint64                             `json:"communityEventClock"`
    17  	Type                protobuf.CommunityEvent_EventType  `json:"type"`
    18  	CommunityConfig     *protobuf.CommunityConfig          `json:"communityConfig,omitempty"`
    19  	TokenPermission     *protobuf.CommunityTokenPermission `json:"tokenPermissions,omitempty"`
    20  	CategoryData        *protobuf.CategoryData             `json:"categoryData,omitempty"`
    21  	ChannelData         *protobuf.ChannelData              `json:"channelData,omitempty"`
    22  	MemberToAction      string                             `json:"memberToAction,omitempty"`
    23  	RequestToJoin       *protobuf.CommunityRequestToJoin   `json:"requestToJoin,omitempty"`
    24  	TokenMetadata       *protobuf.CommunityTokenMetadata   `json:"tokenMetadata,omitempty"`
    25  	Payload             []byte                             `json:"payload"`
    26  	Signature           []byte                             `json:"signature"`
    27  }
    28  
    29  func (e *CommunityEvent) ToProtobuf() *protobuf.CommunityEvent {
    30  	var acceptedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin
    31  	var rejectedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin
    32  
    33  	switch e.Type {
    34  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
    35  		acceptedRequestsToJoin = make(map[string]*protobuf.CommunityRequestToJoin)
    36  		acceptedRequestsToJoin[e.MemberToAction] = e.RequestToJoin
    37  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
    38  		rejectedRequestsToJoin = make(map[string]*protobuf.CommunityRequestToJoin)
    39  		rejectedRequestsToJoin[e.MemberToAction] = e.RequestToJoin
    40  	}
    41  
    42  	return &protobuf.CommunityEvent{
    43  		CommunityEventClock:    e.CommunityEventClock,
    44  		Type:                   e.Type,
    45  		CommunityConfig:        e.CommunityConfig,
    46  		TokenPermission:        e.TokenPermission,
    47  		CategoryData:           e.CategoryData,
    48  		ChannelData:            e.ChannelData,
    49  		MemberToAction:         e.MemberToAction,
    50  		RejectedRequestsToJoin: rejectedRequestsToJoin,
    51  		AcceptedRequestsToJoin: acceptedRequestsToJoin,
    52  		TokenMetadata:          e.TokenMetadata,
    53  	}
    54  }
    55  
    56  func communityEventFromProtobuf(msg *protobuf.SignedCommunityEvent) (*CommunityEvent, error) {
    57  	decodedEvent := protobuf.CommunityEvent{}
    58  	err := proto.Unmarshal(msg.Payload, &decodedEvent)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	memberToAction := decodedEvent.MemberToAction
    64  	var requestToJoin *protobuf.CommunityRequestToJoin
    65  
    66  	switch decodedEvent.Type {
    67  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
    68  		for member, request := range decodedEvent.AcceptedRequestsToJoin {
    69  			memberToAction = member
    70  			requestToJoin = request
    71  			break
    72  		}
    73  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
    74  		for member, request := range decodedEvent.RejectedRequestsToJoin {
    75  			memberToAction = member
    76  			requestToJoin = request
    77  			break
    78  		}
    79  	}
    80  
    81  	return &CommunityEvent{
    82  		CommunityEventClock: decodedEvent.CommunityEventClock,
    83  		Type:                decodedEvent.Type,
    84  		CommunityConfig:     decodedEvent.CommunityConfig,
    85  		TokenPermission:     decodedEvent.TokenPermission,
    86  		CategoryData:        decodedEvent.CategoryData,
    87  		ChannelData:         decodedEvent.ChannelData,
    88  		MemberToAction:      memberToAction,
    89  		RequestToJoin:       requestToJoin,
    90  		TokenMetadata:       decodedEvent.TokenMetadata,
    91  		Payload:             msg.Payload,
    92  		Signature:           msg.Signature,
    93  	}, nil
    94  }
    95  
    96  func (e *CommunityEvent) RecoverSigner() (*ecdsa.PublicKey, error) {
    97  	if e.Signature == nil || len(e.Signature) == 0 {
    98  		return nil, errors.New("missing signature")
    99  	}
   100  
   101  	signer, err := crypto.SigToPub(
   102  		crypto.Keccak256(e.Payload),
   103  		e.Signature,
   104  	)
   105  	if err != nil {
   106  		return nil, errors.New("failed to recover signer")
   107  	}
   108  
   109  	return signer, nil
   110  }
   111  
   112  func (e *CommunityEvent) Sign(pk *ecdsa.PrivateKey) error {
   113  	sig, err := crypto.Sign(crypto.Keccak256(e.Payload), pk)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	e.Signature = sig
   119  	return nil
   120  }
   121  
   122  func (e *CommunityEvent) Validate() error {
   123  	switch e.Type {
   124  	case protobuf.CommunityEvent_COMMUNITY_EDIT:
   125  		if e.CommunityConfig == nil || e.CommunityConfig.Identity == nil ||
   126  			e.CommunityConfig.Permissions == nil || e.CommunityConfig.AdminSettings == nil {
   127  			return errors.New("invalid config change admin event")
   128  		}
   129  
   130  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE:
   131  		if e.TokenPermission == nil || len(e.TokenPermission.Id) == 0 {
   132  			return errors.New("invalid token permission change event")
   133  		}
   134  
   135  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
   136  		if e.TokenPermission == nil || len(e.TokenPermission.Id) == 0 {
   137  			return errors.New("invalid token permission delete event")
   138  		}
   139  
   140  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE:
   141  		if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
   142  			return errors.New("invalid community category create event")
   143  		}
   144  
   145  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE:
   146  		if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
   147  			return errors.New("invalid community category delete event")
   148  		}
   149  
   150  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT:
   151  		if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
   152  			return errors.New("invalid community category edit event")
   153  		}
   154  
   155  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE:
   156  		if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 ||
   157  			e.ChannelData.Channel == nil {
   158  			return errors.New("invalid community channel create event")
   159  		}
   160  
   161  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE:
   162  		if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 {
   163  			return errors.New("invalid community channel delete event")
   164  		}
   165  
   166  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT:
   167  		if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 ||
   168  			e.ChannelData.Channel == nil {
   169  			return errors.New("invalid community channel edit event")
   170  		}
   171  
   172  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
   173  		if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 {
   174  			return errors.New("invalid community channel reorder event")
   175  		}
   176  
   177  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
   178  		if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
   179  			return errors.New("invalid community category reorder event")
   180  		}
   181  
   182  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT, protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
   183  		if len(e.MemberToAction) == 0 || e.RequestToJoin == nil {
   184  			return errors.New("invalid community request to join event")
   185  		}
   186  
   187  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
   188  		if len(e.MemberToAction) == 0 {
   189  			return errors.New("invalid community member kick event")
   190  		}
   191  
   192  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN:
   193  		if len(e.MemberToAction) == 0 {
   194  			return errors.New("invalid community member ban event")
   195  		}
   196  
   197  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
   198  		if len(e.MemberToAction) == 0 {
   199  			return errors.New("invalid community member unban event")
   200  		}
   201  
   202  	case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
   203  		if e.TokenMetadata == nil || len(e.TokenMetadata.ContractAddresses) == 0 {
   204  			return errors.New("invalid add community token event")
   205  		}
   206  	case protobuf.CommunityEvent_COMMUNITY_DELETE_BANNED_MEMBER_MESSAGES:
   207  		if len(e.MemberToAction) == 0 {
   208  			return errors.New("invalid delete all community member messages event")
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  // EventTypeID constructs a unique identifier for an event and its associated target.
   215  func (e *CommunityEvent) EventTypeID() string {
   216  	switch e.Type {
   217  	case protobuf.CommunityEvent_COMMUNITY_EDIT:
   218  		return fmt.Sprintf("%d", e.Type)
   219  
   220  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE,
   221  		protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
   222  		return fmt.Sprintf("%d-%s", e.Type, e.TokenPermission.Id)
   223  
   224  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE,
   225  		protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE,
   226  		protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT,
   227  		protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
   228  		return fmt.Sprintf("%d-%s", e.Type, e.CategoryData.CategoryId)
   229  
   230  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE,
   231  		protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE,
   232  		protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT,
   233  		protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
   234  		return fmt.Sprintf("%d-%s", e.Type, e.ChannelData.ChannelId)
   235  
   236  	case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT,
   237  		protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT,
   238  		protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK,
   239  		protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN,
   240  		protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN,
   241  		protobuf.CommunityEvent_COMMUNITY_DELETE_BANNED_MEMBER_MESSAGES:
   242  		return fmt.Sprintf("%d-%s", e.Type, e.MemberToAction)
   243  
   244  	case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
   245  		return fmt.Sprintf("%d-%s", e.Type, e.TokenMetadata.Name)
   246  	}
   247  
   248  	return ""
   249  }
   250  
   251  func communityEventsToJSONEncodedBytes(communityEvents []CommunityEvent) ([]byte, error) {
   252  	return json.Marshal(communityEvents)
   253  }
   254  
   255  func communityEventsFromJSONEncodedBytes(jsonEncodedRawEvents []byte) ([]CommunityEvent, error) {
   256  	var events []CommunityEvent
   257  	err := json.Unmarshal(jsonEncodedRawEvents, &events)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return events, nil
   263  }