125
vendor/github.com/status-im/status-go/protocol/encryption/multidevice/multidevice.go
generated
vendored
Normal file
125
vendor/github.com/status-im/status-go/protocol/encryption/multidevice/multidevice.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package multidevice
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"database/sql"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
)
|
||||
|
||||
type InstallationMetadata struct {
|
||||
// The name of the device
|
||||
Name string `json:"name"`
|
||||
// The type of device
|
||||
DeviceType string `json:"deviceType"`
|
||||
// The FCMToken for mobile devices
|
||||
FCMToken string `json:"fcmToken"`
|
||||
}
|
||||
|
||||
type Installation struct {
|
||||
// Identity is the string identity of the owner
|
||||
Identity string `json:"identity"`
|
||||
// The installation-id of the device
|
||||
ID string `json:"id"`
|
||||
// The last known protocol version of the device
|
||||
Version uint32 `json:"version"`
|
||||
// Enabled is whether the installation is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
// Timestamp is the last time we saw this device
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
// InstallationMetadata
|
||||
InstallationMetadata *InstallationMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
MaxInstallations int
|
||||
ProtocolVersion uint32
|
||||
InstallationID string
|
||||
}
|
||||
|
||||
type Multidevice struct {
|
||||
persistence *sqlitePersistence
|
||||
config *Config
|
||||
}
|
||||
|
||||
func New(db *sql.DB, config *Config) *Multidevice {
|
||||
return &Multidevice{
|
||||
config: config,
|
||||
persistence: newSQLitePersistence(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Multidevice) InstallationID() string {
|
||||
return s.config.InstallationID
|
||||
}
|
||||
|
||||
func (s *Multidevice) GetActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.GetActiveInstallations(s.config.MaxInstallations, identityC)
|
||||
}
|
||||
|
||||
func (s *Multidevice) GetOurActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, identityC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
installations = append(installations, &Installation{
|
||||
ID: s.config.InstallationID,
|
||||
Version: s.config.ProtocolVersion,
|
||||
})
|
||||
|
||||
return installations, nil
|
||||
}
|
||||
|
||||
func (s *Multidevice) GetOurInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
|
||||
var found bool
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
installations, err := s.persistence.GetInstallations(identityC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, installation := range installations {
|
||||
if installation.ID == s.config.InstallationID {
|
||||
found = true
|
||||
installation.Enabled = true
|
||||
installation.Version = s.config.ProtocolVersion
|
||||
}
|
||||
|
||||
}
|
||||
if !found {
|
||||
installations = append(installations, &Installation{
|
||||
ID: s.config.InstallationID,
|
||||
Enabled: true,
|
||||
Version: s.config.ProtocolVersion,
|
||||
})
|
||||
}
|
||||
|
||||
return installations, nil
|
||||
}
|
||||
|
||||
func (s *Multidevice) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
|
||||
return s.persistence.AddInstallations(identity, timestamp, installations, defaultEnabled)
|
||||
}
|
||||
|
||||
func (s *Multidevice) SetInstallationMetadata(identity *ecdsa.PublicKey, installationID string, metadata *InstallationMetadata) error {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.SetInstallationMetadata(identityC, installationID, metadata)
|
||||
}
|
||||
|
||||
func (s *Multidevice) SetInstallationName(identity *ecdsa.PublicKey, installationID string, name string) error {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.SetInstallationName(identityC, installationID, name)
|
||||
}
|
||||
|
||||
func (s *Multidevice) EnableInstallation(identity *ecdsa.PublicKey, installationID string) error {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.EnableInstallation(identityC, installationID)
|
||||
}
|
||||
|
||||
func (s *Multidevice) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
myIdentityKeyC := crypto.CompressPubkey(myIdentityKey)
|
||||
return s.persistence.DisableInstallation(myIdentityKeyC, installationID)
|
||||
}
|
||||
278
vendor/github.com/status-im/status-go/protocol/encryption/multidevice/persistence.go
generated
vendored
Normal file
278
vendor/github.com/status-im/status-go/protocol/encryption/multidevice/persistence.go
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
package multidevice
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type sqlitePersistence struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func newSQLitePersistence(db *sql.DB) *sqlitePersistence {
|
||||
return &sqlitePersistence{db: db}
|
||||
}
|
||||
|
||||
// GetActiveInstallations returns the active installations for a given identity
|
||||
func (s *sqlitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) {
|
||||
stmt, err := s.db.Prepare(`SELECT installation_id, version
|
||||
FROM installations
|
||||
WHERE enabled = 1 AND identity = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var installations []*Installation
|
||||
rows, err := stmt.Query(identity, maxInstallations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
installationID string
|
||||
version uint32
|
||||
)
|
||||
err = rows.Scan(
|
||||
&installationID,
|
||||
&version,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installations = append(installations, &Installation{
|
||||
ID: installationID,
|
||||
Version: version,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return installations, nil
|
||||
|
||||
}
|
||||
|
||||
// GetInstallations returns all the installations for a given identity
|
||||
// we both return the installations & the metadata
|
||||
// metadata is currently stored in a separate table, as in some cases we
|
||||
// might have metadata for a device, but no other information on the device
|
||||
func (s *sqlitePersistence) GetInstallations(identity []byte) ([]*Installation, error) {
|
||||
installationMap := make(map[string]*Installation)
|
||||
var installations []*Installation
|
||||
|
||||
// We query both tables as sqlite does not support full outer joins
|
||||
installationsStmt, err := s.db.Prepare(`SELECT installation_id, version, enabled, timestamp FROM installations WHERE identity = ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer installationsStmt.Close()
|
||||
|
||||
installationRows, err := installationsStmt.Query(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for installationRows.Next() {
|
||||
var installation Installation
|
||||
err = installationRows.Scan(
|
||||
&installation.ID,
|
||||
&installation.Version,
|
||||
&installation.Enabled,
|
||||
&installation.Timestamp,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We initialized to empty in this case as we want to
|
||||
// return metadata as well in this endpoint, but not in others
|
||||
installation.InstallationMetadata = &InstallationMetadata{}
|
||||
installationMap[installation.ID] = &installation
|
||||
}
|
||||
|
||||
metadataStmt, err := s.db.Prepare(`SELECT installation_id, name, device_type, fcm_token FROM installation_metadata WHERE identity = ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer metadataStmt.Close()
|
||||
|
||||
metadataRows, err := metadataStmt.Query(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for metadataRows.Next() {
|
||||
var (
|
||||
installationID string
|
||||
name sql.NullString
|
||||
deviceType sql.NullString
|
||||
fcmToken sql.NullString
|
||||
installation *Installation
|
||||
)
|
||||
err = metadataRows.Scan(
|
||||
&installationID,
|
||||
&name,
|
||||
&deviceType,
|
||||
&fcmToken,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := installationMap[installationID]; ok {
|
||||
installation = installationMap[installationID]
|
||||
} else {
|
||||
installation = &Installation{ID: installationID}
|
||||
}
|
||||
installation.InstallationMetadata = &InstallationMetadata{
|
||||
Name: name.String,
|
||||
DeviceType: deviceType.String,
|
||||
FCMToken: fcmToken.String,
|
||||
}
|
||||
installationMap[installationID] = installation
|
||||
}
|
||||
|
||||
for _, installation := range installationMap {
|
||||
installations = append(installations, installation)
|
||||
}
|
||||
|
||||
return installations, nil
|
||||
}
|
||||
|
||||
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
|
||||
func (s *sqlitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var insertedInstallations []*Installation
|
||||
|
||||
for _, installation := range installations {
|
||||
stmt, err := tx.Prepare(`SELECT enabled, version
|
||||
FROM installations
|
||||
WHERE identity = ? AND installation_id = ?
|
||||
LIMIT 1`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var oldEnabled bool
|
||||
// We don't override version once we saw one
|
||||
var oldVersion uint32
|
||||
latestVersion := installation.Version
|
||||
|
||||
err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
|
||||
VALUES (?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
defaultEnabled,
|
||||
latestVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insertedInstallations = append(insertedInstallations, installation)
|
||||
} else {
|
||||
// We update timestamp if present without changing enabled, only if this is a new bundle
|
||||
// and we set the version to the latest we ever saw
|
||||
if oldVersion > installation.Version {
|
||||
latestVersion = oldVersion
|
||||
}
|
||||
|
||||
stmt, err = tx.Prepare(`UPDATE installations
|
||||
SET timestamp = ?, enabled = ?, version = ?
|
||||
WHERE identity = ?
|
||||
AND installation_id = ?
|
||||
AND timestamp < ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
timestamp,
|
||||
oldEnabled,
|
||||
latestVersion,
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return insertedInstallations, nil
|
||||
|
||||
}
|
||||
|
||||
// EnableInstallation enables the installation
|
||||
func (s *sqlitePersistence) EnableInstallation(identity []byte, installationID string) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 1
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// DisableInstallation disable the installation
|
||||
func (s *sqlitePersistence) DisableInstallation(identity []byte, installationID string) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 0
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetInstallationMetadata sets the metadata for a given installation
|
||||
func (s *sqlitePersistence) SetInstallationMetadata(identity []byte, installationID string, metadata *InstallationMetadata) error {
|
||||
stmt, err := s.db.Prepare(`INSERT INTO installation_metadata(name, device_type, fcm_token, identity, installation_id) VALUES(?,?,?,?,?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(metadata.Name, metadata.DeviceType, metadata.FCMToken, identity, installationID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetInstallationName sets the only the name in metadata for a given installation
|
||||
func (s *sqlitePersistence) SetInstallationName(identity []byte, installationID string, name string) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE installation_metadata
|
||||
SET name = ?
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(name, identity, installationID)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user