reputable 0.1.1 → 0.1.2
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 +4 -4
- data/.DS_Store +0 -0
- data/Gemfile.lock +76 -0
- data/README.md +5 -27
- data/Rakefile +8 -0
- data/lib/reputable/configuration.rb +6 -5
- data/lib/reputable/middleware.rb +23 -25
- data/lib/reputable/rails.rb +0 -37
- data/lib/reputable/reputation.rb +3 -63
- data/lib/reputable/tracker.rb +1 -9
- data/lib/reputable/version.rb +1 -1
- data/lib/reputable.rb +70 -25
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5cfe6bbd421f190d87e5f0be242142988e269d8df74186f85f66c8f5996f115f
|
|
4
|
+
data.tar.gz: 4db1343a290ef95d775b0694a50d84d48c861af293bf0d3e4d388e44c031c9d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 211cb949190c13bd0f3d8f6c4f0296a84c92653e64c2971d5866e0606cc777ebfe356a8a73d10e5a269f6701105372f9c4c8203f90bf75f279958b385874c3ad
|
|
7
|
+
data.tar.gz: 14dbcf3338f725de441618100e0b57e99314974e0ff89b787c45141dcec56fa6c230c9acaa69feaed42233a87cf10add2cc2ccca2e80883eddb656779821ace6
|
data/.DS_Store
ADDED
|
Binary file
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
reputable (0.1.2)
|
|
5
|
+
connection_pool (~> 2.2)
|
|
6
|
+
redis (>= 4.0, < 6.0)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
connection_pool (2.5.5)
|
|
13
|
+
diff-lcs (1.6.2)
|
|
14
|
+
json (2.18.0)
|
|
15
|
+
language_server-protocol (3.17.0.5)
|
|
16
|
+
lint_roller (1.1.0)
|
|
17
|
+
parallel (1.27.0)
|
|
18
|
+
parser (3.3.10.0)
|
|
19
|
+
ast (~> 2.4.1)
|
|
20
|
+
racc
|
|
21
|
+
prism (1.7.0)
|
|
22
|
+
racc (1.8.1)
|
|
23
|
+
rack (2.2.21)
|
|
24
|
+
rainbow (3.1.1)
|
|
25
|
+
rake (13.3.1)
|
|
26
|
+
redis (5.4.1)
|
|
27
|
+
redis-client (>= 0.22.0)
|
|
28
|
+
redis-client (0.26.2)
|
|
29
|
+
connection_pool
|
|
30
|
+
regexp_parser (2.11.3)
|
|
31
|
+
rspec (3.13.2)
|
|
32
|
+
rspec-core (~> 3.13.0)
|
|
33
|
+
rspec-expectations (~> 3.13.0)
|
|
34
|
+
rspec-mocks (~> 3.13.0)
|
|
35
|
+
rspec-core (3.13.6)
|
|
36
|
+
rspec-support (~> 3.13.0)
|
|
37
|
+
rspec-expectations (3.13.5)
|
|
38
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
39
|
+
rspec-support (~> 3.13.0)
|
|
40
|
+
rspec-mocks (3.13.7)
|
|
41
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
42
|
+
rspec-support (~> 3.13.0)
|
|
43
|
+
rspec-support (3.13.6)
|
|
44
|
+
rubocop (1.82.0)
|
|
45
|
+
json (~> 2.3)
|
|
46
|
+
language_server-protocol (~> 3.17.0.2)
|
|
47
|
+
lint_roller (~> 1.1.0)
|
|
48
|
+
parallel (~> 1.10)
|
|
49
|
+
parser (>= 3.3.0.2)
|
|
50
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
51
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
52
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
53
|
+
ruby-progressbar (~> 1.7)
|
|
54
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
55
|
+
rubocop-ast (1.48.0)
|
|
56
|
+
parser (>= 3.3.7.2)
|
|
57
|
+
prism (~> 1.4)
|
|
58
|
+
ruby-progressbar (1.13.0)
|
|
59
|
+
unicode-display_width (3.2.0)
|
|
60
|
+
unicode-emoji (~> 4.1)
|
|
61
|
+
unicode-emoji (4.2.0)
|
|
62
|
+
|
|
63
|
+
PLATFORMS
|
|
64
|
+
arm64-darwin-25
|
|
65
|
+
ruby
|
|
66
|
+
|
|
67
|
+
DEPENDENCIES
|
|
68
|
+
bundler (~> 2.0)
|
|
69
|
+
rack (~> 2.0)
|
|
70
|
+
rake (~> 13.0)
|
|
71
|
+
reputable!
|
|
72
|
+
rspec (~> 3.0)
|
|
73
|
+
rubocop (~> 1.21)
|
|
74
|
+
|
|
75
|
+
BUNDLED WITH
|
|
76
|
+
2.5.9
|
data/README.md
CHANGED
|
@@ -28,9 +28,6 @@ Reputable.configure do |config|
|
|
|
28
28
|
# Required: Redis/Dragonfly URL (TLS supported via rediss://)
|
|
29
29
|
config.redis_url = ENV['REPUTABLE_REDIS_URL']
|
|
30
30
|
|
|
31
|
-
# Required: Your tenant ID
|
|
32
|
-
config.tenant_id = ENV['REPUTABLE_TENANT_ID']
|
|
33
|
-
|
|
34
31
|
# Optional: Enable logging (logs at debug level)
|
|
35
32
|
Reputable.logger = Rails.logger
|
|
36
33
|
end
|
|
@@ -82,7 +79,6 @@ All configuration can be set via environment variables:
|
|
|
82
79
|
```bash
|
|
83
80
|
# Required
|
|
84
81
|
REPUTABLE_REDIS_URL=rediss://user:password@your-dragonfly.example.com:6379
|
|
85
|
-
REPUTABLE_TENANT_ID=your-tenant-id
|
|
86
82
|
|
|
87
83
|
# Optional: Disable entirely (useful for test environments)
|
|
88
84
|
REPUTABLE_ENABLED=false
|
|
@@ -105,9 +101,6 @@ Reputable.configure do |config|
|
|
|
105
101
|
# Redis connection (supports redis:// and rediss:// for TLS)
|
|
106
102
|
config.redis_url = ENV['REPUTABLE_REDIS_URL']
|
|
107
103
|
|
|
108
|
-
# Your tenant identifier
|
|
109
|
-
config.tenant_id = ENV['REPUTABLE_TENANT_ID']
|
|
110
|
-
|
|
111
104
|
# Connection pool settings
|
|
112
105
|
config.pool_size = 5 # Number of Redis connections
|
|
113
106
|
config.pool_timeout = 1.0 # Max wait for connection (seconds)
|
|
@@ -284,8 +277,6 @@ track_reputable_request(tags: ['custom:tag'])
|
|
|
284
277
|
|
|
285
278
|
# Trust methods (after successful actions)
|
|
286
279
|
trust_current_ip(reason: 'payment_completed', order_id: '123')
|
|
287
|
-
trust_current_user(reason: 'email_verified')
|
|
288
|
-
trust_current_session(reason: 'captcha_passed')
|
|
289
280
|
|
|
290
281
|
# Challenge/Block methods
|
|
291
282
|
challenge_current_ip(reason: 'suspicious_activity')
|
|
@@ -319,11 +310,9 @@ Reputable.track_request(
|
|
|
319
310
|
path: request.path,
|
|
320
311
|
query: request.query_string,
|
|
321
312
|
method: request.request_method,
|
|
322
|
-
session_id: session.id,
|
|
323
|
-
session_present: true,
|
|
324
313
|
user_agent: request.user_agent,
|
|
325
314
|
referer: request.referer,
|
|
326
|
-
tags: ['
|
|
315
|
+
tags: ['view:page:product']
|
|
327
316
|
)
|
|
328
317
|
|
|
329
318
|
# Asynchronous (fire-and-forget, recommended)
|
|
@@ -339,12 +328,6 @@ Reputable.track_request_async(
|
|
|
339
328
|
# Trust IP forever (after payment, verification, etc.)
|
|
340
329
|
Reputable.trust_ip(request.ip, reason: 'payment_completed', order_id: order.id)
|
|
341
330
|
|
|
342
|
-
# Trust a user
|
|
343
|
-
Reputable.trust_user(current_user.id, reason: 'email_verified')
|
|
344
|
-
|
|
345
|
-
# Trust a session (default: 24 hour TTL)
|
|
346
|
-
Reputable.trust_session(session.id, reason: 'captcha_passed')
|
|
347
|
-
|
|
348
331
|
# Challenge (require CAPTCHA, etc.)
|
|
349
332
|
Reputable.challenge_ip(request.ip, reason: 'unusual_activity')
|
|
350
333
|
|
|
@@ -372,10 +355,6 @@ Reputable.lookup_reputation(:ip, request.ip)
|
|
|
372
355
|
# => { status: "trusted_verified", reason: "payment_completed",
|
|
373
356
|
# source: "app_server", updated_at: 1703123456789,
|
|
374
357
|
# expires_at: 0, metadata: { order_id: "123" } }
|
|
375
|
-
|
|
376
|
-
# User/Session lookups
|
|
377
|
-
Reputable.trusted_user?(current_user.id)
|
|
378
|
-
Reputable.trusted_session?(session.id)
|
|
379
358
|
```
|
|
380
359
|
|
|
381
360
|
---
|
|
@@ -438,10 +417,10 @@ Use tags to classify requests for behavioral analysis:
|
|
|
438
417
|
|
|
439
418
|
```ruby
|
|
440
419
|
# Page context
|
|
441
|
-
tags: ['
|
|
442
|
-
tags: ['
|
|
443
|
-
tags: ['
|
|
444
|
-
tags: ['
|
|
420
|
+
tags: ['view:page:product']
|
|
421
|
+
tags: ['view:page:checkout']
|
|
422
|
+
tags: ['view:page:cart']
|
|
423
|
+
tags: ['view:page:login']
|
|
445
424
|
|
|
446
425
|
# Traffic source
|
|
447
426
|
tags: ['trust:channel:email']
|
|
@@ -493,7 +472,6 @@ expect(Reputable.lookup_ip('1.2.3.4')).to be_nil
|
|
|
493
472
|
### What's Available from Rails
|
|
494
473
|
|
|
495
474
|
- IP reputation and history
|
|
496
|
-
- Session tracking
|
|
497
475
|
- Request classification
|
|
498
476
|
- UA churn detection
|
|
499
477
|
- Cross-request pattern analysis
|
data/Rakefile
ADDED
|
@@ -9,12 +9,12 @@ module Reputable
|
|
|
9
9
|
# Supports TLS connections with automatic SSL error handling.
|
|
10
10
|
# All SSL/connection errors are caught and logged, never breaking your app.
|
|
11
11
|
class Configuration
|
|
12
|
-
attr_accessor :redis_url, :redis_options, :
|
|
12
|
+
attr_accessor :redis_url, :redis_options, :buffer_prefix,
|
|
13
13
|
:request_buffer_key, :reputation_buffer_key,
|
|
14
14
|
:default_ttls, :pool_size, :pool_timeout,
|
|
15
15
|
:connect_timeout, :read_timeout, :write_timeout,
|
|
16
16
|
:ssl_params, :trusted_proxies, :ip_header_priority,
|
|
17
|
-
:on_error
|
|
17
|
+
:on_error, :secret_key, :verification_base_url
|
|
18
18
|
|
|
19
19
|
# Default TTLs in seconds (0 = forever)
|
|
20
20
|
DEFAULT_TTLS = {
|
|
@@ -52,7 +52,6 @@ module Reputable
|
|
|
52
52
|
def initialize
|
|
53
53
|
@redis_url = ENV.fetch("REPUTABLE_REDIS_URL", "redis://127.0.0.1:6379")
|
|
54
54
|
@redis_options = {}
|
|
55
|
-
@tenant_id = ENV.fetch("REPUTABLE_TENANT_ID", "default")
|
|
56
55
|
@buffer_prefix = "reputable:buffer"
|
|
57
56
|
@pool_size = Integer(ENV.fetch("REPUTABLE_POOL_SIZE", "5"))
|
|
58
57
|
@pool_timeout = Float(ENV.fetch("REPUTABLE_POOL_TIMEOUT", "1.0"))
|
|
@@ -64,6 +63,8 @@ module Reputable
|
|
|
64
63
|
@trusted_proxies = nil # Additional trusted proxy IPs/ranges
|
|
65
64
|
@ip_header_priority = DEFAULT_IP_HEADERS.dup
|
|
66
65
|
@on_error = nil # Optional error callback: ->(error, context) { ... }
|
|
66
|
+
@secret_key = ENV["REPUTABLE_SECRET_KEY"]
|
|
67
|
+
@verification_base_url = ENV.fetch("REPUTABLE_VERIFY_URL", "https://api.reputable.click/_reputable/verify")
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
# Check if Reputable is enabled (can be disabled via ENV)
|
|
@@ -75,11 +76,11 @@ module Reputable
|
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
def request_buffer_key
|
|
78
|
-
@request_buffer_key || "#{buffer_prefix}:requests
|
|
79
|
+
@request_buffer_key || "#{buffer_prefix}:requests"
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
def reputation_buffer_key
|
|
82
|
-
@reputation_buffer_key || "#{buffer_prefix}:reputation
|
|
83
|
+
@reputation_buffer_key || "#{buffer_prefix}:reputation"
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
def default_ttl_for(status)
|
data/lib/reputable/middleware.rb
CHANGED
|
@@ -45,6 +45,9 @@ module Reputable
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def call(env)
|
|
48
|
+
# Check for verification return parameters and verify signature if present
|
|
49
|
+
handle_verification_return(env)
|
|
50
|
+
|
|
48
51
|
# ALWAYS process the request first - tracking must never block
|
|
49
52
|
status, headers, response = @app.call(env)
|
|
50
53
|
|
|
@@ -70,6 +73,26 @@ module Reputable
|
|
|
70
73
|
Reputable.logger&.debug("Reputable middleware: #{e.class} - #{e.message}")
|
|
71
74
|
end
|
|
72
75
|
|
|
76
|
+
def handle_verification_return(env)
|
|
77
|
+
request = Rack::Request.new(env)
|
|
78
|
+
# Quick check to avoid overhead
|
|
79
|
+
return unless request.query_string.include?("reputable_status")
|
|
80
|
+
|
|
81
|
+
return unless request.params["reputable_status"] == "pass"
|
|
82
|
+
|
|
83
|
+
if Reputable.verify_redirect_return(request.params)
|
|
84
|
+
env["reputable.verified"] = true
|
|
85
|
+
|
|
86
|
+
# Store in session if available
|
|
87
|
+
if env["rack.session"]
|
|
88
|
+
env["rack.session"]["reputable_verified"] = true
|
|
89
|
+
env["rack.session"]["reputable_verified_at"] = Time.now.to_i
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
Reputable.logger&.debug("Reputable verification error: #{e.message}")
|
|
94
|
+
end
|
|
95
|
+
|
|
73
96
|
def skip_request?(env)
|
|
74
97
|
return true if @skip_if&.call(env)
|
|
75
98
|
|
|
@@ -109,8 +132,6 @@ module Reputable
|
|
|
109
132
|
path: request.path,
|
|
110
133
|
query: request.query_string.empty? ? nil : request.query_string,
|
|
111
134
|
method: request.request_method,
|
|
112
|
-
session_id: extract_session_id(env),
|
|
113
|
-
session_present: session_present?(env),
|
|
114
135
|
user_agent: env["HTTP_USER_AGENT"],
|
|
115
136
|
referer: env["HTTP_REFERER"],
|
|
116
137
|
tags: build_tags(env)
|
|
@@ -190,29 +211,6 @@ module Reputable
|
|
|
190
211
|
nil
|
|
191
212
|
end
|
|
192
213
|
|
|
193
|
-
def extract_session_id(env)
|
|
194
|
-
# Try rack.session.id first
|
|
195
|
-
return env["rack.session.id"] if env["rack.session.id"]
|
|
196
|
-
|
|
197
|
-
# Try to get from rack.session
|
|
198
|
-
session = env["rack.session"]
|
|
199
|
-
return session.id.to_s if session.respond_to?(:id)
|
|
200
|
-
|
|
201
|
-
nil
|
|
202
|
-
rescue StandardError
|
|
203
|
-
nil
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def session_present?(env)
|
|
207
|
-
return true if env["rack.session.id"]
|
|
208
|
-
return true if env["rack.session"]&.any?
|
|
209
|
-
return true if env["HTTP_COOKIE"]&.include?("_session")
|
|
210
|
-
|
|
211
|
-
false
|
|
212
|
-
rescue StandardError
|
|
213
|
-
false
|
|
214
|
-
end
|
|
215
|
-
|
|
216
214
|
def build_tags(env)
|
|
217
215
|
tags = []
|
|
218
216
|
|
data/lib/reputable/rails.rb
CHANGED
|
@@ -14,8 +14,6 @@ module Reputable
|
|
|
14
14
|
path: request.path,
|
|
15
15
|
query: request.query_string,
|
|
16
16
|
method: request.method,
|
|
17
|
-
session_id: session.id.to_s,
|
|
18
|
-
session_present: session.any?,
|
|
19
17
|
user_agent: request.user_agent,
|
|
20
18
|
referer: request.referer,
|
|
21
19
|
tags: tags,
|
|
@@ -32,26 +30,6 @@ module Reputable
|
|
|
32
30
|
)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
|
-
# Trust the current user
|
|
36
|
-
def trust_current_user(reason:, **metadata)
|
|
37
|
-
return false unless respond_to?(:current_user) && current_user
|
|
38
|
-
|
|
39
|
-
Reputable::Reputation.trust_user(
|
|
40
|
-
current_user.id,
|
|
41
|
-
reason: reason,
|
|
42
|
-
**metadata
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Trust the current session
|
|
47
|
-
def trust_current_session(reason:, **metadata)
|
|
48
|
-
Reputable::Reputation.trust_session(
|
|
49
|
-
session.id.to_s,
|
|
50
|
-
reason: reason,
|
|
51
|
-
**metadata
|
|
52
|
-
)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
33
|
# Challenge the current IP
|
|
56
34
|
def challenge_current_ip(reason:, **metadata)
|
|
57
35
|
Reputable::Reputation.challenge_ip(
|
|
@@ -103,20 +81,6 @@ module Reputable
|
|
|
103
81
|
def current_ip_status
|
|
104
82
|
Reputable::Reputation.lookup_ip(request.remote_ip)
|
|
105
83
|
end
|
|
106
|
-
|
|
107
|
-
# Check if current user is trusted
|
|
108
|
-
# @return [Boolean]
|
|
109
|
-
def current_user_trusted?
|
|
110
|
-
return false unless respond_to?(:current_user) && current_user
|
|
111
|
-
|
|
112
|
-
Reputable::Reputation.trusted_user?(current_user.id)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Check if current session is trusted
|
|
116
|
-
# @return [Boolean]
|
|
117
|
-
def current_session_trusted?
|
|
118
|
-
Reputable::Reputation.trusted_session?(session.id.to_s)
|
|
119
|
-
end
|
|
120
84
|
end
|
|
121
85
|
|
|
122
86
|
# Railtie for automatic Rails integration (only defined when Rails is present)
|
|
@@ -130,7 +94,6 @@ module Reputable
|
|
|
130
94
|
creds = ::Rails.application.credentials.reputable rescue nil
|
|
131
95
|
if creds
|
|
132
96
|
config.redis_url = creds[:redis_url] if creds[:redis_url]
|
|
133
|
-
config.tenant_id = creds[:tenant_id] if creds[:tenant_id]
|
|
134
97
|
end
|
|
135
98
|
end
|
|
136
99
|
end
|
data/lib/reputable/reputation.rb
CHANGED
|
@@ -19,12 +19,12 @@ module Reputable
|
|
|
19
19
|
untrusted_ignore
|
|
20
20
|
].freeze
|
|
21
21
|
|
|
22
|
-
VALID_ENTITY_TYPES = %i[ip asn ja4
|
|
22
|
+
VALID_ENTITY_TYPES = %i[ip asn ja4].freeze
|
|
23
23
|
|
|
24
24
|
class << self
|
|
25
25
|
# Apply a reputation status to an entity
|
|
26
26
|
#
|
|
27
|
-
# @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4
|
|
27
|
+
# @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4)
|
|
28
28
|
# @param entity_id [String] The entity identifier (IP address, ASN, etc.)
|
|
29
29
|
# @param status [Symbol] Reputation status to apply
|
|
30
30
|
# @param options [Hash] Additional options
|
|
@@ -110,30 +110,6 @@ module Reputable
|
|
|
110
110
|
)
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# Trust a user (by internal user ID)
|
|
114
|
-
def trust_user(user_id, reason: "verified_user", **metadata)
|
|
115
|
-
apply(
|
|
116
|
-
entity_type: :user,
|
|
117
|
-
entity_id: user_id.to_s,
|
|
118
|
-
status: :trusted_verified,
|
|
119
|
-
reason: reason,
|
|
120
|
-
ttl: 0,
|
|
121
|
-
metadata: metadata
|
|
122
|
-
)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Trust a session
|
|
126
|
-
def trust_session(session_id, reason: "verified_session", ttl: nil, **metadata)
|
|
127
|
-
apply(
|
|
128
|
-
entity_type: :session,
|
|
129
|
-
entity_id: session_id,
|
|
130
|
-
status: :trusted_verified,
|
|
131
|
-
reason: reason,
|
|
132
|
-
ttl: ttl || (24 * 3600), # Default 24 hours for sessions
|
|
133
|
-
metadata: metadata
|
|
134
|
-
)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
113
|
# ========================================================================
|
|
138
114
|
# LOOKUP METHODS (O(1) Redis hash lookups)
|
|
139
115
|
# ========================================================================
|
|
@@ -141,7 +117,7 @@ module Reputable
|
|
|
141
117
|
# Lookup the current reputation status for an entity
|
|
142
118
|
# This is an O(1) Redis HGETALL operation
|
|
143
119
|
#
|
|
144
|
-
# @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4
|
|
120
|
+
# @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4)
|
|
145
121
|
# @param entity_id [String] The entity identifier
|
|
146
122
|
# @return [Hash, nil] Reputation data or nil if not found/expired/error
|
|
147
123
|
#
|
|
@@ -227,42 +203,6 @@ module Reputable
|
|
|
227
203
|
lookup_ip(ip) == "untrusted_challenge"
|
|
228
204
|
end
|
|
229
205
|
|
|
230
|
-
# Lookup user reputation
|
|
231
|
-
#
|
|
232
|
-
# @param user_id [String, Integer] User ID
|
|
233
|
-
# @return [String, nil] Status string or nil
|
|
234
|
-
def lookup_user(user_id)
|
|
235
|
-
result = lookup(:user, user_id.to_s)
|
|
236
|
-
result&.dig(:status)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Check if a user is trusted
|
|
240
|
-
#
|
|
241
|
-
# @param user_id [String, Integer] User ID
|
|
242
|
-
# @return [Boolean]
|
|
243
|
-
def trusted_user?(user_id)
|
|
244
|
-
status = lookup_user(user_id)
|
|
245
|
-
status&.start_with?("trusted")
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# Lookup session reputation
|
|
249
|
-
#
|
|
250
|
-
# @param session_id [String] Session ID
|
|
251
|
-
# @return [String, nil] Status string or nil
|
|
252
|
-
def lookup_session(session_id)
|
|
253
|
-
result = lookup(:session, session_id)
|
|
254
|
-
result&.dig(:status)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# Check if a session is trusted
|
|
258
|
-
#
|
|
259
|
-
# @param session_id [String] Session ID
|
|
260
|
-
# @return [Boolean]
|
|
261
|
-
def trusted_session?(session_id)
|
|
262
|
-
status = lookup_session(session_id)
|
|
263
|
-
status&.start_with?("trusted")
|
|
264
|
-
end
|
|
265
|
-
|
|
266
206
|
private
|
|
267
207
|
|
|
268
208
|
def valid_entity_type?(entity_type)
|
data/lib/reputable/tracker.rb
CHANGED
|
@@ -18,9 +18,6 @@ module Reputable
|
|
|
18
18
|
# @param options [Hash] Additional options
|
|
19
19
|
# @option options [String] :query Query string
|
|
20
20
|
# @option options [String] :method HTTP method (GET, POST, etc.)
|
|
21
|
-
# @option options [String] :session_id Session identifier
|
|
22
|
-
# @option options [Boolean] :session_present Whether a session cookie exists
|
|
23
|
-
# @option options [String] :user_id Internal user ID (for logged-in users)
|
|
24
21
|
# @option options [String] :fingerprint Browser fingerprint hash
|
|
25
22
|
# @option options [String] :user_agent User-Agent header
|
|
26
23
|
# @option options [String] :referer Referer header
|
|
@@ -41,11 +38,9 @@ module Reputable
|
|
|
41
38
|
# path: request.path,
|
|
42
39
|
# query: request.query_string,
|
|
43
40
|
# method: request.request_method,
|
|
44
|
-
# session_id: session.id,
|
|
45
|
-
# session_present: true,
|
|
46
41
|
# user_agent: request.user_agent,
|
|
47
42
|
# referer: request.referer,
|
|
48
|
-
# tags: ["
|
|
43
|
+
# tags: ["view:page:product", "trust:channel:organic"]
|
|
49
44
|
# )
|
|
50
45
|
def track_request(ip:, path:, **options)
|
|
51
46
|
return false unless Reputable.enabled?
|
|
@@ -80,9 +75,6 @@ module Reputable
|
|
|
80
75
|
path: path,
|
|
81
76
|
query: options[:query],
|
|
82
77
|
method: options[:method] || "GET",
|
|
83
|
-
session_id: options[:session_id],
|
|
84
|
-
session_present: options[:session_present],
|
|
85
|
-
user_id: options[:user_id],
|
|
86
78
|
fingerprint: options[:fingerprint],
|
|
87
79
|
user_agent: options[:user_agent],
|
|
88
80
|
referer: options[:referer],
|
data/lib/reputable/version.rb
CHANGED
data/lib/reputable.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "reputable/version"
|
|
4
|
+
require "json"
|
|
5
|
+
require "base64"
|
|
4
6
|
require_relative "reputable/configuration"
|
|
5
7
|
require_relative "reputable/connection"
|
|
6
8
|
require_relative "reputable/tracker"
|
|
@@ -23,14 +25,12 @@ end
|
|
|
23
25
|
# @example Basic setup
|
|
24
26
|
# Reputable.configure do |config|
|
|
25
27
|
# config.redis_url = "rediss://user:pass@dragonfly.example.com:6379"
|
|
26
|
-
# config.tenant_id = "my-tenant"
|
|
27
28
|
# end
|
|
28
29
|
#
|
|
29
30
|
# @example Track a request
|
|
30
31
|
# Reputable.track_request(
|
|
31
32
|
# ip: "203.0.113.45",
|
|
32
|
-
# path: "/products/123"
|
|
33
|
-
# session_present: true
|
|
33
|
+
# path: "/products/123"
|
|
34
34
|
# )
|
|
35
35
|
#
|
|
36
36
|
# @example Apply reputation after payment
|
|
@@ -104,14 +104,6 @@ module Reputable
|
|
|
104
104
|
Reputation.ignore_ip(ip, reason: reason, **metadata)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def trust_user(user_id, reason: "verified_user", **metadata)
|
|
108
|
-
Reputation.trust_user(user_id, reason: reason, **metadata)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def trust_session(session_id, reason: "verified_session", **metadata)
|
|
112
|
-
Reputation.trust_session(session_id, reason: reason, **metadata)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
107
|
# Delegate lookup methods to Reputation module (O(1) Redis lookups)
|
|
116
108
|
def lookup_reputation(entity_type, entity_id)
|
|
117
109
|
Reputation.lookup(entity_type, entity_id)
|
|
@@ -133,20 +125,73 @@ module Reputable
|
|
|
133
125
|
Reputation.challenged_ip?(ip)
|
|
134
126
|
end
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
128
|
+
# Generate a signed verification URL
|
|
129
|
+
def verification_url(return_url:, failure_url: nil, session_id: nil)
|
|
130
|
+
secret = configuration.secret_key
|
|
131
|
+
unless secret
|
|
132
|
+
logger&.warn "Reputable: Missing secret_key, cannot generate verification URL"
|
|
133
|
+
return return_url
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
base_url = configuration.verification_base_url
|
|
137
|
+
|
|
138
|
+
# JWT Header
|
|
139
|
+
header = { alg: "HS256", typ: "JWT" }
|
|
140
|
+
encoded_header = base64url_encode(JSON.generate(header))
|
|
141
|
+
|
|
142
|
+
# JWT Payload
|
|
143
|
+
payload = {
|
|
144
|
+
returnUrl: return_url,
|
|
145
|
+
failureUrl: failure_url,
|
|
146
|
+
sessionId: session_id,
|
|
147
|
+
iat: Time.now.to_i
|
|
148
|
+
}
|
|
149
|
+
encoded_payload = base64url_encode(JSON.generate(payload))
|
|
150
|
+
|
|
151
|
+
# Signature
|
|
152
|
+
data = "#{encoded_header}.#{encoded_payload}"
|
|
153
|
+
signature = OpenSSL::HMAC.digest("SHA256", secret, data)
|
|
154
|
+
encoded_signature = base64url_encode(signature)
|
|
155
|
+
|
|
156
|
+
token = "#{data}.#{encoded_signature}"
|
|
157
|
+
|
|
158
|
+
"#{base_url}?token=#{token}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Verify the signature of a redirect return
|
|
162
|
+
# @param params [Hash] Request query parameters
|
|
163
|
+
# @return [Boolean] true if valid logic and passed signature check
|
|
164
|
+
def verify_redirect_return(params)
|
|
165
|
+
status = params["reputable_status"]
|
|
166
|
+
session_id = params["reputable_session_id"]
|
|
167
|
+
signature = params["reputable_signature"]
|
|
168
|
+
|
|
169
|
+
return false unless status && session_id && signature
|
|
170
|
+
|
|
171
|
+
secret = configuration.secret_key
|
|
172
|
+
unless secret
|
|
173
|
+
logger&.warn "Reputable: Missing secret_key, cannot verify redirect"
|
|
174
|
+
return false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
data = "#{status}:#{session_id}"
|
|
178
|
+
expected_signature = OpenSSL::HMAC.hexdigest("SHA256", secret, data)
|
|
179
|
+
|
|
180
|
+
secure_compare(expected_signature, signature)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def base64url_encode(str)
|
|
186
|
+
Base64.urlsafe_encode64(str).gsub("=", "")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def secure_compare(a, b)
|
|
190
|
+
return false unless a.bytesize == b.bytesize
|
|
191
|
+
l = a.unpack "C#{a.bytesize}"
|
|
192
|
+
res = 0
|
|
193
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
|
194
|
+
res == 0
|
|
150
195
|
end
|
|
151
196
|
end
|
|
152
197
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reputable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Reputable
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|
|
@@ -122,8 +122,11 @@ executables: []
|
|
|
122
122
|
extensions: []
|
|
123
123
|
extra_rdoc_files: []
|
|
124
124
|
files:
|
|
125
|
+
- ".DS_Store"
|
|
125
126
|
- Gemfile
|
|
127
|
+
- Gemfile.lock
|
|
126
128
|
- README.md
|
|
129
|
+
- Rakefile
|
|
127
130
|
- lib/reputable.rb
|
|
128
131
|
- lib/reputable/configuration.rb
|
|
129
132
|
- lib/reputable/connection.rb
|