castle-rb 4.1.0 → 6.0.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 +158 -43
- data/lib/castle.rb +46 -21
- data/lib/castle/api.rb +24 -12
- data/lib/castle/api/approve_device.rb +25 -0
- data/lib/castle/api/authenticate.rb +34 -0
- data/lib/castle/api/end_impersonation.rb +29 -0
- data/lib/castle/api/get_device.rb +25 -0
- data/lib/castle/api/get_devices_for_user.rb +25 -0
- data/lib/castle/api/identify.rb +26 -0
- data/lib/castle/api/report_device.rb +25 -0
- data/lib/castle/api/review.rb +24 -0
- data/lib/castle/api/start_impersonation.rb +29 -0
- data/lib/castle/api/track.rb +26 -0
- data/lib/castle/client.rb +52 -45
- data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
- data/lib/castle/commands/approve_device.rb +21 -0
- data/lib/castle/commands/authenticate.rb +13 -13
- data/lib/castle/commands/end_impersonation.rb +25 -0
- data/lib/castle/commands/get_device.rb +21 -0
- data/lib/castle/commands/get_devices_for_user.rb +21 -0
- data/lib/castle/commands/identify.rb +12 -13
- data/lib/castle/commands/report_device.rb +21 -0
- data/lib/castle/commands/review.rb +6 -3
- data/lib/castle/commands/start_impersonation.rb +25 -0
- data/lib/castle/commands/track.rb +12 -13
- data/lib/castle/configuration.rb +45 -28
- data/lib/castle/context/{default.rb → get_default.rb} +5 -6
- data/lib/castle/context/{merger.rb → merge.rb} +3 -3
- data/lib/castle/context/prepare.rb +18 -0
- data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
- data/lib/castle/core/get_connection.rb +25 -0
- data/lib/castle/{api/response.rb → core/process_response.rb} +4 -2
- data/lib/castle/core/process_webhook.rb +20 -0
- data/lib/castle/core/send_request.rb +50 -0
- data/lib/castle/errors.rb +2 -0
- data/lib/castle/events.rb +1 -1
- data/lib/castle/failover/prepare_response.rb +23 -0
- data/lib/castle/failover/strategy.rb +20 -0
- data/lib/castle/headers/extract.rb +47 -0
- data/lib/castle/headers/filter.rb +37 -0
- data/lib/castle/headers/format.rb +24 -0
- data/lib/castle/ip/extract.rb +83 -0
- data/lib/castle/logger.rb +19 -0
- data/lib/castle/payload/prepare.rb +27 -0
- data/lib/castle/secure_mode.rb +6 -2
- data/lib/castle/session.rb +18 -0
- data/lib/castle/singleton_configuration.rb +9 -0
- data/lib/castle/utils/clean_invalid_chars.rb +24 -0
- data/lib/castle/utils/clone.rb +15 -0
- data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
- data/lib/castle/utils/get_timestamp.rb +15 -0
- data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
- data/lib/castle/utils/secure_compare.rb +22 -0
- data/lib/castle/validators/not_supported.rb +1 -0
- data/lib/castle/validators/present.rb +1 -0
- data/lib/castle/verdict.rb +13 -0
- data/lib/castle/version.rb +1 -1
- data/lib/castle/webhooks/verify.rb +43 -0
- data/spec/integration/rails/rails_spec.rb +33 -7
- data/spec/integration/rails/support/application.rb +3 -1
- data/spec/integration/rails/support/home_controller.rb +47 -5
- data/spec/lib/castle/api/approve_device_spec.rb +21 -0
- data/spec/lib/castle/api/authenticate_spec.rb +140 -0
- data/spec/lib/castle/api/end_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/get_device_spec.rb +19 -0
- data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
- data/spec/lib/castle/api/identify_spec.rb +68 -0
- data/spec/lib/castle/api/report_device_spec.rb +21 -0
- data/spec/lib/castle/{review_spec.rb → api/review_spec.rb} +3 -3
- data/spec/lib/castle/api/start_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/track_spec.rb +68 -0
- data/spec/lib/castle/api_spec.rb +16 -1
- data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +2 -2
- data/spec/lib/castle/client_spec.rb +41 -23
- data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
- data/spec/lib/castle/commands/authenticate_spec.rb +7 -16
- data/spec/lib/castle/commands/end_impersonation_spec.rb +82 -0
- data/spec/lib/castle/commands/get_device_spec.rb +24 -0
- data/spec/lib/castle/commands/get_devices_for_user_spec.rb +24 -0
- data/spec/lib/castle/commands/identify_spec.rb +5 -16
- data/spec/lib/castle/commands/report_device_spec.rb +24 -0
- data/spec/lib/castle/commands/review_spec.rb +1 -1
- data/spec/lib/castle/commands/{impersonate_spec.rb → start_impersonation_spec.rb} +9 -34
- data/spec/lib/castle/commands/track_spec.rb +5 -16
- data/spec/lib/castle/configuration_spec.rb +9 -138
- data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +1 -2
- data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
- data/spec/lib/castle/context/prepare_spec.rb +44 -0
- data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
- data/spec/lib/castle/core/get_connection_spec.rb +59 -0
- data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +56 -1
- data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
- data/spec/lib/castle/core/send_request_spec.rb +102 -0
- data/spec/lib/castle/failover/strategy_spec.rb +12 -0
- data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -18
- data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +6 -5
- data/spec/lib/castle/headers/format_spec.rb +25 -0
- data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +35 -7
- data/spec/lib/castle/logger_spec.rb +42 -0
- data/spec/lib/castle/payload/prepare_spec.rb +54 -0
- data/spec/lib/castle/session_spec.rb +88 -0
- data/spec/lib/castle/singleton_configuration_spec.rb +18 -0
- data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
- data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
- data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
- data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
- data/spec/lib/castle/utils/{merger_spec.rb → merge_spec.rb} +3 -3
- data/spec/lib/castle/verdict_spec.rb +9 -0
- data/spec/lib/castle/webhooks/verify_spec.rb +69 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/configuration.rb +129 -0
- metadata +133 -56
- data/lib/castle/api/request.rb +0 -42
- data/lib/castle/api/session.rb +0 -39
- data/lib/castle/commands/impersonate.rb +0 -26
- data/lib/castle/extractors/headers.rb +0 -45
- data/lib/castle/extractors/ip.rb +0 -68
- data/lib/castle/failover_auth_response.rb +0 -21
- data/lib/castle/headers_filter.rb +0 -35
- data/lib/castle/headers_formatter.rb +0 -22
- data/lib/castle/review.rb +0 -11
- data/lib/castle/utils.rb +0 -55
- data/lib/castle/utils/cloner.rb +0 -11
- data/lib/castle/utils/timestamp.rb +0 -12
- data/spec/lib/castle/api/request_spec.rb +0 -72
- data/spec/lib/castle/headers_formatter_spec.rb +0 -25
- data/spec/lib/castle/utils_spec.rb +0 -156
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Castle
|
4
|
-
module
|
4
|
+
module Core
|
5
5
|
# parses api response
|
6
|
-
module
|
6
|
+
module ProcessResponse
|
7
7
|
RESPONSE_ERRORS = {
|
8
8
|
400 => Castle::BadRequestError,
|
9
9
|
401 => Castle::UnauthorizedError,
|
@@ -17,6 +17,8 @@ module Castle
|
|
17
17
|
def call(response)
|
18
18
|
verify!(response)
|
19
19
|
|
20
|
+
Castle::Logger.call('response:', response.body.to_s)
|
21
|
+
|
20
22
|
return {} if response.body.nil? || response.body.empty?
|
21
23
|
|
22
24
|
begin
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Core
|
5
|
+
# Parses a webhook
|
6
|
+
module ProcessWebhook
|
7
|
+
class << self
|
8
|
+
# Checks if webhook is valid
|
9
|
+
# @param webhook [Request]
|
10
|
+
def call(webhook)
|
11
|
+
webhook.body.read.tap do |result|
|
12
|
+
raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
|
13
|
+
|
14
|
+
Castle::Logger.call('webhook:', result.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Core
|
5
|
+
# this class is responsible for making requests to api
|
6
|
+
module SendRequest
|
7
|
+
# Default headers that we add to passed ones
|
8
|
+
DEFAULT_HEADERS = {
|
9
|
+
'Content-Type' => 'application/json'
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
private_constant :DEFAULT_HEADERS
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# @param command [String]
|
16
|
+
# @param headers [Hash]
|
17
|
+
# @param http [Net::HTTP]
|
18
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
19
|
+
def call(command, headers, http = nil, config = Castle.config)
|
20
|
+
(http || Castle::Core::GetConnection.call).request(
|
21
|
+
build(
|
22
|
+
command,
|
23
|
+
headers.merge(DEFAULT_HEADERS),
|
24
|
+
config
|
25
|
+
)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param command [String]
|
30
|
+
# @param headers [Hash]
|
31
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
32
|
+
def build(command, headers, config = Castle.config)
|
33
|
+
url = "#{config.base_url.path}/#{command.path}"
|
34
|
+
request_obj = Net::HTTP.const_get(command.method.to_s.capitalize).new(url, headers)
|
35
|
+
|
36
|
+
unless command.method == :get
|
37
|
+
request_obj.body = ::Castle::Utils::CleanInvalidChars.call(
|
38
|
+
command.data
|
39
|
+
).to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
Castle::Logger.call("#{url}:", request_obj.body)
|
43
|
+
|
44
|
+
request_obj.basic_auth('', config.api_secret)
|
45
|
+
request_obj
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/castle/errors.rb
CHANGED
@@ -20,6 +20,8 @@ module Castle
|
|
20
20
|
class ConfigurationError < Castle::Error; end
|
21
21
|
# error returned by api
|
22
22
|
class ApiError < Castle::Error; end
|
23
|
+
# webhook signature verification error
|
24
|
+
class WebhookVerificationError < Castle::Error; end
|
23
25
|
|
24
26
|
# api error bad request 400
|
25
27
|
class BadRequestError < Castle::ApiError; end
|
data/lib/castle/events.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Castle
|
4
4
|
# list of events based on https://docs.castle.io/api_reference/#list-of-recognized-events
|
5
5
|
module Events
|
6
|
-
# Record when a user
|
6
|
+
# Record when a user successfully logs in.
|
7
7
|
LOGIN_SUCCEEDED = '$login.succeeded'
|
8
8
|
# Record when a user failed to log in.
|
9
9
|
LOGIN_FAILED = '$login.failed'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Failover
|
5
|
+
# generate failover authentication response
|
6
|
+
class PrepareResponse
|
7
|
+
def initialize(user_id, reason:, strategy: Castle.config.failover_strategy)
|
8
|
+
@strategy = strategy
|
9
|
+
@reason = reason
|
10
|
+
@user_id = user_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
{
|
15
|
+
action: @strategy.to_s,
|
16
|
+
user_id: @user_id,
|
17
|
+
failover: true,
|
18
|
+
failover_reason: @reason
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Failover
|
5
|
+
# handles failover strategy consts
|
6
|
+
module Strategy
|
7
|
+
# allow
|
8
|
+
ALLOW = :allow
|
9
|
+
# deny
|
10
|
+
DENY = :deny
|
11
|
+
# challenge
|
12
|
+
CHALLENGE = :challenge
|
13
|
+
# throw an error
|
14
|
+
THROW = :throw
|
15
|
+
end
|
16
|
+
|
17
|
+
# list of possible strategies
|
18
|
+
STRATEGIES = %i[allow deny challenge throw].freeze
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# used for extraction of cookies and headers from the request
|
6
|
+
class Extract
|
7
|
+
# Headers that we will never scrub, even if they land on the configuration denylist.
|
8
|
+
ALWAYS_ALLOWLISTED = %w[User-Agent].freeze
|
9
|
+
|
10
|
+
# Headers that will always be scrubbed, even if allowlisted.
|
11
|
+
ALWAYS_DENYLISTED = %w[Cookie Authorization].freeze
|
12
|
+
|
13
|
+
private_constant :ALWAYS_ALLOWLISTED, :ALWAYS_DENYLISTED
|
14
|
+
|
15
|
+
# @param headers [Hash]
|
16
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
17
|
+
def initialize(headers, config = Castle.config)
|
18
|
+
@headers = headers
|
19
|
+
@config = config
|
20
|
+
@no_allowlist = config.allowlisted.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Serialize HTTP headers
|
24
|
+
# @return [Hash]
|
25
|
+
def call
|
26
|
+
@headers.each_with_object({}) do |(name, value), acc|
|
27
|
+
acc[name] = header_value(name, value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# scrub header value
|
34
|
+
# @param name [String]
|
35
|
+
# @param value [String]
|
36
|
+
# @return [TrueClass | FalseClass | String]
|
37
|
+
def header_value(name, value)
|
38
|
+
return true if ALWAYS_DENYLISTED.include?(name)
|
39
|
+
return value if ALWAYS_ALLOWLISTED.include?(name)
|
40
|
+
return true if @config.denylisted.include?(name)
|
41
|
+
return value if @no_allowlist || @config.allowlisted.include?(name)
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# used for preparing valuable headers list
|
6
|
+
class Filter
|
7
|
+
# headers filter
|
8
|
+
# HTTP_ - this is how Rack prefixes incoming HTTP headers
|
9
|
+
# CONTENT_LENGTH - for responses without Content-Length or Transfer-Encoding header
|
10
|
+
# REMOTE_ADDR - ip address header returned by web server
|
11
|
+
VALUABLE_HEADERS = /^
|
12
|
+
HTTP(?:_|-).*|
|
13
|
+
CONTENT(?:_|-)LENGTH|
|
14
|
+
REMOTE(?:_|-)ADDR
|
15
|
+
$/xi.freeze
|
16
|
+
|
17
|
+
private_constant :VALUABLE_HEADERS
|
18
|
+
|
19
|
+
# @param request [Rack::Request]
|
20
|
+
def initialize(request)
|
21
|
+
@request_env = request.env
|
22
|
+
@header_format = Castle::Headers::Format
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serialize HTTP headers
|
26
|
+
# @return [Hash]
|
27
|
+
def call
|
28
|
+
@request_env.keys.each_with_object({}) do |header_name, acc|
|
29
|
+
next unless header_name.match(VALUABLE_HEADERS)
|
30
|
+
|
31
|
+
formatted_name = @header_format.call(header_name)
|
32
|
+
acc[formatted_name] = @request_env[header_name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# formats header name
|
6
|
+
class Format
|
7
|
+
class << self
|
8
|
+
# @param header [String]
|
9
|
+
# @return [String]
|
10
|
+
def call(header)
|
11
|
+
format(header.to_s.gsub(/^HTTP(?:_|-)/i, ''))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# @param header [String]
|
17
|
+
# @return [String]
|
18
|
+
def format(header)
|
19
|
+
header.split(/_|-/).map(&:capitalize).join('-')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module IP
|
5
|
+
# used for extraction of ip from the request
|
6
|
+
class Extract
|
7
|
+
# ordered list of ip headers for ip extraction
|
8
|
+
DEFAULT = %w[X-Forwarded-For Remote-Addr].freeze
|
9
|
+
# list of header which are used with proxy depth setting
|
10
|
+
DEPTH_RELATED = %w[X-Forwarded-For].freeze
|
11
|
+
|
12
|
+
private_constant :DEFAULT
|
13
|
+
|
14
|
+
# @param headers [Hash]
|
15
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
16
|
+
def initialize(headers, config = Castle.config)
|
17
|
+
@headers = headers
|
18
|
+
@ip_headers = config.ip_headers.empty? ? DEFAULT : config.ip_headers
|
19
|
+
@proxies = config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
|
20
|
+
@trust_proxy_chain = config.trust_proxy_chain
|
21
|
+
@trusted_proxy_depth = config.trusted_proxy_depth
|
22
|
+
end
|
23
|
+
|
24
|
+
# Order of headers:
|
25
|
+
# .... list of headers defined by ip_headers
|
26
|
+
# X-Forwarded-For
|
27
|
+
# Remote-Addr
|
28
|
+
# @return [String]
|
29
|
+
def call
|
30
|
+
all_ips = []
|
31
|
+
|
32
|
+
@ip_headers.each do |ip_header|
|
33
|
+
ips = ips_from(ip_header)
|
34
|
+
ip_value = remove_proxies(ips)
|
35
|
+
|
36
|
+
return ip_value if ip_value
|
37
|
+
|
38
|
+
all_ips.push(*ips)
|
39
|
+
end
|
40
|
+
|
41
|
+
# fallback to first listed ip
|
42
|
+
all_ips.first
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @param ips [Array<String>]
|
48
|
+
# @return [Array<String>]
|
49
|
+
def remove_proxies(ips)
|
50
|
+
return ips.first if @trust_proxy_chain
|
51
|
+
|
52
|
+
ips.reject { |ip| proxy?(ip) }.last
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param ip [String]
|
56
|
+
# @return [Boolean]
|
57
|
+
def proxy?(ip)
|
58
|
+
@proxies.any? { |proxy| proxy.match(ip) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param header [String]
|
62
|
+
# @return [Array<String>]
|
63
|
+
def ips_from(header)
|
64
|
+
value = @headers[header]
|
65
|
+
|
66
|
+
return [] unless value
|
67
|
+
|
68
|
+
ips = value.strip.split(/[,\s]+/)
|
69
|
+
|
70
|
+
limit_proxy_depth(ips, header)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param ips [Array<String>]
|
74
|
+
# @param ip_header [String]
|
75
|
+
# @return [Array<String>]
|
76
|
+
def limit_proxy_depth(ips, ip_header)
|
77
|
+
ips.pop(@trusted_proxy_depth) if DEPTH_RELATED.include?(ip_header)
|
78
|
+
|
79
|
+
ips
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# module for logger handling
|
5
|
+
module Logger
|
6
|
+
class << self
|
7
|
+
# @param message [String]
|
8
|
+
# @param data [String]
|
9
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
10
|
+
def call(message, data = nil, config = Castle.config)
|
11
|
+
logger = config.logger
|
12
|
+
|
13
|
+
return unless logger
|
14
|
+
|
15
|
+
logger.info("[CASTLE] #{message} #{data}".strip)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Payload
|
5
|
+
# this prepare payload based on the request
|
6
|
+
module Prepare
|
7
|
+
class << self
|
8
|
+
# @param payload_options [Hash]
|
9
|
+
# @param request [Request]
|
10
|
+
# @param options [Hash] required for context preparation
|
11
|
+
# @return [Hash]
|
12
|
+
def call(payload_options, request, options = {})
|
13
|
+
context = Castle::Context::Prepare.call(request, payload_options.merge(options))
|
14
|
+
|
15
|
+
payload = Castle::Utils::DeepSymbolizeKeys.call(
|
16
|
+
payload_options || {}
|
17
|
+
).merge(context: context)
|
18
|
+
payload[:timestamp] ||= Castle::Utils::GetTimestamp.call
|
19
|
+
|
20
|
+
warn '[DEPRECATION] use user_traits instead of traits key' if payload.key?(:traits)
|
21
|
+
|
22
|
+
payload
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/castle/secure_mode.rb
CHANGED
@@ -4,8 +4,12 @@ require 'openssl'
|
|
4
4
|
|
5
5
|
module Castle
|
6
6
|
module SecureMode
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
# @param user_id [String]
|
9
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
10
|
+
def signature(user_id, config = Castle.config)
|
11
|
+
OpenSSL::HMAC.hexdigest('sha256', config.api_secret, user_id.to_s)
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# this module uses the Connection object
|
5
|
+
# and provides start method for persistent connection usage
|
6
|
+
# when there is a need of sending multiple requests at once
|
7
|
+
module Session
|
8
|
+
HTTPS_SCHEME = 'https'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(&block)
|
12
|
+
return unless block_given?
|
13
|
+
|
14
|
+
Castle::Core::GetConnection.call.start(&block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|