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 }