From 23fe40fa8159534bab63298ceb27a8676d62c81d Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 22 Jan 2026 20:03:02 +0000 Subject: [PATCH] Correctly initialise WebPush connection (#2417) The `ipaddr` arg here is being interpreted as a positional arg (since the keyword arg doesn't exist), which results in an invalid connection object. This was causing push notifications to silently fail. We should initialise the property on the object instead. --- config/initializers/web_push.rb | 3 +- test/lib/web_push/persistent_request_test.rb | 26 ++++++++++++ test/test_helper.rb | 1 + test/webmock_ipaddr_extension.rb | 44 ++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 test/lib/web_push/persistent_request_test.rb create mode 100644 test/webmock_ipaddr_extension.rb diff --git a/config/initializers/web_push.rb b/config/initializers/web_push.rb index 6914dae22..4487268f5 100644 --- a/config/initializers/web_push.rb +++ b/config/initializers/web_push.rb @@ -20,7 +20,8 @@ module WebPush::PersistentRequest endpoint_ip = @options[:endpoint_ip] if endpoint_ip - http = Net::HTTP.new(uri.host, uri.port, ipaddr: endpoint_ip) + http = Net::HTTP.new(uri.host, uri.port) + http.ipaddr = endpoint_ip http.use_ssl = true http.ssl_timeout = @options[:ssl_timeout] unless @options[:ssl_timeout].nil? http.open_timeout = @options[:open_timeout] unless @options[:open_timeout].nil? diff --git a/test/lib/web_push/persistent_request_test.rb b/test/lib/web_push/persistent_request_test.rb new file mode 100644 index 000000000..2cf8a6c7c --- /dev/null +++ b/test/lib/web_push/persistent_request_test.rb @@ -0,0 +1,26 @@ +require "test_helper" + +class WebPush::PersistentRequestTest < ActiveSupport::TestCase + PUBLIC_TEST_IP = "142.250.185.206" + ENDPOINT = "https://fcm.googleapis.com/fcm/send/test123" + + test "pins connection to endpoint_ip" do + request = stub_request(:post, ENDPOINT) + .with(ipaddr: PUBLIC_TEST_IP) + .to_return(status: 201) + + notification = WebPush::Notification.new( + title: "Test", + body: "Test notification", + path: "/test", + badge: 0, + endpoint: ENDPOINT, + endpoint_ip: PUBLIC_TEST_IP, + p256dh_key: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM", + auth_key: "tBHItJI5svbpez7KI4CCXg" + ) + notification.deliver + + assert_requested request + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 14893b030..b1f813761 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,6 +3,7 @@ require_relative "../config/environment" require "rails/test_help" require "webmock/minitest" +require_relative "webmock_ipaddr_extension" require "vcr" require "mocha/minitest" require "turbo/broadcastable/test_helper" diff --git a/test/webmock_ipaddr_extension.rb b/test/webmock_ipaddr_extension.rb new file mode 100644 index 000000000..874654f87 --- /dev/null +++ b/test/webmock_ipaddr_extension.rb @@ -0,0 +1,44 @@ +# Extends WebMock to support ipaddr matching for testing IP pinning. +# +# Usage: +# stub_request(:post, "https://example.com/push") +# .with(ipaddr: "93.184.216.34") +# .to_return(status: 201) +# +# If the HTTP connection's ipaddr doesn't match, the stub won't match and +# WebMock will raise an error about an unregistered request. + +module WebMock + class RequestSignature + attr_accessor :ipaddr + end + + module RequestPatternIpaddrExtension + attr_accessor :ipaddr_pattern + + def assign_options(options) + options = options.dup + @ipaddr_pattern = options.delete(:ipaddr) || options.delete("ipaddr") + super(options) + end + + def matches?(request_signature) + super && ipaddr_matches?(request_signature) + end + + private + def ipaddr_matches?(request_signature) + @ipaddr_pattern.nil? || @ipaddr_pattern == request_signature.ipaddr + end + end + + RequestPattern.prepend RequestPatternIpaddrExtension + + module NetHTTPUtilityIpaddrExtension + def request_signature_from_request(net_http, request, body = nil) + super.tap { |signature| signature.ipaddr = net_http.ipaddr } + end + end + + NetHTTPUtility.singleton_class.prepend NetHTTPUtilityIpaddrExtension +end