castle-rb 7.1.2 → 8.0.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -159
  3. data/lib/castle/api/authenticate.rb +3 -12
  4. data/lib/castle/api/end_impersonation.rb +1 -3
  5. data/lib/castle/api/filter.rb +2 -10
  6. data/lib/castle/api/log.rb +2 -10
  7. data/lib/castle/api/risk.rb +2 -10
  8. data/lib/castle/api/start_impersonation.rb +1 -3
  9. data/lib/castle/api/track.rb +1 -3
  10. data/lib/castle/client.rb +1 -5
  11. data/lib/castle/commands/log.rb +1 -5
  12. data/lib/castle/commands/risk.rb +1 -5
  13. data/lib/castle/configuration.rb +2 -6
  14. data/lib/castle/core/process_response.rb +25 -2
  15. data/lib/castle/core/process_webhook.rb +4 -7
  16. data/lib/castle/core/send_request.rb +1 -3
  17. data/lib/castle/errors.rb +8 -0
  18. data/lib/castle/headers/extract.rb +1 -3
  19. data/lib/castle/headers/filter.rb +2 -3
  20. data/lib/castle/payload/prepare.rb +1 -2
  21. data/lib/castle/session.rb +1 -1
  22. data/lib/castle/version.rb +1 -1
  23. data/lib/castle.rb +1 -3
  24. data/spec/integration/rails/rails_spec.rb +9 -3
  25. data/spec/integration/rails/support/application.rb +2 -2
  26. data/spec/integration/rails/support/home_controller.rb +4 -30
  27. data/spec/lib/castle/api/approve_device_spec.rb +2 -6
  28. data/spec/lib/castle/api/authenticate_spec.rb +22 -25
  29. data/spec/lib/castle/api/end_impersonation_spec.rb +8 -14
  30. data/spec/lib/castle/api/get_device_spec.rb +1 -3
  31. data/spec/lib/castle/api/get_devices_for_user_spec.rb +1 -3
  32. data/spec/lib/castle/api/report_device_spec.rb +2 -6
  33. data/spec/lib/castle/api/start_impersonation_spec.rb +8 -14
  34. data/spec/lib/castle/api/track_spec.rb +9 -16
  35. data/spec/lib/castle/client_id/extract_spec.rb +2 -9
  36. data/spec/lib/castle/client_spec.rb +37 -78
  37. data/spec/lib/castle/command_spec.rb +3 -3
  38. data/spec/lib/castle/commands/approve_device_spec.rb +2 -2
  39. data/spec/lib/castle/commands/authenticate_spec.rb +17 -24
  40. data/spec/lib/castle/commands/end_impersonation_spec.rb +14 -21
  41. data/spec/lib/castle/commands/filter_spec.rb +15 -15
  42. data/spec/lib/castle/commands/get_device_spec.rb +2 -2
  43. data/spec/lib/castle/commands/get_devices_for_user_spec.rb +2 -2
  44. data/spec/lib/castle/commands/log_spec.rb +15 -15
  45. data/spec/lib/castle/commands/report_device_spec.rb +2 -2
  46. data/spec/lib/castle/commands/risk_spec.rb +15 -15
  47. data/spec/lib/castle/commands/start_impersonation_spec.rb +14 -21
  48. data/spec/lib/castle/commands/track_spec.rb +19 -24
  49. data/spec/lib/castle/configuration_spec.rb +1 -1
  50. data/spec/lib/castle/context/get_default_spec.rb +9 -8
  51. data/spec/lib/castle/context/prepare_spec.rb +3 -4
  52. data/spec/lib/castle/core/process_response_spec.rb +3 -6
  53. data/spec/lib/castle/core/process_webhook_spec.rb +12 -6
  54. data/spec/lib/castle/core/send_request_spec.rb +7 -11
  55. data/spec/lib/castle/failover/strategy_spec.rb +5 -5
  56. data/spec/lib/castle/headers/extract_spec.rb +1 -1
  57. data/spec/lib/castle/headers/filter_spec.rb +6 -3
  58. data/spec/lib/castle/headers/format_spec.rb +5 -5
  59. data/spec/lib/castle/ips/extract_spec.rb +2 -6
  60. data/spec/lib/castle/logger_spec.rb +2 -1
  61. data/spec/lib/castle/payload/prepare_spec.rb +4 -7
  62. data/spec/lib/castle/secure_mode_spec.rb +1 -3
  63. data/spec/lib/castle/session_spec.rb +1 -5
  64. data/spec/lib/castle/singleton_configuration_spec.rb +1 -1
  65. data/spec/lib/castle/utils/clone_spec.rb +1 -1
  66. data/spec/lib/castle/utils/merge_spec.rb +2 -4
  67. data/spec/lib/castle/validators/not_supported_spec.rb +1 -6
  68. data/spec/lib/castle/validators/present_spec.rb +2 -9
  69. data/spec/lib/castle/verdict_spec.rb +3 -3
  70. data/spec/lib/castle/webhooks/verify_spec.rb +12 -6
  71. data/spec/lib/castle_spec.rb +5 -7
  72. data/spec/support/shared_examples/action_request.rb +37 -25
  73. data/spec/support/shared_examples/configuration.rb +14 -16
  74. metadata +48 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2d022daae00e63158ee2f9d886faa5dbf3f70b89c16775c14f9328b0096f446
