reputable 0.1.18 → 0.1.20
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 +52 -38
- data/lib/reputable/configuration.rb +1 -21
- data/lib/reputable/middleware.rb +1 -34
- data/lib/reputable/rails.rb +6 -0
- data/lib/reputable/version.rb +1 -1
- data/lib/reputable.rb +22 -0
- metadata +2 -3
- data/reputable.gemspec +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6060e2e9fd11f70f0722c0f9683c1fe334f75aced2791dfde4a6336b5f08b429
|
|
4
|
+
data.tar.gz: 0c92fbbcc06ec6473e4af1c64b87003c92555c2137643d021b149aa3416a0acc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1fee3bf3e197dbad2377296119ef87d43657f37a008982af4c14b15f39fcff0c59c13054a8d132b5c568cd31f695183e58b54ab34affac94a342893de7ace262
|
|
7
|
+
data.tar.gz: 43a20b7d26fcc4a0464e0c29c15be6cf25874335ea841c4b77c704dca2f4b4fdf94d42cd60ec2638ea29c3cbfdefef1bdf5bd161b22c907275853866e83cf703
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -305,44 +305,6 @@ Notes:
|
|
|
305
305
|
- Use `blocked_page_path` only for local blocked pages (or to build a custom `failure_url`).
|
|
306
306
|
- Override `challenge_redirect_status` (default `302`) or `verification_force_challenge` if needed.
|
|
307
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
|
-
|
|
346
308
|
### Server/JS Request Reconciliation
|
|
347
309
|
|
|
348
310
|
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`.
|
|
@@ -566,6 +528,58 @@ Reputable.lookup_reputation(:asn, "15169")
|
|
|
566
528
|
|
|
567
529
|
**Note:** ASNs are normalized automatically - both `"15169"` and `"AS15169"` work.
|
|
568
530
|
|
|
531
|
+
### Enforcing ASN Reputation in Controllers
|
|
532
|
+
|
|
533
|
+
If you want to challenge or block requests based on ASN reputation (independent of the middleware's IP-based gate), add a controller filter:
|
|
534
|
+
|
|
535
|
+
```ruby
|
|
536
|
+
# app/controllers/application_controller.rb
|
|
537
|
+
class ApplicationController < ActionController::Base
|
|
538
|
+
before_action :enforce_asn_reputation
|
|
539
|
+
|
|
540
|
+
private
|
|
541
|
+
|
|
542
|
+
def enforce_asn_reputation
|
|
543
|
+
return if reputable_verified?
|
|
544
|
+
|
|
545
|
+
asn = request_asn
|
|
546
|
+
return unless asn
|
|
547
|
+
|
|
548
|
+
if Reputable.challenged_asn?(asn)
|
|
549
|
+
redirect_to Reputable.verification_url(
|
|
550
|
+
return_url: request.original_url,
|
|
551
|
+
session_id: session.id.to_s
|
|
552
|
+
), status: 302, allow_other_host: true
|
|
553
|
+
elsif Reputable.blocked_asn?(asn)
|
|
554
|
+
redirect_to Reputable.blocked_page_url, status: 302, allow_other_host: true
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def reputable_verified?
|
|
559
|
+
session[:reputable_verified] || session[:reputable_verified_at].present?
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def request_asn
|
|
563
|
+
# Option 1: From a header (if HAProxy/Cloudflare provides it)
|
|
564
|
+
# request.headers["X-ASN"]&.sub(/^AS/i, "")
|
|
565
|
+
|
|
566
|
+
# Option 2: From your own GeoIP lookup
|
|
567
|
+
# GeoIP.lookup(request.remote_ip)&.asn
|
|
568
|
+
|
|
569
|
+
# Option 3: Stored in request.env from earlier middleware
|
|
570
|
+
# request.env["app.asn"]
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
To apply only to specific controllers or actions:
|
|
576
|
+
|
|
577
|
+
```ruby
|
|
578
|
+
class CheckoutController < ApplicationController
|
|
579
|
+
before_action :enforce_asn_reputation, only: [:create]
|
|
580
|
+
end
|
|
581
|
+
```
|
|
582
|
+
|
|
569
583
|
---
|
|
570
584
|
|
|
571
585
|
## User Verification & Trust Flow
|
|
@@ -15,8 +15,7 @@ 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
|
|
19
|
-
:asn_fallback, :asn_header
|
|
18
|
+
:site_name, :support_email, :support_url
|
|
20
19
|
|
|
21
20
|
# Alias for backward compatibility
|
|
22
21
|
alias_method :verification_base_url, :base_url
|
|
@@ -83,10 +82,6 @@ module Reputable
|
|
|
83
82
|
@site_name = ENV["REPUTABLE_SITE_NAME"]
|
|
84
83
|
@support_email = ENV["REPUTABLE_SUPPORT_EMAIL"]
|
|
85
84
|
@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")
|
|
90
85
|
end
|
|
91
86
|
|
|
92
87
|
# Alias for backward compatibility
|
|
@@ -163,20 +158,5 @@ module Reputable
|
|
|
163
158
|
rescue IPAddr::InvalidAddressError
|
|
164
159
|
false
|
|
165
160
|
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
|
|
181
161
|
end
|
|
182
162
|
end
|
data/lib/reputable/middleware.rb
CHANGED
|
@@ -60,7 +60,6 @@ module Reputable
|
|
|
60
60
|
@blocked_page_options = options.fetch(:blocked_page, {})
|
|
61
61
|
@blocked_page_path = options[:blocked_page_path]
|
|
62
62
|
@ignore_xhr = options.fetch(:ignore_xhr, false)
|
|
63
|
-
@asn_fallback = options.key?(:asn_fallback) ? options[:asn_fallback] : nil
|
|
64
63
|
end
|
|
65
64
|
|
|
66
65
|
def call(env)
|
|
@@ -99,6 +98,7 @@ module Reputable
|
|
|
99
98
|
def safe_reputation_gate(env)
|
|
100
99
|
return nil unless @reputation_gate
|
|
101
100
|
return nil unless Reputable.enabled?
|
|
101
|
+
return nil unless Reputable.operational?
|
|
102
102
|
return nil if skip_request?(env)
|
|
103
103
|
|
|
104
104
|
enforce_reputation_gate(env)
|
|
@@ -319,17 +319,6 @@ module Reputable
|
|
|
319
319
|
ip = extract_ip(env)
|
|
320
320
|
env["reputable.ip"] = ip
|
|
321
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
|
-
|
|
333
322
|
env["reputable.reputation_status"] = status
|
|
334
323
|
env["reputable.ignore_analytics"] = status.to_s.start_with?("untrusted")
|
|
335
324
|
status
|
|
@@ -339,28 +328,6 @@ module Reputable
|
|
|
339
328
|
nil
|
|
340
329
|
end
|
|
341
330
|
|
|
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
|
-
|
|
364
331
|
def blocked_page_options
|
|
365
332
|
config = Reputable.configuration
|
|
366
333
|
defaults = {
|
data/lib/reputable/rails.rb
CHANGED
|
@@ -159,6 +159,9 @@ module Reputable
|
|
|
159
159
|
)
|
|
160
160
|
return if reputable_verified?(session_key: session_key)
|
|
161
161
|
|
|
162
|
+
# Skip redirect when circuit is open — the Reputable server is likely unreachable
|
|
163
|
+
return unless Reputable.operational?
|
|
164
|
+
|
|
162
165
|
# Check for new format (reputable_s) or legacy format (reputable_signature)
|
|
163
166
|
if params[:reputable_s] || params[:reputable_signature]
|
|
164
167
|
if Reputable.verify_redirect_return(params)
|
|
@@ -207,6 +210,9 @@ module Reputable
|
|
|
207
210
|
blocked_message: nil,
|
|
208
211
|
blocked_show_ip: true
|
|
209
212
|
)
|
|
213
|
+
# Skip all gating when circuit is open — the Reputable server is likely unreachable
|
|
214
|
+
return unless Reputable.operational?
|
|
215
|
+
|
|
210
216
|
if current_ip_blocked?
|
|
211
217
|
render_reputable_blocked_page(
|
|
212
218
|
status: blocked_status,
|
data/lib/reputable/version.rb
CHANGED
data/lib/reputable.rb
CHANGED
|
@@ -75,6 +75,28 @@ module Reputable
|
|
|
75
75
|
!enabled?
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# Check if Reputable is operational (enabled AND circuit breaker is closed)
|
|
79
|
+
# Use this to guard redirect/gating decisions — when the circuit is open,
|
|
80
|
+
# the Reputable server is likely unreachable and redirects would fail.
|
|
81
|
+
# @return [Boolean] true if Reputable can be used, false otherwise
|
|
82
|
+
def operational?
|
|
83
|
+
return false unless enabled?
|
|
84
|
+
return false if Connection.circuit_open?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get a detailed status report for monitoring/debugging
|
|
89
|
+
# @return [Hash] status information
|
|
90
|
+
def status
|
|
91
|
+
{
|
|
92
|
+
enabled: enabled?,
|
|
93
|
+
operational: operational?,
|
|
94
|
+
circuit_breaker: Connection.circuit_open? ? "open" : "closed",
|
|
95
|
+
failure_count: Connection.failure_count,
|
|
96
|
+
circuit_opened_at: Connection.circuit_opened_at
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
78
100
|
# Delegate tracking methods to Tracker
|
|
79
101
|
def track_request(ip:, path:, **options)
|
|
80
102
|
Tracker.track_request(ip: ip, path: path, **options)
|
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.20
|
|
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-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|
|
@@ -137,7 +137,6 @@ files:
|
|
|
137
137
|
- lib/reputable/reputation.rb
|
|
138
138
|
- lib/reputable/tracker.rb
|
|
139
139
|
- lib/reputable/version.rb
|
|
140
|
-
- reputable.gemspec
|
|
141
140
|
homepage: https://github.com/reputable-click/reputable-rb
|
|
142
141
|
licenses:
|
|
143
142
|
- MIT
|
data/reputable.gemspec
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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
|