ahoy_email 2.2.0 → 2.3.1

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: 238e4d5f6eb74588ea6d9d628cce5b7c69fa584741280c9d766fa13c5ef8164a
4
+ data.tar.gz: 3dedf13c78c400d139bb9450fc01ceb1a56c844b463ff0cafdf12fc7f6553573
5
5
  SHA512:
6
- metadata.gz: 1d7556134bb052f06eea6755f668cdd7c4eb5355dcbe46098894ff6b5ed8735b4c20b240378bafebb9b03b9c2525cf4e7b79e9a0fa240bf1a86289b8c0eb2c2b
7
- data.tar.gz: b576323d7768c3c0872afecbbfa35a7ce12b1d8df9a6dc9877d96e691e1d4675a60ef590e00705be8c245a543dc151249504ba511a96d6bbf37ead17afcf6666
6
+ metadata.gz: 4cc75f90c29e23f1c4dc5e87cfa626cbff9a6a0f3c23850f5813bb3890f1afbc72912e94bc8fb5f15f87c0f32794c7031f5559d0255d52065412e73dba793985
7
+ data.tar.gz: 559b2bec029c2d659c10799ff38453b939ff615f84a7a4041670badf3b96c8faa91d9f6037f9147d0003422197bdc430ddd2e075f391391c01905cb522abc2f6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2.3.1 (2024-09-09)
2
+
3
+ - Fixed deprecation warning with Rails 7.1
4
+
5
+ ## 2.3.0 (2024-06-01)
6
+
7
+ - Added support for secret token rotation
8
+ - Improved secret token generation
9
+
1
10
  ## 2.2.0 (2023-07-02)
2
11
 
3
12
  - 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.2]
110
110
  def change
111
111
  add_column :ahoy_messages, :coupon_id, :integer
112
112
  end
@@ -156,7 +156,7 @@ end
156
156
  Delete older data with:
157
157
 
158
158
  ```ruby
159
- Ahoy::Message.where("created_at < ?", 1.year.ago).in_batches.delete_all
159
+ Ahoy::Message.where("sent_at < ?", 1.year.ago).in_batches.delete_all
160
160
  ```
161
161
 
162
162
  Delete data for a specific user with:
@@ -324,54 +324,6 @@ Get stats for a campaign
324
324
  AhoyEmail.stats("my-campaign")
325
325
  ```
326
326
 
327
- ## Upgrading
328
-
329
- ### 2.0
330
-
331
- Ahoy Email 2.0 brings a number of changes. Here are a few to be aware of:
332
-
333
- - The `to` field is encrypted by default for new installations. If you’d like to encrypt an existing installation, install [Lockbox](https://github.com/ankane/lockbox) and [Blind Index](https://github.com/ankane/blind_index) and follow the Lockbox instructions for [migrating existing data](https://github.com/ankane/lockbox#migrating-existing-data).
334
-
335
- For the model, create `app/models/ahoy/message.rb` with:
336
-
337
- ```ruby
338
- class Ahoy::Message < ActiveRecord::Base
339
- self.table_name = "ahoy_messages"
340
-
341
- belongs_to :user, polymorphic: true, optional: true
342
-
343
- encrypts :to, migrating: true
344
- blind_index :to, migrating: true
345
- end
346
- ```
347
-
348
- - The `track` method has been broken into:
349
-
350
- - `has_history` for message history
351
- - `utm_params` for UTM tagging
352
- - `track_clicks` for click analytics
353
-
354
- - Message history is no longer enabled by default. Add `has_history` to individual mailers, or create an initializer with:
355
-
356
- ```ruby
357
- AhoyEmail.default_options[:message] = true
358
- ```
359
-
360
- - For privacy, open tracking has been removed.
361
-
362
- - For clicks, we encourage you to try [aggregate analytics](#click-analytics) to measure the performance of campaigns. You can use a library like [Rollup](https://github.com/ankane/rollup) to aggregate existing data, then drop the `token` and `clicked_at` columns.
363
-
364
- To keep individual analytics, use `has_history` and `track_clicks campaign: false` and create an initializer with:
365
-
366
- ```ruby
367
- AhoyEmail.save_token = true
368
- AhoyEmail.subscribers << AhoyEmail::MessageSubscriber
369
- ```
370
-
371
- If you use a custom subscriber, `:message` is no longer included in click events. You can use `:token` to query the message if needed.
372
-
373
- - Users are shown a link expired page when signature verification fails instead of being redirected to the homepage when `AhoyEmail.invalid_redirect_url` is not set
374
-
375
327
  ## History
376
328
 
377
329
  View the [changelog](https://github.com/ankane/ahoy_email/blob/master/CHANGELOG.md)
@@ -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,10 +4,14 @@ 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
10
- elsif app.respond_to?(:secrets)
14
+ elsif app.respond_to?(:secrets) && (Rails::VERSION::STRING.to_f < 7.1 || app.config.paths["config/secrets"].existent.any?)
11
15
  app.secrets
12
16
  else
13
17
  app.config
@@ -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.1"
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.1
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-09-09 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.16
127
127
  signing_key:
128
128
  specification_version: 4
129
129
  summary: First-party email analytics for Rails