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 @@