fix: Twitter inbox creation error (#1783)

fixes #1708

Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose 2021-02-16 19:35:10 +05:30 committed by GitHub
parent ac15f08995
commit 850041bc1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 62 deletions

View file

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

View 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

View file

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

View file

@ -1,24 +1,3 @@
class Twitter::BaseController < ApplicationController class Twitter::BaseController < ApplicationController
private include TwitterConcern
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 end

View file

@ -1,4 +1,6 @@
class Twitter::CallbacksController < Twitter::BaseController class Twitter::CallbacksController < Twitter::BaseController
include TwitterConcern
def show def show
return redirect_to twitter_app_redirect_url if permitted_params[:denied] return redirect_to twitter_app_redirect_url if permitted_params[:denied]

View 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();

View file

@ -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');
});
});

View file

@ -38,7 +38,8 @@
"PICK_A_VALUE": "Pick a value" "PICK_A_VALUE": "Pick a value"
}, },
"TWITTER": { "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": { "WEBSITE_CHANNEL": {
"TITLE": "Website channel", "TITLE": "Website channel",

View file

@ -1,27 +1,42 @@
<template> <template>
<div class="wizard-body columns content-box small-9"> <div class="wizard-body columns content-box small-9">
<div class="login-init full-height text-center"> <div class="login-init full-height text-center">
<form method="POST" action="/twitter/authorization"> <form @submit.prevent="requestAuthorization">
<input type="hidden" name="user_id" :value="currentUserID" />
<woot-submit-button <woot-submit-button
icon-class="ion-social-twitter" icon-class="ion-social-twitter"
button-text="Sign in with Twitter" button-text="Sign in with Twitter"
type="submit" type="submit"
:loading="isRequestingAuthorization"
/> />
</form> </form>
<p>{{ $t('INBOX_MGMT.ADD.TWITTER.HELP') }}</p> <p>{{ $t('INBOX_MGMT.ADD.TWITTER.HELP') }}</p>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import alertMixin from 'shared/mixins/alertMixin';
import twitterClient from '../../../../../api/channel/twitterClient';
export default { export default {
computed: { mixins: [alertMixin],
...mapGetters({ data() {
currentUserID: 'getCurrentUserID', 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> </script>

View file

@ -107,6 +107,10 @@ Rails.application.routes.draw do
end end
end end
namespace :twitter do
resource :authorization, only: [:create]
end
resources :webhooks, except: [:show] resources :webhooks, except: [:show]
namespace :integrations do namespace :integrations do
resources :apps, only: [:index, :show] resources :apps, only: [:index, :show]
@ -202,7 +206,6 @@ Rails.application.routes.draw do
post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events' post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events'
namespace :twitter do namespace :twitter do
resource :authorization, only: [:create]
resource :callback, only: [:show] resource :callback, only: [:show]
end end

View file

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