mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	chore: Security Improvements to the API (#2893)
- Devise auth tokens are reset on password update - Avatar attachment file type is limited to jpeg,gif and png - Avatar attachment file size is limited to 15 mb - Widget Message attachments are limited to types ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/tiff', 'application/pdf', 'audio/mpeg', 'video/mp4', 'audio/ogg', 'text/csv'] - Widget Message attachments are limited to 40Mb size limit.
This commit is contained in:
		@@ -15,21 +15,25 @@ class Messages::MessageBuilder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    @message = @conversation.messages.build(message_params)
 | 
					    @message = @conversation.messages.build(message_params)
 | 
				
			||||||
    if @attachments.present?
 | 
					    process_attachments
 | 
				
			||||||
      @attachments.each do |uploaded_attachment|
 | 
					    @message.save!
 | 
				
			||||||
        attachment = @message.attachments.new(
 | 
					 | 
				
			||||||
          account_id: @message.account_id,
 | 
					 | 
				
			||||||
          file_type: file_type(uploaded_attachment&.content_type)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        attachment.file.attach(uploaded_attachment)
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
    @message.save
 | 
					 | 
				
			||||||
    @message
 | 
					    @message
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def process_attachments
 | 
				
			||||||
 | 
					    return if @attachments.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @attachments.each do |uploaded_attachment|
 | 
				
			||||||
 | 
					      @message.attachments.build(
 | 
				
			||||||
 | 
					        account_id: @message.account_id,
 | 
				
			||||||
 | 
					        file_type: file_type(uploaded_attachment&.content_type),
 | 
				
			||||||
 | 
					        file: uploaded_attachment
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def message_type
 | 
					  def message_type
 | 
				
			||||||
    if @conversation.inbox.channel_type != 'Channel::Api' && @message_type == 'incoming'
 | 
					    if @conversation.inbox.channel_type != 'Channel::Api' && @message_type == 'incoming'
 | 
				
			||||||
      raise StandardError, 'Incoming messages are only allowed in Api inboxes'
 | 
					      raise StandardError, 'Incoming messages are only allowed in Api inboxes'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def create
 | 
					  def create
 | 
				
			||||||
    @message = conversation.messages.new(message_params)
 | 
					    @message = conversation.messages.new(message_params)
 | 
				
			||||||
    @message.save
 | 
					 | 
				
			||||||
    build_attachment
 | 
					    build_attachment
 | 
				
			||||||
 | 
					    @message.save!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def update
 | 
					  def update
 | 
				
			||||||
@@ -29,13 +29,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
 | 
				
			|||||||
    return if params[:message][:attachments].blank?
 | 
					    return if params[:message][:attachments].blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    params[:message][:attachments].each do |uploaded_attachment|
 | 
					    params[:message][:attachments].each do |uploaded_attachment|
 | 
				
			||||||
      attachment = @message.attachments.new(
 | 
					      @message.attachments.new(
 | 
				
			||||||
        account_id: @message.account_id,
 | 
					        account_id: @message.account_id,
 | 
				
			||||||
        file_type: helpers.file_type(uploaded_attachment&.content_type)
 | 
					        file_type: helpers.file_type(uploaded_attachment&.content_type),
 | 
				
			||||||
 | 
					        file: uploaded_attachment
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      attachment.file.attach(uploaded_attachment)
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    @message.save!
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def set_conversation
 | 
					  def set_conversation
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def create
 | 
					  def create
 | 
				
			||||||
    @message = @conversation.messages.new(message_params)
 | 
					    @message = @conversation.messages.new(message_params)
 | 
				
			||||||
    @message.save
 | 
					 | 
				
			||||||
    build_attachment
 | 
					    build_attachment
 | 
				
			||||||
 | 
					    @message.save!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def update
 | 
					  def update
 | 
				
			||||||
@@ -23,13 +23,12 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
 | 
				
			|||||||
    return if params[:attachments].blank?
 | 
					    return if params[:attachments].blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    params[:attachments].each do |uploaded_attachment|
 | 
					    params[:attachments].each do |uploaded_attachment|
 | 
				
			||||||
      attachment = @message.attachments.new(
 | 
					      @message.attachments.new(
 | 
				
			||||||
        account_id: @message.account_id,
 | 
					        account_id: @message.account_id,
 | 
				
			||||||
        file_type: helpers.file_type(uploaded_attachment&.content_type)
 | 
					        file_type: helpers.file_type(uploaded_attachment&.content_type),
 | 
				
			||||||
 | 
					        file: uploaded_attachment
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      attachment.file.attach(uploaded_attachment)
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    @message.save!
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def message_finder_params
 | 
					  def message_finder_params
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,12 @@ module FileTypeHelper
 | 
				
			|||||||
    return :image if [
 | 
					    return :image if [
 | 
				
			||||||
      'image/jpeg',
 | 
					      'image/jpeg',
 | 
				
			||||||
      'image/png',
 | 
					      'image/png',
 | 
				
			||||||
      'image/svg+xml',
 | 
					 | 
				
			||||||
      'image/gif',
 | 
					      'image/gif',
 | 
				
			||||||
      'image/tiff',
 | 
					      'image/tiff',
 | 
				
			||||||
      'image/bmp'
 | 
					      'image/bmp'
 | 
				
			||||||
    ].include?(content_type)
 | 
					    ].include?(content_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return :video if content_type.include?('video/')
 | 
				
			||||||
    return :audio if content_type.include?('audio/')
 | 
					    return :audio if content_type.include?('audio/')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :file
 | 
					    :file
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,10 +11,11 @@
 | 
				
			|||||||
        @click="toggleEmojiPicker"
 | 
					        @click="toggleEmojiPicker"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <!-- ensure the same validations for attachment types are implemented in  backend models as well -->
 | 
				
			||||||
      <file-upload
 | 
					      <file-upload
 | 
				
			||||||
        ref="upload"
 | 
					        ref="upload"
 | 
				
			||||||
        :size="4096 * 4096"
 | 
					        :size="4096 * 4096"
 | 
				
			||||||
        accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
 | 
					        accept="image/png, image/jpeg, image/gif, image/bmp, image/tiff, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
 | 
				
			||||||
        :drop="true"
 | 
					        :drop="true"
 | 
				
			||||||
        :drop-directory="false"
 | 
					        :drop-directory="false"
 | 
				
			||||||
        @input-file="onFileUpload"
 | 
					        @input-file="onFileUpload"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,12 +20,13 @@
 | 
				
			|||||||
        id="file"
 | 
					        id="file"
 | 
				
			||||||
        ref="file"
 | 
					        ref="file"
 | 
				
			||||||
        type="file"
 | 
					        type="file"
 | 
				
			||||||
        accept="image/*"
 | 
					        accept="image/png, image/jpeg, image/gif"
 | 
				
			||||||
        @change="handleImageUpload"
 | 
					        @change="handleImageUpload"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <slot></slot>
 | 
					      <slot></slot>
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ class Attachment < ApplicationRecord
 | 
				
			|||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :message
 | 
					  belongs_to :message
 | 
				
			||||||
  has_one_attached :file
 | 
					  has_one_attached :file
 | 
				
			||||||
 | 
					  validate :acceptable_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  enum file_type: [:image, :audio, :video, :file, :location, :fallback]
 | 
					  enum file_type: [:image, :audio, :video, :file, :location, :fallback]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,4 +77,22 @@ class Attachment < ApplicationRecord
 | 
				
			|||||||
      account_id: account_id
 | 
					      account_id: account_id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def should_validate_file?
 | 
				
			||||||
 | 
					    return unless file.attached?
 | 
				
			||||||
 | 
					    # we are only limiting attachment types in case of website widget
 | 
				
			||||||
 | 
					    return unless message.inbox.channel_type == 'Channel::WebWidget'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def acceptable_file
 | 
				
			||||||
 | 
					    should_validate_file?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    errors.add(:file, 'is too big') if file.byte_size > 40.megabytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acceptable_types = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/tiff', 'application/pdf', 'audio/mpeg', 'video/mp4', 'audio/ogg',
 | 
				
			||||||
 | 
					                        'text/csv'].freeze
 | 
				
			||||||
 | 
					    errors.add(:file, 'filetype not supported') unless acceptable_types.include?(file.content_type)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ module Avatarable
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  included do
 | 
					  included do
 | 
				
			||||||
    has_one_attached :avatar
 | 
					    has_one_attached :avatar
 | 
				
			||||||
 | 
					    validate :acceptable_avatar
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def avatar_url
 | 
					  def avatar_url
 | 
				
			||||||
@@ -18,4 +19,13 @@ module Avatarable
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    ''
 | 
					    ''
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def acceptable_avatar
 | 
				
			||||||
 | 
					    return unless avatar.attached?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    errors.add(:avatar, 'is too big') if avatar.byte_size > 15.megabytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acceptable_types = ['image/jpeg', 'image/png', 'image/gif'].freeze
 | 
				
			||||||
 | 
					    errors.add(:avatar, 'filetype not supported') unless acceptable_types.include?(avatar.content_type)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,10 @@ DeviseTokenAuth.setup do |config|
 | 
				
			|||||||
  # determines how long tokens will remain valid after they are issued.
 | 
					  # determines how long tokens will remain valid after they are issued.
 | 
				
			||||||
  config.token_lifespan = 2.months
 | 
					  config.token_lifespan = 2.months
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # By default, old tokens are not invalidated when password is changed.
 | 
				
			||||||
 | 
					  # Enable this option if you want to make passwords updates to logout other devices.
 | 
				
			||||||
 | 
					  config.remove_tokens_after_password_reset = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Sets the max number of concurrent devices per user, which is 10 by default.
 | 
					  # Sets the max number of concurrent devices per user, which is 10 by default.
 | 
				
			||||||
  # After this limit is reached, the oldest tokens will be removed.
 | 
					  # After this limit is reached, the oldest tokens will be removed.
 | 
				
			||||||
  # config.max_number_of_devices = 10
 | 
					  # config.max_number_of_devices = 10
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user