satellite/nodeevents: validate emails before notifying
Simple email validation before attempting to send notifications. If the email is not valid, skip sending notifications and go to update email_sent so we don't try it again. Also, move ValidateEmail function into new package so it can be used in nodeevents without import cycle. Change-Id: I63ce0fc84f7b1d964f7cc6da61206f54baaf1a21
This commit is contained in:
parent
746c3b2d83
commit
a3ff3eb193
@ -20,6 +20,7 @@ import (
|
||||
"storj.io/storj/private/web"
|
||||
"storj.io/storj/satellite/analytics"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi/utils"
|
||||
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
@ -206,7 +207,7 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
|
||||
// trim leading and trailing spaces of email address.
|
||||
registerData.Email = strings.TrimSpace(registerData.Email)
|
||||
|
||||
isValidEmail := ValidateEmail(registerData.Email)
|
||||
isValidEmail := utils.ValidateEmail(registerData.Email)
|
||||
if !isValidEmail {
|
||||
a.serveJSONError(w, console.ErrValidation.Wrap(errs.New("Invalid email.")))
|
||||
return
|
||||
@ -356,15 +357,6 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateEmail validates email to have correct form and syntax.
|
||||
func ValidateEmail(email string) bool {
|
||||
// This regular expression was built according to RFC 5322 and then extended to include international characters.
|
||||
re := regexp.MustCompile(`^(?:[a-z0-9\p{L}!#$%&'*+/=?^_{|}~\x60-]+(?:\.[a-z0-9\p{L}!#$%&'*+/=?^_{|}~\x60-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9\p{L}](?:[a-z0-9\p{L}-]*[a-z0-9\p{L}])?\.)+[a-z0-9\p{L}](?:[a-z\p{L}]*[a-z\p{L}])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9\p{L}-]*[a-z0-9\p{L}]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$`)
|
||||
match := re.MatchString(email)
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
// loadSession looks for a cookie for the session id.
|
||||
// this cookie is set from the reverse proxy if the user opts into cookies from Storj.
|
||||
func loadSession(req *http.Request) string {
|
||||
|
@ -915,41 +915,3 @@ func TestAuth_Register_PasswordLength(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmailValidation(t *testing.T) {
|
||||
invalidEmailAddresses := []string{
|
||||
"test@t@t.test",
|
||||
"test",
|
||||
"test@!t.test",
|
||||
"test@#test.test",
|
||||
"test@$t.test",
|
||||
"t%t.test",
|
||||
"test@^test.test",
|
||||
"test@&test.test",
|
||||
"test@*test.test",
|
||||
"test@(test.test",
|
||||
"test@)test.test",
|
||||
"test@=test.test",
|
||||
"test@[test.test",
|
||||
"test@]test.test",
|
||||
"test@{test.test",
|
||||
"test@}test.test",
|
||||
"test@/test.test",
|
||||
"test@\\test.test",
|
||||
"test@|test.test",
|
||||
"test@:test.test",
|
||||
"test@;test.test",
|
||||
"test@,test.test",
|
||||
"test@\"test.test",
|
||||
"test@'test.test",
|
||||
"test@<test.test",
|
||||
"test@>test.test",
|
||||
"test@_test.test",
|
||||
"test@?test.test",
|
||||
}
|
||||
|
||||
for _, e := range invalidEmailAddresses {
|
||||
result := consoleapi.ValidateEmail(e)
|
||||
require.False(t, result)
|
||||
}
|
||||
}
|
||||
|
15
satellite/console/consoleweb/consoleapi/utils/validation.go
Normal file
15
satellite/console/consoleweb/consoleapi/utils/validation.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
// ValidateEmail validates email to have correct form and syntax.
|
||||
func ValidateEmail(email string) bool {
|
||||
// This regular expression was built according to RFC 5322 and then extended to include international characters.
|
||||
re := regexp.MustCompile(`^(?:[a-z0-9\p{L}!#$%&'*+/=?^_{|}~\x60-]+(?:\.[a-z0-9\p{L}!#$%&'*+/=?^_{|}~\x60-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9\p{L}](?:[a-z0-9\p{L}-]*[a-z0-9\p{L}])?\.)+[a-z0-9\p{L}](?:[a-z\p{L}]*[a-z\p{L}])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9\p{L}-]*[a-z0-9\p{L}]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$`)
|
||||
match := re.MatchString(email)
|
||||
|
||||
return match
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi/utils"
|
||||
)
|
||||
|
||||
func TestEmailValidation(t *testing.T) {
|
||||
invalidEmailAddresses := []string{
|
||||
"test@t@t.test",
|
||||
"test",
|
||||
"test@!t.test",
|
||||
"test@#test.test",
|
||||
"test@$t.test",
|
||||
"t%t.test",
|
||||
"test@^test.test",
|
||||
"test@&test.test",
|
||||
"test@*test.test",
|
||||
"test@(test.test",
|
||||
"test@)test.test",
|
||||
"test@=test.test",
|
||||
"test@[test.test",
|
||||
"test@]test.test",
|
||||
"test@{test.test",
|
||||
"test@}test.test",
|
||||
"test@/test.test",
|
||||
"test@\\test.test",
|
||||
"test@|test.test",
|
||||
"test@:test.test",
|
||||
"test@;test.test",
|
||||
"test@,test.test",
|
||||
"test@\"test.test",
|
||||
"test@'test.test",
|
||||
"test@<test.test",
|
||||
"test@>test.test",
|
||||
"test@_test.test",
|
||||
"test@?test.test",
|
||||
}
|
||||
|
||||
for _, e := range invalidEmailAddresses {
|
||||
result := utils.ValidateEmail(e)
|
||||
require.False(t, result)
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"storj.io/common/sync2"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -92,15 +93,24 @@ func (chore *Chore) process(ctx context.Context) (n int, err error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(batch) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
email := batch[0].Email
|
||||
|
||||
var rowIDs []uuid.UUID
|
||||
for _, event := range batch {
|
||||
rowIDs = append(rowIDs, event.ID)
|
||||
}
|
||||
|
||||
if err = chore.notifier.Notify(ctx, chore.satellite, batch); err != nil {
|
||||
err = errs.Combine(err, chore.db.UpdateLastAttempted(ctx, rowIDs, chore.nowFn()))
|
||||
return 0, err
|
||||
if utils.ValidateEmail(email) {
|
||||
if err = chore.notifier.Notify(ctx, chore.satellite, batch); err != nil {
|
||||
err = errs.Combine(err, chore.db.UpdateLastAttempted(ctx, rowIDs, chore.nowFn()))
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
chore.log.Error("invalid email", zap.String("email", email))
|
||||
}
|
||||
|
||||
err = chore.db.UpdateEmailSent(ctx, rowIDs, chore.nowFn())
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/private/teststorj"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/nodeevents"
|
||||
"storj.io/storj/satellite/overlay"
|
||||
@ -154,3 +155,65 @@ func TestNodeEventsChoreFailedNotify(t *testing.T) {
|
||||
require.Nil(t, event.EmailSent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodeEventsChoreInvalidEmails(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Overlay.SendNodeEmails = true
|
||||
config.NodeEvents.SelectionWaitPeriod = 5 * time.Minute
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
|
||||
// just a handful of emails, not exhaustive
|
||||
emails := []string{
|
||||
"",
|
||||
"abc",
|
||||
"abc.storj.test",
|
||||
"abc@def@storj.test",
|
||||
"abc\"@storj.test",
|
||||
"abc@storj..test",
|
||||
"abc @storj.test",
|
||||
|
||||
// one valid email as a control group
|
||||
"abc@storj.test",
|
||||
}
|
||||
|
||||
validEmail := emails[len(emails)-1]
|
||||
|
||||
chore := sat.NodeEvents.Chore
|
||||
chore.Loop.Pause()
|
||||
|
||||
tn := &TestNotifier{
|
||||
notifications: make(map[string][]nodeevents.NodeEvent),
|
||||
}
|
||||
chore.SetNotifier(tn)
|
||||
|
||||
// set nowFn on chore to 5 minutes in the future to test that chore will select node events.
|
||||
futureTime := func() time.Time {
|
||||
return time.Now().Add(5 * time.Minute)
|
||||
}
|
||||
chore.SetNow(futureTime)
|
||||
|
||||
event := nodeevents.Disqualified
|
||||
for _, e := range emails {
|
||||
_, err := sat.DB.NodeEvents().Insert(ctx, e, teststorj.NodeIDFromString("test"), event)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
chore.Loop.TriggerWait()
|
||||
|
||||
require.Len(t, tn.notifications, 1)
|
||||
require.NotEmpty(t, tn.notifications[validEmail])
|
||||
|
||||
// Check that email_sent is not null for invalid emails, so they don't clog up the table
|
||||
for _, e := range emails {
|
||||
ne, err := sat.DB.NodeEvents().GetLatestByEmailAndEvent(ctx, e, event)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ne.EmailSent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user