diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index a3a90451d..ff67eefc5 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -32,6 +32,8 @@ class AccountBuilder end def validate_email + raise InvalidEmail.new({ domain_blocked: domain_blocked }) if domain_blocked? + address = ValidEmail2::Address.new(@email) if address.valid? && !address.disposable? true @@ -79,4 +81,21 @@ class AccountBuilder @user.confirm if @confirmed @user.save! end + + def domain_blocked? + domain = @email.split('@').last + + blocked_domains.each do |blocked_domain| + return true if domain.match?(blocked_domain) + end + + false + end + + def blocked_domains + domains = GlobalConfigService.load('BLOCKED_EMAIL_DOMAINS', '') + domains.split("\n").map(&:strip) if domains.present? + + [] + end end diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 0626ced59..0ff363874 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -13,7 +13,7 @@ import wootConstants from 'dashboard/constants/globals'; import { CMD_REOPEN_CONVERSATION, CMD_RESOLVE_CONVERSATION, -} from 'dashboard/routes/dashboard/commands/commandBarBusEvents'; +} from 'dashboard/helper/commandbar/events'; const store = useStore(); const getters = useStoreGetters(); diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue b/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue index 360599631..96fe2830a 100644 --- a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue +++ b/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue @@ -7,7 +7,7 @@ import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useAI } from 'dashboard/composables/useAI'; import AICTAModal from './AICTAModal.vue'; import AIAssistanceModal from './AIAssistanceModal.vue'; -import { CMD_AI_ASSIST } from 'dashboard/routes/dashboard/commands/commandBarBusEvents'; +import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events'; import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue'; export default { diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue index 547746131..8f3100d80 100644 --- a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue +++ b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue @@ -1,13 +1,12 @@ diff --git a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js b/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js deleted file mode 100644 index 652624872..000000000 --- a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js +++ /dev/null @@ -1,413 +0,0 @@ -import { mapGetters } from 'vuex'; -import wootConstants from 'dashboard/constants/globals'; -import { emitter } from 'shared/helpers/mitt'; - -import { CMD_AI_ASSIST } from './commandBarBusEvents'; -import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants'; -import { - ICON_ADD_LABEL, - ICON_ASSIGN_AGENT, - ICON_ASSIGN_PRIORITY, - ICON_ASSIGN_TEAM, - ICON_REMOVE_LABEL, - ICON_PRIORITY_URGENT, - ICON_PRIORITY_HIGH, - ICON_PRIORITY_LOW, - ICON_PRIORITY_MEDIUM, - ICON_PRIORITY_NONE, - ICON_AI_ASSIST, - ICON_AI_SUMMARY, - ICON_AI_SHORTEN, - ICON_AI_EXPAND, - ICON_AI_GRAMMAR, -} from './CommandBarIcons'; - -import { - OPEN_CONVERSATION_ACTIONS, - SNOOZE_CONVERSATION_ACTIONS, - RESOLVED_CONVERSATION_ACTIONS, - SEND_TRANSCRIPT_ACTION, - UNMUTE_ACTION, - MUTE_ACTION, -} from './commandBarActions'; -import { - isAConversationRoute, - isAInboxViewRoute, -} from '../../../helper/routeHelpers'; -export default { - watch: { - assignableAgents() { - this.setCommandbarData(); - }, - currentChat() { - this.setCommandbarData(); - }, - teamsList() { - this.setCommandbarData(); - }, - activeLabels() { - this.setCommandbarData(); - }, - draftMessage() { - this.setCommandbarData(); - }, - replyMode() { - this.setCommandbarData(); - }, - contextMenuChatId() { - this.setCommandbarData(); - }, - }, - computed: { - ...mapGetters({ - currentChat: 'getSelectedChat', - replyMode: 'draftMessages/getReplyEditorMode', - contextMenuChatId: 'getContextMenuChatId', - teams: 'teams/getTeams', - }), - draftMessage() { - return this.$store.getters['draftMessages/get'](this.draftKey); - }, - draftKey() { - return `draft-${this.conversationId}-${this.replyMode}`; - }, - inboxId() { - return this.currentChat?.inbox_id; - }, - conversationId() { - return this.currentChat?.id; - }, - hasAnAssignedTeam() { - return !!this.currentChat?.meta?.team; - }, - teamsList() { - if (this.hasAnAssignedTeam) { - return [ - { id: 0, name: this.$t('TEAMS_SETTINGS.LIST.NONE') }, - ...this.teams, - ]; - } - return this.teams; - }, - statusActions() { - const isOpen = - this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN; - const isSnoozed = - this.currentChat?.status === wootConstants.STATUS_TYPE.SNOOZED; - const isResolved = - this.currentChat?.status === wootConstants.STATUS_TYPE.RESOLVED; - - let actions = []; - if (isOpen) { - actions = [ - ...OPEN_CONVERSATION_ACTIONS, - ...SNOOZE_CONVERSATION_ACTIONS, - ]; - } else if (isResolved || isSnoozed) { - actions = RESOLVED_CONVERSATION_ACTIONS; - } - return this.prepareActions(actions); - }, - - priorityOptions() { - return [ - { - label: this.$t('CONVERSATION.PRIORITY.OPTIONS.NONE'), - key: null, - icon: ICON_PRIORITY_NONE, - }, - { - label: this.$t('CONVERSATION.PRIORITY.OPTIONS.URGENT'), - key: 'urgent', - icon: ICON_PRIORITY_URGENT, - }, - { - label: this.$t('CONVERSATION.PRIORITY.OPTIONS.HIGH'), - key: 'high', - icon: ICON_PRIORITY_HIGH, - }, - { - label: this.$t('CONVERSATION.PRIORITY.OPTIONS.MEDIUM'), - key: 'medium', - icon: ICON_PRIORITY_MEDIUM, - }, - { - label: this.$t('CONVERSATION.PRIORITY.OPTIONS.LOW'), - key: 'low', - icon: ICON_PRIORITY_LOW, - }, - ].filter(item => item.key !== this.currentChat?.priority); - }, - assignAgentActions() { - const agentOptions = this.agentsList.map(agent => ({ - id: `agent-${agent.id}`, - title: agent.name, - parent: 'assign_an_agent', - section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_ASSIGNEE'), - agentInfo: agent, - icon: ICON_ASSIGN_AGENT, - handler: this.onChangeAssignee, - })); - return [ - { - id: 'assign_an_agent', - title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_AN_AGENT'), - section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'), - icon: ICON_ASSIGN_AGENT, - children: agentOptions.map(option => option.id), - }, - ...agentOptions, - ]; - }, - assignPriorityActions() { - const options = this.priorityOptions.map(priority => ({ - id: `priority-${priority.key}`, - title: priority.label, - parent: 'assign_priority', - section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_PRIORITY'), - priority: priority, - icon: priority.icon, - handler: this.onChangePriority, - })); - return [ - { - id: 'assign_priority', - title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_PRIORITY'), - section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'), - icon: ICON_ASSIGN_PRIORITY, - children: options.map(option => option.id), - }, - ...options, - ]; - }, - assignTeamActions() { - const teamOptions = this.teamsList.map(team => ({ - id: `team-${team.id}`, - title: team.name, - parent: 'assign_a_team', - section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_TEAM'), - teamInfo: team, - icon: ICON_ASSIGN_TEAM, - handler: this.onChangeTeam, - })); - return [ - { - id: 'assign_a_team', - title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_A_TEAM'), - section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'), - icon: ICON_ASSIGN_TEAM, - children: teamOptions.map(option => option.id), - }, - ...teamOptions, - ]; - }, - - addLabelActions() { - const availableLabels = this.inactiveLabels.map(label => ({ - id: label.title, - title: `#${label.title}`, - parent: 'add_a_label_to_the_conversation', - section: this.$t('COMMAND_BAR.SECTIONS.ADD_LABEL'), - icon: ICON_ADD_LABEL, - handler: action => this.addLabelToConversation({ title: action.id }), - })); - return [ - ...availableLabels, - { - id: 'add_a_label_to_the_conversation', - title: this.$t('COMMAND_BAR.COMMANDS.ADD_LABELS_TO_CONVERSATION'), - section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'), - icon: ICON_ADD_LABEL, - children: this.inactiveLabels.map(label => label.title), - }, - ]; - }, - removeLabelActions() { - const activeLabels = this.activeLabels.map(label => ({ - id: label.title, - title: `#${label.title}`, - parent: 'remove_a_label_to_the_conversation', - section: this.$t('COMMAND_BAR.SECTIONS.REMOVE_LABEL'), - icon: ICON_REMOVE_LABEL, - handler: action => this.removeLabelFromConversation(action.id), - })); - return [ - ...activeLabels, - { - id: 'remove_a_label_to_the_conversation', - title: this.$t('COMMAND_BAR.COMMANDS.REMOVE_LABEL_FROM_CONVERSATION'), - section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'), - icon: ICON_REMOVE_LABEL, - children: this.activeLabels.map(label => label.title), - }, - ]; - }, - labelActions() { - if (this.activeLabels.length) { - return [...this.addLabelActions, ...this.removeLabelActions]; - } - return this.addLabelActions; - }, - conversationAdditionalActions() { - return this.prepareActions([ - this.currentChat.muted ? UNMUTE_ACTION : MUTE_ACTION, - SEND_TRANSCRIPT_ACTION, - ]); - }, - - nonDraftMessageAIAssistActions() { - if (this.replyMode === REPLY_EDITOR_MODES.REPLY) { - return [ - { - label: this.$t( - 'INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPLY_SUGGESTION' - ), - key: 'reply_suggestion', - icon: ICON_AI_ASSIST, - }, - ]; - } - return [ - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SUMMARIZE'), - key: 'summarize', - icon: ICON_AI_SUMMARY, - }, - ]; - }, - - draftMessageAIAssistActions() { - return [ - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPHRASE'), - key: 'rephrase', - icon: ICON_AI_ASSIST, - }, - { - label: this.$t( - 'INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.FIX_SPELLING_GRAMMAR' - ), - key: 'fix_spelling_grammar', - icon: ICON_AI_GRAMMAR, - }, - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.EXPAND'), - key: 'expand', - icon: ICON_AI_EXPAND, - }, - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SHORTEN'), - key: 'shorten', - icon: ICON_AI_SHORTEN, - }, - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FRIENDLY'), - key: 'make_friendly', - icon: ICON_AI_ASSIST, - }, - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FORMAL'), - key: 'make_formal', - icon: ICON_AI_ASSIST, - }, - { - label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SIMPLIFY'), - key: 'simplify', - icon: ICON_AI_ASSIST, - }, - ]; - }, - - AIAssistActions() { - const aiOptions = this.draftMessage - ? this.draftMessageAIAssistActions - : this.nonDraftMessageAIAssistActions; - const options = aiOptions.map(item => ({ - id: `ai-assist-${item.key}`, - title: item.label, - parent: 'ai_assist', - section: this.$t('COMMAND_BAR.SECTIONS.AI_ASSIST'), - priority: item, - icon: item.icon, - handler: () => emitter.emit(CMD_AI_ASSIST, item.key), - })); - return [ - { - id: 'ai_assist', - title: this.$t('COMMAND_BAR.COMMANDS.AI_ASSIST'), - section: this.$t('COMMAND_BAR.SECTIONS.AI_ASSIST'), - icon: ICON_AI_ASSIST, - children: options.map(option => option.id), - }, - ...options, - ]; - }, - - isConversationOrInboxRoute() { - return ( - isAConversationRoute(this.$route.name) || - isAInboxViewRoute(this.$route.name) - ); - }, - - shouldShowSnoozeOption() { - return ( - isAConversationRoute(this.$route.name, true, false) && - this.contextMenuChatId - ); - }, - - getDefaultConversationHotKeys() { - const defaultConversationHotKeys = [ - ...this.statusActions, - ...this.conversationAdditionalActions, - ...this.assignAgentActions, - ...this.assignTeamActions, - ...this.labelActions, - ...this.assignPriorityActions, - ]; - if (this.isAIIntegrationEnabled) { - return [...defaultConversationHotKeys, ...this.AIAssistActions]; - } - return defaultConversationHotKeys; - }, - - conversationHotKeys() { - if (this.shouldShowSnoozeOption) { - return this.prepareActions(SNOOZE_CONVERSATION_ACTIONS); - } - if (this.isConversationOrInboxRoute) { - return this.getDefaultConversationHotKeys; - } - return []; - }, - }, - - methods: { - onChangeAssignee(action) { - this.$store.dispatch('assignAgent', { - conversationId: this.currentChat.id, - agentId: action.agentInfo.id, - }); - }, - onChangePriority(action) { - this.$store.dispatch('assignPriority', { - conversationId: this.currentChat.id, - priority: action.priority.key, - }); - }, - onChangeTeam(action) { - this.$store.dispatch('assignTeam', { - conversationId: this.currentChat.id, - teamId: action.teamInfo.id, - }); - }, - prepareActions(actions) { - return actions.map(action => ({ - ...action, - title: this.$t(action.title), - section: this.$t(action.section), - })); - }, - }, -}; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue b/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue index b7d5e17dd..03d41a778 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue @@ -1,6 +1,5 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue index 492775542..78b433a42 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue @@ -3,7 +3,6 @@ import { useAlert } from 'dashboard/composables'; import BotMetrics from './components/BotMetrics.vue'; import ReportFilterSelector from './components/FilterSelector.vue'; import { GROUP_BY_FILTER } from './constants'; -import reportMixin from 'dashboard/mixins/reportMixin'; import ReportContainer from './ReportContainer.vue'; import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; @@ -14,7 +13,6 @@ export default { ReportFilterSelector, ReportContainer, }, - mixins: [reportMixin], data() { return { from: 0, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue index 1f96b161f..da2f2f232 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue @@ -4,7 +4,6 @@ import fromUnixTime from 'date-fns/fromUnixTime'; import format from 'date-fns/format'; import ReportFilterSelector from './components/FilterSelector.vue'; import { GROUP_BY_FILTER } from './constants'; -import reportMixin from 'dashboard/mixins/reportMixin'; import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; import ReportContainer from './ReportContainer.vue'; @@ -24,7 +23,6 @@ export default { ReportFilterSelector, ReportContainer, }, - mixins: [reportMixin], data() { return { from: 0, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue index f90c3730c..fc323d037 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue @@ -1,19 +1,23 @@ @@ -29,7 +34,7 @@ export default { {{ metric.NAME }}
-
+
{{ displayMetric(metric.KEY) }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue index 2d5e52462..51fb8932d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -3,7 +3,6 @@ import { useAlert } from 'dashboard/composables'; import ReportFilters from './ReportFilters.vue'; import ReportContainer from '../ReportContainer.vue'; import { GROUP_BY_FILTER } from '../constants'; -import reportMixin from '../../../../../mixins/reportMixin'; import { generateFileName } from '../../../../../helper/downloadHelper'; import { REPORTS_EVENTS } from '../../../../../helper/AnalyticsHelper/events'; @@ -22,7 +21,6 @@ export default { ReportFilters, ReportContainer, }, - mixins: [reportMixin], props: { type: { type: String, diff --git a/app/javascript/portal/components/SearchSuggestions.vue b/app/javascript/portal/components/SearchSuggestions.vue index 5838e8c7c..cb4adc9fc 100644 --- a/app/javascript/portal/components/SearchSuggestions.vue +++ b/app/javascript/portal/components/SearchSuggestions.vue @@ -1,10 +1,9 @@ diff --git a/config/installation_config.yml b/config/installation_config.yml index 95d9cc6a5..26f05211e 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -168,6 +168,11 @@ display_title: 'Dashboard Scripts' description: 'Scripts are loaded as the last item in the tag' type: code +- name: BLOCKED_EMAIL_DOMAINS + value: + display_title: 'Blocked Email Domains' + description: 'Add a domain per line to block them from signing up, accepts Regex' + type: code # ------- End of Chatwoot Internal Config for Cloud ----# # ------- Chatwoot Internal Config for Self Hosted ----# @@ -220,11 +225,11 @@ ## ----- Captain Configs ----- ## - name: CAPTAIN_API_URL - value: + value: display_title: 'Captain API URL' description: 'The API URL for Captain' - name: CAPTAIN_APP_URL - value: + value: display_title: 'Captain App URL' description: 'The App URL for Captain' ## ----- End of Captain Configs ----- ## diff --git a/config/locales/en.yml b/config/locales/en.yml index 63593ef04..dd7b07bcf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -43,6 +43,7 @@ en: invalid: Invalid events signup: disposable_email: We do not allow disposable emails + blocked_domain: This domain is not allowed. If you believe this is a mistake, please contact support. invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" invalid_params: 'Invalid, please check the signup paramters and try again' diff --git a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb index 126ed292e..a9c8ee1f0 100644 --- a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb +++ b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb @@ -32,6 +32,6 @@ module Enterprise::SuperAdmin::AppConfigsController end def internal_config_options - %w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS] + %w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS BLOCKED_EMAIL_DOMAINS] end end diff --git a/lib/custom_exceptions/account.rb b/lib/custom_exceptions/account.rb index efacc9e6e..08c6f0ddb 100644 --- a/lib/custom_exceptions/account.rb +++ b/lib/custom_exceptions/account.rb @@ -3,7 +3,9 @@ module CustomExceptions::Account class InvalidEmail < CustomExceptions::Base def message - if @data[:disposable] + if @data[:domain_blocked] + I18n.t 'errors.signup.blocked_domain' + elsif @data[:disposable] I18n.t 'errors.signup.disposable_email' elsif !@data[:valid] I18n.t 'errors.signup.invalid_email'