Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Email validation that scales
# app/models/email_blacklist_domain.rb
class EmailBlacklistDomain < ActiveRecord::Base
validates :value, presence: true, uniqueness: { case_sensitive: false }
end
# app/models/email_blacklist_ip_range.rb
class EmailBlacklistIpRange < ActiveRecord::Base
validates :value, presence: true, uniqueness: true, ip_addr: true
end
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
result = false
# change to /\A[^@\s]+@(([^@.\s]+\.)*[^@.\s]+)\z/ to allow local hosts to work
if value =~ /\A[^@\s]+@(([^@.\s]+\.)+[^@.\s]+)\z/
domain = $2
result = dns_domain_exists?(domain) && ! forbidden?(domain)
end
record.errors[attribute] << (options[:message] || 'is not a valid email address') unless result
end
protected
def forbidden?(domain)
forbidden_dns_domain?(domain) || forbidden_any_ip?(domain)
end
def dns_domain_records(domain)
Resolv::DNS.open do |dns|
dns_records = dns.getresources(domain, Resolv::DNS::Resource::IN::ANY)
yield(dns_records)
end
end
def dns_domain_exists?(domain)
dns_domain_records(domain) do |dns_records|
return true
end
return false
end
def forbidden_dns_domain?(domain)
domain = domain.to_s
EmailBlacklistDomain.all.each { |email_blacklist_domain|
if domain =~ /\A.*#{email_blacklist_domain.value}.*\z/i
return true
end
}
return false
end
def forbidden_any_ip?(domain)
dns_domain_records(domain) do |dns_records|
dns_records.map { |dns_record|
next unless dns_record.respond_to? :address
return true if forbidden_ip?(dns_record.address)
}
end
return false
end
def forbidden_ip?(ip)
EmailBlacklistIpRange.all.each { |email_blacklist_ip_record|
begin
value = email_blacklist_ip_record.value
return true if IPAddr.new(value).include? ip.to_s
rescue IPAddr::InvalidAddressError
logger.warn "invalid ip addr or ip range in email_blacklist_ip table: #{email_blacklist_ip_record}"
end
}
return false
end
end
bundle exec rails g migration CreateEmailBlacklistDomains value:string:uniq notes:string
bundle exec rails g migration CreateEmailBlacklistIpRanges value:string:uniq notes:string
bundle exec rake db:migrate
# app/validators/ip_addr_validator.rb
require 'ipaddr'
class IpAddrValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
IPAddr.new value
rescue IPAddr::InvalidAddressError
record.errors[attribute] << (options[:message] || 'is not a valid IP (v4/v6) addresss or range')
end
end
# db/seeds.rb
### append the following to your seeds.rb
# EmailBlacklistIPRange
[
# mailinator.com
'207.198.106.56',
# yopmail
'213.251.128.128/28',
'213.251.188.128/28',
'2001:41d0:1:1980::1/60',
'2001:41d0:1:4a80::1/60',
].map { |value| EmailBlacklistIpRange.create! value: value }
# EmailBlacklistDomain
%w[
deadaddress\.com
sharklasers\.com
mailinator\.com
yopmail\.com
].map { |value| EmailBlacklistDomain.create! value: value }
@steakknife

This comment has been minimized.

Copy link
Owner Author

commented Feb 25, 2014

TODO: gemify

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.