4
- data.tar.gz: 1820e1afb9bd65683f5dd1a183369ed231b616c8466ee5c2b3d3cdd59d704319
3
+ metadata.gz: e9dc06eac5803e36c8576d9938442ba1c1f6bb852420c7a736044127deea50b8
4
+ data.tar.gz: 5d89738c670359a0345d51b41d0137d614622002711f20665238a4c289f28bab
5
5
  SHA512:
6
- metadata.gz: '009de5db675b53f46bb90f0f2cfe57cb081598c5af02d3770125d79d27739b03e82e6170013b86d5e74321e26a9f053023f6914ae68c1a7770b3ee2496c0482b'
7
- data.tar.gz: 2dee44618c8c3318ba1dacc2f4484187fc8e28d2413db81531c9e46451da61dfcbea6aa5916a6df2d0eceaf6e5f97bed4c81f08bb3f9e1607f4ac798c90561ea
6
+ metadata.gz: 118236690b20da223208e043cb4aafea5c1d9870e0d2e44d87106a45122652ca894f8bfbc79bf465e474923a026b9b2253a6c7a1ab2c2f5157b0b21d51459d6c
7
+ data.tar.gz: 223933e1de62111635ba5d22fed064541a1d54aee9c9374fc8524a808b27b623245ee0d5240a42d0eff4ac72e214cc4f61ce44bb72f15591d3944790d8bc3e0d
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Coverage Status](https://coveralls.io/repos/github/castle/castle-ruby/badge.svg?branch=coveralls)](https://coveralls.io/github/castle/castle-ruby?branch=coveralls)
5
5
  [![Gem Version](https://badge.fury.io/rb/castle-rb.svg)](https://badge.fury.io/rb/castle-rb)
6
6
 
7
- **[Castle](https://castle.io) analyzes device, location, and interaction patterns in your web and mobile apps and lets you stop account takeover attacks in real-time..**
7
+ **[Castle](https://castle.io) analyzes user behavior in web and mobile apps to stop fraud before it happens.**
8
8
 
9
9
  ## Installation
10
10
 
@@ -26,11 +26,11 @@ Castle.api_secret = 'YOUR_API_SECRET'
26
26
 
27
27
  A Castle client instance will be made available as `castle` in your
28
28
 
29
- * Rails controllers when you add `require 'castle/support/rails'`
29
+ - Rails controllers when you add `require 'castle/support/rails'`
30
30
 
31
- * Padrino controllers when you add `require 'castle/support/padrino'`
31
+ - Padrino controllers when you add `require 'castle/support/padrino'`
32
32
 
33
- * Sinatra app when you add `require 'castle/support/sinatra'` (and additionally explicitly add `register Sinatra::Castle` to your `Sinatra::Base` class if you have a modular application)
33
+ - Sinatra app when you add `require 'castle/support/sinatra'` (and additionally explicitly add `register Sinatra::Castle` to your `Sinatra::Base` class if you have a modular application)
34
34
 
35
35
  ```ruby
36
36
  require 'castle/support/sinatra'
@@ -40,7 +40,7 @@ class ApplicationController < Sinatra::Base
40
40
  end
41
41
  ```
42
42
 
43
- * Hanami when you add `require 'castle/support/hanami'` and include `Castle::Hanami` to your Hanami application
43
+ - Hanami when you add `require 'castle/support/hanami'` and include `Castle::Hanami` to your Hanami application
44
44
 
45
45
  ```ruby
46
46
  require 'castle/support/hanami'
@@ -119,8 +119,10 @@ Castle.configure do |config|
119
119
  # you can achieve this by listing all the proxies ip defined by string or regular expressions
120
120
  # in the trusted_proxies setting
121
121
  config.trusted_proxies = []
122
+
122
123
  # or by providing number of trusted proxies used in the chain
123
124
  config.trusted_proxy_depth = 0
125
+
124
126
  # note that you must pick one approach over the other.
125
127
 
126
128
  # If there is no possibility to define options above and there is no other header that holds the client IP,
@@ -138,10 +140,11 @@ It is also possible to define multiple configs within one application.
138
140
 
139
141
  ```ruby
140
142
  # Initialize new instance of Castle::Configuration
141
- config = Castle::Configuration.new.tap do |c|
142
- # and set any attribute
143
- c.api_secret = 'YOUR_API_SECRET'
144
- end
143
+ config =
144
+ Castle::Configuration.new.tap do |c|
145
+ # and set any attribute
146
+ c.api_secret = 'YOUR_API_SECRET'
147
+ end
145
148
  ```
146
149
 
147
150
  After a successful setup, you can pass the config to any API command as follows:
@@ -150,159 +153,11 @@ After a successful setup, you can pass the config to any API command as follows:
150
153
  ::Castle::API::GetDevice.call(device_token: device_token, config: config)
151
154
  ```
152
155
 
153
- ## Event Context
154
-
155
- The client will automatically configure the context for each request.
156
-
157
- ### Overriding Default Context Properties
158
-
159
- If you need to modify the event context properties or if you desire to add additional properties such as user traits to the context, you can pass the properties along with the other data. For example:
160
- ```ruby
161
- {
162
- event: '$login.succeeded',
163
- user_id: user.id,
164
- properties: {
165
- key: 'value'
166
- },
167
- user_traits: {
168
- key: 'value'
169
- },
170
- context: {
171
- section: 'mobile'
172
- }
173
- }
174
- ```
175
-
176
- ## Tracking
177
-
178
- Here is a simple example of a track event.
179
-
180
- ```ruby
181
- begin
182
- castle.track(
183
- event: '$login.succeeded',
184
- user_id: user.id
185
- )
186
- rescue Castle::Error => e
187
- puts e.message
188
- end
189
- ```
190
-
191
- ## Signature
156
+ ## Usage
192
157
 
193
- `Castle::SecureMode.signature(user_id)` will create a signed user_id.
194
-
195
- ## Async tracking
196
-
197
- By default Castle sends requests synchronously. To eg. use Sidekiq to send requests in a background worker you can pass data to the worker:
198
-
199
- #### castle_tracking_worker.rb
200
-
201
- ```ruby
202
- class CastleTrackingWorker
203
- include Sidekiq::Worker
204
-
205
- def perform(payload = {})
206
- ::Castle::API::Track.call(payload)
207
- end
208
- end
209
- ```
210
-
211
- #### tracking_controller.rb
212
-
213
- ```ruby
214
- payload = ::Castle::Payload::Prepare.call(
215
- {
216
- event: '$login.succeeded',
217
- user_id: user.id,
218
- properties: {
219
- key: 'value'
220
- },
221
- user_traits: {
222
- key: 'value'
223
- }
224
- },
225
- request
226
- )
227
- CastleTrackingWorker.perform_async(payload)
228
- ```
229
-
230
- ## Connection reuse
231
-
232
- If you want to reuse the connection to send multiple events:
233
-
234
- ```ruby
235
- Castle::Session.call do |http|
236
- castle.track(
237
- event: '$logout.succeeded',
238
- user_id: user2.id
239
- http: http
240
- )
241
- castle.track(
242
- event: '$login.succeeded',
243
- user_id: user1.id
244
- http: http
245
- )
246
- end
247
- ```
248
-
249
- ## Events
250
-
251
- List of Recognized Events can be found in the [docs](https://docs.castle.io/v1/reference/events/)
252
-
253
- ## Device management
254
-
255
- This SDK allows issuing requests to [Castle's Device Management Endpoints](https://docs.castle.io/v1/reference/api-reference/#devices). Use these endpoints for admin-level management of end-user devices (i.e., for an internal dashboard).
256
-
257
- Fetching device data, approving a device, reporting a device requires a valid `device_token`.
258
-
259
- ```ruby
260
- # Get device data
261
- ::Castle::API::GetDevice.call(device_token: device_token)
262
- # Approve a device
263
- ::Castle::API::ApproveDevice.call(device_token: device_token)
264
- # Report a device
265
- ::Castle::API::ReportDevice.call(device_token: device_token)
266
- ```
267
-
268
- #### castle_device_reporting_worker.rb
269
-
270
- ```ruby
271
- class CastleDeviceReportingWorker
272
- include Sidekiq::Worker
273
-
274
- def perform(device_token)
275
- ::Castle::API::ReportDevice.call(device_token: device_token)
276
- end
277
- end
278
- ```
279
-
280
- Fetching available devices that belong to a given user requires a valid `user_id`.
281
-
282
- ```ruby
283
- # Get user's devices data
284
- ::Castle::API::GetDevicesForUser.call(user_id: user.id)
285
- ```
286
-
287
- ## Impersonation mode
288
-
289
- https://castle.io/docs/impersonation_mode
158
+ See [documentation](https://docs.castle.io/docs/) for how to use this SDK with the Castle APIs
290
159
 
291
160
  ## Exceptions
292
161
 
293
162
  `Castle::Error` will be thrown if the Castle API returns a 400 or a 500 level HTTP response.
294
163
  You can also choose to catch a more [finegrained error](https://github.com/castle/castle-ruby/blob/master/lib/castle/errors.rb).
295
-
296
- ## Webhooks
297
-
298
- Castle uses webhooks to notify about `$incident.confirmed` or `$review.opened` events. Each webhook has `X-Castle-Signature` header that allows verifying webhook's source.
299
-
300
- ```ruby
301
- # Verify the webhook, passed as a Request object
302
- ::Castle::Webhooks::Verify.call(webhook_request)
303
- # Castle::WebhookVerificationError is raised when the signature is not matching
304
- ```
305
-
306
- ## Documentation
307
-
308
- [Official Castle docs](https://docs.castle.io/)
@@ -7,26 +7,17 @@ module Castle
7
7
  # @param options [Hash]
8
8
  # return [Hash]
9
9
  def call(options = {})
10
- unless options[:no_symbolize]
11
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
12
- end
10
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
13
11
  options.delete(:no_symbolize)
14
12
  http = options.delete(:http)
15
13
  config = options.delete(:config) || Castle.config
16
14
 
17
- response =
18
- Castle::API.call(Castle::Commands::Authenticate.build(options), {}, http, config)
15
+ response = Castle::API.call(Castle::Commands::Authenticate.build(options), {}, http, config)
19
16
  response.merge(failover: false, failover_reason: nil)
20
17
  rescue Castle::RequestError, Castle::InternalServerError => e
21
18
  unless config.failover_strategy == :throw
22
19
  strategy = (config || Castle.config).failover_strategy
23
- return(
24
- Castle::Failover::PrepareResponse.new(
25
- options[:user_id],
26
- reason: e.to_s,
27
- strategy: strategy
28
- ).call
29
- )
20
+ return(Castle::Failover::PrepareResponse.new(options[:user_id], reason: e.to_s, strategy: strategy).call)
30
21
  end
31
22
 
32
23
  raise e
@@ -7,9 +7,7 @@ module Castle
7
7
  class << self
8
8
  # @param options [Hash]
9
9
  def call(options = {})
10
- unless options[:no_symbolize]
11
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
12
- end
10
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
13
11
  options.delete(:no_symbolize)
14
12
  http = options.delete(:http)
15
13
  config = options.delete(:config) || Castle.config
@@ -8,9 +8,7 @@ module Castle
8
8
  # @param options [Hash]
9
9
  # return [Hash]
10
10
  def call(options = {})
11
- unless options[:no_symbolize]
12
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
13
- end
11
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
14
12
  options.delete(:no_symbolize)
15
13
  http = options.delete(:http)
16
14
  config = options.delete(:config) || Castle.config
@@ -20,13 +18,7 @@ module Castle
20
18
  rescue Castle::RequestError, Castle::InternalServerError => e
21
19
  unless config.failover_strategy == :throw
22
20
  strategy = (config || Castle.config).failover_strategy
23
- return(
24
- Castle::Failover::PrepareResponse.new(
25
- options[:user][:id],
26
- reason: e.to_s,
27
- strategy: strategy
28
- ).call
29
- )
21
+ return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
30
22
  end
31
23
 
32
24
  raise e
@@ -8,9 +8,7 @@ module Castle
8
8
  # @param options [Hash]
9
9
  # return [Hash]
10
10
  def call(options = {})
11
- unless options[:no_symbolize]
12
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
13
- end
11
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
14
12
  options.delete(:no_symbolize)
15
13
  http = options.delete(:http)
16
14
  config = options.delete(:config) || Castle.config
@@ -20,13 +18,7 @@ module Castle
20
18
  rescue Castle::RequestError, Castle::InternalServerError => e
21
19
  unless config.failover_strategy == :throw
22
20
  strategy = (config || Castle.config).failover_strategy
23
- return(
24
- Castle::Failover::PrepareResponse.new(
25
- options[:user][:id],
26
- reason: e.to_s,
27
- strategy: strategy
28
- ).call
29
- )
21
+ return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
30
22
  end
31
23
 
32
24
  raise e
@@ -8,9 +8,7 @@ module Castle
8
8
  # @param options [Hash]
9
9
  # return [Hash]
10
10
  def call(options = {})
11
- unless options[:no_symbolize]
12
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
13
- end
11
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
14
12
  options.delete(:no_symbolize)
15
13
  http = options.delete(:http)
16
14
  config = options.delete(:config) || Castle.config
@@ -20,13 +18,7 @@ module Castle
20
18
  rescue Castle::RequestError, Castle::InternalServerError => e
21
19
  unless config.failover_strategy == :throw
22
20
  strategy = (config || Castle.config).failover_strategy
23
- return(
24
- Castle::Failover::PrepareResponse.new(
25
- options[:user][:id],
26
- reason: e.to_s,
27
- strategy: strategy
28
- ).call
29
- )
21
+ return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
30
22
  end
31
23
 
32
24
  raise e
@@ -7,9 +7,7 @@ module Castle
7
7
  class << self
8
8
  # @param options [Hash]
9
9
  def call(options = {})
10
- unless options[:no_symbolize]
11
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
12
- end
10
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
13
11
  options.delete(:no_symbolize)
14
12
  http = options.delete(:http)
15
13
  config = options.delete(:config) || Castle.config
@@ -6,9 +6,7 @@ module Castle
6
6
  class << self
7
7
  # @param options [Hash]
8
8
  def call(options = {})
9
- unless options[:no_symbolize]
10
- options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
11
- end
9
+ options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
12
10
  options.delete(:no_symbolize)
13
11
  http = options.delete(:http)
14
12
  config = options.delete(:config) || Castle.config
data/lib/castle/client.rb CHANGED
@@ -123,11 +123,7 @@ module Castle
123
123
 
124
124
  # @param user_id [String, Boolean]
125
125
  def generate_do_not_track_response(user_id)
126
- Castle::Failover::PrepareResponse.new(
127
- user_id,
128
- strategy: :allow,
129
- reason: 'Castle is set to do not track.'
130
- ).call
126
+ Castle::Failover::PrepareResponse.new(user_id, strategy: :allow, reason: 'Castle is set to do not track.').call
131
127
  end
132
128
 
133
129
  # @param options [Hash]
@@ -10,11 +10,7 @@ module Castle
10
10
  def build(options = {})
11
11
  context = Castle::Context::Sanitize.call(options[:context])
12
12
 
13
- Castle::Command.new(
14
- 'log',
15
- options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
16
- :post
17
- )
13
+ Castle::Command.new('log', options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call), :post)
18
14
  end
19
15
  end
20
16
  end
@@ -10,11 +10,7 @@ module Castle
10
10
  def build(options = {})
11
11
  context = Castle::Context::Sanitize.call(options[:context])
12
12
 
13
- Castle::Command.new(
14
- 'risk',
15
- options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
16
- :post
17
- )
13
+ Castle::Command.new('risk', options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call), :post)
18
14
  end
19
15
  end
20
16
  end
@@ -45,7 +45,6 @@ module Castle
45
45
  Te
46
46
  Upgrade-Insecure-Requests
47
47
  User-Agent
48
- X-Castle-Client-Id
49
48
  X-Requested-With
50
49
  ].freeze
51
50
 
@@ -105,9 +104,7 @@ module Castle
105
104
  # sets trusted proxies
106
105
  # @param value [Array<String,Regexp>]
107
106
  def trusted_proxies=(value)
108
- unless value.is_a?(Array)
109
- raise Castle::ConfigurationError, 'trusted proxies must be an Array'
110
- end
107
+ raise Castle::ConfigurationError, 'trusted proxies must be an Array' unless value.is_a?(Array)
111
108
 
112
109
  @trusted_proxies = value
113
110
  end
@@ -122,8 +119,7 @@ module Castle
122
119
  end
123
120
 
124
121
  def failover_strategy=(value)
125
- @failover_strategy =
126
- Castle::Failover::STRATEGIES.detect { |strategy| strategy == value.to_sym }
122
+ @failover_strategy = Castle::Failover::STRATEGIES.detect { |strategy| strategy == value.to_sym }
127
123
  raise Castle::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
128
124
  end
129
125
 
@@ -10,9 +10,11 @@ module Castle
10
10
  403 => Castle::ForbiddenError,
11
11
  404 => Castle::NotFoundError,
12
12
  419 => Castle::UserUnauthorizedError,
13
- 422 => Castle::InvalidParametersError
13
+ 429 => Castle::RateLimitError
14
14
  }.freeze
15
15
 
16
+ INVALID_REQUEST_TOKEN = 'invalid_request_token'
17
+
16
18
  class << self
17
19
  # @param response [Response]
18
20
  # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
@@ -36,8 +38,29 @@ module Castle
36
38
 
37
39
  raise Castle::InternalServerError if response.code.to_i.between?(500, 599)
38
40
 
41
+ raise_error422(response) if response.code.to_i == 422
42
+
39
43
  error = RESPONSE_ERRORS.fetch(response.code.to_i, Castle::ApiError)
40
- raise error, response[:message]
44
+
45
+ raise error
46
+ end
47
+
48
+ def raise_error422(response)
49
+ if response.body
50
+ begin
51
+ parsed_body = JSON.parse(response.body, symbolize_names: true)
52
+ if parsed_body.is_a?(Hash) && parsed_body.key?(:type)
53
+ if parsed_body[:type] == INVALID_REQUEST_TOKEN
54
+ raise Castle::InvalidRequestTokenError, parsed_body[:message]
55
+ end
56
+
57
+ raise Castle::InvalidParametersError, parsed_body[:message]
58
+ end
59
+ rescue JSON::ParserError
60
+ end
61
+ end
62
+
63
+ raise Castle::InvalidParametersError
41
64
  end
42
65
  end
43
66
  end
@@ -10,14 +10,11 @@ module Castle
10
10
  # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
11
11
  # @return [String]
12
12
  def call(webhook, config = nil)
13
- webhook
14
- .body
15
- .read
16
- .tap do |result|
17
- raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
13
+ webhook.body.read.tap do |result|
14
+ raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
18
15
 
19
- Castle::Logger.call('webhook:', result.to_s, config)
20
- end
16
+ Castle::Logger.call('webhook:', result.to_s, config)
17
+ end
21
18
  end
22
19
  end
23
20
  end
@@ -15,9 +15,7 @@ module Castle
15
15
  # @param http [Net::HTTP]
16
16
  # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
17
17
  def call(command, headers, http = nil, config = nil)
18
- (http || Castle::Core::GetConnection.call).request(
19
- build(command, headers.merge(DEFAULT_HEADERS), config)
20
- )
18
+ (http || Castle::Core::GetConnection.call).request(build(command, headers.merge(DEFAULT_HEADERS), config))
21
19
  end
22
20
 
23
21
  # @param command [String]
data/lib/castle/errors.rb CHANGED
@@ -53,10 +53,18 @@ module Castle
53
53
  class InvalidParametersError < Castle::ApiError
54
54
  end
55
55
 
56
+ # api error invalid param 422 (invalid token)
57
+ class InvalidRequestTokenError < Castle::ApiError
58
+ end
59
+
56
60
  # api error unauthorized 401
57
61
  class UnauthorizedError < Castle::ApiError
58
62
  end
59
63
 
64
+ # api error too many requests 429
65
+ class RateLimitError < Castle::ApiError
66
+ end
67
+
60
68
  # all internal server errors
61
69
  class InternalServerError < Castle::ApiError
62
70
  end
@@ -23,9 +23,7 @@ module Castle
23
23
  # Serialize HTTP headers
24
24
  # @return [Hash]
25
25
  def call
26
- @headers.each_with_object({}) do |(name, value), acc|
27
- acc[name] = header_value(name, value)
28
- end
26
+ @headers.each_with_object({}) { |(name, value), acc| acc[name] = header_value(name, value) }
29
27
  end
30
28
 
31
29
  private
@@ -12,8 +12,7 @@ module Castle
12
12
  HTTP(?:_|-).*|
13
13
  CONTENT(?:_|-)LENGTH|
14
14
  REMOTE(?:_|-)ADDR
15
- $/xi
16
- .freeze
15
+ $/xi.freeze
17
16
 
18
17
  private_constant :VALUABLE_HEADERS
19
18
 
@@ -32,7 +31,7 @@ module Castle
32
31
  next unless header_name.match(VALUABLE_HEADERS)
33
32
 
34
33
  formatted_name = @header_format.call(header_name)
35
- acc[formatted_name] = @request_env[header_name]
34
+ acc[formatted_name] = @request_env[header_name].to_s
36
35
  end
37
36
  end
38
37
  end
@@ -12,8 +12,7 @@ module Castle
12
12
  def call(payload_options, request, options = {})
13
13
  context = Castle::Context::Prepare.call(request, payload_options.merge(options))
14
14
 
15
- payload =
16
- Castle::Utils::DeepSymbolizeKeys.call(payload_options || {}).merge(context: context)
15
+ payload = Castle::Utils::DeepSymbolizeKeys.call(payload_options || {}).merge(context: context)
17
16
  payload[:timestamp] ||= Castle::Utils::GetTimestamp.call
18
17
 
19
18
  warn '[DEPRECATION] use user_traits instead of traits key' if payload.key?(:traits)
@@ -9,7 +9,7 @@ module Castle
9
9
 
10
10
  class << self
11
11
  def call(&block)
12
- return unless block_given?
12
+ return unless block
13
13
 
14
14
  Castle::Core::GetConnection.call.start(&block)
15
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- VERSION = '7.1.2'
4
+ VERSION = '8.0.0'
5
5
  end
data/lib/castle.rb CHANGED
@@ -67,9 +67,7 @@
67
67
  module Castle
68
68
  class << self
69
69
  def configure(config_hash = nil)
70
- (config_hash || {}).each do |config_name, config_value|
71
- config.send("#{config_name}=", config_value)
72
- end
70
+ (config_hash || {}).each { |config_name, config_value| config.send(:"#{config_name}=", config_value) }
73
71
 
74
72
  yield(config) if block_given?
75
73
  end
@@ -24,11 +24,12 @@ RSpec.describe HomeController, type: :request do
24
24
  'Accept' =>
25
25
  'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
26
26
  'Authorization' => true,
27
- 'Content-Length' => '0',
28
27
  'Cookie' => true,
28
+ 'Content-Length' => '0',
29
29
  'Host' => 'www.example.com',
30
30
  'X-Forwarded-For' => '5.5.5.5, 1.2.3.4',
31
- 'Remote-Addr' => '127.0.0.1'
31
+ 'Remote-Addr' => '127.0.0.1',
32
+ 'Version' => 'HTTP/1.0'
32
33
  },
33
34
  'ip' => '1.2.3.4',
34
35
  'library' => {
@@ -40,7 +41,12 @@ RSpec.describe HomeController, type: :request do
40
41
  end
41
42
  let(:now) { Time.now }
42
43
  let(:headers) do
43
- { 'HTTP_AUTHORIZATION' => 'Basic 123', 'HTTP_X_FORWARDED_FOR' => '5.5.5.5, 1.2.3.4' }
44
+ {
45
+ 'HTTP_AUTHORIZATION' => 'Basic 123',
46
+ 'HTTP_X_FORWARDED_FOR' => '5.5.5.5, 1.2.3.4',
47
+ 'HTTP_VERSION' => 'HTTP/1.0',
48
+ 'HTTP_CONTENT_LENGTH' => '0'
49
+ }
44
50
  end
45
51
 
46
52
  before do
@@ -3,8 +3,8 @@
3
3
  require 'action_controller/railtie'
4
4
 
5
5
  class TestApp < Rails::Application
6
- secrets.secret_token = 'secret_token'
7
- secrets.secret_key_base = 'secret_key_base'
6
+ credentials.secret_token = 'secret_token'
7
+ credentials.secret_key_base = 'secret_key_base'
8
8
 
9
9
  config.logger = Logger.new($stdout)
10
10
  Rails.logger = config.logger