fix: validate display name on account creation (#4994)

This commit is contained in:
Mykhailo Prakhov 2024-03-28 16:57:59 +01:00 committed by GitHub
parent 223a1d759e
commit e4c1abb5ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 51 deletions

View File

@ -2,11 +2,19 @@ package common
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"regexp"
"strings"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/identity/alias"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
) )
var ErrInvalidDisplayNameRegExp = errors.New("only letters, numbers, underscores and hyphens allowed")
var ErrInvalidDisplayNameEthSuffix = errors.New(`usernames ending with "eth" are not allowed`)
var ErrInvalidDisplayNameNotAllowed = errors.New("name is not allowed")
func RecoverKey(m *protobuf.ApplicationMetadataMessage) (*ecdsa.PublicKey, error) { func RecoverKey(m *protobuf.ApplicationMetadataMessage) (*ecdsa.PublicKey, error) {
if m.Signature == nil { if m.Signature == nil {
return nil, nil return nil, nil
@ -22,3 +30,28 @@ func RecoverKey(m *protobuf.ApplicationMetadataMessage) (*ecdsa.PublicKey, error
return recoveredKey, nil return recoveredKey, nil
} }
func ValidateDisplayName(displayName *string) error {
name := strings.TrimSpace(*displayName)
*displayName = name
if name == "" {
return nil
}
// ^[\\w-\\s]{5,24}$ to allow spaces
if match, _ := regexp.MatchString("^[\\w-\\s]{5,24}$", name); !match {
return ErrInvalidDisplayNameRegExp
}
// .eth should not happen due to the regexp above, but let's keep it here in case the regexp is changed in the future
if strings.HasSuffix(name, "_eth") || strings.HasSuffix(name, ".eth") || strings.HasSuffix(name, "-eth") {
return ErrInvalidDisplayNameEthSuffix
}
if alias.IsAlias(name) {
return ErrInvalidDisplayNameNotAllowed
}
return nil
}

View File

@ -266,24 +266,26 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name. // _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){ var _bindata = map[string]func() (*asset, error){
"_assets/tests/1.gif": _assetsTests1Gif, "_assets/tests/1.gif": _assetsTests1Gif,
"_assets/tests/2x1.png": _assetsTests2x1Png, "_assets/tests/2x1.png": _assetsTests2x1Png,
"_assets/tests/spin.gif": _assetsTestsSpinGif, "_assets/tests/spin.gif": _assetsTestsSpinGif,
"_assets/tests/qr/QRWithLogo.png": _assetsTestsQrQrwithlogoPng, "_assets/tests/qr/QRWithLogo.png": _assetsTestsQrQrwithlogoPng,
"_assets/tests/qr/defaultQR.png": _assetsTestsQrDefaultqrPng, "_assets/tests/qr/defaultQR.png": _assetsTestsQrDefaultqrPng,
"_assets/tests/qr/status.png": _assetsTestsQrStatusPng, "_assets/tests/qr/status.png": _assetsTestsQrStatusPng,
"_assets/tests/wikipedia.ico": _assetsTestsWikipediaIco, "_assets/tests/wikipedia.ico": _assetsTestsWikipediaIco,
} }
// AssetDir returns the file names below a certain // AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata. // directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the // For example if you run go-bindata on data/... and data contains the
// following hierarchy: // following hierarchy:
// data/ //
// foo.txt // data/
// img/ // foo.txt
// a.png // img/
// b.png // a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"} // then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"} // AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("foo.txt") and AssetDir("notexist") would return an error
@ -314,17 +316,18 @@ type bintree struct {
Func func() (*asset, error) Func func() (*asset, error)
Children map[string]*bintree Children map[string]*bintree
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"_assets": &bintree{nil, map[string]*bintree{ "_assets": &bintree{nil, map[string]*bintree{
"tests": &bintree{nil, map[string]*bintree{ "tests": &bintree{nil, map[string]*bintree{
"1.gif": &bintree{_assetsTests1Gif, map[string]*bintree{}}, "1.gif": &bintree{_assetsTests1Gif, map[string]*bintree{}},
"2x1.png": &bintree{_assetsTests2x1Png, map[string]*bintree{}}, "2x1.png": &bintree{_assetsTests2x1Png, map[string]*bintree{}},
"qr": &bintree{nil, map[string]*bintree{ "qr": &bintree{nil, map[string]*bintree{
"QRWithLogo.png": &bintree{_assetsTestsQrQrwithlogoPng, map[string]*bintree{}}, "QRWithLogo.png": &bintree{_assetsTestsQrQrwithlogoPng, map[string]*bintree{}},
"defaultQR.png": &bintree{_assetsTestsQrDefaultqrPng, map[string]*bintree{}}, "defaultQR.png": &bintree{_assetsTestsQrDefaultqrPng, map[string]*bintree{}},
"status.png": &bintree{_assetsTestsQrStatusPng, map[string]*bintree{}}, "status.png": &bintree{_assetsTestsQrStatusPng, map[string]*bintree{}},
}}, }},
"spin.gif": &bintree{_assetsTestsSpinGif, map[string]*bintree{}}, "spin.gif": &bintree{_assetsTestsSpinGif, map[string]*bintree{}},
"wikipedia.ico": &bintree{_assetsTestsWikipediaIco, map[string]*bintree{}}, "wikipedia.ico": &bintree{_assetsTestsWikipediaIco, map[string]*bintree{}},
}}, }},
}}, }},
@ -376,4 +379,3 @@ func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1) cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
} }

