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 +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
|
-
[![Build Status](https://github.com/ankane/ahoy_email/workflows/build/badge.svg
|
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.
|
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
|