Feat: authenticate direct upload (#4160)

This commit is contained in:
Tejaswini Chile 2022-03-16 13:54:18 +05:30 committed by GitHub
parent 796a7805db
commit 207a03155e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 174 additions and 57 deletions

View file

@ -1,32 +1,6 @@
class Api::V1::Accounts::BaseController < Api::BaseController class Api::V1::Accounts::BaseController < Api::BaseController
include SwitchLocale include SwitchLocale
include EnsureCurrentAccountHelper
before_action :current_account before_action :current_account
around_action :switch_locale_using_account_locale 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 end

View file

@ -1,4 +1,5 @@
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
include EnsureCurrentAccountHelper
before_action :conversation before_action :conversation
private private

View file

@ -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

View file

@ -1,5 +1,6 @@
class Api::V1::Widget::BaseController < ApplicationController class Api::V1::Widget::BaseController < ApplicationController
include SwitchLocale include SwitchLocale
include WebsiteTokenHelper
before_action :set_web_widget before_action :set_web_widget
before_action :set_contact before_action :set_contact
@ -19,25 +20,6 @@ class Api::V1::Widget::BaseController < ApplicationController
@conversation ||= conversations.last @conversation ||= conversations.last
end 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 def create_conversation
::Conversation.create!(conversation_params) ::Conversation.create!(conversation_params)
end end
@ -96,10 +78,6 @@ class Api::V1::Widget::BaseController < ApplicationController
{ timestamp: permitted_params[:message][:timestamp] } { timestamp: permitted_params[:message][:timestamp] }
end end
def permitted_params
params.permit(:website_token)
end
def message_params def message_params
{ {
account_id: conversation.account_id, account_id: conversation.account_id,

View 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

View 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

View 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

View file

@ -645,10 +645,17 @@ export default {
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) { if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
const upload = new DirectUpload( const upload = new DirectUpload(
file.file, file.file,
'/rails/active_storage/direct_uploads', `/api/v1/accounts/${this.accountId}/conversations/${this.currentChat.id}/direct_uploads`,
null, {
file.file.name directUploadWillCreateBlobWithXHR: xhr => {
xhr.setRequestHeader(
'api_access_token',
this.currentUser.access_token
);
},
}
); );
upload.create((error, blob) => { upload.create((error, blob) => {
if (error) { if (error) {
this.showAlert(error); this.showAlert(error);

View file

@ -3,7 +3,7 @@
:size="4096 * 2048" :size="4096 * 2048"
:accept="allowedFileTypes" :accept="allowedFileTypes"
:data="{ :data="{
direct_upload_url: '/rails/active_storage/direct_uploads', direct_upload_url: '/api/v1/widget/direct_uploads',
direct_upload: true, direct_upload: true,
}" }"
@input-file="onFileUpload" @input-file="onFileUpload"
@ -66,11 +66,15 @@ export default {
this.isUploading = true; this.isUploading = true;
try { try {
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) { if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
const { websiteToken } = window.chatwootWebChannel;
const upload = new DirectUpload( const upload = new DirectUpload(
file.file, file.file,
'/rails/active_storage/direct_uploads', `/api/v1/widget/direct_uploads?website_token=${websiteToken}`,
null, {
file.file.name directUploadWillCreateBlobWithXHR: xhr => {
xhr.setRequestHeader('X-Auth-Token', window.authToken);
},
}
); );
upload.create((error, blob) => { upload.create((error, blob) => {

View file

@ -71,6 +71,7 @@ Rails.application.routes.draw do
resources :messages, only: [:index, :create, :destroy] resources :messages, only: [:index, :create, :destroy]
resources :assignments, only: [:create] resources :assignments, only: [:create]
resources :labels, only: [:create, :index] resources :labels, only: [:create, :index]
resource :direct_uploads, only: [:create]
end end
member do member do
post :mute post :mute
@ -179,6 +180,7 @@ Rails.application.routes.draw do
resource :notification_subscriptions, only: [:create, :destroy] resource :notification_subscriptions, only: [:create, :destroy]
namespace :widget do namespace :widget do
resource :direct_uploads, only: [:create]
resource :config, only: [:create] resource :config, only: [:create]
resources :campaigns, only: [:index] resources :campaigns, only: [:index]
resources :events, only: [:create] resources :events, only: [:create]

View file

@ -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

View file

@ -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