discourse/app/services/email_settings_exception_handler.rb
2023-01-09 14:14:59 +00:00

132 lines
3.7 KiB
Ruby

# frozen_string_literal: true
require "net/imap"
require "net/smtp"
require "net/pop"
class EmailSettingsExceptionHandler
EXPECTED_EXCEPTIONS = [
Net::POPAuthenticationError,
Net::IMAP::NoResponseError,
Net::IMAP::Error,
Net::SMTPAuthenticationError,
Net::SMTPServerBusy,
Net::SMTPSyntaxError,
Net::SMTPFatalError,
Net::SMTPUnknownError,
Net::OpenTimeout,
Net::ReadTimeout,
SocketError,
Errno::ECONNREFUSED,
]
class GenericProvider
def initialize(exception)
@exception = exception
end
def message
case @exception
when Net::POPAuthenticationError
net_pop_authentication_error
when Net::IMAP::NoResponseError
net_imap_no_response_error
when Net::IMAP::Error
net_imap_unhandled_error
when Net::SMTPAuthenticationError
net_smtp_authentication_error
when Net::SMTPServerBusy
net_smtp_server_busy
when Net::SMTPSyntaxError, Net::SMTPFatalError, Net::SMTPUnknownError
net_smtp_unhandled_error
when SocketError, Errno::ECONNREFUSED
socket_connection_error
when Net::OpenTimeout, Net::ReadTimeout
net_timeout_error
else
unhandled_error
end
end
private
def net_pop_authentication_error
I18n.t("email_settings.pop3_authentication_error")
end
def net_imap_no_response_error
# Most of IMAP's errors are lumped under the NoResponseError, including invalid
# credentials errors, because it is raised when a "NO" response is
# raised from the IMAP server https://datatracker.ietf.org/doc/html/rfc3501#section-7.1.2
#
# Generally, it should be fairly safe to just return the error message as is.
if @exception.message.match(/Invalid credentials/)
I18n.t("email_settings.imap_authentication_error")
else
I18n.t(
"email_settings.imap_no_response_error",
message: @exception.message.gsub(" (Failure)", ""),
)
end
end
def net_imap_unhandled_error
I18n.t("email_settings.imap_unhandled_error", message: @exception.message)
end
def net_smtp_authentication_error
I18n.t("email_settings.smtp_authentication_error")
end
def net_smtp_server_busy
I18n.t("email_settings.smtp_server_busy_error")
end
def net_smtp_unhandled_error
I18n.t("email_settings.smtp_unhandled_error", message: @exception.message)
end
def socket_connection_error
I18n.t("email_settings.connection_error")
end
def net_timeout_error
I18n.t("email_settings.timeout_error")
end
def unhandled_error
I18n.t("email_settings.unhandled_error", message: @exception.message)
end
end
class GmailProvider < GenericProvider
def net_smtp_authentication_error
# Gmail requires use of application-specific passwords when 2FA is enabled and return
# a special error message calling this out.
if @exception.message.match(/Application-specific password required/)
I18n.t("email_settings.authentication_error_gmail_app_password")
else
super
end
end
def net_imap_no_response_error
# Gmail requires use of application-specific passwords when 2FA is enabled and return
# a special error message calling this out.
if @exception.message.match(/Application-specific password required/)
I18n.t("email_settings.authentication_error_gmail_app_password")
else
super
end
end
end
def self.friendly_exception_message(exception, host)
if host.include?("gmail.com")
EmailSettingsExceptionHandler::GmailProvider.new(exception).message
else
EmailSettingsExceptionHandler::GenericProvider.new(exception).message
end
end
end