Merge pull request #4405 from tomhughes/normalize-display-name
Require user names to be unique after unicode normalisation
This commit is contained in:
commit
d5efa4c357
5 changed files with 49 additions and 12 deletions
|
@ -34,12 +34,13 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# users_auth_idx (auth_provider,auth_uid) UNIQUE
|
||||
# users_display_name_idx (display_name) UNIQUE
|
||||
# users_display_name_lower_idx (lower((display_name)::text))
|
||||
# users_email_idx (email) UNIQUE
|
||||
# users_email_lower_idx (lower((email)::text))
|
||||
# users_home_idx (home_tile)
|
||||
# users_auth_idx (auth_provider,auth_uid) UNIQUE
|
||||
# users_display_name_canonical_idx (lower(NORMALIZE(display_name, NFKC)))
|
||||
# users_display_name_idx (display_name) UNIQUE
|
||||
# users_display_name_lower_idx (lower((display_name)::text))
|
||||
# users_email_idx (email) UNIQUE
|
||||
# users_email_lower_idx (lower((email)::text))
|
||||
# users_home_idx (home_tile)
|
||||
#
|
||||
|
||||
class User < ApplicationRecord
|
||||
|
@ -95,7 +96,7 @@ class User < ApplicationRecord
|
|||
validates :display_name, :presence => true, :length => 3..255,
|
||||
:exclusion => %w[new terms save confirm confirm-email go_public reset-password forgot-password suspended]
|
||||
validates :display_name, :if => proc { |u| u.display_name_changed? },
|
||||
:uniqueness => { :case_sensitive => false }
|
||||
:normalized_uniqueness => { :case_sensitive => false }
|
||||
validates :display_name, :if => proc { |u| u.display_name_changed? },
|
||||
:characters => { :url_safe => true },
|
||||
:whitespace => { :leading => false, :trailing => false }
|
||||
|
@ -132,7 +133,7 @@ class User < ApplicationRecord
|
|||
user = find_by("email = ? OR display_name = ?", options[:username].strip, options[:username])
|
||||
|
||||
if user.nil?
|
||||
users = where("LOWER(email) = LOWER(?) OR LOWER(display_name) = LOWER(?)", options[:username].strip, options[:username])
|
||||
users = where("LOWER(email) = LOWER(?) OR LOWER(NORMALIZE(display_name, NFKC)) = LOWER(NORMALIZE(?, NFKC))", options[:username].strip, options[:username])
|
||||
|
||||
user = users.first if users.count == 1
|
||||
end
|
||||
|
|
18
app/validators/normalized_uniqueness_validator.rb
Normal file
18
app/validators/normalized_uniqueness_validator.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class NormalizedUniquenessValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
relation = if options.fetch(:case_sensitive, true)
|
||||
record.class.where("NORMALIZE(#{attribute}, NFKC) = NORMALIZE(?, NFKC)", value)
|
||||
else
|
||||
record.class.where("LOWER(NORMALIZE(#{attribute}, NFKC)) = LOWER(NORMALIZE(?, NFKC))", value)
|
||||
end
|
||||
|
||||
relation = relation.where.not(record.class.primary_key => [record.id_in_database]) if record.persisted?
|
||||
|
||||
if relation.exists?
|
||||
error_options = options.except(:case_sensitive)
|
||||
error_options[:value] = value
|
||||
|
||||
record.errors.add(attribute, :taken, **error_options)
|
||||
end
|
||||
end
|
||||
end
|
7
db/migrate/20231213182102_add_canonical_user_index.rb
Normal file
7
db/migrate/20231213182102_add_canonical_user_index.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class AddCanonicalUserIndex < ActiveRecord::Migration[7.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_index :users, "LOWER(NORMALIZE(display_name, NFKC))", :name => "users_display_name_canonical_idx", :algorithm => :concurrently
|
||||
end
|
||||
end
|
|
@ -2922,6 +2922,13 @@ CREATE INDEX user_tokens_user_id_idx ON public.user_tokens USING btree (user_id)
|
|||
CREATE UNIQUE INDEX users_auth_idx ON public.users USING btree (auth_provider, auth_uid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_display_name_canonical_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX users_display_name_canonical_idx ON public.users USING btree (lower(NORMALIZE(display_name, NFKC)));
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_display_name_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -3581,6 +3588,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('23'),
|
||||
('22'),
|
||||
('21'),
|
||||
('20231213182102'),
|
||||
('20231206141457'),
|
||||
('20231117170422'),
|
||||
('20231101222146'),
|
||||
|
|
|
@ -27,10 +27,13 @@ class UserTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_unique_display_name
|
||||
existing_user = create(:user)
|
||||
new_user = build(:user, :display_name => existing_user.display_name)
|
||||
assert_not new_user.save
|
||||
assert_includes new_user.errors[:display_name], "has already been taken"
|
||||
create(:user, :display_name => "H\u{e9}nryIV")
|
||||
|
||||
%W[H\u{e9}nryIV he\u{301}nryiv H\u{c9}nry\u2163 he\u{301}nry\u2173].each do |name|
|
||||
new_user = build(:user, :display_name => name)
|
||||
assert_not new_user.save
|
||||
assert_includes new_user.errors[:display_name], "has already been taken"
|
||||
end
|
||||
end
|
||||
|
||||
def test_email_valid
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue