FEATURE: Add email normalization rules setting (#14593)
When this setting is turned on, it will check that normalized emails are unique. Normalized emails are emails without any dots or plus aliases. This setting can be used to block use of aliases of the same email address.
This commit is contained in:
parent
a6aff40e4b
commit
3ea8937157
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class MigrateNormalizedEmails < ::Jobs::Onceoff
|
||||
def execute_onceoff(args)
|
||||
::UserEmail.find_each do |user_email|
|
||||
user_email.update(normalized_email: user_email.normalize_email)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ class UserEmail < ActiveRecord::Base
|
|||
attr_accessor :skip_validate_unique_email
|
||||
|
||||
before_validation :strip_downcase_email
|
||||
before_validation :normalize_email
|
||||
|
||||
validates :email, presence: true
|
||||
validates :email, email: true, if: :validate_email?
|
||||
|
@ -17,6 +18,14 @@ class UserEmail < ActiveRecord::Base
|
|||
|
||||
scope :secondary, -> { where(primary: false) }
|
||||
|
||||
def normalize_email
|
||||
self.normalized_email = if self.email.present?
|
||||
username, domain = self.email.split('@', 2)
|
||||
username = username.gsub('.', '').gsub(/\+.*/, '')
|
||||
"#{username}@#{domain}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def strip_downcase_email
|
||||
|
@ -37,9 +46,13 @@ class UserEmail < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def unique_email
|
||||
if self.class.where("lower(email) = ?", email).exists?
|
||||
self.errors.add(:email, :taken)
|
||||
email_exists = if SiteSetting.normalize_emails?
|
||||
self.class.where("lower(email) = ? OR lower(normalized_email) = ?", email, normalized_email).exists?
|
||||
else
|
||||
self.class.where("lower(email) = ?", email).exists?
|
||||
end
|
||||
|
||||
self.errors.add(:email, :taken) if email_exists
|
||||
end
|
||||
|
||||
def user_id_not_changed
|
||||
|
@ -55,16 +68,18 @@ end
|
|||
#
|
||||
# Table name: user_emails
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# email :string(513) not null
|
||||
# primary :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# email :string(513) not null
|
||||
# primary :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# normalized_email :string
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_emails_on_email (lower((email)::text)) UNIQUE
|
||||
# index_user_emails_on_normalized_email (lower((normalized_email)::text))
|
||||
# index_user_emails_on_user_id (user_id)
|
||||
# index_user_emails_on_user_id_and_primary (user_id,primary) UNIQUE WHERE "primary"
|
||||
#
|
||||
|
|
|
@ -1649,6 +1649,7 @@ en:
|
|||
allow_index_in_robots_txt: "Specify in robots.txt that this site is allowed to be indexed by web search engines. In exceptional cases you can permanently <a href='%{base_path}/admin/customize/robots'>override robots.txt</a>."
|
||||
blocked_email_domains: "A pipe-delimited list of email domains that users are not allowed to register accounts with. Example: mailinator.com|trashmail.net"
|
||||
allowed_email_domains: "A pipe-delimited list of email domains that users MUST register accounts with. WARNING: Users with email domains other than those listed will not be allowed!"
|
||||
normalize_emails: "Check if normalized email is unique. Normalized email removes all dots from the username and everything between + and @ symbols."
|
||||
auto_approve_email_domains: "Users with email addresses from this list of domains will be automatically approved."
|
||||
hide_email_address_taken: "Don't inform users that an account exists with a given email address during signup and from the forgot password form."
|
||||
log_out_strict: "When logging out, log out ALL sessions for the user on all devices"
|
||||
|
|
|
@ -510,6 +510,8 @@ login:
|
|||
default: ""
|
||||
type: list
|
||||
list_type: simple
|
||||
normalize_emails:
|
||||
default: false
|
||||
auto_approve_email_domains:
|
||||
default: ""
|
||||
type: list
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNormalizedEmailToUserEmail < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :user_emails, :normalized_email, :string
|
||||
execute "CREATE INDEX index_user_emails_on_normalized_email ON user_emails (LOWER(normalized_email))"
|
||||
end
|
||||
|
||||
def down
|
||||
execute "DROP INDEX index_user_emails_on_normalized_email"
|
||||
drop_column :user_emails, :normalized_email, :string
|
||||
end
|
||||
end
|
|
@ -26,6 +26,32 @@ describe UserEmail do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'normalized_email' do
|
||||
it 'checks if normalized email is unique' do
|
||||
SiteSetting.normalize_emails = true
|
||||
|
||||
user_email = user.user_emails.create(email: "a.b+c@example.com", primary: false)
|
||||
expect(user_email.normalized_email).to eq("ab@example.com")
|
||||
expect(user_email).to be_valid
|
||||
|
||||
user_email = user.user_emails.create(email: "a.b+d@example.com", primary: false)
|
||||
expect(user_email.normalized_email).to eq("ab@example.com")
|
||||
expect(user_email).not_to be_valid
|
||||
end
|
||||
|
||||
it 'does not check uniqueness if email normalization is not enabled' do
|
||||
SiteSetting.normalize_emails = false
|
||||
|
||||
user_email = user.user_emails.create(email: "a.b+c@example.com", primary: false)
|
||||
expect(user_email.normalized_email).to eq("ab@example.com")
|
||||
expect(user_email).to be_valid
|
||||
|
||||
user_email = user.user_emails.create(email: "a.b+d@example.com", primary: false)
|
||||
expect(user_email.normalized_email).to eq("ab@example.com")
|
||||
expect(user_email).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "indexes" do
|
||||
it "allows only one primary email" do
|
||||
expect {
|
||||
|
|
Loading…
Reference in New Issue