diff --git a/app/controllers/api/v1/accounts/base_controller.rb b/app/controllers/api/v1/accounts/base_controller.rb index ddb0f44f4..e30effc59 100644 --- a/app/controllers/api/v1/accounts/base_controller.rb +++ b/app/controllers/api/v1/accounts/base_controller.rb @@ -1,32 +1,6 @@ class Api::V1::Accounts::BaseController < Api::BaseController include SwitchLocale + include EnsureCurrentAccountHelper before_action :current_account around_action :switch_locale_using_account_locale - - private - - def current_account - @current_account ||= ensure_current_account - Current.account = @current_account - end - - def ensure_current_account - account = Account.find(params[:account_id]) - if current_user - account_accessible_for_user?(account) - elsif @resource.is_a?(AgentBot) - account_accessible_for_bot?(account) - end - account - end - - def account_accessible_for_user?(account) - @current_account_user = account.account_users.find_by(user_id: current_user.id) - Current.account_user = @current_account_user - render_unauthorized('You are not authorized to access this account') unless @current_account_user - end - - def account_accessible_for_bot?(account) - render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id) - end end diff --git a/app/controllers/api/v1/accounts/conversations/base_controller.rb b/app/controllers/api/v1/accounts/conversations/base_controller.rb index f521719ae..b55b72013 100644 --- a/app/controllers/api/v1/accounts/conversations/base_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/base_controller.rb @@ -1,4 +1,5 @@ class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController + include EnsureCurrentAccountHelper before_action :conversation private diff --git a/app/controllers/api/v1/accounts/conversations/direct_uploads_controller.rb b/app/controllers/api/v1/accounts/conversations/direct_uploads_controller.rb new file mode 100644 index 000000000..f4ac05d6e --- /dev/null +++ b/app/controllers/api/v1/accounts/conversations/direct_uploads_controller.rb @@ -0,0 +1,17 @@ +class Api::V1::Accounts::Conversations::DirectUploadsController < ActiveStorage::DirectUploadsController + include EnsureCurrentAccountHelper + before_action :current_account + before_action :conversation + + def create + return if @conversation.nil? || @current_account.nil? + + super + end + + private + + def conversation + @conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id]) + end +end diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index 0a50835bc..8df4737db 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Widget::BaseController < ApplicationController include SwitchLocale + include WebsiteTokenHelper before_action :set_web_widget before_action :set_contact @@ -19,25 +20,6 @@ class Api::V1::Widget::BaseController < ApplicationController @conversation ||= conversations.last end - def auth_token_params - @auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token - end - - def set_web_widget - @web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token]) - @current_account = @web_widget.account - end - - def set_contact - @contact_inbox = @web_widget.inbox.contact_inboxes.find_by( - source_id: auth_token_params[:source_id] - ) - @contact = @contact_inbox&.contact - raise ActiveRecord::RecordNotFound unless @contact - - Current.contact = @contact - end - def create_conversation ::Conversation.create!(conversation_params) end @@ -96,10 +78,6 @@ class Api::V1::Widget::BaseController < ApplicationController { timestamp: permitted_params[:message][:timestamp] } end - def permitted_params - params.permit(:website_token) - end - def message_params { account_id: conversation.account_id, diff --git a/app/controllers/api/v1/widget/direct_uploads_controller.rb b/app/controllers/api/v1/widget/direct_uploads_controller.rb new file mode 100644 index 000000000..a6abdb3e1 --- /dev/null +++ b/app/controllers/api/v1/widget/direct_uploads_controller.rb @@ -0,0 +1,11 @@ +class Api::V1::Widget::DirectUploadsController < ActiveStorage::DirectUploadsController + include WebsiteTokenHelper + before_action :set_web_widget + before_action :set_contact + + def create + return if @contact.nil? || @current_account.nil? + + super + end +end diff --git a/app/controllers/concerns/ensure_current_account_helper.rb b/app/controllers/concerns/ensure_current_account_helper.rb new file mode 100644 index 000000000..dccc64350 --- /dev/null +++ b/app/controllers/concerns/ensure_current_account_helper.rb @@ -0,0 +1,28 @@ +module EnsureCurrentAccountHelper + private + + def current_account + @current_account ||= ensure_current_account + Current.account = @current_account + end + + def ensure_current_account + account = Account.find(params[:account_id]) + if current_user + account_accessible_for_user?(account) + elsif @resource.is_a?(AgentBot) + account_accessible_for_bot?(account) + end + account + end + + def account_accessible_for_user?(account) + @current_account_user = account.account_users.find_by(user_id: current_user.id) + Current.account_user = @current_account_user + render_unauthorized('You are not authorized to access this account') unless @current_account_user + end + + def account_accessible_for_bot?(account) + render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id) + end +end diff --git a/app/controllers/concerns/website_token_helper.rb b/app/controllers/concerns/website_token_helper.rb new file mode 100644 index 000000000..b59f70a17 --- /dev/null +++ b/app/controllers/concerns/website_token_helper.rb @@ -0,0 +1,22 @@ +module WebsiteTokenHelper + def auth_token_params + @auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token + end + + def set_web_widget + @web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token]) + @current_account = @web_widget.account + end + + def set_contact + @contact_inbox = @web_widget.inbox.contact_inboxes.find_by( + source_id: auth_token_params[:source_id] + ) + @contact = @contact_inbox&.contact + raise ActiveRecord::RecordNotFound unless @contact + end + + def permitted_params + params.permit(:website_token) + end +end diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 5a9253fba..597a6e1d0 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -645,10 +645,17 @@ export default { if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) { const upload = new DirectUpload( file.file, - '/rails/active_storage/direct_uploads', - null, - file.file.name + `/api/v1/accounts/${this.accountId}/conversations/${this.currentChat.id}/direct_uploads`, + { + directUploadWillCreateBlobWithXHR: xhr => { + xhr.setRequestHeader( + 'api_access_token', + this.currentUser.access_token + ); + }, + } ); + upload.create((error, blob) => { if (error) { this.showAlert(error); diff --git a/app/javascript/widget/components/ChatAttachment.vue b/app/javascript/widget/components/ChatAttachment.vue index ff37abefd..87da3f5df 100755 --- a/app/javascript/widget/components/ChatAttachment.vue +++ b/app/javascript/widget/components/ChatAttachment.vue @@ -3,7 +3,7 @@ :size="4096 * 2048" :accept="allowedFileTypes" :data="{ - direct_upload_url: '/rails/active_storage/direct_uploads', + direct_upload_url: '/api/v1/widget/direct_uploads', direct_upload: true, }" @input-file="onFileUpload" @@ -66,11 +66,15 @@ export default { this.isUploading = true; try { if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) { + const { websiteToken } = window.chatwootWebChannel; const upload = new DirectUpload( file.file, - '/rails/active_storage/direct_uploads', - null, - file.file.name + `/api/v1/widget/direct_uploads?website_token=${websiteToken}`, + { + directUploadWillCreateBlobWithXHR: xhr => { + xhr.setRequestHeader('X-Auth-Token', window.authToken); + }, + } ); upload.create((error, blob) => { diff --git a/config/routes.rb b/config/routes.rb index 366552c61..6e4296e62 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,6 +71,7 @@ Rails.application.routes.draw do resources :messages, only: [:index, :create, :destroy] resources :assignments, only: [:create] resources :labels, only: [:create, :index] + resource :direct_uploads, only: [:create] end member do post :mute @@ -179,6 +180,7 @@ Rails.application.routes.draw do resource :notification_subscriptions, only: [:create, :destroy] namespace :widget do + resource :direct_uploads, only: [:create] resource :config, only: [:create] resources :campaigns, only: [:index] resources :events, only: [:create] diff --git a/spec/controllers/api/v1/accounts/conversations/direct_uploads_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations/direct_uploads_controller_spec.rb new file mode 100644 index 000000000..c2518c1bd --- /dev/null +++ b/spec/controllers/api/v1/accounts/conversations/direct_uploads_controller_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe '/api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads', type: :request do + let(:account) { create(:account) } + let(:web_widget) { create(:channel_widget, account: account) } + let(:agent) { create(:user, account: account, role: :agent) } + let(:contact) { create(:contact, account: account, email: nil) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: web_widget.inbox) } + let(:conversation) { create(:conversation, contact: contact, account: account, inbox: web_widget.inbox, contact_inbox: contact_inbox) } + + describe 'POST /api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads' do + context 'when post request is made' do + it 'creates attachment message in conversation' do + contact + + post api_v1_account_conversation_direct_uploads_path(account_id: account.id, conversation_id: conversation.display_id), + params: { + blob: { + filename: 'avatar.png', + byte_size: '1234', + checksum: 'dsjbsdhbfif3874823mnsdbf', + content_type: 'image/png' + } + }, + headers: { api_access_token: agent.access_token.token }, + as: :json + + expect(response).to have_http_status(:success) + json_response = JSON.parse(response.body) + expect(json_response['content_type']).to eq('image/png') + end + end + end +end diff --git a/spec/controllers/api/v1/widget/direct_uploads_controller_spec.rb b/spec/controllers/api/v1/widget/direct_uploads_controller_spec.rb new file mode 100644 index 000000000..0db922370 --- /dev/null +++ b/spec/controllers/api/v1/widget/direct_uploads_controller_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe '/api/v1/widget/direct_uploads', type: :request do + let(:account) { create(:account) } + let(:web_widget) { create(:channel_widget, account: account) } + let(:contact) { create(:contact, account: account, email: nil) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: web_widget.inbox) } + let(:conversation) { create(:conversation, contact: contact, account: account, inbox: web_widget.inbox, contact_inbox: contact_inbox) } + let(:payload) { { source_id: contact_inbox.source_id, inbox_id: web_widget.inbox.id } } + let(:token) { ::Widget::TokenService.new(payload: payload).generate_token } + + describe 'POST /api/v1/widget/direct_uploads' do + context 'when post request is made' do + before do + token + contact + payload + end + + it 'creates attachment message in conversation' do + post api_v1_widget_direct_uploads_url, + params: { + website_token: web_widget.website_token, + blob: { + filename: 'avatar.png', + byte_size: '1234', + checksum: 'dsjbsdhbfif3874823mnsdbf', + content_type: 'image/png' + } + }, + headers: { 'X-Auth-Token' => token } + + expect(response).to have_http_status(:success) + json_response = JSON.parse(response.body) + expect(json_response['content_type']).to eq('image/png') + end + end + end +end