mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	refactor: use state-based authentication (#11690)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		| @@ -1,32 +1,23 @@ | |||||||
| class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController | class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController | ||||||
|   include GoogleConcern |   include GoogleConcern | ||||||
|   before_action :check_authorization |  | ||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     email = params[:authorization][:email] |  | ||||||
|     redirect_url = google_client.auth_code.authorize_url( |     redirect_url = google_client.auth_code.authorize_url( | ||||||
|       { |       { | ||||||
|         redirect_uri: "#{base_url}/google/callback", |         redirect_uri: "#{base_url}/google/callback", | ||||||
|         scope: 'email profile https://mail.google.com/', |         scope: scope, | ||||||
|         response_type: 'code', |         response_type: 'code', | ||||||
|         prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it |         prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it | ||||||
|         access_type: 'offline', # the default is 'online' |         access_type: 'offline', # the default is 'online' | ||||||
|  |         state: state, | ||||||
|         client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil) |         client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil) | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     if redirect_url |     if redirect_url | ||||||
|       cache_key = "google::#{email.downcase}" |  | ||||||
|       ::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes) |  | ||||||
|       render json: { success: true, url: redirect_url } |       render json: { success: true, url: redirect_url } | ||||||
|     else |     else | ||||||
|       render json: { success: false }, status: :unprocessable_entity |       render json: { success: false }, status: :unprocessable_entity | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   private |  | ||||||
|  |  | ||||||
|   def check_authorization |  | ||||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::BaseController | class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController | ||||||
|   include InstagramConcern |   include InstagramConcern | ||||||
|   include Instagram::IntegrationHelper |   include Instagram::IntegrationHelper | ||||||
|   before_action :check_authorization |  | ||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     # https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#step-1--get-authorization |     # https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#step-1--get-authorization | ||||||
| @@ -21,10 +20,4 @@ class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts | |||||||
|       render json: { success: false }, status: :unprocessable_entity |       render json: { success: false }, status: :unprocessable_entity | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   private |  | ||||||
|  |  | ||||||
|   def check_authorization |  | ||||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,28 +1,19 @@ | |||||||
| class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::BaseController | class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController | ||||||
|   include MicrosoftConcern |   include MicrosoftConcern | ||||||
|   before_action :check_authorization |  | ||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     email = params[:authorization][:email] |  | ||||||
|     redirect_url = microsoft_client.auth_code.authorize_url( |     redirect_url = microsoft_client.auth_code.authorize_url( | ||||||
|       { |       { | ||||||
|         redirect_uri: "#{base_url}/microsoft/callback", |         redirect_uri: "#{base_url}/microsoft/callback", | ||||||
|         scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile', |         scope: scope, | ||||||
|  |         state: state, | ||||||
|         prompt: 'consent' |         prompt: 'consent' | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|     if redirect_url |     if redirect_url | ||||||
|       cache_key = "microsoft::#{email.downcase}" |  | ||||||
|       ::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes) |  | ||||||
|       render json: { success: true, url: redirect_url } |       render json: { success: true, url: redirect_url } | ||||||
|     else |     else | ||||||
|       render json: { success: false }, status: :unprocessable_entity |       render json: { success: false }, status: :unprocessable_entity | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   private |  | ||||||
|  |  | ||||||
|   def check_authorization |  | ||||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | class Api::V1::Accounts::OauthAuthorizationController < Api::V1::Accounts::BaseController | ||||||
|  |   before_action :check_authorization | ||||||
|  |  | ||||||
|  |   protected | ||||||
|  |  | ||||||
|  |   def scope | ||||||
|  |     '' | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def state | ||||||
|  |     Current.account.to_sgid(expires_in: 15.minutes).to_s | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def base_url | ||||||
|  |     ENV.fetch('FRONTEND_URL', 'http://localhost:3000') | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def check_authorization | ||||||
|  |     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -14,7 +14,7 @@ module GoogleConcern | |||||||
|  |  | ||||||
|   private |   private | ||||||
|  |  | ||||||
|   def base_url |   def scope | ||||||
|     ENV.fetch('FRONTEND_URL', 'http://localhost:3000') |     'email profile https://mail.google.com/' | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ module MicrosoftConcern | |||||||
|  |  | ||||||
|   private |   private | ||||||
|  |  | ||||||
|   def base_url |   def scope | ||||||
|     ENV.fetch('FRONTEND_URL', 'http://localhost:3000') |     'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile email' | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ class OauthCallbackController < ApplicationController | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     handle_response |     handle_response | ||||||
|     ::Redis::Alfred.delete(cache_key) |  | ||||||
|   rescue StandardError => e |   rescue StandardError => e | ||||||
|     ChatwootExceptionTracker.new(e).capture_exception |     ChatwootExceptionTracker.new(e).capture_exception | ||||||
|     redirect_to '/' |     redirect_to '/' | ||||||
| @@ -64,10 +63,6 @@ class OauthCallbackController < ApplicationController | |||||||
|     raise NotImplementedError |     raise NotImplementedError | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def cache_key |  | ||||||
|     "#{provider_name}::#{users_data['email'].downcase}" |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def create_channel_with_inbox |   def create_channel_with_inbox | ||||||
|     ActiveRecord::Base.transaction do |     ActiveRecord::Base.transaction do | ||||||
|       channel_email = Channel::Email.create!(email: users_data['email'], account: account) |       channel_email = Channel::Email.create!(email: users_data['email'], account: account) | ||||||
| @@ -86,12 +81,17 @@ class OauthCallbackController < ApplicationController | |||||||
|     decoded_token[0] |     decoded_token[0] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def account_id |   def account_from_signed_id | ||||||
|     ::Redis::Alfred.get(cache_key) |     raise ActionController::BadRequest, 'Missing state variable' if params[:state].blank? | ||||||
|  |  | ||||||
|  |     account = GlobalID::Locator.locate_signed(params[:state]) | ||||||
|  |     raise 'Invalid or expired state' if account.nil? | ||||||
|  |  | ||||||
|  |     account | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def account |   def account | ||||||
|     @account ||= Account.find(account_id) |     @account ||= account_from_signed_id | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   # Fallback name, for when name field is missing from users_data |   # Fallback name, for when name field is missing from users_data | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ defineOptions({ | |||||||
|     provider="google" |     provider="google" | ||||||
|     :title="$t('INBOX_MGMT.ADD.GOOGLE.TITLE')" |     :title="$t('INBOX_MGMT.ADD.GOOGLE.TITLE')" | ||||||
|     :description="$t('INBOX_MGMT.ADD.GOOGLE.DESCRIPTION')" |     :description="$t('INBOX_MGMT.ADD.GOOGLE.DESCRIPTION')" | ||||||
|     :input-placeholder="$t('INBOX_MGMT.ADD.GOOGLE.EMAIL_PLACEHOLDER')" |  | ||||||
|     :submit-button-text="$t('INBOX_MGMT.ADD.GOOGLE.SIGN_IN')" |     :submit-button-text="$t('INBOX_MGMT.ADD.GOOGLE.SIGN_IN')" | ||||||
|     :error-message="$t('INBOX_MGMT.ADD.GOOGLE.ERROR_MESSAGE')" |     :error-message="$t('INBOX_MGMT.ADD.GOOGLE.ERROR_MESSAGE')" | ||||||
|   /> |   /> | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ defineOptions({ | |||||||
|     provider="microsoft" |     provider="microsoft" | ||||||
|     :title="$t('INBOX_MGMT.ADD.MICROSOFT.TITLE')" |     :title="$t('INBOX_MGMT.ADD.MICROSOFT.TITLE')" | ||||||
|     :description="$t('INBOX_MGMT.ADD.MICROSOFT.DESCRIPTION')" |     :description="$t('INBOX_MGMT.ADD.MICROSOFT.DESCRIPTION')" | ||||||
|     :input-placeholder="$t('INBOX_MGMT.ADD.MICROSOFT.EMAIL_PLACEHOLDER')" |  | ||||||
|     :submit-button-text="$t('INBOX_MGMT.ADD.MICROSOFT.SIGN_IN')" |     :submit-button-text="$t('INBOX_MGMT.ADD.MICROSOFT.SIGN_IN')" | ||||||
|     :error-message="$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE')" |     :error-message="$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE')" | ||||||
|   /> |   /> | ||||||
|   | |||||||
| @@ -30,14 +30,9 @@ const props = defineProps({ | |||||||
|     type: String, |     type: String, | ||||||
|     required: true, |     required: true, | ||||||
|   }, |   }, | ||||||
|   inputPlaceholder: { |  | ||||||
|     type: String, |  | ||||||
|     required: true, |  | ||||||
|   }, |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const isRequestingAuthorization = ref(false); | const isRequestingAuthorization = ref(false); | ||||||
| const email = ref(''); |  | ||||||
|  |  | ||||||
| const client = computed(() => { | const client = computed(() => { | ||||||
|   if (props.provider === 'microsoft') { |   if (props.provider === 'microsoft') { | ||||||
| @@ -50,9 +45,7 @@ const client = computed(() => { | |||||||
| async function requestAuthorization() { | async function requestAuthorization() { | ||||||
|   try { |   try { | ||||||
|     isRequestingAuthorization.value = true; |     isRequestingAuthorization.value = true; | ||||||
|     const response = await client.value.generateAuthorization({ |     const response = await client.value.generateAuthorization(); | ||||||
|       email: email.value, |  | ||||||
|     }); |  | ||||||
|     const { |     const { | ||||||
|       data: { url }, |       data: { url }, | ||||||
|     } = response; |     } = response; | ||||||
| @@ -75,11 +68,6 @@ async function requestAuthorization() { | |||||||
|       :header-content="description" |       :header-content="description" | ||||||
|     /> |     /> | ||||||
|     <form class="mt-6" @submit.prevent="requestAuthorization"> |     <form class="mt-6" @submit.prevent="requestAuthorization"> | ||||||
|       <woot-input |  | ||||||
|         v-model="email" |  | ||||||
|         type="email" |  | ||||||
|         :placeholder="inputPlaceholder" |  | ||||||
|       /> |  | ||||||
|       <NextButton |       <NextButton | ||||||
|         :is-loading="isRequestingAuthorization" |         :is-loading="isRequestingAuthorization" | ||||||
|         type="submit" |         type="submit" | ||||||
|   | |||||||
| @@ -32,19 +32,20 @@ RSpec.describe 'Google Authorization API', type: :request do | |||||||
|              as: :json |              as: :json | ||||||
|  |  | ||||||
|         expect(response).to have_http_status(:success) |         expect(response).to have_http_status(:success) | ||||||
|         google_service = Class.new { extend GoogleConcern } |  | ||||||
|         response_url = google_service.google_client.auth_code.authorize_url( |         # Validate URL components | ||||||
|           { |         url = response.parsed_body['url'] | ||||||
|             redirect_uri: "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback", |         uri = URI.parse(url) | ||||||
|             scope: 'email profile https://mail.google.com/', |         params = CGI.parse(uri.query) | ||||||
|             response_type: 'code', |  | ||||||
|             prompt: 'consent', |         expect(url).to start_with('https://accounts.google.com/o/oauth2/auth') | ||||||
|             access_type: 'offline', |         expect(params['scope']).to eq(['email profile https://mail.google.com/']) | ||||||
|             client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil) |         expect(params['redirect_uri']).to eq(["#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback"]) | ||||||
|           } |  | ||||||
|         ) |         # Validate state parameter exists and can be decoded back to the account | ||||||
|         expect(response.parsed_body['url']).to eq response_url |         expect(params['state']).to be_present | ||||||
|         expect(Redis::Alfred.get("google::#{administrator.email}")).to eq(account.id.to_s) |         decoded_account = GlobalID::Locator.locate_signed(params['state'].first, for: 'default') | ||||||
|  |         expect(decoded_account).to eq(account) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ RSpec.describe 'Microsoft Authorization API', type: :request do | |||||||
|       it 'returns unathorized for agent' do |       it 'returns unathorized for agent' do | ||||||
|         post "/api/v1/accounts/#{account.id}/microsoft/authorization", |         post "/api/v1/accounts/#{account.id}/microsoft/authorization", | ||||||
|              headers: agent.create_new_auth_token, |              headers: agent.create_new_auth_token, | ||||||
|              params: { email: administrator.email }, |  | ||||||
|              as: :json |              as: :json | ||||||
|  |  | ||||||
|         expect(response).to have_http_status(:unauthorized) |         expect(response).to have_http_status(:unauthorized) | ||||||
| @@ -28,20 +27,27 @@ RSpec.describe 'Microsoft Authorization API', type: :request do | |||||||
|       it 'creates a new authorization and returns the redirect url' do |       it 'creates a new authorization and returns the redirect url' do | ||||||
|         post "/api/v1/accounts/#{account.id}/microsoft/authorization", |         post "/api/v1/accounts/#{account.id}/microsoft/authorization", | ||||||
|              headers: administrator.create_new_auth_token, |              headers: administrator.create_new_auth_token, | ||||||
|              params: { email: administrator.email }, |  | ||||||
|              as: :json |              as: :json | ||||||
|  |  | ||||||
|         expect(response).to have_http_status(:success) |         expect(response).to have_http_status(:success) | ||||||
|         microsoft_service = Class.new { extend MicrosoftConcern } |  | ||||||
|         response_url = microsoft_service.microsoft_client.auth_code.authorize_url( |         # Validate URL components | ||||||
|           { |         url = response.parsed_body['url'] | ||||||
|             redirect_uri: "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback", |         uri = URI.parse(url) | ||||||
|             scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile', |         params = CGI.parse(uri.query) | ||||||
|             prompt: 'consent' |  | ||||||
|           } |         expect(url).to start_with('https://login.microsoftonline.com/common/oauth2/v2.0/authorize') | ||||||
|         ) |         expected_scope = [ | ||||||
|         expect(response.parsed_body['url']).to eq response_url |           'offline_access https://outlook.office.com/IMAP.AccessAsUser.All ' \ | ||||||
|         expect(Redis::Alfred.get("microsoft::#{administrator.email}")).to eq(account.id.to_s) |           'https://outlook.office.com/SMTP.Send openid profile email' | ||||||
|  |         ] | ||||||
|  |         expect(params['scope']).to eq(expected_scope) | ||||||
|  |         expect(params['redirect_uri']).to eq(["#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback"]) | ||||||
|  |  | ||||||
|  |         # Validate state parameter exists and can be decoded back to the account | ||||||
|  |         expect(params['state']).to be_present | ||||||
|  |         decoded_account = GlobalID::Locator.locate_signed(params['state'].first, for: 'default') | ||||||
|  |         expect(decoded_account).to eq(account) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -4,11 +4,7 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|   let(:account) { create(:account) } |   let(:account) { create(:account) } | ||||||
|   let(:code) { SecureRandom.hex(10) } |   let(:code) { SecureRandom.hex(10) } | ||||||
|   let(:email) { Faker::Internet.email } |   let(:email) { Faker::Internet.email } | ||||||
|   let(:cache_key) { "google::#{email.downcase}" } |   let(:state) { account.to_sgid(expires_in: 15.minutes).to_s } | ||||||
|  |  | ||||||
|   before do |  | ||||||
|     Redis::Alfred.set(cache_key, account.id) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   describe 'GET /google/callback' do |   describe 'GET /google/callback' do | ||||||
|     let(:response_body_success) do |     let(:response_body_success) do | ||||||
| @@ -27,7 +23,7 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get google_callback_url, params: { code: code } |       get google_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) |       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
| @@ -36,7 +32,6 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') |       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') | ||||||
|       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] |       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] | ||||||
|       expect(inbox.channel.imap_address).to eq 'imap.gmail.com' |       expect(inbox.channel.imap_address).to eq 'imap.gmail.com' | ||||||
|       expect(Redis::Alfred.get(cache_key)).to be_nil |  | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     it 'updates inbox channel config if inbox exists with imap_login and authentication is successful' do |     it 'updates inbox channel config if inbox exists with imap_login and authentication is successful' do | ||||||
| @@ -49,14 +44,13 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get google_callback_url, params: { code: code } |       get google_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: inbox.id) |       expect(response).to redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: inbox.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
|       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') |       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') | ||||||
|       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] |       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] | ||||||
|       expect(inbox.channel.imap_address).to eq 'imap.gmail.com' |       expect(inbox.channel.imap_address).to eq 'imap.gmail.com' | ||||||
|       expect(Redis::Alfred.get(cache_key)).to be_nil |  | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     it 'creates inboxes with fallback_name when account name is not present in id_token' do |     it 'creates inboxes with fallback_name when account name is not present in id_token' do | ||||||
| @@ -65,7 +59,7 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success_without_name.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success_without_name.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get google_callback_url, params: { code: code } |       get google_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) |       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
| @@ -79,10 +73,9 @@ RSpec.describe 'Google::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/google/callback" }) | ||||||
|         .to_return(status: 401) |         .to_return(status: 401) | ||||||
|  |  | ||||||
|       get google_callback_url, params: { code: code } |       get google_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to '/' |       expect(response).to redirect_to '/' | ||||||
|       expect(Redis::Alfred.get(cache_key).to_i).to eq account.id |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -4,11 +4,7 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|   let(:account) { create(:account) } |   let(:account) { create(:account) } | ||||||
|   let(:code) { SecureRandom.hex(10) } |   let(:code) { SecureRandom.hex(10) } | ||||||
|   let(:email) { Faker::Internet.email } |   let(:email) { Faker::Internet.email } | ||||||
|   let(:cache_key) { "microsoft::#{email.downcase}" } |   let(:state) { account.to_sgid(expires_in: 15.minutes).to_s } | ||||||
|  |  | ||||||
|   before do |  | ||||||
|     Redis::Alfred.set(cache_key, account.id) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   describe 'GET /microsoft/callback' do |   describe 'GET /microsoft/callback' do | ||||||
|     let(:response_body_success) do |     let(:response_body_success) do | ||||||
| @@ -27,7 +23,7 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get microsoft_callback_url, params: { code: code } |       get microsoft_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) |       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
| @@ -36,7 +32,6 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') |       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') | ||||||
|       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] |       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] | ||||||
|       expect(inbox.channel.imap_address).to eq 'outlook.office365.com' |       expect(inbox.channel.imap_address).to eq 'outlook.office365.com' | ||||||
|       expect(Redis::Alfred.get(cache_key)).to be_nil |  | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     it 'creates updates inbox channel config if inbox exists and authentication is successful' do |     it 'creates updates inbox channel config if inbox exists and authentication is successful' do | ||||||
| @@ -48,14 +43,13 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get microsoft_callback_url, params: { code: code } |       get microsoft_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: account.inboxes.last.id) |       expect(response).to redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: account.inboxes.last.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
|       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') |       expect(inbox.channel.reload.provider_config.keys).to include('access_token', 'refresh_token', 'expires_on') | ||||||
|       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] |       expect(inbox.channel.reload.provider_config['access_token']).to eq response_body_success[:access_token] | ||||||
|       expect(inbox.channel.imap_address).to eq 'outlook.office365.com' |       expect(inbox.channel.imap_address).to eq 'outlook.office365.com' | ||||||
|       expect(Redis::Alfred.get(cache_key)).to be_nil |  | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     it 'creates inboxes with fallback_name when account name is not present in id_token' do |     it 'creates inboxes with fallback_name when account name is not present in id_token' do | ||||||
| @@ -64,7 +58,7 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) | ||||||
|         .to_return(status: 200, body: response_body_success_without_name.to_json, headers: { 'Content-Type' => 'application/json' }) |         .to_return(status: 200, body: response_body_success_without_name.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||||
|  |  | ||||||
|       get microsoft_callback_url, params: { code: code } |       get microsoft_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) |       expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) | ||||||
|       expect(account.inboxes.count).to be 1 |       expect(account.inboxes.count).to be 1 | ||||||
| @@ -78,10 +72,9 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do | |||||||
|                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) |                       'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" }) | ||||||
|         .to_return(status: 401) |         .to_return(status: 401) | ||||||
|  |  | ||||||
|       get microsoft_callback_url, params: { code: code } |       get microsoft_callback_url, params: { code: code, state: state } | ||||||
|  |  | ||||||
|       expect(response).to redirect_to '/' |       expect(response).to redirect_to '/' | ||||||
|       expect(Redis::Alfred.get(cache_key).to_i).to eq account.id |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shivam Mishra
					Shivam Mishra