From 7fc5845e72a381b0e87df11671eafa93b35acd83 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 28 Dec 2020 19:31:40 -0600 Subject: [PATCH] /emails: add more search options. Add options to search for invalid emails and emails from restricted domains. --- app/logical/email_validator.rb | 17 ++++++++-- app/models/email_address.rb | 59 +++++++++++++++++++++++++-------- app/views/emails/index.html.erb | 28 +++++++++++++--- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/app/logical/email_validator.rb b/app/logical/email_validator.rb index 35fd5c46a..be9951810 100644 --- a/app/logical/email_validator.rb +++ b/app/logical/email_validator.rb @@ -3,6 +3,10 @@ require 'resolv' module EmailValidator module_function + # https://www.regular-expressions.info/email.html + EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/ + POSTGRES_EMAIL_REGEX = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" + IGNORE_DOTS = %w[gmail.com] IGNORE_PLUS_ADDRESSING = %w[gmail.com hotmail.com outlook.com live.com] IGNORE_MINUS_ADDRESSING = %w[yahoo.com] @@ -80,10 +84,17 @@ module EmailValidator "#{name}@#{domain}" end - def nondisposable?(address) - return true if Danbooru.config.email_domain_verification_list.blank? + def is_valid?(address) + address.match?(EMAIL_REGEX) + end + + def is_restricted?(address) + return false if Danbooru.config.email_domain_verification_list.blank? + domain = Mail::Address.new(address).domain - domain.in?(Danbooru.config.email_domain_verification_list.to_a) + !domain.in?(Danbooru.config.email_domain_verification_list.to_a) + rescue Mail::Field::IncompleteParseError + true end def undeliverable?(to_address, from_address: Danbooru.config.contact_email, timeout: 3) diff --git a/app/models/email_address.rb b/app/models/email_address.rb index 7897d6335..b43f0a6b4 100644 --- a/app/models/email_address.rb +++ b/app/models/email_address.rb @@ -1,10 +1,7 @@ class EmailAddress < ApplicationRecord - # https://www.regular-expressions.info/email.html - EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/ - belongs_to :user, inverse_of: :email_address - validates :address, presence: true, confirmation: true, format: { with: EMAIL_REGEX } + validates :address, presence: true, confirmation: true, format: { with: EmailValidator::EMAIL_REGEX } validates :normalized_address, uniqueness: true validates :user_id, uniqueness: true validate :validate_deliverable, on: :deliverable @@ -23,8 +20,49 @@ class EmailAddress < ApplicationRecord super end - def nondisposable? - EmailValidator.nondisposable?(normalized_address) + def is_restricted? + EmailValidator.is_restricted?(normalized_address) + end + + def is_normalized? + address == normalized_address + end + + def is_valid? + EmailValidator.is_valid?(address) + end + + def self.restricted(restricted = true) + domains = Danbooru.config.email_domain_verification_list + domain_regex = domains.map { |domain| Regexp.escape(domain) }.join("|") + + if restricted.to_s.truthy? + where_not_regex(:normalized_address, "@(#{domain_regex})$") + elsif restricted.to_s.falsy? + where_regex(:normalized_address, "@(#{domain_regex})$") + else + all + end + end + + def self.valid(valid = true) + if valid.to_s.truthy? + where_regex(:address, EmailValidator::POSTGRES_EMAIL_REGEX.to_s) + elsif valid.to_s.falsy? + where_not_regex(:address, EmailValidator::POSTGRES_EMAIL_REGEX.to_s) + else + all + end + end + + def self.search(params) + q = search_attributes(params, :id, :created_at, :updated_at, :user, :address, :normalized_address, :is_verified, :is_deliverable) + + q = q.restricted(params[:is_restricted]) + q = q.valid(params[:is_valid]) + q = q.apply_default_order(params) + + q end def validate_deliverable @@ -34,14 +72,7 @@ class EmailAddress < ApplicationRecord end def update_user - user.update!(is_verified: is_verified? && nondisposable?) - end - - def self.search(params) - q = search_attributes(params, :id, :created_at, :updated_at, :user, :address, :normalized_address, :is_verified, :is_deliverable) - q = q.apply_default_order(params) - - q + user.update!(is_verified: is_verified? && !is_restricted?) end concerning :VerificationMethods do diff --git a/app/views/emails/index.html.erb b/app/views/emails/index.html.erb index c1f7429ac..b03490a0a 100644 --- a/app/views/emails/index.html.erb +++ b/app/views/emails/index.html.erb @@ -5,8 +5,11 @@ <%= fa.input :name, label: "User Name", input_html: { value: params.dig(:search, :user, :name), "data-autocomplete": "user" } %> <% end %> - <%= f.input :address_ilike, label: "Address", input_html: { value: params[:search][:address] }, hint: "Use * for wildcard" %> - <%= f.input :is_verified, label: "Verified?", collection: %w[Yes No], selected: params[:search][:is_verified] %> + <%= f.input :address_ilike, label: "Address", input_html: { value: params[:search][:address_ilike] }, hint: "Use * for wildcard" %> + <%= f.input :normalized_address_ilike, label: "Normalized Address", input_html: { value: params[:search][:normalized_address_ilike] }, hint: "Use * for wildcard" %> + <%= f.input :is_valid, label: "Valid?", as: :select, include_blank: true, selected: params[:search][:is_valid] %> + <%= f.input :is_verified, label: "Verified?", as: :select, include_blank: true, selected: params[:search][:is_verified] %> + <%= f.input :is_restricted, label: "Restricted?", as: :select, include_blank: true, selected: params[:search][:is_restricted] %> <%= f.submit "Search" %> <% end %> @@ -14,8 +17,25 @@ <% t.column :user do |email| %> <%= link_to_user email.user %> <% end %> - <% t.column :address %> - <% t.column :is_verified, name: "Verified?" do |email| %> + <% t.column :address do |email| %> + <%= link_to email.address, emails_path(search: { address_ilike: email.address }) %> + <% end %> + <% t.column :normalized_address do |email| %> + <% unless email.is_normalized? %> + <%= link_to email.normalized_address, emails_path(search: { normalized_address_ilike: email.normalized_address }) %> + <% end %> + <% end %> + <% t.column "Valid?" do |email| %> + <% if !email.is_valid? %> + <%= link_to "No", emails_path(search: { is_valid: false }) %> + <% end %> + <% end %> + <% t.column "Restricted?" do |email| %> + <% if email.is_restricted? %> + <%= link_to "Yes", emails_path(search: { is_restricted: true }) %> + <% end %> + <% end %> + <% t.column "Verified?" do |email| %> <% if email.is_verified? %> <%= link_to "Yes", emails_path(search: { is_verified: true }) %> <% else %>