mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-05 03:47:53 +02:00
fix guard rails for lowercasing emails
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -754,6 +755,31 @@ func (r *Recipient) GetByEmail(
|
||||
return ToRecipient(&dbRecipient)
|
||||
}
|
||||
|
||||
// GetByEmailLower looks up a recipient by a case insensitive email match.
|
||||
// the supplied email is compared with LOWER() on both sides so that the same
|
||||
// address in a different casing (e.g. Foo@bar.tld vs foo@bar.tld) is treated as
|
||||
// the same recipient. used to reject a new recipient that duplicates an
|
||||
// existing one apart from casing. for resolving a specific recipient use the
|
||||
// exact match GetByEmail instead.
|
||||
func (r *Recipient) GetByEmailLower(
|
||||
ctx context.Context,
|
||||
email *vo.Email,
|
||||
fields ...string,
|
||||
) (*model.Recipient, error) {
|
||||
var dbRecipient database.Recipient
|
||||
fields = assignTableToColumns(database.RECIPIENT_TABLE, fields)
|
||||
res := useSelect(r.DB, fields).
|
||||
Where(
|
||||
fmt.Sprintf("LOWER(%s) = LOWER(?)", TableColumn(database.RECIPIENT_TABLE, "email")),
|
||||
email.String(),
|
||||
).
|
||||
First(&dbRecipient)
|
||||
if res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
return ToRecipient(&dbRecipient)
|
||||
}
|
||||
|
||||
// GetByEmailLowerAndCompanyID looks up a recipient by a case-insensitive email
|
||||
// match within a company. the supplied email is compared with LOWER() on both
|
||||
// sides so that differently cased addresses (e.g. John@X.com vs john@x.com) are
|
||||
@@ -832,6 +858,12 @@ func (r *Recipient) Insert(
|
||||
) (*uuid.UUID, error) {
|
||||
id := uuid.New()
|
||||
row := recp.ToDBMap()
|
||||
// the email is the recipient identity and is stored lowercased so the same
|
||||
// address in a different casing is one recipient. only applied on create so
|
||||
// an existing stored casing is never rewritten on update
|
||||
if email, ok := row["email"].(string); ok {
|
||||
row["email"] = strings.ToLower(email)
|
||||
}
|
||||
row["id"] = id
|
||||
AddTimestamps(row)
|
||||
res := r.DB.Model(&database.Recipient{}).Create(row)
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
@@ -51,7 +50,8 @@ func (r *Recipient) Create(
|
||||
email := recipient.Email.MustGet()
|
||||
// check if recipient already exists to avoid a unique constraint error
|
||||
// and gorm does not return a unique constraint error but a string error depending on DB
|
||||
_, err = r.RecipientRepository.GetByEmail(
|
||||
// the match is case insensitive so the same address in a different casing is rejected
|
||||
_, err = r.RecipientRepository.GetByEmailLower(
|
||||
ctx,
|
||||
&email,
|
||||
)
|
||||
@@ -112,32 +112,8 @@ func (r *Recipient) UpdateByID(
|
||||
}
|
||||
// update config - if a field is present and not null, update it
|
||||
|
||||
// if the email is changed, check that another recipient is not using this email already
|
||||
if v, err := incoming.Email.Get(); err != nil {
|
||||
if v.String() != current.Email.MustGet().String() {
|
||||
var companyID *uuid.UUID
|
||||
if current.CompanyID != nil {
|
||||
if cid, err := current.CompanyID.Get(); err != nil {
|
||||
companyID = &cid
|
||||
}
|
||||
}
|
||||
_, err := r.RecipientRepository.GetByEmailAndCompanyID(
|
||||
ctx,
|
||||
&v,
|
||||
companyID,
|
||||
)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
r.Logger.Errorw("failed check existing recipient email", "error", err)
|
||||
return err
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
r.Logger.Debugw("email is already taken", "email", v.String())
|
||||
s := fmt.Sprintf("email '%s' is already used by another recipient", v.String())
|
||||
return validate.WrapErrorWithField(errors.New("not unique"), s)
|
||||
}
|
||||
}
|
||||
current.Email.Set(v)
|
||||
}
|
||||
// the email is the recipient identity and is immutable, so any incoming
|
||||
// email is ignored and the stored one is preserved
|
||||
if v, err := incoming.Phone.Get(); err == nil {
|
||||
current.Phone.Set(v)
|
||||
}
|
||||
@@ -589,9 +565,11 @@ func (r *Recipient) Import(
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the recipient exists
|
||||
// check if the recipient exists, case insensitively so the same address
|
||||
// in a different casing updates the existing recipient instead of creating
|
||||
// a duplicate
|
||||
email := incoming.Email.MustGet()
|
||||
current, err := r.RecipientRepository.GetByEmail(
|
||||
current, err := r.RecipientRepository.GetByEmailLower(
|
||||
ctx,
|
||||
&email,
|
||||
"id", "email", "company_id",
|
||||
|
||||
Reference in New Issue
Block a user