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 +4 -4
- data/CHANGELOG.md +21 -3
- data/Gemfile.lock +114 -66
- data/README.md +44 -0
- data/lib/traitify/client/connection.rb +8 -6
- data/lib/traitify/client/request.rb +9 -2
- data/lib/traitify/configuration.rb +7 -4
- data/lib/traitify/error.rb +4 -0
- data/lib/traitify/version.rb +1 -1
- data/lib/traitify.rb +60 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/traitify/client/examples/configuration_spec.rb +5 -5
- data/spec/traitify/client/examples/retry_spec.rb +60 -0
- data/spec/traitify_spec.rb +242 -0
- data/traitify.gemspec +4 -1
- metadata +52 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f53da4ace0d1005c2982c0e2681132c21a05e9d9365057eab89e488caf10d4f
|
|
4
|
+
data.tar.gz: be22aff51d262cc2c685cf51b957547ad5e4d0a4d16c4a15b47db7e3913cd130
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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.
|
|
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.
|
|
18
|
-
public_suffix (>= 2.0.2, <
|
|
19
|
-
ast (2.4.
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
24
|
-
|
|
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.
|
|
27
|
-
diff-lcs (1.
|
|
28
|
-
docile (1.4.
|
|
29
|
-
|
|
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.
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
45
|
-
|
|
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
|
|
48
|
-
regexp_parser (2.
|
|
49
|
-
rexml (3.
|
|
50
|
-
rspec (3.
|
|
51
|
-
rspec-core (~> 3.
|
|
52
|
-
rspec-expectations (~> 3.
|
|
53
|
-
rspec-mocks (~> 3.
|
|
54
|
-
rspec-core (3.
|
|
55
|
-
rspec-support (~> 3.
|
|
56
|
-
rspec-expectations (3.
|
|
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.
|
|
59
|
-
rspec-mocks (3.
|
|
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.
|
|
62
|
-
rspec-support (3.
|
|
63
|
-
rubocop (
|
|
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 (>=
|
|
97
|
+
parser (>= 3.3.0.2)
|
|
66
98
|
rainbow (>= 2.2.2, < 4.0)
|
|
67
|
-
regexp_parser (>=
|
|
68
|
-
|
|
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 (>=
|
|
72
|
-
rubocop-airbnb (
|
|
73
|
-
|
|
74
|
-
rubocop
|
|
75
|
-
rubocop-
|
|
76
|
-
rubocop-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 (>=
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
142
|
+
simplecov-html (0.13.2)
|
|
98
143
|
simplecov_json_formatter (0.1.4)
|
|
99
|
-
tzinfo (2.0.
|
|
144
|
+
tzinfo (2.0.6)
|
|
100
145
|
concurrent-ruby (~> 1.0)
|
|
101
|
-
unicode-display_width (
|
|
102
|
-
|
|
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.
|
|
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
|
|
12
|
-
faraday.request
|
|
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
|
|
16
|
-
faraday.response
|
|
17
|
-
faraday.response
|
|
18
|
-
faraday.adapter
|
|
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
|
|
16
|
-
|
|
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)
|
data/lib/traitify/error.rb
CHANGED
data/lib/traitify/version.rb
CHANGED
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
|
@@ -14,15 +14,15 @@ describe Traitify::Client do
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
it "assigns
|
|
18
|
-
expect(traitify.
|
|
17
|
+
it "assigns host" do
|
|
18
|
+
expect(traitify.host).to eq("https://example.com")
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it "assigns
|
|
22
|
-
expect(traitify.
|
|
21
|
+
it "assigns secret" do
|
|
22
|
+
expect(traitify.secret_key).to eq("secret")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
it "assigns
|
|
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@
|
|
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.
|
|
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:
|
|
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@
|
|
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
|