reputable 0.1.7 → 0.1.9
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 +84 -1
- data/lib/reputable/configuration.rb +5 -1
- data/lib/reputable/middleware.rb +190 -0
- data/lib/reputable/rails.rb +137 -0
- data/lib/reputable/version.rb +1 -1
- data/lib/reputable.rb +10 -0
- data/reputable.gemspec +38 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8d9854d3f0dfbf85029fa9411273ffdebad9288eaa11b91649a3aec538b6b2b
|
|
4
|
+
data.tar.gz: 8d7fbfaf636c06c80a1ea291a773d35c1e478f5dd1261efd6e213a473f1d0362
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a13fdb389753a6aed4bde2d9e6bc2304e184db0ded7a579adc6d7fef16d4e1e72a6032f11eec7e6e03e0e706aa2a135c07c17e3e607e227f7670864e84c91aea
|
|
7
|
+
data.tar.gz: fb215dbd673959e3414eaa31c0a77a3c78366db796f34b1afbef12d066041b1308b0c6312a90dc7ee953a8b578083192509a535fb3bf9b53f265f9f3b9a22f80
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Ruby gem for integrating with Reputable - bot detection and reputation scoring f
|
|
|
4
4
|
|
|
5
5
|
**Resilience First**: This gem is designed to never break your application. All operations fail silently with safe defaults.
|
|
6
6
|
|
|
7
|
+
Release notes and version bumps: see `clients/ruby/RELEASING.md`.
|
|
8
|
+
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
9
11
|
Add to your Gemfile:
|
|
@@ -83,6 +85,11 @@ REPUTABLE_REDIS_URL=rediss://user:password@your-dragonfly.example.com:6379
|
|
|
83
85
|
# Optional: Base URL for verification and API endpoints (domain only)
|
|
84
86
|
REPUTABLE_BASE_URL=https://api.reputable.click
|
|
85
87
|
|
|
88
|
+
# Optional: Blocked page branding/support info
|
|
89
|
+
REPUTABLE_SITE_NAME="Example Store"
|
|
90
|
+
REPUTABLE_SUPPORT_EMAIL=support@example.com
|
|
91
|
+
REPUTABLE_SUPPORT_URL=https://example.com/support
|
|
92
|
+
|
|
86
93
|
# Optional: Disable entirely (useful for test environments)
|
|
87
94
|
REPUTABLE_ENABLED=false
|
|
88
95
|
|
|
@@ -142,6 +149,11 @@ Reputable.configure do |config|
|
|
|
142
149
|
# Supports comma-separated list in REPUTABLE_TRUSTED_KEYS or single key in REPUTABLE_TRUSTED_KEY
|
|
143
150
|
config.trusted_keys = ENV['REPUTABLE_TRUSTED_KEYS']&.split(',') || ENV['REPUTABLE_TRUSTED_KEY']
|
|
144
151
|
config.base_url = ENV['REPUTABLE_BASE_URL'] # Domain only
|
|
152
|
+
|
|
153
|
+
# Optional: blocked page branding/support info
|
|
154
|
+
config.site_name = ENV['REPUTABLE_SITE_NAME']
|
|
155
|
+
config.support_email = ENV['REPUTABLE_SUPPORT_EMAIL']
|
|
156
|
+
config.support_url = ENV['REPUTABLE_SUPPORT_URL']
|
|
145
157
|
|
|
146
158
|
# Error callback (optional)
|
|
147
159
|
config.on_error = ->(error, context) {
|
|
@@ -260,6 +272,27 @@ config.middleware.use Reputable::Middleware,
|
|
|
260
272
|
async: true
|
|
261
273
|
```
|
|
262
274
|
|
|
275
|
+
### Optional Reputation Gate
|
|
276
|
+
|
|
277
|
+
If you want the middleware to enforce IP reputation decisions, enable the gate:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
config.middleware.use Reputable::Middleware,
|
|
281
|
+
reputation_gate: true,
|
|
282
|
+
challenge_action: :verify, # Redirect to verification for untrusted_challenge
|
|
283
|
+
block_action: :blocked_page_remote, # Redirect to hosted blocked page (uses app UI settings)
|
|
284
|
+
blocked_page_path: "/_reputable/blocked" # Only used for local blocked page
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Notes:
|
|
288
|
+
- For `untrusted_challenge`, the middleware redirects to the Reputable verification URL.
|
|
289
|
+
- For `untrusted_block`, the default is to redirect to the hosted blocked page (`/_reputable/verify/blocked`).
|
|
290
|
+
- The hosted blocked page uses the same app UI settings as the verify/failure pages (`siteName`, `supportEmail`).
|
|
291
|
+
- To render a local blocked page instead, set `block_action: :blocked_page` and pass `blocked_page` options.
|
|
292
|
+
- To use a custom hosted page, set `blocked_redirect_url: "https://example.com/blocked"`.
|
|
293
|
+
- Use `blocked_page_path` only for local blocked pages (or to build a custom `failure_url`).
|
|
294
|
+
- Override `challenge_redirect_status` (default `302`) or `verification_force_challenge` if needed.
|
|
295
|
+
|
|
263
296
|
### Default Skipped Paths
|
|
264
297
|
|
|
265
298
|
The middleware automatically skips:
|
|
@@ -305,6 +338,56 @@ rep = current_ip_reputation
|
|
|
305
338
|
# => { status: 'trusted_verified', reason: 'payment', ... }
|
|
306
339
|
```
|
|
307
340
|
|
|
341
|
+
### Verification Redirect Helpers
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
class SessionsController < ApplicationController
|
|
345
|
+
def new
|
|
346
|
+
require_reputable_verification!
|
|
347
|
+
# If verified, continue
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Optional args:
|
|
353
|
+
- `return_url` (default: `request.original_url`)
|
|
354
|
+
- `failure_url` (default: API `/verify/failure` page)
|
|
355
|
+
- `session_id` (default: `session.id`)
|
|
356
|
+
- `force_challenge` (default: `false`)
|
|
357
|
+
- `session_key` (default: `:reputable_verified_at`)
|
|
358
|
+
|
|
359
|
+
You can check or clear the session flag:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
reputable_verified?
|
|
363
|
+
clear_reputable_verification!
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Reputation Gate Helpers
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
class SessionsController < ApplicationController
|
|
370
|
+
def new
|
|
371
|
+
require_reputable_reputation!
|
|
372
|
+
# If not blocked/challenged, continue
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`require_reputable_reputation!` will:
|
|
378
|
+
- Render a blocked page for `untrusted_block`
|
|
379
|
+
- Run verification flow for `untrusted_challenge`
|
|
380
|
+
|
|
381
|
+
You can also render the blocked page directly:
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
render_reputable_blocked_page(
|
|
385
|
+
site_name: "Example Store",
|
|
386
|
+
support_email: "support@example.com",
|
|
387
|
+
support_url: "https://example.com/support"
|
|
388
|
+
)
|
|
389
|
+
```
|
|
390
|
+
|
|
308
391
|
---
|
|
309
392
|
|
|
310
393
|
## Manual API Usage
|
|
@@ -391,7 +474,7 @@ end
|
|
|
391
474
|
|
|
392
475
|
**Options:**
|
|
393
476
|
- `return_url` (required): Where to redirect after successful verification
|
|
394
|
-
- `failure_url` (optional): Where to redirect on failure (defaults to
|
|
477
|
+
- `failure_url` (optional): Where to redirect on failure (defaults to API `/verify/failure` page)
|
|
395
478
|
- `session_id` (optional): Bind verification to a specific session
|
|
396
479
|
- `force_challenge` (optional): If `true`, always show CAPTCHA even for trusted users. Useful for testing the challenge flow.
|
|
397
480
|
|
|
@@ -14,7 +14,8 @@ module Reputable
|
|
|
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, :trusted_keys, :base_url
|
|
17
|
+
:on_error, :trusted_keys, :base_url,
|
|
18
|
+
:site_name, :support_email, :support_url
|
|
18
19
|
|
|
19
20
|
# Alias for backward compatibility
|
|
20
21
|
alias_method :verification_base_url, :base_url
|
|
@@ -78,6 +79,9 @@ module Reputable
|
|
|
78
79
|
@trusted_keys = [ENV["REPUTABLE_SECRET_KEY"]]
|
|
79
80
|
end
|
|
80
81
|
@base_url = ENV.fetch("REPUTABLE_BASE_URL", "https://api.reputable.click")
|
|
82
|
+
@site_name = ENV["REPUTABLE_SITE_NAME"]
|
|
83
|
+
@support_email = ENV["REPUTABLE_SUPPORT_EMAIL"]
|
|
84
|
+
@support_url = ENV["REPUTABLE_SUPPORT_URL"]
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
# Alias for backward compatibility
|
data/lib/reputable/middleware.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "blocked_page"
|
|
4
|
+
|
|
3
5
|
module Reputable
|
|
4
6
|
# Rack middleware for automatic request tracking
|
|
5
7
|
#
|
|
@@ -42,12 +44,34 @@ module Reputable
|
|
|
42
44
|
@skip_if = options[:skip_if]
|
|
43
45
|
@tag_builder = options[:tag_builder]
|
|
44
46
|
@async = options.fetch(:async, true)
|
|
47
|
+
@reputation_gate = options.fetch(:reputation_gate, false)
|
|
48
|
+
@challenge_action = options.fetch(:challenge_action, :verify)
|
|
49
|
+
@block_action = options.fetch(:block_action, :blocked_page_remote)
|
|
50
|
+
@challenge_redirect_status = options.fetch(:challenge_redirect_status, 302)
|
|
51
|
+
@blocked_redirect_status = options.fetch(:blocked_redirect_status, 302)
|
|
52
|
+
@blocked_redirect_url = options[:blocked_redirect_url]
|
|
53
|
+
@verification_force_challenge = options.fetch(:verification_force_challenge, false)
|
|
54
|
+
@verification_failure_url = options[:verification_failure_url]
|
|
55
|
+
@verification_session_id = options[:verification_session_id]
|
|
56
|
+
@verified_session_keys = Array(options.fetch(:verified_session_keys, [:reputable_verified_at, :reputable_verified]))
|
|
57
|
+
@blocked_page_options = options.fetch(:blocked_page, {})
|
|
58
|
+
@blocked_page_path = options[:blocked_page_path]
|
|
45
59
|
end
|
|
46
60
|
|
|
47
61
|
def call(env)
|
|
48
62
|
# Check for verification return parameters and verify signature if present
|
|
49
63
|
handle_verification_return(env)
|
|
50
64
|
|
|
65
|
+
# Optional: render blocked page path directly
|
|
66
|
+
if (blocked_response = handle_blocked_page_request(env))
|
|
67
|
+
return blocked_response
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Optional: enforce reputation gate before app
|
|
71
|
+
if (gate_response = safe_reputation_gate(env))
|
|
72
|
+
return gate_response
|
|
73
|
+
end
|
|
74
|
+
|
|
51
75
|
# ALWAYS process the request first - tracking must never block
|
|
52
76
|
status, headers, response = @app.call(env)
|
|
53
77
|
|
|
@@ -60,6 +84,33 @@ module Reputable
|
|
|
60
84
|
|
|
61
85
|
private
|
|
62
86
|
|
|
87
|
+
def safe_reputation_gate(env)
|
|
88
|
+
return nil unless @reputation_gate
|
|
89
|
+
return nil unless Reputable.enabled?
|
|
90
|
+
return nil if skip_request?(env)
|
|
91
|
+
|
|
92
|
+
enforce_reputation_gate(env)
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
Reputable.logger&.debug("Reputable reputation gate: #{e.class} - #{e.message}")
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def enforce_reputation_gate(env)
|
|
99
|
+
ip = extract_ip(env)
|
|
100
|
+
status = Reputable::Reputation.lookup_ip(ip)
|
|
101
|
+
return nil if status.nil?
|
|
102
|
+
|
|
103
|
+
case status
|
|
104
|
+
when "untrusted_block"
|
|
105
|
+
handle_block_action(env, ip)
|
|
106
|
+
when "untrusted_challenge"
|
|
107
|
+
return nil if verified_session?(env)
|
|
108
|
+
handle_challenge_action(env)
|
|
109
|
+
else
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
63
114
|
def safe_track_request(env)
|
|
64
115
|
# Skip if disabled globally
|
|
65
116
|
return unless Reputable.enabled?
|
|
@@ -93,6 +144,145 @@ module Reputable
|
|
|
93
144
|
Reputable.logger&.debug("Reputable verification error: #{e.message}")
|
|
94
145
|
end
|
|
95
146
|
|
|
147
|
+
def handle_blocked_page_request(env)
|
|
148
|
+
return nil unless @blocked_page_path
|
|
149
|
+
|
|
150
|
+
path = env["PATH_INFO"] || "/"
|
|
151
|
+
return nil unless path == @blocked_page_path
|
|
152
|
+
|
|
153
|
+
ip = extract_ip(env)
|
|
154
|
+
build_blocked_page_response(ip)
|
|
155
|
+
rescue StandardError => e
|
|
156
|
+
Reputable.logger&.debug("Reputable blocked page: #{e.class} - #{e.message}")
|
|
157
|
+
nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def handle_block_action(env, ip)
|
|
161
|
+
case @block_action
|
|
162
|
+
when :blocked_page
|
|
163
|
+
build_blocked_page_response(ip)
|
|
164
|
+
when :blocked_page_remote
|
|
165
|
+
url = resolve_blocked_redirect_url(env) || Reputable.blocked_page_url
|
|
166
|
+
redirect_response(url, @blocked_redirect_status)
|
|
167
|
+
when :forbidden
|
|
168
|
+
[403, { "Content-Type" => "text/plain; charset=utf-8" }, ["Forbidden"]]
|
|
169
|
+
when Proc
|
|
170
|
+
@block_action.call(env, ip)
|
|
171
|
+
else
|
|
172
|
+
build_blocked_page_response(ip)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def handle_challenge_action(env)
|
|
177
|
+
case @challenge_action
|
|
178
|
+
when :verify
|
|
179
|
+
keys = Reputable.configuration.trusted_keys
|
|
180
|
+
return nil if keys.nil? || keys.empty?
|
|
181
|
+
|
|
182
|
+
request = Rack::Request.new(env)
|
|
183
|
+
return_url = request.url
|
|
184
|
+
failure_url = build_verification_failure_url(request)
|
|
185
|
+
session_id = resolve_session_id(env)
|
|
186
|
+
|
|
187
|
+
redirect_url = Reputable.verification_url(
|
|
188
|
+
return_url: return_url,
|
|
189
|
+
failure_url: failure_url,
|
|
190
|
+
session_id: session_id,
|
|
191
|
+
force_challenge: @verification_force_challenge
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return nil if redirect_url == return_url
|
|
195
|
+
|
|
196
|
+
redirect_response(redirect_url, @challenge_redirect_status)
|
|
197
|
+
when Proc
|
|
198
|
+
@challenge_action.call(env)
|
|
199
|
+
else
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def build_verification_failure_url(request)
|
|
205
|
+
if @verification_failure_url.respond_to?(:call)
|
|
206
|
+
@verification_failure_url.call(request)
|
|
207
|
+
elsif @verification_failure_url
|
|
208
|
+
@verification_failure_url
|
|
209
|
+
elsif @blocked_page_path
|
|
210
|
+
base = request.base_url
|
|
211
|
+
path = @blocked_page_path.start_with?("/") ? @blocked_page_path : "/#{@blocked_page_path}"
|
|
212
|
+
"#{base}#{path}"
|
|
213
|
+
end
|
|
214
|
+
rescue StandardError
|
|
215
|
+
nil
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def resolve_session_id(env)
|
|
219
|
+
return @verification_session_id.call(env) if @verification_session_id.respond_to?(:call)
|
|
220
|
+
return @verification_session_id if @verification_session_id.is_a?(String)
|
|
221
|
+
|
|
222
|
+
if @verification_session_id.is_a?(Symbol)
|
|
223
|
+
session = env["rack.session"]
|
|
224
|
+
return session[@verification_session_id] if session
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
session = env["rack.session"]
|
|
228
|
+
return nil unless session
|
|
229
|
+
|
|
230
|
+
return session.id if session.respond_to?(:id) && session.id
|
|
231
|
+
|
|
232
|
+
options = env["rack.session.options"]
|
|
233
|
+
return options[:id] if options.is_a?(Hash) && options[:id]
|
|
234
|
+
|
|
235
|
+
session[:session_id] || session["session_id"]
|
|
236
|
+
rescue StandardError
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def verified_session?(env)
|
|
241
|
+
return true if env["reputable.verified"]
|
|
242
|
+
|
|
243
|
+
session = env["rack.session"]
|
|
244
|
+
return false unless session
|
|
245
|
+
|
|
246
|
+
@verified_session_keys.any? do |key|
|
|
247
|
+
session[key] || session[key.to_s]
|
|
248
|
+
end
|
|
249
|
+
rescue StandardError
|
|
250
|
+
false
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def build_blocked_page_response(client_ip)
|
|
254
|
+
options = blocked_page_options.merge(client_ip: client_ip)
|
|
255
|
+
Reputable::BlockedPage.response(**options)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def blocked_page_options
|
|
259
|
+
config = Reputable.configuration
|
|
260
|
+
defaults = {
|
|
261
|
+
site_name: config.site_name,
|
|
262
|
+
support_email: config.support_email,
|
|
263
|
+
support_url: config.support_url
|
|
264
|
+
}
|
|
265
|
+
defaults.merge(@blocked_page_options)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def redirect_response(url, status)
|
|
269
|
+
headers = {
|
|
270
|
+
"Location" => url,
|
|
271
|
+
"Content-Type" => "text/html; charset=utf-8",
|
|
272
|
+
"Cache-Control" => "no-store"
|
|
273
|
+
}
|
|
274
|
+
[status, headers, ["Redirecting to verification..."]]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def resolve_blocked_redirect_url(env)
|
|
278
|
+
return @blocked_redirect_url.call(env) if @blocked_redirect_url.respond_to?(:call)
|
|
279
|
+
return @blocked_redirect_url if @blocked_redirect_url.is_a?(String)
|
|
280
|
+
|
|
281
|
+
nil
|
|
282
|
+
rescue StandardError
|
|
283
|
+
nil
|
|
284
|
+
end
|
|
285
|
+
|
|
96
286
|
def skip_request?(env)
|
|
97
287
|
return true if @skip_if&.call(env)
|
|
98
288
|
|
data/lib/reputable/rails.rb
CHANGED
|
@@ -7,6 +7,10 @@ module Reputable
|
|
|
7
7
|
module ControllerHelpers
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
|
|
10
|
+
included do
|
|
11
|
+
helper_method :reputable_verified? if respond_to?(:helper_method)
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
# Track the current request with optional extra tags
|
|
11
15
|
def track_reputable_request(tags: [], **options)
|
|
12
16
|
Reputable::Tracker.track_request(
|
|
@@ -81,6 +85,139 @@ module Reputable
|
|
|
81
85
|
def current_ip_status
|
|
82
86
|
Reputable::Reputation.lookup_ip(request.remote_ip)
|
|
83
87
|
end
|
|
88
|
+
|
|
89
|
+
# ========================================
|
|
90
|
+
# Verification redirect helpers
|
|
91
|
+
# ========================================
|
|
92
|
+
|
|
93
|
+
# Render the standard blocked page
|
|
94
|
+
def render_reputable_blocked_page(
|
|
95
|
+
status: 403,
|
|
96
|
+
site_name: nil,
|
|
97
|
+
support_email: nil,
|
|
98
|
+
support_url: nil,
|
|
99
|
+
heading: "Access blocked",
|
|
100
|
+
message: nil,
|
|
101
|
+
show_ip: true
|
|
102
|
+
)
|
|
103
|
+
config = Reputable.configuration
|
|
104
|
+
html = Reputable::BlockedPage.html(
|
|
105
|
+
site_name: site_name || config.site_name,
|
|
106
|
+
support_email: support_email || config.support_email,
|
|
107
|
+
support_url: support_url || config.support_url,
|
|
108
|
+
client_ip: request.remote_ip,
|
|
109
|
+
heading: heading,
|
|
110
|
+
message: message,
|
|
111
|
+
show_ip: show_ip
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
render html: html.html_safe, status: status, content_type: "text/html"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Check if the current session has already passed verification
|
|
118
|
+
# @param session_key [Symbol]
|
|
119
|
+
# @return [Boolean]
|
|
120
|
+
def reputable_verified?(session_key: :reputable_verified_at)
|
|
121
|
+
session[session_key].present?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Clear verification status for the current session
|
|
125
|
+
# @param session_key [Symbol]
|
|
126
|
+
def clear_reputable_verification!(session_key: :reputable_verified_at)
|
|
127
|
+
session.delete(session_key)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Enforce verification redirect flow.
|
|
131
|
+
# - If already verified in this session, returns immediately.
|
|
132
|
+
# - If returning with signature, validates and marks session.
|
|
133
|
+
# - Otherwise redirects to verification URL.
|
|
134
|
+
#
|
|
135
|
+
# @param return_url [String] URL to return to after verification
|
|
136
|
+
# @param failure_url [String] URL to return to on failure/invalid token
|
|
137
|
+
# @param session_id [String] Optional session id to link
|
|
138
|
+
# @param force_challenge [Boolean] Force challenge even if trusted
|
|
139
|
+
# @param session_key [Symbol] Session key used to store verified state
|
|
140
|
+
def require_reputable_verification!(
|
|
141
|
+
return_url: request.original_url,
|
|
142
|
+
failure_url: nil,
|
|
143
|
+
session_id: session.id,
|
|
144
|
+
force_challenge: false,
|
|
145
|
+
session_key: :reputable_verified_at
|
|
146
|
+
)
|
|
147
|
+
return if reputable_verified?(session_key: session_key)
|
|
148
|
+
|
|
149
|
+
if params[:reputable_signature]
|
|
150
|
+
if Reputable.verify_redirect_return(params)
|
|
151
|
+
if params[:reputable_status] == "pass"
|
|
152
|
+
session[session_key] = Time.now.to_i
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
redirect_to failure_url and return
|
|
157
|
+
else
|
|
158
|
+
render plain: "Verification failed", status: 403 and return
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
redirect_to reputable_verification_url(
|
|
163
|
+
return_url: return_url,
|
|
164
|
+
failure_url: failure_url,
|
|
165
|
+
session_id: session_id,
|
|
166
|
+
force_challenge: force_challenge
|
|
167
|
+
) and return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Enforce reputation-based access control.
|
|
171
|
+
# - If IP is blocked, render a blocked page.
|
|
172
|
+
# - If IP is challenged, run verification flow.
|
|
173
|
+
def require_reputable_reputation!(
|
|
174
|
+
return_url: request.original_url,
|
|
175
|
+
failure_url: nil,
|
|
176
|
+
session_id: session.id,
|
|
177
|
+
force_challenge: false,
|
|
178
|
+
session_key: :reputable_verified_at,
|
|
179
|
+
blocked_status: 403,
|
|
180
|
+
blocked_site_name: nil,
|
|
181
|
+
blocked_support_email: nil,
|
|
182
|
+
blocked_support_url: nil,
|
|
183
|
+
blocked_heading: "Access blocked",
|
|
184
|
+
blocked_message: nil,
|
|
185
|
+
blocked_show_ip: true
|
|
186
|
+
)
|
|
187
|
+
if current_ip_blocked?
|
|
188
|
+
render_reputable_blocked_page(
|
|
189
|
+
status: blocked_status,
|
|
190
|
+
site_name: blocked_site_name,
|
|
191
|
+
support_email: blocked_support_email,
|
|
192
|
+
support_url: blocked_support_url,
|
|
193
|
+
heading: blocked_heading,
|
|
194
|
+
message: blocked_message,
|
|
195
|
+
show_ip: blocked_show_ip
|
|
196
|
+
)
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
return unless current_ip_challenged?
|
|
201
|
+
|
|
202
|
+
require_reputable_verification!(
|
|
203
|
+
return_url: return_url,
|
|
204
|
+
failure_url: failure_url,
|
|
205
|
+
session_id: session_id,
|
|
206
|
+
force_challenge: force_challenge,
|
|
207
|
+
session_key: session_key
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
def reputable_verification_url(return_url:, failure_url:, session_id:, force_challenge:)
|
|
214
|
+
Reputable.verification_url(
|
|
215
|
+
return_url: return_url,
|
|
216
|
+
failure_url: failure_url,
|
|
217
|
+
session_id: session_id,
|
|
218
|
+
force_challenge: force_challenge
|
|
219
|
+
)
|
|
220
|
+
end
|
|
84
221
|
end
|
|
85
222
|
|
|
86
223
|
# Railtie for automatic Rails integration (only defined when Rails is present)
|
data/lib/reputable/version.rb
CHANGED
data/lib/reputable.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "reputable/configuration"
|
|
|
7
7
|
require_relative "reputable/connection"
|
|
8
8
|
require_relative "reputable/tracker"
|
|
9
9
|
require_relative "reputable/reputation"
|
|
10
|
+
require_relative "reputable/blocked_page"
|
|
10
11
|
require_relative "reputable/middleware"
|
|
11
12
|
|
|
12
13
|
# Optional Rails integration (only load if Rails is defined)
|
|
@@ -145,6 +146,7 @@ module Reputable
|
|
|
145
146
|
# Ensure base_url doesn't have a trailing slash, then append the verify path
|
|
146
147
|
base_url = base_url.chomp("/")
|
|
147
148
|
verify_url = "#{base_url}/_reputable/verify"
|
|
149
|
+
failure_url ||= "#{base_url}/_reputable/verify/failure"
|
|
148
150
|
|
|
149
151
|
# JWT Header
|
|
150
152
|
header = { alg: "HS256", typ: "JWT" }
|
|
@@ -170,6 +172,14 @@ module Reputable
|
|
|
170
172
|
"#{verify_url}?token=#{token}"
|
|
171
173
|
end
|
|
172
174
|
|
|
175
|
+
# Build the hosted blocked page URL
|
|
176
|
+
# @return [String]
|
|
177
|
+
def blocked_page_url
|
|
178
|
+
base_url = configuration.base_url
|
|
179
|
+
base_url = base_url.chomp("/")
|
|
180
|
+
"#{base_url}/_reputable/verify/blocked"
|
|
181
|
+
end
|
|
182
|
+
|
|
173
183
|
# Verify the signature of a redirect return
|
|
174
184
|
# @param params [Hash] Request query parameters
|
|
175
185
|
# @return [Boolean] true if valid logic and passed signature check
|
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.
|
|
4
|
+
version: 0.1.9
|
|
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-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- lib/reputable/reputation.rb
|
|
136
136
|
- lib/reputable/tracker.rb
|
|
137
137
|
- lib/reputable/version.rb
|
|
138
|
+
- reputable.gemspec
|
|
138
139
|
homepage: https://github.com/reputable-click/reputable-rb
|
|
139
140
|
licenses:
|
|
140
141
|
- MIT
|