reputable 0.1.16 → 0.1.18

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: d33fb1c68fdaca4082540ed61021d61ec5d687779c6c290635327dfe58a83ec7
4
- data.tar.gz: 8a217cc36dcc8eef21623025b2dca9b0ccd697b1a312fd46bcfa814c7958e189
3
+ metadata.gz: 40be87edb5494144f499f263572e00d98331cd07913239fca32b5ce71450df26
4
+ data.tar.gz: 63cfe644b94396a6d72b05a75a45076e8bc41196f6c4970307992e6260d6f86f
5
5
  SHA512:
6
- metadata.gz: d9470847736221d9aa4b0c0012487f18b32ed15bba2f22d5e85bbcd3f376263aa66899b8e14eda2058b0aa0e52e1c659845a516dee09709f379d9ef660cd2ed4
7
- data.tar.gz: 879cc235f75b50f054db621572dbd2dfb58020102bc784d14fed109b921bfa82a4f91f0fe4fff7c7c3f2811ff0f292d5638ab2864323bcf93ddbcd18ecf18a3c
6
+ metadata.gz: ed8aee9a76c98611718621fde0c1dada4aff7449092c9fc6cd1990d545b91ee1641c169d841eea12b2b0eeb56edc399e2f8db612041565a2d90dbf76bad34d92
7
+ data.tar.gz: 5b2eeb4fb8ddbf12beeb989b0a33aa5f42352c6a131ca1211ad5475b19978ba8244dbf52504555256aa0d79eb58935ab23be05cd503405d11d35b835bca506c0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reputable (0.1.15)
4
+ reputable (0.1.18)
5
5
  connection_pool (~> 2.2)
6
6
  redis (>= 4.0, < 6.0)
7
7
 
data/README.md CHANGED
@@ -275,6 +275,10 @@ config.middleware.use Reputable::Middleware,
275
275
  # Falls back to ENV REPUTABLE_TRACK_REQUEST if not set
276
276
  track_request: true,
277
277
 
278
+ # Ignore XHR requests (default: false)
279
+ # Useful for suppressing tracking of background widgets or polling
280
+ ignore_xhr: true,
281
+
278
282
  # Expose reputation flags in request env for views/controllers (default: true)
279
283
  # Sets request.env['reputable.ignore_analytics'] for any untrusted_* status
280
284
  expose_reputation: true
@@ -301,6 +305,44 @@ Notes:
301
305
  - Use `blocked_page_path` only for local blocked pages (or to build a custom `failure_url`).
302
306
  - Override `challenge_redirect_status` (default `302`) or `verification_force_challenge` if needed.
303
307
 
308
+ ### ASN Fallback
309
+
310
+ When an IP has no reputation, the middleware can fall back to checking ASN reputation. This is useful for blocking/challenging entire ASNs (e.g., datacenter ASNs known for abuse).
311
+
312
+ **Enable via environment variable:**
313
+ ```bash
314
+ REPUTABLE_ASN_FALLBACK=true
315
+ REPUTABLE_ASN_HEADER=HTTP_X_ASN # Optional, defaults to HTTP_X_ASN
316
+ ```
317
+
318
+ **Enable via configuration:**
319
+ ```ruby
320
+ Reputable.configure do |config|
321
+ config.asn_fallback = true
322
+ config.asn_header = "HTTP_X_ASN" # Or HTTP_CF_ASN for Cloudflare, etc.
323
+ end
324
+ ```
325
+
326
+ **Enable via middleware option:**
327
+ ```ruby
328
+ config.middleware.use Reputable::Middleware,
329
+ reputation_gate: true,
330
+ asn_fallback: true
331
+ ```
332
+
333
+ **How it works:**
334
+ 1. Middleware looks up IP reputation first
335
+ 2. If IP has no reputation and ASN fallback is enabled, it extracts ASN from the configured header
336
+ 3. If ASN has a reputation (blocked, challenged), that decision is applied
337
+ 4. The `env['reputable.reputation_source']` is set to `'asn'` when using ASN-based decision
338
+
339
+ **Providing ASN from your app:**
340
+ If your app has its own GeoIP lookup, set the ASN directly:
341
+ ```ruby
342
+ # In a before_action or middleware
343
+ request.env['reputable.asn'] = lookup_asn_for_ip(request.remote_ip)
344
+ ```
345
+
304
346
  ### Server/JS Request Reconciliation
