This switches widget and public inbox HMAC identity checks from direct string equality to constant-time comparison with a length guard. Fixes [https://linear.app/chatwoot/issue/CW-6937](https://linear.app/chatwoot/issue/CW-6937) Fixes [https://linear.app/chatwoot/issue/CW-7464](https://linear.app/chatwoot/issue/CW-7464) Fixes [https://linear.app/chatwoot/issue/CW-7227](https://linear.app/chatwoot/issue/CW-7227)
92 lines
2.8 KiB
Ruby
92 lines
2.8 KiB
Ruby
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
|
include WidgetHelper
|
|
|
|
before_action :validate_hmac, only: [:set_user]
|
|
before_action :validate_hmac_for_identified_update, only: [:update]
|
|
|
|
def show; end
|
|
|
|
def update
|
|
identify_contact(@contact)
|
|
end
|
|
|
|
def set_user
|
|
contact = nil
|
|
|
|
if a_different_contact?
|
|
@contact_inbox, @widget_auth_token = build_contact_inbox_with_token(@web_widget)
|
|
contact = @contact_inbox.contact
|
|
else
|
|
contact = @contact
|
|
end
|
|
|
|
@contact_inbox.update(hmac_verified: true) if should_verify_hmac?
|
|
|
|
identify_contact(contact)
|
|
end
|
|
|
|
# TODO : clean up this with proper routes delete contacts/custom_attributes
|
|
def destroy_custom_attributes
|
|
@contact.custom_attributes = @contact.custom_attributes.excluding(params[:custom_attributes])
|
|
@contact.save!
|
|
render json: @contact
|
|
end
|
|
|
|
private
|
|
|
|
def identify_contact(contact)
|
|
contact_identify_action = ContactIdentifyAction.new(
|
|
contact: contact,
|
|
params: permitted_params.to_h.deep_symbolize_keys,
|
|
discard_invalid_attrs: true
|
|
)
|
|
@contact = contact_identify_action.perform
|
|
end
|
|
|
|
def a_different_contact?
|
|
@contact.identifier.present? && @contact.identifier != permitted_params[:identifier]
|
|
end
|
|
|
|
# The plain update endpoint is also used for anonymous prechat updates
|
|
# (name/email/phone/custom_attributes with no identifier), which must keep
|
|
# working on hmac_mandatory inboxes. Only the identity-binding path, where an
|
|
# identifier is supplied and the contact can be rebound, requires HMAC.
|
|
def validate_hmac_for_identified_update
|
|
return if params[:identifier].blank?
|
|
|
|
validate_hmac
|
|
end
|
|
|
|
def validate_hmac
|
|
return unless should_verify_hmac?
|
|
|
|
render json: { error: 'HMAC failed: Invalid Identifier Hash Provided' }, status: :unauthorized unless valid_hmac?
|
|
end
|
|
|
|
def should_verify_hmac?
|
|
return false if params[:identifier_hash].blank? && !@web_widget.hmac_mandatory
|
|
|
|
# Taking an extra caution that the hmac is triggered whenever identifier is present
|
|
return false if params[:custom_attributes].present? && params[:identifier].blank?
|
|
|
|
true
|
|
end
|
|
|
|
def valid_hmac?
|
|
expected_hash = OpenSSL::HMAC.hexdigest(
|
|
'sha256',
|
|
@web_widget.hmac_token,
|
|
params[:identifier].to_s
|
|
)
|
|
identifier_hash = params[:identifier_hash].to_s
|
|
return false unless identifier_hash.bytesize == expected_hash.bytesize
|
|
|
|
ActiveSupport::SecurityUtils.secure_compare(identifier_hash, expected_hash)
|
|
end
|
|
|
|
def permitted_params
|
|
params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {},
|
|
additional_attributes: {})
|
|
end
|
|
end
|