diff --git a/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb b/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb new file mode 100644 index 000000000..f96a2921c --- /dev/null +++ b/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb @@ -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 diff --git a/app/controllers/concerns/twitter_concern.rb b/app/controllers/concerns/twitter_concern.rb new file mode 100644 index 000000000..e2c236d25 --- /dev/null +++ b/app/controllers/concerns/twitter_concern.rb @@ -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 diff --git a/app/controllers/twitter/authorizations_controller.rb b/app/controllers/twitter/authorizations_controller.rb deleted file mode 100644 index 765e026fe..000000000 --- a/app/controllers/twitter/authorizations_controller.rb +++ /dev/null @@ -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 diff --git a/app/controllers/twitter/base_controller.rb b/app/controllers/twitter/base_controller.rb index 353c1159c..9b4ba2a6a 100644 --- a/app/controllers/twitter/base_controller.rb +++ b/app/controllers/twitter/base_controller.rb @@ -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 diff --git a/app/controllers/twitter/callbacks_controller.rb b/app/controllers/twitter/callbacks_controller.rb index b3cafbe12..d484b0871 100644 --- a/app/controllers/twitter/callbacks_controller.rb +++ b/app/controllers/twitter/callbacks_controller.rb @@ -1,4 +1,6 @@ class Twitter::CallbacksController < Twitter::BaseController + include TwitterConcern + def show return redirect_to twitter_app_redirect_url if permitted_params[:denied] diff --git a/app/javascript/dashboard/api/channel/twitterClient.js b/app/javascript/dashboard/api/channel/twitterClient.js new file mode 100644 index 000000000..110cf6cde --- /dev/null +++ b/app/javascript/dashboard/api/channel/twitterClient.js @@ -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(); diff --git a/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js new file mode 100644 index 000000000..0480aeb82 --- /dev/null +++ b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js @@ -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'); + }); +}); diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 55ba4a0ff..16217e5ae 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -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", diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Twitter.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Twitter.vue index 31ee3f984..095657d54 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Twitter.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Twitter.vue @@ -1,27 +1,42 @@ - diff --git a/config/routes.rb b/config/routes.rb index b521bece0..d5e1162fa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/spec/controllers/api/v1/accounts/twitter/authorizations_controller_spec.rb b/spec/controllers/api/v1/accounts/twitter/authorizations_controller_spec.rb new file mode 100644 index 000000000..1a0772243 --- /dev/null +++ b/spec/controllers/api/v1/accounts/twitter/authorizations_controller_spec.rb @@ -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