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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbabd6630a3e0d348391e2795ea58cd8a858944101b0ac8a0dfaa04277b16035
4
- data.tar.gz: 3939cf81a472b76d4084d6236a39242d7a693e9f2434df576ac73bb153e8c5d0
3
+ metadata.gz: 1f667ca542120eb294a6c61a0650a1c1deb82b5383c87e8d744c98c9fba7548a
4
+ data.tar.gz: d061fbe002a8ae3c96bbbf45b7470f5df4bdac89f49bcb36f42ab1e676ee5699
5
5
  SHA512:
6
- metadata.gz: 4e868fd7037ffbecfda2be3103486940210bae975619d51d9c9f68521c0a9d7ea36bf7ebbea7ae5da8c90e19158fc11bbe82762b65c00d216d03d1342eb78d2a
7
- data.tar.gz: f664f98a97054be9f2f9ddc89611f2fb68028b641eb47057534633dec2291939f770a792bc0b0506d7ce2297f0c5648e36d885315490810b338fe6dee1c6ca34
6
+ metadata.gz: 0f47bb9c880009d89a36d71ef0021edd07de2b87fb38ab5625625b89d7ad0240391fbc5ff4e5630e12bc7fafc4290a9649aec9685c25cfca5c28546e8e9699f6
7
+ data.tar.gz: 7223bdf677132850ccb4ef226c8540dad07fba22853c9461873a696b3c16f52a65fc06eba1757aeb121607470baea291e027db7364a19ee9c505a92bbec1b49d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reputable (0.1.20)
4
+ reputable (0.1.22)
5
5
  connection_pool (~> 2.2)
6
6
  redis (>= 4.0, < 6.0)
7
7
 
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: Enable logging (logs at debug level)
34
- Reputable.logger = Rails.logger
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
@@ -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
- return nil unless Reputable.operational?
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&.debug("Reputable reputation gate: #{e.class} - #{e.message}")
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
- return nil if verified_session?(env)
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
- decoded = Reputable.decode_reputable_response(params)
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
- return unless status == "pass"
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 Reputable.verify_redirect_return(params)
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&.debug("Reputable verification error: #{e.message}")
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
- return nil if keys.nil? || keys.empty?
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"
@@ -138,15 +138,19 @@ module Reputable
138
138
  redis.hgetall(key)
139
139
  end
140
140
 
141
- return nil if data.nil? || data.empty?
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&.debug("Reputable lookup error: #{e.class} - #{e.message}")
165
+ Reputable.logger&.warn("[Reputable] lookup: exception #{e.class} - #{e.message}")
159
166
  nil
160
167
  end
161
168
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reputable
4
- VERSION = "0.1.21"
4
+ VERSION = "0.1.23"
5
5
  end
data/lib/reputable.rb CHANGED
@@ -69,6 +69,12 @@ module Reputable
69
69
  configuration.enabled?
70
70
  end
71
71
 
72
+ # Check if verbose logging is enabled
73
+ # @return [Boolean]
74
+ def verbose?
75
+ configuration.verbose
76
+ end
77
+
72
78
  # Check if Reputable is disabled
73
79
  # @return [Boolean]
74
80
  def disabled?
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.21
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-07 00:00:00.000000000 Z
11
+ date: 2026-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis