1
0
зеркало из https://github.com/glebtv/yookassa.git synced 2026-08-28 15:16:18 +03:00

fix webhook auth diagnostics and token length handling

Этот коммит содержится в:
Gleb Tv
2026-03-25 18:27:58 +03:00
родитель 5b002870b7
Коммит 185a470641
2 изменённых файлов: 63 добавлений и 10 удалений

Просмотреть файл

@@ -24,21 +24,42 @@ module Yookassa
end
def authentic_webhook?(payload)
token_valid? && source_ip_allowed? && payload_matches_api_object?(payload)
token_ok = token_valid?
ip_ok = source_ip_allowed?
api_ok = payload_matches_api_object?(payload)
unless token_ok && ip_ok && api_ok
Rails.logger.info("[Yookassa Webhook] Auth failed: token=#{token_ok}, ip=#{ip_ok}, api=#{api_ok}, remote_ip=#{request.remote_ip}")
end
token_ok && ip_ok && api_ok
end
def token_valid?
token = params[:token].to_s
configured_token = Yookassa.config.webhook_token.to_s
return false if token.empty? || configured_token.empty?
if token.empty? || configured_token.empty?
Rails.logger.info("[Yookassa Webhook] Token validation failed: param_token=#{token.empty? ? "empty" : "present"}, configured_token=#{configured_token.empty? ? "empty" : "present"}")
return false
end
ActiveSupport::SecurityUtils.secure_compare(token, configured_token)
if token.bytesize != configured_token.bytesize
Rails.logger.info("[Yookassa Webhook] Token comparison: false (length mismatch)")
return false
end
result = ActiveSupport::SecurityUtils.secure_compare(token, configured_token)
Rails.logger.info("[Yookassa Webhook] Token comparison: #{result}")
result
end
def source_ip_allowed?
source_ip = request.remote_ip
allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
rescue IPAddr::InvalidAddressError
allowed = allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
Rails.logger.info("[Yookassa Webhook] IP check: remote_ip=#{source_ip}, allowed=#{allowed}, cidrs=#{allowed_cidrs}")
allowed
rescue IPAddr::InvalidAddressError => e
Rails.logger.info("[Yookassa Webhook] IP check failed: #{e.message}")
false
end
@@ -55,17 +76,29 @@ module Yookassa
def payload_matches_api_object?(payload)
object = extract_object(payload)
return false unless object.is_a?(Hash)
unless object.is_a?(Hash)
Rails.logger.info("[Yookassa Webhook] API object check failed: object is not a Hash")
return false
end
object_id = object["id"].to_s
object_status = object["status"].to_s
return false if object_id.empty? || object_status.empty?
if object_id.empty? || object_status.empty?
Rails.logger.info("[Yookassa Webhook] API object check failed: object_id=#{object_id}, object_status=#{object_status}")
return false
end
fetched_object = fetch_object_from_api(payload, object_id)
return false if fetched_object.nil?
if fetched_object.nil?
Rails.logger.info("[Yookassa Webhook] API object check failed: could not fetch object #{object_id}")
return false
end
fetched_object.id == object_id && fetched_object.status == object_status
rescue StandardError
result = fetched_object.id == object_id && fetched_object.status == object_status
Rails.logger.info("[Yookassa Webhook] API object check: fetched_id=#{fetched_object.id}, fetched_status=#{fetched_object.status}, matches=#{result}")
result
rescue StandardError => e
Rails.logger.info("[Yookassa Webhook] API object check failed: #{e.message}")
false
end

Просмотреть файл

@@ -36,6 +36,11 @@ RSpec.describe Yookassa::WebhooksController do
end
it "rejects webhook when token is invalid" do
payments_client = instance_double(Yookassa::Payments)
allow(Yookassa).to receive(:payments).and_return(payments_client)
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "succeeded"))
post "/yookassa/webhooks/wrong-token", payload, headers
expect(last_response.status).to eq(401)
@@ -64,6 +69,21 @@ RSpec.describe Yookassa::WebhooksController do
end
end
describe "logging", :rails do
it "logs auth result when webhook authentication fails" do
controller = described_class.new
allow(controller).to receive(:token_valid?).and_return(true)
allow(controller).to receive(:source_ip_allowed?).and_return(false)
allow(controller).to receive(:payload_matches_api_object?).and_return(true)
allow(controller).to receive(:request).and_return(instance_double(ActionDispatch::Request, remote_ip: "192.168.0.10"))
expect(Rails.logger).to receive(:info).with(include("Auth failed: token=true, ip=false, api=true"))
expect(controller.send(:authentic_webhook?, {})).to eq(false)
end
end
describe "IP source check", :rails do
it "uses request.remote_ip" do
controller = described_class.new