add validator for typoed emails

It happens quite often that people typo their email address (I blame
phone keyboards for that).  Catch common cases in a validator and
tell the user that they have a typo in their email.

Why yes, I did write the tests for this first, thanks for asking!
This commit is contained in:
Georg Gadinger 2022-01-11 18:37:07 +01:00
parent 515e6d09ff
commit 29923fac84
3 changed files with 100 additions and 1 deletions

View File

@ -61,7 +61,7 @@ class User < ApplicationRecord
screen_name.strip!
end
validates :email, fake_email: true
validates :email, fake_email: true, typoed_email: true
validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX }, uniqueness: { case_sensitive: false }, screen_name: true
mount_uploader :profile_picture, ProfilePictureUploader, mount_on: :profile_picture_file_name

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
class TypoedEmailValidator < ActiveModel::EachValidator
# this array contains "forbidden" email address endings
INVALID_ENDINGS = [
# without @:
*%w[
.con
.coom
],
# with @:
*%w[
fmail.com
gemail.com
gmail.co
gmaile.com
gmaill.com
icluod.com
proton.mail
].map { "@#{_1}" }
].freeze
def validate_each(record, attribute, value)
return if valid?(value)
record.errors[attribute] << "contains a typo"
end
private
def valid?(value)
# needs an @
return false unless value.include?('@')
# part after the @ needs to have at least one period
return false if value.split('@', 2).last.count('.') == 0
# finally, common typos
return false if INVALID_ENDINGS.any? { value.end_with?(_1) }
true
end
end

View File

@ -33,6 +33,61 @@ RSpec.describe User, type: :model do
end
end
describe 'email validation' do
subject do
FactoryBot.build(:user, email: email).tap(&:validate).errors[:email]
end
shared_examples_for 'valid email' do |example_email|
context "when email is #{example_email}" do
let(:email) { example_email }
it "does not have validation errors" do
expect(subject).to be_empty
end
end
end
shared_examples_for 'invalid email' do |example_email|
context "when email is #{example_email}" do
let(:email) { example_email }
it "has validation errors" do
expect(subject).not_to be_empty
end
end
end
include_examples 'valid email', 'ifyouusethismailyouarebanned@nilsding.org'
include_examples 'valid email', 'fritz.fantom@gmail.com'
include_examples 'valid email', 'fritz.fantom@columbiamail.co'
include_examples 'valid email', 'fritz.fantom@protonmail.com'
include_examples 'valid email', 'fritz.fantom@enterprise.k8s.420stripes.k8s.needs.more.k8s.jira.atlassian.k8s.eu-central-1.s3.amazonaws.com'
include_examples 'invalid email', '@jack'
# examples from the real world:
# .con is not a valid TLD
include_examples 'invalid email', 'fritz.fantom@gmail.con'
include_examples 'invalid email', 'fritz.fantom@protonmail.con'
# neither is .coom
include_examples 'invalid email', 'fritz.fantom@gmail.coom'
# common typos:
include_examples 'invalid email', 'fritz.fantom@fmail.com'
include_examples 'invalid email', 'fritz.fantom@gemail.com'
include_examples 'invalid email', 'fritz.fantom@gmail.co'
include_examples 'invalid email', 'fritz.fantom@gmailcom'
include_examples 'invalid email', 'fritz.fantom@gmaile.com'
include_examples 'invalid email', 'fritz.fantom@gmaill.com'
include_examples 'invalid email', 'fritz.fantom@hotmailcom'
include_examples 'invalid email', 'fritz.fantom@icluod.com'
# no TLD
include_examples 'invalid email', 'fritz.fantom@gmail'
include_examples 'invalid email', 'fritz.fantom@protonmail'
# not registered as of 2022-01-11
include_examples 'invalid email', 'fritz.fantom@proton.mail'
end
# -- User::TimelineMethods --
shared_examples_for 'result is blank' do