package auth import ( "context" "strings" "github.com/perfect-panel/server/internal/config" usermodel "github.com/perfect-panel/server/internal/model/user" "gorm.io/gorm" ) // IsEmailDomainWhitelisted checks if the email's domain is in the comma-separated whitelist. // Returns false if the email format is invalid. func IsEmailDomainWhitelisted(email, whitelistCSV string) bool { if whitelistCSV == "" { return false } parts := strings.SplitN(email, "@", 2) if len(parts) != 2 { return false } domain := strings.ToLower(strings.TrimSpace(parts[1])) for _, d := range strings.Split(whitelistCSV, ",") { if strings.ToLower(strings.TrimSpace(d)) == domain { return true } } return false } func ShouldGrantTrialForEmail(register config.RegisterConfig, email string) bool { if !register.EnableTrial { return false } if !register.EnableTrialEmailWhitelist { return true } if register.TrialEmailDomainWhitelist == "" { return false } return IsEmailDomainWhitelisted(email, register.TrialEmailDomainWhitelist) } // NormalizeEmail returns a canonical form of the email for trial deduplication. // Strips "+" aliases universally (user+tag@any.com → user@any.com). // Removes dots from local part for Gmail-like providers (gmail.com, googlemail.com). func NormalizeEmail(email string) string { email = strings.ToLower(strings.TrimSpace(email)) parts := strings.SplitN(email, "@", 2) if len(parts) != 2 { return email } local, domain := parts[0], parts[1] // Strip + alias if idx := strings.IndexByte(local, '+'); idx != -1 { local = local[:idx] } // Remove dots for Gmail-like providers that ignore dots in local part if isGmailLikeDomain(domain) { local = strings.ReplaceAll(local, ".", "") } return local + "@" + domain } func isGmailLikeDomain(domain string) bool { switch domain { case "gmail.com", "googlemail.com": return true } return false } // NormalizedEmailHasTrial returns true if any user with the same normalized email // already holds a trial subscription. Only performs the cross-user DB check when // normalization actually changes the email (i.e., dots removed or + alias stripped). func NormalizedEmailHasTrial(ctx context.Context, db *gorm.DB, email string, trialSubscribeId int64) bool { normalized := NormalizeEmail(email) if normalized == strings.ToLower(strings.TrimSpace(email)) { return false // normalization changed nothing, skip cross-user check } parts := strings.SplitN(normalized, "@", 2) if len(parts) != 2 { return false } domain := parts[1] var authMethods []usermodel.AuthMethods if err := db.WithContext(ctx). Model(&usermodel.AuthMethods{}). Select("user_id, auth_identifier"). Where("auth_type = ? AND auth_identifier LIKE ?", "email", "%@"+domain). Find(&authMethods).Error; err != nil { return false } for _, am := range authMethods { if NormalizeEmail(am.AuthIdentifier) != normalized { continue } var count int64 if err := db.WithContext(ctx). Model(&usermodel.Subscribe{}). Where("user_id = ? AND subscribe_id = ?", am.UserId, trialSubscribeId). Count(&count).Error; err != nil { continue } if count > 0 { return true } } return false }