Skip to content

Commit

Permalink
feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#…
Browse files Browse the repository at this point in the history
…5766)

The original GH issue status-im/status-mobile#21113
came from a request from the Legal team. We must show to Status v1 users the new
terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2
from the stores.

The solution we use is to create a flag in the accounts table, named
hasAcceptedTerms. The flag will be set to true on the first account ever
created in v2 and we provide a native call in mobile/status.go#AcceptTerms,
which allows the client to persist the user's choice in case they are upgrading
(from v1 -> v2, or from a v2 older than this PR).

This solution is not the best because we should store the setting in a separate
table, not in the accounts table.

Related Mobile PR status-im/status-mobile#21124
  • Loading branch information
ilmotta committed Oct 22, 2024
1 parent 3179532 commit ac24afc
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 10 deletions.
25 changes: 25 additions & 0 deletions api/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ func TestLoginAccount(t *testing.T) {
acc, err := b.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
require.Equal(t, nameserver, b.config.WakuV2Config.Nameserver)
require.True(t, acc.HasAcceptedTerms)

waitForLogin(c)
require.NoError(t, b.Logout())
Expand Down Expand Up @@ -1684,6 +1685,30 @@ func TestRestoreAccountAndLoginWithoutDisplayName(t *testing.T) {
require.NotEmpty(t, account.Name)
}

func TestAcceptTerms(t *testing.T) {
tmpdir := t.TempDir()
b := NewGethStatusBackend()
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir))
b.UpdateRootDataDir(conf.DataDir)
require.NoError(t, b.OpenAccounts())
nameserver := "8.8.8.8"
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: "some-password",
RootDataDir: tmpdir,
LogFilePath: tmpdir + "/log",
WakuV2Nameserver: &nameserver,
WakuV2Fleet: "status.staging",
}
_, err = b.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
err = b.AcceptTerms()
require.NoError(t, err)
}

func TestCreateAccountPathsValidation(t *testing.T) {
tmpdir := t.TempDir()

Expand Down
26 changes: 26 additions & 0 deletions api/geth_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,24 @@ func (b *GethStatusBackend) GetAccounts() ([]multiaccounts.Account, error) {
return b.multiaccountsDB.GetAccounts()
}

func (b *GethStatusBackend) AcceptTerms() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return errors.New("accounts db wasn't initialized")

Check warning on line 245 in api/geth_backend.go

View check run for this annotation

Codecov / codecov/patch

api/geth_backend.go#L245

Added line #L245 was not covered by tests
}

accounts, err := b.multiaccountsDB.GetAccounts()
if err != nil {
return err

Check warning on line 250 in api/geth_backend.go

View check run for this annotation

Codecov / codecov/patch

api/geth_backend.go#L250

Added line #L250 was not covered by tests
}
if len(accounts) == 0 {
return errors.New("accounts is empty")

Check warning on line 253 in api/geth_backend.go

View check run for this annotation

Codecov / codecov/patch

api/geth_backend.go#L253

Added line #L253 was not covered by tests
}

return b.multiaccountsDB.UpdateHasAcceptedTerms(accounts[0].KeyUID, true)
}

