github.com/status-im/status-go@v1.1.0/protocol/pushnotificationserver/persistence.go (about)

     1  package pushnotificationserver
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"database/sql"
     6  	"strings"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	sqlite3 "github.com/mutecomm/go-sqlcipher/v4"
    10  
    11  	"github.com/status-im/status-go/eth-node/crypto"
    12  	"github.com/status-im/status-go/protocol/protobuf"
    13  )
    14  
    15  type Persistence interface {
    16  	// GetPushNotificationRegistrationByPublicKeyAndInstallationID retrieve a push notification registration from storage given a public key and installation id
    17  	GetPushNotificationRegistrationByPublicKeyAndInstallationID(publicKey []byte, installationID string) (*protobuf.PushNotificationRegistration, error)
    18  	// GetPushNotificationRegistrationByPublicKey retrieve all the push notification registrations from storage given a public key
    19  	GetPushNotificationRegistrationByPublicKeys(publicKeys [][]byte) ([]*PushNotificationIDAndRegistration, error)
    20  	// GetPushNotificationRegistrationPublicKeys return all the public keys stored
    21  	GetPushNotificationRegistrationPublicKeys() ([][]byte, error)
    22  
    23  	//GetPushNotificationRegistrationVersion returns the latest version or 0 for a given pk and installationID
    24  	GetPushNotificationRegistrationVersion(publicKey []byte, installationID string) (uint64, error)
    25  	// UnregisterPushNotificationRegistration unregister a given pk/installationID
    26  	UnregisterPushNotificationRegistration(publicKey []byte, installationID string, version uint64) error
    27  
    28  	// DeletePushNotificationRegistration deletes a push notification registration from storage given a public key and installation id
    29  	DeletePushNotificationRegistration(publicKey []byte, installationID string) error
    30  	// SavePushNotificationRegistration saves a push notification option to the db
    31  	SavePushNotificationRegistration(publicKey []byte, registration *protobuf.PushNotificationRegistration) error
    32  	// GetIdentity returns the server identity key
    33  	GetIdentity() (*ecdsa.PrivateKey, error)
    34  	// SaveIdentity saves the server identity key
    35  	SaveIdentity(*ecdsa.PrivateKey) error
    36  	// PushNotificationExists checks whether a push notification exists and inserts it otherwise
    37  	PushNotificationExists([]byte) (bool, error)
    38  }
    39  
    40  type SQLitePersistence struct {
    41  	db *sql.DB
    42  }
    43  
    44  func NewSQLitePersistence(db *sql.DB) Persistence {
    45  	return &SQLitePersistence{db: db}
    46  }
    47  
    48  func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeyAndInstallationID(publicKey []byte, installationID string) (*protobuf.PushNotificationRegistration, error) {
    49  	var marshaledRegistration []byte
    50  	err := p.db.QueryRow(`SELECT registration FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ? AND registration IS NOT NULL`, publicKey, installationID).Scan(&marshaledRegistration)
    51  
    52  	if err == sql.ErrNoRows {
    53  		return nil, nil
    54  	} else if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	registration := &protobuf.PushNotificationRegistration{}
    59  	if err := proto.Unmarshal(marshaledRegistration, registration); err != nil {
    60  		return nil, err
    61  	}
    62  	return registration, nil
    63  }
    64  
    65  func (p *SQLitePersistence) GetPushNotificationRegistrationVersion(publicKey []byte, installationID string) (uint64, error) {
    66  	var version uint64
    67  	err := p.db.QueryRow(`SELECT version FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ?`, publicKey, installationID).Scan(&version)
    68  
    69  	if err == sql.ErrNoRows {
    70  		return 0, nil
    71  	} else if err != nil {
    72  		return 0, err
    73  	}
    74  
    75  	return version, nil
    76  }
    77  
    78  type PushNotificationIDAndRegistration struct {
    79  	ID           []byte
    80  	Registration *protobuf.PushNotificationRegistration
    81  }
    82  
    83  func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeys(publicKeys [][]byte) ([]*PushNotificationIDAndRegistration, error) {
    84  	// TODO: check for a max number of keys
    85  
    86  	publicKeyArgs := make([]interface{}, 0, len(publicKeys))
    87  	for _, pk := range publicKeys {
    88  		publicKeyArgs = append(publicKeyArgs, pk)
    89  	}
    90  
    91  	inVector := strings.Repeat("?, ", len(publicKeys)-1) + "?"
    92  
    93  	rows, err := p.db.Query(`SELECT public_key,registration FROM push_notification_server_registrations WHERE registration IS NOT NULL AND public_key IN (`+inVector+`)`, publicKeyArgs...) // nolint: gosec
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	defer rows.Close()
    98  
    99  	var registrations []*PushNotificationIDAndRegistration
   100  	for rows.Next() {
   101  		response := &PushNotificationIDAndRegistration{}
   102  		var marshaledRegistration []byte
   103  		err := rows.Scan(&response.ID, &marshaledRegistration)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  
   108  		registration := &protobuf.PushNotificationRegistration{}
   109  		// Skip if there's no registration
   110  		if marshaledRegistration == nil {
   111  			continue
   112  		}
   113  
   114  		if err := proto.Unmarshal(marshaledRegistration, registration); err != nil {
   115  			return nil, err
   116  		}
   117  		response.Registration = registration
   118  		registrations = append(registrations, response)
   119  	}
   120  	return registrations, nil
   121  }
   122  
   123  func (p *SQLitePersistence) GetPushNotificationRegistrationPublicKeys() ([][]byte, error) {
   124  	rows, err := p.db.Query(`SELECT public_key FROM push_notification_server_registrations WHERE registration IS NOT NULL`)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	defer rows.Close()
   129  
   130  	var publicKeys [][]byte
   131  	for rows.Next() {
   132  		var publicKey []byte
   133  		err := rows.Scan(&publicKey)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  
   138  		publicKeys = append(publicKeys, publicKey)
   139  	}
   140  	return publicKeys, nil
   141  }
   142  
   143  func (p *SQLitePersistence) SavePushNotificationRegistration(publicKey []byte, registration *protobuf.PushNotificationRegistration) error {
   144  	marshaledRegistration, err := proto.Marshal(registration)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	_, err = p.db.Exec(`INSERT INTO push_notification_server_registrations (public_key, installation_id, version, registration) VALUES (?, ?, ?, ?)`, publicKey, registration.InstallationId, registration.Version, marshaledRegistration)
   150  	return err
   151  }
   152  
   153  func (p *SQLitePersistence) UnregisterPushNotificationRegistration(publicKey []byte, installationID string, version uint64) error {
   154  	_, err := p.db.Exec(`UPDATE push_notification_server_registrations SET registration = NULL, version = ? WHERE public_key = ? AND installation_id = ?`, version, publicKey, installationID)
   155  	return err
   156  }
   157  
   158  func (p *SQLitePersistence) DeletePushNotificationRegistration(publicKey []byte, installationID string) error {
   159  	_, err := p.db.Exec(`DELETE FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ?`, publicKey, installationID)
   160  	return err
   161  }
   162  
   163  func (p *SQLitePersistence) SaveIdentity(privateKey *ecdsa.PrivateKey) error {
   164  	_, err := p.db.Exec(`INSERT INTO push_notification_server_identity (private_key) VALUES (?)`, crypto.FromECDSA(privateKey))
   165  	return err
   166  }
   167  
   168  func (p *SQLitePersistence) GetIdentity() (*ecdsa.PrivateKey, error) {
   169  	var pkBytes []byte
   170  	err := p.db.QueryRow(`SELECT private_key FROM push_notification_server_identity LIMIT 1`).Scan(&pkBytes)
   171  	if err == sql.ErrNoRows {
   172  		return nil, nil
   173  	}
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	pk, err := crypto.ToECDSA(pkBytes)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	return pk, nil
   183  }
   184  
   185  func (p *SQLitePersistence) PushNotificationExists(messageID []byte) (bool, error) {
   186  	_, err := p.db.Exec(`INSERT INTO push_notification_server_notifications  VALUES (?)`, messageID)
   187  	if err != nil && err.(sqlite3.Error).ExtendedCode == sqlite3.ErrConstraintUnique {
   188  		return true, nil
   189  	} else if err != nil {
   190  		return false, err
   191  	}
   192  
   193  	return false, nil
   194  }