pezza_action_push_web 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb9c4ebdcbd17365efb10ed10e722ddc65e1313c46904a2fefcab41e2a77d7b3
4
- data.tar.gz: b470819fb3149a2a9b48d98ccdfce99aafa86bf7f7f153eb0019c33b15a2e9ac
3
+ metadata.gz: 93d488729aae040d4f85f2251e45aefb608835869b9c5d568f8e538c02b0e41d
4
+ data.tar.gz: f6984298ffdcf0b4722bf7eb4a7efdd860befe66ac602add26061596a2bef0c2
5
5
  SHA512:
6
- metadata.gz: 3b550df8977bdd96661dd9cb96993697d409b0f6555a22e235429971b2e7cbd68a5800205526217b622a101774f1482b65fc60ef4a6ecd99467d8bc28cfb353b
7
- data.tar.gz: 644ee1e71e8d4065e87fe9334078725b801ff3a9c2a7ea6f81de719e3e53ec139c4642c4d19852fbed0b798eb051854901a79f3f561189d2419971dd9b5eadef
6
+ metadata.gz: eb237077e3d17e665abe9213a2a1aac9dbb53458b78ffef87ae3533c7239b2695927f3d94f3c0d01c0ed97e0c568fe12c4a6432a0bdcbac2f326ce2eb0469fdc
7
+ data.tar.gz: 0d72b0362ea10d90bd530e89eaf4efb083f8e00dff5cf287f966106782bc3ca33f014e45e7498a183844fd2b6e5c3db2441e2c36aa214e29ef3486acc010c717
data/README.md CHANGED
@@ -93,8 +93,8 @@ shared:
93
93
  # Change the subject (default: mailto:sender@example.com).
94
94
  # expiration: mailto:support@my-domain.com
95
95
 
