diff --git a/protocol/push_notification_server/common.go b/protocol/common/crypto.go similarity index 59% rename from protocol/push_notification_server/common.go rename to protocol/common/crypto.go index 589a6301b..b5741b2e4 100644 --- a/protocol/push_notification_server/common.go +++ b/protocol/common/crypto.go @@ -1,19 +1,24 @@ -package push_notification_server +package common import ( "crypto/aes" "crypto/cipher" "crypto/ecdsa" + "errors" "github.com/status-im/status-go/eth-node/crypto" "golang.org/x/crypto/sha3" "io" ) -func hashPublicKey(pk *ecdsa.PublicKey) []byte { - return shake256(crypto.CompressPubkey(pk)) +const nonceLength = 12 + +var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length") + +func HashPublicKey(pk *ecdsa.PublicKey) []byte { + return Shake256(crypto.CompressPubkey(pk)) } -func decrypt(cyphertext []byte, key []byte) ([]byte, error) { +func Decrypt(cyphertext []byte, key []byte) ([]byte, error) { if len(cyphertext) < nonceLength { return nil, ErrInvalidCiphertextLength } @@ -32,7 +37,7 @@ func decrypt(cyphertext []byte, key []byte) ([]byte, error) { return gcm.Open(nil, nonce, cyphertext[nonceLength:], nil) } -func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) { +func Encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err @@ -51,8 +56,14 @@ func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) { return gcm.Seal(nonce, nonce, plaintext, nil), nil } -func shake256(buf []byte) []byte { +func Shake256(buf []byte) []byte { h := make([]byte, 64) sha3.ShakeSum256(h, buf) return h } + +// IsPubKeyEqual checks that two public keys are equal +func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { + // the curve is always the same, just compare the points + return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 +} diff --git a/protocol/common/message_processor.go b/protocol/common/message_processor.go index b3d014efe..59891f145 100644 --- a/protocol/common/message_processor.go +++ b/protocol/common/message_processor.go @@ -482,9 +482,3 @@ func calculatePoW(payload []byte) float64 { } return whisperDefaultPoW } - -// IsPubKeyEqual checks that two public keys are equal -func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { - // the curve is always the same, just compare the points - return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 -} diff --git a/protocol/messenger.go b/protocol/messenger.go index 1076ecc3d..a367e73b3 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -72,6 +72,7 @@ type Messenger struct { allInstallations map[string]*multidevice.Installation modifiedInstallations map[string]bool installationID string + mailserver []byte mutex sync.Mutex } @@ -2036,13 +2037,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte return messageState.Response, nil } +func (m *Messenger) SetMailserver(peer []byte) { + m.mailserver = peer +} + func (m *Messenger) RequestHistoricMessages( ctx context.Context, - peer []byte, // should be removed after mailserver logic is ported from, to uint32, cursor []byte, ) ([]byte, error) { - return m.transport.SendMessagesRequest(ctx, peer, from, to, cursor) + if m.mailserver == nil { + return nil, errors.New("no mailserver selected") + } + return m.transport.SendMessagesRequest(ctx, m.mailserver, from, to, cursor) } func (m *Messenger) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) { @@ -2965,6 +2972,46 @@ func (m *Messenger) Timesource() TimeSource { return m.getTimesource() } +// AddPushNotificationServer adds a push notification server +func (m *Messenger) AddPushNotificationServer(ctx context.Context, publicKey *ecdsa.PublicKey) error { + if m.pushNotificationClient == nil { + return errors.New("push notification client not enabled") + } + return m.pushNotificationClient.AddPushNotificationServer(publicKey) +} + +// RegisterForPushNotification register deviceToken with any push notification server enabled +func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToken string) error { + if m.pushNotificationClient == nil { + return errors.New("push notification client not enabled") + } + + var contactIDs []*ecdsa.PublicKey + var mutedChatIDs []string + + m.mutex.Lock() + for _, contact := range m.allContacts { + if contact.IsAdded() { + pk, err := contact.PublicKey() + if err != nil { + m.logger.Warn("could not parse contact public key") + continue + } + contactIDs = append(contactIDs, pk) + } else if contact.IsBlocked() { + mutedChatIDs = append(mutedChatIDs, contact.ID) + } + } + for _, chat := range m.allChats { + if chat.Muted { + mutedChatIDs = append(mutedChatIDs, chat.ID) + } + + } + m.mutex.Unlock() + return m.pushNotificationClient.Register(deviceToken, contactIDs, mutedChatIDs) +} + func generateAliasAndIdenticon(pk string) (string, string, error) { identicon, err := identicon.GenerateBase64(pk) if err != nil { diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index 441bbecd8..d0b3aa23a 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -2106,7 +2106,8 @@ func (s *MessengerSuite) TestRequestHistoricMessagesRequest() { m := s.newMessenger(shh) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() - cursor, err := m.RequestHistoricMessages(ctx, nil, 10, 20, []byte{0x01}) + m.mailserver = []byte("mailserver-id") + cursor, err := m.RequestHistoricMessages(ctx, 10, 20, []byte{0x01}) s.EqualError(err, ctx.Err().Error()) s.Empty(cursor) // verify request is correct diff --git a/protocol/push_notification_client/migrations/1593601729_initial_schema.down.sql b/protocol/push_notification_client/migrations/1593601729_initial_schema.down.sql new file mode 100644 index 000000000..f0e2c5fa5 --- /dev/null +++ b/protocol/push_notification_client/migrations/1593601729_initial_schema.down.sql @@ -0,0 +1,3 @@ +DROP TABLE push_notification_client_servers; +DROP TABLE push_notification_client_info; +DROP INDEX idx_push_notification_client_info_public_key; diff --git a/protocol/push_notification_client/migrations/1593601729_initial_schema.up.sql b/protocol/push_notification_client/migrations/1593601729_initial_schema.up.sql new file mode 100644 index 000000000..a1efbd827 --- /dev/null +++ b/protocol/push_notification_client/migrations/1593601729_initial_schema.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS push_notification_client_servers ( + public_key BLOB NOT NULL, + registered BOOLEAN DEFAULT FALSE, + registered_at INT NOT NULL DEFAULT 0, + UNIQUE(public_key) ON CONFLICT REPLACE +); + +CREATE TABLE IF NOT EXISTS push_notification_client_info ( + public_key BLOB NOT NULL, + installation_id TEXT NOT NULL, + access_token TEXT NOT NULL, + UNIQUE(public_key, installation_id) ON CONFLICT REPLACE +); + +CREATE INDEX idx_push_notification_client_info_public_key ON push_notification_client_info(public_key, installation_id); diff --git a/protocol/push_notification_client/migrations/doc.go b/protocol/push_notification_client/migrations/doc.go new file mode 100644 index 000000000..0315ccce1 --- /dev/null +++ b/protocol/push_notification_client/migrations/doc.go @@ -0,0 +1,9 @@ +// This file is necessary because "github.com/status-im/migrate/v4" +// can't handle files starting with a prefix. At least that's the case +// for go-bindata. +// If go-bindata is called from the same directory, asset names +// have no prefix and "github.com/status-im/migrate/v4" works as expected. + +package migrations + +//go:generate go-bindata -pkg migrations -o ./migrations.go . diff --git a/protocol/push_notification_client/persistence.go b/protocol/push_notification_client/persistence.go new file mode 100644 index 000000000..7038b2cc8 --- /dev/null +++ b/protocol/push_notification_client/persistence.go @@ -0,0 +1,57 @@ +package push_notification_client + +import ( + "crypto/ecdsa" + "database/sql" + + "github.com/status-im/status-go/eth-node/crypto" +) + +type Persistence struct { + db *sql.DB +} + +func NewPersistence(db *sql.DB) *Persistence { + return &Persistence{db: db} +} + +func (p *Persistence) TrackPushNotification(messageID []byte) error { + return nil +} + +func (p *Persistence) ShouldSentNotificationFor(publicKey *ecdsa.PublicKey, messageID []byte) (bool, error) { + return false, nil +} + +func (p *Persistence) SentFor(publicKey *ecdsa.PublicKey, messageID []byte) error { + return nil +} + +func (p *Persistence) UpsertServer(server *PushNotificationServer) error { + _, err := p.db.Exec(`INSERT INTO push_notification_client_servers (public_key, registered, registered_at) VALUES (?,?,?)`, crypto.CompressPubkey(server.publicKey), server.registered, server.registeredAt) + return err + +} + +func (p *Persistence) GetServers() ([]*PushNotificationServer, error) { + rows, err := p.db.Query(`SELECT public_key, registered, registered_at FROM push_notification_client_servers`) + if err != nil { + return nil, err + } + var servers []*PushNotificationServer + for rows.Next() { + server := &PushNotificationServer{} + var key []byte + err := rows.Scan(&key, &server.registered, &server.registeredAt) + if err != nil { + return nil, err + } + parsedKey, err := crypto.DecompressPubkey(key) + if err != nil { + return nil, err + } + server.publicKey = parsedKey + servers = append(servers, server) + } + return servers, nil +} diff --git a/protocol/push_notification_client/persistence_test.go b/protocol/push_notification_client/persistence_test.go new file mode 100644 index 000000000..5a6c7a7ee --- /dev/null +++ b/protocol/push_notification_client/persistence_test.go @@ -0,0 +1,71 @@ +package push_notification_client + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/sqlite" +) + +func TestSQLitePersistenceSuite(t *testing.T) { + suite.Run(t, new(SQLitePersistenceSuite)) +} + +type SQLitePersistenceSuite struct { + suite.Suite + tmpFile *os.File + persistence *Persistence +} + +func (s *SQLitePersistenceSuite) SetupTest() { + tmpFile, err := ioutil.TempFile("", "") + s.Require().NoError(err) + s.tmpFile = tmpFile + + database, err := sqlite.Open(s.tmpFile.Name(), "") + s.Require().NoError(err) + s.persistence = NewPersistence(database) +} + +func (s *SQLitePersistenceSuite) TearDownTest() { + _ = os.Remove(s.tmpFile.Name()) +} + +func (s *SQLitePersistenceSuite) TestSaveAndRetrieveServer() { + key, err := crypto.GenerateKey() + s.Require().NoError(err) + + server := &PushNotificationServer{ + publicKey: &key.PublicKey, + registered: true, + registeredAt: 1, + } + + s.Require().NoError(s.persistence.UpsertServer(server)) + + retrievedServers, err := s.persistence.GetServers() + s.Require().NoError(err) + + s.Require().Len(retrievedServers, 1) + s.Require().True(retrievedServers[0].registered) + s.Require().Equal(int64(1), retrievedServers[0].registeredAt) + s.Require().True(common.IsPubKeyEqual(retrievedServers[0].publicKey, &key.PublicKey)) + + server.registered = false + server.registeredAt = 2 + + s.Require().NoError(s.persistence.UpsertServer(server)) + + retrievedServers, err = s.persistence.GetServers() + s.Require().NoError(err) + + s.Require().Len(retrievedServers, 1) + s.Require().False(retrievedServers[0].registered) + s.Require().Equal(int64(2), retrievedServers[0].registeredAt) + s.Require().True(common.IsPubKeyEqual(retrievedServers[0].publicKey, &key.PublicKey)) +} diff --git a/protocol/push_notification_client/push_notification.go b/protocol/push_notification_client/push_notification.go index 50ccf1b3d..3b94ca7f2 100644 --- a/protocol/push_notification_client/push_notification.go +++ b/protocol/push_notification_client/push_notification.go @@ -20,8 +20,15 @@ import ( const accessTokenKeyLength = 16 type PushNotificationServer struct { - key *ecdsa.PublicKey - registered bool + publicKey *ecdsa.PublicKey + registered bool + registeredAt int64 +} + +type PushNotificationInfo struct { + AccessToken string + InstallationID string + PublicKey *ecdsa.PublicKey } type Config struct { @@ -35,10 +42,6 @@ type Config struct { // AllowOnlyFromContacts indicates whether we should be receiving push notifications // only from contacts AllowOnlyFromContacts bool - // ContactIDs is the public keys for each contact that we allow notifications from - ContactIDs []*ecdsa.PublicKey - // MutedChatIDs is the IDs of the chats we don't want to receive notifications from - MutedChatIDs []string // PushNotificationServers is an array of push notification servers we want to register with PushNotificationServers []*PushNotificationServer // InstallationID is the installation-id for this device @@ -122,10 +125,10 @@ func (p *Client) NotifyOnMessageID(messageID []byte) error { return nil } -func (p *Client) mutedChatIDsHashes() [][]byte { +func (p *Client) mutedChatIDsHashes(chatIDs []string) [][]byte { var mutedChatListHashes [][]byte - for _, chatID := range p.config.MutedChatIDs { + for _, chatID := range chatIDs { mutedChatListHashes = append(mutedChatListHashes, shake256(chatID)) } @@ -148,9 +151,9 @@ func (p *Client) encryptToken(publicKey *ecdsa.PublicKey, token []byte) ([]byte, return encryptedToken, nil } -func (p *Client) allowedUserList(token []byte) ([][]byte, error) { +func (p *Client) allowedUserList(token []byte, contactIDs []*ecdsa.PublicKey) ([][]byte, error) { var encryptedTokens [][]byte - for _, publicKey := range p.config.ContactIDs { + for _, publicKey := range contactIDs { encryptedToken, err := p.encryptToken(publicKey, token) if err != nil { return nil, err @@ -162,9 +165,9 @@ func (p *Client) allowedUserList(token []byte) ([][]byte, error) { return encryptedTokens, nil } -func (p *Client) buildPushNotificationRegistrationMessage() (*protobuf.PushNotificationRegistration, error) { +func (p *Client) buildPushNotificationRegistrationMessage(contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) (*protobuf.PushNotificationRegistration, error) { token := uuid.New().String() - allowedUserList, err := p.allowedUserList([]byte(token)) + allowedUserList, err := p.allowedUserList([]byte(token), contactIDs) if err != nil { return nil, err } @@ -175,13 +178,20 @@ func (p *Client) buildPushNotificationRegistrationMessage() (*protobuf.PushNotif InstallationId: p.config.InstallationID, Token: p.DeviceToken, Enabled: p.config.RemoteNotificationsEnabled, - BlockedChatList: p.mutedChatIDsHashes(), + BlockedChatList: p.mutedChatIDsHashes(mutedChatIDs), AllowedUserList: allowedUserList, } return options, nil } -func (p *Client) Register(deviceToken string) error { +func (p *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error { + servers, err := p.persistence.GetServers() + if err != nil { + return err + } + if len(servers) == 0 { + return errors.New("no servers to register with") + } return nil } @@ -205,16 +215,40 @@ func (p *Client) HandlePushNotificationResponse(ack *protobuf.PushNotificationRe return nil } -func (p *Client) SetContactIDs(contactIDs []*ecdsa.PublicKey) error { - p.config.ContactIDs = contactIDs - // Update or schedule update - return nil +func (c *Client) AddPushNotificationServer(publicKey *ecdsa.PublicKey) error { + currentServers, err := c.persistence.GetServers() + if err != nil { + return err + } + + for _, server := range currentServers { + if common.IsPubKeyEqual(server.publicKey, publicKey) { + return errors.New("push notification server already added") + } + } + + return c.persistence.UpsertServer(&PushNotificationServer{ + publicKey: publicKey, + }) } -func (p *Client) SetMutedChatIDs(chatIDs []string) error { - p.config.MutedChatIDs = chatIDs - // Update or schedule update - return nil +func (c *Client) RetrievePushNotificationInfo(publicKey *ecdsa.PublicKey) ([]*PushNotificationInfo, error) { + return nil, nil + /* + currentServers, err := c.persistence.GetServers() + if err != nil { + return err + } + + for _, server := range currentServers { + if common.IsPubKeyEqual(server.publicKey, publicKey) { + return errors.New("push notification server already added") + } + } + + return c.persistence.UpsertServer(&PushNotificationServer{ + publicKey: publicKey, + })*/ } func encryptAccessToken(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) { diff --git a/protocol/push_notification_client/push_notification_persistence.go b/protocol/push_notification_client/push_notification_persistence.go deleted file mode 100644 index 7305ced91..000000000 --- a/protocol/push_notification_client/push_notification_persistence.go +++ /dev/null @@ -1,26 +0,0 @@ -package push_notification_client - -import ( - "crypto/ecdsa" - "database/sql" -) - -type Persistence struct { - db *sql.DB -} - -func NewPersistence(db *sql.DB) *Persistence { - return &Persistence{db: db} -} - -func (p *Persistence) TrackPushNotification(messageID []byte) error { - return nil -} - -func (p *Persistence) ShouldSentNotificationFor(publicKey *ecdsa.PublicKey, messageID []byte) (bool, error) { - return false, nil -} -func (p *Persistence) SentFor(publicKey *ecdsa.PublicKey, messageID []byte) error { - - return nil -} diff --git a/protocol/push_notification_client/push_notification_test.go b/protocol/push_notification_client/push_notification_test.go index d0b058a16..1791fd92e 100644 --- a/protocol/push_notification_client/push_notification_test.go +++ b/protocol/push_notification_client/push_notification_test.go @@ -56,8 +56,6 @@ func TestBuildPushNotificationRegisterMessage(t *testing.T) { config := &Config{ Identity: identity, RemoteNotificationsEnabled: true, - MutedChatIDs: mutedChatList, - ContactIDs: contactIDs, InstallationID: myInstallationID, } @@ -77,7 +75,7 @@ func TestBuildPushNotificationRegisterMessage(t *testing.T) { AllowedUserList: [][]byte{encryptedToken}, } - actualMessage, err := client.buildPushNotificationRegistrationMessage() + actualMessage, err := client.buildPushNotificationRegistrationMessage(contactIDs, mutedChatList) require.NoError(t, err) require.Equal(t, options, actualMessage) diff --git a/protocol/push_notification_server/errors.go b/protocol/push_notification_server/errors.go index 176215483..33a78dc9c 100644 --- a/protocol/push_notification_server/errors.go +++ b/protocol/push_notification_server/errors.go @@ -7,7 +7,6 @@ var ErrEmptyPushNotificationRegistrationPayload = errors.New("empty payload") var ErrMalformedPushNotificationRegistrationInstallationID = errors.New("invalid installationID") var ErrEmptyPushNotificationRegistrationPublicKey = errors.New("no public key") var ErrCouldNotUnmarshalPushNotificationRegistration = errors.New("could not unmarshal preferences") -var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length") var ErrMalformedPushNotificationRegistrationDeviceToken = errors.New("invalid device token") var ErrMalformedPushNotificationRegistrationAccessToken = errors.New("invalid access token") var ErrUnknownPushNotificationRegistrationTokenType = errors.New("invalid token type") diff --git a/protocol/push_notification_server/push_notification_server.go b/protocol/push_notification_server/push_notification_server.go index 2bd4f4400..acc398f4d 100644 --- a/protocol/push_notification_server/push_notification_server.go +++ b/protocol/push_notification_server/push_notification_server.go @@ -1,6 +1,7 @@ package push_notification_server import ( + "context" "crypto/ecdsa" "errors" @@ -14,7 +15,6 @@ import ( ) const encryptedPayloadKeyLength = 16 -const nonceLength = 12 type Config struct { // Identity is our identity key @@ -57,7 +57,7 @@ func (p *Server) decryptRegistration(publicKey *ecdsa.PublicKey, payload []byte) return nil, err } - return decrypt(payload, sharedKey) + return common.Decrypt(payload, sharedKey) } // ValidateRegistration validates a new message against the last one received for a given installationID and and public key @@ -90,7 +90,7 @@ func (p *Server) ValidateRegistration(publicKey *ecdsa.PublicKey, payload []byte return nil, ErrMalformedPushNotificationRegistrationInstallationID } - previousRegistration, err := p.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(publicKey), registration.InstallationId) + previousRegistration, err := p.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(publicKey), registration.InstallationId) if err != nil { return nil, err } @@ -205,7 +205,7 @@ func (p *Server) HandlePushNotificationRequest(request *protobuf.PushNotificatio func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, payload []byte) *protobuf.PushNotificationRegistrationResponse { response := &protobuf.PushNotificationRegistrationResponse{ - RequestId: shake256(payload), + RequestId: common.Shake256(payload), } registration, err := p.ValidateRegistration(publicKey, payload) @@ -227,12 +227,12 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, Version: registration.Version, InstallationId: registration.InstallationId, } - if err := p.persistence.SavePushNotificationRegistration(hashPublicKey(publicKey), emptyRegistration); err != nil { + if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), emptyRegistration); err != nil { response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR return response } - } else if err := p.persistence.SavePushNotificationRegistration(hashPublicKey(publicKey), registration); err != nil { + } else if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), registration); err != nil { response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR return response } @@ -243,17 +243,60 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, } func (p *Server) HandlePushNotificationRegistration2(publicKey *ecdsa.PublicKey, payload []byte) error { - return nil + response := p.HandlePushNotificationRegistration(publicKey, payload) + if response == nil { + return nil + } + encodedMessage, err := proto.Marshal(response) + if err != nil { + return err + } + rawMessage := &common.RawMessage{ + Payload: encodedMessage, + MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION_RESPONSE, + } + + _, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage) + return err } func (p *Server) HandlePushNotificationQuery2(publicKey *ecdsa.PublicKey, query protobuf.PushNotificationQuery) error { - return nil + response := p.HandlePushNotificationQuery(&query) + if response == nil { + return nil + } + encodedMessage, err := proto.Marshal(response) + if err != nil { + return err + } + + rawMessage := &common.RawMessage{ + Payload: encodedMessage, + MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_QUERY_RESPONSE, + } + + _, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage) + return err } func (p *Server) HandlePushNotificationRequest2(publicKey *ecdsa.PublicKey, request protobuf.PushNotificationRequest) error { - return nil + response := p.HandlePushNotificationRequest(&request) + if response == nil { + return nil + } + encodedMessage, err := proto.Marshal(response) + if err != nil { + return err + } + rawMessage := &common.RawMessage{ + Payload: encodedMessage, + MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_RESPONSE, + } + + _, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage) + return err } diff --git a/protocol/push_notification_server/push_notification_server_persistence_test.go b/protocol/push_notification_server/push_notification_server_persistence_test.go index 258a0c224..0f398f567 100644 --- a/protocol/push_notification_server/push_notification_server_persistence_test.go +++ b/protocol/push_notification_server/push_notification_server_persistence_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/sqlite" ) @@ -49,9 +50,9 @@ func (s *SQLitePersistenceSuite) TestSaveAndRetrieve() { Version: 5, } - s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&key.PublicKey), registration)) + s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&key.PublicKey), registration)) - retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&key.PublicKey), installationID) + retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&key.PublicKey), installationID) s.Require().NoError(err) s.Require().True(proto.Equal(registration, retrievedRegistration)) diff --git a/protocol/push_notification_server/push_notification_server_test.go b/protocol/push_notification_server/push_notification_server_test.go index 7ecb4f831..420f09d8d 100644 --- a/protocol/push_notification_server/push_notification_server_test.go +++ b/protocol/push_notification_server/push_notification_server_test.go @@ -11,6 +11,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/stretchr/testify/suite" + "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/sqlite" ) @@ -76,24 +77,24 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { // Invalid cyphertext length _, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("too short")) - s.Require().Equal(ErrInvalidCiphertextLength, err) + s.Require().Equal(common.ErrInvalidCiphertextLength, err) // Invalid cyphertext length _, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("too short")) - s.Require().Equal(ErrInvalidCiphertextLength, err) + s.Require().Equal(common.ErrInvalidCiphertextLength, err) // Invalid ciphertext _, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("not too short but invalid")) - s.Require().Error(ErrInvalidCiphertextLength, err) + s.Require().Error(common.ErrInvalidCiphertextLength, err) // Different key ciphertext - cyphertext, err := encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader) + cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Error(err) // Right cyphertext but non unmarshable payload - cyphertext, err = encrypt([]byte("plaintext"), s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrCouldNotUnmarshalPushNotificationRegistration, err) @@ -106,7 +107,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err) @@ -118,7 +119,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { InstallationId: "abc", Version: 1, }) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err) @@ -131,7 +132,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err) @@ -145,11 +146,11 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) // Setup persistence - s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{ + s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{ AccessToken: s.accessToken, TokenType: protobuf.PushNotificationRegistration_APN_TOKEN, InstallationId: s.installationID, @@ -159,7 +160,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err) // Cleanup persistence - s.Require().NoError(s.persistence.DeletePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), s.installationID)) + s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID)) // Unregistering message payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{ @@ -170,7 +171,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Nil(err) @@ -183,7 +184,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err) @@ -197,7 +198,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err) @@ -211,7 +212,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrMalformedPushNotificationRegistrationDeviceToken, err) @@ -225,7 +226,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().Equal(ErrUnknownPushNotificationRegistrationTokenType, err) @@ -240,7 +241,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) _, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext) s.Require().NoError(err) @@ -278,7 +279,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE) // Different key ciphertext - cyphertext, err := encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader) + cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -286,7 +287,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE) // Right cyphertext but non unmarshable payload - cyphertext, err = encrypt([]byte("plaintext"), s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -300,7 +301,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -313,7 +314,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { InstallationId: "abc", Version: 1, }) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -327,7 +328,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -342,11 +343,11 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) // Setup persistence - s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{ + s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{ AccessToken: s.accessToken, InstallationId: s.installationID, Version: 2})) @@ -357,7 +358,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH) // Cleanup persistence - s.Require().NoError(s.persistence.DeletePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), s.installationID)) + s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID)) // Missing access token payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{ @@ -366,7 +367,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -381,7 +382,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -396,7 +397,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -414,14 +415,14 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { payload, err = proto.Marshal(registration) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) s.Require().True(response.Success) // Pull from the db - retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&s.key.PublicKey), s.installationID) + retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID) s.Require().NoError(err) s.Require().NotNil(retrievedRegistration) s.Require().True(proto.Equal(retrievedRegistration, registration)) @@ -435,25 +436,25 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { }) s.Require().NoError(err) - cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) s.Require().True(response.Success) // Check is gone from the db - retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&s.key.PublicKey), s.installationID) + retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID) s.Require().NoError(err) s.Require().NotNil(retrievedRegistration) s.Require().Empty(retrievedRegistration.AccessToken) s.Require().Empty(retrievedRegistration.Token) s.Require().Equal(uint64(2), retrievedRegistration.Version) s.Require().Equal(s.installationID, retrievedRegistration.InstallationId) - s.Require().Equal(shake256(cyphertext), response.RequestId) + s.Require().Equal(common.Shake256(cyphertext), response.RequestId) } func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() { - hashedPublicKey := hashPublicKey(&s.key.PublicKey) + hashedPublicKey := common.HashPublicKey(&s.key.PublicKey) // Successful registration := &protobuf.PushNotificationRegistration{ Token: "abc", @@ -465,7 +466,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() { payload, err := proto.Marshal(registration) s.Require().NoError(err) - cyphertext, err := encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response := s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) @@ -486,7 +487,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() { } func (s *ServerSuite) TestHandlePushNotificationQueryWithFiltering() { - hashedPublicKey := hashPublicKey(&s.key.PublicKey) + hashedPublicKey := common.HashPublicKey(&s.key.PublicKey) allowedUserList := [][]byte{[]byte("a")} // Successful @@ -501,7 +502,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryWithFiltering() { payload, err := proto.Marshal(registration) s.Require().NoError(err) - cyphertext, err := encrypt(payload, s.sharedKey, rand.Reader) + cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader) s.Require().NoError(err) response := s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) diff --git a/protocol/push_notification_test.go b/protocol/push_notification_test.go new file mode 100644 index 000000000..a993ef780 --- /dev/null +++ b/protocol/push_notification_test.go @@ -0,0 +1,145 @@ +package protocol + +import ( + "context" + "crypto/ecdsa" + "io/ioutil" + "os" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/tt" + "github.com/status-im/status-go/whisper/v6" +) + +func TestMessengerPushNotificationSuite(t *testing.T) { + suite.Run(t, new(MessengerPushNotificationSuite)) +} + +type MessengerPushNotificationSuite struct { + suite.Suite + m *Messenger // main instance of Messenger + privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger + // If one wants to send messages between different instances of Messenger, + // a single Whisper service should be shared. + shh types.Whisper + tmpFiles []*os.File // files to clean up + logger *zap.Logger +} + +func (s *MessengerPushNotificationSuite) SetupTest() { + s.logger = tt.MustCreateTestLogger() + + config := whisper.DefaultConfig + config.MinimumAcceptedPOW = 0 + shh := whisper.New(&config) + s.shh = gethbridge.NewGethWhisperWrapper(shh) + s.Require().NoError(shh.Start(nil)) + + s.m = s.newMessenger(s.shh) + s.privateKey = s.m.identity +} + +func (s *MessengerPushNotificationSuite) newMessengerWithKey(shh types.Whisper, privateKey *ecdsa.PrivateKey) *Messenger { + tmpFile, err := ioutil.TempFile("", "") + s.Require().NoError(err) + + options := []Option{ + WithCustomLogger(s.logger), + WithMessagesPersistenceEnabled(), + WithDatabaseConfig(tmpFile.Name(), "some-key"), + WithDatasync(), + } + m, err := NewMessenger( + privateKey, + &testNode{shh: shh}, + uuid.New().String(), + options..., + ) + s.Require().NoError(err) + + err = m.Init() + s.Require().NoError(err) + + s.tmpFiles = append(s.tmpFiles, tmpFile) + + return m +} + +func (s *MessengerPushNotificationSuite) newMessenger(shh types.Whisper) *Messenger { + privateKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + return s.newMessengerWithKey(s.shh, privateKey) +} + +func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { + deviceToken := "token" + + server := s.newMessenger(s.shh) + client2 := s.newMessenger(s.shh) + + err := s.m.AddPushNotificationServer(context.Background(), &server.identity.PublicKey) + s.Require().NoError(err) + + err = s.m.RegisterForPushNotifications(context.Background(), deviceToken) + s.Require().NoError(err) + + info, err := client2.pushNotificationClient.RetrievePushNotificationInfo(&s.m.identity.PublicKey) + s.Require().NoError(err) + s.Require().NotNil(info) + + /* + s.Require().Len(response.Contacts, 1) + contact := response.Contacts[0] + s.Require().True(contact.IsAdded()) + + s.Require().Len(response.Chats, 1) + chat := response.Chats[0] + s.Require().False(chat.Active, "It does not create an active chat") + + // Wait for the message to reach its destination + response, err = WaitOnMessengerResponse( + s.m, + func(r *MessengerResponse) bool { return len(r.Contacts) > 0 }, + "contact request not received", + ) + s.Require().NoError(err) + + receivedContact := response.Contacts[0] + s.Require().Equal(theirName, receivedContact.Name) + s.Require().Equal(theirPicture, receivedContact.Photo) + s.Require().False(receivedContact.ENSVerified) + s.Require().True(receivedContact.HasBeenAdded()) + s.Require().NotEmpty(receivedContact.LastUpdated) + + newPicture := "new-picture" + err = theirMessenger.SendPushNotifications(context.Background(), newName, newPicture) + s.Require().NoError(err) + + // Wait for the message to reach its destination + response, err = WaitOnMessengerResponse( + s.m, + func(r *MessengerResponse) bool { + return len(r.Contacts) > 0 && response.Contacts[0].ID == theirContactID + }, + "contact request not received", + ) + + s.Require().NoError(err) + + receivedContact = response.Contacts[0] + s.Require().Equal(theirContactID, receivedContact.ID) + s.Require().Equal(newName, receivedContact.Name) + s.Require().Equal(newPicture, receivedContact.Photo) + s.Require().False(receivedContact.ENSVerified) + s.Require().True(receivedContact.HasBeenAdded()) + s.Require().NotEmpty(receivedContact.LastUpdated) + */ +} diff --git a/protocol/sqlite/migrations.go b/protocol/sqlite/migrations.go index f082352cd..50b381a6d 100644 --- a/protocol/sqlite/migrations.go +++ b/protocol/sqlite/migrations.go @@ -7,6 +7,7 @@ import ( encryptmigrations "github.com/status-im/status-go/protocol/encryption/migrations" appmigrations "github.com/status-im/status-go/protocol/migrations" + push_notification_client_migrations "github.com/status-im/status-go/protocol/push_notification_client/migrations" push_notification_server_migrations "github.com/status-im/status-go/protocol/push_notification_server/migrations" wakumigrations "github.com/status-im/status-go/protocol/transport/waku/migrations" whispermigrations "github.com/status-im/status-go/protocol/transport/whisper/migrations" @@ -40,6 +41,10 @@ var defaultMigrations = []migrationsWithGetter{ Names: push_notification_server_migrations.AssetNames(), Getter: push_notification_server_migrations.Asset, }, + { + Names: push_notification_client_migrations.AssetNames(), + Getter: push_notification_client_migrations.Asset, + }, } func prepareMigrations(migrations []migrationsWithGetter) ([]string, getter, error) { diff --git a/services/ext/service.go b/services/ext/service.go index 36045c0b9..5346892fa 100644 --- a/services/ext/service.go +++ b/services/ext/service.go @@ -339,6 +339,9 @@ func (s *Service) DisableInstallation(installationID string) error { // UpdateMailservers updates information about selected mail servers. func (s *Service) UpdateMailservers(nodes []*enode.Node) error { + if len(nodes) > 0 && s.messenger != nil { + s.messenger.SetMailserver(nodes[0].ID().Bytes()) + } if err := s.peerStore.Update(nodes); err != nil { return err }