traitify 2.0.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac1ecd58611516c2f31b6e56e290c1efc0ee7e1842cce7b48a0157c8b5687acf
4
- data.tar.gz: 84f2887d1ae2bd1fd6ded295cb864596d41fa3a3aad1b1a9dcffe02e22142015
3
+ metadata.gz: 1f53da4ace0d1005c2982c0e2681132c21a05e9d9365057eab89e488caf10d4f
4
+ data.tar.gz: be22aff51d262cc2c685cf51b957547ad5e4d0a4d16c4a15b47db7e3913cd130
5
5
  SHA512:
6
- metadata.gz: 535343ccff192c9a91c2b3fa4f1495e38c3fbd9bc02d30312718f6a36d230262a280ae8b59947b4f97104c4d6926cf1f88e5c303f71ca9acacb1afc613c2c546
7
- data.tar.gz: a882126511dbb26e8dc2bb92a78393943296488b97a246799fe187b51e2e6ac58c8a9d499d50fd9229c1c28125cf03d8bf62ef6ae0222268af616b966100bb59
6
+ metadata.gz: 1ee8e51a6ad407827f7dc68c538278ce1a4f22e1a1891de56e275b8f2267a882c48327055d478b55e9bcc8753498c1ee681b6aff523297bc0c4dd202d13b5288
7
+ data.tar.gz: '08ffb2eabe9e9c7fc92a12ef81a1a03c71eb1fb83061c98819b1861bed669385f278fb30d4f040a611b65cd9a6499f6ce76dedf6e6d7613a5f4d587a19734a98'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ### 2.1.0
2
+
3
+ * Features
4
+ * Added automatic retries
5
+
6
+ ### 2.0.1
7
+
8
+ * Bug Fixes
9
+ * Fixed an issue with nested params
10
+
11
+ ### 2.0.0
12
+
13
+ * Features
14
+ * Refactored everything
15
+ * Added dynamic system to build a request
16
+ * Added overrides for specific resources
17
+ * Added dynamic system to interact with a response
18
+
19
+ * Breaking Changes
20
+ * Removed custom methods for resources
21
+
1
22
  ### 1.9.0
2
23
 
3
24
  * Features
@@ -81,6 +102,3 @@
81
102
  * Get/Create Assessments
82
103
  * Get/Update Slides
83
104
  * Get Personality Blend, Types, and Traits
84
-
85
- [@cpreisinger]:https://github.com/cpreisinger
86
- [@tomprats]:https://github.com/tomprats
data/Gemfile.lock CHANGED
@@ -1,105 +1,152 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- traitify (2.0.1)
4
+ traitify (2.1.0)
5
5
  activesupport (>= 5.1, < 8.x)
6
6
  faraday (~> 2.5)
7
7
  faraday-net_http (~> 3.0)
8
+ faraday-retry (~> 2.2)
9
+ jwt (~> 2.0)
8
10
 
9
11
  GEM
10
12
  remote: https://rubygems.org/
11
13
  specs:
12
- activesupport (7.0.3.1)
14
+ activesupport (7.1.5.2)
15
+ base64
16
+ benchmark (>= 0.3)
17
+ bigdecimal
13
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ connection_pool (>= 2.2.5)
20
+ drb
14
21
  i18n (>= 1.6, < 2)
22
+ logger (>= 1.4.2)
15
23
  minitest (>= 5.1)
24
+ mutex_m
25
+ securerandom (>= 0.3)
16
26
  tzinfo (~> 2.0)
17
- addressable (2.8.1)
18
- public_suffix (>= 2.0.2, < 6.0)
19
- ast (2.4.2)
20
- binding_of_caller (1.0.0)
21
- debug_inspector (>= 0.0.1)
27
+ addressable (2.8.7)
28
+ public_suffix (>= 2.0.2, < 7.0)
29
+ ast (2.4.3)
30
+ base64 (0.3.0)
31
+ benchmark (0.4.1)
32
+ bigdecimal (3.3.0)
33
+ binding_of_caller (1.0.1)
34
+ debug_inspector (>= 1.2.0)
22
35
  coderay (1.1.3)
23
- concurrent-ruby (1.1.10)
24
- crack (0.4.5)
36
+ concurrent-ruby (1.3.5)
37
+ connection_pool (2.5.4)
38
+ crack (1.0.0)
39
+ bigdecimal
25
40
  rexml
