raims 0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +130 -0
- data/lib/raims/api_list.rb +98 -0
- data/lib/raims/api_resource.rb +31 -0
- data/lib/raims/api_response.rb +45 -0
- data/lib/raims/enum_values.rb +208 -0
- data/lib/raims/errors.rb +59 -0
- data/lib/raims/generated/contract.json +6982 -0
- data/lib/raims/http_client.rb +91 -0
- data/lib/raims/models.rb +1863 -0
- data/lib/raims/raims_client.rb +276 -0
- data/lib/raims/request_options.rb +47 -0
- data/lib/raims/schema.rb +104 -0
- data/lib/raims/services.rb +506 -0
- data/lib/raims/util.rb +141 -0
- data/lib/raims/version.rb +5 -0
- data/lib/raims/webhook.rb +72 -0
- data/lib/raims.rb +16 -0
- metadata +143 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module Raims
|
|
6
|
+
class Webhook
|
|
7
|
+
ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
8
|
+
|
|
9
|
+
def self.verify_signature(payload, headers, secret, tolerance: 300, now: nil)
|
|
10
|
+
raise ArgumentError, 'Webhook tolerance must be a non-negative integer.' unless tolerance.is_a?(Integer) && tolerance >= 0
|
|
11
|
+
current = now.nil? ? Time.now.to_i : now
|
|
12
|
+
raise ArgumentError, 'Webhook now must be a non-negative integer.' unless current.is_a?(Integer) && current >= 0
|
|
13
|
+
raise SignatureVerificationError, 'The webhook body must contain the original HTTP bytes.' unless payload.is_a?(String)
|
|
14
|
+
delivery = header(headers, 'webhook-id')
|
|
15
|
+
timestamp = header(headers, 'webhook-timestamp')
|
|
16
|
+
signatures = header(headers, 'webhook-signature')
|
|
17
|
+
unless delivery.match?(/\A[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\z/)
|
|
18
|
+
raise SignatureVerificationError, 'The webhook delivery ID is invalid.'
|
|
19
|
+
end
|
|
20
|
+
unless timestamp.match?(/\A[0-9]{1,12}\z/) && (timestamp.to_i - current).abs <= tolerance
|
|
21
|
+
raise SignatureVerificationError, 'The webhook timestamp is invalid or outside tolerance.'
|
|
22
|
+
end
|
|
23
|
+
key = decode_secret(secret)
|
|
24
|
+
expected = OpenSSL::HMAC.digest('SHA256', key, "#{delivery}.#{timestamp}.".b + payload.b)
|
|
25
|
+
matched = false
|
|
26
|
+
signatures.split(',').each do |candidate|
|
|
27
|
+
match = candidate.strip.match(/\Av1=([0-9a-fA-F]{64})\z/i)
|
|
28
|
+
next unless match
|
|
29
|
+
valid = OpenSSL.fixed_length_secure_compare(expected, [match[1]].pack('H*'))
|
|
30
|
+
matched = true if valid
|
|
31
|
+
end
|
|
32
|
+
raise SignatureVerificationError, 'No webhook signature matches the configured secret.' unless matched
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.construct_event(payload, headers, secret, tolerance: 300, now: nil)
|
|
37
|
+
verify_signature(payload, headers, secret, tolerance: tolerance, now: now)
|
|
38
|
+
begin
|
|
39
|
+
event = Util.parse_json(payload)
|
|
40
|
+
rescue JSON::ParserError, ArgumentError
|
|
41
|
+
raise SignatureVerificationError, 'The authenticated webhook body is not valid JSON.', cause: nil
|
|
42
|
+
end
|
|
43
|
+
raise SignatureVerificationError, 'The authenticated webhook body must be a JSON object.' unless event.is_a?(Hash)
|
|
44
|
+
event
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.header(headers, name)
|
|
48
|
+
raise SignatureVerificationError, 'Webhook headers must be a Hash.' unless headers.is_a?(Hash)
|
|
49
|
+
values = headers.select { |key, _value| key.is_a?(String) && key.downcase == name }.values
|
|
50
|
+
raise SignatureVerificationError, 'A webhook signing header is missing or duplicated.' unless values.length == 1
|
|
51
|
+
value = values.first
|
|
52
|
+
value = value.first if value.is_a?(Array) && value.length == 1
|
|
53
|
+
unless value.is_a?(String) && value.ascii_only? && !value.empty? && !value.match?(/[\x00-\x08\x0a-\x1f\x7f]/)
|
|
54
|
+
raise SignatureVerificationError, 'A webhook signing header is invalid.'
|
|
55
|
+
end
|
|
56
|
+
value
|
|
57
|
+
end
|
|
58
|
+
private_class_method :header
|
|
59
|
+
|
|
60
|
+
def self.decode_secret(secret)
|
|
61
|
+
unless secret.is_a?(String) && secret.ascii_only? && secret.match?(/\Awhsec_[0-9A-Za-z]{1,43}\z/)
|
|
62
|
+
raise SignatureVerificationError, 'The webhook signing secret is invalid.'
|
|
63
|
+
end
|
|
64
|
+
encoded = secret.delete_prefix('whsec_')
|
|
65
|
+
raise SignatureVerificationError, 'The webhook signing secret is invalid.' if encoded.length > 1 && encoded.start_with?('0')
|
|
66
|
+
number = encoded.each_char.reduce(0) { |value, char| value * 62 + ALPHABET.index(char) }
|
|
67
|
+
raise SignatureVerificationError, 'The webhook signing secret is invalid.' if number >= (1 << 256)
|
|
68
|
+
Array.new(32) { |index| (number >> (index * 8)) & 255 }.pack('C*')
|
|
69
|
+
end
|
|
70
|
+
private_class_method :decode_secret
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/raims.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'raims/version'
|
|
4
|
+
require_relative 'raims/util'
|
|
5
|
+
require_relative 'raims/api_response'
|
|
6
|
+
require_relative 'raims/errors'
|
|
7
|
+
require_relative 'raims/request_options'
|
|
8
|
+
require_relative 'raims/api_resource'
|
|
9
|
+
require_relative 'raims/models'
|
|
10
|
+
require_relative 'raims/enum_values'
|
|
11
|
+
require_relative 'raims/schema'
|
|
12
|
+
require_relative 'raims/api_list'
|
|
13
|
+
require_relative 'raims/http_client'
|
|
14
|
+
require_relative 'raims/services'
|
|
15
|
+
require_relative 'raims/raims_client'
|
|
16
|
+
require_relative 'raims/webhook'
|
metadata
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: raims
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Raims
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: net-http
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.4'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '1'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0.4'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '1'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: json
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: 2.19.9
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '3'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 2.19.9
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: bigdecimal
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '3.1'
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.1'
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '5'
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: openssl
|
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '3.2'
|
|
79
|
+
- - "<"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '5'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.2'
|
|
89
|
+
- - "<"
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '5'
|
|
92
|
+
description: Use the Raims public API from Ruby applications with resource services,
|
|
93
|
+
cursor pagination, structured errors, and webhook signature verification.
|
|
94
|
+
email:
|
|
95
|
+
- security@raims.com
|
|
96
|
+
executables: []
|
|
97
|
+
extensions: []
|
|
98
|
+
extra_rdoc_files: []
|
|
99
|
+
files:
|
|
100
|
+
- LICENSE
|
|
101
|
+
- README.md
|
|
102
|
+
- lib/raims.rb
|
|
103
|
+
- lib/raims/api_list.rb
|
|
104
|
+
- lib/raims/api_resource.rb
|
|
105
|
+
- lib/raims/api_response.rb
|
|
106
|
+
- lib/raims/enum_values.rb
|
|
107
|
+
- lib/raims/errors.rb
|
|
108
|
+
- lib/raims/generated/contract.json
|
|
109
|
+
- lib/raims/http_client.rb
|
|
110
|
+
- lib/raims/models.rb
|
|
111
|
+
- lib/raims/raims_client.rb
|
|
112
|
+
- lib/raims/request_options.rb
|
|
113
|
+
- lib/raims/schema.rb
|
|
114
|
+
- lib/raims/services.rb
|
|
115
|
+
- lib/raims/util.rb
|
|
116
|
+
- lib/raims/version.rb
|
|
117
|
+
- lib/raims/webhook.rb
|
|
118
|
+
homepage: https://docs.raims.com/en/integracoes/sdk-ruby
|
|
119
|
+
licenses:
|
|
120
|
+
- MIT
|
|
121
|
+
metadata:
|
|
122
|
+
source_code_uri: https://github.com/raimshq/raims-ruby
|
|
123
|
+
documentation_uri: https://docs.raims.com/en/integracoes/sdk-ruby
|
|
124
|
+
bug_tracker_uri: https://github.com/raimshq/raims-ruby/issues
|
|
125
|
+
rubygems_mfa_required: 'true'
|
|
126
|
+
rdoc_options: []
|
|
127
|
+
require_paths:
|
|
128
|
+
- lib
|
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: '3.3'
|
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
requirements: []
|
|
140
|
+
rubygems_version: 4.0.20
|
|
141
|
+
specification_version: 4
|
|
142
|
+
summary: Official Ruby SDK for the Raims public API.
|
|
143
|
+
test_files: []
|