feat: a profile keypair name follows display name

As part of this commit `UpdateKeypairName` endpoint added,
will be used to rename all but the profile keypairs.
This commit is contained in:
Sale Djenic 2023-05-24 16:42:31 +02:00 committed by saledjenic
parent 20f38bf62b
commit 34f5ef031c
10 changed files with 414 additions and 42 deletions

View file

@ -376,28 +376,28 @@ func (db *Database) getKeypairs(tx *sql.Tx, keyUID string) ([]*Keypair, error) {
}
query := fmt.Sprintf( // nolint: gosec
`
SELECT
k.*,
ka.address,
SELECT
k.*,
ka.address,
ka.key_uid,
ka.pubkey,
ka.path,
ka.name,
ka.pubkey,
ka.path,
ka.name,
ka.color,
ka.emoji,
ka.wallet,
ka.chat,
ka.wallet,
ka.chat,
ka.hidden,
ka.operable,
ka.clock
FROM
FROM
keypairs k
LEFT JOIN
LEFT JOIN
keypairs_accounts ka
ON
k.key_uid = ka.key_uid
%s
ORDER BY
ORDER BY
ka.created_at`, where)
if tx == nil {
@ -457,28 +457,28 @@ func (db *Database) getAccounts(tx *sql.Tx, address types.Address) ([]*Account,
query := fmt.Sprintf( // nolint: gosec
`
SELECT
k.*,
ka.address,
SELECT
k.*,
ka.address,
ka.key_uid,
ka.pubkey,
ka.path,
ka.name,
ka.pubkey,
ka.path,
ka.name,
ka.color,
ka.emoji,
ka.wallet,
ka.chat,
ka.wallet,
ka.chat,
ka.hidden,
ka.operable,
ka.clock
FROM
FROM
keypairs_accounts ka
LEFT JOIN
LEFT JOIN
keypairs k
ON
ka.key_uid = k.key_uid
%s
ORDER BY
ORDER BY
ka.created_at`, where)
if tx == nil {
@ -665,12 +665,12 @@ func updateKeypairLastUsedIndex(tx *sql.Tx, keyUID string, index uint64, clock u
return errDbTransactionIsNil
}
_, err := tx.Exec(`
UPDATE
keypairs
SET
UPDATE
keypairs
SET
last_used_derivation_index = ?,
clock = ?
WHERE
WHERE
key_uid = ?`,
index, clock, keyUID)
@ -699,20 +699,20 @@ func (db *Database) saveOrUpdateAccounts(tx *sql.Tx, accounts []*Account) (err e
}
_, err = tx.Exec(`
INSERT OR IGNORE INTO
keypairs_accounts (address, key_uid, pubkey, path, wallet, chat, created_at, updated_at)
VALUES
INSERT OR IGNORE INTO
keypairs_accounts (address, key_uid, pubkey, path, wallet, chat, created_at, updated_at)
VALUES
(?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'));
UPDATE
keypairs_accounts
SET
name = ?,
color = ?,
emoji = ?,
name = ?,
color = ?,
emoji = ?,
hidden = ?,
operable = ?,
clock = ?
clock = ?
WHERE
address = ?;
`,
@ -807,15 +807,15 @@ func (db *Database) SaveOrUpdateKeypair(keypair *Keypair) error {
}
_, err = tx.Exec(`
INSERT OR IGNORE INTO
keypairs (key_uid, type, derived_from)
VALUES
INSERT OR IGNORE INTO
keypairs (key_uid, type, derived_from)
VALUES
(?, ?, ?);
UPDATE
keypairs
SET
name = ?,
name = ?,
last_used_derivation_index = ?,
synced_from = ?,
clock = ?
@ -830,6 +830,37 @@ func (db *Database) SaveOrUpdateKeypair(keypair *Keypair) error {
return db.saveOrUpdateAccounts(tx, keypair.Accounts)
}
func (db *Database) UpdateKeypairName(keyUID string, name string, clock uint64) error {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
_, err = db.getKeypairByKeyUID(tx, keyUID)
if err != nil {
return err
}
_, err = tx.Exec(`
UPDATE
keypairs
SET
name = ?,
clock = ?
WHERE
key_uid = ?;
`, name, clock, keyUID)
return err
}
func (db *Database) GetWalletAddress() (rst types.Address, err error) {
err = db.db.QueryRow("SELECT address FROM keypairs_accounts WHERE wallet = 1").Scan(&rst)
return

View file

@ -176,6 +176,37 @@ func TestWatchOnlyAccounts(t *testing.T) {
require.True(t, err == ErrDbAccountNotFound)
}
func TestUpdateKeypairName(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
kp := GetProfileKeypairForTest(true, false, false)
// check the db
dbAccounts, err := db.GetAccounts()
require.NoError(t, err)
require.Equal(t, 0, len(dbAccounts))
// save keypair
err = db.SaveOrUpdateKeypair(kp)
require.NoError(t, err)
dbKeypairs, err := db.GetKeypairs()
require.NoError(t, err)
require.Equal(t, 1, len(dbKeypairs))
require.True(t, SameKeypairs(kp, dbKeypairs[0]))
// update keypair name
kp.Name = kp.Name + "updated"
err = db.UpdateKeypairName(kp.KeyUID, kp.Name, kp.Clock)
require.NoError(t, err)
// check keypair
dbKp, err := db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, len(kp.Accounts), len(dbKp.Accounts))
require.True(t, SameKeypairs(kp, dbKp))
}
func TestKeypairs(t *testing.T) {
keypairs := []*Keypair{
GetProfileKeypairForTest(true, true, true),

View file

@ -22,6 +22,7 @@ import (
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/common"
@ -546,6 +547,15 @@ func (s *MessengerCommunitiesSuite) joinCommunity(community *communities.Communi
}
func (s *MessengerCommunitiesSuite) TestCommunityContactCodeAdvertisement() {
// add bob's profile keypair
bobProfileKp := accounts.GetProfileKeypairForTest(true, false, false)
bobProfileKp.KeyUID = s.bob.account.KeyUID
bobProfileKp.Accounts[0].KeyUID = s.bob.account.KeyUID
err := s.bob.settings.SaveOrUpdateKeypair(bobProfileKp)
s.Require().NoError(err)
// create community and make bob and alice join to it
community := s.createCommunity()
s.advertiseCommunityTo(community, s.bob)
s.advertiseCommunityTo(community, s.alice)
@ -554,7 +564,7 @@ func (s *MessengerCommunitiesSuite) TestCommunityContactCodeAdvertisement() {
s.joinCommunity(community, s.alice)
// Trigger ContactCodeAdvertisement
err := s.bob.SetDisplayName("bobby")
err = s.bob.SetDisplayName("bobby")
s.Require().NoError(err)
err = s.bob.SetBio("I like P2P chats")
s.Require().NoError(err)

View file

@ -146,7 +146,15 @@ func (s *MessengerBackupSuite) TestBackupProfile() {
// Create bob1
bob1 := s.m
err := bob1.SetDisplayName(bob1DisplayName)
bobProfileKp := accounts.GetProfileKeypairForTest(true, false, false)
bobProfileKp.KeyUID = bob1.account.KeyUID
bobProfileKp.Accounts[0].KeyUID = bob1.account.KeyUID
err := bob1.settings.SaveOrUpdateKeypair(bobProfileKp)
s.Require().NoError(err)
err = bob1.SetDisplayName(bob1DisplayName)
s.Require().NoError(err)
bob1KeyUID := bob1.account.KeyUID
imagesExpected := fmt.Sprintf(`[{"keyUid":"%s","type":"large","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUg=","width":240,"height":300,"fileSize":1024,"resizeTarget":240,"clock":0},{"keyUid":"%s","type":"thumbnail","uri":"data:image/jpeg;base64,/9j/2wCEAFA3PEY8MlA=","width":80,"height":80,"fileSize":256,"resizeTarget":80,"clock":0}]`,
@ -200,7 +208,7 @@ func (s *MessengerBackupSuite) TestBackupProfile() {
// Check bob2
storedBob2DisplayName, err := bob2.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal("", storedBob2DisplayName)
s.Require().Equal(DefaultProfileDisplayName, storedBob2DisplayName)
var expectedEmpty []*images.IdentityImage
bob2Images, err := bob2.multiAccounts.GetIdentityImages(bob1KeyUID)

View file

@ -22,6 +22,8 @@ import (
"github.com/status-im/status-go/waku"
)
const DefaultProfileDisplayName = ""
func TestMessengerCollapsedComunityCategoriesSuite(t *testing.T) {
suite.Run(t, new(MessengerCollapsedCommunityCategoriesSuite))
}
@ -86,6 +88,7 @@ func newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey, logger *z
WithDatasync(),
WithToplevelDatabaseMigrations(),
WithAppSettings(settings.Settings{
DisplayName: DefaultProfileDisplayName,
ProfilePicturesShowTo: 1,
ProfilePicturesVisibility: 1,
}, params.NodeConfig{}),

View file

@ -76,6 +76,11 @@ func (m *Messenger) SetDisplayName(displayName string) error {
return err
}
err = m.UpdateKeypairName(m.account.KeyUID, displayName)
if err != nil {
return err
}
err = m.resetLastPublishedTimeForChatIdentity()
if err != nil {
return err

View file

@ -0,0 +1,210 @@
package protocol
import (
"context"
"crypto/ecdsa"
"errors"
"testing"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/waku"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/types"
)
const testDisplayName = "My New Display Name"
func TestMessengerProfileDisplayNameHandlerSuite(t *testing.T) {
suite.Run(t, new(MessengerProfileDisplayNameHandlerSuite))
}
type MessengerProfileDisplayNameHandlerSuite struct {
suite.Suite
m *Messenger // main instance of Messenger
privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger
// If one wants to send messages between different instances of Messenger,
// a single Waku service should be shared.
shh types.Waku
logger *zap.Logger
}
func (s *MessengerProfileDisplayNameHandlerSuite) SetupTest() {
s.logger = tt.MustCreateTestLogger()
config := waku.DefaultConfig
config.MinimumAcceptedPoW = 0
shh := waku.New(&config, s.logger)
s.shh = gethbridge.NewGethWakuWrapper(shh)
s.Require().NoError(shh.Start())
s.m = s.newMessenger(s.shh)
s.privateKey = s.m.identity
// We start the messenger in order to receive installations
_, err := s.m.Start()
s.Require().NoError(err)
}
func (s *MessengerProfileDisplayNameHandlerSuite) TearDownTest() {
s.Require().NoError(s.m.Shutdown())
}
func (s *MessengerProfileDisplayNameHandlerSuite) newMessenger(shh types.Waku) *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, nil)
s.Require().NoError(err)
return messenger
}
func (s *MessengerProfileDisplayNameHandlerSuite) TestDisplayNameChange() {
// check display name for the created instance
displayName, err := s.m.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal(DefaultProfileDisplayName, displayName)
// add profile keypair
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
profileKp.KeyUID = s.m.account.KeyUID
profileKp.Name = DefaultProfileDisplayName
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
err = s.m.settings.SaveOrUpdateKeypair(profileKp)
s.Require().NoError(err)
// check account is present in the db
dbProfileKp, err := s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp))
// set new display name
err = s.m.SetDisplayName(testDisplayName)
s.Require().NoError(err)
// check display name after change - mutliaccount
multiAcc, err := s.m.multiAccounts.GetAccount(s.m.account.KeyUID)
s.Require().NoError(err)
s.Require().Equal(testDisplayName, multiAcc.Name)
// check display name after change - settings
displayName, err = s.m.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal(testDisplayName, displayName)
// check display name after change - keypair
dbProfileKp, err = s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().Equal(testDisplayName, dbProfileKp.Name)
}
func (s *MessengerProfileDisplayNameHandlerSuite) TestDisplayNameSync() {
// check display name for the created instance
displayName, err := s.m.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal(DefaultProfileDisplayName, displayName)
// add profile keypair
profileKp := accounts.GetProfileKeypairForTest(true, true, false)
profileKp.KeyUID = s.m.account.KeyUID
profileKp.Name = DefaultProfileDisplayName
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
profileKp.Accounts[1].KeyUID = s.m.account.KeyUID
err = s.m.settings.SaveOrUpdateKeypair(profileKp)
s.Require().NoError(err, "profile keypair alicesDevice.settings.SaveOrUpdateKeypair")
// check account is present in the db
dbProfileKp, err := s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp))
// Create new device and add main account to
alicesOtherDevice, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil)
s.Require().NoError(err)
// Store only chat and default wallet account on other device
profileKpOtherDevice := accounts.GetProfileKeypairForTest(true, true, false)
profileKp.KeyUID = s.m.account.KeyUID
profileKp.Name = DefaultProfileDisplayName
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
profileKp.Accounts[1].KeyUID = s.m.account.KeyUID
err = alicesOtherDevice.settings.SaveOrUpdateKeypair(profileKpOtherDevice)
s.Require().NoError(err, "profile keypair alicesOtherDevice.settings.SaveOrUpdateKeypair")
// Check account is present in the db
dbProfileKp2, err := alicesOtherDevice.settings.GetKeypairByKeyUID(profileKpOtherDevice.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKpOtherDevice, dbProfileKp2))
// Pair devices
im1 := &multidevice.InstallationMetadata{
Name: "alice's-other-device",
DeviceType: "alice's-other-device-type",
}
err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1)
s.Require().NoError(err)
response, err := alicesOtherDevice.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1)
s.Require().False(response.Chats()[0].Active)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
"installation not received",
)
s.Require().NoError(err)
actualInstallation := response.Installations[0]
s.Require().Equal(alicesOtherDevice.installationID, actualInstallation.ID)
s.Require().NotNil(actualInstallation.InstallationMetadata)
s.Require().Equal("alice's-other-device", actualInstallation.InstallationMetadata.Name)
s.Require().Equal("alice's-other-device-type", actualInstallation.InstallationMetadata.DeviceType)
err = s.m.EnableInstallation(alicesOtherDevice.installationID)
s.Require().NoError(err)
// Set new display name on alice's device
err = s.m.SetDisplayName(testDisplayName)
s.Require().NoError(err)
err = tt.RetryWithBackOff(func() error {
response, err := alicesOtherDevice.RetrieveAll()
if err != nil {
return err
}
if len(response.Keypairs) != 1 || len(response.Settings) != 1 {
return errors.New("no sync data received")
}
return nil
})
s.Require().NoError(err)
// check display name after change - mutliaccount
multiAcc, err := alicesOtherDevice.multiAccounts.GetAccount(s.m.account.KeyUID)
s.Require().NoError(err)
s.Require().Equal(testDisplayName, multiAcc.Name)
// check display name after change - settings
displayName, err = alicesOtherDevice.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal(testDisplayName, displayName)
// check display name after change - keypair
dbProfileKp, err = alicesOtherDevice.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().Equal(testDisplayName, dbProfileKp.Name)
}

View file

@ -65,6 +65,48 @@ func (s *MessengerSyncWalletSuite) newMessenger(shh types.Waku) *Messenger {
return messenger
}
// user should not be able to change a keypair name directly, it follows display name
func (s *MessengerSyncWalletSuite) TestProfileKeypairNameChange() {
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
profileKp.KeyUID = s.m.account.KeyUID
profileKp.Name = s.m.account.Name
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
// Create a main account on alice
err := s.m.settings.SaveOrUpdateKeypair(profileKp)
s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair")
// Check account is present in the db
dbProfileKp, err := s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp))
// Try to change profile keypair name using `SaveOrUpdateKeypair` function
profileKp1 := accounts.GetProfileKeypairForTest(true, false, false)
profileKp1.Name = profileKp1.Name + "updated"
profileKp1.KeyUID = s.m.account.KeyUID
profileKp1.Accounts[0].KeyUID = s.m.account.KeyUID
err = s.m.SaveOrUpdateKeypair(profileKp1)
s.Require().Error(err)
s.Require().True(err == ErrCannotChangeKeypairName)
// Check the db
dbProfileKp, err = s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp))
// Try to change profile keypair name using `UpdateKeypairName` function
err = s.m.UpdateKeypairName(profileKp1.KeyUID, profileKp1.Name)
s.Require().Error(err)
s.Require().True(err == ErrCannotChangeKeypairName)
// Check the db
dbProfileKp, err = s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID)
s.Require().NoError(err)
s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp))
}
func (s *MessengerSyncWalletSuite) TestSyncWallets() {
profileKp := accounts.GetProfileKeypairForTest(true, true, true)

View file

@ -16,7 +16,11 @@ import (
"github.com/status-im/status-go/protocol/protobuf"
)
var checkBalancesInterval = time.Minute * 10
var (
checkBalancesInterval = time.Minute * 10
ErrCannotChangeKeypairName = errors.New("cannot change profile keypair name")
)
func (m *Messenger) retrieveWalletBalances() error {
if m.walletAPI == nil {
@ -73,7 +77,30 @@ func (m *Messenger) watchWalletBalances() {
}()
}
func (m *Messenger) UpdateKeypairName(keyUID string, name string) error {
if keyUID == m.account.KeyUID && name != m.account.Name {
// profile keypair name must always follow profile display name
return ErrCannotChangeKeypairName
}
clock, _ := m.getLastClockWithRelatedChat()
err := m.settings.UpdateKeypairName(keyUID, name, clock)
if err != nil {
return err
}
dbKeypair, err := m.settings.GetKeypairByKeyUID(m.account.KeyUID)
if err != nil {
return err
}
return m.syncKeypair(dbKeypair, false, m.dispatchMessage)
}
func (m *Messenger) SaveOrUpdateKeypair(keypair *accounts.Keypair) error {
if keypair.KeyUID == m.account.KeyUID && keypair.Name != m.account.Name {
// profile keypair name must always follow profile display name
return ErrCannotChangeKeypairName
}
clock, _ := m.getLastClockWithRelatedChat()
keypair.Clock = clock

View file

@ -60,6 +60,11 @@ func (api *API) SaveKeypair(ctx context.Context, keypair *accounts.Keypair) erro
return nil
}
// Setting `Keypair` without `Accounts` will update keypair only.
func (api *API) UpdateKeypairName(ctx context.Context, keyUID string, name string) error {
return (*api.messenger).UpdateKeypairName(keyUID, name)
}
func (api *API) GetAccounts(ctx context.Context) ([]*accounts.Account, error) {
return api.db.GetAccounts()
}