package auth import ( _ "embed" "strings" ) //go:embed disposable_domains.txt var disposableList string // disposableDomains is the parsed blocklist, built once at package init. var disposableDomains = parseDisposable(disposableList) // parseDisposable turns the embedded wordlist into a lookup set. func parseDisposable(raw string) map[string]struct{} { set := make(map[string]struct{}) for _, line := range strings.Split(raw, "\n") { line = strings.TrimSpace(strings.ToLower(line)) if line == "" || strings.HasPrefix(line, "#") { continue } set[line] = struct{}{} } return set } // NormalizeEmail lowercases and trims the address for consistent storage and // rate-limit keying. It does not alter the local part beyond trimming. func NormalizeEmail(email string) string { return strings.ToLower(strings.TrimSpace(email)) } // emailDomain returns the lowercased domain part of an email, or "" if the // address has no single '@'. func emailDomain(email string) string { at := strings.LastIndex(email, "@") if at < 0 || at == len(email)-1 { return "" } return strings.ToLower(email[at+1:]) } // ValidEmail performs a cheap structural sanity check: exactly one '@', a // non-empty local part, and a domain containing a dot. It is deliberately // permissive — true deliverability is proven by the verification code. func ValidEmail(email string) bool { email = strings.TrimSpace(email) if len(email) < 3 || len(email) > 254 { return false } at := strings.IndexByte(email, '@') if at <= 0 || at != strings.LastIndexByte(email, '@') { return false } domain := email[at+1:] return strings.Contains(domain, ".") && !strings.HasPrefix(domain, ".") && !strings.HasSuffix(domain, ".") } // IsDisposable reports whether the email's domain is on the disposable-domain // blocklist. func IsDisposable(email string) bool { domain := emailDomain(email) if domain == "" { return false } _, ok := disposableDomains[domain] return ok }