helo-email-sdk 1.0.0.pre.beta.6 → 1.0.0.pre.beta.7
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/README.md +125 -0
- data/lib/helo/models/bounced_details.rb +11 -0
- data/lib/helo/models/bounced_webhook_payload.rb +1 -1
- data/lib/helo/models/clicked_details.rb +14 -0
- data/lib/helo/models/clicked_webhook_payload.rb +1 -1
- data/lib/helo/models/client_details.rb +10 -0
- data/lib/helo/models/complained_details.rb +9 -0
- data/lib/helo/models/complained_webhook_payload.rb +1 -1
- data/lib/helo/models/delivered_details.rb +9 -0
- data/lib/helo/models/delivered_webhook_payload.rb +1 -1
- data/lib/helo/models/device_details.rb +11 -0
- data/lib/helo/models/engagement_details.rb +13 -0
- data/lib/helo/models/opened_details.rb +13 -0
- data/lib/helo/models/opened_webhook_payload.rb +1 -1
- data/lib/helo/models/recipient_event_fields.rb +0 -1
- data/lib/helo/models/resubscribed_details.rb +9 -0
- data/lib/helo/models/resubscribed_webhook_payload.rb +1 -1
- data/lib/helo/models/unsubscribed_details.rb +9 -0
- data/lib/helo/models/unsubscribed_webhook_payload.rb +1 -1
- data/lib/helo/version.rb +1 -1
- data/lib/helo/webhook_signatures.rb +155 -0
- data/lib/helo.rb +1 -0
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbf62a1f7adb7ad672a9ddf17441cc9cda10e7c199775a4bf3a94785e2f3ba3d
|
|
4
|
+
data.tar.gz: 434a84d00c0104fb81211d0c105293f2dc1271b59a8872003b761d151247249d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bd2ba8cd121447ead38cbd6916ccdbbd5c755afd4035d0c550fab2c82220ec2f56d80f2ebe5bc23a9352acd21bd7f2f68867a0c3feddfeca322071c51c8bb4a
|
|
7
|
+
data.tar.gz: 1f3bb34bbcfafc14d5d7d2ec10e9c74a9bc924db20b240e639d30f42423cd70e48f9e9fda9b0736ebc683061cbd9082faa681952131449e01645330e89b82b96
|
data/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# helo-email-sdk
|
|
2
|
+
|
|
3
|
+
Helo API
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "helo-email-sdk"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
Configure the SDK once, at boot:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "helo-email-sdk"
|
|
25
|
+
|
|
26
|
+
Helo.configure do |config|
|
|
27
|
+
config.api_key = ENV.fetch("HELO_API_KEY")
|
|
28
|
+
config.base_url = "https://api.helohq.com" # optional, this is the default
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Each API is a class with class-level methods. Responses come back as model objects with
|
|
35
|
+
typed attribute readers.
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
result = Helo::Channels.list
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Methods that take parameters accept a hash:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
result = Helo::Channels.list(limit: 10)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
See the [API docs](#apis) for every method, with a runnable example each.
|
|
48
|
+
|
|
49
|
+
### Errors
|
|
50
|
+
|
|
51
|
+
Failed requests raise `Helo::APIError`:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
begin
|
|
55
|
+
Helo::Channels.list
|
|
56
|
+
rescue Helo::APIError => e
|
|
57
|
+
e.code # HTTP status
|
|
58
|
+
e.detail # human-readable detail from the API
|
|
59
|
+
e.errors # field-level validation errors, when present
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Webhook signature verification
|
|
64
|
+
|
|
65
|
+
Webhook deliveries are signed with the endpoint's signing key. Verify every delivery before
|
|
66
|
+
acting on it, against the **raw** request body — parsing and re-serializing the JSON changes
|
|
67
|
+
the bytes and the signature will not match.
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class WebhooksController < ApplicationController
|
|
71
|
+
skip_before_action :verify_authenticity_token
|
|
72
|
+
|
|
73
|
+
def create
|
|
74
|
+
Helo::WebhookSignatures.verify!(
|
|
75
|
+
request.headers["X-Helo-Webhook-Signature"],
|
|
76
|
+
request.raw_post, # raw body, exactly as received
|
|
77
|
+
ENV.fetch("HELO_WEBHOOK_SIGNING_KEY")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
event = JSON.parse(request.raw_post)
|
|
81
|
+
# ... handle the event, then acknowledge quickly
|
|
82
|
+
head :no_content
|
|
83
|
+
rescue Helo::WebhookSignatures::Error
|
|
84
|
+
head :bad_request
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`verify!` returns `true` when the signature is valid and raises otherwise. Each rejection has
|
|
90
|
+
its own class, so a stale delivery can be treated differently from a genuinely bad one:
|
|
91
|
+
|
|
92
|
+
| Exception | Meaning |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `MalformedHeaderError` | The header was not in the expected format |
|
|
95
|
+
| `UnsupportedVersionError` | The delivery used a signing scheme this SDK version cannot verify — upgrade the gem |
|
|
96
|
+
| `TimestampSkewError` | Correctly signed, but too old to accept — possible replay, or clock drift |
|
|
97
|
+
| `SignatureMismatchError` | Wrong signing key, or the body was modified in transit |
|
|
98
|
+
|
|
99
|
+
All four inherit from `Helo::WebhookSignatures::Error`, so `rescue` that one class
|
|
100
|
+
to catch any rejection. If you only want a boolean, use `valid?` instead:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
if Helo::WebhookSignatures.valid?(signature_header, raw_body, signing_key)
|
|
104
|
+
# ...
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The signature header may carry several versions at once (`t=...,v1=...,v2=...`) while a new
|
|
109
|
+
signing scheme is being rolled out. This SDK verifies against the newest version it supports
|
|
110
|
+
(`SUPPORTED_VERSIONS`) and ignores elements it does not recognize, so a rollout will not break
|
|
111
|
+
this integration.
|
|
112
|
+
|
|
113
|
+
To compute a signature yourself — signing a fixture in tests, for example — use
|
|
114
|
+
`Helo::WebhookSignatures.generate(payload, signing_key, timestamp)`.
|
|
115
|
+
|
|
116
|
+
## APIs
|
|
117
|
+
|
|
118
|
+
- [Helo::Channels](docs/Channels.md)
|
|
119
|
+
- [Helo::Activity](docs/Activity.md)
|
|
120
|
+
- [Helo::Domains](docs/Domains.md)
|
|
121
|
+
- [Helo::Sending](docs/Sending.md)
|
|
122
|
+
- [Helo::Broadcasts](docs/Broadcasts.md)
|
|
123
|
+
- [Helo::Statistics](docs/Statistics.md)
|
|
124
|
+
- [Helo::Suppressions](docs/Suppressions.md)
|
|
125
|
+
- [Helo::Webhooks](docs/Webhooks.md)
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::BouncedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Helo
|
|
4
|
+
class ClickedDetails
|
|
5
|
+
include Helo::Core::Model
|
|
6
|
+
|
|
7
|
+
api_attribute :link, :string, key: "link"
|
|
8
|
+
api_attribute :ip, :string, key: "ip"
|
|
9
|
+
api_attribute :country, :string, key: "country"
|
|
10
|
+
api_attribute :country_code, :string, key: "countryCode"
|
|
11
|
+
api_attribute :client, Helo::Core::ModelType.new("Helo::ClientDetails"), key: "client"
|
|
12
|
+
api_attribute :device, Helo::Core::ModelType.new("Helo::DeviceDetails"), key: "device"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::ClickedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::ComplainedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::DeliveredDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Helo
|
|
4
|
+
class EngagementDetails
|
|
5
|
+
include Helo::Core::Model
|
|
6
|
+
|
|
7
|
+
api_attribute :ip, :string, key: "ip"
|
|
8
|
+
api_attribute :country, :string, key: "country"
|
|
9
|
+
api_attribute :country_code, :string, key: "countryCode"
|
|
10
|
+
api_attribute :client, Helo::Core::ModelType.new("Helo::ClientDetails"), key: "client"
|
|
11
|
+
api_attribute :device, Helo::Core::ModelType.new("Helo::DeviceDetails"), key: "device"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Helo
|
|
4
|
+
class OpenedDetails
|
|
5
|
+
include Helo::Core::Model
|
|
6
|
+
|
|
7
|
+
api_attribute :ip, :string, key: "ip"
|
|
8
|
+
api_attribute :country, :string, key: "country"
|
|
9
|
+
api_attribute :country_code, :string, key: "countryCode"
|
|
10
|
+
api_attribute :client, Helo::Core::ModelType.new("Helo::ClientDetails"), key: "client"
|
|
11
|
+
api_attribute :device, Helo::Core::ModelType.new("Helo::DeviceDetails"), key: "device"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::OpenedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::ResubscribedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
|
@@ -5,8 +5,8 @@ module Helo
|
|
|
5
5
|
include Helo::Core::Model
|
|
6
6
|
|
|
7
7
|
api_attribute :event_type, :string, key: "eventType"
|
|
8
|
+
api_attribute :details, Helo::Core::ModelType.new("Helo::UnsubscribedDetails"), key: "details"
|
|
8
9
|
api_attribute :recipient, :string, key: "recipient"
|
|
9
|
-
api_attribute :details, :string, key: "details"
|
|
10
10
|
api_attribute :message_id, :string, key: "messageId"
|
|
11
11
|
api_attribute :channel_id, :string, key: "channelId"
|
|
12
12
|
api_attribute :mail_type, :string, key: "mailType"
|
data/lib/helo/version.rb
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Helo
|
|
6
|
+
# Helpers for verifying the signature on an incoming webhook request.
|
|
7
|
+
module WebhookSignatures
|
|
8
|
+
# Base class for every rejection reason, so callers that do not care why a
|
|
9
|
+
# webhook was rejected can rescue this one class.
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
# The header was not in the documented
|
|
13
|
+
# t={timestamp},v{version}={signature} form.
|
|
14
|
+
class MalformedHeaderError < Error; end
|
|
15
|
+
|
|
16
|
+
# The header carried only signing schemes this SDK does not know how to
|
|
17
|
+
# verify. Upgrading the SDK is the fix; see SUPPORTED_VERSIONS.
|
|
18
|
+
class UnsupportedVersionError < Error; end
|
|
19
|
+
|
|
20
|
+
# The signature was correctly formed but its timestamp is too far from the
|
|
21
|
+
# current time, so it may be a replay.
|
|
22
|
+
class TimestampSkewError < Error; end
|
|
23
|
+
|
|
24
|
+
# The signature did not match the body, either because the body was tampered
|
|
25
|
+
# with or the signing key is wrong.
|
|
26
|
+
class SignatureMismatchError < Error; end
|
|
27
|
+
|
|
28
|
+
# Signing schemes this SDK can verify. The signature header may carry several
|
|
29
|
+
# versions at once (t=...,v1=...,v2=...) so that a new scheme can be rolled
|
|
30
|
+
# out while receivers upgrade; verification uses the newest version present
|
|
31
|
+
# that appears in this list, and ignores the rest.
|
|
32
|
+
SUPPORTED_VERSIONS = [1].freeze
|
|
33
|
+
|
|
34
|
+
TIMESTAMP_VALUE_REGEX = /\A\d+\z/
|
|
35
|
+
SIGNATURE_KEY_REGEX = /\Av(\d+)\z/
|
|
36
|
+
HEX_SIGNATURE_REGEX = /\A[a-f0-9]+\z/
|
|
37
|
+
MAX_TIMESTAMP_SKEW_SECONDS = 300 # 5 minutes
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Verify a webhook signature header against the raw request body.
|
|
41
|
+
#
|
|
42
|
+
# @param signature_header [String] value of the signature header sent with the webhook
|
|
43
|
+
# @param request_body [String] raw (unparsed) request body
|
|
44
|
+
# @param signing_key [String] signing key for the webhook endpoint
|
|
45
|
+
# @return [true] when the signature is valid
|
|
46
|
+
# @raise [Error] a subclass describing why the signature was rejected
|
|
47
|
+
def verify!(signature_header, request_body, signing_key)
|
|
48
|
+
timestamp, signatures = parse_header(signature_header)
|
|
49
|
+
|
|
50
|
+
version = newest_supported_version(signatures)
|
|
51
|
+
unless version
|
|
52
|
+
raise UnsupportedVersionError,
|
|
53
|
+
"Unsupported webhook signature version: header carries only " \
|
|
54
|
+
"#{signatures.keys.sort.map { |v| "v#{v}" }.join(', ')}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
skew = (Time.now.to_i - timestamp.to_i).abs
|
|
58
|
+
if skew > MAX_TIMESTAMP_SKEW_SECONDS
|
|
59
|
+
raise TimestampSkewError,
|
|
60
|
+
"Webhook signature timestamp outside tolerance: off by #{skew}s, " \
|
|
61
|
+
"tolerance is #{MAX_TIMESTAMP_SKEW_SECONDS}s"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
computed = signature_for_version(version, request_body, signing_key, timestamp)
|
|
65
|
+
unless signatures.fetch(version).any? { |signature| secure_compare(computed, signature) }
|
|
66
|
+
raise SignatureMismatchError, "Webhook signature mismatch"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Verify a webhook signature header, returning false instead of raising.
|
|
73
|
+
#
|
|
74
|
+
# @see #verify!
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def valid?(signature_header, request_body, signing_key)
|
|
77
|
+
verify!(signature_header, request_body, signing_key)
|
|
78
|
+
rescue Error
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Compute the hex-encoded HMAC-SHA256 signature for a webhook payload,
|
|
83
|
+
# using the v1 signing scheme.
|
|
84
|
+
#
|
|
85
|
+
# @param payload [String] raw (unparsed) request body
|
|
86
|
+
# @param key [String] signing key for the webhook endpoint
|
|
87
|
+
# @param timestamp [String] unix timestamp in seconds, as sent in the signature header
|
|
88
|
+
# @return [String]
|
|
89
|
+
def generate(payload, key, timestamp)
|
|
90
|
+
OpenSSL::HMAC.hexdigest("SHA256", key, "#{timestamp}.#{payload}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Compute the signature for one signing scheme. This is the single place a
|
|
96
|
+
# new scheme needs to be added.
|
|
97
|
+
def signature_for_version(version, payload, key, timestamp)
|
|
98
|
+
case version
|
|
99
|
+
when 1 then generate(payload, key, timestamp)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Split the header into its timestamp and its signatures keyed by version.
|
|
104
|
+
# Elements that are not recognized are ignored, so that a sender adding new
|
|
105
|
+
# elements does not break verification here.
|
|
106
|
+
def parse_header(signature_header)
|
|
107
|
+
timestamp = nil
|
|
108
|
+
signatures = Hash.new { |hash, key| hash[key] = [] }
|
|
109
|
+
|
|
110
|
+
signature_header.to_s.split(",").each do |element|
|
|
111
|
+
key, value = element.strip.split("=", 2)
|
|
112
|
+
next if value.nil?
|
|
113
|
+
|
|
114
|
+
if key == "t"
|
|
115
|
+
raise MalformedHeaderError, "Malformed webhook signature header" unless TIMESTAMP_VALUE_REGEX.match?(value)
|
|
116
|
+
|
|
117
|
+
timestamp = value
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
match = SIGNATURE_KEY_REGEX.match(key)
|
|
122
|
+
next unless match
|
|
123
|
+
|
|
124
|
+
version = match[1].to_i
|
|
125
|
+
|
|
126
|
+
# Only versions this SDK verifies have a signature format it can insist
|
|
127
|
+
# on; anything else is recorded but left unchecked.
|
|
128
|
+
if SUPPORTED_VERSIONS.include?(version) && !HEX_SIGNATURE_REGEX.match?(value)
|
|
129
|
+
raise MalformedHeaderError, "Malformed webhook signature header"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
signatures[version] << value
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if timestamp.nil? || signatures.empty?
|
|
136
|
+
raise MalformedHeaderError, "Malformed webhook signature header"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
[timestamp, signatures]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Pick the highest version present that this SDK can verify, so that once a
|
|
143
|
+
# sender emits a newer scheme the older one stops being honored here.
|
|
144
|
+
def newest_supported_version(signatures)
|
|
145
|
+
(signatures.keys & SUPPORTED_VERSIONS).max
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def secure_compare(computed, given)
|
|
149
|
+
return false unless computed.bytesize == given.bytesize
|
|
150
|
+
|
|
151
|
+
OpenSSL.fixed_length_secure_compare(computed, given)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/lib/helo.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: helo-email-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.pre.beta.
|
|
4
|
+
version: 1.0.0.pre.beta.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Helo Email
|
|
@@ -86,6 +86,7 @@ executables: []
|
|
|
86
86
|
extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
|
88
88
|
files:
|
|
89
|
+
- README.md
|
|
89
90
|
- lib/helo-email-sdk.rb
|
|
90
91
|
- lib/helo.rb
|
|
91
92
|
- lib/helo/api_error.rb
|
|
@@ -121,6 +122,7 @@ files:
|
|
|
121
122
|
- lib/helo/models/activity_list_messages_request.rb
|
|
122
123
|
- lib/helo/models/activity_mail_address.rb
|
|
123
124
|
- lib/helo/models/attachment.rb
|
|
125
|
+
- lib/helo/models/bounced_details.rb
|
|
124
126
|
- lib/helo/models/bounced_webhook_payload.rb
|
|
125
127
|
- lib/helo/models/broadcast_content.rb
|
|
126
128
|
- lib/helo/models/broadcast_content_attachment.rb
|
|
@@ -137,7 +139,10 @@ files:
|
|
|
137
139
|
- lib/helo/models/channel_details_response.rb
|
|
138
140
|
- lib/helo/models/channel_tracking.rb
|
|
139
141
|
- lib/helo/models/channels_list_request.rb
|
|
142
|
+
- lib/helo/models/clicked_details.rb
|
|
140
143
|
- lib/helo/models/clicked_webhook_payload.rb
|
|
144
|
+
- lib/helo/models/client_details.rb
|
|
145
|
+
- lib/helo/models/complained_details.rb
|
|
141
146
|
- lib/helo/models/complained_webhook_payload.rb
|
|
142
147
|
- lib/helo/models/create_channel_request.rb
|
|
143
148
|
- lib/helo/models/create_channel_tracking.rb
|
|
@@ -145,9 +150,11 @@ files:
|
|
|
145
150
|
- lib/helo/models/create_suppressions_request.rb
|
|
146
151
|
- lib/helo/models/create_suppressions_response.rb
|
|
147
152
|
- lib/helo/models/create_webhook_request.rb
|
|
153
|
+
- lib/helo/models/delivered_details.rb
|
|
148
154
|
- lib/helo/models/delivered_webhook_payload.rb
|
|
149
155
|
- lib/helo/models/delivery_stats.rb
|
|
150
156
|
- lib/helo/models/delivery_webhook_payload_common.rb
|
|
157
|
+
- lib/helo/models/device_details.rb
|
|
151
158
|
- lib/helo/models/dns_record_response.rb
|
|
152
159
|
- lib/helo/models/dns_records_response.rb
|
|
153
160
|
- lib/helo/models/domain_channel_response.rb
|
|
@@ -157,6 +164,7 @@ files:
|
|
|
157
164
|
- lib/helo/models/domain_response.rb
|
|
158
165
|
- lib/helo/models/domain_with_dns_response.rb
|
|
159
166
|
- lib/helo/models/domains_list_request.rb
|
|
167
|
+
- lib/helo/models/engagement_details.rb
|
|
160
168
|
- lib/helo/models/error_response.rb
|
|
161
169
|
- lib/helo/models/mail_address.rb
|
|
162
170
|
- lib/helo/models/message.rb
|
|
@@ -165,6 +173,7 @@ files:
|
|
|
165
173
|
- lib/helo/models/message_details_response_event.rb
|
|
166
174
|
- lib/helo/models/message_details_response_tracking.rb
|
|
167
175
|
- lib/helo/models/message_statistics.rb
|
|
176
|
+
- lib/helo/models/opened_details.rb
|
|
168
177
|
- lib/helo/models/opened_webhook_payload.rb
|
|
169
178
|
- lib/helo/models/paginated_events_response.rb
|
|
170
179
|
- lib/helo/models/paginated_messages_response.rb
|
|
@@ -181,6 +190,7 @@ files:
|
|
|
181
190
|
- lib/helo/models/remove_suppression_result.rb
|
|
182
191
|
- lib/helo/models/remove_suppressions_request.rb
|
|
183
192
|
- lib/helo/models/remove_suppressions_response.rb
|
|
193
|
+
- lib/helo/models/resubscribed_details.rb
|
|
184
194
|
- lib/helo/models/resubscribed_webhook_payload.rb
|
|
185
195
|
- lib/helo/models/return_path_domain_verification_failed_payload.rb
|
|
186
196
|
- lib/helo/models/return_path_domain_verified_payload.rb
|
|
@@ -208,6 +218,7 @@ files:
|
|
|
208
218
|
- lib/helo/models/suppression_response.rb
|
|
209
219
|
- lib/helo/models/suppression_result.rb
|
|
210
220
|
- lib/helo/models/suppressions_list_request.rb
|
|
221
|
+
- lib/helo/models/unsubscribed_details.rb
|
|
211
222
|
- lib/helo/models/unsubscribed_webhook_payload.rb
|
|
212
223
|
- lib/helo/models/update_channel_request.rb
|
|
213
224
|
- lib/helo/models/update_channel_tracking.rb
|
|
@@ -220,6 +231,7 @@ files:
|
|
|
220
231
|
- lib/helo/models/webhook_response.rb
|
|
221
232
|
- lib/helo/models/webhooks_list_request.rb
|
|
222
233
|
- lib/helo/version.rb
|
|
234
|
+
- lib/helo/webhook_signatures.rb
|
|
223
235
|
homepage: https://rubygems.org/gems/helo-email-sdk
|
|
224
236
|
licenses:
|
|
225
237
|
- MIT
|