Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03db72a6f4 | ||
|
|
1df28973ed | ||
|
|
4baf8d4409 | ||
|
|
5fff10325e | ||
|
|
4303dc32fc | ||
|
|
86c8f1ec67 | ||
|
|
685a1dda48 | ||
|
|
0cb5eb8a5f |
@@ -20,7 +20,7 @@ gem 'rest-client'
|
|||||||
gem 'telephone_number'
|
gem 'telephone_number'
|
||||||
gem 'time_diff'
|
gem 'time_diff'
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
gem 'valid_email2'
|
gem 'valid_email2', '~> 7.0'
|
||||||
gem 'email-provider-info'
|
gem 'email-provider-info'
|
||||||
gem 'gemoji'
|
gem 'gemoji'
|
||||||
# compress javascript config.assets.js_compressor
|
# compress javascript config.assets.js_compressor
|
||||||
|
|||||||
+11
-10
@@ -167,8 +167,8 @@ GEM
|
|||||||
statsd-ruby (~> 1.1)
|
statsd-ruby (~> 1.1)
|
||||||
base64 (0.3.0)
|
base64 (0.3.0)
|
||||||
bcrypt (3.1.22)
|
bcrypt (3.1.22)
|
||||||
benchmark (0.4.1)
|
benchmark (0.5.0)
|
||||||
bigdecimal (3.2.2)
|
bigdecimal (3.3.1)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.16.0)
|
bootsnap (1.16.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
@@ -190,7 +190,7 @@ GEM
|
|||||||
climate_control (1.2.0)
|
climate_control (1.2.0)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
commonmarker (0.23.10)
|
commonmarker (0.23.10)
|
||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.3.6)
|
||||||
connection_pool (2.5.5)
|
connection_pool (2.5.5)
|
||||||
crack (1.0.0)
|
crack (1.0.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
@@ -451,7 +451,7 @@ GEM
|
|||||||
mini_mime (>= 1.0.0)
|
mini_mime (>= 1.0.0)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.14.7)
|
i18n (1.14.8)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.12.2)
|
image_processing (1.12.2)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
@@ -545,7 +545,8 @@ GEM
|
|||||||
loofah (2.23.1)
|
loofah (2.23.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.9.0)
|
||||||
|
logger
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
@@ -562,7 +563,7 @@ GEM
|
|||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.9)
|
mini_portile2 (2.8.9)
|
||||||
minitest (5.25.5)
|
minitest (5.27.0)
|
||||||
mock_redis (0.36.0)
|
mock_redis (0.36.0)
|
||||||
ruby2_keywords
|
ruby2_keywords
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
@@ -577,7 +578,7 @@ GEM
|
|||||||
uri (>= 0.11.1)
|
uri (>= 0.11.1)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.24)
|
net-imap (0.6.4)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -991,8 +992,8 @@ GEM
|
|||||||
uri (1.1.1)
|
uri (1.1.1)
|
||||||
uri-idna (0.3.1)
|
uri-idna (0.3.1)
|
||||||
uri_template (0.7.0)
|
uri_template (0.7.0)
|
||||||
valid_email2 (5.2.6)
|
valid_email2 (7.0.15)
|
||||||
activemodel (>= 3.2)
|
activemodel (>= 6.0)
|
||||||
mail (~> 2.5)
|
mail (~> 2.5)
|
||||||
version_gem (1.1.4)
|
version_gem (1.1.4)
|
||||||
vite_rails (3.0.17)
|
vite_rails (3.0.17)
|
||||||
@@ -1181,7 +1182,7 @@ DEPENDENCIES
|
|||||||
twitty (~> 0.1.5)
|
twitty (~> 0.1.5)
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
uglifier
|
uglifier
|
||||||
valid_email2
|
valid_email2 (~> 7.0)
|
||||||
vite_rails
|
vite_rails
|
||||||
web-console (>= 4.2.1)
|
web-console (>= 4.2.1)
|
||||||
web-push (>= 3.0.1)
|
web-push (>= 3.0.1)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
class AccountBuilder
|
class AccountBuilder
|
||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
pattr_initialize [:account_name, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale]
|
pattr_initialize [:account_name, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale,
|
||||||
|
:allow_free_email_provider]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
if @user.nil?
|
if @user.nil?
|
||||||
@@ -32,7 +33,7 @@ class AccountBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def validate_email
|
def validate_email
|
||||||
Account::SignUpEmailValidationService.new(@email).perform
|
Account::SignUpEmailValidationService.new(@email, allow_free_email_provider: @allow_free_email_provider).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_user
|
def validate_user
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class Installation::OnboardingController < ApplicationController
|
|||||||
email: onboarding_params.dig(:user, :email),
|
email: onboarding_params.dig(:user, :email),
|
||||||
user_password: params.dig(:user, :password),
|
user_password: params.dig(:user, :password),
|
||||||
super_admin: true,
|
super_admin: true,
|
||||||
confirmed: true
|
confirmed: true,
|
||||||
|
allow_free_email_provider: true
|
||||||
).perform
|
).perform
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
redirect_to '/', flash: { error: e.message } and return
|
redirect_to '/', flash: { error: e.message } and return
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ class Account::SignUpEmailValidationService
|
|||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
attr_reader :email
|
attr_reader :email
|
||||||
|
|
||||||
def initialize(email)
|
def initialize(email, allow_free_email_provider: false)
|
||||||
@email = email
|
@email = email
|
||||||
|
@allow_free_email_provider = allow_free_email_provider
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
@@ -17,6 +18,8 @@ class Account::SignUpEmailValidationService
|
|||||||
|
|
||||||
raise InvalidEmail.new({ valid: true, disposable: true }) if address.disposable?
|
raise InvalidEmail.new({ valid: true, disposable: true }) if address.disposable?
|
||||||
|
|
||||||
|
raise InvalidEmail.new({ free_email_provider: true }) if !@allow_free_email_provider && address.deny_listed?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,7 @@ en:
|
|||||||
signup:
|
signup:
|
||||||
disposable_email: We do not allow disposable emails
|
disposable_email: We do not allow disposable emails
|
||||||
blocked_domain: This domain is not allowed. If you believe this is a mistake, please contact support.
|
blocked_domain: This domain is not allowed. If you believe this is a mistake, please contact support.
|
||||||
|
free_email_provider: Please sign up with your work email address.
|
||||||
invalid_email: You have entered an invalid email
|
invalid_email: You have entered an invalid email
|
||||||
email_already_exists: 'You have already signed up for an account with %{email}'
|
email_already_exists: 'You have already signed up for an account with %{email}'
|
||||||
invalid_params: 'Invalid, please check the signup paramters and try again'
|
invalid_params: 'Invalid, please check the signup paramters and try again'
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ module CustomExceptions::Account
|
|||||||
I18n.t 'errors.signup.blocked_domain'
|
I18n.t 'errors.signup.blocked_domain'
|
||||||
elsif @data[:disposable]
|
elsif @data[:disposable]
|
||||||
I18n.t 'errors.signup.disposable_email'
|
I18n.t 'errors.signup.disposable_email'
|
||||||
|
elsif @data[:free_email_provider]
|
||||||
|
I18n.t 'errors.signup.free_email_provider'
|
||||||
elsif !@data[:valid]
|
elsif !@data[:valid]
|
||||||
I18n.t 'errors.signup.invalid_email'
|
I18n.t 'errors.signup.invalid_email'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ RSpec.describe AccountBuilder do
|
|||||||
|
|
||||||
# Mock the email validation service
|
# Mock the email validation service
|
||||||
before do
|
before do
|
||||||
allow(Account::SignUpEmailValidationService).to receive(:new).with(email).and_return(validation_service)
|
allow(Account::SignUpEmailValidationService).to receive(:new).with(email, allow_free_email_provider: anything).and_return(validation_service)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ require 'rails_helper'
|
|||||||
RSpec.describe Account::SignUpEmailValidationService, type: :service do
|
RSpec.describe Account::SignUpEmailValidationService, type: :service do
|
||||||
let(:service) { described_class.new(email) }
|
let(:service) { described_class.new(email) }
|
||||||
let(:blocked_domains) { "gmail.com\noutlook.com" }
|
let(:blocked_domains) { "gmail.com\noutlook.com" }
|
||||||
let(:valid_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: false) }
|
let(:valid_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: false, deny_listed?: false) }
|
||||||
|
let(:free_provider_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: false, deny_listed?: true) }
|
||||||
let(:disposable_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: true) }
|
let(:disposable_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: true) }
|
||||||
let(:invalid_email_address) { instance_double(ValidEmail2::Address, valid?: false) }
|
let(:invalid_email_address) { instance_double(ValidEmail2::Address, valid?: false) }
|
||||||
|
|
||||||
@@ -62,13 +63,77 @@ RSpec.describe Account::SignUpEmailValidationService, type: :service do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when email is valid business email' do
|
context 'when email is from a free provider' do
|
||||||
let(:email) { 'test@example.com' }
|
let(:email) { 'test@example.com' }
|
||||||
|
|
||||||
|
it 'raises InvalidEmail with free email provider message' do
|
||||||
|
allow(ValidEmail2::Address).to receive(:new).with(email).and_return(free_provider_email_address)
|
||||||
|
expect { service.perform }.to raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('CustomExceptions::Account::InvalidEmail')
|
||||||
|
expect(error.message).to eq(I18n.t('errors.signup.free_email_provider'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when email is valid business email' do
|
||||||
|
let(:email) { 'test@chatwoot.com' }
|
||||||
|
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
allow(ValidEmail2::Address).to receive(:new).with(email).and_return(valid_email_address)
|
allow(ValidEmail2::Address).to receive(:new).with(email).and_return(valid_email_address)
|
||||||
expect(service.perform).to be(true)
|
expect(service.perform).to be(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Exercises the real ValidEmail2 + config/deny_listed_email_domains.yml
|
||||||
|
# integration without stubbing ValidEmail2::Address. Guards against the YAML
|
||||||
|
# file going missing, the path drifting, or upstream API renames.
|
||||||
|
describe '#perform with the real deny list' do
|
||||||
|
before do
|
||||||
|
allow(GlobalConfigService).to receive(:load).with('BLOCKED_EMAIL_DOMAINS', '').and_return('')
|
||||||
|
# ValidEmail2::Dns is instantiated lazily inside ValidEmail2::Address per call,
|
||||||
|
# so we cannot capture the instance to stub it. Stubbing the class-level constructor
|
||||||
|
# would also break ValidEmail2's internal caching. Returning empty MX/A records keeps
|
||||||
|
# disposable? and valid_mx? offline and deterministic in CI.
|
||||||
|
allow_any_instance_of(ValidEmail2::Dns).to receive_messages(mx_servers: [], a_servers: []) # rubocop:disable RSpec/AnyInstance
|
||||||
|
end
|
||||||
|
|
||||||
|
%w[user@gmail.com user@outlook.com user@hotmail.com user@yahoo.com user@protonmail.com user@icloud.com].each do |email|
|
||||||
|
it "rejects #{email} with the free_email_provider message" do
|
||||||
|
expect { described_class.new(email).perform }.to raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('CustomExceptions::Account::InvalidEmail')
|
||||||
|
expect(error.message).to eq(I18n.t('errors.signup.free_email_provider'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects a subdomain of a free provider via ValidEmail2 depth walking' do
|
||||||
|
expect { described_class.new('user@mail.gmail.com').perform }.to raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('CustomExceptions::Account::InvalidEmail')
|
||||||
|
expect(error.message).to eq(I18n.t('errors.signup.free_email_provider'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows a real business domain' do
|
||||||
|
expect(described_class.new('hello@chatwoot.com').perform).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'still rejects a disposable email with the disposable message, not the free_email message' do
|
||||||
|
expect { described_class.new('user@mailinator.com').perform }.to raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('CustomExceptions::Account::InvalidEmail')
|
||||||
|
expect(error.message).to eq(I18n.t('errors.signup.disposable_email'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows free email providers when allow_free_email_provider is true' do
|
||||||
|
expect(described_class.new('admin@gmail.com', allow_free_email_provider: true).perform).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'still rejects disposable emails even when allow_free_email_provider is true' do
|
||||||
|
expect { described_class.new('user@mailinator.com', allow_free_email_provider: true).perform }.to raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('CustomExceptions::Account::InvalidEmail')
|
||||||
|
expect(error.message).to eq(I18n.t('errors.signup.disposable_email'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user