heya 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![Test](https://github.com/honeybadger-io/heya/workflows/Test/badge.svg)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/heya.svg)](https://badge.fury.io/rb/heya)
|
3
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/a6416e63ffc426715857/maintainability)](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"
|