Skip to content

Commit

Permalink
Merge pull request #13 from DIMO-Network/as-3172-email-case-sensitivi…
Browse files Browse the repository at this point in the history
…ty-in-accounts

Normalize emails everywhere
  • Loading branch information
elffjs authored Nov 25, 2024
2 parents 8d3716e + 07dc180 commit 77a015a
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 19 deletions.
11 changes: 7 additions & 4 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ func getUserAccountClaims(c *fiber.Ctx) (*AccountClaims, error) {
func (d *Controller) getUserAccount(ctx context.Context, userAccount *AccountClaims, exec boil.ContextExecutor) (*models.Account, error) {
switch {
case userAccount.EmailAddress != nil:
normalEmail := normalizeEmail(*userAccount.EmailAddress)
email, err := models.Emails(
models.EmailWhere.Address.EQ(*userAccount.EmailAddress),
models.EmailWhere.Address.EQ(normalEmail),
qm.Load(qm.Rels(models.EmailRels.Account, models.AccountRels.Wallet)),
qm.Load(qm.Rels(models.EmailRels.Account, models.AccountRels.ReferredByAccount, models.AccountRels.Wallet)),
).One(ctx, exec)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("No account found with email %s.", *userAccount.EmailAddress))
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("No account found with email %s.", normalEmail))
}
return nil, err
}
Expand Down Expand Up @@ -199,17 +200,19 @@ func (d *Controller) createUser(ctx context.Context, userAccount *AccountClaims,
d.log.Err(err).Msg("Error sending wallet information to Customer.io.")
}
} else if userAccount.EmailAddress != nil {
normalEmail := normalizeEmail(*userAccount.EmailAddress)

email := models.Email{
AccountID: acct.ID,
Address: *userAccount.EmailAddress,
Address: normalEmail,
ConfirmedAt: null.TimeFrom(time.Now()),
}

if err := email.Insert(ctx, tx, boil.Infer()); err != nil {
return fmt.Errorf("failed to insert email: %w", err)
}

if err := d.cioService.SetEmail(acct.ID, *userAccount.EmailAddress); err != nil {
if err := d.cioService.SetEmail(acct.ID, normalEmail); err != nil {
d.log.Err(err).Msg("Error sending email information to Customer.io.")
}
}
Expand Down
39 changes: 24 additions & 15 deletions internal/controller/email_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"time"

"github.com/DIMO-Network/accounts-api/models"
Expand All @@ -13,6 +14,10 @@ import (
"github.com/volatiletech/sqlboiler/v4/boil"
)

func normalizeEmail(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}

// LinkEmail godoc
// @Summary Add an unconfirmed email to the account.
// @Success 204
Expand All @@ -34,8 +39,10 @@ func (d *Controller) LinkEmail(c *fiber.Ctx) error {
return err
}

if !emailPattern.MatchString(body.Address) {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Email address %q is invalid.", body.Address))
normalAddr := normalizeEmail(body.Address)

if !emailPattern.MatchString(normalAddr) {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Email address %q is invalid.", normalAddr))
}

tx, err := d.dbs.DBS().Writer.BeginTx(c.Context(), &sql.TxOptions{Isolation: sql.LevelSerializable})
Expand All @@ -53,20 +60,20 @@ func (d *Controller) LinkEmail(c *fiber.Ctx) error {
c.Locals("logger", &logger)

if acct.R.Email != nil {
if acct.R.Email.Address == body.Address {
if acct.R.Email.Address == normalAddr {
return c.JSON(StandardRes{Message: "Account already linked to this email."})
}
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Account already has a linked email address %s.", acct.R.Email.Address))
}

if inUse, err := models.EmailExists(c.Context(), tx, body.Address); err != nil {
if inUse, err := models.EmailExists(c.Context(), tx, normalAddr); err != nil {
return err
} else if inUse {
return fiber.NewError(fiber.StatusBadRequest, "Email address already linked to another account.")
}

email := models.Email{
Address: body.Address,
Address: normalAddr,
AccountID: acct.ID,
ConfirmedAt: null.TimeFromPtr(nil),
}
Expand All @@ -83,14 +90,14 @@ func (d *Controller) LinkEmail(c *fiber.Ctx) error {
return err
}

logger.Info().Msgf("Added unconfirmed email %s to account.", body.Address)
logger.Info().Msgf("Added unconfirmed email %s to account.", normalAddr)

if err := d.cioService.SetEmail(acct.ID, body.Address); err != nil {
if err := d.cioService.SetEmail(acct.ID, normalAddr); err != nil {
d.log.Err(err).Str("account", acct.ID).Msg("Failed to send email to Customer.io.")
}

return c.JSON(StandardRes{
Message: fmt.Sprintf("Linked unconfirmed email %s to account.", body.Address),
Message: fmt.Sprintf("Linked unconfirmed email %s to account.", normalAddr),
})
}

Expand Down Expand Up @@ -136,20 +143,22 @@ func (d *Controller) LinkEmailToken(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Token in the body does not have an email claim.")
}

normalEmail := normalizeEmail(*infos.EmailAddress)

emailConflict, err := models.Emails(
models.EmailWhere.Address.EQ(*infos.EmailAddress),
models.EmailWhere.Address.EQ(normalEmail),
models.EmailWhere.AccountID.NEQ(acct.ID),
).One(c.Context(), tx)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
return err
}
} else {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Email %s already linked to account %s.", *infos.EmailAddress, emailConflict.AccountID))
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Email %s already linked to account %s.", normalEmail, emailConflict.AccountID))
}

if acct.R.Email != nil {
if acct.R.Email.Address != *infos.EmailAddress {
if acct.R.Email.Address != normalEmail {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Account already linked to email %s.", acct.R.Email.Address))
}
if acct.R.Email.ConfirmedAt.Valid {
Expand All @@ -162,7 +171,7 @@ func (d *Controller) LinkEmailToken(c *fiber.Ctx) error {
}

email := models.Email{
Address: *infos.EmailAddress,
Address: normalEmail,
AccountID: acct.ID,
ConfirmedAt: null.TimeFrom(time.Now()),
}
Expand All @@ -180,13 +189,13 @@ func (d *Controller) LinkEmailToken(c *fiber.Ctx) error {
return err
}

if err := d.cioService.SetEmail(acct.ID, *infos.EmailAddress); err != nil {
if err := d.cioService.SetEmail(acct.ID, normalEmail); err != nil {
logger.Err(err).Str("account", acct.ID).Msg("Failed to send email to Customer.io.")
}

logger.Info().Msgf("Linked confirmed email %s.", *infos.EmailAddress)
logger.Info().Msgf("Linked confirmed email %s.", normalEmail)

return c.JSON(StandardRes{
Message: fmt.Sprintf("Linked email %s.", *infos.EmailAddress),
Message: fmt.Sprintf("Linked email %s.", normalEmail),
})
}

0 comments on commit 77a015a

Please sign in to comment.