305
347
 
306
348
  When using both server-side tracking (Rack middleware) and client-side JavaScript tracking, requests can be double-counted. The reconciliation system prevents this by correlating requests using a unique `request_id`.
@@ -497,6 +539,33 @@ Reputable.lookup_reputation(:ip, request.ip)
497
539
  # expires_at: 0, metadata: { order_id: "123" } }
498
540
  ```
499
541
 
542
+ ### ASN Reputation
543
+
544
+ Apply and lookup reputations for entire ASNs (Autonomous System Numbers). Useful for blocking datacenter traffic or known-bad networks.
545
+
546
+ ```ruby
547
+ # Apply reputation to an ASN
548
+ Reputable.block_asn("15169", reason: "datacenter_abuse")
549
+ Reputable.challenge_asn("7922", reason: "suspicious_traffic")
550
+ Reputable.trust_asn("16509", reason: "known_partner")
551
+ Reputable.ignore_asn("32934", reason: "internal_monitoring")
552
+
553
+ # Quick boolean checks
554
+ Reputable.blocked_asn?("15169") # => true/false
555
+ Reputable.challenged_asn?("7922") # => true/false
556
+ Reputable.trusted_asn?("16509") # => true/false
557
+
558
+ # Get status string
559
+ Reputable.lookup_asn("15169")
560
+ # => "untrusted_block" or nil
561
+
562
+ # Full lookup with metadata
563
+ Reputable.lookup_reputation(:asn, "15169")
564
+ # => { status: "untrusted_block", reason: "datacenter_abuse", ... }
565
+ ```
566
+
567
+ **Note:** ASNs are normalized automatically - both `"15169"` and `"AS15169"` work.
568
+
500
569
  ---
501
570
 
502
571
  ## User Verification & Trust Flow
@@ -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
+ :asn_fallback, :asn_header
19
20
 
20
21
  # Alias for backward compatibility
21
22
  alias_method :verification_base_url, :base_url
@@ -82,6 +83,10 @@ 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
+
87
+ # ASN fallback: when IP has no reputation, check ASN reputation
88
+ @asn_fallback = env_truthy?("REPUTABLE_ASN_FALLBACK")
89
+ @asn_header = ENV.fetch("REPUTABLE_ASN_HEADER", "HTTP_X_ASN")
85
90
  end
86
91
 
87
92
  # Alias for backward compatibility
@@ -158,5 +163,20 @@ module Reputable
158
163
  rescue IPAddr::InvalidAddressError
159
164
  false
160
165
  end
166
+
167
+ # Check if ASN fallback is enabled
168
+ def asn_fallback?
169
+ @asn_fallback
170
+ end
171
+
172
+ private
173
+
174
+ # Helper to check if an environment variable is truthy
175
+ def env_truthy?(name)
176
+ value = ENV[name]
177
+ return false if value.nil?
178
+
179
+ %w[1 true yes on enabled].include?(value.to_s.downcase)
180
+ end
161
181
  end
162
182
  end
@@ -59,6 +59,8 @@ module Reputable
59
59
  @verified_session_keys = Array(options.fetch(:verified_session_keys, [:reputable_verified_at, :reputable_verified]))
60
60
  @blocked_page_options = options.fetch(:blocked_page, {})
61
61
  @blocked_page_path = options[:blocked_page_path]
62
+ @ignore_xhr = options.fetch(:ignore_xhr, false)
63
+ @asn_fallback = options.key?(:asn_fallback) ? options[:asn_fallback] : nil
62
64
  end
63
65
 
64
66
  def call(env)
@@ -317,6 +319,17 @@ module Reputable
317
319
  ip = extract_ip(env)
318
320
  env["reputable.ip"] = ip
319
321
  status = Reputable::Reputation.lookup_ip(ip)
322
+
323
+ # Fallback to ASN reputation if IP has no status and ASN fallback is enabled
324
+ if status.nil? && asn_fallback_enabled?
325
+ asn = extract_asn(env)
326
+ if asn
327
+ env["reputable.asn"] = asn
328
+ status = Reputable::Reputation.lookup_asn(asn)
329
+ env["reputable.reputation_source"] = "asn" if status
330
+ end
331
+ end
332
+
320
333
  env["reputable.reputation_status"] = status
321
334
  env["reputable.ignore_analytics"] = status.to_s.start_with?("untrusted")
322
335
  status
@@ -326,6 +339,28 @@ module Reputable
326
339
  nil
327
340
  end
328
341
 
342
+ def asn_fallback_enabled?
343
+ # Middleware option takes precedence, then config
344
+ return @asn_fallback unless @asn_fallback.nil?
345
+
346
+ Reputable.configuration.asn_fallback?
347
+ end
348
+
349
+ def extract_asn(env)
350
+ # First check if app explicitly set it
351
+ return env["reputable.asn"] if env["reputable.asn"]
352
+
353
+ # Then check the configured header
354
+ header = Reputable.configuration.asn_header
355
+ value = env[header]
356
+ return nil if value.nil? || value.empty?
357
+
358
+ # Normalize: strip "AS" prefix if present
359
+ value.to_s.strip.sub(/^AS/i, "")
360
+ rescue StandardError
361
+ nil
362
+ end
363
+
329
364
  def blocked_page_options
330
365
  config = Reputable.configuration
331
366
  defaults = {
@@ -356,6 +391,7 @@ module Reputable
356
391
 
357
392
  def skip_request?(env)
358
393
  return true if @skip_if&.call(env)
394
+ return true if @ignore_xhr && xhr_request?(env)
359
395
 
360
396
  path = env["PATH_INFO"] || "/"
361
397
 
@@ -522,5 +558,9 @@ module Reputable
522
558
  rescue StandardError
523
559
  nil
524
560
  end
561
+
562
+ def xhr_request?(env)
563
+ env["HTTP_X_REQUESTED_WITH"].to_s.downcase == "xmlhttprequest"
564
+ end
525
565
  end
526
566
  end
@@ -205,8 +205,113 @@ module Reputable
205
205
  lookup_ip(ip) == "untrusted_challenge"
206
206
  end
207
207
 
208
+ # ========================================================================
209
+ # ASN LOOKUP METHODS
210
+ # ========================================================================
211
+
212
+ # Quick lookup for ASN reputation status
213
+ # Returns just the status string (or nil)
214
+ # ASN is normalized (strips "AS" prefix if present)
215
+ #
216
+ # @param asn [String] ASN (e.g., "15169" or "AS15169")
217
+ # @return [String, nil] Status string or nil
218
+ #
219
+ # @example
220
+ # status = Reputable::Reputation.lookup_asn("15169")
221
+ # # => "untrusted_block" or nil
222
+ def lookup_asn(asn)
223
+ result = lookup(:asn, normalize_asn(asn))
224
+ result&.dig(:status)
225
+ end
226
+
227
+ # Check if an ASN is trusted (any trusted_* status)
228
+ #
229
+ # @param asn [String] ASN
230
+ # @return [Boolean] Returns false if disabled, nil lookup, or not trusted
231
+ def trusted_asn?(asn)
232
+ status = lookup_asn(asn)
233
+ return false if status.nil?
234
+
235
+ status.start_with?("trusted")
236
+ end
237
+
238
+ # Check if an ASN should be blocked
239
+ #
240
+ # @param asn [String] ASN
241
+ # @return [Boolean]
242
+ def blocked_asn?(asn)
243
+ lookup_asn(asn) == "untrusted_block"
244
+ end
245
+
246
+ # Check if an ASN should be challenged
247
+ #
248
+ # @param asn [String] ASN
249
+ # @return [Boolean]
250
+ def challenged_asn?(asn)
251
+ lookup_asn(asn) == "untrusted_challenge"
252
+ end
253
+
254
+ # ========================================================================
255
+ # ASN APPLY CONVENIENCE METHODS
256
+ # ========================================================================
257
+
258
+ # Convenience method: Trust an ASN (behavioral by default)
259
+ def trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata)
260
+ apply(
261
+ entity_type: :asn,
262
+ entity_id: normalize_asn(asn),
263
+ status: status,
264
+ reason: reason,
265
+ ttl: ttl,
266
+ metadata: metadata
267
+ )
268
+ end
269
+
270
+ # Convenience method: Block an ASN
271
+ def block_asn(asn, reason: "manual_block", ttl: nil, **metadata)
272
+ apply(
273
+ entity_type: :asn,
274
+ entity_id: normalize_asn(asn),
275
+ status: :untrusted_block,
276
+ reason: reason,
277
+ ttl: ttl,
278
+ metadata: metadata
279
+ )
280
+ end
281
+
282
+ # Convenience method: Challenge an ASN (CAPTCHA, etc.)
283
+ def challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **metadata)
284
+ apply(
285
+ entity_type: :asn,
286
+ entity_id: normalize_asn(asn),
287
+ status: :untrusted_challenge,
288
+ reason: reason,
289
+ ttl: ttl,
290
+ metadata: metadata
291
+ )
292
+ end
293
+
294
+ # Convenience method: Mark ASN to be ignored in analytics
295
+ def ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **metadata)
296
+ apply(
297
+ entity_type: :asn,
298
+ entity_id: normalize_asn(asn),
299
+ status: :untrusted_ignore,
300
+ reason: reason,
301
+ ttl: ttl,
302
+ metadata: metadata
303
+ )
304
+ end
305
+
208
306
  private
209
307
 
308
+ # Normalize ASN by stripping "AS" prefix if present
309
+ # @param asn [String] ASN with or without prefix
310
+ # @return [String] ASN without prefix
311
+ def normalize_asn(asn)
312
+ asn.to_s.sub(/^AS/i, "")
313
+ end
314
+
210
315
  def valid_entity_type?(entity_type)
211
316
  VALID_ENTITY_TYPES.include?(entity_type.to_sym)
212
317
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reputable
4
- VERSION = "0.1.16"
4
+ VERSION = "0.1.18"
5
5
  end
data/lib/reputable.rb CHANGED
@@ -126,6 +126,39 @@ module Reputable
126
126
  Reputation.challenged_ip?(ip)
127
127
  end
128
128
 
129
+ # Delegate ASN reputation methods to Reputation module
130
+ def lookup_asn(asn)
131
+ Reputation.lookup_asn(asn)
132
+ end
133
+
134
+ def trusted_asn?(asn)
135
+ Reputation.trusted_asn?(asn)
136
+ end
137
+
138
+ def blocked_asn?(asn)
139
+ Reputation.blocked_asn?(asn)
140
+ end
141
+
142
+ def challenged_asn?(asn)
143
+ Reputation.challenged_asn?(asn)
144
+ end
145
+
146
+ def trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata)
147
+ Reputation.trust_asn(asn, reason: reason, status: status, ttl: ttl, **metadata)
148
+ end
149
+
150
+ def block_asn(asn, reason: "manual_block", ttl: nil, **metadata)
151
+ Reputation.block_asn(asn, reason: reason, ttl: ttl, **metadata)
152
+ end
153
+
154
+ def challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **metadata)
155
+ Reputation.challenge_asn(asn, reason: reason, ttl: ttl, **metadata)
156
+ end
157
+
158
+ def ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **metadata)
159
+ Reputation.ignore_asn(asn, reason: reason, ttl: ttl, **metadata)
160
+ end
161
+
129
162
  # Generate a signed verification URL
130
163
  # @param return_url [String] URL to redirect to after successful verification
131
164
  # @param failure_url [String, nil] URL to redirect to on failure (optional)
data/reputable.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/reputable/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "reputable"
7
+ spec.version = Reputable::VERSION
8
+ spec.authors = ["Reputable"]
9
+ spec.email = ["support@reputable.click"]
10
+
11
+ spec.summary = "Ruby client for Reputable - bot detection and reputation scoring"
12
+ spec.description = "Track requests and manage IP reputation through Redis/Dragonfly integration with Reputable"
13
+ spec.homepage = "https://github.com/reputable-click/reputable-rb"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) || f.end_with?(".gem")
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "redis", ">= 4.0", "< 6.0"
31
+ spec.add_dependency "connection_pool", "~> 2.2"
32
+
33
+ spec.add_development_dependency "bundler", "~> 2.0"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "rspec", "~> 3.12"
36
+ spec.add_development_dependency "rack", "~> 2.0"
37
+ spec.add_development_dependency "rubocop", "~> 1.0"
38
+ 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.16
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reputable
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-29 00:00:00.000000000 Z
11
+ date: 2026-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -137,6 +137,7 @@ files:
137
137
  - lib/reputable/reputation.rb
138
138
  - lib/reputable/tracker.rb
139
139
  - lib/reputable/version.rb
140
+ - reputable.gemspec
140
141
  homepage: https://github.com/reputable-click/reputable-rb
141
142
  licenses:
142
143
  - MIT