2020-06-30 08:30:58 +00:00
package push_notification_client
2020-06-30 07:50:59 +00:00
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"io"
"golang.org/x/crypto/sha3"
"github.com/google/uuid"
"github.com/status-im/status-go/eth-node/crypto/ecies"
"github.com/status-im/status-go/protocol/encryption"
"github.com/status-im/status-go/protocol/protobuf"
)
const accessTokenKeyLength = 16
type PushNotificationServer struct {
key * ecdsa . PublicKey
registered bool
}
2020-06-30 08:30:58 +00:00
type Config struct {
2020-06-30 07:50:59 +00:00
// Identity is our identity key
Identity * ecdsa . PrivateKey
// SendEnabled indicates whether we should be sending push notifications
SendEnabled bool
// RemoteNotificationsEnabled is whether we should register with a remote server for push notifications
RemoteNotificationsEnabled bool
// 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
InstallationID string
}
2020-06-30 08:30:58 +00:00
type Client struct {
persistence * Persistence
config * Config
2020-06-30 07:50:59 +00:00
2020-07-01 08:37:54 +00:00
// lastPushNotificationVersion is the latest known push notification version
lastPushNotificationVersion uint64
2020-06-30 07:50:59 +00:00
// AccessToken is the access token that is currently being used
AccessToken string
// DeviceToken is the device token for this device
DeviceToken string
// randomReader only used for testing so we have deterministic encryption
reader io . Reader
}
2020-06-30 08:30:58 +00:00
func New ( persistence * Persistence ) * Client {
return & Client { persistence : persistence , reader : rand . Reader }
2020-06-30 07:50:59 +00:00
}
// This likely will return a channel as it's an asynchrous operation
func fetchNotificationInfoFor ( publicKey * ecdsa . PublicKey ) error {
return nil
}
// Sends an actual push notification, where do we get the chatID?
func sendPushNotificationTo ( publicKey * ecdsa . PublicKey , chatID string ) error {
return nil
}
// This should schedule:
// 1) Check we have reasonably fresh push notifications info
// 2) Otherwise it should fetch them
// 3) Send a push notification to the devices in question
2020-06-30 08:30:58 +00:00
func ( p * Client ) HandleMessageSent ( publicKey * ecdsa . PublicKey , spec * encryption . ProtocolMessageSpec , messageIDs [ ] [ ] byte ) error {
2020-06-30 07:50:59 +00:00
return nil
}
2020-06-30 08:30:58 +00:00
func ( p * Client ) NotifyOnMessageID ( messageID [ ] byte ) error {
2020-06-30 07:50:59 +00:00
return nil
}
2020-06-30 08:30:58 +00:00
func ( p * Client ) mutedChatIDsHashes ( ) [ ] [ ] byte {
2020-06-30 07:50:59 +00:00
var mutedChatListHashes [ ] [ ] byte
for _ , chatID := range p . config . MutedChatIDs {
mutedChatListHashes = append ( mutedChatListHashes , shake256 ( chatID ) )
}
return mutedChatListHashes
}
2020-07-01 08:37:54 +00:00
func ( p * Client ) encryptToken ( publicKey * ecdsa . PublicKey , token [ ] byte ) ( [ ] byte , error ) {
2020-06-30 07:50:59 +00:00
sharedKey , err := ecies . ImportECDSA ( p . config . Identity ) . GenerateShared (
ecies . ImportECDSAPublic ( publicKey ) ,
accessTokenKeyLength ,
accessTokenKeyLength ,
)
if err != nil {
return nil , err
}
encryptedToken , err := encryptAccessToken ( token , sharedKey , p . reader )
if err != nil {
return nil , err
}
2020-07-01 08:37:54 +00:00
return encryptedToken , nil
2020-06-30 07:50:59 +00:00
}
2020-07-01 08:37:54 +00:00
func ( p * Client ) allowedUserList ( token [ ] byte ) ( [ ] [ ] byte , error ) {
var encryptedTokens [ ] [ ] byte
2020-06-30 07:50:59 +00:00
for _ , publicKey := range p . config . ContactIDs {
2020-07-01 08:37:54 +00:00
encryptedToken , err := p . encryptToken ( publicKey , token )
2020-06-30 07:50:59 +00:00
if err != nil {
return nil , err
}
2020-07-01 08:37:54 +00:00
encryptedTokens = append ( encryptedTokens , encryptedToken )
2020-06-30 07:50:59 +00:00
}
2020-07-01 08:37:54 +00:00
return encryptedTokens , nil
2020-06-30 07:50:59 +00:00
}
2020-07-01 08:37:54 +00:00
func ( p * Client ) buildPushNotificationOptionsMessage ( ) ( * protobuf . PushNotificationOptions , error ) {
token := uuid . New ( ) . String ( )
2020-06-30 07:50:59 +00:00
allowedUserList , err := p . allowedUserList ( [ ] byte ( token ) )
if err != nil {
return nil , err
}
options := & protobuf . PushNotificationOptions {
2020-07-01 08:37:54 +00:00
AccessToken : token ,
Version : p . lastPushNotificationVersion + 1 ,
2020-06-30 07:50:59 +00:00
InstallationId : p . config . InstallationID ,
Token : p . DeviceToken ,
Enabled : p . config . RemoteNotificationsEnabled ,
BlockedChatList : p . mutedChatIDsHashes ( ) ,
AllowedUserList : allowedUserList ,
}
return options , nil
}
2020-06-30 08:30:58 +00:00
func ( p * Client ) Register ( deviceToken string ) error {
2020-06-30 07:50:59 +00:00
return nil
}
// HandlePushNotificationRegistrationResponse should check whether the response was successful or not, retry if necessary otherwise store the result in the database
2020-06-30 08:30:58 +00:00
func ( p * Client ) HandlePushNotificationRegistrationResponse ( response * protobuf . PushNotificationRegistrationResponse ) error {
2020-06-30 07:50:59 +00:00
return nil
}
// HandlePushNotificationAdvertisement should store any info related to push notifications
2020-06-30 08:30:58 +00:00
func ( p * Client ) HandlePushNotificationAdvertisement ( info * protobuf . PushNotificationAdvertisementInfo ) error {
2020-06-30 07:50:59 +00:00
return nil
}
// HandlePushNotificationQueryResponse should update the data in the database for a given user
2020-06-30 08:30:58 +00:00
func ( p * Client ) HandlePushNotificationQueryResponse ( response * protobuf . PushNotificationQueryResponse ) error {
2020-06-30 07:50:59 +00:00
return nil
}
// HandlePushNotificationAcknowledgement should set the request as processed
2020-06-30 08:30:58 +00:00
func ( p * Client ) HandlePushNotificationAcknowledgement ( ack * protobuf . PushNotificationAcknowledgement ) error {
2020-06-30 07:50:59 +00:00
return nil
}
2020-06-30 08:30:58 +00:00
func ( p * Client ) SetContactIDs ( contactIDs [ ] * ecdsa . PublicKey ) error {
2020-06-30 07:50:59 +00:00
p . config . ContactIDs = contactIDs
// Update or schedule update
return nil
}
2020-06-30 08:30:58 +00:00
func ( p * Client ) SetMutedChatIDs ( chatIDs [ ] string ) error {
2020-06-30 07:50:59 +00:00
p . config . MutedChatIDs = chatIDs
// Update or schedule update
return nil
}
func encryptAccessToken ( plaintext [ ] byte , key [ ] byte , reader io . Reader ) ( [ ] byte , error ) {
c , err := aes . NewCipher ( key )
if err != nil {
return nil , err
}
gcm , err := cipher . NewGCM ( c )
if err != nil {
return nil , err
}
nonce := make ( [ ] byte , gcm . NonceSize ( ) )
if _ , err = io . ReadFull ( reader , nonce ) ; err != nil {
return nil , err
}
return gcm . Seal ( nonce , nonce , plaintext , nil ) , nil
}
func shake256 ( input string ) [ ] byte {
buf := [ ] byte ( input )
h := make ( [ ] byte , 64 )
sha3 . ShakeSum256 ( h , buf )
return h
}