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 +4 -4
- data/README.md +14 -159
- data/lib/castle/configuration.rb +0 -1
- data/lib/castle/core/process_response.rb +25 -3
- data/lib/castle/core/process_webhook.rb +4 -7
- data/lib/castle/errors.rb +4 -0
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/client_spec.rb +8 -3
- data/spec/support/shared_examples/action_request.rb +30 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ada9f9e25a4da59d7577cb10848d45697a6709648846011fc803167a8b96203
|
4
|
+
data.tar.gz: 2b0927e4ae6f686a35c9c5ac39e510f46f8fc06506932441b421a845ba296f27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
29
|
+
- Rails controllers when you add `require 'castle/support/rails'`
|
30
30
|
|
31
|
-
|
31
|
+
- Padrino controllers when you add `require 'castle/support/padrino'`
|
32
32
|
|
33
|
-
|
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
|
-
|
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 =
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
##
|
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
|
-
|
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/)
|
data/lib/castle/configuration.rb
CHANGED
@@ -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
|
-
|
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
|
-
.
|
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
|
-
|
20
|
-
|
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
|
data/lib/castle/version.rb
CHANGED
@@ -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
|
-
|
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.
|
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:
|
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.
|
180
|
+
rubygems_version: 3.2.27
|
181
181
|
signing_key:
|
182
182
|
specification_version: 4
|
183
183
|
summary: Castle
|