ahoy_email 2.2.0 → 2.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: 9c664f526e52ac8f97cf3d5fe5b154ab6396e3f5c8db3d42c7c76022df9e0b35
4
- data.tar.gz: 063f9ac13bebe763ff2e33677e68bebd75f46837c5e63b920208e2547725a2a9
3
+ metadata.gz: 55896391d55a0158cdf032a2d7a29860984db55a4c116b96317e8986acdef143
4
+ data.tar.gz: 6c1c742d62fa0c2b2b62832dc0030fa7dad31839fc5e015f3806766c14ce9804
5
5
  SHA512:
6
- metadata.gz: 1d7556134bb052f06eea6755f668cdd7c4eb5355dcbe46098894ff6b5ed8735b4c20b240378bafebb9b03b9c2525cf4e7b79e9a0fa240bf1a86289b8c0eb2c2b
7
- data.tar.gz: b576323d7768c3c0872afecbbfa35a7ce12b1d8df9a6dc9877d96e691e1d4675a60ef590e00705be8c245a543dc151249504ba511a96d6bbf37ead17afcf6666
6
+ metadata.gz: 411579c14c08b65b59aaf34dafa6e37e0fdff00acc2232eb9962c31ce7077826aa228d3e7e7b09f657b2e6a2542b003b005e43237106b162ffd60930a13e9cf8
7
+ data.tar.gz: a042562ba6f0a1ed0e342f823bd75b7960fdae2490699f02cbfd1320f81fd82cd19bdaabb437369825701656eb78f5a1663a14ccf918c28605257c3af966dbbf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 2.3.0 (2024-06-01)
2
+
3
+ - Added support for secret token rotation
4
+ - Improved secret token generation
5
+
1
6
  ## 2.2.0 (2023-07-02)
2
7
 
3
8
  - Removed support for Ruby < 3 and Rails < 6.1
data/README.md CHANGED
@@ -4,9 +4,9 @@ First-party email analytics for Rails
4
4
 
5
5
  :fire: For web and native app analytics, check out [Ahoy](https://github.com/ankane/ahoy)
6
6
 
7
- :bullettrain_side: To manage unsubscribes, check out [Mailkick](https://github.com/ankane/mailkick)
7
+ :bullettrain_side: To manage email subscriptions, check out [Mailkick](https://github.com/ankane/mailkick)
8
8
 
9
- [![Build Status](https://github.com/ankane/ahoy_email/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy_email/actions)
9
+ [![Build Status](https://github.com/ankane/ahoy_email/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/ahoy_email/actions)
10
10
 
11
11
  ## Installation
12
12
 
@@ -106,7 +106,7 @@ user.messages
106
106
  Add extra data to messages. Create a migration like:
107
107
 
108
108
  ```ruby
109
- class AddCouponIdToAhoyMessages < ActiveRecord::Migration[7.0]
109
+ class AddCouponIdToAhoyMessages < ActiveRecord::Migration[7.1]
110
110
  def change
111
111
  add_column :ahoy_messages, :coupon_id, :integer
112
112
  end
@@ -11,24 +11,23 @@ module Ahoy
11
11
  end
12
12
 
13
13
  def click
14
- if params[:id]
15
- # legacy
14
+ legacy = params[:id]
15
+ if legacy
16
16
  token = params[:id].to_s
17
+ campaign = nil
17
18
  url = params[:url].to_s
18
19
  signature = params[:signature].to_s
19
- expected_signature = OpenSSL::HMAC.hexdigest("SHA1", AhoyEmail::Utils.secret_token, url)
20
20
  else
21
21
  token = params[:t].to_s
22
22
  campaign = params[:c].to_s
23
23
  url = params[:u].to_s
24
24
  signature = params[:s].to_s
25
- expected_signature = AhoyEmail::Utils.signature(token: token, campaign: campaign, url: url)
26
25
  end
27
26
 
28
27
  redirect_options = {}
29
28
  redirect_options[:allow_other_host] = true if ActionPack::VERSION::MAJOR >= 7
30
29
 
31
- if ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
30
+ if AhoyEmail::Utils.signature_verified?(legacy: legacy, token: token, campaign: campaign, url: url, signature: signature)
32
31
  data = {}
33
32
  data[:campaign] = campaign if campaign
34
33
  data[:token] = token
@@ -4,6 +4,10 @@ module AhoyEmail
4
4
  class Engine < ::Rails::Engine
5
5
  initializer "ahoy_email" do |app|
6
6
  AhoyEmail.secret_token ||= begin
7
+ tokens = []
8
+ tokens << app.key_generator.generate_key("ahoy_email")
9
+
10
+ # TODO remove in 3.0
7
11
  creds =
8
12
  if app.respond_to?(:credentials) && app.credentials.secret_key_base
9
13
  app.credentials
@@ -15,7 +19,9 @@ module AhoyEmail
15
19
 
16
20
  token = creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
17
21
  token ||= app.secret_key_base # should come first, but need to maintain backward compatibility
18
- token
22
+ tokens << token
23
+
24
+ tokens
19
25
  end
20
26
  end
21
27
  end
@@ -7,13 +7,28 @@ module AhoyEmail
7
7
  }
8
8
 
9
9
  class << self
10
- def signature(token:, campaign:, url:)
10
+ def signature(token:, campaign:, url:, secret_token: nil)
11
+ secret_token ||= secret_tokens.first
12
+
11
13
  # encode and join with a character outside encoding
12
14
  data = [token, campaign, url].map { |v| Base64.strict_encode64(v.to_s) }.join("|")
13
15
 
14
16
  Base64.urlsafe_encode64(OpenSSL::HMAC.digest("SHA256", secret_token, data), padding: false)
15
17
  end
16
18
 
19
+ def signature_verified?(legacy:, token:, campaign:, url:, signature:)
20
+ secret_tokens.any? do |secret_token|
21
+ expected_signature =
22
+ if legacy
23
+ OpenSSL::HMAC.hexdigest("SHA1", secret_token, url)
24
+ else
25
+ signature(token: token, campaign: campaign, url: url, secret_token: secret_token)
26
+ end
27
+
28
+ ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
29
+ end
30
+ end
31
+
17
32
  def publish(name, event)
18
33
  method_name = "track_#{name}"
19
34
  AhoyEmail.subscribers.each do |subscriber|
@@ -27,8 +42,8 @@ module AhoyEmail
27
42
  end
28
43
  end
29
44
 
30
- def secret_token
31
- AhoyEmail.secret_token || (raise "Secret token is empty")
45
+ def secret_tokens
46
+ Array(AhoyEmail.secret_token || (raise "Secret token is empty"))
32
47
  end
33
48
  end
34
49
  end
@@ -1,3 +1,3 @@
1
1
  module AhoyEmail
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ahoy_email
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-02 00:00:00.000000000 Z
11
+ date: 2024-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 3.4.10
126
+ rubygems_version: 3.5.9
127
127
  signing_key:
128
128
  specification_version: 4
129
129
  summary: First-party email analytics for Rails