diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..6a244bcdc
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,8 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+We use [huntr.dev](https://huntr.dev/) for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via this [form](https://huntr.dev/bounties/disclose).
+This will enable us to review the vulnerability, fix it promptly, and reward you for your efforts.
+
+If you have any questions about the process, feel free to reach out to hello@chatwoot.com.
diff --git a/app/builders/contact_builder.rb b/app/builders/contact_builder.rb
index dc15a299a..f7fcdfb12 100644
--- a/app/builders/contact_builder.rb
+++ b/app/builders/contact_builder.rb
@@ -43,6 +43,8 @@ class ContactBuilder
contact ||= account.contacts.find_by(email: contact_attributes[:email]) if contact_attributes[:email].present?
+ contact ||= account.contacts.find_by(phone_number: contact_attributes[:phone_number]) if contact_attributes[:phone_number].present?
+
contact
end
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 7928a3d41..c8d01a1c7 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -7,6 +7,10 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
end
+ def assignable_agents
+ @assignable_agents = (Current.account.users.where(id: @inbox.members.select(:user_id)) + Current.account.administrators).uniq
+ end
+
def create
ActiveRecord::Base.transaction do
channel = create_channel
diff --git a/app/controllers/super_admin/platform_apps_controller.rb b/app/controllers/super_admin/platform_apps_controller.rb
new file mode 100644
index 000000000..2097638b9
--- /dev/null
+++ b/app/controllers/super_admin/platform_apps_controller.rb
@@ -0,0 +1,44 @@
+class SuperAdmin::PlatformAppsController < SuperAdmin::ApplicationController
+ # Overwrite any of the RESTful controller actions to implement custom behavior
+ # For example, you may want to send an email after a foo is updated.
+ #
+ # def update
+ # super
+ # send_foo_updated_email(requested_resource)
+ # end
+
+ # Override this method to specify custom lookup behavior.
+ # This will be used to set the resource for the `show`, `edit`, and `update`
+ # actions.
+ #
+ # def find_resource(param)
+ # Foo.find_by!(slug: param)
+ # end
+
+ # The result of this lookup will be available as `requested_resource`
+
+ # Override this if you have certain roles that require a subset
+ # this will be used to set the records shown on the `index` action.
+ #
+ # def scoped_resource
+ # if current_user.super_admin?
+ # resource_class
+ # else
+ # resource_class.with_less_stuff
+ # end
+ # end
+
+ # Override `resource_params` if you want to transform the submitted
+ # data before it's persisted. For example, the following would turn all
+ # empty values into nil values. It uses other APIs such as `resource_class`
+ # and `dashboard`:
+ #
+ # def resource_params
+ # params.require(resource_class.model_name.param_key).
+ # permit(dashboard.permitted_attributes).
+ # transform_values { |value| value == "" ? nil : value }
+ # end
+
+ # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
+ # for more information
+end
diff --git a/app/dashboards/access_token_dashboard.rb b/app/dashboards/access_token_dashboard.rb
index 6864b99d4..8d4f7840e 100644
--- a/app/dashboards/access_token_dashboard.rb
+++ b/app/dashboards/access_token_dashboard.rb
@@ -57,8 +57,8 @@ class AccessTokenDashboard < Administrate::BaseDashboard
# }.freeze
COLLECTION_FILTERS = {
user: ->(resources) { resources.where(owner_type: 'User') },
- super_admin: ->(resources) { resources.where(owner_type: 'SuperAdmin') },
- agent_bot: ->(resources) { resources.where(owner_type: 'AgentBot') }
+ agent_bot: ->(resources) { resources.where(owner_type: 'AgentBot') },
+ platform_app: ->(resources) { resources.where(owner_type: 'PlatformApp') }
}.freeze
# Overwrite this method to customize how access tokens are displayed
diff --git a/app/dashboards/platform_app_dashboard.rb b/app/dashboards/platform_app_dashboard.rb
new file mode 100644
index 000000000..f5ed564ef
--- /dev/null
+++ b/app/dashboards/platform_app_dashboard.rb
@@ -0,0 +1,62 @@
+require 'administrate/base_dashboard'
+
+class PlatformAppDashboard < Administrate::BaseDashboard
+ # ATTRIBUTE_TYPES
+ # a hash that describes the type of each of the model's fields.
+ #
+ # Each different type represents an Administrate::Field object,
+ # which determines how the attribute is displayed
+ # on pages throughout the dashboard.
+ ATTRIBUTE_TYPES = {
+ access_token: Field::HasOne,
+ id: Field::Number,
+ name: Field::String,
+ created_at: Field::DateTime,
+ updated_at: Field::DateTime
+ }.freeze
+
+ # COLLECTION_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's index page.
+ #
+ # By default, it's limited to four items to reduce clutter on index pages.
+ # Feel free to add, remove, or rearrange items.
+ COLLECTION_ATTRIBUTES = %i[
+ id
+ name
+ ].freeze
+
+ # SHOW_PAGE_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's show page.
+ SHOW_PAGE_ATTRIBUTES = %i[
+ id
+ name
+ created_at
+ updated_at
+ ].freeze
+
+ # FORM_ATTRIBUTES
+ # an array of attributes that will be displayed
+ # on the model's form (`new` and `edit`) pages.
+ FORM_ATTRIBUTES = %i[
+ name
+ ].freeze
+
+ # COLLECTION_FILTERS
+ # a hash that defines filters that can be used while searching via the search
+ # field of the dashboard.
+ #
+ # For example to add an option to search for open resources by typing "open:"
+ # in the search field:
+ #
+ # COLLECTION_FILTERS = {
+ # open: ->(resources) { resources.where(open: true) }
+ # }.freeze
+ COLLECTION_FILTERS = {}.freeze
+
+ # Overwrite this method to customize how platform apps are displayed
+ # across all pages of the admin dashboard.
+ #
+ # def display_resource(platform_app)
+ # "PlatformApp ##{platform_app.id}"
+ # end
+end
diff --git a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
index bc024e024..1e4d9c733 100644
--- a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
+++ b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
@@ -29,46 +29,57 @@
&::before {
right: 0;
- top: 60%;
}
}
- .multiselect__content .multiselect__option {
- font-size: $font-size-small;
- font-weight: $font-weight-normal;
+ .multiselect__content {
+ max-width: 100%;
- &.multiselect__option--highlight {
- background: var(--white);
- color: var(--color-body);
- }
+ .multiselect__option {
+ font-size: $font-size-small;
+ font-weight: $font-weight-normal;
- &.multiselect__option--highlight:hover {
- background: var(--w-50);
- color: var(--color-body);
-
- &::after {
- background: var(--w-50);
- color: var(--s-600);
+ span {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
}
- }
- &.multiselect__option--highlight::after {
- background: transparent;
- }
-
- &.multiselect__option--selected {
- background: var(--w-400);
- color: var(--white);
+ &.multiselect__option--highlight {
+ background: var(--white);
+ color: var(--color-body);
+ }
&.multiselect__option--highlight:hover {
- background: var(--w-600);
- color: var(--white);
+ background: var(--w-50);
+ color: var(--color-body);
&::after {
- background: transparent;
+ background: var(--w-50);
+ color: var(--s-600);
+ }
+ }
+
+ &.multiselect__option--highlight::after {
+ background: transparent;
+ }
+
+ &.multiselect__option--selected {
+ background: var(--w-400);
+ color: var(--white);
+
+ &.multiselect__option--highlight:hover {
+ background: var(--w-600);
color: var(--white);
- &:hover {
+ &::after {
+ background: transparent;
+ color: var(--white);
+ }
+
+ &::after:hover {
color: var(--color-body);
}
}
@@ -126,7 +137,6 @@
}
.sidebar-labels-wrap {
-
&.has-edited,
&:hover {
.multiselect {
@@ -149,7 +159,6 @@
}
}
-
.multiselect-wrap--small {
$multiselect-height: 3.8rem;
diff --git a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
index dc2a4c2d1..ce29e226e 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
@@ -14,7 +14,7 @@ $resolve-button-width: 13.2rem;
border: 1px solid var(--color-border);
border-radius: var(--space-smaller);
margin-right: var(--space-small);
- width: 20.2rem;
+ width: 21.6rem;
.icon {
color: $medium-gray;
@@ -25,11 +25,12 @@ $resolve-button-width: 13.2rem;
}
.multiselect {
+ border-radius: var(--border-radius-small);
margin: 0;
min-width: 0;
.multiselect__tags {
- border: 0;
+ border-color: transparent;
}
}
}
diff --git a/app/javascript/dashboard/components/SnackbarContainer.vue b/app/javascript/dashboard/components/SnackbarContainer.vue
index 294dc6185..8414afc7e 100644
--- a/app/javascript/dashboard/components/SnackbarContainer.vue
+++ b/app/javascript/dashboard/components/SnackbarContainer.vue
@@ -2,14 +2,13 @@