castle-rb 7.1.2 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2d022daae00e63158ee2f9d886faa5dbf3f70b89c16775c14f9328b0096f446
4
- data.tar.gz: 1820e1afb9bd65683f5dd1a183369ed231b616c8466ee5c2b3d3cdd59d704319
3
+ metadata.gz: 5ada9f9e25a4da59d7577cb10848d45697a6709648846011fc803167a8b96203
4
+ data.tar.gz: 2b0927e4ae6f686a35c9c5ac39e510f46f8fc06506932441b421a845ba296f27
5
5
  SHA512:
6
- metadata.gz: '009de5db675b53f46bb90f0f2cfe57cb081598c5af02d3770125d79d27739b03e82e6170013b86d5e74321e26a9f053023f6914ae68c1a7770b3ee2496c0482b'
7
- data.tar.gz: 2dee44618c8c3318ba1dacc2f4484187fc8e28d2413db81531c9e46451da61dfcbea6aa5916a6df2d0eceaf6e5f97bed4c81f08bb3f9e1607f4ac798c90561ea
6
+ metadata.gz: 5ff2d32ab3ae269adbcd5380ba7488e06c0d9ae0ab463cd73856eb65b67bb4559daa1118344bc1a6896b097f00f9e5cc35c80ce68add430785769c2502e261b0
7
+ data.tar.gz: e48b7c5332de7d74ad8d13a89f52b2fd756d1c2b77600af86b86d74c78789916ac2972983781708be097d9d6f4067705b59ba26eaca2dc3e45720eee4ced6f08
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/)
@@ -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
 
@@ -9,10 +9,11 @@ module Castle
9
9
  401 => Castle::UnauthorizedError,
10
10
  403 => Castle::ForbiddenError,
11
11
  404 => Castle::NotFoundError,
12
- 419 => Castle::UserUnauthorizedError,
13
- 422 => Castle::InvalidParametersError
12
+ 419 => Castle::UserUnauthorizedError
14
13
  }.freeze
15
14
 
15
+ INVALID_REQUEST_TOKEN = 'invalid_request_token'
16
+
16
17
  class << self
17
18
  # @param response [Response]
18
19
  # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
@@ -36,8 +37,29 @@ module Castle
36
37
 
37
38
  raise Castle::InternalServerError if response.code.to_i.between?(500, 599)
38
39
 
40
+ raise_error422(response) if response.code.to_i == 422
41
+
39
42
  error = RESPONSE_ERRORS.fetch(response.code.to_i, Castle::ApiError)
40
- raise error, response[:message]
43
+
44
+ raise error
45
+ end
46
+
47
+ def raise_error422(response)
48
+ if response.body
49
+ begin
50
+ parsed_body = JSON.parse(response.body, symbolize_names: true)
51
+ if parsed_body.is_a?(Hash) && parsed_body.key?(:type)
52
+ if parsed_body[:type] == INVALID_REQUEST_TOKEN
53
+ raise Castle::InvalidRequestTokenError, parsed_body[:message]
54
+ else
55
+ raise Castle::InvalidParametersError, parsed_body[:message]
56
+ end
57
+ end
58
+ rescue JSON::ParserError
59
+ end
60
+ end
61
+
62
+ raise Castle::InvalidParametersError
41
63
  end
42
64
  end
43
65
  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
data/lib/castle/errors.rb CHANGED
@@ -53,6 +53,10 @@ 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- VERSION = '7.1.2'
4
+ VERSION = '7.2.0'
5
5
  end
@@ -41,13 +41,18 @@ describe Castle::Client do
41
41
  let(:time_auto) { time_now.utc.iso8601(3) }
42
42
  let(:time_user) { (Time.now - 10_000).utc.iso8601(3) }
43
43
  let(:response_body) { {}.to_json }
44
+ let(:response_code) { 200 }
45
+
46
+ let(:stub_response) do
47
+ stub_request(:any, /api.castle.io/)
48
+ .with(basic_auth: ['', 'secret'])
49
+ .to_return(status: response_code, body: response_body, headers: {})
50
+ end
44
51
 
45
52
  before do
46
53
  Timecop.freeze(time_now)
47
54
  stub_const('Castle::VERSION', '2.2.0')
48
- stub_request(:any, /api.castle.io/)
49
- .with(basic_auth: ['', 'secret'])
50
- .to_return(status: 200, body: response_body, headers: {})
55
+ stub_response
51
56
  end
52
57
 
53
58
  after { Timecop.return }
@@ -152,4 +152,34 @@ RSpec.shared_examples_for 'action request' do |action|
152
152
  it { expect(request_response[:failover_reason]).to be_eql('Castle::InternalServerError') }
153
153
  end
154
154
  end
155
+
156
+ context 'when request is 422' do
157
+ describe 'throw InvalidParametersError' do
158
+ let(:response_body) { { type: 'bad_request', message: 'wrong params' }.to_json }
159
+ let(:response_code) { 422 }
160
+
161
+ it do
162
+ expect { request_response }.to raise_error(Castle::InvalidParametersError, 'wrong params')
163
+ end
164
+ end
165
+
166
+ describe 'throw InvalidParametersError for legacy endpoints' do
167
+ let(:response_body) { {}.to_json }
168
+ let(:response_code) { 422 }
169
+
170
+ it { expect { request_response }.to raise_error(Castle::InvalidParametersError) }
171
+ end
172
+
173
+ describe 'throw InvalidRequestTokenError' do
174
+ let(:response_body) { { type: 'invalid_request_token', message: 'invalid token' }.to_json }
175
+ let(:response_code) { 422 }
176
+
177
+ it do
178
+ expect { request_response }.to raise_error(
179
+ Castle::InvalidRequestTokenError,
180
+ 'invalid token'
181
+ )
182
+ end
183
+ end
184
+ end
155
185
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castle-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.2
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-02 00:00:00.000000000 Z
11
+ date: 2022-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -177,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
177
  - !ruby/object:Gem::Version
178
178
  version: '0'
179
179
  requirements: []
180
- rubygems_version: 3.2.23
180
+ rubygems_version: 3.2.27
181
181
  signing_key:
182
182
  specification_version: 4
183
183
  summary: Castle