26
- debug_inspector (1.1.0)
27
- diff-lcs (1.5.0)
28
- docile (1.4.0)
29
- faraday (2.5.2)
41
+ debug_inspector (1.2.0)
42
+ diff-lcs (1.6.2)
43
+ docile (1.4.1)
44
+ drb (2.2.3)
45
+ faraday (2.8.1)
46
+ base64
30
47
  faraday-net_http (>= 2.0, < 3.1)
31
48
  ruby2_keywords (>= 0.0.4)
32
- faraday-net_http (3.0.0)
33
- hashdiff (1.0.1)
34
- i18n (1.12.0)
49
+ faraday-net_http (3.0.2)
50
+ faraday-retry (2.3.2)
51
+ faraday (~> 2.0)
52
+ hashdiff (1.2.1)
53
+ i18n (1.14.7)
35
54
  concurrent-ruby (~> 1.0)
36
- method_source (1.0.0)
37
- minitest (5.16.3)
38
- parallel (1.22.1)
39
- parser (3.1.2.1)
55
+ json (2.15.1)
56
+ jwt (2.10.2)
57
+ base64
58
+ language_server-protocol (3.17.0.5)
59
+ lint_roller (1.1.0)
60
+ logger (1.7.0)
61
+ method_source (1.1.0)
62
+ minitest (5.25.5)
63
+ mutex_m (0.3.0)
64
+ parallel (1.27.0)
65
+ parser (3.3.9.0)
40
66
  ast (~> 2.4.1)
41
- pry (0.14.1)
67
+ racc
68
+ prism (1.5.1)
69
+ pry (0.15.2)
42
70
  coderay (~> 1.1)
43
71
  method_source (~> 1.0)
44
- public_suffix (5.0.0)
45
- rack (2.2.4)
72
+ public_suffix (5.1.1)
73
+ racc (1.8.1)
74
+ rack (3.1.17)
46
75
  rainbow (3.1.1)
47
- rake (13.0.6)
48
- regexp_parser (2.5.0)
49
- rexml (3.2.5)
50
- rspec (3.11.0)
51
- rspec-core (~> 3.11.0)
52
- rspec-expectations (~> 3.11.0)
53
- rspec-mocks (~> 3.11.0)
54
- rspec-core (3.11.0)
55
- rspec-support (~> 3.11.0)
56
- rspec-expectations (3.11.0)
76
+ rake (13.3.0)
77
+ regexp_parser (2.11.3)
78
+ rexml (3.4.4)
79
+ rspec (3.13.1)
80
+ rspec-core (~> 3.13.0)
81
+ rspec-expectations (~> 3.13.0)
82
+ rspec-mocks (~> 3.13.0)
83
+ rspec-core (3.13.5)
84
+ rspec-support (~> 3.13.0)
85
+ rspec-expectations (3.13.5)
57
86
  diff-lcs (>= 1.2.0, < 2.0)
58
- rspec-support (~> 3.11.0)
59
- rspec-mocks (3.11.1)
87
+ rspec-support (~> 3.13.0)
88
+ rspec-mocks (3.13.5)
60
89
  diff-lcs (>= 1.2.0, < 2.0)
61
- rspec-support (~> 3.11.0)
62
- rspec-support (3.11.0)
63
- rubocop (0.93.1)
90
+ rspec-support (~> 3.13.0)
91
+ rspec-support (3.13.6)
92
+ rubocop (1.81.1)
93
+ json (~> 2.3)
94
+ language_server-protocol (~> 3.17.0.2)
95
+ lint_roller (~> 1.1.0)
64
96
  parallel (~> 1.10)
65
- parser (>= 2.7.1.5)
97
+ parser (>= 3.3.0.2)
66
98
  rainbow (>= 2.2.2, < 4.0)
67
- regexp_parser (>= 1.8)
68
- rexml
69
- rubocop-ast (>= 0.6.0)
99
+ regexp_parser (>= 2.9.3, < 3.0)
100
+ rubocop-ast (>= 1.47.1, < 2.0)
70
101
  ruby-progressbar (~> 1.7)
