fix: Twitter inbox creation error (#1783)
fixes #1708 Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
parent
ac15f08995
commit
850041bc1d
11 changed files with 156 additions and 62 deletions
|
@ -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
|
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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
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"
|
"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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue