mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 02:32:29 +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