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 }