Skip to content

Commit

Permalink
feat: new re-authorization flow for Microsoft (#9510)
Browse files Browse the repository at this point in the history
This PR adds a cleaner re-authorization flow to Microsoft. This PR has
the following changes

1. Use `reauthorization_required` value for Microsoft Channel
2. Refactor `InboxReconnectionRequired` to reuse the `banner` component
3. Refactor `microsoft/Reauthorize.vue` to reuse
`InboxReconnectionRequired` component
4. Update `reauthorizable.rb` to update cache keys if the model has an
inbox
5. Update `microsoft/callbacks_controller.rb` to handle the
reauthorization case with a redirect to the inbox settings page if the
inbox already exists at the time of authorization.

## How Has This Been Tested?

- [x] Local Instance
- [ ] Staging Instance
- [x] Unit tests

## Pending Tasks

- [ ] ~Success Toast~ will do this in a follow-up PR with the screen

## Demo

The following video shows the whole process of creation and
re-authorization of the Microsoft channel


https://www.loom.com/share/e5cd9bd4439c4741b0dcfe66d67f88b3?sid=100f3642-43e4-46b3-8123-88a5dd9d8509

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
  • Loading branch information
scmmishra and muhsin-k committed May 23, 2024
1 parent 616e3a8 commit eafd3ae
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 95 deletions.
19 changes: 16 additions & 3 deletions app/controllers/microsoft/callbacks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ def show
redirect_uri: "#{base_url}/microsoft/callback"
)

inbox = find_or_create_inbox
inbox, already_exists = find_or_create_inbox
::Redis::Alfred.delete(users_data['email'].downcase)
redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)

if already_exists
redirect_to app_microsoft_inbox_settings_url(account_id: account.id, inbox_id: inbox.id)
else
redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
end
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
redirect_to '/'
Expand Down Expand Up @@ -40,9 +45,17 @@ def account

def find_or_create_inbox
channel_email = Channel::Email.find_by(email: users_data['email'], account: account)
# we need this value to know where to redirect on sucessful processing of the callback
channel_exists = channel_email.present?

channel_email ||= create_microsoft_channel_with_inbox
update_microsoft_channel(channel_email)
channel_email.inbox

# reauthorize channel, this code path only triggers when microsoft auth is successful
# reauthorized will also update cache keys for the associated inbox
channel_email.reauthorized!

[channel_email.inbox, channel_exists]
end

