Files
chatwoot/app/models/channel/web_widget.rb
Pranav de4430ea5d feat: Introduce allowed_domains for web widget (#12450)
We wanted to provide an option for users to specify the domains on which
they can show the website. The rest of the sites shouldn't see the
widget at all.

It's not possible generally through Origin because you can't get Origin
when loading via an iframe. What I've done is add frame ancestors for
the domains specified in allowed domains. I hope this solves most of the
problems.

This is added in a way that it won't affect existing widgets. Only If
they have configured allowed domains, it will start blocking. Otherwise,
it would follow the previous behavior without any changes.

This change supports called wild card domains as well. You can add a
comma‑separated list of domains, either wild card or regular domains.


---

To test, deploy to staging. Call the following API to update the
allowed_domains list.

```
URL: PATCH /api/v1/accounts/<account-id>/inboxes/<inbox-id>

Payload:
{
   "channel": { "allowed_domains": "*.chatwoot.dev,chatwoot.com" }
}

```



Fixes https://github.com/chatwoot/chatwoot/issues/1985
2025-09-17 10:01:27 +05:30

109 lines
3.9 KiB
Ruby

# == Schema Information
#
# Table name: channel_web_widgets
#
# id :integer not null, primary key
# allowed_domains :text default("")
# continuity_via_email :boolean default(TRUE), not null
# feature_flags :integer default(7), not null
# hmac_mandatory :boolean default(FALSE)
# hmac_token :string
# pre_chat_form_enabled :boolean default(FALSE)
# pre_chat_form_options :jsonb
# reply_time :integer default("in_a_few_minutes")
# website_token :string
# website_url :string
# welcome_tagline :string
# welcome_title :string
# widget_color :string default("#1f93ff")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer
#
# Indexes
#
# index_channel_web_widgets_on_hmac_token (hmac_token) UNIQUE
# index_channel_web_widgets_on_website_token (website_token) UNIQUE
#
class Channel::WebWidget < ApplicationRecord
include Channelable
include FlagShihTzu
self.table_name = 'channel_web_widgets'
EDITABLE_ATTRS = [:website_url, :widget_color, :welcome_title, :welcome_tagline, :reply_time, :pre_chat_form_enabled,
:continuity_via_email, :hmac_mandatory, :allowed_domains,
{ pre_chat_form_options: [:pre_chat_message, :require_email,
{ pre_chat_fields:
[:field_type, :label, :placeholder, :name, :enabled, :type, :enabled, :required,
:locale, { values: [] }, :regex_pattern, :regex_cue] }] },
{ selected_feature_flags: [] }].freeze
before_validation :validate_pre_chat_options
validates :website_url, presence: true
validates :widget_color, presence: true
has_many :portals, foreign_key: 'channel_web_widget_id', dependent: :nullify, inverse_of: :channel_web_widget
has_secure_token :website_token
has_secure_token :hmac_token
has_flags 1 => :attachments,
2 => :emoji_picker,
3 => :end_conversation,
4 => :use_inbox_avatar_for_bot,
:column => 'feature_flags',
:check_for_column => false
enum reply_time: { in_a_few_minutes: 0, in_a_few_hours: 1, in_a_day: 2 }
def name
'Website'
end
def web_widget_script
"
<script>
(function(d,t) {
var BASE_URL=\"#{ENV.fetch('FRONTEND_URL', '')}\";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+\"/packs/js/sdk.js\";
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: '#{website_token}',
baseUrl: BASE_URL
})
}
})(document,\"script\");
</script>
"
end
def validate_pre_chat_options
return if pre_chat_form_options.with_indifferent_access['pre_chat_fields'].present?
self.pre_chat_form_options = {
pre_chat_message: 'Share your queries or comments here.',
pre_chat_fields: [
{
'field_type': 'standard', 'label': 'Email Id', 'name': 'emailAddress', 'type': 'email', 'required': true, 'enabled': false
},
{
'field_type': 'standard', 'label': 'Full name', 'name': 'fullName', 'type': 'text', 'required': false, 'enabled': false
},
{
'field_type': 'standard', 'label': 'Phone number', 'name': 'phoneNumber', 'type': 'text', 'required': false, 'enabled': false
}
]
}
end
def create_contact_inbox(additional_attributes = {})
::ContactInboxWithContactBuilder.new({
inbox: inbox,
contact_attributes: { additional_attributes: additional_attributes }
}).perform
end
end