2020-07-22 09:41:40 +02:00
package pushnotificationserver
2020-06-30 10:30:58 +02:00
import (
2020-08-25 11:46:01 +02:00
"bytes"
2020-07-07 11:00:04 +02:00
"context"
2020-06-30 15:14:25 +02:00
"crypto/ecdsa"
2020-07-09 18:52:26 +02:00
"encoding/hex"
2020-06-30 15:14:25 +02:00
2020-06-30 16:55:24 +02:00
"github.com/golang/protobuf/proto"
2020-07-01 10:37:54 +02:00
"github.com/google/uuid"
2020-08-20 09:26:00 +02:00
"github.com/pkg/errors"
2020-07-22 09:41:40 +02:00
"go.uber.org/zap"
2020-06-30 16:55:24 +02:00
2020-07-13 10:53:13 +02:00
"github.com/status-im/status-go/eth-node/crypto"
2020-06-30 15:14:25 +02:00
"github.com/status-im/status-go/eth-node/crypto/ecies"
2020-07-14 16:07:19 +02:00
"github.com/status-im/status-go/eth-node/types"
2020-07-06 10:54:22 +02:00
"github.com/status-im/status-go/protocol/common"
2020-06-30 10:30:58 +02:00
"github.com/status-im/status-go/protocol/protobuf"
)
2020-06-30 15:14:25 +02:00
const encryptedPayloadKeyLength = 16
parent 3179532b645549c103266e007694d2c81a7091b4
author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530
committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530
test: Code Migration from status-cli-tests
fix_: functional tests (#5979)
* fix_: generate on test-functional
* chore(test)_: fix functional test assertion
---------
Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com>
feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977)
* feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766)
The original GH issue https://github.com/status-im/status-mobile/issues/21113
came from a request from the Legal team. We must show to Status v1 users the new
terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2
from the stores.
The solution we use is to create a flag in the accounts table, named
hasAcceptedTerms. The flag will be set to true on the first account ever
created in v2 and we provide a native call in mobile/status.go#AcceptTerms,
which allows the client to persist the user's choice in case they are upgrading
(from v1 -> v2, or from a v2 older than this PR).
This solution is not the best because we should store the setting in a separate
table, not in the accounts table.
Related Mobile PR https://github.com/status-im/status-mobile/pull/21124
* fix(test)_: Compare addresses using uppercased strings
---------
Co-authored-by: Icaro Motta <icaro.ldm@gmail.com>
test_: restore account (#5960)
feat_: `LogOnPanic` linter (#5969)
* feat_: LogOnPanic linter
* fix_: add missing defer LogOnPanic
* chore_: make vendor
* fix_: tests, address pr comments
* fix_: address pr comments
fix(ci)_: remove workspace and tmp dir
This ensures we do not encounter weird errors like:
```
+ ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go
ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists
script returned exit code 1
```
Signed-off-by: Jakub Sokołowski <jakub@status.im>
chore_: enable windows and macos CI build (#5840)
- Added support for Windows and macOS in CI pipelines
- Added missing dependencies for Windows and x86-64-darwin
- Resolved macOS SDK version compatibility for darwin-x86_64
The `mkShell` override was necessary to ensure compatibility with the newer
macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures
because of the missing libs and frameworks. OverrideSDK creates a mapping from
the default SDK in all package categories to the requested SDK (11.0).
fix(contacts)_: fix trust status not being saved to cache when changed (#5965)
Fixes https://github.com/status-im/status-desktop/issues/16392
cleanup
added logger and cleanup
review comments changes
fix_: functional tests (#5979)
* fix_: generate on test-functional
* chore(test)_: fix functional test assertion
---------
Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com>
feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977)
* feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766)
The original GH issue https://github.com/status-im/status-mobile/issues/21113
came from a request from the Legal team. We must show to Status v1 users the new
terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2
from the stores.
The solution we use is to create a flag in the accounts table, named
hasAcceptedTerms. The flag will be set to true on the first account ever
created in v2 and we provide a native call in mobile/status.go#AcceptTerms,
which allows the client to persist the user's choice in case they are upgrading
(from v1 -> v2, or from a v2 older than this PR).
This solution is not the best because we should store the setting in a separate
table, not in the accounts table.
Related Mobile PR https://github.com/status-im/status-mobile/pull/21124
* fix(test)_: Compare addresses using uppercased strings
---------
Co-authored-by: Icaro Motta <icaro.ldm@gmail.com>
test_: restore account (#5960)
feat_: `LogOnPanic` linter (#5969)
* feat_: LogOnPanic linter
* fix_: add missing defer LogOnPanic
* chore_: make vendor
* fix_: tests, address pr comments
* fix_: address pr comments
chore_: enable windows and macos CI build (#5840)
- Added support for Windows and macOS in CI pipelines
- Added missing dependencies for Windows and x86-64-darwin
- Resolved macOS SDK version compatibility for darwin-x86_64
The `mkShell` override was necessary to ensure compatibility with the newer
macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures
because of the missing libs and frameworks. OverrideSDK creates a mapping from
the default SDK in all package categories to the requested SDK (11.0).
fix(contacts)_: fix trust status not being saved to cache when changed (#5965)
Fixes https://github.com/status-im/status-desktop/issues/16392
test_: remove port bind
chore(wallet)_: move route execution code to separate module
chore_: replace geth logger with zap logger (#5962)
closes: #6002
feat(telemetry)_: add metrics for message reliability (#5899)
* feat(telemetry)_: track message reliability
Add metrics for dial errors, missed messages,
missed relevant messages, and confirmed delivery.
* fix_: handle error from json marshal
chore_: use zap logger as request logger
iterates: status-im/status-desktop#16536
test_: unique project per run
test_: use docker compose v2, more concrete project name
fix(codecov)_: ignore folders without tests
Otherwise Codecov reports incorrect numbers when making changes.
https://docs.codecov.com/docs/ignoring-paths
Signed-off-by: Jakub Sokołowski <jakub@status.im>
test_: verify schema of signals during init; fix schema verification warnings (#5947)
fix_: update defaultGorushURL (#6011)
fix(tests)_: use non-standard port to avoid conflicts
We have observed `nimbus-eth2` build failures reporting this port:
```json
{
"lvl": "NTC",
"ts": "2024-10-28 13:51:32.308+00:00",
"msg": "REST HTTP server could not be started",
"topics": "beacnde",
"address": "127.0.0.1:5432",
"reason": "(98) Address already in use"
}
```
https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/
Signed-off-by: Jakub Sokołowski <jakub@status.im>
fix_: create request logger ad-hoc in tests
Fixes `TestCall` failing when run concurrently.
chore_: configure codecov (#6005)
* chore_: configure codecov
* fix_: after_n_builds
2024-10-24 19:59:15 +05:30
const defaultGorushURL = "https://gorush.infra.status.im/"
2020-06-30 15:14:25 +02:00
2020-09-07 10:25:57 +02:00
var errUnhandledPushNotificationType = errors . New ( "unhandled push notification type" )
2020-06-30 10:30:58 +02:00
type Config struct {
2020-08-20 09:26:00 +02:00
Enabled bool
2020-06-30 10:30:58 +02:00
// Identity is our identity key
Identity * ecdsa . PrivateKey
// GorushUrl is the url for the gorush service
GorushURL string
2020-07-03 12:08:47 +02:00
Logger * zap . Logger
2020-06-30 10:30:58 +02:00
}
type Server struct {
2021-06-23 17:13:48 +03:00
persistence Persistence
config * Config
messageSender * common . MessageSender
2021-03-31 18:23:45 +02:00
// SentRequests keeps track of the requests sent to gorush, for testing only
SentRequests int64
2020-06-30 10:30:58 +02:00
}
2021-06-23 17:13:48 +03:00
func New ( config * Config , persistence Persistence , messageSender * common . MessageSender ) * Server {
2020-07-13 12:39:33 +02:00
if len ( config . GorushURL ) == 0 {
config . GorushURL = defaultGorushURL
}
2021-06-23 17:13:48 +03:00
return & Server { persistence : persistence , config : config , messageSender : messageSender }
2020-06-30 10:30:58 +02:00
}
2020-07-22 09:41:40 +02:00
func ( s * Server ) Start ( ) error {
2020-08-20 09:26:00 +02:00
if s . config . Logger == nil {
logger , err := zap . NewDevelopment ( )
if err != nil {
return errors . Wrap ( err , "failed to create a logger" )
}
s . config . Logger = logger
}
2020-07-22 09:41:40 +02:00
s . config . Logger . Info ( "starting push notification server" )
if s . config . Identity == nil {
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "Identity nil" )
2020-07-22 09:41:40 +02:00
// Pull identity from database
identity , err := s . persistence . GetIdentity ( )
if err != nil {
return err
}
if identity == nil {
identity , err = crypto . GenerateKey ( )
if err != nil {
return err
}
if err := s . persistence . SaveIdentity ( identity ) ; err != nil {
return err
}
}
s . config . Identity = identity
}
pks , err := s . persistence . GetPushNotificationRegistrationPublicKeys ( )
if err != nil {
return err
}
// listen to all topics for users registered
for _ , pk := range pks {
if err := s . listenToPublicKeyQueryTopic ( pk ) ; err != nil {
return err
}
}
s . config . Logger . Info ( "started push notification server" , zap . String ( "identity" , types . EncodeHex ( crypto . FromECDSAPub ( & s . config . Identity . PublicKey ) ) ) )
return nil
}
// HandlePushNotificationRegistration builds a response for the registration and sends it back to the user
func ( s * Server ) HandlePushNotificationRegistration ( publicKey * ecdsa . PublicKey , payload [ ] byte ) error {
response := s . buildPushNotificationRegistrationResponse ( publicKey , payload )
if response == nil {
return nil
}
encodedMessage , err := proto . Marshal ( response )
if err != nil {
return err
}
2020-07-28 15:22:22 +02:00
rawMessage := common . RawMessage {
2020-07-22 09:41:40 +02:00
Payload : encodedMessage ,
MessageType : protobuf . ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION_RESPONSE ,
// we skip encryption as might be sent from an ephemeral key
2023-11-08 19:05:33 +01:00
SkipEncryptionLayer : true ,
2020-07-22 09:41:40 +02:00
}
2021-06-23 17:13:48 +03:00
_ , err = s . messageSender . SendPrivate ( context . Background ( ) , publicKey , & rawMessage )
2020-07-22 09:41:40 +02:00
return err
}
// HandlePushNotificationQuery builds a response for the query and sends it back to the user
2023-08-18 12:39:59 +01:00
func ( s * Server ) HandlePushNotificationQuery ( publicKey * ecdsa . PublicKey , messageID [ ] byte , query * protobuf . PushNotificationQuery ) error {
if query == nil {
return nil
}
response := s . buildPushNotificationQueryResponse ( query )
2020-07-22 09:41:40 +02:00
if response == nil {
return nil
}
response . MessageId = messageID
encodedMessage , err := proto . Marshal ( response )
if err != nil {
return err
}
2020-07-28 15:22:22 +02:00
rawMessage := common . RawMessage {
2020-07-22 09:41:40 +02:00
Payload : encodedMessage ,
MessageType : protobuf . ApplicationMetadataMessage_PUSH_NOTIFICATION_QUERY_RESPONSE ,
// we skip encryption as sent from an ephemeral key
2023-11-08 19:05:33 +01:00
SkipEncryptionLayer : true ,
2020-07-22 09:41:40 +02:00
}
2021-06-23 17:13:48 +03:00
_ , err = s . messageSender . SendPrivate ( context . Background ( ) , publicKey , & rawMessage )
2020-07-22 09:41:40 +02:00
return err
}
// HandlePushNotificationRequest will send a gorush notification and send a response back to the user
func ( s * Server ) HandlePushNotificationRequest ( publicKey * ecdsa . PublicKey ,
2020-08-26 07:54:00 +02:00
messageID [ ] byte ,
2023-08-18 12:39:59 +01:00
request * protobuf . PushNotificationRequest ) error {
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "handling pn request" , zap . Binary ( "message-id" , messageID ) )
2020-08-26 07:54:00 +02:00
// This is at-most-once semantic for now
exists , err := s . persistence . PushNotificationExists ( messageID )
if err != nil {
return err
}
if exists {
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "already handled" )
2020-08-26 07:54:00 +02:00
return nil
}
2023-08-18 12:39:59 +01:00
response , requestsAndRegistrations := s . buildPushNotificationRequestResponse ( request )
2020-09-03 08:53:26 +02:00
//AndSendNotification(&request)
2020-07-22 09:41:40 +02:00
if response == nil {
return nil
}
2020-09-03 08:53:26 +02:00
err = s . sendPushNotification ( requestsAndRegistrations )
if err != nil {
s . config . Logger . Error ( "failed to send go rush notification" , zap . Error ( err ) )
return err
}
2020-07-22 09:41:40 +02:00
encodedMessage , err := proto . Marshal ( response )
if err != nil {
return err
}
2020-07-28 15:22:22 +02:00
rawMessage := common . RawMessage {
2020-07-22 09:41:40 +02:00
Payload : encodedMessage ,
MessageType : protobuf . ApplicationMetadataMessage_PUSH_NOTIFICATION_RESPONSE ,
// We skip encryption here as the message has been sent from an ephemeral key
2023-11-08 19:05:33 +01:00
SkipEncryptionLayer : true ,
2020-07-22 09:41:40 +02:00
}
2021-06-23 17:13:48 +03:00
_ , err = s . messageSender . SendPrivate ( context . Background ( ) , publicKey , & rawMessage )
2020-07-22 09:41:40 +02:00
return err
}
// buildGrantSignatureMaterial builds a grant for a specific server.
// We use 3 components:
// 1) The client public key. Not sure this applies to our signature scheme, but best to be conservative. https://crypto.stackexchange.com/questions/15538/given-a-message-and-signature-find-a-public-key-that-makes-the-signature-valid
// 2) The server public key
// 3) The access token
// By verifying this signature, a client can trust the server was instructed to store this access token.
func ( s * Server ) buildGrantSignatureMaterial ( clientPublicKey * ecdsa . PublicKey , serverPublicKey * ecdsa . PublicKey , accessToken string ) [ ] byte {
var signatureMaterial [ ] byte
signatureMaterial = append ( signatureMaterial , crypto . CompressPubkey ( clientPublicKey ) ... )
signatureMaterial = append ( signatureMaterial , crypto . CompressPubkey ( serverPublicKey ) ... )
signatureMaterial = append ( signatureMaterial , [ ] byte ( accessToken ) ... )
a := crypto . Keccak256 ( signatureMaterial )
return a
}
func ( s * Server ) verifyGrantSignature ( clientPublicKey * ecdsa . PublicKey , accessToken string , grant [ ] byte ) error {
signatureMaterial := s . buildGrantSignatureMaterial ( clientPublicKey , & s . config . Identity . PublicKey , accessToken )
recoveredPublicKey , err := crypto . SigToPub ( signatureMaterial , grant )
if err != nil {
return err
}
if ! common . IsPubKeyEqual ( recoveredPublicKey , clientPublicKey ) {
return errors . New ( "pubkey mismatch" )
}
return nil
}
func ( s * Server ) generateSharedKey ( publicKey * ecdsa . PublicKey ) ( [ ] byte , error ) {
return ecies . ImportECDSA ( s . config . Identity ) . GenerateShared (
2020-06-30 16:55:24 +02:00
ecies . ImportECDSAPublic ( publicKey ) ,
encryptedPayloadKeyLength ,
encryptedPayloadKeyLength ,
)
}
2020-07-22 09:41:40 +02:00
func ( s * Server ) validateUUID ( u string ) error {
2020-07-01 10:37:54 +02:00
if len ( u ) == 0 {
return errors . New ( "empty uuid" )
}
_ , err := uuid . Parse ( u )
return err
}
2020-07-22 09:41:40 +02:00
func ( s * Server ) decryptRegistration ( publicKey * ecdsa . PublicKey , payload [ ] byte ) ( [ ] byte , error ) {
sharedKey , err := s . generateSharedKey ( publicKey )
2020-07-01 12:09:40 +02:00
if err != nil {
return nil , err
}
2020-07-07 11:00:04 +02:00
return common . Decrypt ( payload , sharedKey )
2020-07-01 12:09:40 +02:00
}
2020-07-22 09:41:40 +02:00
// validateRegistration validates a new message against the last one received for a given installationID and and public key
2020-07-01 10:53:05 +02:00
// and return the decrypted message
2020-07-22 09:41:40 +02:00
func ( s * Server ) validateRegistration ( publicKey * ecdsa . PublicKey , payload [ ] byte ) ( * protobuf . PushNotificationRegistration , error ) {
2020-06-30 15:14:25 +02:00
if payload == nil {
2020-07-02 10:08:19 +02:00
return nil , ErrEmptyPushNotificationRegistrationPayload
2020-06-30 15:14:25 +02:00
}
if publicKey == nil {
2020-07-02 10:08:19 +02:00
return nil , ErrEmptyPushNotificationRegistrationPublicKey
2020-06-30 15:14:25 +02:00
}
2020-07-13 10:53:13 +02:00
decryptedPayload , err := s . decryptRegistration ( publicKey , payload )
2020-06-30 15:14:25 +02:00
if err != nil {
2020-07-01 10:53:05 +02:00
return nil , err
2020-06-30 10:30:58 +02:00
}
2020-06-30 15:14:25 +02:00
2020-07-02 10:08:19 +02:00
registration := & protobuf . PushNotificationRegistration { }
2020-06-30 16:55:24 +02:00
2020-07-02 10:08:19 +02:00
if err := proto . Unmarshal ( decryptedPayload , registration ) ; err != nil {
return nil , ErrCouldNotUnmarshalPushNotificationRegistration
2020-06-30 16:55:24 +02:00
}
2020-07-02 10:08:19 +02:00
if registration . Version < 1 {
return nil , ErrInvalidPushNotificationRegistrationVersion
2020-07-01 10:37:54 +02:00
}
2020-07-13 10:53:13 +02:00
if err := s . validateUUID ( registration . InstallationId ) ; err != nil {
2020-07-02 10:08:19 +02:00
return nil , ErrMalformedPushNotificationRegistrationInstallationID
2020-07-01 10:37:54 +02:00
}
2020-07-30 16:47:24 +02:00
previousVersion , err := s . persistence . GetPushNotificationRegistrationVersion ( common . HashPublicKey ( publicKey ) , registration . InstallationId )
2020-07-01 12:09:40 +02:00
if err != nil {
return nil , err
}
2020-07-30 16:47:24 +02:00
if registration . Version <= previousVersion {
2020-07-02 10:08:19 +02:00
return nil , ErrInvalidPushNotificationRegistrationVersion
2020-07-01 12:09:40 +02:00
}
2020-07-22 09:41:40 +02:00
// unregistering message
2020-07-02 10:08:19 +02:00
if registration . Unregister {
return registration , nil
2020-07-01 10:37:54 +02:00
}
2020-07-13 10:53:13 +02:00
if err := s . validateUUID ( registration . AccessToken ) ; err != nil {
2020-07-02 10:08:19 +02:00
return nil , ErrMalformedPushNotificationRegistrationAccessToken
2020-07-01 10:37:54 +02:00
}
2020-07-13 10:53:13 +02:00
if len ( registration . Grant ) == 0 {
return nil , ErrMalformedPushNotificationRegistrationGrant
}
if err := s . verifyGrantSignature ( publicKey , registration . AccessToken , registration . Grant ) ; err != nil {
s . config . Logger . Error ( "failed to verify grant" , zap . Error ( err ) )
return nil , ErrMalformedPushNotificationRegistrationGrant
}
2020-07-22 09:41:40 +02:00
if len ( registration . DeviceToken ) == 0 {
2020-07-02 10:08:19 +02:00
return nil , ErrMalformedPushNotificationRegistrationDeviceToken
2020-06-30 16:55:24 +02:00
}
2020-06-30 15:14:25 +02:00
2020-07-02 16:19:21 +02:00
if registration . TokenType == protobuf . PushNotificationRegistration_UNKNOWN_TOKEN_TYPE {
return nil , ErrUnknownPushNotificationRegistrationTokenType
}
2020-07-02 10:08:19 +02:00
return registration , nil
2020-06-30 10:30:58 +02:00
}
2020-06-30 15:14:25 +02:00
2020-07-22 09:41:40 +02:00
// buildPushNotificationQueryResponse check if we have the client information and send them back
func ( s * Server ) buildPushNotificationQueryResponse ( query * protobuf . PushNotificationQuery ) * protobuf . PushNotificationQueryResponse {
2020-07-09 18:52:26 +02:00
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "handling push notification query" )
2020-07-02 15:57:50 +02:00
response := & protobuf . PushNotificationQueryResponse { }
if query == nil || len ( query . PublicKeys ) == 0 {
return response
}
2020-07-14 16:07:19 +02:00
registrations , err := s . persistence . GetPushNotificationRegistrationByPublicKeys ( query . PublicKeys )
2020-07-02 15:57:50 +02:00
if err != nil {
2020-07-14 16:07:19 +02:00
s . config . Logger . Error ( "failed to retrieve registration" , zap . Error ( err ) )
2020-07-02 15:57:50 +02:00
return response
}
for _ , idAndResponse := range registrations {
registration := idAndResponse . Registration
2020-07-30 16:47:24 +02:00
2020-07-02 15:57:50 +02:00
info := & protobuf . PushNotificationQueryInfo {
PublicKey : idAndResponse . ID ,
2020-07-13 10:53:13 +02:00
Grant : registration . Grant ,
2020-07-17 13:41:49 +02:00
Version : registration . Version ,
2020-07-02 15:57:50 +02:00
InstallationId : registration . InstallationId ,
}
2020-07-22 09:41:40 +02:00
// if instructed to only allow from contacts, send back a list
2020-07-20 10:32:00 +02:00
if registration . AllowFromContactsOnly {
2020-07-22 09:41:40 +02:00
info . AllowedKeyList = registration . AllowedKeyList
2020-07-02 15:57:50 +02:00
} else {
info . AccessToken = registration . AccessToken
}
response . Info = append ( response . Info , info )
}
response . Success = true
return response
}
2020-09-03 08:53:26 +02:00
func ( s * Server ) contains ( list [ ] [ ] byte , chatID [ ] byte ) bool {
for _ , list := range list {
if bytes . Equal ( list , chatID ) {
2020-08-25 11:46:01 +02:00
return true
}
}
return false
}
2020-09-07 10:25:57 +02:00
type reportResult struct {
sendNotification bool
report * protobuf . PushNotificationReport
}
// buildPushNotificationReport checks the request against the registration and
// returns whether we should send the notification and what the response should be
func ( s * Server ) buildPushNotificationReport ( pn * protobuf . PushNotification , registration * protobuf . PushNotificationRegistration ) ( * reportResult , error ) {
response := & reportResult { }
report := & protobuf . PushNotificationReport {
PublicKey : pn . PublicKey ,
InstallationId : pn . InstallationId ,
}
if pn . Type == protobuf . PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE {
s . config . Logger . Warn ( "unhandled type" )
return nil , errUnhandledPushNotificationType
}
if registration == nil {
s . config . Logger . Warn ( "empty registration" )
report . Error = protobuf . PushNotificationReport_NOT_REGISTERED
} else if registration . AccessToken != pn . AccessToken {
s . config . Logger . Debug ( "invalid token" )
report . Error = protobuf . PushNotificationReport_WRONG_TOKEN
2021-03-31 18:23:45 +02:00
} else if ( s . isMessageNotification ( pn ) && ! s . isValidMessageNotification ( pn , registration ) ) || ( s . isMentionNotification ( pn ) && ! s . isValidMentionNotification ( pn , registration ) ) || ( s . isRequestToJoinCommunityNotification ( pn ) && ! s . isValidRequestToJoinCommunityNotification ( pn , registration ) ) {
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "filtered notification" )
// We report as successful but don't send the notification
// for privacy reasons, as otherwise we would disclose that
// the sending client has been blocked or that the registering
// client has not joined a given public chat
report . Success = true
} else {
response . sendNotification = true
s . config . Logger . Debug ( "sending push notification" )
report . Success = true
}
response . report = report
return response , nil
}
2020-09-03 08:53:26 +02:00
// buildPushNotificationRequestResponse will build a response
func ( s * Server ) buildPushNotificationRequestResponse ( request * protobuf . PushNotificationRequest ) ( * protobuf . PushNotificationResponse , [ ] * RequestAndRegistration ) {
2020-07-03 10:02:28 +02:00
response := & protobuf . PushNotificationResponse { }
// We don't even send a response in this case
if request == nil || len ( request . MessageId ) == 0 {
2020-07-14 16:07:19 +02:00
s . config . Logger . Warn ( "empty message id" )
2020-09-03 08:53:26 +02:00
return nil , nil
2020-07-03 10:02:28 +02:00
}
response . MessageId = request . MessageId
2020-07-22 09:41:40 +02:00
// collect successful requests & registrations
2020-07-03 10:02:28 +02:00
var requestAndRegistrations [ ] * RequestAndRegistration
for _ , pn := range request . Requests {
2020-07-14 16:07:19 +02:00
registration , err := s . persistence . GetPushNotificationRegistrationByPublicKeyAndInstallationID ( pn . PublicKey , pn . InstallationId )
2020-09-07 10:25:57 +02:00
var report * protobuf . PushNotificationReport
2020-07-03 10:02:28 +02:00
if err != nil {
2020-09-07 10:25:57 +02:00
report = & protobuf . PushNotificationReport {
PublicKey : pn . PublicKey ,
Error : protobuf . PushNotificationReport_UNKNOWN_ERROR_TYPE ,
InstallationId : pn . InstallationId ,
}
2020-09-03 09:19:46 +02:00
} else {
2020-09-07 10:25:57 +02:00
response , err := s . buildPushNotificationReport ( pn , registration )
if err != nil {
s . config . Logger . Warn ( "unhandled type" )
continue
}
if response . sendNotification {
requestAndRegistrations = append ( requestAndRegistrations , & RequestAndRegistration {
Request : pn ,
Registration : registration ,
} )
}
report = response . report
2020-07-03 10:02:28 +02:00
}
2020-09-07 10:25:57 +02:00
2020-07-03 10:02:28 +02:00
response . Reports = append ( response . Reports , report )
}
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "built pn request" )
2020-07-03 10:02:28 +02:00
if len ( requestAndRegistrations ) == 0 {
2020-07-14 16:07:19 +02:00
s . config . Logger . Warn ( "no request and registration" )
2020-09-03 08:53:26 +02:00
return response , nil
2020-07-03 10:02:28 +02:00
}
2020-09-03 08:53:26 +02:00
return response , requestAndRegistrations
}
2020-07-03 10:02:28 +02:00
2020-09-03 08:53:26 +02:00
func ( s * Server ) sendPushNotification ( requestAndRegistrations [ ] * RequestAndRegistration ) error {
if len ( requestAndRegistrations ) == 0 {
return nil
}
2021-03-31 18:23:45 +02:00
s . SentRequests ++
2020-09-03 08:53:26 +02:00
goRushRequest := PushNotificationRegistrationToGoRushRequest ( requestAndRegistrations )
return sendGoRushNotification ( goRushRequest , s . config . GorushURL , s . config . Logger )
2020-07-03 10:02:28 +02:00
}
2020-07-22 09:41:40 +02:00
// listenToPublicKeyQueryTopic listen to a topic derived from the hashed public key
func ( s * Server ) listenToPublicKeyQueryTopic ( hashedPublicKey [ ] byte ) error {
2021-06-23 17:13:48 +03:00
if s . messageSender == nil {
2020-07-22 09:41:40 +02:00
return nil
}
encodedPublicKey := hex . EncodeToString ( hashedPublicKey )
2021-06-23 17:13:48 +03:00
_ , err := s . messageSender . JoinPublic ( encodedPublicKey )
2021-01-11 11:32:51 +01:00
return err
2020-07-22 09:41:40 +02:00
}
// buildPushNotificationRegistrationResponse will check the registration is valid, save it, and listen to the topic for the queries
func ( s * Server ) buildPushNotificationRegistrationResponse ( publicKey * ecdsa . PublicKey , payload [ ] byte ) * protobuf . PushNotificationRegistrationResponse {
2020-07-07 15:55:24 +02:00
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "handling push notification registration" )
2020-07-02 10:08:19 +02:00
response := & protobuf . PushNotificationRegistrationResponse {
2020-07-07 11:00:04 +02:00
RequestId : common . Shake256 ( payload ) ,
2020-07-02 10:08:19 +02:00
}
2020-07-22 09:41:40 +02:00
registration , err := s . validateRegistration ( publicKey , payload )
2020-07-01 12:09:40 +02:00
if err != nil {
2020-07-02 10:08:19 +02:00
if err == ErrInvalidPushNotificationRegistrationVersion {
response . Error = protobuf . PushNotificationRegistrationResponse_VERSION_MISMATCH
} else {
response . Error = protobuf . PushNotificationRegistrationResponse_MALFORMED_MESSAGE
}
2020-07-07 15:55:24 +02:00
s . config . Logger . Warn ( "registration did not validate" , zap . Error ( err ) )
2020-07-02 10:08:19 +02:00
return response
2020-07-01 12:09:40 +02:00
}
2020-07-02 10:08:19 +02:00
if registration . Unregister {
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "unregistering client" )
2020-07-02 10:08:19 +02:00
// We save an empty registration, only keeping version and installation-id
2020-07-30 16:47:24 +02:00
if err := s . persistence . UnregisterPushNotificationRegistration ( common . HashPublicKey ( publicKey ) , registration . InstallationId , registration . Version ) ; err != nil {
2020-07-02 10:08:19 +02:00
response . Error = protobuf . PushNotificationRegistrationResponse_INTERNAL_ERROR
2020-07-07 15:55:24 +02:00
s . config . Logger . Error ( "failed to unregister " , zap . Error ( err ) )
2020-07-02 10:08:19 +02:00
return response
}
2020-07-07 15:55:24 +02:00
} else if err := s . persistence . SavePushNotificationRegistration ( common . HashPublicKey ( publicKey ) , registration ) ; err != nil {
2020-07-02 10:08:19 +02:00
response . Error = protobuf . PushNotificationRegistrationResponse_INTERNAL_ERROR
2020-07-07 15:55:24 +02:00
s . config . Logger . Error ( "failed to save registration" , zap . Error ( err ) )
2020-07-02 10:08:19 +02:00
return response
}
2020-07-09 18:52:26 +02:00
if err := s . listenToPublicKeyQueryTopic ( common . HashPublicKey ( publicKey ) ) ; err != nil {
response . Error = protobuf . PushNotificationRegistrationResponse_INTERNAL_ERROR
s . config . Logger . Error ( "failed to listen to topic" , zap . Error ( err ) )
return response
}
2020-07-02 10:08:19 +02:00
response . Success = true
2020-09-07 10:25:57 +02:00
s . config . Logger . Debug ( "handled push notification registration successfully" )
2020-07-07 15:55:24 +02:00
2020-07-02 10:08:19 +02:00
return response
2020-07-01 12:09:40 +02:00
}
2020-09-03 08:53:26 +02:00
2020-09-03 11:54:05 +02:00
func ( s * Server ) isMentionNotification ( pn * protobuf . PushNotification ) bool {
return pn . Type == protobuf . PushNotification_MENTION
}
2020-09-07 10:25:57 +02:00
// isValidMentionNotification checks:
// this is a mention
// mentions are enabled
// the user joined the public chat
// the author is not blocked
2020-09-03 08:53:26 +02:00
func ( s * Server ) isValidMentionNotification ( pn * protobuf . PushNotification , registration * protobuf . PushNotificationRegistration ) bool {
2020-09-07 10:25:57 +02:00
return s . isMentionNotification ( pn ) && ! registration . BlockMentions && s . contains ( registration . AllowedMentionsChatList , pn . ChatId ) && ! s . contains ( registration . BlockedChatList , pn . Author )
}
func ( s * Server ) isMessageNotification ( pn * protobuf . PushNotification ) bool {
return pn . Type == protobuf . PushNotification_MESSAGE
}
2021-03-31 18:23:45 +02:00
// isValidMessageNotification checks:
2020-09-07 10:25:57 +02:00
// this is a message
// the chat is not muted
// the author is not blocked
func ( s * Server ) isValidMessageNotification ( pn * protobuf . PushNotification , registration * protobuf . PushNotificationRegistration ) bool {
2023-06-22 08:06:32 +03:00
return s . isMessageNotification ( pn ) && ! s . contains ( registration . BlockedChatList , pn . ChatId ) && ! s . contains ( registration . MutedChatList , pn . ChatId ) && ! s . contains ( registration . BlockedChatList , pn . Author )
2020-09-03 08:53:26 +02:00
}
2021-03-31 18:23:45 +02:00
func ( s * Server ) isRequestToJoinCommunityNotification ( pn * protobuf . PushNotification ) bool {
return pn . Type == protobuf . PushNotification_REQUEST_TO_JOIN_COMMUNITY
}
// isValidRequestToJoinCommunityNotification checks:
// this is a request to join a community
// the author is not blocked
func ( s * Server ) isValidRequestToJoinCommunityNotification ( pn * protobuf . PushNotification , registration * protobuf . PushNotificationRegistration ) bool {
return s . isRequestToJoinCommunityNotification ( pn ) && ! s . contains ( registration . BlockedChatList , pn . Author )
}