email_mx_validator.rb 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. # frozen_string_literal: true
  2. require 'resolv'
  3. class EmailMxValidator < ActiveModel::Validator
  4. def validate(user)
  5. return if user.email.blank?
  6. domain = get_domain(user.email)
  7. if domain.blank?
  8. user.errors.add(:email, :invalid)
  9. elsif domain.include?('..')
  10. user.errors.add(:email, :invalid)
  11. elsif !on_allowlist?(domain)
  12. resolved_ips, resolved_domains = resolve_mx(domain)
  13. if resolved_ips.empty?
  14. user.errors.add(:email, :unreachable)
  15. elsif on_blacklist?(resolved_domains, user.sign_up_ip)
  16. user.errors.add(:email, :blocked)
  17. end
  18. end
  19. end
  20. private
  21. def get_domain(value)
  22. _, domain = value.split('@', 2)
  23. return nil if domain.nil?
  24. TagManager.instance.normalize_domain(domain)
  25. rescue Addressable::URI::InvalidURIError
  26. nil
  27. end
  28. def on_allowlist?(domain)
  29. return false if Rails.configuration.x.email_domains_whitelist.blank?
  30. Rails.configuration.x.email_domains_whitelist.include?(domain)
  31. end
  32. def resolve_mx(domain)
  33. records = []
  34. ips = []
  35. Resolv::DNS.open do |dns|
  36. dns.timeouts = 5
  37. records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
  38. ([domain] + records).uniq.each do |hostname|
  39. ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
  40. ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
  41. end
  42. end
  43. [ips, records]
  44. end
  45. def on_blacklist?(domains, attempt_ip)
  46. EmailDomainBlock.block?(domains, attempt_ip: attempt_ip)
  47. end
  48. end