feat: Add an expiration and periodical publishing for community grants (#5024)

* feat_: add periodical publishing for community grants
feat_: Validate grant when receiving it
feat_: add expiration for grants
feat_: add test for grants expiration
fix_: move grants test to profile showcase, fix a few bugs
* feat_: use one group mesage to update grants
* chore_: review fixes
This commit is contained in:
Mikhail Rogachev 2024-04-17 16:53:51 +02:00 committed by GitHub
parent 3de2756660
commit 6da423fc71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1042 additions and 541 deletions

View File

@ -31,6 +31,9 @@ import (
const signatureLength = 65
// GrantExpirationTime interval of 7 days
var GrantExpirationTime = 168 * time.Hour
type Config struct {
PrivateKey *ecdsa.PrivateKey
ControlNode *ecdsa.PublicKey
@ -1881,6 +1884,9 @@ func (o *Community) VerifyGrantSignature(data []byte) (*protobuf.Grant, error) {
if !bytes.Equal(grant.CommunityId, o.ID()) {
return nil, ErrInvalidGrant
}
if grant.Expires < uint64(time.Now().UnixMilli()) {
return nil, ErrGrantExpired
}
extractedPublicKey, err := crypto.SigToPub(crypto.Keccak256(payload), signature)
if err != nil {
@ -1973,6 +1979,7 @@ func (o *Community) buildGrant(key *ecdsa.PublicKey, chatID string) ([]byte, err
MemberId: crypto.CompressPubkey(key),
ChatId: chatID,
Clock: o.config.CommunityDescription.Clock,
Expires: uint64(time.Now().Add(GrantExpirationTime).UnixMilli()),
}
marshaledGrant, err := proto.Marshal(grant)
if err != nil {

View File

@ -27,6 +27,8 @@ var ErrNotAdmin = errors.New("no admin privileges for this community")
var ErrNotOwner = errors.New("no owner privileges for this community")
var ErrNotControlNode = errors.New("not a control node")
var ErrInvalidGrant = errors.New("invalid grant")
var ErrGrantExpired = errors.New("expired grant")
var ErrGrantOlder = errors.New("received grant older than the current one")
var ErrNotAuthorized = errors.New("not authorized")
var ErrAlreadyMember = errors.New("already a member")
var ErrAlreadyJoined = errors.New("already joined")
@ -46,3 +48,4 @@ var ErrNoRevealedAccountsSignature = errors.New("revealed accounts without the s
var ErrNoFreeSpaceForHistoryArchives = errors.New("history archive: No free space for downloading history archives")
var ErrPermissionToJoinNotSatisfied = errors.New("permission to join not satisfied")
var ErrBannedMemberNotFound = errors.New("banned member not found")
var ErrGrantMemberPublicKeyIsDifferent = errors.New("grant member public key is different")

View File

@ -1,6 +1,7 @@
package communities
import (
"bytes"
"context"
"crypto/ecdsa"
"database/sql"
@ -862,7 +863,7 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo
if err != nil {
return nil, err
}
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, description.Clock)
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, uint64(time.Now().UnixMilli()))
if err != nil {
return nil, err
}
@ -1415,7 +1416,7 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey, clock uint64) (*Communi
if err != nil {
return nil, err
}
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, community.Description().Clock)
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, uint64(time.Now().UnixMilli()))
if err != nil {
return nil, err
}
@ -2979,8 +2980,11 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey,
return nil, err
}
if err = m.handleCommunityGrant(community.ID(), request.Grant, request.Clock); err != nil {
return nil, err
if community.Encrypted() && len(request.Grant) > 0 {
_, err = m.HandleCommunityGrant(community, request.Grant, request.Clock)
if err != nil && err != ErrGrantOlder && err != ErrGrantExpired {
m.logger.Error("Error handling a community grant", zap.Error(err))
}
}
err = m.persistence.SaveCommunity(community)
@ -4870,17 +4874,26 @@ func (m *Manager) handleCommunityTokensMetadata(community *Community) error {
return nil
}
func (m *Manager) handleCommunityGrant(communityID types.HexBytes, grant []byte, clock uint64) error {
_, oldClock, err := m.persistence.GetCommunityGrant(communityID.String())
func (m *Manager) HandleCommunityGrant(community *Community, grant []byte, clock uint64) (uint64, error) {
_, oldClock, err := m.GetCommunityGrant(community.IDString())
if err != nil {
return err
return 0, err
}
if oldClock >= clock {
return nil
return 0, ErrGrantOlder
}
return m.persistence.SaveCommunityGrant(communityID.String(), grant, clock)
verifiedGrant, err := community.VerifyGrantSignature(grant)
if err != nil {
return 0, err
}
if !bytes.Equal(verifiedGrant.MemberId, crypto.CompressPubkey(&m.identity.PublicKey)) {
return 0, ErrGrantMemberPublicKeyIsDifferent
}
return clock - oldClock, m.persistence.SaveCommunityGrant(community.IDString(), grant, clock)
}
func (m *Manager) FetchCommunityToken(community *Community, tokenMetadata *protobuf.CommunityTokenMetadata, chainID uint64, contractAddress string) (*community_token.CommunityToken, error) {

View File

@ -3,6 +3,7 @@ package encryption
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"database/sql"
"fmt"
@ -422,6 +423,43 @@ func (p *Protocol) BuildHashRatchetMessage(groupID []byte, payload []byte) (*Pro
return spec, nil
}
func (p *Protocol) EncryptCommunityGrants(privateKey *ecdsa.PrivateKey, recipientGrants map[*ecdsa.PublicKey][]byte) (map[uint32][]byte, error) {
grants := make(map[uint32][]byte)
for recipientKey, grant := range recipientGrants {
sharedKey, err := generateSharedKey(privateKey, recipientKey)
if err != nil {
return nil, err
}
encryptedGrant, err := encrypt(grant, sharedKey, rand.Reader)
if err != nil {
return nil, err
}
kBytes := publicKeyMostRelevantBytes(recipientKey)
grants[kBytes] = encryptedGrant
}
return grants, nil
}
func (p *Protocol) DecryptCommunityGrant(myIdentityKey *ecdsa.PrivateKey, senderKey *ecdsa.PublicKey, grants map[uint32][]byte) ([]byte, error) {
kBytes := publicKeyMostRelevantBytes(&myIdentityKey.PublicKey)
ecryptedGrant, ok := grants[kBytes]
if !ok {
return nil, errors.New("can't find related grant in the map")
}
sharedKey, err := generateSharedKey(myIdentityKey, senderKey)
if err != nil {
return nil, err
}
return decrypt(ecryptedGrant, sharedKey)
}
func (p *Protocol) GetKeyExMessageSpecs(groupID []byte, identity *ecdsa.PrivateKey, recipients []*ecdsa.PublicKey, forceRekey bool) ([]*ProtocolMessageSpec, error) {
var ratchets []*HashRatchetKeyCompatibility
var err error

View File

@ -813,6 +813,7 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
m.handleCommunitiesSubscription(m.communitiesManager.Subscribe())
m.handleCommunitiesHistoryArchivesSubscription(m.communitiesManager.Subscribe())
m.updateCommunitiesActiveMembersPeriodically()
m.schedulePublishGrantsForControlledCommunities()
m.handleENSVerificationSubscription(ensSubscription)
m.watchConnectionChange()
m.watchChatsAndCommunitiesToUnmute()

View File

@ -51,6 +51,12 @@ var messageArchiveInterval = 7 * 24 * time.Hour
// 1 day interval
var updateActiveMembersInterval = 24 * time.Hour
// 1 day interval
var grantUpdateInterval = 24 * time.Hour
// 4 hours interval
var grantInvokesProfileDispatchInterval = 4 * time.Hour
const discordTimestampLayout = time.RFC3339
const (
@ -481,8 +487,8 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti
lastPublished = time.Now().Unix()
case <-m.quit:
ticker.Stop()
return
}
}
}()
@ -526,6 +532,134 @@ func (m *Messenger) updateCommunitiesActiveMembersPeriodically() {
}
case <-m.quit:
ticker.Stop()
return
}
}
}()
}
func (m *Messenger) HandleCommunityUpdateGrant(state *ReceivedMessageState, message *protobuf.CommunityUpdateGrant, statusMessage *v1protocol.StatusMessage) error {
community, err := m.communitiesManager.GetByID(message.CommunityId)
if err != nil {
return err
}
grant, err := m.encryptor.DecryptCommunityGrant(m.identity, state.CurrentMessageState.PublicKey, message.Grants)
if err != nil {
return err
}
return m.handleCommunityGrant(community, grant, message.Timestamp)
}
func (m *Messenger) handleCommunityGrant(community *communities.Community, grant []byte, clock uint64) error {
difference, err := m.communitiesManager.HandleCommunityGrant(community, grant, clock)
if err == communities.ErrGrantOlder || err == communities.ErrGrantExpired {
// Don't log an error for these cases
return nil
}
if err != nil {
return err
}
// if grant is significantly newer than the one we have, we should check the profile showcase
if time.Duration(difference)*time.Millisecond > grantInvokesProfileDispatchInterval {
err = m.UpdateProfileShowcaseCommunity(community)
if err != nil {
return err
}
}
return nil
}
func (m *Messenger) publishGroupGrantMessage(community *communities.Community, timestamp uint64, recipientGrants map[*ecdsa.PublicKey][]byte) error {
grants, err := m.encryptor.EncryptCommunityGrants(community.PrivateKey(), recipientGrants)
if err != nil {
return err
}
message := &protobuf.CommunityUpdateGrant{
Timestamp: timestamp,
CommunityId: community.ID(),
Grants: grants,
}
payload, err := proto.Marshal(message)
if err != nil {
return err
}
rawMessage := common.RawMessage{
Payload: payload,
Sender: community.PrivateKey(),
SkipEncryptionLayer: true,
MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_UPDATE_GRANT,
PubsubTopic: community.PubsubTopic(),
}
_, err = m.sender.SendPublic(context.Background(), community.IDString(), rawMessage)
return err
}
func (m *Messenger) updateGrantsForControlledCommunities() {
controlledCommunities, err := m.communitiesManager.Controlled()
if err != nil {
m.logger.Error("failed fetch controlled communities for grants update", zap.Error(err))
}
for _, community := range controlledCommunities {
// Skip unencrypted communities
if !community.Encrypted() {
continue
}
memberGrants := map[*ecdsa.PublicKey][]byte{}
for memberKey := range community.Members() {
if memberKey == m.IdentityPublicKeyString() {
grant, err := community.BuildGrant(m.IdentityPublicKey(), "")
if err != nil {
m.logger.Error("can't build own grant for controlled community", zap.Error(err))
}
err = m.handleCommunityGrant(community, grant, uint64(time.Now().UnixMilli()))
if err != nil {
m.logger.Error("error handling grant for controlled community", zap.Error(err))
}
} else {
memberPubKey, err := common.HexToPubkey(memberKey)
if err != nil {
m.logger.Error("Pubkey decode ", zap.Error(err))
}
grant, err := community.BuildGrant(memberPubKey, "")
if err != nil {
m.logger.Error("can't build member's grant for controlled community", zap.Error(err))
}
memberGrants[memberPubKey] = grant
}
}
err = m.publishGroupGrantMessage(community, uint64(time.Now().UnixMilli()), memberGrants)
if err != nil {
m.logger.Error("failed to update grant for community members", zap.Error(err))
}
}
}
func (m *Messenger) schedulePublishGrantsForControlledCommunities() {
// Send once immediately
m.updateGrantsForControlledCommunities()
ticker := time.NewTicker(grantUpdateInterval)
go func() {
for {
select {
case <-ticker.C:
m.updateGrantsForControlledCommunities()
case <-m.quit:
ticker.Stop()
return
}
}

View File

@ -253,6 +253,9 @@ func (m *Messenger) dispatchToHandler(messageState *ReceivedMessageState, protoB
case protobuf.ApplicationMetadataMessage_DELETE_COMMUNITY_MEMBER_MESSAGES:
return m.handleDeleteCommunityMemberMessagesProtobuf(messageState, protoBytes, msg, filter)
case protobuf.ApplicationMetadataMessage_COMMUNITY_UPDATE_GRANT:
return m.handleCommunityUpdateGrantProtobuf(messageState, protoBytes, msg, filter)
default:
m.logger.Info("protobuf type not found", zap.String("type", string(msg.ApplicationLayer.Type)))
return errors.New("protobuf type not found")
@ -1817,3 +1820,21 @@ func (m *Messenger) handleDeleteCommunityMemberMessagesProtobuf(messageState *Re
}
func (m *Messenger) handleCommunityUpdateGrantProtobuf(messageState *ReceivedMessageState, protoBytes []byte, msg *v1protocol.StatusMessage, filter transport.Filter) error {
m.logger.Info("handling CommunityUpdateGrant")
p := &protobuf.CommunityUpdateGrant{}
err := proto.Unmarshal(protoBytes, p)
if err != nil {
return err
}
m.outputToCSV(msg.TransportLayer.Message.Timestamp, msg.ApplicationLayer.ID, messageState.CurrentMessageState.Contact.ID, filter.ContentTopic, filter.ChatID, msg.ApplicationLayer.Type, p)
return m.HandleCommunityUpdateGrant(messageState, p, msg)
}

View File

@ -118,13 +118,13 @@ func (m *Messenger) validateCommunityMembershipEntry(
}
if community.Encrypted() {
grant, err := community.VerifyGrantSignature(entry.Grant)
verifiedGrant, err := community.VerifyGrantSignature(entry.Grant)
if err != nil {
m.logger.Warn("failed to verify grant signature ", zap.Error(err))
return identity.ProfileShowcaseMembershipStatusNotAMember, nil
return identity.ProfileShowcaseMembershipStatusUnproven, nil
}
if grant != nil && bytes.Equal(grant.MemberId, crypto.CompressPubkey(contactPubKey)) {
if bytes.Equal(verifiedGrant.MemberId, crypto.CompressPubkey(contactPubKey)) {
return identity.ProfileShowcaseMembershipStatusProvenMember, nil
}
// Show as not a member if membership can't be proven
@ -432,8 +432,6 @@ func (m *Messenger) GetProfileShowcaseForContact(contactID string, validate bool
return nil, err
}
// TODO: validate collectibles & assets ownership, https://github.com/status-im/status-desktop/issues/14129
return profileShowcase, nil
}
@ -713,6 +711,19 @@ func (m *Messenger) DeleteProfileShowcaseWalletAccount(account *accounts.Account
return nil
}
func (m *Messenger) UpdateProfileShowcaseCommunity(community *communities.Community) error {
profileCommunity, err := m.persistence.GetProfileShowcaseCommunityPreference(community.IDString())
if err != nil {
return err
}
if profileCommunity == nil {
// No corresponding profile entry, exit
return nil
}
return m.DispatchProfileShowcase()
}
func (m *Messenger) DeleteProfileShowcaseCommunity(community *communities.Community) error {
deleted, err := m.persistence.DeleteProfileShowcaseCommunityPreference(community.IDString())
if err != nil {

View File

@ -6,6 +6,7 @@ import (
"errors"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
@ -17,6 +18,7 @@ import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/identity"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/requests"
@ -702,5 +704,139 @@ func (s *TestMessengerProfileShowcase) TestProfileShowcaseProofOfMembershipEncry
s.Require().Equal(profileShowcase.Communities[0].CommunityID, aliceCommunity.IDString())
s.Require().Equal(profileShowcase.Communities[0].MembershipStatus, identity.ProfileShowcaseMembershipStatusProvenMember)
s.Require().Equal(profileShowcase.Communities[1].CommunityID, bobCommunity.IDString())
s.Require().Equal(profileShowcase.Communities[1].MembershipStatus, identity.ProfileShowcaseMembershipStatusNotAMember)
s.Require().Equal(profileShowcase.Communities[1].MembershipStatus, identity.ProfileShowcaseMembershipStatusUnproven)
}
// The scenario tested is as follow:
// 1) Owner creates an encrypted community
// 2) Bob add Alice becommes a mutual contacts
// 3) Alice and bob join the community
// 4) Alice presents the community in her profile showcase
// 5) Bob gets the community from Alice's profile showcase and validates community's membership with grant
// 6) Wait until the grant expires, Bob should not be able to validate the membership anymore (commented step)
// 7) Owner updates the grant
// 8) Bob should be able to validate the membership again
func (s *TestMessengerProfileShowcase) TestProfileShowcaseCommuniesDispatchOnGrantUpdate() {
// NOTE: smaller timeouts can lead test to be flaky
communities.GrantExpirationTime = 500 * time.Millisecond
grantInvokesProfileDispatchInterval = 1 * time.Millisecond
alice := s.m
// Set Display name to pass shouldPublishChatIdentity check
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
profileKp.KeyUID = alice.account.KeyUID
profileKp.Accounts[0].KeyUID = alice.account.KeyUID
err := alice.settings.SaveOrUpdateKeypair(profileKp)
s.Require().NoError(err)
err = alice.SetDisplayName("Alice")
s.Require().NoError(err)
// 1) Owner creates an encrypted community
owner := s.newMessengerForProfileShowcase()
defer TearDownMessenger(&s.Suite, owner)
owner.communitiesManager.PermissionChecker = &testPermissionChecker{}
community, _ := createEncryptedCommunity(&s.Suite, owner)
s.Require().True(community.Encrypted())
// 2) Bob add Alice becommes a mutual contacts
bob := s.newMessengerForProfileShowcase()
defer TearDownMessenger(&s.Suite, bob)
s.mutualContact(bob)
// 3) Alice and bob join the community
advertiseCommunityTo(&s.Suite, community, owner, alice)
advertiseCommunityTo(&s.Suite, community, owner, bob)
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
joinCommunity(&s.Suite, community, owner, alice, request, "")
joinedCommunities, err := alice.communitiesManager.Joined()
s.Require().NoError(err)
s.Require().Len(joinedCommunities, 1)
s.Require().Equal(joinedCommunities[0].IDString(), community.IDString())
s.Require().True(joinedCommunities[0].Encrypted())
grant, clock, err := alice.communitiesManager.GetCommunityGrant(community.IDString())
s.Require().NoError(err)
s.Require().NotEqual(grant, []byte{})
s.Require().True(clock > 0)
// 4) Alice presents the community in her profile showcase
err = alice.SetProfileShowcasePreferences(&identity.ProfileShowcasePreferences{
Communities: []*identity.ProfileShowcaseCommunityPreference{
&identity.ProfileShowcaseCommunityPreference{
CommunityID: community.IDString(),
ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
Order: 0,
},
},
}, false)
s.Require().NoError(err)
// 5) Bob gets the community from Alice's profile showcase and validates community's membership with grant
contactID := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))
_, err = WaitOnMessengerResponse(
bob,
func(r *MessengerResponse) bool {
return r.updatedProfileShowcaseContactIDs[contactID] == true
},
"no messages",
)
s.Require().NoError(err)
profileShowcase, err := bob.GetProfileShowcaseForContact(contactID, true)
s.Require().NoError(err)
s.Require().Len(profileShowcase.Communities, 1)
s.Require().Equal(community.IDString(), profileShowcase.Communities[0].CommunityID)
s.Require().Equal(identity.ProfileShowcaseMembershipStatusProvenMember, profileShowcase.Communities[0].MembershipStatus)
// 6) Wait until the grant expires, Bob should not be able to validate the membership anymore
time.Sleep(communities.GrantExpirationTime)
profileShowcase, err = bob.GetProfileShowcaseForContact(contactID, true)
s.Require().NoError(err)
s.Require().Len(profileShowcase.Communities, 1)
s.Require().Equal(community.IDString(), profileShowcase.Communities[0].CommunityID)
s.Require().Equal(identity.ProfileShowcaseMembershipStatusUnproven, profileShowcase.Communities[0].MembershipStatus)
// 7) Owner updates the grant
owner.updateGrantsForControlledCommunities()
// Retrieve for grant clock update
err = tt.RetryWithBackOff(func() error {
_, err = alice.RetrieveAll()
if err != nil {
return err
}
_, updatedClock, err := alice.communitiesManager.GetCommunityGrant(community.IDString())
if err != nil {
return err
}
if clock == updatedClock {
return errors.New("can't recive an updated grant")
}
return nil
})
s.Require().NoError(err)
// 8) Bob should be able to validate the membership again
_, err = WaitOnMessengerResponse(
bob,
func(r *MessengerResponse) bool {
return r.updatedProfileShowcaseContactIDs[contactID] == true
},
"no messages",
)
s.Require().NoError(err)
profileShowcase, err = bob.GetProfileShowcaseForContact(contactID, true)
s.Require().NoError(err)
s.Require().Len(profileShowcase.Communities, 1)
s.Require().Equal(profileShowcase.Communities[0].CommunityID, community.IDString())
s.Require().Equal(profileShowcase.Communities[0].MembershipStatus, identity.ProfileShowcaseMembershipStatusProvenMember)
}

View File

@ -14,6 +14,7 @@ const selectProfileShowcasePreferencesQuery = "SELECT clock FROM profile_showcas
const upsertProfileShowcaseCommunityPreferenceQuery = "INSERT OR REPLACE INTO profile_showcase_communities_preferences(community_id, visibility, sort_order) VALUES (?, ?, ?)" // #nosec G101
const selectProfileShowcaseCommunityPreferenceQuery = "SELECT community_id, visibility, sort_order FROM profile_showcase_communities_preferences" // #nosec G101
const selectSpecifiedShowcaseCommunityPreferenceQuery = "SELECT community_id, visibility, sort_order FROM profile_showcase_communities_preferences WHERE community_id = ?" // #nosec G101
const deleteProfileShowcaseCommunityPreferenceQuery = "DELETE FROM profile_showcase_communities_preferences WHERE community_id = ?" // #nosec G101
const clearProfileShowcaseCommunitiyPreferencesQuery = "DELETE FROM profile_showcase_communities_preferences" // #nosec G101
@ -99,14 +100,11 @@ func (db sqlitePersistence) saveProfileShowcaseCommunityPreference(tx *sql.Tx, c
return err
}
func (db sqlitePersistence) getProfileShowcaseCommunitiesPreferences(tx *sql.Tx) ([]*identity.ProfileShowcaseCommunityPreference, error) {
rows, err := tx.Query(selectProfileShowcaseCommunityPreferenceQuery)
if err != nil {
return nil, err
func (db sqlitePersistence) processProfileShowcaseCommunityPreferences(rows *sql.Rows) (result []*identity.ProfileShowcaseCommunityPreference, err error) {
if rows == nil {
return nil, errors.New("rows is nil")
}
communities := []*identity.ProfileShowcaseCommunityPreference{}
for rows.Next() {
community := &identity.ProfileShowcaseCommunityPreference{}
@ -120,9 +118,33 @@ func (db sqlitePersistence) getProfileShowcaseCommunitiesPreferences(tx *sql.Tx)
return nil, err
}
communities = append(communities, community)
result = append(result, community)
}
return communities, nil
err = rows.Err()
return
}
func (db sqlitePersistence) getProfileShowcaseCommunitiesPreferences(tx *sql.Tx) ([]*identity.ProfileShowcaseCommunityPreference, error) {
rows, err := tx.Query(selectProfileShowcaseCommunityPreferenceQuery)
if err != nil {
return nil, err
}
return db.processProfileShowcaseCommunityPreferences(rows)
}
func (db sqlitePersistence) GetProfileShowcaseCommunityPreference(communityID string) (*identity.ProfileShowcaseCommunityPreference, error) {
rows, err := db.db.Query(selectSpecifiedShowcaseCommunityPreferenceQuery, communityID)
if err != nil {
return nil, err
}
communities, err := db.processProfileShowcaseCommunityPreferences(rows)
if len(communities) > 0 {
return communities[0], err
}
return nil, err
}
func (db sqlitePersistence) DeleteProfileShowcaseCommunityPreference(communityID string) (bool, error) {

View File

@ -106,6 +106,7 @@ const (
ApplicationMetadataMessage_COMMUNITY_PUBLIC_STORENODES_INFO ApplicationMetadataMessage_Type = 83
ApplicationMetadataMessage_COMMUNITY_REEVALUATE_PERMISSIONS_REQUEST ApplicationMetadataMessage_Type = 84
ApplicationMetadataMessage_DELETE_COMMUNITY_MEMBER_MESSAGES ApplicationMetadataMessage_Type = 85
ApplicationMetadataMessage_COMMUNITY_UPDATE_GRANT ApplicationMetadataMessage_Type = 86
)
// Enum value maps for ApplicationMetadataMessage_Type.
@ -192,6 +193,7 @@ var (
83: "COMMUNITY_PUBLIC_STORENODES_INFO",
84: "COMMUNITY_REEVALUATE_PERMISSIONS_REQUEST",
85: "DELETE_COMMUNITY_MEMBER_MESSAGES",
86: "COMMUNITY_UPDATE_GRANT",
}
ApplicationMetadataMessage_Type_value = map[string]int32{
"UNKNOWN": 0,
@ -275,6 +277,7 @@ var (
"COMMUNITY_PUBLIC_STORENODES_INFO": 83,
"COMMUNITY_REEVALUATE_PERMISSIONS_REQUEST": 84,
"DELETE_COMMUNITY_MEMBER_MESSAGES": 85,
"COMMUNITY_UPDATE_GRANT": 86,
}
)
@ -376,7 +379,7 @@ var File_application_metadata_message_proto protoreflect.FileDescriptor
var file_application_metadata_message_proto_rawDesc = []byte{
0x0a, 0x22, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0xc3,
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0xdf,
0x15, 0x0a, 0x1a, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a,
0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
@ -386,7 +389,7 @@ var file_application_metadata_message_proto_rawDesc = []byte{
0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41,
0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0xad, 0x14, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a,
0x74, 0x79, 0x70, 0x65, 0x22, 0xc9, 0x14, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a,
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x48,
0x41, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e,
0x43, 0x4f, 0x4e, 0x54, 0x41, 0x43, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02,
@ -538,19 +541,21 @@ var file_application_metadata_message_proto_rawDesc = []byte{
0x45, 0x5f, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x52, 0x45,
0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x54, 0x12, 0x24, 0x0a, 0x20, 0x44, 0x45, 0x4c, 0x45, 0x54,
0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x55, 0x4e, 0x49, 0x54, 0x59, 0x5f, 0x4d, 0x45, 0x4d, 0x42,
0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x53, 0x10, 0x55, 0x22, 0x04, 0x08,
0x0e, 0x10, 0x0e, 0x22, 0x04, 0x08, 0x41, 0x10, 0x41, 0x22, 0x04, 0x08, 0x42, 0x10, 0x42, 0x22,
0x04, 0x08, 0x47, 0x10, 0x47, 0x2a, 0x1d, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x53, 0x54,
0x41, 0x4c, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f,
0x43, 0x48, 0x41, 0x54, 0x2a, 0x22, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56,
0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x53, 0x10, 0x55, 0x12, 0x1a, 0x0a,
0x16, 0x43, 0x4f, 0x4d, 0x4d, 0x55, 0x4e, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54,
0x45, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x10, 0x56, 0x22, 0x04, 0x08, 0x0e, 0x10, 0x0e, 0x22,
0x04, 0x08, 0x41, 0x10, 0x41, 0x22, 0x04, 0x08, 0x42, 0x10, 0x42, 0x22, 0x04, 0x08, 0x47, 0x10,
0x47, 0x2a, 0x1d, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x41,
0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x43, 0x48, 0x41, 0x54,
0x2a, 0x22, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x49, 0x54, 0x59, 0x5f,
0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54,
0x49, 0x4f, 0x4e, 0x53, 0x2a, 0x27, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56,
0x49, 0x54, 0x59, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46,
0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x2a, 0x27, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x41,
0x43, 0x54, 0x49, 0x56, 0x49, 0x54, 0x59, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x4e,
0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54,
0x45, 0x2a, 0x21, 0x43, 0x4f, 0x4d, 0x4d, 0x55, 0x4e, 0x49, 0x54, 0x59, 0x5f, 0x45, 0x56, 0x45,
0x4e, 0x54, 0x53, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x4a, 0x45,
0x43, 0x54, 0x45, 0x44, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x2a, 0x21, 0x43,
0x4f, 0x4d, 0x4d, 0x55, 0x4e, 0x49, 0x54, 0x59, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x53, 0x5f,
0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44,
0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -104,5 +104,6 @@ message ApplicationMetadataMessage {
COMMUNITY_PUBLIC_STORENODES_INFO = 83;
COMMUNITY_REEVALUATE_PERMISSIONS_REQUEST = 84;
DELETE_COMMUNITY_MEMBER_MESSAGES = 85;
COMMUNITY_UPDATE_GRANT = 86;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ message Grant {
bytes member_id = 2;
string chat_id = 3;
uint64 clock = 4;
uint64 expires = 5;
}
message CommunityMember {
@ -270,4 +271,10 @@ message DeleteCommunityMemberMessages {
bytes community_id = 2;
string member_id = 3;
repeated DeleteCommunityMemberMessage messages = 4;
}
}
message CommunityUpdateGrant {
uint64 timestamp = 1;
bytes community_id = 2;
map<uint32, bytes> grants = 3;
}