96
- # Change the urgency (default: normal). You also choose to set this at the notification level.
97
- # urgency: high
96
+ # Change the urgency (default: high). You also choose to set this at the notification level.
97
+ # urgency: normal
98
98
  ```
99
99
 
100
100
  This file contains the configuration for the push notification services you want to use.
@@ -1,11 +1,8 @@
1
1
  module ActionPushWeb
2
2
  class SubscriptionsController < ApplicationController
3
3
  def create
4
- if subscription = ApplicationPushSubscription.find_by(push_subscription_params)
5
- subscription.touch
6
- else
7
- ApplicationPushSubscription.create! push_subscription_params.merge(user_agent: request.user_agent)
8
- end
4
+ ApplicationPushSubscription.create_with(user_agent: request.user_agent).
5
+ create_or_find_by!(push_subscription_params)
9
6
 
10
7
  head :ok
11
8
  end
@@ -2,12 +2,58 @@ module ActionPushWeb
2
2
  class Subscription < ApplicationRecord
3
3
  include ActiveSupport::Rescuable
4
4
 
5
+ PERMITTED_ENDPOINT_HOSTS = %w[
6
+ jmt17.google.com
7
+ fcm.googleapis.com
8
+ updates.push.services.mozilla.com
9
+ web.push.apple.com
10
+ notify.windows.com
11
+ ].freeze
12
+
5
13
  rescue_from(TokenError) { destroy! }
6
14
 
7
15
  belongs_to :owner, polymorphic: true, optional: true
8
16
 
17
+ validates :endpoint, presence: true
18
+ validate :validate_endpoint_url
19
+
9
20
  def push(notification)
10
21
  ActionPushWeb.push(SubscriptionNotification.new(notification:, subscription: self))
11
22
  end
23
+
24
+ def resolved_endpoint_ip
25
+ return @resolved_endpoint_ip if defined?(@resolved_endpoint_ip)
26
+ @resolved_endpoint_ip = Surfguard.resolve_public_ips(endpoint_uri&.host).first
27
+ rescue Surfguard::Unresolvable
28
+ # A host that resolves to nothing has no usable public IP, same outcome as
29
+ # one whose only addresses are blocked: no endpoint IP to pin, which fails
30
+ # endpoint validation. Push has no lookup-failed surface to distinguish.
31
+ @resolved_endpoint_ip = nil
32
+ end
33
+
34
+ private
35
+
36
+ def endpoint_uri
37
+ @endpoint_uri ||= URI.parse(endpoint) if endpoint.present?
38
+ rescue URI::InvalidURIError
39
+ nil
40
+ end
41
+
42
+ def validate_endpoint_url
43
+ if endpoint_uri.nil?
44
+ errors.add(:endpoint, "is not a valid URL")
45
+ elsif endpoint_uri.scheme != "https"
46
+ errors.add(:endpoint, "must use HTTPS")
47
+ elsif !permitted_endpoint_host?
48
+ errors.add(:endpoint, "is not a permitted push service")
49
+ elsif resolved_endpoint_ip.nil?
50
+ errors.add(:endpoint, "resolves to a private or invalid IP address")
51
+ end
52
+ end
53
+
54
+ def permitted_endpoint_host?
55
+ host = endpoint_uri&.host&.downcase
56
+ PERMITTED_ENDPOINT_HOSTS.any? { |permitted| host&.end_with?(permitted) }
57
+ end
12
58
  end
13
59
  end
@@ -0,0 +1,6 @@
1
+ class AddUniqueIndex < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_index :action_push_web_subscriptions,
4
+ [ :owner_type, :owner_id, :endpoint ], unique: true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class AddUniqueIndexForNullOwner < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_index :action_push_web_subscriptions, :endpoint, unique: true,
4
+ where: "owner_id IS NULL AND owner_type IS NULL"
5
+ end
6
+ end
@@ -46,7 +46,7 @@ module ActionPushWeb
46
46
  end
47
47
 
48
48
  def urgency
49
- (@urgency.presence || config.fetch(:urgency, :normal)).to_s
49
+ (@urgency.presence || config.fetch(:urgency, :high)).to_s
50
50
  end
51
51
 
52
52
  def as_json
@@ -10,7 +10,11 @@ module ActionPushWeb
10
10
  it.body = payload
11
11
  end
12
12
 
13
- connection.request(uri, request).tap { handle_response(it) }
13
+ if resolved_endpoint_ip
14
+ pinned_connection.request(request)
15
+ else
16
+ connection.request(uri, request)
17
+ end.tap { handle_response(it) }
14
18
  rescue OpenSSL::OpenSSLError
15
19
  raise TokenError.new
16
20
  end
@@ -20,7 +24,7 @@ module ActionPushWeb
20
24
  attr_reader :config, :notification
21
25
 
22
26
  delegate :title, :body, :icon_path, :path, :silent, :badge, :endpoint, :p256dh_key,
23
- :auth_key, to: :notification
27
+ :auth_key, :resolved_endpoint_ip, to: :notification
24
28
 
25
29
  def message
26
30
  JSON.generate title:, options: { body:, icon: icon_path, silent:, badge:, data: { path: } }
@@ -38,6 +42,13 @@ module ActionPushWeb
38
42
  @uri ||= URI.parse(endpoint)
39
43
  end
40
44
 
45
+ def pinned_connection
46
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
47
+ http.ipaddr = resolved_endpoint_ip
48
+ http.use_ssl = true
49
+ end
50
+ end
51
+
41
52
  def headers
42
53
  headers = {}
43
54
  headers["Content-Type"] = "application/octet-stream"
@@ -8,6 +8,6 @@ module ActionPushWeb
8
8
  attr_reader :notification, :subscription
9
9
 
10
10
  delegate_missing_to :notification
11
- delegate :endpoint, :p256dh_key, :auth_key, to: :subscription
11
+ delegate :endpoint, :p256dh_key, :auth_key, :resolved_endpoint_ip, to: :subscription
12
12
  end
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module ActionPushWeb
2
- VERSION = "0.1.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -4,6 +4,7 @@ require "action_push_web/errors"
4
4
  require "net/http"
5
5
  require "net/http/persistent"
6
6
  require "jwt"
7
+ require "surfguard"
7
8
 
8
9
  loader= Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
9
10
  loader.ignore("#{__dir__}/generators")
@@ -15,8 +15,8 @@ shared:
15
15
  # Change the subject (default: mailto:sender@example.com).
16
16
  # subject: mailto:support@my-domain.com
17
17
 
18
- # Change the urgency (default: normal). You also choose to set this at the notification level.
19
- # urgency: high
18
+ # Change the urgency (default: high). You also choose to set this at the notification level.
19
+ # urgency: normal
20
20
 
21
21
  # Change the icon path (default: nil). You also choose to set this at the notification level.
22
22
  # icon_path: https://example.com/icon.png
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pezza_action_push_web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Pezza
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: surfguard
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
96
110
  description: Send push notifications to web apps
97
111
  email:
98
112
  - pezza@hey.com
@@ -116,6 +130,8 @@ files:
116
130
  - config/importmap.rb
117
131
  - config/routes.rb
118
132
  - db/migrate/20250907213606_create_action_push_web_subscriptions.rb
133
+ - db/migrate/20251209235643_add_unique_index.rb
134
+ - db/migrate/20260517003340_add_unique_index_for_null_owner.rb
119
135
  - lib/action_push_web.rb
120
136
  - lib/action_push_web/engine.rb
121
137
  - lib/action_push_web/errors.rb
@@ -156,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
172
  - !ruby/object:Gem::Version
157
173
  version: '0'
158
174
  requirements: []
159
- rubygems_version: 3.7.1
175
+ rubygems_version: 4.0.18
160
176
  specification_version: 4
161
177
  summary: Send push notifications to web apps
162
178
  test_files: []