# Pull Request Template ## Description This PR addresses an issue where users were unable to view images sent via WhatsApp on Chatwoot due to incorrect Twilio authentication configuration. https://app.chatwoot.com/app/accounts/1/conversations/50824 The problem stemmed from how authentication was being handled for Twilio API requests. The user had configured their inbox using api_key_sid, but the backend logic used only auth_token, leading to failed authentication. Further investigation showed that some customers might input api_secret into the auth_token field unintentionally. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - Tested on console with Client(api_key_sid, auth_token, account_sid) and validated successful authentication for the customer (Twilio channel ID: 2702). - Simulated toggling the “Use API Key Authentication” checkbox to ensure backend behavior matches UI intent - Verified image rendering by testing with the same image URL that was previously failing for the user. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
159 lines
4.3 KiB
Ruby
159 lines
4.3 KiB
Ruby
class Twilio::IncomingMessageService
|
|
include ::FileTypeHelper
|
|
|
|
pattr_initialize [:params!]
|
|
|
|
def perform
|
|
return if twilio_channel.blank?
|
|
|
|
set_contact
|
|
set_conversation
|
|
@message = @conversation.messages.build(
|
|
content: message_body,
|
|
account_id: @inbox.account_id,
|
|
inbox_id: @inbox.id,
|
|
message_type: :incoming,
|
|
sender: @contact,
|
|
source_id: params[:SmsSid]
|
|
)
|
|
attach_files
|
|
@message.save!
|
|
end
|
|
|
|
private
|
|
|
|
def twilio_channel
|
|
@twilio_channel ||= ::Channel::TwilioSms.find_by(messaging_service_sid: params[:MessagingServiceSid]) if params[:MessagingServiceSid].present?
|
|
if params[:AccountSid].present? && params[:To].present?
|
|
@twilio_channel ||= ::Channel::TwilioSms.find_by!(account_sid: params[:AccountSid],
|
|
phone_number: params[:To])
|
|
end
|
|
@twilio_channel
|
|
end
|
|
|
|
def inbox
|
|
@inbox ||= twilio_channel.inbox
|
|
end
|
|
|
|
def account
|
|
@account ||= inbox.account
|
|
end
|
|
|
|
def phone_number
|
|
twilio_channel.sms? ? params[:From] : params[:From].gsub('whatsapp:', '')
|
|
end
|
|
|
|
def formatted_phone_number
|
|
TelephoneNumber.parse(phone_number).international_number
|
|
end
|
|
|
|
def message_body
|
|
params[:Body]&.delete("\u0000")
|
|
end
|
|
|
|
def set_contact
|
|
contact_inbox = ::ContactInboxWithContactBuilder.new(
|
|
source_id: params[:From],
|
|
inbox: inbox,
|
|
contact_attributes: contact_attributes
|
|
).perform
|
|
|
|
@contact_inbox = contact_inbox
|
|
@contact = contact_inbox.contact
|
|
end
|
|
|
|
def conversation_params
|
|
{
|
|
account_id: @inbox.account_id,
|
|
inbox_id: @inbox.id,
|
|
contact_id: @contact.id,
|
|
contact_inbox_id: @contact_inbox.id,
|
|
additional_attributes: additional_attributes
|
|
}
|
|
end
|
|
|
|
def set_conversation
|
|
# if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved
|
|
@conversation = if @inbox.lock_to_single_conversation
|
|
@contact_inbox.conversations.last
|
|
else
|
|
@contact_inbox.conversations.where
|
|
.not(status: :resolved).last
|
|
end
|
|
return if @conversation
|
|
|
|
@conversation = ::Conversation.create!(conversation_params)
|
|
end
|
|
|
|
def contact_attributes
|
|
{
|
|
name: formatted_phone_number,
|
|
phone_number: phone_number,
|
|
additional_attributes: additional_attributes
|
|
}
|
|
end
|
|
|
|
def additional_attributes
|
|
if twilio_channel.sms?
|
|
{
|
|
from_zip_code: params[:FromZip],
|
|
from_country: params[:FromCountry],
|
|
from_state: params[:FromState]
|
|
}
|
|
else
|
|
{}
|
|
end
|
|
end
|
|
|
|
def attach_files
|
|
num_media = params[:NumMedia].to_i
|
|
return if num_media.zero?
|
|
|
|
num_media.times do |i|
|
|
media_url = params[:"MediaUrl#{i}"]
|
|
attach_single_file(media_url) if media_url.present?
|
|
end
|
|
end
|
|
|
|
def attach_single_file(media_url)
|
|
attachment_file = download_attachment_file(media_url)
|
|
return if attachment_file.blank?
|
|
|
|
@message.attachments.new(
|
|
account_id: @message.account_id,
|
|
file_type: file_type(attachment_file.content_type),
|
|
file: {
|
|
io: attachment_file,
|
|
filename: attachment_file.original_filename,
|
|
content_type: attachment_file.content_type
|
|
}
|
|
)
|
|
end
|
|
|
|
def download_attachment_file(media_url)
|
|
download_with_auth(media_url)
|
|
rescue Down::Error, Down::ClientError => e
|
|
handle_download_attachment_error(e, media_url)
|
|
end
|
|
|
|
def download_with_auth(media_url)
|
|
auth_credentials = if twilio_channel.api_key_sid.present?
|
|
# When using api_key_sid, the auth token should be the api_secret_key
|
|
[twilio_channel.api_key_sid, twilio_channel.auth_token]
|
|
else
|
|
# When using account_sid, the auth token is the account's auth token
|
|
[twilio_channel.account_sid, twilio_channel.auth_token]
|
|
end
|
|
|
|
Down.download(media_url, http_basic_authentication: auth_credentials)
|
|
end
|
|
|
|
def handle_download_attachment_error(error, media_url)
|
|
Rails.logger.info "Error downloading attachment from Twilio: #{error.message}: Retrying without auth"
|
|
Down.download(media_url)
|
|
rescue StandardError => e
|
|
Rails.logger.info "Error downloading attachment from Twilio: #{e.message}: Skipping"
|
|
nil
|
|
end
|
|
end
|