mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	Feat: authenticate direct upload (#4160)
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController | ||||
|   include EnsureCurrentAccountHelper | ||||
|   before_action :conversation | ||||
|  | ||||
|   private | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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, | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/controllers/api/v1/widget/direct_uploads_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/controllers/api/v1/widget/direct_uploads_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										28
									
								
								app/controllers/concerns/ensure_current_account_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/controllers/concerns/ensure_current_account_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										22
									
								
								app/controllers/concerns/website_token_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/controllers/concerns/website_token_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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) => { | ||||
|   | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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 | ||||
		Reference in New Issue
	
	Block a user
	 Tejaswini Chile
					Tejaswini Chile