2025-12-03 19:29:37 +00:00
|
|
|
package validators
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"veza-backend-api/internal/models"
|
2026-03-05 22:03:43 +00:00
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
2025-12-03 19:29:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// RFC 5322 compliant email regex (simplified but covers most cases)
|
|
|
|
|
// This regex validates:
|
|
|
|
|
// - Local part: alphanumeric, dots, underscores, hyphens, plus signs
|
|
|
|
|
// - @ symbol
|
|
|
|
|
// - Domain part: alphanumeric, dots, hyphens
|
|
|
|
|
// - TLD: at least 2 characters
|
|
|
|
|
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
|
|
|
|
|
|
|
|
|
// EmailValidator valide les emails selon RFC 5322
|
|
|
|
|
type EmailValidator struct {
|
|
|
|
|
db *gorm.DB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewEmailValidator crée une nouvelle instance d'EmailValidator
|
|
|
|
|
func NewEmailValidator(db *gorm.DB) *EmailValidator {
|
|
|
|
|
return &EmailValidator{db: db}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateFormat valide le format de l'email selon RFC 5322
|
|
|
|
|
func (v *EmailValidator) ValidateFormat(email string) bool {
|
|
|
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
|
|
|
|
|
|
|
|
// RFC 5321: Email addresses are limited to 254 characters
|
|
|
|
|
if len(email) > 254 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vérifier que l'email n'est pas vide
|
|
|
|
|
if len(email) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vérifier le format avec regex
|
|
|
|
|
if !emailRegex.MatchString(email) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vérifications supplémentaires
|
|
|
|
|
parts := strings.Split(email, "@")
|
|
|
|
|
if len(parts) != 2 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localPart := parts[0]
|
|
|
|
|
domainPart := parts[1]
|
|
|
|
|
|
|
|
|
|
// Local part ne peut pas être vide
|
|
|
|
|
if len(localPart) == 0 || len(localPart) > 64 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Domain part ne peut pas être vide
|
|
|
|
|
if len(domainPart) == 0 || len(domainPart) > 253 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Le domaine doit contenir au moins un point
|
|
|
|
|
if !strings.Contains(domainPart, ".") {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Le local part ne peut pas commencer ou finir par un point
|
|
|
|
|
if strings.HasPrefix(localPart, ".") || strings.HasSuffix(localPart, ".") {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Le domaine ne peut pas commencer ou finir par un point ou un tiret
|
|
|
|
|
if strings.HasPrefix(domainPart, ".") || strings.HasSuffix(domainPart, ".") ||
|
|
|
|
|
strings.HasPrefix(domainPart, "-") || strings.HasSuffix(domainPart, "-") {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsUnique vérifie si l'email est unique en base de données
|
|
|
|
|
func (v *EmailValidator) IsUnique(email string) (bool, error) {
|
|
|
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
|
|
|
|
|
|
|
|
var count int64
|
|
|
|
|
err := v.db.Model(&models.User{}).
|
|
|
|
|
Where("LOWER(email) = ?", email).
|
|
|
|
|
Count(&count).Error
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count == 0, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate effectue une validation complète de l'email (format + unicité)
|
|
|
|
|
func (v *EmailValidator) Validate(email string) error {
|
|
|
|
|
if !v.ValidateFormat(email) {
|
|
|
|
|
return errors.New("invalid email format")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unique, err := v.IsUnique(email)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !unique {
|
|
|
|
|
return errors.New("email already exists")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|