heya 0.1.0 → 0.2.0
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 +5 -0
- data/README.md +2 -23
- data/app/mailers/heya/campaign_mailer.rb +12 -0
- data/lib/heya.rb +42 -0
- data/lib/heya/campaigns/base.rb +21 -9
- data/lib/heya/config.rb +1 -0
- data/lib/heya/engine.rb +11 -0
- data/lib/heya/license.rb +179 -0
- data/lib/heya/license/boundary.rb +64 -0
- data/lib/heya/license/encryptor.rb +122 -0
- data/lib/heya/version.rb +1 -1
- data/license_key.pub +9 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 125b5f898dd3d7841fd0e50ae05d424ec56d2818bb011a43a083f224d15aa3ea
|
4
|
+
data.tar.gz: 23a1fd76278729817ab0317dff81ab816cabd9f09b323e8f723cb5c8f9186eff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22b4d72ffa9e69068ce5a77008b76047cf3f1379d330f233b0a4f174a6a89d3a91ab3353e1941b4b1262a4898316cdc1b14e3842468e4fd9bbffa301234c3e62
|
7
|
+
data.tar.gz: bc5c5604c1e1d95adb14fe4435817431c4af2f471a5df7d6bfd4ec100d6a5607cbd4121a31d66660cde7e9a892b84126bf6b6e2d15676afbd36af2fad958910e
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.2.0] - 2020-04-14
|
10
|
+
### Added
|
11
|
+
- Added licensing.
|
12
|
+
- Added `rescue_from` (rescuable) in campaigns.
|
13
|
+
|
9
14
|
## [0.1.0] - 2020-03-19
|
10
15
|
### Added
|
11
16
|
- Initial release. 👋
|
data/README.md
CHANGED
@@ -1,30 +1,9 @@
|
|
1
1
|
# Heya 👋
|
2
2
|