71
- unicode-display_width (>= 1.4.0, < 2.0)
72
- rubocop-airbnb (4.0.0)
73
- rubocop (~> 0.93.1)
74
- rubocop-performance (~> 1.10.2)
75
- rubocop-rails (~> 2.9.1)
76
- rubocop-rspec (~> 1.44.1)
77
- rubocop-ast (1.21.0)
78
- parser (>= 3.1.1.0)
79
- rubocop-performance (1.10.2)
80
- rubocop (>= 0.90.0, < 2.0)
81
- rubocop-ast (>= 0.4.0)
82
- rubocop-rails (2.9.1)
102
+ unicode-display_width (>= 2.4.0, < 4.0)
103
+ rubocop-airbnb (8.0.0)
104
+ lint_roller (~> 1.1)
105
+ rubocop (~> 1.72)
106
+ rubocop-capybara (~> 2.22)
107
+ rubocop-factory_bot (~> 2.27)
108
+ rubocop-performance (~> 1.24)
109
+ rubocop-rails (~> 2.30)
110
+ rubocop-rspec (~> 3.5)
111
+ rubocop-ast (1.47.1)
112
+ parser (>= 3.3.7.2)
113
+ prism (~> 1.4)
114
+ rubocop-capybara (2.22.1)
115
+ lint_roller (~> 1.1)
116
+ rubocop (~> 1.72, >= 1.72.1)
117
+ rubocop-factory_bot (2.27.1)
118
+ lint_roller (~> 1.1)
119
+ rubocop (~> 1.72, >= 1.72.1)
120
+ rubocop-performance (1.26.0)
121
+ lint_roller (~> 1.1)
122
+ rubocop (>= 1.75.0, < 2.0)
123
+ rubocop-ast (>= 1.44.0, < 2.0)
124
+ rubocop-rails (2.33.4)
83
125
  activesupport (>= 4.2.0)
126
+ lint_roller (~> 1.1)
84
127
  rack (>= 1.1)
85
- rubocop (>= 0.90.0, < 2.0)
86
- rubocop-rspec (1.44.1)
87
- rubocop (~> 0.87)
88
- rubocop-ast (>= 0.7.1)
89
- rubocop-traitify (1.1.0)
90
- rubocop-airbnb (~> 4.0.0)
91
- ruby-progressbar (1.11.0)
128
+ rubocop (>= 1.75.0, < 2.0)
129
+ rubocop-ast (>= 1.44.0, < 2.0)
130
+ rubocop-rspec (3.7.0)
131
+ lint_roller (~> 1.1)
132
+ rubocop (~> 1.72, >= 1.72.1)
133
+ rubocop-traitify (1.3.0)
134
+ rubocop-airbnb (~> 8.0.0)
135
+ ruby-progressbar (1.13.0)
92
136
  ruby2_keywords (0.0.5)
137
+ securerandom (0.3.2)
93
138
  simplecov (0.21.2)
94
139
  docile (~> 1.1)
95
140
  simplecov-html (~> 0.11)
96
141
  simplecov_json_formatter (~> 0.1)
97
- simplecov-html (0.12.3)
142
+ simplecov-html (0.13.2)
98
143
  simplecov_json_formatter (0.1.4)
99
- tzinfo (2.0.5)
144
+ tzinfo (2.0.6)
100
145
  concurrent-ruby (~> 1.0)
101
- unicode-display_width (1.8.0)
102
- webmock (3.18.1)
146
+ unicode-display_width (3.2.0)
147
+ unicode-emoji (~> 4.1)
148
+ unicode-emoji (4.1.0)
149
+ webmock (3.25.1)
103
150
  addressable (>= 2.8.0)
104
151
  crack (>= 0.3.2)
105
152
  hashdiff (>= 0.4.0, < 2.0.0)
@@ -107,6 +154,7 @@ GEM
107
154
  PLATFORMS
108
155
  arm64-darwin-20
109
156
  arm64-darwin-21
157
+ x86_64-linux
110
158
 
111
159
  DEPENDENCIES
112
160
  binding_of_caller (~> 1.0)
@@ -120,4 +168,4 @@ DEPENDENCIES
120
168
  webmock (~> 3.18)
121
169
 
122
170
  BUNDLED WITH
123
- 2.2.33
171
+ 2.3.16
data/README.md CHANGED
@@ -47,6 +47,50 @@ All the configuration options can be found in `lib/traitify/configuration.rb`
47
47
  )
48
48
  traitify.assessments.create
49
49
 
