mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-29 01:52:38 +09:00
7d5bb50b9b
- Resolve the guarded endpoint IP lazily inside WebPush::Notification#deliver (on the bounded delivery worker) instead of eagerly when the notification is built on the serial enqueue path, so a slow resolver can't stall the push job before any delivery starts. resolved_endpoint_ip only reads the already-loaded endpoint attribute, so it is safe off the AR connection. - Pin the delivery socket with an explicit nil proxy address so http_proxy/ https_proxy can't route the request through a proxy that re-resolves the host and defeats the ipaddr pin. - Revalidate an existing subscription on re-registration so a row predating endpoint validation gets the same 422 as a fresh create instead of being kept alive by touch. - Regression tests: resolution deferred to delivery, pin survives proxy env, legacy invalid row rejected with 422.
44 lines
1.9 KiB
Ruby
44 lines
1.9 KiB
Ruby
class WebPush::Notification
|
|
def initialize(title:, body:, path:, badge:, endpoint:, endpoint_ip_resolver:, p256dh_key:, auth_key:)
|
|
@title, @body, @path, @badge = title, body, path, badge
|
|
@endpoint, @endpoint_ip_resolver, @p256dh_key, @auth_key = endpoint, endpoint_ip_resolver, p256dh_key, auth_key
|
|
end
|
|
|
|
# @endpoint_ip_resolver resolves and guards the endpoint's public address --
|
|
# Push::Subscription's allowlist plus surfguard's private-network classification
|
|
# -- returning the IP to pin, or nil. It is invoked here, on the bounded
|
|
# delivery worker, rather than when the notification is built on the serial
|
|
# enqueue path, so a slow or stalled resolver can't hold up the push job before
|
|
# any delivery starts (one blocking lookup per recipient, serialized, would
|
|
# otherwise multiply a resolver timeout by the room's subscriber count).
|
|
#
|
|
# nil means the host resolved to nothing or to a blocked (private) address, so
|
|
# we skip delivery rather than let the request fall back to re-resolving the
|
|
# raw host -- which is what would reopen the SSRF for a subscription that
|
|
# slipped in before endpoint validation existed.
|
|
def deliver(connection: nil)
|
|
if endpoint_ip = @endpoint_ip_resolver.call
|
|
WebPush.payload_send \
|
|
message: encoded_message,
|
|
endpoint: @endpoint, endpoint_ip: endpoint_ip, p256dh: @p256dh_key, auth: @auth_key,
|
|
vapid: vapid_identification,
|
|
connection: connection,
|
|
urgency: "high"
|
|
end
|
|
end
|
|
|
|
private
|
|
def vapid_identification
|
|
{ subject: "mailto:support@37signals.com" }.merge \
|
|
Rails.configuration.x.vapid.symbolize_keys
|
|
end
|
|
|
|
def encoded_message
|
|
JSON.generate title: @title, options: { body: @body, icon: icon_path, data: { path: @path, badge: @badge } }
|
|
end
|
|
|
|
def icon_path
|
|
Rails.application.routes.url_helpers.account_logo_path
|
|
end
|
|
end
|