diff --git a/users/users.go b/users/users.go index bdd28321..935d66bc 100644 --- a/users/users.go +++ b/users/users.go @@ -9,9 +9,18 @@ import ( "strings" "github.com/mainflux/mainflux/errors" + "golang.org/x/net/idna" ) -const minPassLen = 8 +const ( + minPassLen = 8 + maxLocalLen = 64 + maxDomainLen = 255 + maxTLDLen = 24 // longest TLD currently in existence + + atSeparator = "@" + dotSeparator = "." +) var ( userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$") @@ -57,23 +66,43 @@ type UserRepository interface { } func isEmail(email string) bool { - if len(email) < 6 || len(email) > 254 { + if email == "" { return false } - at := strings.LastIndex(email, "@") - if at <= 0 || at > len(email)-3 { + es := strings.Split(email, atSeparator) + if len(es) != 2 { + return false + } + local, host := es[0], es[1] + + if local == "" || len(local) > maxLocalLen { return false } - user := email[:at] - host := email[at+1:] + hs := strings.Split(host, dotSeparator) + if len(hs) != 2 { + return false + } + domain, ext := hs[0], hs[1] - if len(user) > 64 { + if domain == "" || len(domain) > maxDomainLen { + return false + } + if ext == "" || len(ext) > maxTLDLen { return false } - if userDotRegexp.MatchString(user) || !userRegexp.MatchString(user) || !hostRegexp.MatchString(host) { + punyLocal, err := idna.ToASCII(local) + if err != nil { + return false + } + punyHost, err := idna.ToASCII(host) + if err != nil { + return false + } + + if userDotRegexp.MatchString(punyLocal) || !userRegexp.MatchString(punyLocal) || !hostRegexp.MatchString(punyHost) { return false } diff --git a/users/users_test.go b/users/users_test.go index 79e615d9..5cc96928 100644 --- a/users/users_test.go +++ b/users/users_test.go @@ -5,6 +5,7 @@ package users_test import ( "fmt" + "math/rand" "testing" "github.com/mainflux/mainflux/errors" @@ -16,8 +17,22 @@ const ( email = "user@example.com" password = "password" metadata = `{"role":"manager"}` + + maxLocalLen = 64 + maxDomainLen = 255 + maxTLDLen = 24 ) +var letters = "abcdefghijklmnopqrstuvwxyz" + +func randomString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + func TestValidate(t *testing.T) { cases := map[string]struct { user users.User @@ -51,6 +66,62 @@ func TestValidate(t *testing.T) { }, err: users.ErrMalformedEntity, }, + "validate user with utf8 email (cyrillic)": { + user: users.User{ + Email: "почта@кино-россия.рф", + Password: password, + }, + err: nil, + }, + "validate user with utf8 email (hieroglyph)": { + user: users.User{ + Email: "艾付忧西开@艾付忧西开.再得", + Password: password, + }, + err: nil, + }, + "validate user with no email tld": { + user: users.User{ + Email: "user@example.", + Password: password, + }, + err: users.ErrMalformedEntity, + }, + "validate user with too long email tld": { + user: users.User{ + Email: "user@example." + randomString(maxTLDLen+1), + Password: password, + }, + err: users.ErrMalformedEntity, + }, + "validate user with no email domain": { + user: users.User{ + Email: "user@.com", + Password: password, + }, + err: users.ErrMalformedEntity, + }, + "validate user with too long email domain": { + user: users.User{ + Email: "user@" + randomString(maxDomainLen+1) + ".com", + Password: password, + }, + err: users.ErrMalformedEntity, + }, + "validate user with no email local": { + user: users.User{ + Email: "@example.com", + Password: password, + }, + err: users.ErrMalformedEntity, + }, + "validate user with too long email local": { + user: users.User{ + Email: randomString(maxLocalLen+1) + "@example.com", + Password: password, + }, + err: users.ErrMalformedEntity, + }, } for desc, tc := range cases {