mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	fix: Twitter inbox creation error (#1783)
fixes #1708 Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
		| @@ -0,0 +1,29 @@ | ||||
| class Api::V1::Accounts::Twitter::AuthorizationsController < Api::V1::Accounts::BaseController | ||||
|   include TwitterConcern | ||||
|  | ||||
|   before_action :check_authorization | ||||
|  | ||||
|   def create | ||||
|     @response = twitter_client.request_oauth_token(url: twitter_callback_url) | ||||
|     if @response.status == '200' | ||||
|       ::Redis::Alfred.setex(oauth_token, Current.account.id) | ||||
|       render json: { success: true, url: oauth_authorize_endpoint(oauth_token) } | ||||
|     else | ||||
|       render json: { success: false }, status: :unprocessable_entity | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def oauth_token | ||||
|     parsed_body['oauth_token'] | ||||
|   end | ||||
|  | ||||
|   def oauth_authorize_endpoint(oauth_token) | ||||
|     "#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}" | ||||
|   end | ||||
|  | ||||
|   def check_authorization | ||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? | ||||
|   end | ||||
| end | ||||
							
								
								
									
										26
									
								
								app/controllers/concerns/twitter_concern.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/controllers/concerns/twitter_concern.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| module TwitterConcern | ||||
|   extend ActiveSupport::Concern | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def parsed_body | ||||
|     @parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body) | ||||
|   end | ||||
|  | ||||
|   def host | ||||
|     ENV.fetch('FRONTEND_URL', '') | ||||
|   end | ||||
|  | ||||
|   def twitter_client | ||||
|     Twitty::Facade.new do |config| | ||||
|       config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil) | ||||
|       config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil) | ||||
|       config.base_url = twitter_api_base_url | ||||
|       config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '') | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def twitter_api_base_url | ||||
|     'https://api.twitter.com' | ||||
|   end | ||||
| end | ||||
| @@ -1,30 +0,0 @@ | ||||
| class Twitter::AuthorizationsController < Twitter::BaseController | ||||
|   def create | ||||
|     @response = twitter_client.request_oauth_token(url: twitter_callback_url) | ||||
|  | ||||
|     if @response.status == '200' | ||||
|       ::Redis::Alfred.setex(oauth_token, account.id) | ||||
|       redirect_to oauth_authorize_endpoint(oauth_token) | ||||
|     else | ||||
|       redirect_to app_new_twitter_inbox_url(account_id: account.id) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def oauth_token | ||||
|     parsed_body['oauth_token'] | ||||
|   end | ||||
|  | ||||
|   def user | ||||
|     @user ||= User.find_by(id: params[:user_id]) | ||||
|   end | ||||
|  | ||||
|   def account | ||||
|     @account ||= user.account | ||||
|   end | ||||
|  | ||||
|   def oauth_authorize_endpoint(oauth_token) | ||||
|     "#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}" | ||||
|   end | ||||
| end | ||||
| @@ -1,24 +1,3 @@ | ||||
| class Twitter::BaseController < ApplicationController | ||||
|   private | ||||
|  | ||||
|   def parsed_body | ||||
|     @parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body) | ||||
|   end | ||||
|  | ||||
|   def host | ||||
|     ENV.fetch('FRONTEND_URL', '') | ||||
|   end | ||||
|  | ||||
|   def twitter_client | ||||
|     Twitty::Facade.new do |config| | ||||
|       config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil) | ||||
|       config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil) | ||||
|       config.base_url = twitter_api_base_url | ||||
|       config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '') | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def twitter_api_base_url | ||||
|     'https://api.twitter.com' | ||||
|   end | ||||
|   include TwitterConcern | ||||
| end | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| class Twitter::CallbacksController < Twitter::BaseController | ||||
|   include TwitterConcern | ||||
|  | ||||
|   def show | ||||
|     return redirect_to twitter_app_redirect_url if permitted_params[:denied] | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								app/javascript/dashboard/api/channel/twitterClient.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/javascript/dashboard/api/channel/twitterClient.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| /* global axios */ | ||||
| import ApiClient from '../ApiClient'; | ||||
|  | ||||
| class TwitterClient extends ApiClient { | ||||
|   constructor() { | ||||
|     super('twitter', { accountScoped: true }); | ||||
|   } | ||||
|  | ||||
|   generateAuthorization() { | ||||
|     return axios.post(`${this.url}/authorization`); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default new TwitterClient(); | ||||
| @@ -0,0 +1,9 @@ | ||||
| import TwitterClient from '../../channel/twitterClient'; | ||||
| import ApiClient from '../../ApiClient'; | ||||
|  | ||||
| describe('#TwitterClient', () => { | ||||
|   it('creates correct instance', () => { | ||||
|     expect(TwitterClient).toBeInstanceOf(ApiClient); | ||||
|     expect(TwitterClient).toHaveProperty('generateAuthorization'); | ||||
|   }); | ||||
| }); | ||||
| @@ -38,7 +38,8 @@ | ||||
|         "PICK_A_VALUE": "Pick a value" | ||||
|       }, | ||||
|       "TWITTER": { | ||||
|         "HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' " | ||||
|         "HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' ", | ||||
|         "ERROR_MESSAGE": "There was an error connecting to Twitter, please try again" | ||||
|       }, | ||||
|       "WEBSITE_CHANNEL": { | ||||
|         "TITLE": "Website channel", | ||||
|   | ||||
| @@ -1,27 +1,42 @@ | ||||
| <template> | ||||
|   <div class="wizard-body columns content-box small-9"> | ||||
|     <div class="login-init full-height text-center"> | ||||
|       <form method="POST" action="/twitter/authorization"> | ||||
|         <input type="hidden" name="user_id" :value="currentUserID" /> | ||||
|       <form @submit.prevent="requestAuthorization"> | ||||
|         <woot-submit-button | ||||
|           icon-class="ion-social-twitter" | ||||
|           button-text="Sign in with Twitter" | ||||
|           type="submit" | ||||
|           :loading="isRequestingAuthorization" | ||||
|         /> | ||||
|       </form> | ||||
|       <p>{{ $t('INBOX_MGMT.ADD.TWITTER.HELP') }}</p> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from 'vuex'; | ||||
| import alertMixin from 'shared/mixins/alertMixin'; | ||||
| import twitterClient from '../../../../../api/channel/twitterClient'; | ||||
|  | ||||
| export default { | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       currentUserID: 'getCurrentUserID', | ||||
|     }), | ||||
|   mixins: [alertMixin], | ||||
|   data() { | ||||
|     return { isRequestingAuthorization: false }; | ||||
|   }, | ||||
|   methods: { | ||||
|     async requestAuthorization() { | ||||
|       try { | ||||
|         this.isRequestingAuthorization = true; | ||||
|         const response = await twitterClient.generateAuthorization(); | ||||
|         const { | ||||
|           data: { url }, | ||||
|         } = response; | ||||
|         window.location.href = url; | ||||
|       } catch (error) { | ||||
|         this.showAlert(this.$t('INBOX_MGMT.ADD.TWITTER.ERROR_MESSAGE')); | ||||
|       } finally { | ||||
|         this.isRequestingAuthorization = false; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -107,6 +107,10 @@ Rails.application.routes.draw do | ||||
|             end | ||||
|           end | ||||
|  | ||||
|           namespace :twitter do | ||||
|             resource :authorization, only: [:create] | ||||
|           end | ||||
|  | ||||
|           resources :webhooks, except: [:show] | ||||
|           namespace :integrations do | ||||
|             resources :apps, only: [:index, :show] | ||||
| @@ -202,7 +206,6 @@ Rails.application.routes.draw do | ||||
|   post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events' | ||||
|  | ||||
|   namespace :twitter do | ||||
|     resource :authorization, only: [:create] | ||||
|     resource :callback, only: [:show] | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,46 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe 'Twitter Authorization API', type: :request do | ||||
|   let(:account) { create(:account) } | ||||
|  | ||||
|   describe 'POST /api/v1/accounts/{account.id}/twitter/authorization' do | ||||
|     context 'when it is an unauthenticated user' do | ||||
|       it 'returns unauthorized' do | ||||
|         post "/api/v1/accounts/#{account.id}/twitter/authorization" | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) { create(:user, account: account, role: :agent) } | ||||
|       let(:administrator) { create(:user, account: account, role: :administrator) } | ||||
|       let(:twitter_client) { double } | ||||
|       let(:twitter_response) { double } | ||||
|       let(:raw_response) { double } | ||||
|  | ||||
|       it 'returns unathorized for agent' do | ||||
|         post "/api/v1/accounts/#{account.id}/twitter/authorization", | ||||
|              headers: agent.create_new_auth_token, | ||||
|              as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|  | ||||
|       it 'creates a new authorization and returns the redirect url' do | ||||
|         allow(Twitty::Facade).to receive(:new).and_return(twitter_client) | ||||
|         allow(twitter_client).to receive(:request_oauth_token).and_return(twitter_response) | ||||
|         allow(twitter_response).to receive(:status).and_return('200') | ||||
|         allow(twitter_response).to receive(:raw_response).and_return(raw_response) | ||||
|         allow(raw_response).to receive(:body).and_return('oauth_token=test_token') | ||||
|  | ||||
|         post "/api/v1/accounts/#{account.id}/twitter/authorization", | ||||
|              headers: administrator.create_new_auth_token, | ||||
|              as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(JSON.parse(response.body)['url']).to include('test_token') | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose