ahoy_email 2.2.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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