Fixes https://linear.app/chatwoot/issue/CW-7559/inbox-limit-abuse ## Why The regular inbox API checked limits in the controller, but WhatsApp embedded signup creates inboxes through a service using `Inbox.create!`. That let Enterprise account inbox limits be skipped for embedded signup. ## What this change does - Adds an Inbox create-time validation hook in OSS and implements the limit check in the Enterprise Inbox module. - Removes the duplicate controller/helper limit check so the model is the single enforcement point. - Preserves the existing `402 Payment Required` API response for account inbox limit failures. - Keeps updates to existing inboxes allowed when an account is already at its inbox limit. ## Validation - `bundle exec rspec spec/controllers/api/v1/accounts/inboxes_controller_spec.rb spec/enterprise/models/inbox_spec.rb`
118 lines
4.6 KiB
Ruby
118 lines
4.6 KiB
Ruby
module Api::V1::InboxesHelper
|
|
def inbox_name(channel)
|
|
return channel.try(:bot_name) if channel.is_a?(Channel::Telegram)
|
|
|
|
permitted_params[:name]
|
|
end
|
|
|
|
def validate_email_channel(attributes)
|
|
channel_data = permitted_params(attributes)[:channel]
|
|
|
|
validate_imap(channel_data)
|
|
validate_smtp(channel_data)
|
|
end
|
|
|
|
private
|
|
|
|
def validate_imap(channel_data)
|
|
return unless channel_data.key?('imap_enabled') && channel_data[:imap_enabled]
|
|
|
|
# Validate the user-selected auth mechanism before opening the connection.
|
|
authentication = Imap::Authentication.validate_user_configurable!(channel_data[:imap_authentication])
|
|
|
|
# Use the same auth adapter as the fetch service so LOGIN uses the IMAP LOGIN command,
|
|
# not SASL AUTH=LOGIN.
|
|
check_imap_connection(channel_data, authentication)
|
|
end
|
|
|
|
def validate_smtp(channel_data)
|
|
return unless channel_data.key?('smtp_enabled') && channel_data[:smtp_enabled]
|
|
|
|
smtp = Net::SMTP.new(channel_data[:smtp_address], channel_data[:smtp_port])
|
|
|
|
set_smtp_encryption(channel_data, smtp)
|
|
check_smtp_connection(channel_data, smtp)
|
|
end
|
|
|
|
def check_imap_connection(channel_data, authentication)
|
|
imap = open_imap_connection(channel_data, authentication)
|
|
rescue SocketError => e
|
|
raise StandardError, I18n.t('errors.inboxes.imap.socket_error')
|
|
rescue Net::IMAP::NoResponseError => e
|
|
raise StandardError, I18n.t('errors.inboxes.imap.no_response_error')
|
|
rescue Errno::EHOSTUNREACH => e
|
|
raise StandardError, I18n.t('errors.inboxes.imap.host_unreachable_error')
|
|
rescue Net::OpenTimeout => e
|
|
raise StandardError,
|
|
I18n.t('errors.inboxes.imap.connection_timed_out_error', address: channel_data[:imap_address], port: channel_data[:imap_port])
|
|
rescue Net::IMAP::Error => e
|
|
raise StandardError, I18n.t('errors.inboxes.imap.connection_closed_error')
|
|
rescue StandardError => e
|
|
raise StandardError, e.message
|
|
ensure
|
|
imap.disconnect if imap.present? && !imap.disconnected?
|
|
Rails.logger.error "[Api::V1::InboxesHelper] check_imap_connection failed with #{e.message}" if e.present?
|
|
end
|
|
|
|
def open_imap_connection(channel_data, authentication)
|
|
imap = build_imap_connection(channel_data)
|
|
Imap::Authentication.authenticate!(imap, authentication, channel_data[:imap_login], channel_data[:imap_password])
|
|
imap
|
|
end
|
|
|
|
def build_imap_connection(channel_data)
|
|
Net::IMAP.new(channel_data[:imap_address], port: channel_data[:imap_port], ssl: channel_data[:imap_enable_ssl])
|
|
end
|
|
|
|
def check_smtp_connection(channel_data, smtp)
|
|
smtp.open_timeout = 10
|
|
smtp.start(channel_data[:smtp_domain], channel_data[:smtp_login], channel_data[:smtp_password],
|
|
channel_data[:smtp_authentication]&.to_sym || :login)
|
|
smtp.finish
|
|
rescue Net::SMTPAuthenticationError
|
|
raise StandardError, I18n.t('errors.inboxes.smtp.authentication_error')
|
|
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Net::OpenTimeout
|
|
raise StandardError, I18n.t('errors.inboxes.smtp.connection_error')
|
|
rescue OpenSSL::SSL::SSLError
|
|
raise StandardError, I18n.t('errors.inboxes.smtp.ssl_error')
|
|
rescue Net::SMTPServerBusy, Net::SMTPSyntaxError, Net::SMTPFatalError
|
|
raise StandardError, I18n.t('errors.inboxes.smtp.smtp_error')
|
|
rescue StandardError => e
|
|
raise StandardError, e.message
|
|
end
|
|
|
|
def set_smtp_encryption(channel_data, smtp)
|
|
if channel_data[:smtp_enable_ssl_tls]
|
|
set_smtp_ssl_method(smtp, :enable_tls, channel_data[:smtp_openssl_verify_mode])
|
|
elsif channel_data[:smtp_enable_starttls_auto]
|
|
set_smtp_ssl_method(smtp, :enable_starttls_auto, channel_data[:smtp_openssl_verify_mode])
|
|
end
|
|
end
|
|
|
|
def set_smtp_ssl_method(smtp, method, openssl_verify_mode)
|
|
return unless smtp.respond_to?(method)
|
|
|
|
context = enable_openssl_mode(openssl_verify_mode) if openssl_verify_mode
|
|
context ? smtp.send(method, context) : smtp.send(method)
|
|
end
|
|
|
|
def enable_openssl_mode(smtp_openssl_verify_mode)
|
|
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{smtp_openssl_verify_mode.upcase}".constantize if smtp_openssl_verify_mode.is_a?(String)
|
|
context = Net::SMTP.default_ssl_context
|
|
context.verify_mode = openssl_verify_mode
|
|
context
|
|
end
|
|
|
|
def account_channels_method
|
|
{
|
|
'web_widget' => Current.account.web_widgets,
|
|
'api' => Current.account.api_channels,
|
|
'email' => Current.account.email_channels,
|
|
'line' => Current.account.line_channels,
|
|
'telegram' => Current.account.telegram_channels,
|
|
'whatsapp' => Current.account.whatsapp_channels,
|
|
'sms' => Current.account.sms_channels
|
|
}[permitted_params[:channel][:type]]
|
|
end
|
|
end
|