50
+ ### Auto Retry
51
+
52
+ Requests that fail can be automatically retried.
53
+
54
+ #### Retry Configuration
55
+
56
+ Retrying can be configured in multiple ways.
57
+
58
+ Through the general config:
59
+
60
+ Traitify.configure do |traitify|
61
+ traitify.auto_retry = true
62
+ traitify.retry_options = retry_options
63
+ end
64
+
65
+ traitify = Traitify.new
66
+ assessment = traitify.assessments("assessment-uuid").data
67
+
68
+ On a specific request:
69
+
70
+ traitify = Traitify.new
71
+ assessment = traitify.retriable(retry_options).assessments("assessment-uuid").data
72
+
73
+ Or with both:
74
+
75
+ Traitify.configure do |traitify|
76
+ traitify.auto_retry = false
77
+ traitify.retry_options = retry_options
78
+ end
79
+
80
+ traitify = Traitify.new
81
+ assessment = traitify.retriable.assessments("assessment-uuid").data
82
+
83
+ This allows fine tuned control when and what to retry.
84
+
85
+ #### Retry Options
86
+
87
+ The options are passed to the [Faraday Retry](https://github.com/lostisland/faraday-retry) middleware. Supported options can be found in their documentation.
88
+
89
+ retry_options = {
90
+ methods: [:get, :post],
91
+ retry_statuses: [429, 500]
92
+ }
93
+
50
94
  ### Decks
51
95
 
52
96
  #### Getting all the decks:
@@ -1,5 +1,6 @@
1
1
  require "faraday"
2
2
  require "faraday/net_http"
3
+ require "faraday/retry"
3
4
  require "traitify/middleware/formatter"
4
5
  require "traitify/middleware/raise_error"
5
6
 
@@ -8,14 +9,15 @@ module Traitify
8
9
  module Connection
9
10
  def connection(options = {})
10
11
  Faraday.new(options) do |faraday|
11
- faraday.request :authorization, :basic, secret_key || public_key, "x"
12
- faraday.request :json
12
+ faraday.request(:authorization, :basic, secret_key || public_key, "x")
13
+ faraday.request(:json)
14
+ faraday.request(:retry, retry_options) if auto_retry
13
15
  faraday.headers["Accept"] = "application/json"
14
16
  faraday.options.params_encoder = Faraday::FlatParamsEncoder
15
- faraday.response :logger, Traitify.logger, formatter: Traitify::Middleware::Formatter
16
- faraday.response :raise_traitify_error
17
- faraday.response :json
18
- faraday.adapter :net_http
17
+ faraday.response(:logger, Traitify.logger, formatter: Traitify::Middleware::Formatter)
18
+ faraday.response(:raise_traitify_error)
19
+ faraday.response(:json)
20
+ faraday.adapter(:net_http)
19
21
  end
20
22
  end
21
23
  end
@@ -7,13 +7,20 @@ module Traitify
7
7
  end
8
8
  end
9
9
 
10
+ def data
11
+ @data ||= request.data
12
+ end
13
+
10
14
  def request
11
15
  @request = Traitify::Response.new(base_request)
12
16
  end
13
17
  alias_method :fetch, :request
14
18
 
15
- def data
16
- @data ||= request.data
19
+ def retriable(**options)
20
+ copy.tap do |client|
21
+ client.auto_retry = true
22
+ client.retry_options = options unless options.blank?
23
+ end
17
24
  end
18
25
 
19
26
  private
@@ -1,13 +1,16 @@
1
1
  module Traitify
2
2
  module Configuration
3
3
  VALID_OPTIONS_KEYS = [
4
+ :auto_retry,
5
+ :deck_id,
4
6
  :host,
7
+ :image_pack,
8
+ :jwt_public_keys,
9
+ :locale_key,
5
10
  :public_key,
11
+ :retry_options,
6
12
  :secret_key,
7
- :version,
8
- :deck_id,
9
- :image_pack,
10
- :locale_key
13
+ :version
11
14
  ].freeze
12
15
 
13
16
  attr_accessor(*VALID_OPTIONS_KEYS)
@@ -37,8 +37,12 @@ module Traitify
37
37
  end
38
38
 
39
39
  class BadRequest < Error; end
40
+
40
41
  class Unauthorized < Error; end
42
+
41
43
  class NotFound < Error; end
44
+
42
45
  class UnprocessableEntity < Error; end
46
+
43
47
  class ServerError < Error; end
44
48
  end
@@ -1,3 +1,3 @@
1
1
  module Traitify
2
- VERSION = "2.0.1".freeze
2
+ VERSION = "2.1.1".freeze
3
3
  end
data/lib/traitify.rb CHANGED
@@ -1,3 +1,8 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/object/deep_dup"
3
+ require "ostruct"
4
+ require "jwt"
5
+ require "openssl"
1
6
  require "traitify/configuration"
2
7
  require "traitify/client"
3
8
  require "traitify/data"
@@ -5,8 +10,6 @@ require "traitify/error"
5
10
  require "traitify/response"
6
11
  require "traitify/version"
7
12
  require "logger"
8
- require "active_support"
9
- require "active_support/core_ext/object/deep_dup"
10
13
 
11
14
  module Traitify
12
15
  extend Configuration
@@ -33,9 +36,64 @@ module Traitify
33
36
  case level
34
37
  when :debug
35
38
  logger.debug message
39
+ when :warn
40
+ logger.warn message
41
+ when :error
42
+ logger.error message
36
43
  else
37
44
  logger.info message
38
45
  end
39
46
  end
47
+
48
+ def valid_jwt_token?(token)
49
+ algorithm = "RS256"
50
+ return false unless jwt_public_keys && jwt_public_keys.any?
51
+
52
+ public_keys = jwt_public_keys.map { |key| OpenSSL::PKey::RSA.new(key) }
53
+
54
+ public_keys.each do |public_key|
55
+ decoded_token = JWT.decode(token, public_key, true, {
56
+ algorithm: algorithm,
57
+ iss: "Traitify by Paradox",
58
+ verify_iss: true,
59
+ verify_iat: true,
60
+ verify_nbf: true,
61
+ verify_jti: true
62
+ })
63
+
64
+ payload = decoded_token[0]
65
+ validate_claims(payload)
66
+ return true
67
+ rescue JWT::ExpiredSignature, JWT::DecodeError, JWT::VerificationError => e
68
+ log(:warn, "[JWT] #{e.class.name}: #{e.message}")
69
+ next
70
+ rescue => e
71
+ log(:error, "[JWT] Unexpected error: #{e.class} - #{e.message}")
72
+ next
73
+ end
74
+
75
+ false
76
+ end
77
+
78
+ private
79
+
80
+ def validate_claims(payload)
81
+ current_time = Time.now.to_i
82
+
83
+ iat_value = payload["iat"] || payload[:iat]
84
+ if iat_value && iat_value > current_time
85
+ raise JWT::InvalidIatError.new("Token issued in the future")
86
+ end
87
+
88
+ nbf_value = payload["nbf"] || payload[:nbf]
89
+ if nbf_value && nbf_value > current_time
90
+ raise JWT::DecodeError.new("Token not yet valid")
91
+ end
92
+
93
+ jti_value = payload["jti"] || payload[:jti]
94
+ if jti_value.nil? || jti_value.empty?
95
+ raise JWT::DecodeError.new("Missing JWT ID (jti)")
96
+ end
97
+ end
40
98
  end
41
99
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,7 @@
1
+ require "active_support"
1
2
  require "active_support/core_ext/object/conversions"
2
3
  require "active_support/core_ext/object/json"
4
+ require "active_support/core_ext/numeric/time"
3
5
  require "webmock/rspec"
4
6
  require "pry"
5
7
  require "simplecov"
@@ -14,15 +14,15 @@ describe Traitify::Client do
14
14
  end
15
15
  end
16
16
 
17
- it "assigns secret" do
18
- expect(traitify.secret_key).to eq("secret")
17
+ it "assigns host" do
18
+ expect(traitify.host).to eq("https://example.com")
19
19
  end
20
20
 
21
- it "assigns api_host" do
22
- expect(traitify.host).to eq("https://example.com")
21
+ it "assigns secret" do
22
+ expect(traitify.secret_key).to eq("secret")
23
23
  end
24
24
 
25
- it "assigns api_version" do
25
+ it "assigns version" do
26
26
  expect(traitify.version).to eq("v1")
27
27
  end
28
28
  end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe Traitify::Client do
4
+ before do
5
+ Traitify.configure do |client|
6
+ client.secret_key = "secret"
7
+ client.host = "https://example.com"
8
+ client.version = "v1"
9
+ client.deck_id = "deck-uuid"
10
+ client.logger = Logger.new("/dev/null")
11
+ end
12
+ end
13
+
14
+ let(:client){ Traitify.new(auto_retry: false) }
15
+
16
+ describe ".auto_retry" do
17
+ it "is set to true" do
18
+ client = Traitify.new(auto_retry: true)
19
+
20
+ expect(client.auto_retry).to eq(true)
21
+ end
22
+
23
+ it "is set to false" do
24
+ expect(client.auto_retry).to eq(false)
25
+ end
26
+ end
27
+
28
+ describe ".retriable" do
29
+ it "sets auto_retry" do
30
+ expect(client.retriable.auto_retry).to eq(true)
31
+ end
32
+
33
+ it "sets retry_options" do
34
+ expect(client.retriable(retry_statuses: [500]).retry_options).to match(
35
+ retry_statuses: [500]
36
+ )
37
+ end
38
+
39
+ it "uses existing retry_options" do
40
+ client = Traitify.new(retry_options: {retry_statuses: [500]})
41
+
42
+ expect(client.retriable.retry_options).to match(
43
+ retry_statuses: [500]
44
+ )
45
+ end
46
+
47
+ it "is retriable when extended" do
48
+ expect(client.retriable.profiles.auto_retry).to eq(true)
49
+ end
50
+
51
+ it "is retriable when nested" do
52
+ expect(client.profiles.retriable.auto_retry).to eq(true)
53
+ end
54
+
55
+ it "doesn't leak" do
56
+ expect(client.retriable.auto_retry).to eq(true)
57
+ expect(client.profiles.auto_retry).to eq(false)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,242 @@
1
+ require "spec_helper"
2
+
3
+ describe Traitify do
4
+ describe ".valid_jwt_token?" do
5
+ let(:private_key){ OpenSSL::PKey::RSA.new(2048) }
6
+ let(:public_key){ private_key.public_key }
7
+ let(:valid_payload) do
8
+ {
9
+ iss: "Traitify by Paradox",
10
+ iat: Time.now.to_i,
11
+ nbf: Time.now.to_i,
12
+ jti: "unique-token-id"
13
+ }
14
+ end
15
+
16
+ before do
17
+ Traitify.jwt_public_keys = [public_key.to_pem]
18
+ end
19
+
20
+ after do
21
+ Traitify.jwt_public_keys = nil
22
+ end
23
+
24
+ context "with valid token" do
25
+ let(:valid_token) do
26
+ JWT.encode(valid_payload, private_key, "RS256")
27
+ end
28
+
29
+ it "returns true" do
30
+ expect(Traitify.valid_jwt_token?(valid_token)).to be true
31
+ end
32
+ end
33
+
34
+ context "with invalid signature" do
35
+ let(:invalid_token) do
36
+ other_private_key = OpenSSL::PKey::RSA.new(2048)
37
+ JWT.encode(valid_payload, other_private_key, "RS256")
38
+ end
39
+
40
+ it "returns false" do
41
+ expect(Traitify.valid_jwt_token?(invalid_token)).to be false
42
+ end
43
+ end
44
+
45
+ context "with malformed token" do
46
+ let(:malformed_token){ "not.a.valid.jwt" }
47
+
48
+ it "returns false" do
49
+ expect(Traitify.valid_jwt_token?(malformed_token)).to be false
50
+ end
51
+ end
52
+
53
+ context "with expired token" do
54
+ let(:expired_payload) do
55
+ valid_payload.merge(iat: 1.hour.ago.to_i, exp: 1.hour.ago.to_i)
56
+ end
57
+ let(:expired_token) do
58
+ JWT.encode(expired_payload, private_key, "RS256")
59
+ end
60
+
61
+ it "returns false" do
62
+ expect(Traitify.valid_jwt_token?(expired_token)).to be false
63
+ end
64
+ end
65
+
66
+ context "with wrong issuer" do
67
+ let(:wrong_issuer_payload) do
68
+ valid_payload.merge(iss: "Wrong Issuer")
69
+ end
70
+ let(:wrong_issuer_token) do
71
+ JWT.encode(wrong_issuer_payload, private_key, "RS256")
72
+ end
73
+
74
+ it "returns false" do
75
+ expect(Traitify.valid_jwt_token?(wrong_issuer_token)).to be false
76
+ end
77
+ end
78
+
79
+ context "with multiple public keys" do
80
+ let(:legacy_private_key){ OpenSSL::PKey::RSA.new(2048) }
81
+ let(:legacy_public_key){ legacy_private_key.public_key }
82
+
83
+ before do
84
+ Traitify.jwt_public_keys = [public_key.to_pem, legacy_public_key.to_pem]
85
+ end
86
+
87
+ context "when token is signed with current key" do
88
+ let(:current_token) do
89
+ JWT.encode(valid_payload, private_key, "RS256")
90
+ end
91
+
92
+ it "returns true" do
93
+ expect(Traitify.valid_jwt_token?(current_token)).to be true
94
+ end
95
+ end
96
+
97
+ context "when token is signed with legacy key" do
98
+ let(:legacy_token) do
99
+ JWT.encode(valid_payload, legacy_private_key, "RS256")
100
+ end
101
+
102
+ it "returns true" do
103
+ expect(Traitify.valid_jwt_token?(legacy_token)).to be true
104
+ end
105
+ end
106
+ end
107
+
108
+ context "with future iat" do
109
+ let(:future_iat_payload) do
110
+ {
111
+ iss: "Traitify by Paradox",
112
+ iat: Time.now.to_i + 100,
113
+ nbf: Time.now.to_i - 50,
114
+ jti: "unique-token-id"
115
+ }
116
+ end
117
+ let(:future_iat_token) do
118
+ JWT.encode(future_iat_payload, private_key, "RS256")
119
+ end
120
+
121
+ it "returns false" do
122
+ expect(Traitify.valid_jwt_token?(future_iat_token)).to be false
123
+ end
124
+ end
125
+
126
+ context "with future nbf" do
127
+ let(:future_nbf_payload) do
128
+ {
129
+ iss: "Traitify by Paradox",
130
+ iat: Time.now.to_i - 100,
131
+ nbf: Time.now.to_i + 50,
132
+ jti: "unique-token-id"
133
+ }
134
+ end
135
+ let(:future_nbf_token) do
136
+ JWT.encode(future_nbf_payload, private_key, "RS256")
137
+ end
138
+
139
+ it "returns false" do
140
+ expect(Traitify.valid_jwt_token?(future_nbf_token)).to be false
141
+ end
142
+ end
143
+
144
+ context "with missing jti" do
145
+ let(:missing_jti_payload) do
146
+ {
147
+ iss: "Traitify by Paradox",
148
+ iat: Time.now.to_i - 100,
149
+ nbf: Time.now.to_i - 50
150
+ }
151
+ end
152
+ let(:missing_jti_token) do
153
+ JWT.encode(missing_jti_payload, private_key, "RS256")
154
+ end
155
+
156
+ it "returns false" do
157
+ expect(Traitify.valid_jwt_token?(missing_jti_token)).to be false
158
+ end
159
+ end
160
+
161
+ context "with blank jti" do
162
+ let(:blank_jti_payload) do
163
+ {
164
+ iss: "Traitify by Paradox",
165
+ iat: Time.now.to_i - 100,
166
+ nbf: Time.now.to_i - 50,
167
+ jti: ""
168
+ }
169
+ end
170
+ let(:blank_jti_token) do
171
+ JWT.encode(blank_jti_payload, private_key, "RS256")
172
+ end
173
+
174
+ it "returns false" do
175
+ expect(Traitify.valid_jwt_token?(blank_jti_token)).to be false
176
+ end
177
+ end
178
+
179
+ context "with nil jti" do
180
+ let(:nil_jti_payload) do
181
+ {
182
+ iss: "Traitify by Paradox",
183
+ iat: Time.now.to_i - 100,
184
+ nbf: Time.now.to_i - 50,
185
+ jti: nil
186
+ }
187
+ end
188
+ let(:nil_jti_token) do
189
+ JWT.encode(nil_jti_payload, private_key, "RS256")
190
+ end
191
+
192
+ it "returns false" do
193
+ expect(Traitify.valid_jwt_token?(nil_jti_token)).to be false
194
+ end
195
+ end
196
+
197
+ context "with missing iat" do
198
+ let(:missing_iat_payload) do
199
+ {
200
+ iss: "Traitify by Paradox",
201
+ nbf: Time.now.to_i - 50,
202
+ jti: "unique-token-id"
203
+ }
204
+ end
205
+ let(:missing_iat_token) do
206
+ JWT.encode(missing_iat_payload, private_key, "RS256")
207
+ end
208
+
209
+ it "returns true" do
210
+ expect(Traitify.valid_jwt_token?(missing_iat_token)).to be true
211
+ end
212
+ end
213
+
214
+ context "with missing nbf" do
215
+ let(:missing_nbf_payload) do
216
+ {
217
+ iss: "Traitify by Paradox",
218
+ iat: Time.now.to_i - 100,
219
+ jti: "unique-token-id"
220
+ }
221
+ end
222
+ let(:missing_nbf_token) do
223
+ JWT.encode(missing_nbf_payload, private_key, "RS256")
224
+ end
225
+
226
+ it "returns true" do
227
+ expect(Traitify.valid_jwt_token?(missing_nbf_token)).to be true
228
+ end
229
+ end
230
+
231
+ context "when no public keys are configured" do
232
+ before do
233
+ Traitify.jwt_public_keys = nil
234
+ end
235
+
236
+ it "returns false" do
237
+ expect(Traitify.valid_jwt_token?("any.token")).to be false
238
+ end
239
+ end
240
+ end
241
+ end
242
+
data/traitify.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.summary = "Traitify Gem"
11
11
  spec.description = "Traitify is a ruby gem wrapper for the Traitify API"
12
12
  spec.authors = ["Tom Prats", "Eric Fleming", "Carson Wright"]
13
- spec.email = "tom@traitify.com"
13
+ spec.email = "tom.prats@paradox.ai"
14
14
  spec.homepage = "https://www.traitify.com"
15
15
 
16
16
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
@@ -21,12 +21,15 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency "activesupport", ">= 5.1", "< 8.x"
22
22
  spec.add_runtime_dependency "faraday", "~> 2.5"
23
23
  spec.add_runtime_dependency "faraday-net_http", "~> 3.0"
24
+ spec.add_runtime_dependency "faraday-retry", "~> 2.2"
25
+ spec.add_runtime_dependency "jwt", "~> 2.0"
24
26
 
25
27
  spec.add_development_dependency "binding_of_caller", "~> 1.0"
26
28
  spec.add_development_dependency "bundler", "~> 2.2"
27
29
  spec.add_development_dependency "pry", "~> 0.14"
28
30
  spec.add_development_dependency "rake", "~> 13.0"
29
31
  spec.add_development_dependency "rspec", "~> 3.11"
32
+ spec.add_development_dependency "rubocop-traitify", "~> 1.2"
30
33
  spec.add_development_dependency "simplecov", "~> 0.21.2"
31
34
  spec.add_development_dependency "webmock", "~> 3.18"
32
35
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: traitify
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Prats
8
8
  - Eric Fleming
9
9
  - Carson Wright
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-09-16 00:00:00.000000000 Z
13
+ date: 2026-01-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -60,6 +60,34 @@ dependencies:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
62
  version: '3.0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: faraday-retry
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2.2'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.2'
77
+ - !ruby/object:Gem::Dependency
78
+ name: jwt
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.0'
84
+ type: :runtime
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '2.0'
63
91
  - !ruby/object:Gem::Dependency
64
92
  name: binding_of_caller
65
93
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +158,20 @@ dependencies:
130
158
  - - "~>"
131
159
  - !ruby/object:Gem::Version
132
160
  version: '3.11'
161
+ - !ruby/object:Gem::Dependency
162
+ name: rubocop-traitify
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '1.2'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - "~>"
173
+ - !ruby/object:Gem::Version
174
+ version: '1.2'
133
175
  - !ruby/object:Gem::Dependency
134
176
  name: simplecov
135
177
  requirement: !ruby/object:Gem::Requirement
@@ -159,7 +201,7 @@ dependencies:
159
201
  - !ruby/object:Gem::Version
160
202
  version: '3.18'
161
203
  description: Traitify is a ruby gem wrapper for the Traitify API
162
- email: tom@traitify.com
204
+ email: tom.prats@paradox.ai
163
205
  executables: []
164
206
  extensions: []
165
207
  extra_rdoc_files: []
@@ -217,6 +259,7 @@ files:
217
259
  - spec/traitify/client/examples/major_spec.rb
218
260
  - spec/traitify/client/examples/profiles_spec.rb
219
261
  - spec/traitify/client/examples/result_spec.rb
262
+ - spec/traitify/client/examples/retry_spec.rb
220
263
  - spec/traitify/client/examples/slide_spec.rb
221
264
  - spec/traitify/client/model_spec.rb
222
265
  - spec/traitify/client/request_spec.rb
@@ -226,12 +269,13 @@ files:
226
269
  - spec/traitify/error_spec.rb
227
270
  - spec/traitify/response_spec.rb
228
271
  - spec/traitify/version_spec.rb
272
+ - spec/traitify_spec.rb
229
273
  - traitify.gemspec
230
274
  homepage: https://www.traitify.com
231
275
  licenses:
232
276
  - MIT
233
277
  metadata: {}
234
- post_install_message:
278
+ post_install_message:
235
279
  rdoc_options: []
236
280
  require_paths:
237
281
  - lib
@@ -247,7 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
247
291
  version: '0'
248
292
  requirements: []
249
293
  rubygems_version: 3.1.4
250
- signing_key:
294
+ signing_key:
251
295
  specification_version: 4
252
296
  summary: Traitify Gem
253
297
  test_files:
@@ -282,6 +326,7 @@ test_files:
282
326
  - spec/traitify/client/examples/major_spec.rb
283
327
  - spec/traitify/client/examples/profiles_spec.rb
284
328
  - spec/traitify/client/examples/result_spec.rb
329
+ - spec/traitify/client/examples/retry_spec.rb
285
330
  - spec/traitify/client/examples/slide_spec.rb
286
331
  - spec/traitify/client/model_spec.rb
287
332
  - spec/traitify/client/request_spec.rb
@@ -291,3 +336,4 @@ test_files:
291
336
  - spec/traitify/error_spec.rb
292
337
  - spec/traitify/response_spec.rb
293
338
  - spec/traitify/version_spec.rb
339
+ - spec/traitify_spec.rb