# Fallback name, for when name field is missing from users_data
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"INBOX_MGMT": {
"HEADER": "Inboxes",
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a website or a facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a website or a Facebook Page. </p><p> In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
"RECONNECTION_REQUIRED": "Your inbox is disconnected, you will not receive any new messages. <a class=\"underline\" href=\"%{actionUrl}\">Click here</a> to reconnect.",
"RECONNECTION_REQUIRED": "Your inbox is disconnected. You won't receive new messages until you reauthorize it.",
"CLICK_TO_RECONNECT": "Click here to reconnect.",
"LIST": {
"404": "There are no inboxes attached to this account."
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
</woot-tabs>
</setting-intro-banner>

<inbox-reconnection-required
v-if="isReconnectionRequired"
class="mx-8 mt-5"
/>
<microsoft-reauthorize v-if="microsoftUnauthorized" :inbox="inbox" />

<div v-if="selectedTabKey === 'inbox_settings'" class="mx-8">
<settings-section
Expand Down Expand Up @@ -440,7 +437,7 @@ import WeeklyAvailability from './components/WeeklyAvailability.vue';
import GreetingsEditor from 'shared/components/GreetingsEditor.vue';
import ConfigurationPage from './settingsPage/ConfigurationPage.vue';
import CollaboratorsPage from './settingsPage/CollaboratorsPage.vue';
import InboxReconnectionRequired from './components/InboxReconnectionRequired';
import MicrosoftReauthorize from './channels/microsoft/Reauthorize.vue';
import WidgetBuilder from './WidgetBuilder.vue';
import BotConfiguration from './components/BotConfiguration.vue';
import { FEATURE_FLAGS } from '../../../../featureFlags';
Expand All @@ -459,7 +456,7 @@ export default {
WeeklyAvailability,
WidgetBuilder,
SenderNameExamplePreview,
InboxReconnectionRequired,
MicrosoftReauthorize,
},
mixins: [alertMixin, configMixin, inboxMixin],
data() {
Expand Down Expand Up @@ -621,8 +618,8 @@ export default {
return true;
return false;
},
isReconnectionRequired() {
return false;
microsoftUnauthorized() {
return this.inbox.microsoft_reauthorization;
},
},
watch: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,44 @@
<template>
<div class="mx-8">
<settings-section
:title="$t('INBOX_MGMT.MICROSOFT.TITLE')"
:sub-title="$t('INBOX_MGMT.MICROSOFT.SUBTITLE')"
>
<div class="mb-6">
<form @submit.prevent="requestAuthorization">
<woot-submit-button
icon="brand-twitter"
button-text="Sign in with Microsoft"
type="submit"
:loading="isRequestingAuthorization"
/>
</form>
</div>
</settings-section>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import microsoftClient from '../../../../../../api/channel/microsoftClient';
import SettingsSection from '../../../../../../components/SettingsSection.vue';
<script setup>
import { ref } from 'vue';
import InboxReconnectionRequired from '../../components/InboxReconnectionRequired';
import microsoftClient from 'dashboard/api/channel/microsoftClient';
export default {
components: {
SettingsSection,
},
mixins: [alertMixin],
props: {
inbox: {
type: Object,
default: () => ({}),
},
},
data() {
return { isRequestingAuthorization: false };
},
methods: {
async requestAuthorization() {
try {
this.isRequestingAuthorization = true;
const response = await microsoftClient.generateAuthorization({
email: this.inbox.email,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
this.showAlert(this.$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE'));
} finally {
this.isRequestingAuthorization = false;
}
},
import { useI18n } from 'dashboard/composables/useI18n';
import { useAlert } from 'dashboard/composables';
const { t } = useI18n();
const props = defineProps({
inbox: {
type: Object,
default: () => ({}),
},
};
</script>
<style lang="scss" scoped>
.smtp-details-wrap {
margin-bottom: var(--space-medium);
});
const isRequestingAuthorization = ref(false);
async function requestAuthorization() {
try {
isRequestingAuthorization.value = true;
const response = await microsoftClient.generateAuthorization({
email: props.inbox.email,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
useAlert(t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE'));
} finally {
isRequestingAuthorization.value = false;
}
}
</style>
</script>

<template>
<inbox-reconnection-required
class="mx-8 mt-5"
@reauthorize="requestAuthorization"
/>
</template>
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
<script setup>
defineProps({
actionUrl: {
type: String,
required: true,
},
});
import Banner from 'dashboard/components/ui/Banner.vue';
</script>

<template>
<div
class="flex items-center gap-2 px-4 py-3 text-sm text-white bg-red-500 rounded-md dark:bg-red-800/30 dark:text-red-50 min-h-10"
>
<fluent-icon icon="error-circle" class="text-white dark:text-red-50" />
<slot>
<span v-html="$t('INBOX_MGMT.RECONNECTION_REQUIRED', { actionUrl })" />
</slot>
</div>
<banner
color-scheme="alert"
class="justify-start rounded-md"
:banner-message="$t('INBOX_MGMT.RECONNECTION_REQUIRED')"
:action-button-label="$t('INBOX_MGMT.CLICK_TO_RECONNECT')"
has-action-button
@click="$emit('reauthorize')"
/>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@
</div>
<imap-settings :inbox="inbox" />
<smtp-settings v-if="inbox.imap_enabled" :inbox="inbox" />
<microsoft-reauthorize
v-if="inbox.microsoft_reauthorization"
:inbox="inbox"
/>
</div>
<div v-else-if="isAWhatsAppChannel && !isATwilioChannel">
<div v-if="inbox.provider_config" class="mx-8">
Expand All @@ -130,7 +126,7 @@
"
>
<div
class="whatsapp-settings--content items-center flex flex-1 justify-between mt-2"
class="flex items-center justify-between flex-1 mt-2 whatsapp-settings--content"
>
<woot-input
v-model.trim="whatsAppInboxAPIKey"
Expand Down Expand Up @@ -160,15 +156,13 @@ import inboxMixin from 'shared/mixins/inboxMixin';
import SettingsSection from '../../../../../components/SettingsSection.vue';
import ImapSettings from '../ImapSettings.vue';
import SmtpSettings from '../SmtpSettings.vue';
import MicrosoftReauthorize from '../channels/microsoft/Reauthorize.vue';
import { required } from 'vuelidate/lib/validators';
export default {
components: {
SettingsSection,
ImapSettings,
SmtpSettings,
MicrosoftReauthorize,
},
mixins: [inboxMixin, alertMixin],
props: {
Expand Down
8 changes: 8 additions & 0 deletions app/models/concerns/reauthorizable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def prompt_reauthorization!
update!(active: false)
mailer.automation_rule_disabled(self).deliver_later
end

invalidate_inbox_cache unless instance_of?(::AutomationRule)
end

def process_integration_hook_reauthorization_emails(mailer)
Expand All @@ -68,10 +70,16 @@ def process_integration_hook_reauthorization_emails(mailer)
def reauthorized!
::Redis::Alfred.delete(authorization_error_count_key)
::Redis::Alfred.delete(reauthorization_required_key)

invalidate_inbox_cache unless instance_of?(::AutomationRule)
end

private

def invalidate_inbox_cache
inbox.update_account_cache if inbox.present?
end

def authorization_error_count_key
format(::Redis::Alfred::AUTHORIZATION_ERROR_COUNT, obj_type: self.class.table_name.singularize, obj_id: id)
end
Expand Down
5 changes: 4 additions & 1 deletion app/views/api/v1/models/_inbox.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ if resource.email?
json.imap_address resource.channel.try(:imap_address)
json.imap_port resource.channel.try(:imap_port)
json.imap_enabled resource.channel.try(:imap_enabled)
json.microsoft_reauthorization resource.channel.try(:microsoft?) && resource.channel.try(:provider_config).empty?
json.imap_enable_ssl resource.channel.try(:imap_enable_ssl)

if resource.channel.try(:microsoft?)
json.microsoft_reauthorization resource.channel.try(:provider_config).empty? || resource.channel.try(:reauthorization_required?)
end
end

## SMTP
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
get '/app/accounts/:account_id/settings/inboxes/new/microsoft', to: 'dashboard#index', as: 'app_new_microsoft_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_twitter_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_microsoft_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_microsoft_inbox_settings'

resource :widget, only: [:show]
namespace :survey do
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/microsoft/callbacks_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

get microsoft_callback_url, params: { code: code }

expect(response).to redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id)
expect(response).to redirect_to app_microsoft_inbox_settings_url(account_id: account.id, inbox_id: account.inboxes.last.id)
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['access_token']).to eq response_body_success[:access_token]
Expand Down

0 comments on commit eafd3ae

Please sign in to comment.