|
3
|
+
[](https://badge.fury.io/rb/heya)
|
3
4
|
[](https://codeclimate.com/github/honeybadger-io/heya/maintainability)
|
4
5
|
|
5
|
-
Heya is a
|
6
|
-
|
7
|
-
## Why Heya?
|
8
|
-
Imagine onboarding and engaging your users with full access to their data, without maintaining a single 3rd-party integration or stressing over compliance. That's Heya: a new way to email your users.
|
9
|
-
|
10
|
-
Heya lives inside your Rails app, giving you full access to your customer data with the power of Ruby and Rails for building automated email campaigns.
|
11
|
-
|
12
|
-
If you've ever used 3rd-party email or marketing platforms, you've felt the pain of integrating, syncing customer data, and maintaining complex logic and rules--all of which is unnecessary in Heya.
|
13
|
-
|
14
|
-
Email automation becomes startlingly simple when your application framework *is* your automation tool.
|
15
|
-
|
16
|
-
## Who is Heya for?
|
17
|
-
- SaaS developers who are marketers at heart
|
18
|
-
- SaaS marketers who are developers at heart
|
19
|
-
- Development and marketing teams who work closely together
|
20
|
-
|
21
|
-
## Stuff we believe
|
22
|
-
- You should avoid duplicating customer data
|
23
|
-
- Segments should live as close to home as possible
|
24
|
-
- So should automation
|
25
|
-
- So should content
|
26
|
-
- Code is for everyone, not just developers
|
27
|
-
- Heya is the best way to engage your users
|
6
|
+
Heya is a campaign mailer for Rails. Think of it like ActionMailer, but for timed email sequences. It can also perform other actions like sending a text message.
|
28
7
|
|
29
8
|
## Getting started
|
30
9
|
Getting started with Heya is easy:
|
@@ -21,5 +21,17 @@ module Heya
|
|
21
21
|
template_name: step.name.underscore
|
22
22
|
)
|
23
23
|
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def _prefixes
|
28
|
+
@_prefixes_with_campaign_path ||= begin
|
29
|
+
if params.is_a?(Hash) && (campaign_name = params[:step]&.campaign&.name&.underscore)
|
30
|
+
super | ["heya/campaign_mailer/#{campaign_name}"]
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
data/lib/heya.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "heya/version"
|
4
4
|
require "heya/engine"
|
5
5
|
require "heya/config"
|
6
|
+
require "heya/license"
|
6
7
|
require "heya/campaigns/action"
|
7
8
|
require "heya/campaigns/actions/email"
|
8
9
|
require "heya/campaigns/actions/block"
|
@@ -45,4 +46,45 @@ module Heya
|
|
45
46
|
return user.send(segment) if segment.is_a?(Symbol)
|
46
47
|
segment.call(user)
|
47
48
|
end
|
49
|
+
|
50
|
+
def verify_license!
|
51
|
+
unless File.file?(config.license_file)
|
52
|
+
puts(<<-NOTICE.strip_heredoc)
|
53
|
+
This copy of Heya is licensed for non-commercial non-profit, or 30-day trial usage only.
|
54
|
+
For a commercial use license, please visit https://www.heya.email
|
55
|
+
NOTICE
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
license = License.import(File.read(config.license_file))
|
61
|
+
rescue License::ImportError
|
62
|
+
warn(<<-NOTICE.strip_heredoc)
|
63
|
+
Your Heya license is invalid.
|
64
|
+
If you need support, please visit https://www.heya.email
|
65
|
+
NOTICE
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
if license.expired?
|
70
|
+
warn(<<-NOTICE.strip_heredoc)
|
71
|
+
Your Heya license has expired.
|
72
|
+
To update your license, please visit https://www.heya.email
|
73
|
+
NOTICE
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
if (max_user_count = license.restrictions[:user_count]&.to_i)
|
78
|
+
user_count = config.user_type.constantize.count
|
79
|
+
if user_count > max_user_count
|
80
|
+
warn(<<-NOTICE.strip_heredoc)
|
81
|
+
Your app exceeds the number of users for your Heya license.
|
82
|
+
To upgrade your license, please visit https://www.heya.email
|
83
|
+
NOTICE
|
84
|
+
end
|
85
|
+
return # rubocop:disable Style/RedundantReturn
|
86
|
+
end
|
87
|
+
|
88
|
+
# Valid license
|
89
|
+
end
|
48
90
|
end
|
data/lib/heya/campaigns/base.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/descendants_tracker"
|
4
|
+
require "active_support/rescuable"
|
4
5
|
|
5
6
|
module Heya
|
6
7
|
module Campaigns
|
@@ -11,6 +12,7 @@ module Heya
|
|
11
12
|
|
12
13
|
include Singleton
|
13
14
|
include GlobalID::Identification
|
15
|
+
include ActiveSupport::Rescuable
|
14
16
|
|
15
17
|
def initialize
|
16
18
|
self.steps = []
|
@@ -27,11 +29,21 @@ module Heya
|
|
27
29
|
def add(user, restart: false, concurrent: false, send_now: true)
|
28
30
|
return false unless Heya.in_segments?(user, *__segments)
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
membership = CampaignMembership.where(user: user, campaign_gid: gid)
|
33
|
+
if membership.exists?
|
34
|
+
return false unless restart
|
35
|
+
membership.delete_all
|
36
|
+
end
|
37
|
+
|
38
|
+
if restart
|
39
|
+
CampaignReceipt
|
40
|
+
.where(user: user, step_gid: steps.map(&:gid))
|
41
|
+
.delete_all
|
42
|
+
end
|
33
43
|
|
34
|
-
|
44
|
+
membership.create! do |m|
|
45
|
+
m.concurrent = concurrent
|
46
|
+
end
|
35
47
|
|
36
48
|
if send_now && (step = steps.first) && step.wait <= 0
|
37
49
|
Scheduler.process(self, step, user)
|
@@ -61,6 +73,10 @@ module Heya
|
|
61
73
|
@user_class ||= self.class.user_type.constantize
|
62
74
|
end
|
63
75
|
|
76
|
+
def handle_exception(exception)
|
77
|
+
rescue_with_handler(exception) || raise(exception)
|
78
|
+
end
|
79
|
+
|
64
80
|
attr_accessor :steps
|
65
81
|
|
66
82
|
private
|
@@ -89,11 +105,7 @@ module Heya
|
|
89
105
|
instance
|
90
106
|
end
|
91
107
|
|
92
|
-
|
93
|
-
raise exception
|
94
|
-
end
|
95
|
-
|
96
|
-
delegate :steps, :add, :remove, :users, :gid, :user_class, to: :instance
|
108
|
+
delegate :steps, :add, :remove, :users, :gid, :user_class, :handle_exception, to: :instance
|
97
109
|
|
98
110
|
def default(**params)
|
99
111
|
self.__defaults = __defaults.merge(params).freeze
|
data/lib/heya/config.rb
CHANGED
data/lib/heya/engine.rb
CHANGED
@@ -15,5 +15,16 @@ module Heya
|
|
15
15
|
require_dependency(c)
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
19
|
+
config.after_initialize do
|
20
|
+
license_key = File.expand_path("../../../license_key.pub", __FILE__)
|
21
|
+
License.encryption_key = File.read(license_key) if File.file?(license_key)
|
22
|
+
|
23
|
+
Heya.configure do |config|
|
24
|
+
config.license_file ||= Rails.root.join("config/Heya.heya-license")
|
25
|
+
end
|
26
|
+
|
27
|
+
Heya.verify_license!
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
data/lib/heya/license.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015 GitLab B.V.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
# THE SOFTWARE.
|
24
|
+
|
25
|
+
require "openssl"
|
26
|
+
require "date"
|
27
|
+
require "json"
|
28
|
+
require "base64"
|
29
|
+
|
30
|
+
require "heya/license/encryptor"
|
31
|
+
require "heya/license/boundary"
|
32
|
+
|
33
|
+
module Heya
|
34
|
+
class License
|
35
|
+
class Error < StandardError; end
|
36
|
+
class ImportError < Error; end
|
37
|
+
class ValidationError < Error; end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_reader :encryption_key
|
41
|
+
@encryption_key = nil
|
42
|
+
|
43
|
+
def encryption_key=(key)
|
44
|
+
key = OpenSSL::PKey::RSA.new(key.to_s) unless key&.is_a?(OpenSSL::PKey::RSA)
|
45
|
+
@encryption_key = key
|
46
|
+
@encryptor = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def encryptor
|
50
|
+
@encryptor ||= Encryptor.new(encryption_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
def import(data)
|
54
|
+
if data.nil?
|
55
|
+
raise ImportError, "No license data."
|
56
|
+
end
|
57
|
+
|
58
|
+
data = Boundary.remove_boundary(data)
|
59
|
+
|
60
|
+
begin
|
61
|
+
license_json = encryptor.decrypt(data)
|
62
|
+
rescue Encryptor::Error
|
63
|
+
raise ImportError, "License data could not be decrypted."
|
64
|
+
end
|
65
|
+
|
66
|
+
begin
|
67
|
+
attributes = JSON.parse(license_json)
|
68
|
+
rescue JSON::ParseError
|
69
|
+
raise ImportError, "License data is invalid JSON."
|
70
|
+
end
|
71
|
+
|
72
|
+
new(attributes)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :version
|
77
|
+
attr_accessor :licensee, :starts_at, :expires_at
|
78
|
+
attr_accessor :restrictions
|
79
|
+
|
80
|
+
def initialize(attributes = {})
|
81
|
+
load_attributes(attributes)
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid?
|
85
|
+
return false if !licensee || !licensee.is_a?(Hash) || licensee.length == 0
|
86
|
+
return false if !starts_at || !starts_at.is_a?(Date)
|
87
|
+
return false if expires_at && !expires_at.is_a?(Date)
|
88
|
+
return false if restrictions && !restrictions.is_a?(Hash)
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate!
|
94
|
+
raise ValidationError, "License is invalid" unless valid?
|
95
|
+
end
|
96
|
+
|
97
|
+
def will_expire?
|
98
|
+
expires_at
|
99
|
+
end
|
100
|
+
|
101
|
+
def expired?
|
102
|
+
will_expire? && Date.today >= expires_at
|
103
|
+
end
|
104
|
+
|
105
|
+
def restricted?(key = nil)
|
106
|
+
if key
|
107
|
+
restricted? && restrictions.has_key?(key)
|
108
|
+
else
|
109
|
+
restrictions && restrictions.length >= 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def attributes
|
114
|
+
hash = {}
|
115
|
+
|
116
|
+
hash["version"] = version
|
117
|
+
hash["licensee"] = licensee
|
118
|
+
|
119
|
+
hash["starts_at"] = starts_at
|
120
|
+
hash["expires_at"] = expires_at if will_expire?
|
121
|
+
|
122
|
+
hash["restrictions"] = restrictions if restricted?
|
123
|
+
|
124
|
+
hash
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_json
|
128
|
+
JSON.dump(attributes)
|
129
|
+
end
|
130
|
+
|
131
|
+
def export(boundary: nil)
|
132
|
+
validate!
|
133
|
+
|
134
|
+
data = self.class.encryptor.encrypt(to_json)
|
135
|
+
|
136
|
+
if boundary
|
137
|
+
data = Boundary.add_boundary(data, boundary)
|
138
|
+
end
|
139
|
+
|
140
|
+
data
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def load_attributes(attributes)
|
146
|
+
attributes = Hash[attributes.map { |k, v| [k.to_s, v] }]
|
147
|
+
|
148
|
+
version = attributes["version"] || 1
|
149
|
+
unless version && version == 1
|
150
|
+
raise ArgumentError, "Version is too new"
|
151
|
+
end
|
152
|
+
|
153
|
+
@version = version
|
154
|
+
|
155
|
+
@licensee = attributes["licensee"]
|
156
|
+
|
157
|
+
%w[starts_at expires_at].each do |attr|
|
158
|
+
value = attributes[attr]
|
159
|
+
if value.is_a?(String)
|
160
|
+
value = begin
|
161
|
+
Date.parse(value)
|
162
|
+
rescue
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
next unless value
|
168
|
+
|
169
|
+
send("#{attr}=", value)
|
170
|
+
end
|
171
|
+
|
172
|
+
restrictions = attributes["restrictions"]
|
173
|
+
if restrictions&.is_a?(Hash)
|
174
|
+
restrictions = Hash[restrictions.map { |k, v| [k.to_sym, v] }]
|
175
|
+
@restrictions = restrictions
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015 GitLab B.V.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
# THE SOFTWARE.
|
24
|
+
|
25
|
+
module Heya
|
26
|
+
class License
|
27
|
+
module Boundary
|
28
|
+
BOUNDARY_START = /(\A|\r?\n)-*BEGIN .+? LICENSE-*\r?\n/.freeze
|
29
|
+
BOUNDARY_END = /\r?\n-*END .+? LICENSE-*(\r?\n|\z)/.freeze
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def add_boundary(data, product_name)
|
33
|
+
data = remove_boundary(data)
|
34
|
+
|
35
|
+
product_name.upcase!
|
36
|
+
|
37
|
+
pad = lambda do |message, width|
|
38
|
+
total_padding = [width - message.length, 0].max
|
39
|
+
|
40
|
+
padding = total_padding / 2.0
|
41
|
+
[
|
42
|
+
"-" * padding.ceil,
|
43
|
+
message,
|
44
|
+
"-" * padding.floor
|
45
|
+
].join
|
46
|
+
end
|
47
|
+
|
48
|
+
[
|
49
|
+
pad.call("BEGIN #{product_name} LICENSE", 60),
|
50
|
+
data.strip,
|
51
|
+
pad.call("END #{product_name} LICENSE", 60)
|
52
|
+
].join("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_boundary(data)
|
56
|
+
after_boundary = data.split(BOUNDARY_START).last
|
57
|
+
in_boundary = after_boundary.split(BOUNDARY_END).first
|
58
|
+
|
59
|
+
in_boundary
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015 GitLab B.V.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
# THE SOFTWARE.
|
24
|
+
|
25
|
+
module Heya
|
26
|
+
class License
|
27
|
+
class Encryptor
|
28
|
+
class Error < StandardError; end
|
29
|
+
class KeyError < Error; end
|
30
|
+
class DecryptionError < Error; end
|
31
|
+
|
32
|
+
attr_accessor :key
|
33
|
+
|
34
|
+
def initialize(key)
|
35
|
+
if key && !key.is_a?(OpenSSL::PKey::RSA)
|
36
|
+
raise KeyError, "No RSA encryption key provided."
|
37
|
+
end
|
38
|
+
|
39
|
+
@key = key
|
40
|
+
end
|
41
|
+
|
42
|
+
def encrypt(data)
|
43
|
+
unless key.private?
|
44
|
+
raise KeyError, "Provided key is not a private key."
|
45
|
+
end
|
46
|
+
|
47
|
+
# Encrypt the data using symmetric AES encryption.
|
48
|
+
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
49
|
+
cipher.encrypt
|
50
|
+
aes_key = cipher.random_key
|
51
|
+
aes_iv = cipher.random_iv
|
52
|
+
|
53
|
+
encrypted_data = cipher.update(data) + cipher.final
|
54
|
+
|
55
|
+
# Encrypt the AES key using asymmetric RSA encryption.
|
56
|
+
encrypted_key = key.private_encrypt(aes_key)
|
57
|
+
|
58
|
+
encryption_data = {
|
59
|
+
"data" => Base64.encode64(encrypted_data),
|
60
|
+
"key" => Base64.encode64(encrypted_key),
|
61
|
+
"iv" => Base64.encode64(aes_iv)
|
62
|
+
}
|
63
|
+
|
64
|
+
json_data = JSON.dump(encryption_data)
|
65
|
+
Base64.encode64(json_data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def decrypt(data)
|
69
|
+
unless key.public?
|
70
|
+
raise KeyError, "Provided key is not a public key."
|
71
|
+
end
|
72
|
+
|
73
|
+
json_data = Base64.decode64(data.chomp)
|
74
|
+
|
75
|
+
begin
|
76
|
+
encryption_data = JSON.parse(json_data)
|
77
|
+
rescue JSON::ParserError
|
78
|
+
raise DecryptionError, "Encryption data is invalid JSON."
|
79
|
+
end
|
80
|
+
|
81
|
+
unless %w[data key iv].all? { |key| encryption_data[key] }
|
82
|
+
raise DecryptionError, "Required field missing from encryption data."
|
83
|
+
end
|
84
|
+
|
85
|
+
encrypted_data = Base64.decode64(encryption_data["data"])
|
86
|
+
encrypted_key = Base64.decode64(encryption_data["key"])
|
87
|
+
aes_iv = Base64.decode64(encryption_data["iv"])
|
88
|
+
|
89
|
+
begin
|
90
|
+
# Decrypt the AES key using asymmetric RSA encryption.
|
91
|
+
aes_key = self.key.public_decrypt(encrypted_key)
|
92
|
+
rescue OpenSSL::PKey::RSAError
|
93
|
+
raise DecryptionError, "AES encryption key could not be decrypted."
|
94
|
+
end
|
95
|
+
|
96
|
+
# Decrypt the data using symmetric AES encryption.
|
97
|
+
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
98
|
+
cipher.decrypt
|
99
|
+
|
100
|
+
begin
|
101
|
+
cipher.key = aes_key
|
102
|
+
rescue OpenSSL::Cipher::CipherError
|
103
|
+
raise DecryptionError, "AES encryption key is invalid."
|
104
|
+
end
|
105
|
+
|
106
|
+
begin
|
107
|
+
cipher.iv = aes_iv
|
108
|
+
rescue OpenSSL::Cipher::CipherError
|
109
|
+
raise DecryptionError, "AES IV is invalid."
|
110
|
+
end
|
111
|
+
|
112
|
+
begin
|
113
|
+
data = cipher.update(encrypted_data) + cipher.final
|
114
|
+
rescue OpenSSL::Cipher::CipherError
|
115
|
+
raise DecryptionError, "Data could not be decrypted."
|
116
|
+
end
|
117
|
+
|
118
|
+
data
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/heya/version.rb
CHANGED
data/license_key.pub
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
-----BEGIN PUBLIC KEY-----
|
2
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycgMy1EVcD50WlDhfMDq
|
3
|
+
0wvLZb1iF1Hes5IU37QZeswq4qy5MZKBx8669Jy04QH3RnrY0XyO6c1bnr2rnv8M
|
4
|
+
NZL8M0ibGpG0NWv0O2tzaVb7706Vm/VuQGR9BuFSe8ZlKRyvMJpk1Kl8NKeRl+iu
|
5
|
+
KMN7AK48/b7/stB8P5NVb/8LCOUfIkcOZcTZN9YJvXbyq5i/i2i2IRgxib0zmkU3
|
6
|
+
yCOVZnvW0spaH+Z6SuXTZ/wefXM9XRCpshuEvBnS8vihcyQioFkswF2An4FL8B+N
|
7
|
+
AWaeA3NcNJEiUFsCpp0uUOVqVZ2avhpATB7fAG4Qh3JauajhWBrqSJYZn2W6uyDp
|
8
|
+
fQIDAQAB
|
9
|
+
-----END PUBLIC KEY-----
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heya
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Wood
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -108,8 +108,12 @@ files:
|
|
108
108
|
- lib/heya/campaigns/step_action_job.rb
|
109
109
|
- lib/heya/config.rb
|
110
110
|
- lib/heya/engine.rb
|
111
|
+
- lib/heya/license.rb
|
112
|
+
- lib/heya/license/boundary.rb
|
113
|
+
- lib/heya/license/encryptor.rb
|
111
114
|
- lib/heya/version.rb
|
112
115
|
- lib/tasks/heya_tasks.rake
|
116
|
+
- license_key.pub
|
113
117
|
homepage: https://github.com/honeybadger-io/heya
|
114
118
|
licenses:
|
115
119
|
- Prosperity Public License
|
@@ -129,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
133
|
- !ruby/object:Gem::Version
|
130
134
|
version: '0'
|
131
135
|
requirements: []
|
132
|
-
rubygems_version: 3.
|
136
|
+
rubygems_version: 3.1.2
|
133
137
|
signing_key:
|
134
138
|
specification_version: 4
|
135
139
|
summary: "Heya \U0001F44B"
|