func (b *GethStatusBackend) getAccountByKeyUID(keyUID string) (*multiaccounts.Account, error) {
b.mu.Lock()
defer b.mu.Unlock()
Expand Down Expand Up @@ -1580,6 +1598,14 @@ func (b *GethStatusBackend) buildAccount(request *requests.CreateAccount, input
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}

count, err := b.multiaccountsDB.GetAccountsCount()
if err != nil {
return nil, err

Check warning on line 1603 in api/geth_backend.go

View check run for this annotation

Codecov / codecov/patch

api/geth_backend.go#L1603

Added line #L1603 was not covered by tests
}
if count == 0 {
acc.HasAcceptedTerms = true
}

if request.ImagePath != "" {
imageCropRectangle := request.ImageCropRectangle
if imageCropRectangle == nil {
Expand Down
5 changes: 5 additions & 0 deletions api/old_mobile_user_upgrading_from_v1_to_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ func (s *OldMobileUserUpgradingFromV1ToV2Test) TestLoginAndMigrationsStillWorkWi
s.Require().True(len(keyKps[0].Accounts) == 1)
info, err = generator.LoadAccount(keyKps[0].Accounts[0].Address.Hex(), oldMobileUserPasswd)
s.Require().NoError(err)

// The user should manually accept terms, so we make sure we don't set it
// automatically by mistake.
s.Require().False(info.ToMultiAccount().HasAcceptedTerms)

s.Require().Equal(keyKps[0].KeyUID, info.KeyUID)
s.Require().Equal(keyKps[0].Accounts[0].KeyUID, info.KeyUID)
info, err = generator.ImportPrivateKey("c3ad0b50652318f845565c13761e5369ce75dcbc2a94616e15b829d4b07410fe")
Expand Down
5 changes: 5 additions & 0 deletions mobile/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,11 @@ func createAccountAndLogin(requestJSON string) string {
return makeJSONResponse(nil)
}

func AcceptTerms() string {
err := statusBackend.AcceptTerms()
return makeJSONResponse(err)

Check warning on line 448 in mobile/status.go

View check run for this annotation

Codecov / codecov/patch

mobile/status.go#L446-L448

Added lines #L446 - L448 were not covered by tests
}

func LoginAccount(requestJSON string) string {
return callWithResponse(loginAccount, requestJSON)
}
Expand Down
24 changes: 20 additions & 4 deletions multiaccounts/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type Account struct {
Images []images.IdentityImage `json:"images"`
KDFIterations int `json:"kdfIterations,omitempty"`
CustomizationColorClock uint64 `json:"-"`

// HasAcceptedTerms will be set to true when the first account is created.
HasAcceptedTerms bool `json:"hasAcceptedTerms"`
}

func (a *Account) RefersToKeycard() bool {
Expand Down Expand Up @@ -145,7 +148,7 @@ func (db *Database) GetAccountKDFIterationsNumber(keyUID string) (kdfIterationsN
}

func (db *Database) GetAccounts() (rst []Account, err error) {
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid ORDER BY loginTimestamp DESC")
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, a.hasAcceptedTerms, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid ORDER BY loginTimestamp DESC")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -179,6 +182,7 @@ func (db *Database) GetAccounts() (rst []Account, err error) {
&acc.KeycardPairing,
&acc.KeyUID,
&acc.KDFIterations,
&acc.HasAcceptedTerms,
&iiName,
&ii.Payload,
&iiWidth,
Expand Down Expand Up @@ -236,8 +240,14 @@ func (db *Database) GetAccounts() (rst []Account, err error) {
return rst, nil
}

func (db *Database) GetAccountsCount() (int, error) {
var count int
err := db.db.QueryRow("SELECT COUNT(1) FROM accounts").Scan(&count)
return count, err
}

func (db *Database) GetAccount(keyUID string) (*Account, error) {
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, ii.key_uid, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid WHERE a.keyUid = ? ORDER BY loginTimestamp DESC", keyUID)
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, a.hasAcceptedTerms, ii.key_uid, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid WHERE a.keyUid = ? ORDER BY loginTimestamp DESC", keyUID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -273,6 +283,7 @@ func (db *Database) GetAccount(keyUID string) (*Account, error) {
&acc.KeycardPairing,
&acc.KeyUID,
&acc.KDFIterations,
&acc.HasAcceptedTerms,
&iiKeyUID,
&iiName,
&ii.Payload,
Expand Down Expand Up @@ -323,7 +334,7 @@ func (db *Database) SaveAccount(account Account) error {
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}

_, err = db.db.Exec("INSERT OR REPLACE INTO accounts (name, identicon, colorHash, colorId, customizationColor, customizationColorClock, keycardPairing, keyUid, kdfIterations, loginTimestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KeyUID, account.KDFIterations, account.Timestamp)
_, err = db.db.Exec("INSERT OR REPLACE INTO accounts (name, identicon, colorHash, colorId, customizationColor, customizationColorClock, keycardPairing, keyUid, kdfIterations, loginTimestamp, hasAcceptedTerms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KeyUID, account.KDFIterations, account.Timestamp, account.HasAcceptedTerms)
if err != nil {
return err
}
Expand All @@ -340,6 +351,11 @@ func (db *Database) UpdateDisplayName(keyUID string, displayName string) error {
return err
}

func (db *Database) UpdateHasAcceptedTerms(keyUID string, hasAcceptedTerms bool) error {
_, err := db.db.Exec("UPDATE accounts SET hasAcceptedTerms = ? WHERE keyUid = ?", hasAcceptedTerms, keyUID)
return err
}

func (db *Database) UpdateAccount(account Account) error {
colorHash, err := json.Marshal(account.ColorHash)
if err != nil {
Expand All @@ -350,7 +366,7 @@ func (db *Database) UpdateAccount(account Account) error {
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}

_, err = db.db.Exec("UPDATE accounts SET name = ?, identicon = ?, colorHash = ?, colorId = ?, customizationColor = ?, customizationColorClock = ?, keycardPairing = ?, kdfIterations = ? WHERE keyUid = ?", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KDFIterations, account.KeyUID)
_, err = db.db.Exec("UPDATE accounts SET name = ?, identicon = ?, colorHash = ?, colorId = ?, customizationColor = ?, customizationColorClock = ?, keycardPairing = ?, kdfIterations = ?, hasAcceptedTerms = ? WHERE keyUid = ?", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KDFIterations, account.HasAcceptedTerms, account.KeyUID)
return err
}

Expand Down
Loading

0 comments on commit ac24afc

Please sign in to comment.