reputable 0.1.21 → 0.1.23
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/Gemfile.lock +1 -1
- data/README.md +50 -4
- data/lib/reputable/configuration.rb +3 -1
- data/lib/reputable/middleware.rb +46 -14
- data/lib/reputable/reputation.rb +10 -3
- data/lib/reputable/version.rb +1 -1
- data/lib/reputable.rb +6 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f667ca542120eb294a6c61a0650a1c1deb82b5383c87e8d744c98c9fba7548a
|
|
4
|
+
data.tar.gz: d061fbe002a8ae3c96bbbf45b7470f5df4bdac89f49bcb36f42ab1e676ee5699
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f47bb9c880009d89a36d71ef0021edd07de2b87fb38ab5625625b89d7ad0240391fbc5ff4e5630e12bc7fafc4290a9649aec9685c25cfca5c28546e8e9699f6
|
|
7
|
+
data.tar.gz: 7223bdf677132850ccb4ef226c8540dad07fba22853c9461873a696b3c16f52a65fc06eba1757aeb121607470baea291e027db7364a19ee9c505a92bbec1b49d
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -29,10 +29,13 @@ bundle install
|
|
|
29
29
|
Reputable.configure do |config|
|
|
30
30
|
# Required: Redis/Dragonfly URL (TLS supported via rediss://)
|
|
31
31
|
config.redis_url = ENV['REPUTABLE_REDIS_URL']
|
|
32
|
-
|
|
33
|
-
# Optional:
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
# Optional: Verbose logging (logs every middleware decision)
|
|
34
|
+
# config.verbose = true
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
# Optional: Enable logging
|
|
38
|
+
Reputable.logger = Rails.logger
|
|
36
39
|
```
|
|
37
40
|
|
|
38
41
|
### 2. Add Middleware
|
|
@@ -100,6 +103,9 @@ REPUTABLE_WRITE_TIMEOUT=0.5 # Redis write timeout (seconds)
|
|
|
100
103
|
REPUTABLE_POOL_SIZE=5 # Connection pool size
|
|
101
104
|
REPUTABLE_POOL_TIMEOUT=1.0 # Pool checkout timeout (seconds)
|
|
102
105
|
|
|
106
|
+
# Optional: Verbose logging (logs every middleware decision at info level)
|
|
107
|
+
REPUTABLE_VERBOSE=true
|
|
108
|
+
|
|
103
109
|
# Optional: SSL (for custom certificates)
|
|
104
110
|
REPUTABLE_SSL_VERIFY=false # Disable SSL verification (NOT recommended for production)
|
|
105
111
|
```
|
|
@@ -155,6 +161,10 @@ Reputable.configure do |config|
|
|
|
155
161
|
config.support_email = ENV['REPUTABLE_SUPPORT_EMAIL']
|
|
156
162
|
config.support_url = ENV['REPUTABLE_SUPPORT_URL']
|
|
157
163
|
|
|
164
|
+
# Verbose logging — logs every middleware decision at info level
|
|
165
|
+
# Also available via REPUTABLE_VERBOSE=true
|
|
166
|
+
config.verbose = true
|
|
167
|
+
|
|
158
168
|
# Error callback (optional)
|
|
159
169
|
config.on_error = ->(error, context) {
|
|
160
170
|
# Report to your error tracking service
|
|
@@ -162,7 +172,7 @@ Reputable.configure do |config|
|
|
|
162
172
|
}
|
|
163
173
|
end
|
|
164
174
|
|
|
165
|
-
# Enable logging
|
|
175
|
+
# Enable logging (required for verbose output)
|
|
166
176
|
Reputable.logger = Rails.logger
|
|
167
177
|
```
|
|
168
178
|
|
|
@@ -719,6 +729,42 @@ config.on_error = ->(error, context) {
|
|
|
719
729
|
|
|
720
730
|
---
|
|
721
731
|
|
|
732
|
+
## Verbose Logging / Debugging
|
|
733
|
+
|
|
734
|
+
When troubleshooting middleware behavior (e.g., unexpected redirects or blocked requests), enable verbose mode to see every decision the gem makes:
|
|
735
|
+
|
|
736
|
+
```ruby
|
|
737
|
+
# config/initializers/reputable.rb
|
|
738
|
+
Reputable.configure do |config|
|
|
739
|
+
config.verbose = true
|
|
740
|
+
# ...
|
|
741
|
+
end
|
|
742
|
+
Reputable.logger = Rails.logger
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Or via environment variable:
|
|
746
|
+
|
|
747
|
+
```bash
|
|
748
|
+
REPUTABLE_VERBOSE=true
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
When enabled, the middleware logs at `info` level with a `[Reputable]` prefix. Example output for a challenged request:
|
|
752
|
+
|
|
753
|
+
```
|
|
754
|
+
[Reputable] request: GET / ip=68.6.118.12 rid=abc-123
|
|
755
|
+
[Reputable] state: enabled=true operational=true gate=true track=false
|
|
756
|
+
[Reputable] extract_ip: 68.6.118.12 (from HTTP_X_FORWARDED_FOR: 68.6.118.12, 10.0.0.1)
|
|
757
|
+
[Reputable] lookup: reputation:ip:68.6.118.12 → status=untrusted_challenge reason=inherited:asn:7922 source=inherited
|
|
758
|
+
[Reputable] gate: ip=68.6.118.12 status="untrusted_challenge"
|
|
759
|
+
[Reputable] verified_session?: false (reputable_verified_at= reputable_verified=)
|
|
760
|
+
[Reputable] gate: CHALLENGING ip=68.6.118.12 action=verify
|
|
761
|
+
[Reputable] challenge: REDIRECTING 302 → https://api.reputable.click/_reputable/verify?token=...
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
When verbose is `false` (the default), none of these logs are emitted.
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
722
768
|
## Tags for Classification
|
|
723
769
|
|
|
724
770
|
Use tags to classify requests for behavioral analysis:
|
|
@@ -15,7 +15,8 @@ module Reputable
|
|
|
15
15
|
:connect_timeout, :read_timeout, :write_timeout,
|
|
16
16
|
:ssl_params, :trusted_proxies, :ip_header_priority,
|
|
17
17
|
:on_error, :trusted_keys, :base_url,
|
|
18
|
-
:site_name, :support_email, :support_url
|
|
18
|
+
:site_name, :support_email, :support_url,
|
|
19
|
+
:verbose
|
|
19
20
|
|
|
20
21
|
# Alias for backward compatibility
|
|
21
22
|
alias_method :verification_base_url, :base_url
|
|
@@ -82,6 +83,7 @@ module Reputable
|
|
|
82
83
|
@site_name = ENV["REPUTABLE_SITE_NAME"]
|
|
83
84
|
@support_email = ENV["REPUTABLE_SUPPORT_EMAIL"]
|
|
84
85
|
@support_url = ENV["REPUTABLE_SUPPORT_URL"]
|
|
86
|
+
@verbose = %w[1 true yes on].include?(ENV.fetch("REPUTABLE_VERBOSE", "").downcase)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
# Alias for backward compatibility
|
data/lib/reputable/middleware.rb
CHANGED
|
@@ -67,6 +67,10 @@ module Reputable
|
|
|
67
67
|
# This ID is exposed to views so it can be included in the JS snippet
|
|
68
68
|
env['reputable.request_id'] = SecureRandom.uuid
|
|
69
69
|
|
|
70
|
+
ip = extract_ip(env)
|
|
71
|
+
env["reputable.ip"] = ip
|
|
72
|
+
path = env["PATH_INFO"] || "/"
|
|
73
|
+
|
|
70
74
|
# Check for verification return parameters and verify signature if present
|
|
71
75
|
handle_verification_return(env)
|
|
72
76
|
|
|
@@ -90,6 +94,12 @@ module Reputable
|
|
|
90
94
|
# All tracking is wrapped in rescue to ensure it never fails
|
|
91
95
|
safe_track_request(env)
|
|
92
96
|
|
|
97
|
+
# Single summary line for requests that passed through without intervention
|
|
98
|
+
if Reputable.verbose?
|
|
99
|
+
rep_status = env["reputable.reputation_status"]
|
|
100
|
+
Reputable.logger&.info("[Reputable] #{env['REQUEST_METHOD']} #{path} ip=#{ip} status=#{rep_status || 'unknown'} gate=#{@reputation_gate} result=pass")
|
|
101
|
+
end
|
|
102
|
+
|
|
93
103
|
[status, headers, response]
|
|
94
104
|
end
|
|
95
105
|
|
|
@@ -98,25 +108,34 @@ module Reputable
|
|
|
98
108
|
def safe_reputation_gate(env)
|
|
99
109
|
return nil unless @reputation_gate
|
|
100
110
|
return nil unless Reputable.enabled?
|
|
101
|
-
|
|
111
|
+
unless Reputable.operational?
|
|
112
|
+
Reputable.logger&.warn("[Reputable] gate: skipped (not operational, circuit_breaker=#{Connection.circuit_open? ? 'open' : 'closed'})") if Reputable.verbose?
|
|
113
|
+
return nil
|
|
114
|
+
end
|
|
102
115
|
return nil if skip_request?(env)
|
|
103
116
|
|
|
104
117
|
enforce_reputation_gate(env)
|
|
105
118
|
rescue StandardError => e
|
|
106
|
-
Reputable.logger&.
|
|
119
|
+
Reputable.logger&.warn("[Reputable] gate: exception #{e.class} - #{e.message}")
|
|
120
|
+
Reputable.logger&.debug(e.backtrace&.first(5)&.join("\n")) if Reputable.verbose?
|
|
107
121
|
nil
|
|
108
122
|
end
|
|
109
123
|
|
|
110
124
|
def enforce_reputation_gate(env)
|
|
111
125
|
status = reputation_status(env)
|
|
112
|
-
return nil if status.nil?
|
|
113
126
|
ip = env["reputable.ip"] || extract_ip(env)
|
|
114
127
|
|
|
128
|
+
return nil if status.nil?
|
|
129
|
+
|
|
115
130
|
case status
|
|
116
131
|
when "untrusted_block"
|
|
132
|
+
Reputable.logger&.info("[Reputable] gate: BLOCKING ip=#{ip} action=#{@block_action}")
|
|
117
133
|
handle_block_action(env, ip)
|
|
118
134
|
when "untrusted_challenge"
|
|
119
|
-
|
|
135
|
+
if verified_session?(env)
|
|
136
|
+
return nil
|
|
137
|
+
end
|
|
138
|
+
Reputable.logger&.info("[Reputable] gate: CHALLENGING ip=#{ip} action=#{@challenge_action}")
|
|
120
139
|
handle_challenge_action(env)
|
|
121
140
|
else
|
|
122
141
|
nil
|
|
@@ -160,15 +179,19 @@ module Reputable
|
|
|
160
179
|
|
|
161
180
|
# Determine status from new format or legacy format
|
|
162
181
|
status = if params["reputable_r"]
|
|
163
|
-
|
|
164
|
-
decoded&.dig("status")
|
|
182
|
+
Reputable.decode_reputable_response(params)&.dig("status")
|
|
165
183
|
else
|
|
166
184
|
params["reputable_status"]
|
|
167
185
|
end
|
|
168
186
|
|
|
169
|
-
|
|
187
|
+
unless status == "pass"
|
|
188
|
+
Reputable.logger&.info("[Reputable] verification: status=#{status.inspect} (not 'pass'), ignoring") if Reputable.verbose?
|
|
189
|
+
return
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
sig_valid = Reputable.verify_redirect_return(params)
|
|
170
193
|
|
|
171
|
-
if
|
|
194
|
+
if sig_valid
|
|
172
195
|
env["reputable.verified"] = true
|
|
173
196
|
|
|
174
197
|
# Extract ignore_analytics from new format or legacy format
|
|
@@ -182,15 +205,20 @@ module Reputable
|
|
|
182
205
|
unless ignore_analytics.nil?
|
|
183
206
|
env["reputable.ignore_analytics"] = ignore_analytics == true || ignore_analytics.to_s == "true"
|
|
184
207
|
end
|
|
185
|
-
|
|
208
|
+
|
|
186
209
|
# Store in session if available
|
|
187
210
|
if env["rack.session"]
|
|
188
211
|
env["rack.session"]["reputable_verified"] = true
|
|
189
212
|
env["rack.session"]["reputable_verified_at"] = Time.now.to_i
|
|
190
213
|
end
|
|
214
|
+
|
|
215
|
+
Reputable.logger&.info("[Reputable] verification: pass, stored in session") if Reputable.verbose?
|
|
216
|
+
else
|
|
217
|
+
Reputable.logger&.warn("[Reputable] verification: INVALID signature, rejecting")
|
|
191
218
|
end
|
|
192
219
|
rescue StandardError => e
|
|
193
|
-
Reputable.logger&.
|
|
220
|
+
Reputable.logger&.warn("[Reputable] verification: exception #{e.class} - #{e.message}")
|
|
221
|
+
Reputable.logger&.debug(e.backtrace&.first(5)&.join("\n")) if Reputable.verbose?
|
|
194
222
|
end
|
|
195
223
|
|
|
196
224
|
def handle_blocked_page_request(env)
|
|
@@ -212,6 +240,7 @@ module Reputable
|
|
|
212
240
|
build_blocked_page_response(ip)
|
|
213
241
|
when :blocked_page_remote
|
|
214
242
|
url = resolve_blocked_redirect_url(env) || Reputable.blocked_page_url
|
|
243
|
+
Reputable.logger&.info("[Reputable] block: REDIRECTING #{@blocked_redirect_status} → #{url}")
|
|
215
244
|
redirect_response(url, @blocked_redirect_status)
|
|
216
245
|
when :forbidden
|
|
217
246
|
[403, { "Content-Type" => "text/plain; charset=utf-8" }, ["Forbidden"]]
|
|
@@ -226,7 +255,10 @@ module Reputable
|
|
|
226
255
|
case @challenge_action
|
|
227
256
|
when :verify
|
|
228
257
|
keys = Reputable.configuration.trusted_keys
|
|
229
|
-
|
|
258
|
+
if keys.nil? || keys.empty?
|
|
259
|
+
Reputable.logger&.warn("[Reputable] challenge: no trusted_keys configured, cannot redirect to verification")
|
|
260
|
+
return nil
|
|
261
|
+
end
|
|
230
262
|
|
|
231
263
|
request = Rack::Request.new(env)
|
|
232
264
|
return_url = request.url
|
|
@@ -242,6 +274,7 @@ module Reputable
|
|
|
242
274
|
|
|
243
275
|
return nil if redirect_url == return_url
|
|
244
276
|
|
|
277
|
+
Reputable.logger&.info("[Reputable] challenge: REDIRECTING #{@challenge_redirect_status} → #{redirect_url}")
|
|
245
278
|
redirect_response(redirect_url, @challenge_redirect_status)
|
|
246
279
|
when Proc
|
|
247
280
|
@challenge_action.call(env)
|
|
@@ -408,12 +441,12 @@ module Reputable
|
|
|
408
441
|
|
|
409
442
|
def extract_ip(env)
|
|
410
443
|
config = Reputable.configuration
|
|
411
|
-
|
|
444
|
+
|
|
412
445
|
# Try each header in priority order
|
|
413
446
|
config.ip_header_priority.each do |header|
|
|
414
447
|
value = env[header]
|
|
415
448
|
next if value.nil? || value.empty?
|
|
416
|
-
|
|
449
|
+
|
|
417
450
|
# X-Forwarded-For and Forwarded can contain multiple IPs
|
|
418
451
|
if header == "HTTP_X_FORWARDED_FOR"
|
|
419
452
|
ip = extract_from_xff(value)
|
|
@@ -428,7 +461,6 @@ module Reputable
|
|
|
428
461
|
end
|
|
429
462
|
end
|
|
430
463
|
|
|
431
|
-
# Ultimate fallback
|
|
432
464
|
"0.0.0.0"
|
|
433
465
|
rescue StandardError
|
|
434
466
|
"0.0.0.0"
|
data/lib/reputable/reputation.rb
CHANGED
|
@@ -138,15 +138,19 @@ module Reputable
|
|
|
138
138
|
redis.hgetall(key)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
if data.nil? || data.empty?
|
|
142
|
+
Reputable.logger&.debug("[Reputable] lookup: #{key} → (none)") if Reputable.verbose?
|
|
143
|
+
return nil
|
|
144
|
+
end
|
|
142
145
|
|
|
143
146
|
# Check if expired (Redis TTL should handle this, but double-check)
|
|
144
147
|
expires_at = data["expires_at"].to_i
|
|
145
148
|
if expires_at > 0 && expires_at < (Time.now.to_f * 1000).to_i
|
|
149
|
+
Reputable.logger&.debug("[Reputable] lookup: #{key} → expired") if Reputable.verbose?
|
|
146
150
|
return nil
|
|
147
151
|
end
|
|
148
152
|
|
|
149
|
-
{
|
|
153
|
+
result = {
|
|
150
154
|
status: data["status"],
|
|
151
155
|
reason: data["reason"],
|
|
152
156
|
source: data["source"],
|
|
@@ -154,8 +158,11 @@ module Reputable
|
|
|
154
158
|
expires_at: expires_at,
|
|
155
159
|
metadata: safe_parse_json(data["metadata"])
|
|
156
160
|
}
|
|
161
|
+
|
|
162
|
+
Reputable.logger&.info("[Reputable] lookup: #{key} → status=#{result[:status]} reason=#{result[:reason]} source=#{result[:source]}") if Reputable.verbose?
|
|
163
|
+
result
|
|
157
164
|
rescue StandardError => e
|
|
158
|
-
Reputable.logger&.
|
|
165
|
+
Reputable.logger&.warn("[Reputable] lookup: exception #{e.class} - #{e.message}")
|
|
159
166
|
nil
|
|
160
167
|
end
|
|
161
168
|
|
data/lib/reputable/version.rb
CHANGED
data/lib/reputable.rb
CHANGED
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.23
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Reputable
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|