View File

@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"strings" "strings"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/v1" "github.com/status-im/status-go/protocol/v1"
) )
@ -343,7 +344,7 @@ func ValidateReceivedChatMessage(message *protobuf.ChatMessage, whisperTimestamp
return errors.New("mutual state event system message content type not allowed") return errors.New("mutual state event system message content type not allowed")
} }
if err := ValidateDisplayName(&message.DisplayName); err != nil { if err := utils.ValidateDisplayName(&message.DisplayName); err != nil {
return err return err
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images" "github.com/status-im/status-go/images"
@ -1212,7 +1213,7 @@ func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message *pr
return ErrMessageNotAllowed return ErrMessageNotAllowed
} }
if err = ValidateDisplayName(&message.DisplayName); err != nil { if err = utils.ValidateDisplayName(&message.DisplayName); err != nil {
return err return err
} }
@ -3023,7 +3024,7 @@ func (m *Messenger) HandleChatIdentity(state *ReceivedMessageState, ci *protobuf
} }
if clockChanged { if clockChanged {
if err = ValidateDisplayName(&ci.DisplayName); err != nil { if err = utils.ValidateDisplayName(&ci.DisplayName); err != nil {
return err return err
} }

View File

@ -4,15 +4,14 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"regexp"
"runtime" "runtime"
"strings" "strings"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/multiaccounts/settings"
sociallinkssettings "github.com/status-im/status-go/multiaccounts/settings_social_links" sociallinkssettings "github.com/status-im/status-go/multiaccounts/settings_social_links"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/identity" "github.com/status-im/status-go/protocol/identity"
"github.com/status-im/status-go/protocol/identity/alias"
"github.com/status-im/status-go/server" "github.com/status-im/status-go/server"
) )
@ -21,38 +20,10 @@ const (
maxSocialLinkTextLength = 24 maxSocialLinkTextLength = 24
) )
var ErrInvalidDisplayNameRegExp = errors.New("only letters, numbers, underscores and hyphens allowed")
var ErrInvalidDisplayNameEthSuffix = errors.New(`usernames ending with "eth" are not allowed`)
var ErrInvalidDisplayNameNotAllowed = errors.New("name is not allowed")
var ErrInvalidBioLength = errors.New("invalid bio length") var ErrInvalidBioLength = errors.New("invalid bio length")
var ErrInvalidSocialLinkTextLength = errors.New("invalid social link text length") var ErrInvalidSocialLinkTextLength = errors.New("invalid social link text length")
var ErrDisplayNameDupeOfCommunityMember = errors.New("display name duplicates on of community members") var ErrDisplayNameDupeOfCommunityMember = errors.New("display name duplicates on of community members")
func ValidateDisplayName(displayName *string) error {
name := strings.TrimSpace(*displayName)
*displayName = name
if name == "" {
return nil
}
// ^[\\w-\\s]{5,24}$ to allow spaces
if match, _ := regexp.MatchString("^[\\w-\\s]{5,24}$", name); !match {
return ErrInvalidDisplayNameRegExp
}
// .eth should not happen due to the regexp above, but let's keep it here in case the regexp is changed in the future
if strings.HasSuffix(name, "_eth") || strings.HasSuffix(name, ".eth") || strings.HasSuffix(name, "-eth") {
return ErrInvalidDisplayNameEthSuffix
}
if alias.IsAlias(name) {
return ErrInvalidDisplayNameNotAllowed
}
return nil
}
func (m *Messenger) SetDisplayName(displayName string) error { func (m *Messenger) SetDisplayName(displayName string) error {
currDisplayName, err := m.settings.DisplayName() currDisplayName, err := m.settings.DisplayName()
if err != nil { if err != nil {
@ -63,7 +34,7 @@ func (m *Messenger) SetDisplayName(displayName string) error {
return nil // Do nothing return nil // Do nothing
} }
if err = ValidateDisplayName(&displayName); err != nil { if err = utils.ValidateDisplayName(&displayName); err != nil {
return err return err
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/tt" "github.com/status-im/status-go/protocol/tt"
@ -182,3 +183,42 @@ func (s *MessengerProfileDisplayNameHandlerSuite) TestDisplayNameSync() {
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Equal(testDisplayName, dbProfileKp.Name) s.Require().Equal(testDisplayName, dbProfileKp.Name)
} }
func (s *MessengerProfileDisplayNameHandlerSuite) TestDisplayNameRestrictions() {
// 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)
setInvalidName := func(invalidName string, expectedErr error) {
err = s.m.SetDisplayName(invalidName)
s.Require().ErrorIs(err, expectedErr)
}
setInvalidName("test.eth", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("test-eth", utils.ErrInvalidDisplayNameEthSuffix)
setInvalidName("test_eth", utils.ErrInvalidDisplayNameEthSuffix)
setInvalidName("dot.not", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("t", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("tt", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("ttt", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("tttt", utils.ErrInvalidDisplayNameRegExp)
setInvalidName("name is bigger than 24 symb", utils.ErrInvalidDisplayNameRegExp)
err = s.m.SetDisplayName("name with space")
s.Require().NoError(err)
displayName, err = s.m.settings.DisplayName()
s.Require().NoError(err)
s.Require().Equal("name with space", displayName)
}

View File

@ -1,7 +1,9 @@
package requests package requests
import ( import (
"errors" "github.com/pkg/errors"
utils "github.com/status-im/status-go/common"
) )
var ErrCreateAccountInvalidDisplayName = errors.New("create-account: invalid display name") var ErrCreateAccountInvalidDisplayName = errors.New("create-account: invalid display name")
@ -87,6 +89,10 @@ func (c *CreateAccount) Validate(validation *CreateAccountValidation) error {
return ErrCreateAccountInvalidDisplayName return ErrCreateAccountInvalidDisplayName
} }
if err := utils.ValidateDisplayName(&c.DisplayName); err != nil {
return errors.Wrap(ErrCreateAccountInvalidDisplayName, err.Error())
}
if len(c.Password) == 0 { if len(c.Password) == 0 {
return ErrCreateAccountInvalidPassword return ErrCreateAccountInvalidPassword
} }