Account webhooks sign outgoing payloads with HMAC-SHA256, but agent bot and API inbox webhooks were delivered unsigned. This PR adds the same signing to both. Each model gets a dedicated `secret` column rather than reusing the agent bot's `access_token` (for API auth back into Chatwoot) or the API inbox's `hmac_token` (for inbound contact identity verification). These serve different trust boundaries and shouldn't be coupled — rotating a signing secret shouldn't invalidate API access or contact verification. The existing `Webhooks::Trigger` already signs when a secret is present, so the backend change is just passing `secret:` through to the jobs. Shared token logic is extracted into a `WebhookSecretable` concern included by `Webhook`, `AgentBot`, and `Channel::Api`. The frontend reuses the existing `AccessToken` component for secret display. Secrets are admin-only and excluded from enterprise audit logs. ### How to test Point an agent bot or API inbox webhook URL at a request inspector. Send a message and verify `X-Chatwoot-Signature` and `X-Chatwoot-Timestamp` headers are present. Reset the secret from settings and confirm subsequent deliveries use the new value. --------- Co-authored-by: Sojan Jose <sojan@pepalo.com>
71 lines
1.6 KiB
Ruby
71 lines
1.6 KiB
Ruby
# == Schema Information
|
|
#
|
|
# Table name: agent_bots
|
|
#
|
|
# id :bigint not null, primary key
|
|
# bot_config :jsonb
|
|
# bot_type :integer default("webhook")
|
|
# description :string
|
|
# name :string
|
|
# outgoing_url :string
|
|
# secret :string
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# account_id :bigint
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_agent_bots_on_account_id (account_id)
|
|
#
|
|
|
|
class AgentBot < ApplicationRecord
|
|
include AccessTokenable
|
|
include Avatarable
|
|
|
|
include WebhookSecretable
|
|
|
|
scope :accessible_to, lambda { |account|
|
|
account_id = account&.id
|
|
where(account_id: [nil, account_id])
|
|
}
|
|
|
|
has_many :agent_bot_inboxes, dependent: :destroy_async
|
|
has_many :inboxes, through: :agent_bot_inboxes
|
|
has_many :messages, as: :sender, dependent: :nullify
|
|
has_many :assigned_conversations, class_name: 'Conversation',
|
|
foreign_key: :assignee_agent_bot_id,
|
|
dependent: :nullify,
|
|
inverse_of: :assignee_agent_bot
|
|
belongs_to :account, optional: true
|
|
enum bot_type: { webhook: 0 }
|
|
|
|
validates :outgoing_url, length: { maximum: Limits::URL_LENGTH_LIMIT }
|
|
|
|
def available_name
|
|
name
|
|
end
|
|
|
|
def push_event_data(inbox = nil)
|
|
{
|
|
id: id,
|
|
name: name,
|
|
avatar_url: avatar_url || inbox&.avatar_url,
|
|
type: 'agent_bot'
|
|
}
|
|
end
|
|
|
|
def webhook_data
|
|
{
|
|
id: id,
|
|
name: name,
|
|
type: 'agent_bot'
|
|
}
|
|
end
|
|
|
|
def system_bot?
|
|
account.nil?
|
|
end
|
|
end
|
|
|
|
AgentBot.include_mod_with('Audit::AgentBot')
|