certynix 1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +20 -0
- data/README.md +316 -0
- data/lib/certynix/client.rb +27 -0
- data/lib/certynix/config.rb +34 -0
- data/lib/certynix/errors.rb +96 -0
- data/lib/certynix/http_client.rb +140 -0
- data/lib/certynix/models/paginator.rb +43 -0
- data/lib/certynix/resources/alerts.rb +15 -0
- data/lib/certynix/resources/api_keys.rb +23 -0
- data/lib/certynix/resources/assets.rb +55 -0
- data/lib/certynix/resources/audit_logs.rb +15 -0
- data/lib/certynix/resources/trust_score.rb +15 -0
- data/lib/certynix/resources/verify.rb +26 -0
- data/lib/certynix/resources/webhooks.rb +41 -0
- data/lib/certynix/version.rb +5 -0
- data/lib/certynix/webhooks.rb +65 -0
- data/lib/certynix.rb +16 -0
- metadata +130 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 206f0b9118f06cfe87e16926a83cca66fae4186fd19bf87926e4fdb54b412db9
|
|
4
|
+
data.tar.gz: a48a96dbfcb73d9ac8e23cc4a1a85da97ff9a44bb3742cd942ca4529da7e2aff
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9c1d9204cc01e24bb2a46d1a9dd7f4dafa501264988bf0265da3d6dff68e9e947480654efa5540c316bb7ed077d99c80a3997aa04ffd968ca4b375e28fc6168b
|
|
7
|
+
data.tar.gz: 3b773469de280162e98b3d8e01d949a024ac39c43d35b2a24fb617060c7122c88676a57a4ab2fe124f87a61022576c153c0288071ffd7162c10ae03c0a7ce101
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-03-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release of `certynix` gem
|
|
7
|
+
- Ruby 3.1+ with keyword arguments, `Data.define` patterns
|
|
8
|
+
- `Assets`: `register`, `register_batch`, `get`, `list` (Enumerable/lazy), `delete`
|
|
9
|
+
- `Verify`: `by_hash`, `by_asset_id`, `by_hash_post` (public, no auth)
|
|
10
|
+
- `Webhooks`: `create`, `list`, `update`, `delete`, `list_deliveries`
|
|
11
|
+
- `Alerts`: `list`
|
|
12
|
+
- `ApiKeys`: `create`, `list`, `revoke`
|
|
13
|
+
- `AuditLogs`: `list`
|
|
14
|
+
- `TrustScore`: `get`
|
|
15
|
+
- `Certynix::Webhooks.validate_signature` — uses `OpenSSL.secure_compare` (constant-time)
|
|
16
|
+
- Anti-replay protection (5-minute timestamp tolerance)
|
|
17
|
+
- `Paginator` with `Enumerable` — supports `.lazy`, `.first(n)`, `.select`, `.map`
|
|
18
|
+
- Faraday-based HTTP with automatic retry (faraday-retry)
|
|
19
|
+
- Automatic sandbox detection via `cnx_test_sk_` prefix
|
|
20
|
+
- API key never exposed in error messages — `mask_api_key` helper
|
data/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# certynix
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the [Certynix](https://certynix.com) Trust Infrastructure API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'certynix'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install certynix
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires **Ruby 3.1+**.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require 'certynix'
|
|
31
|
+
|
|
32
|
+
client = Certynix::Client.new(ENV['CERTYNIX_API_KEY']) # cnx_live_sk_... or cnx_test_sk_...
|
|
33
|
+
|
|
34
|
+
# Register an asset by SHA-256 hash
|
|
35
|
+
asset = client.assets.register(
|
|
36
|
+
hash_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
|
37
|
+
filename: 'contract-2024.pdf'
|
|
38
|
+
)
|
|
39
|
+
puts "#{asset.id} — #{asset.status}"
|
|
40
|
+
puts asset.is_first_registrant ? 'First registrant!' : 'Already registered'
|
|
41
|
+
|
|
42
|
+
# Public verification (no auth required)
|
|
43
|
+
result = client.verify.by_hash(asset.hash)
|
|
44
|
+
puts result.match ? 'Verified' : 'Not found'
|
|
45
|
+
puts result.first_registrant.organization_name
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Authentication
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Production
|
|
52
|
+
client = Certynix::Client.new('cnx_live_sk_...')
|
|
53
|
+
|
|
54
|
+
# Sandbox (auto-detected from key prefix cnx_test_sk_)
|
|
55
|
+
client = Certynix::Client.new('cnx_test_sk_...')
|
|
56
|
+
# Automatically uses https://sandbox.certynix.com
|
|
57
|
+
|
|
58
|
+
# Custom options
|
|
59
|
+
client = Certynix::Client.new(
|
|
60
|
+
'cnx_live_sk_...',
|
|
61
|
+
base_url: 'https://api.staging.certynix.com',
|
|
62
|
+
timeout: 30,
|
|
63
|
+
max_retries: 3
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Resources
|
|
68
|
+
|
|
69
|
+
### Assets
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# Register by hash
|
|
73
|
+
asset = client.assets.register(
|
|
74
|
+
hash_sha256: 'abc123...',
|
|
75
|
+
filename: 'document.pdf',
|
|
76
|
+
source_url: 'https://example.com/document.pdf',
|
|
77
|
+
metadata: { author: 'John Doe' }
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Register by URL (Certynix downloads and hashes)
|
|
81
|
+
asset = client.assets.register(
|
|
82
|
+
source_url: 'https://example.com/document.pdf',
|
|
83
|
+
filename: 'document.pdf'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Register batch (up to 1,000 assets)
|
|
87
|
+
batch = client.assets.register_batch(
|
|
88
|
+
assets: [
|
|
89
|
+
{ hash_sha256: 'abc...', filename: 'file1.pdf' },
|
|
90
|
+
{ hash_sha256: 'def...', filename: 'file2.pdf' }
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
puts "#{batch.batch_id} — #{batch.status}"
|
|
94
|
+
|
|
95
|
+
# Get by ID
|
|
96
|
+
asset = client.assets.get('ast_abc123')
|
|
97
|
+
|
|
98
|
+
# List (Enumerable — all pages iterated automatically)
|
|
99
|
+
client.assets.list(limit: 50).each do |asset|
|
|
100
|
+
puts "#{asset.id} — #{asset.status}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# List with filters
|
|
104
|
+
verified = client.assets.list(status: 'verified').to_a
|
|
105
|
+
|
|
106
|
+
# Lazy with limit
|
|
107
|
+
first_10 = client.assets.list.lazy.first(10)
|
|
108
|
+
|
|
109
|
+
# Delete (soft delete — history preserved)
|
|
110
|
+
client.assets.delete('ast_abc123')
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Verification (public — no API key required)
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# Verify by SHA-256 hash
|
|
117
|
+
result = client.verify.by_hash(
|
|
118
|
+
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
|
119
|
+
)
|
|
120
|
+
puts result.match ? 'Verified' : 'Not found'
|
|
121
|
+
puts result.first_registrant.organization_name
|
|
122
|
+
|
|
123
|
+
# Verify by asset ID
|
|
124
|
+
result = client.verify.by_asset_id('ast_abc123')
|
|
125
|
+
|
|
126
|
+
# Verify by URL
|
|
127
|
+
result = client.verify.by_url('https://example.com/document.pdf')
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Webhooks
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
# Create webhook
|
|
134
|
+
webhook = client.webhooks.create(
|
|
135
|
+
url: 'https://example.com/webhook',
|
|
136
|
+
events: ['asset.created', 'asset.verified', 'exposure.alert.created']
|
|
137
|
+
)
|
|
138
|
+
# Store webhook.signing_secret securely — shown only once!
|
|
139
|
+
|
|
140
|
+
# List
|
|
141
|
+
client.webhooks.list.each do |webhook|
|
|
142
|
+
puts "#{webhook.id} — #{webhook.url}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Update
|
|
146
|
+
webhook = client.webhooks.update('wh_abc123', active: false)
|
|
147
|
+
|
|
148
|
+
# Delete
|
|
149
|
+
client.webhooks.delete('wh_abc123')
|
|
150
|
+
|
|
151
|
+
# Validate signature (in your webhook handler)
|
|
152
|
+
begin
|
|
153
|
+
event = Certynix::Webhooks.validate_signature(
|
|
154
|
+
raw_body: request.body.read,
|
|
155
|
+
signature: request.headers['X-Certynix-Signature'],
|
|
156
|
+
secret: ENV['CERTYNIX_WEBHOOK_SECRET']
|
|
157
|
+
)
|
|
158
|
+
puts event.type
|
|
159
|
+
puts event.payload.inspect
|
|
160
|
+
rescue Certynix::WebhookSignatureError => e
|
|
161
|
+
render plain: 'Invalid signature', status: :bad_request
|
|
162
|
+
rescue Certynix::WebhookReplayError => e
|
|
163
|
+
render plain: 'Replay attack detected', status: :bad_request
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Exposure Alerts
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# List active alerts
|
|
171
|
+
client.alerts.list(resolved: false).each do |alert|
|
|
172
|
+
puts "[#{alert.severity}] #{alert.description}"
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### API Keys
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# Create — value shown only once!
|
|
180
|
+
key = client.api_keys.create(name: 'Production App')
|
|
181
|
+
puts key.key_value # cnx_live_sk_... — store immediately!
|
|
182
|
+
|
|
183
|
+
# List
|
|
184
|
+
client.api_keys.list.each do |key|
|
|
185
|
+
puts "#{key.name} — #{key.prefix}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Revoke
|
|
189
|
+
client.api_keys.revoke('key_abc123')
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Audit Logs
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
client.audit_logs.list(limit: 50).each do |log|
|
|
196
|
+
puts "#{log.action} by #{log.actor_id} at #{log.created_at}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Filter by action
|
|
200
|
+
client.audit_logs.list(action: 'asset.created').each do |log|
|
|
201
|
+
puts log.resource_id
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Trust Score
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
score = client.trust_score.get
|
|
209
|
+
puts "Score: #{score.score}/100"
|
|
210
|
+
puts "Identity: #{score.components.identity}"
|
|
211
|
+
puts "Security: #{score.components.security}"
|
|
212
|
+
puts "Behavior: #{score.components.behavior}"
|
|
213
|
+
puts "Assets: #{score.components.assets}"
|
|
214
|
+
|
|
215
|
+
score.penalties.each do |penalty|
|
|
216
|
+
puts "Penalty: #{penalty.description} (-#{penalty.points})"
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Error Handling
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
require 'certynix'
|
|
224
|
+
|
|
225
|
+
begin
|
|
226
|
+
asset = client.assets.get('ast_notfound')
|
|
227
|
+
rescue Certynix::NotFoundError => e
|
|
228
|
+
puts "Not found: #{e.message}"
|
|
229
|
+
puts "Code: #{e.code}"
|
|
230
|
+
puts "Request ID: #{e.request_id}"
|
|
231
|
+
rescue Certynix::RateLimitError => e
|
|
232
|
+
puts "Rate limited. Retry after: #{e.retry_after}s"
|
|
233
|
+
rescue Certynix::AuthenticationError => e
|
|
234
|
+
puts "Invalid API key"
|
|
235
|
+
rescue Certynix::ServerError => e
|
|
236
|
+
puts "Server error: #{e.message}"
|
|
237
|
+
rescue Certynix::NetworkError => e
|
|
238
|
+
puts "Network error: #{e.message}"
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Pagination
|
|
243
|
+
|
|
244
|
+
All `list` methods return a `Paginator` that includes Ruby's `Enumerable`:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# Iterate all pages automatically
|
|
248
|
+
client.assets.list(limit: 100).each do |asset|
|
|
249
|
+
puts asset.id
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Collect all into array
|
|
253
|
+
assets = client.assets.list.to_a
|
|
254
|
+
|
|
255
|
+
# Lazy enumeration (stops fetching when you stop iterating)
|
|
256
|
+
first_10 = client.assets.list.lazy.first(10)
|
|
257
|
+
|
|
258
|
+
# Map, select, reduce
|
|
259
|
+
verified_ids = client.assets.list
|
|
260
|
+
.select { |a| a.status == 'verified' }
|
|
261
|
+
.map(&:id)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Retry Policy
|
|
265
|
+
|
|
266
|
+
The SDK automatically retries on transient errors (via `faraday-retry`):
|
|
267
|
+
|
|
268
|
+
| Scenario | Retried |
|
|
269
|
+
|---|---|
|
|
270
|
+
| `429 Too Many Requests` | Yes — respects `Retry-After` |
|
|
271
|
+
| `500`, `502`, `503`, `504` | Yes — exponential backoff + jitter |
|
|
272
|
+
| Network errors | Yes |
|
|
273
|
+
| `400`, `401`, `403`, `404`, `409` | No |
|
|
274
|
+
|
|
275
|
+
Default: 3 retries, max 60s backoff.
|
|
276
|
+
|
|
277
|
+
## Webhook Signature Validation
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# Rails example
|
|
281
|
+
class WebhooksController < ApplicationController
|
|
282
|
+
skip_before_action :verify_authenticity_token
|
|
283
|
+
|
|
284
|
+
def certynix
|
|
285
|
+
event = Certynix::Webhooks.validate_signature(
|
|
286
|
+
raw_body: request.body.read,
|
|
287
|
+
signature: request.headers['X-Certynix-Signature'],
|
|
288
|
+
secret: Rails.application.credentials.certynix_webhook_secret
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
case event.type
|
|
292
|
+
when 'asset.created'
|
|
293
|
+
AssetCreatedJob.perform_later(event.payload)
|
|
294
|
+
when 'asset.verified'
|
|
295
|
+
AssetVerifiedJob.perform_later(event.payload)
|
|
296
|
+
when 'exposure.alert.created'
|
|
297
|
+
AlertCreatedJob.perform_later(event.payload)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
head :ok
|
|
301
|
+
rescue Certynix::WebhookSignatureError, Certynix::WebhookReplayError
|
|
302
|
+
head :bad_request
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Testing
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
bundle install
|
|
311
|
+
bundle exec rspec
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :assets, :verify, :webhooks, :api_keys, :alerts, :audit_logs, :trust_score
|
|
6
|
+
|
|
7
|
+
def initialize(api_key:, base_url: nil, timeout: 30, max_retries: 3, access_token: nil)
|
|
8
|
+
config = Config.new(
|
|
9
|
+
api_key: api_key,
|
|
10
|
+
base_url: base_url,
|
|
11
|
+
timeout: timeout,
|
|
12
|
+
max_retries: max_retries,
|
|
13
|
+
access_token: access_token,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
http = HttpClient.new(config)
|
|
17
|
+
|
|
18
|
+
@assets = Resources::Assets.new(http)
|
|
19
|
+
@verify = Resources::Verify.new(http)
|
|
20
|
+
@webhooks = Resources::Webhooks.new(http)
|
|
21
|
+
@api_keys = Resources::ApiKeys.new(http)
|
|
22
|
+
@alerts = Resources::Alerts.new(http)
|
|
23
|
+
@audit_logs = Resources::AuditLogs.new(http)
|
|
24
|
+
@trust_score = Resources::TrustScore.new(http)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
class Config
|
|
5
|
+
API_KEY_REGEX = /\Acnx_(live|test)_sk_[a-zA-Z0-9]{32,}\z/
|
|
6
|
+
PRODUCTION_URL = 'https://api.certynix.com'
|
|
7
|
+
SANDBOX_URL = 'https://sandbox.certynix.com'
|
|
8
|
+
|
|
9
|
+
attr_reader :api_key, :base_url, :timeout, :max_retries, :access_token, :sandbox
|
|
10
|
+
|
|
11
|
+
def initialize(api_key:, base_url: nil, timeout: 30, max_retries: 3, access_token: nil)
|
|
12
|
+
raise ConfigurationError, 'api_key is required' if api_key.nil? || api_key.empty?
|
|
13
|
+
unless api_key.match?(API_KEY_REGEX)
|
|
14
|
+
# NUNCA incluir a API key na mensagem de erro
|
|
15
|
+
raise ConfigurationError, 'Invalid API key format. Expected cnx_live_sk_... or cnx_test_sk_...'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@api_key = api_key
|
|
19
|
+
@sandbox = api_key.start_with?('cnx_test_')
|
|
20
|
+
@base_url = base_url || (@sandbox ? SANDBOX_URL : PRODUCTION_URL)
|
|
21
|
+
@timeout = timeout
|
|
22
|
+
@max_retries = max_retries
|
|
23
|
+
@access_token = access_token
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
alias sandbox? sandbox
|
|
27
|
+
|
|
28
|
+
# Mascara a API key para logs — nunca expõe o valor completo
|
|
29
|
+
def mask_api_key
|
|
30
|
+
return '***' if api_key.length <= 12
|
|
31
|
+
"#{api_key[0..11]}***"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
# Erro base para todos os erros do SDK Certynix.
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :code, :request_id, :status_code
|
|
7
|
+
|
|
8
|
+
def initialize(message:, code:, request_id: nil, status_code: 0)
|
|
9
|
+
super(message)
|
|
10
|
+
@code = code
|
|
11
|
+
@request_id = request_id
|
|
12
|
+
@status_code = status_code
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# API key inválida, configuração incorreta
|
|
17
|
+
class ConfigurationError < Error
|
|
18
|
+
def initialize(message)
|
|
19
|
+
super(message: message, code: 'CONFIGURATION_ERROR', status_code: 0)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# HTTP 401
|
|
24
|
+
class AuthenticationError < Error
|
|
25
|
+
def initialize(message:, code:, request_id: nil)
|
|
26
|
+
super(message: message, code: code, request_id: request_id, status_code: 401)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# HTTP 403
|
|
31
|
+
class PermissionError < Error
|
|
32
|
+
def initialize(message:, code:, request_id: nil)
|
|
33
|
+
super(message: message, code: code, request_id: request_id, status_code: 403)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# HTTP 404
|
|
38
|
+
class NotFoundError < Error
|
|
39
|
+
def initialize(message:, code:, request_id: nil)
|
|
40
|
+
super(message: message, code: code, request_id: request_id, status_code: 404)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# HTTP 409
|
|
45
|
+
class ConflictError < Error
|
|
46
|
+
def initialize(message:, code:, request_id: nil)
|
|
47
|
+
super(message: message, code: code, request_id: request_id, status_code: 409)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# HTTP 400
|
|
52
|
+
class ValidationError < Error
|
|
53
|
+
def initialize(message:, code:, request_id: nil)
|
|
54
|
+
super(message: message, code: code, request_id: request_id, status_code: 400)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# HTTP 429
|
|
59
|
+
class RateLimitError < Error
|
|
60
|
+
attr_reader :retry_after
|
|
61
|
+
|
|
62
|
+
def initialize(message:, code:, request_id: nil, retry_after: 0)
|
|
63
|
+
super(message: message, code: code, request_id: request_id, status_code: 429)
|
|
64
|
+
@retry_after = retry_after
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# HTTP 5xx
|
|
69
|
+
class ServerError < Error
|
|
70
|
+
def initialize(message:, code:, request_id: nil, status_code: 500)
|
|
71
|
+
super(message: message, code: code, request_id: request_id, status_code: status_code)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Timeout, DNS, conexão recusada
|
|
76
|
+
class NetworkError < Error
|
|
77
|
+
def initialize(message, cause: nil)
|
|
78
|
+
super(message: message, code: 'NETWORK_ERROR', status_code: 0)
|
|
79
|
+
@cause = cause
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# HMAC inválido
|
|
84
|
+
class WebhookSignatureError < Error
|
|
85
|
+
def initialize(message)
|
|
86
|
+
super(message: message, code: 'INVALID_WEBHOOK_SIGNATURE', status_code: 0)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Timestamp fora do prazo
|
|
91
|
+
class WebhookReplayError < Error
|
|
92
|
+
def initialize(message)
|
|
93
|
+
super(message: message, code: 'WEBHOOK_REPLAY_ATTACK', status_code: 0)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/retry'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Certynix
|
|
9
|
+
class HttpClient
|
|
10
|
+
SDK_VERSION = Certynix::VERSION
|
|
11
|
+
|
|
12
|
+
def initialize(config)
|
|
13
|
+
@config = config
|
|
14
|
+
@conn = build_connection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(path, params = {})
|
|
18
|
+
request(:get, path, params: compact_params(params))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post(path, body = nil)
|
|
22
|
+
request(:post, path, body: body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def put(path, body = nil)
|
|
26
|
+
request(:put, path, body: body)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def delete(path)
|
|
30
|
+
request(:delete, path)
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET sem autenticação — para endpoints públicos (verify)
|
|
35
|
+
def get_public(path, params = {})
|
|
36
|
+
request(:get, path, params: compact_params(params), public: true)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def post_public(path, body = nil)
|
|
40
|
+
request(:post, path, body: body, public: true)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def build_connection
|
|
46
|
+
Faraday.new(url: @config.base_url) do |f|
|
|
47
|
+
f.options.timeout = @config.timeout
|
|
48
|
+
f.options.open_timeout = 10
|
|
49
|
+
|
|
50
|
+
f.request :retry,
|
|
51
|
+
max: @config.max_retries,
|
|
52
|
+
interval: 1.0,
|
|
53
|
+
interval_randomness: 0.5,
|
|
54
|
+
backoff_factor: 2,
|
|
55
|
+
exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed],
|
|
56
|
+
retry_statuses: [429, 500, 502, 503, 504]
|
|
57
|
+
|
|
58
|
+
f.request :json
|
|
59
|
+
f.response :raise_error
|
|
60
|
+
f.adapter Faraday.default_adapter
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def request(method, path, params: {}, body: nil, public: false)
|
|
65
|
+
headers = build_headers(public_request: public)
|
|
66
|
+
request_id = headers['X-Request-ID']
|
|
67
|
+
|
|
68
|
+
response = @conn.public_send(method, path) do |req|
|
|
69
|
+
req.headers.merge!(headers)
|
|
70
|
+
req.params.merge!(params) if params.any?
|
|
71
|
+
req.body = body.to_json if body && %i[post put patch].include?(method)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
parse_response(response.body)
|
|
75
|
+
rescue Faraday::ClientError => e
|
|
76
|
+
handle_error(e, request_id)
|
|
77
|
+
rescue Faraday::ServerError => e
|
|
78
|
+
handle_error(e, request_id)
|
|
79
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
80
|
+
raise NetworkError.new(e.message, cause: e)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def handle_error(err, request_id)
|
|
84
|
+
status = err.response&.dig(:status) || 0
|
|
85
|
+
body = err.response&.dig(:body) || '{}'
|
|
86
|
+
|
|
87
|
+
begin
|
|
88
|
+
parsed = JSON.parse(body, symbolize_names: true)
|
|
89
|
+
code = parsed.dig(:error, :code) || 'INTERNAL_ERROR'
|
|
90
|
+
message = parsed.dig(:error, :message) || "HTTP #{status}"
|
|
91
|
+
req_id = parsed.dig(:error, :request_id) || request_id
|
|
92
|
+
rescue JSON::ParserError
|
|
93
|
+
code = 'INTERNAL_ERROR'
|
|
94
|
+
message = "HTTP #{status}"
|
|
95
|
+
req_id = request_id
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
retry_after = err.response&.dig(:headers, 'retry-after').to_i
|
|
99
|
+
|
|
100
|
+
raise map_error(status, code, message, req_id, retry_after)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def map_error(status, code, message, request_id, retry_after = 0)
|
|
104
|
+
case status
|
|
105
|
+
when 400 then ValidationError.new(message: message, code: code, request_id: request_id)
|
|
106
|
+
when 401 then AuthenticationError.new(message: message, code: code, request_id: request_id)
|
|
107
|
+
when 403 then PermissionError.new(message: message, code: code, request_id: request_id)
|
|
108
|
+
when 404 then NotFoundError.new(message: message, code: code, request_id: request_id)
|
|
109
|
+
when 409 then ConflictError.new(message: message, code: code, request_id: request_id)
|
|
110
|
+
when 429 then RateLimitError.new(message: message, code: code, request_id: request_id, retry_after: retry_after)
|
|
111
|
+
else ServerError.new(message: message, code: code, request_id: request_id, status_code: status)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def build_headers(public_request: false)
|
|
116
|
+
headers = {
|
|
117
|
+
'Accept' => 'application/json',
|
|
118
|
+
'Content-Type' => 'application/json',
|
|
119
|
+
'User-Agent' => "certynix-ruby/#{SDK_VERSION} (ruby/#{RUBY_VERSION})",
|
|
120
|
+
'X-Request-ID' => SecureRandom.uuid,
|
|
121
|
+
}
|
|
122
|
+
headers['x-api-key'] = @config.api_key unless public_request
|
|
123
|
+
if !public_request && @config.access_token
|
|
124
|
+
headers['Authorization'] = "Bearer #{@config.access_token}"
|
|
125
|
+
end
|
|
126
|
+
headers
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def compact_params(params)
|
|
130
|
+
params.reject { |_, v| v.nil? }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_response(body)
|
|
134
|
+
return {} if body.nil? || body.empty?
|
|
135
|
+
JSON.parse(body, symbolize_names: true)
|
|
136
|
+
rescue JSON::ParserError
|
|
137
|
+
{}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Models
|
|
5
|
+
# Paginador automático com suporte completo ao módulo Enumerable.
|
|
6
|
+
#
|
|
7
|
+
# @example Iterar todos os assets
|
|
8
|
+
# client.assets.list.each { |a| puts a[:id] }
|
|
9
|
+
#
|
|
10
|
+
# @example Lazy — pegar apenas os 10 primeiros
|
|
11
|
+
# client.assets.list.lazy.first(10)
|
|
12
|
+
#
|
|
13
|
+
# @example Usar métodos Enumerable
|
|
14
|
+
# verified = client.assets.list.select { |a| a[:status] == 'verified' }
|
|
15
|
+
class Paginator
|
|
16
|
+
include Enumerable
|
|
17
|
+
|
|
18
|
+
def initialize(http:, path:, params: {})
|
|
19
|
+
@http = http
|
|
20
|
+
@path = path
|
|
21
|
+
@params = params
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each
|
|
25
|
+
cursor = nil
|
|
26
|
+
|
|
27
|
+
loop do
|
|
28
|
+
query = cursor ? @params.merge(cursor: cursor) : @params
|
|
29
|
+
response = @http.get(@path, query)
|
|
30
|
+
|
|
31
|
+
data = response[:data] || []
|
|
32
|
+
pagination = response[:pagination] || {}
|
|
33
|
+
|
|
34
|
+
data.each { |item| yield item }
|
|
35
|
+
|
|
36
|
+
break unless pagination[:has_more]
|
|
37
|
+
cursor = pagination[:next_cursor]
|
|
38
|
+
break if cursor.nil? || cursor.empty?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class Alerts
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list(**params)
|
|
11
|
+
Models::Paginator.new(http: @http, path: '/v1/alerts', params: params)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class ApiKeys
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(name:)
|
|
11
|
+
@http.post('/v1/api-keys', { name: name })
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def list(**params)
|
|
15
|
+
Models::Paginator.new(http: @http, path: '/v1/api-keys', params: params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def revoke(id)
|
|
19
|
+
@http.delete("/v1/api-keys/#{URI.encode_www_form_component(id)}")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class Assets
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Registra um asset por hash SHA-256, URL ou arquivo.
|
|
11
|
+
def register(hash_sha256: nil, url: nil, file: nil, filename: nil, mime_type: nil, file_size: nil)
|
|
12
|
+
body = {}
|
|
13
|
+
if file
|
|
14
|
+
# Upload — usar multipart (simplificado: enviar como hash com file_data)
|
|
15
|
+
body[:file] = file
|
|
16
|
+
body[:filename] = filename if filename
|
|
17
|
+
body[:mime_type] = mime_type if mime_type
|
|
18
|
+
elsif hash_sha256
|
|
19
|
+
body[:hash_sha256] = hash_sha256
|
|
20
|
+
body[:filename] = filename if filename
|
|
21
|
+
body[:mime_type] = mime_type if mime_type
|
|
22
|
+
body[:file_size] = file_size if file_size
|
|
23
|
+
elsif url
|
|
24
|
+
body[:url] = url
|
|
25
|
+
body[:filename] = filename if filename
|
|
26
|
+
end
|
|
27
|
+
@http.post('/v1/assets', body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Registra um lote de assets.
|
|
31
|
+
def register_batch(assets:)
|
|
32
|
+
@http.post('/v1/assets/batch', { assets: assets })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Busca um asset por ID.
|
|
36
|
+
def get(id)
|
|
37
|
+
@http.get("/v1/assets/#{URI.encode_www_form_component(id)}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Lista assets com paginação automática via Enumerable.
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# client.assets.list.each { |a| puts a[:id] }
|
|
44
|
+
# client.assets.list.lazy.first(10)
|
|
45
|
+
def list(**params)
|
|
46
|
+
Models::Paginator.new(http: @http, path: '/v1/assets', params: params)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Remove um asset (soft delete).
|
|
50
|
+
def delete(id)
|
|
51
|
+
@http.delete("/v1/assets/#{URI.encode_www_form_component(id)}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class AuditLogs
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list(**params)
|
|
11
|
+
Models::Paginator.new(http: @http, path: '/v1/audit', params: params)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class Verify
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Verificação pública por hash SHA-256 — sem autenticação.
|
|
11
|
+
def by_hash(hash)
|
|
12
|
+
@http.get_public("/v1/verify/#{URI.encode_www_form_component(hash)}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Verificação pública por asset ID — sem autenticação.
|
|
16
|
+
def by_asset_id(id)
|
|
17
|
+
@http.get_public("/v1/verify/#{URI.encode_www_form_component(id)}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Verificação pública por hash via POST — sem autenticação.
|
|
21
|
+
def by_hash_post(hash)
|
|
22
|
+
@http.post_public('/v1/verify', { hash_sha256: hash })
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Certynix
|
|
4
|
+
module Resources
|
|
5
|
+
class Webhooks
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(url:, events: nil)
|
|
11
|
+
body = { url: url }
|
|
12
|
+
body[:events] = events if events
|
|
13
|
+
@http.post('/v1/webhooks', body)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def list(**params)
|
|
17
|
+
Models::Paginator.new(http: @http, path: '/v1/webhooks', params: params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(id, url: nil, events: nil, active: nil)
|
|
21
|
+
body = {}
|
|
22
|
+
body[:url] = url if url
|
|
23
|
+
body[:events] = events if events
|
|
24
|
+
body[:active] = active unless active.nil?
|
|
25
|
+
@http.put("/v1/webhooks/#{URI.encode_www_form_component(id)}", body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete(id)
|
|
29
|
+
@http.delete("/v1/webhooks/#{URI.encode_www_form_component(id)}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def list_deliveries(id, **params)
|
|
33
|
+
Models::Paginator.new(
|
|
34
|
+
http: @http,
|
|
35
|
+
path: "/v1/webhooks/#{URI.encode_www_form_component(id)}/deliveries",
|
|
36
|
+
params: params
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Certynix
|
|
7
|
+
# Utilitários para validação de assinatura de webhooks Certynix.
|
|
8
|
+
module Webhooks
|
|
9
|
+
TOLERANCE_SECONDS = 300 # 5 minutos
|
|
10
|
+
|
|
11
|
+
# Valida a assinatura HMAC-SHA256 de um delivery de webhook.
|
|
12
|
+
#
|
|
13
|
+
# CRÍTICO: raw_body deve ser o body bruto ANTES de qualquer JSON.parse.
|
|
14
|
+
# Usa OpenSSL.secure_compare (Ruby 2.7+) para constant-time comparison.
|
|
15
|
+
#
|
|
16
|
+
# @param raw_body [String] body bruto do request
|
|
17
|
+
# @param signature [String] valor do header X-Certynix-Signature
|
|
18
|
+
# @param secret [String] signing secret do webhook
|
|
19
|
+
# @return [Hash] { type:, payload:, timestamp: }
|
|
20
|
+
# @raise [WebhookSignatureError] se a assinatura for inválida
|
|
21
|
+
# @raise [WebhookReplayError] se o timestamp for > 5 minutos
|
|
22
|
+
def self.validate_signature(raw_body:, signature:, secret:)
|
|
23
|
+
# 1. Parse: "t=timestamp,v1=hash"
|
|
24
|
+
timestamp = nil
|
|
25
|
+
hash = nil
|
|
26
|
+
|
|
27
|
+
signature.split(',').each do |part|
|
|
28
|
+
timestamp = part[2..] if part.start_with?('t=')
|
|
29
|
+
hash = part[3..] if part.start_with?('v1=')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if timestamp.nil? || timestamp.empty? || hash.nil? || hash.empty?
|
|
33
|
+
raise WebhookSignatureError, 'Invalid signature format: expected t=timestamp,v1=hash'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 2. Anti-replay
|
|
37
|
+
ts = timestamp.to_i
|
|
38
|
+
diff = (Time.now.to_i - ts).abs
|
|
39
|
+
if diff > TOLERANCE_SECONDS
|
|
40
|
+
raise WebhookReplayError, "Webhook timestamp is #{diff}s old — exceeds #{TOLERANCE_SECONDS}s tolerance"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# 3. Calcular HMAC-SHA256("{timestamp}.{raw_body}")
|
|
44
|
+
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}.#{raw_body}")
|
|
45
|
+
|
|
46
|
+
# 4. Constant-time comparison
|
|
47
|
+
unless OpenSSL.secure_compare(expected, hash)
|
|
48
|
+
raise WebhookSignatureError, 'Webhook signature mismatch'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 5. Parse payload
|
|
52
|
+
begin
|
|
53
|
+
payload = JSON.parse(raw_body, symbolize_names: true)
|
|
54
|
+
rescue JSON::ParserError
|
|
55
|
+
raise WebhookSignatureError, 'Failed to parse webhook payload as JSON'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
type: payload[:type].to_s,
|
|
60
|
+
payload: payload,
|
|
61
|
+
timestamp: ts,
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/certynix.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'certynix/version'
|
|
4
|
+
require 'certynix/errors'
|
|
5
|
+
require 'certynix/config'
|
|
6
|
+
require 'certynix/http_client'
|
|
7
|
+
require 'certynix/webhooks'
|
|
8
|
+
require 'certynix/models/paginator'
|
|
9
|
+
require 'certynix/resources/assets'
|
|
10
|
+
require 'certynix/resources/verify'
|
|
11
|
+
require 'certynix/resources/webhooks'
|
|
12
|
+
require 'certynix/resources/alerts'
|
|
13
|
+
require 'certynix/resources/api_keys'
|
|
14
|
+
require 'certynix/resources/audit_logs'
|
|
15
|
+
require 'certynix/resources/trust_score'
|
|
16
|
+
require 'certynix/client'
|
metadata
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: certynix
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Certynix
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faraday-retry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.12'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.12'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.23'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.23'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: vcr
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '6.2'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '6.2'
|
|
83
|
+
description: Register, certify and verify digital assets with Certynix Trust Infrastructure
|
|
84
|
+
email:
|
|
85
|
+
- sdk@certynix.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- CHANGELOG.md
|
|
91
|
+
- README.md
|
|
92
|
+
- lib/certynix.rb
|
|
93
|
+
- lib/certynix/client.rb
|
|
94
|
+
- lib/certynix/config.rb
|
|
95
|
+
- lib/certynix/errors.rb
|
|
96
|
+
- lib/certynix/http_client.rb
|
|
97
|
+
- lib/certynix/models/paginator.rb
|
|
98
|
+
- lib/certynix/resources/alerts.rb
|
|
99
|
+
- lib/certynix/resources/api_keys.rb
|
|
100
|
+
- lib/certynix/resources/assets.rb
|
|
101
|
+
- lib/certynix/resources/audit_logs.rb
|
|
102
|
+
- lib/certynix/resources/trust_score.rb
|
|
103
|
+
- lib/certynix/resources/verify.rb
|
|
104
|
+
- lib/certynix/resources/webhooks.rb
|
|
105
|
+
- lib/certynix/version.rb
|
|
106
|
+
- lib/certynix/webhooks.rb
|
|
107
|
+
homepage: https://certynix.com
|
|
108
|
+
licenses:
|
|
109
|
+
- Proprietary
|
|
110
|
+
metadata: {}
|
|
111
|
+
post_install_message:
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 3.1.0
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubygems_version: 3.5.22
|
|
127
|
+
signing_key:
|
|
128
|
+
specification_version: 4
|
|
129
|
+
summary: Official Ruby SDK for Certynix Trust Infrastructure API
|
|
130
|
+
test_files: []
|