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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +4 -52
- data/app/controllers/ahoy/messages_controller.rb +4 -5
- data/lib/ahoy_email/engine.rb +8 -2
- data/lib/ahoy_email/utils.rb +18 -3
- data/lib/ahoy_email/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 238e4d5f6eb74588ea6d9d628cce5b7c69fa584741280c9d766fa13c5ef8164a
|
4
|
+
data.tar.gz: 3dedf13c78c400d139bb9450fc01ceb1a56c844b463ff0cafdf12fc7f6553573
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
7
|
+
:bullettrain_side: To manage email subscriptions, check out [Mailkick](https://github.com/ankane/mailkick)
|
8
8
|
|
9
|
-
[](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.
|
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("
|
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
|
-
|
15
|
-
|
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
|
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
|
data/lib/ahoy_email/engine.rb
CHANGED
@@ -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
|
data/lib/ahoy_email/utils.rb
CHANGED
@@ -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
|
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
|
data/lib/ahoy_email/version.rb
CHANGED
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.
|
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:
|
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.
|
126
|
+
rubygems_version: 3.5.16
|
127
127
|
signing_key:
|
128
128
|
specification_version: 4
|
129
129
|
summary: First-party email analytics for Rails
|