traffic_orchestrator 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/README.md +135 -0
- data/lib/traffic_orchestrator.rb +227 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 70635b2e17549aeb91e0aec3d144c254fe07a81978324dfaa62e5df697e3e0b1
|
|
4
|
+
data.tar.gz: df7c26f54a757112be4886a6b7d88aa195f1de32aa8be1cd4a29ea84990c6db8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 36da1c9cbe288d412e06fbc205198de3ed801f8d1bedfab2a651ab4c3d9273e7ebb13bb294cc8d4f275052a4f484f824992401995bad84999eb23a7663cae44a
|
|
7
|
+
data.tar.gz: 9dccaf4ab4690ff0c2e43370afbd080ceed638367fe5fa5eb94cb3d7e8be66a381a54445911b7c4d56d8be57d43735f248f741322b69c903942b93ba570a2283
|
data/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# traffic_orchestrator (Ruby)
|
|
2
|
+
|
|
3
|
+
Official Ruby gem for [Traffic Orchestrator](https://trafficorchestrator.com) — enterprise-grade software license management.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Gemfile
|
|
9
|
+
gem 'traffic_orchestrator'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle install
|
|
14
|
+
# or
|
|
15
|
+
gem install traffic_orchestrator
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
require 'traffic_orchestrator'
|
|
22
|
+
|
|
23
|
+
client = TrafficOrchestrator::Client.new(
|
|
24
|
+
api_key: ENV['TO_API_KEY'],
|
|
25
|
+
timeout: 5,
|
|
26
|
+
retries: 3
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
result = client.validate_license('LK-xxxx', domain: 'example.com')
|
|
30
|
+
puts "Valid: #{result.valid}, Plan: #{result.plan_id}" if result.valid
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API Methods
|
|
34
|
+
|
|
35
|
+
### Core License Operations
|
|
36
|
+
|
|
37
|
+
| Method | Description |
|
|
38
|
+
|--------|-------------|
|
|
39
|
+
| `validate_license(token, domain:)` | Validate a license key against a domain |
|
|
40
|
+
| `verify_offline(token)` | Verify license locally using Ed25519 signatures |
|
|
41
|
+
| `list_licenses` | List all licenses for the authenticated user |
|
|
42
|
+
| `create_license(plan_id:, domains:)` | Create a new license |
|
|
43
|
+
| `rotate_license(license_id)` | Rotate a license key (revoke old, generate new) |
|
|
44
|
+
| `add_domain(license_id, domain)` | Add a domain to a license |
|
|
45
|
+
| `remove_domain(license_id, domain)` | Remove a domain from a license |
|
|
46
|
+
| `delete_license(license_id)` | Delete (revoke) a license |
|
|
47
|
+
| `get_usage` | Get current usage statistics |
|
|
48
|
+
|
|
49
|
+
### Portal & Enterprise Methods
|
|
50
|
+
|
|
51
|
+
| Method | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `get_analytics(days: 30)` | Detailed analytics for the specified period |
|
|
54
|
+
| `get_dashboard` | Full dashboard overview |
|
|
55
|
+
| `get_sla` | SLA compliance data |
|
|
56
|
+
| `export_audit_logs(format: 'json')` | Export audit logs as JSON or CSV |
|
|
57
|
+
| `get_webhook_deliveries(page: 1)` | Webhook delivery history |
|
|
58
|
+
| `batch_license_operation(op:, ids:)` | Batch suspend/activate/extend licenses |
|
|
59
|
+
| `get_ip_allowlist(license_id)` | Get IP allowlist for a license |
|
|
60
|
+
| `set_ip_allowlist(license_id, ips:)` | Set IP allowlist for a license |
|
|
61
|
+
| `health_check` | Check API health status |
|
|
62
|
+
|
|
63
|
+
## Error Handling
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
begin
|
|
67
|
+
client.validate_license(token, domain: 'example.com')
|
|
68
|
+
rescue TrafficOrchestrator::ApiError => e
|
|
69
|
+
puts "#{e.code}: #{e.message} (HTTP #{e.status})"
|
|
70
|
+
rescue TrafficOrchestrator::NetworkError => e
|
|
71
|
+
puts "Network: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Rails Integration
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# config/initializers/traffic_orchestrator.rb
|
|
79
|
+
TrafficOrchestrator.configure do |config|
|
|
80
|
+
config.api_key = Rails.application.credentials.dig(:traffic_orchestrator, :api_key)
|
|
81
|
+
config.timeout = 5
|
|
82
|
+
config.retries = 3
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# In a controller
|
|
86
|
+
class LicensesController < ApplicationController
|
|
87
|
+
def validate
|
|
88
|
+
result = TrafficOrchestrator.client.validate_license(
|
|
89
|
+
params[:license_key],
|
|
90
|
+
domain: request.host
|
|
91
|
+
)
|
|
92
|
+
render json: { valid: result.valid, plan: result.plan_id }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Multi-Environment Keys
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
# Development
|
|
101
|
+
dev_client = TrafficOrchestrator::Client.new(
|
|
102
|
+
api_key: ENV['TO_API_KEY_DEV'],
|
|
103
|
+
base_url: 'https://api-staging.trafficorchestrator.com'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Production
|
|
107
|
+
prod_client = TrafficOrchestrator::Client.new(
|
|
108
|
+
api_key: ENV['TO_API_KEY']
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Offline Verification (Enterprise)
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
client = TrafficOrchestrator::Client.new(
|
|
116
|
+
public_key: ENV['TO_PUBLIC_KEY']
|
|
117
|
+
)
|
|
118
|
+
result = client.verify_offline(license_token)
|
|
119
|
+
puts "Plan: #{result.plan_id}" if result.valid
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
- Ruby 3.0+
|
|
125
|
+
- Faraday 2.x
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
- [API Reference](https://trafficorchestrator.com/docs)
|
|
130
|
+
- [Ruby SDK Guide](https://trafficorchestrator.com/docs/sdk/ruby)
|
|
131
|
+
- [OpenAPI Spec](https://api.trafficorchestrator.com/api/v1/openapi.json)
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module TrafficOrchestrator
|
|
6
|
+
VERSION = '2.0.0'
|
|
7
|
+
|
|
8
|
+
class Client
|
|
9
|
+
attr_reader :base_url, :api_key, :timeout, :retries
|
|
10
|
+
|
|
11
|
+
def initialize(base_url = 'https://api.trafficorchestrator.com/api/v1', api_key: nil, timeout: 10, retries: 2)
|
|
12
|
+
@base_url = base_url.chomp('/')
|
|
13
|
+
@api_key = api_key
|
|
14
|
+
@timeout = timeout
|
|
15
|
+
@retries = retries
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# ── Core: License Validation ──────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
# Validate a license key against the API server.
|
|
21
|
+
def validate_license(token, domain = nil)
|
|
22
|
+
request('POST', '/validate', { token: token, domain: domain })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Verify offline using Ed25519.
|
|
26
|
+
# Requires 'ed25519' and 'base64' gems.
|
|
27
|
+
def verify_offline(token, public_key_base64, domain = nil)
|
|
28
|
+
require 'ed25519'
|
|
29
|
+
require 'base64'
|
|
30
|
+
|
|
31
|
+
parts = token.split('.')
|
|
32
|
+
return { 'valid' => false, 'error' => 'Invalid token format' } unless parts.length == 3
|
|
33
|
+
|
|
34
|
+
header = JSON.parse(Base64.urlsafe_decode64(parts[0]))
|
|
35
|
+
payload = JSON.parse(Base64.urlsafe_decode64(parts[1]))
|
|
36
|
+
signature = Base64.urlsafe_decode64(parts[2])
|
|
37
|
+
|
|
38
|
+
return { 'valid' => false, 'error' => 'Algorithm not supported' } unless header['alg'] == 'EdDSA'
|
|
39
|
+
|
|
40
|
+
# Verify Signature
|
|
41
|
+
public_key_bytes = Base64.strict_decode64(public_key_base64)
|
|
42
|
+
verify_key = Ed25519::VerifyKey.new(public_key_bytes)
|
|
43
|
+
|
|
44
|
+
message = "#{parts[0]}.#{parts[1]}"
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
verify_key.verify(signature, message)
|
|
48
|
+
rescue Ed25519::VerifyError
|
|
49
|
+
return { 'valid' => false, 'error' => 'Invalid signature' }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Verify Expiration
|
|
53
|
+
if payload['exp'] && payload['exp'] < Time.now.to_i
|
|
54
|
+
return { 'valid' => false, 'error' => 'Token expired' }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Verify Domain
|
|
58
|
+
if domain && payload['dom'].is_a?(Array)
|
|
59
|
+
match = payload['dom'].any? { |d| domain.include?(d) }
|
|
60
|
+
return { 'valid' => false, 'error' => 'Domain mismatch' } unless match
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
{ 'valid' => true, 'payload' => payload }
|
|
64
|
+
rescue => e
|
|
65
|
+
{ 'valid' => false, 'error' => e.message }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# ── License Management (requires API key) ────────────────────────────
|
|
69
|
+
|
|
70
|
+
# List all licenses for the authenticated user.
|
|
71
|
+
def list_licenses
|
|
72
|
+
data = request('GET', '/portal/licenses')
|
|
73
|
+
data['licenses'] || []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create a new license.
|
|
77
|
+
def create_license(app_name, domain: nil, plan_id: nil)
|
|
78
|
+
body = { appName: app_name }
|
|
79
|
+
body[:domain] = domain if domain
|
|
80
|
+
body[:planId] = plan_id if plan_id
|
|
81
|
+
request('POST', '/portal/licenses', body)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Rotate a license key (revoke old, generate new).
|
|
85
|
+
def rotate_license(license_id)
|
|
86
|
+
request('POST', "/portal/licenses/#{license_id}/rotate")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Add a domain to a license.
|
|
90
|
+
def add_domain(license_id, domain)
|
|
91
|
+
request('POST', "/portal/licenses/#{license_id}/domains", { domain: domain })
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Remove a domain from a license.
|
|
95
|
+
def remove_domain(license_id, domain)
|
|
96
|
+
request('DELETE', "/portal/licenses/#{license_id}/domains", { domain: domain })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Delete (revoke) a license.
|
|
100
|
+
def delete_license(license_id)
|
|
101
|
+
request('DELETE', "/portal/licenses/#{license_id}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# ── Usage & Analytics ────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
# Get current usage statistics.
|
|
107
|
+
def get_usage
|
|
108
|
+
request('GET', '/portal/stats')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# ── Health ───────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
# Check API health status.
|
|
114
|
+
def health_check
|
|
115
|
+
request('GET', '/health')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ── Analytics & SLA ──────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
# Get detailed analytics for the specified number of days.
|
|
121
|
+
def get_analytics(days: 30)
|
|
122
|
+
request('GET', "/portal/analytics?days=#{days}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get a full dashboard overview.
|
|
126
|
+
def get_dashboard
|
|
127
|
+
request('GET', '/portal/dashboard')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get SLA compliance data.
|
|
131
|
+
def get_sla(days: 30)
|
|
132
|
+
request('GET', "/portal/sla?days=#{days}")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ── Audit & Webhooks ─────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
# Export audit logs in the specified format.
|
|
138
|
+
def export_audit_logs(format: 'json', since: nil)
|
|
139
|
+
path = "/portal/audit-logs/export?format=#{format}"
|
|
140
|
+
path += "&since=#{since}" if since
|
|
141
|
+
request('GET', path)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Get webhook delivery history.
|
|
145
|
+
def get_webhook_deliveries(limit: 50, status: nil)
|
|
146
|
+
path = "/portal/webhooks/deliveries?limit=#{limit}"
|
|
147
|
+
path += "&status=#{status}" if status
|
|
148
|
+
request('GET', path)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# ── Batch Operations ─────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
# Perform a batch operation on multiple licenses.
|
|
154
|
+
def batch_license_operation(action, license_ids, days: nil)
|
|
155
|
+
body = { action: action, licenseIds: license_ids }
|
|
156
|
+
body[:days] = days if days
|
|
157
|
+
request('POST', '/portal/licenses/batch', body)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# ── IP Allowlist ─────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
# Get the IP allowlist for a license.
|
|
163
|
+
def get_ip_allowlist(license_id)
|
|
164
|
+
request('GET', "/portal/licenses/#{license_id}/ip-allowlist")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Set the IP allowlist for a license.
|
|
168
|
+
def set_ip_allowlist(license_id, allowed_ips)
|
|
169
|
+
request('PUT', "/portal/licenses/#{license_id}/ip-allowlist", { allowedIps: allowed_ips })
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
# ── Internal ─────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
def request(method, path, body = nil)
|
|
177
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
178
|
+
last_error = nil
|
|
179
|
+
|
|
180
|
+
(0..@retries).each do |attempt|
|
|
181
|
+
begin
|
|
182
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
183
|
+
http.use_ssl = true if uri.scheme == 'https'
|
|
184
|
+
http.open_timeout = @timeout
|
|
185
|
+
http.read_timeout = @timeout
|
|
186
|
+
|
|
187
|
+
headers = {
|
|
188
|
+
'Content-Type' => 'application/json',
|
|
189
|
+
'User-Agent' => "TrafficOrchestrator-Ruby/#{VERSION}"
|
|
190
|
+
}
|
|
191
|
+
headers['Authorization'] = "Bearer #{@api_key}" if @api_key
|
|
192
|
+
|
|
193
|
+
case method
|
|
194
|
+
when 'POST'
|
|
195
|
+
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
196
|
+
req.body = body.to_json if body
|
|
197
|
+
when 'PUT'
|
|
198
|
+
req = Net::HTTP::Put.new(uri.request_uri, headers)
|
|
199
|
+
req.body = body.to_json if body
|
|
200
|
+
when 'DELETE'
|
|
201
|
+
req = Net::HTTP::Delete.new(uri.request_uri, headers)
|
|
202
|
+
req.body = body.to_json if body
|
|
203
|
+
when 'GET'
|
|
204
|
+
req = Net::HTTP::Get.new(uri.request_uri, headers)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
response = http.request(req)
|
|
208
|
+
data = JSON.parse(response.body)
|
|
209
|
+
|
|
210
|
+
code = response.code.to_i
|
|
211
|
+
return data if code >= 200 && code < 300
|
|
212
|
+
return data if code >= 400 && code < 500
|
|
213
|
+
|
|
214
|
+
last_error = "HTTP #{code}"
|
|
215
|
+
rescue => e
|
|
216
|
+
last_error = e.message
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if attempt < @retries
|
|
220
|
+
sleep([1.0 * (2 ** attempt), 5.0].min)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
{ 'valid' => false, 'error' => last_error || 'Request failed' }
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: traffic_orchestrator
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Traffic Orchestrator
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: ed25519
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: jwt
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.7'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.7'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: faraday
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.7'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.7'
|
|
55
|
+
description: Ruby SDK for Traffic Orchestrator license validation with offline verification
|
|
56
|
+
support
|
|
57
|
+
email:
|
|
58
|
+
- contact@trafficorchestrator.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- README.md
|
|
64
|
+
- lib/traffic_orchestrator.rb
|
|
65
|
+
homepage: https://trafficorchestrator.com
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata: {}
|
|
69
|
+
post_install_message:
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 2.7.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 3.4.19
|
|
85
|
+
signing_key:
|
|
86
|
+
specification_version: 4
|
|
87
|
+
summary: Traffic Orchestrator License Validation SDK
|
|
88
|
+
test_files: []
|