Module: Reputable

Defined in:
lib/reputable.rb,
lib/reputable/rails.rb,
lib/reputable/verify.rb,
lib/reputable/signals.rb,
lib/reputable/tracker.rb,
lib/reputable/version.rb,
lib/reputable/connection.rb,
lib/reputable/middleware.rb,
lib/reputable/reputation.rb,
lib/reputable/measurement.rb,
lib/reputable/blocked_page.rb,
lib/reputable/configuration.rb

Overview

Reputable - Bot detection and reputation scoring client

RESILIENCE: This gem is designed to fail silently and never break your app.

  • All methods return safe defaults (false/nil) on any error
  • Can be disabled via REPUTABLE_ENABLED=false
  • Circuit breaker prevents connection storms
  • All Redis timeouts are configurable via ENV

Examples:

Basic setup

Reputable.configure do |config|
  config.redis_url = "rediss://user:pass@dragonfly.example.com:6379"
end

Track a request

Reputable.track_request(
  ip: "203.0.113.45",
  path: "/products/123"
)

Apply verified reputation after payment

Reputable.trust_ip("203.0.113.45", reason: "payment_completed", status: :trusted_verified, ttl: 0)

Disable completely via ENV

# In your environment: REPUTABLE_ENABLED=false

Defined Under Namespace

Modules: BlockedPage, Measurement, Rails, Reputation, Signals, Tracker, Verify Classes: Configuration, ConfigurationError, Connection, ConnectionError, Error, Middleware

Constant Summary collapse

VERSION =
"0.1.26"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



52
53
54
# File 'lib/reputable.rb', line 52

def logger
  @logger
end

Class Method Details

.apply_reputation(entity_type:, entity_id:, status:, **options) ⇒ Object

Delegate reputation methods to Reputation module



127
128
129
# File 'lib/reputable.rb', line 127

def apply_reputation(entity_type:, entity_id:, status:, **options)
  Reputation.apply(entity_type: entity_type, entity_id: entity_id, status: status, **options)
end

.block_asn(asn, reason: "manual_block", ttl: nil, **metadata) ⇒ Object



189
190
191
# File 'lib/reputable.rb', line 189

def block_asn(asn, reason: "manual_block", ttl: nil, **)
  Reputation.block_asn(asn, reason: reason, ttl: ttl, **)
end

.block_ip(ip, reason: "manual_block", **metadata) ⇒ Object



135
136
137
# File 'lib/reputable.rb', line 135

def block_ip(ip, reason: "manual_block", **)
  Reputation.block_ip(ip, reason: reason, **)
end

.blocked_asn?(asn) ⇒ Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/reputable.rb', line 177

def blocked_asn?(asn)
  Reputation.blocked_asn?(asn)
end

.blocked_ip?(ip) ⇒ Boolean

Returns:

  • (Boolean)


160
161
162
# File 'lib/reputable.rb', line 160

def blocked_ip?(ip)
  Reputation.blocked_ip?(ip)
end

.blocked_page_urlString

Build the hosted blocked page URL

Returns:

  • (String)


307
308
309
310
311
# File 'lib/reputable.rb', line 307

def blocked_page_url
  base_url = configuration.base_url
  base_url = base_url.chomp("/")
  "#{base_url}/_reputable/verify/blocked"
end

.challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **metadata) ⇒ Object



193
194
195
# File 'lib/reputable.rb', line 193

def challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **)
  Reputation.challenge_asn(asn, reason: reason, ttl: ttl, **)
end

.challenge_ip(ip, reason: "suspicious_activity", **metadata) ⇒ Object



139
140
141
# File 'lib/reputable.rb', line 139

def challenge_ip(ip, reason: "suspicious_activity", **)
  Reputation.challenge_ip(ip, reason: reason, **)
end

.challenged_asn?(asn) ⇒ Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/reputable.rb', line 181

def challenged_asn?(asn)
  Reputation.challenged_asn?(asn)
end

.challenged_ip?(ip) ⇒ Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/reputable.rb', line 164

def challenged_ip?(ip)
  Reputation.challenged_ip?(ip)
end

.configurationObject



54
55
56
# File 'lib/reputable.rb', line 54

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



58
59
60
61
# File 'lib/reputable.rb', line 58

def configure
  yield(configuration)
  Connection.reset! # Reset pool on reconfiguration
end

.decode_reputable_response(params) ⇒ Hash?

Decode the new reputable_r payload

Parameters:

  • params (Hash)

    Request query parameters

Returns:

  • (Hash, nil)

    Decoded payload with normalized keys, or nil if invalid



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/reputable.rb', line 337

def decode_reputable_response(params)
  encoded = params["reputable_r"]
  return nil unless encoded

  begin
    json = base64url_decode(encoded)
    payload = JSON.parse(json)
    
    # Return normalized hash with full key names
    {
      "version" => payload["v"],
      "status" => payload["s"],
      "session_id" => payload["sid"],
      "outcome" => payload["o"],
      "ignore_analytics" => payload["i"],
      "country" => payload["c"],
      "challenge_passed" => payload["cp"]
    }.compact
  rescue StandardError => e
    logger&.warn "Reputable: Failed to decode response: #{e.message}"
    nil
  end
end

.disabled?Boolean

Check if Reputable is disabled

Returns:

  • (Boolean)


83
84
85
# File 'lib/reputable.rb', line 83

def disabled?
  !enabled?
end

.enabled?Boolean

Check if Reputable is enabled Can be disabled via REPUTABLE_ENABLED=false environment variable

Returns:

  • (Boolean)


71
72
73
# File 'lib/reputable.rb', line 71

def enabled?
  configuration.enabled?
end

.ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **metadata) ⇒ Object



197
198
199
# File 'lib/reputable.rb', line 197

def ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **)
  Reputation.ignore_asn(asn, reason: reason, ttl: ttl, **)
end

.ignore_ip(ip, reason: "internal_traffic", **metadata) ⇒ Object



143
144
145
# File 'lib/reputable.rb', line 143

def ignore_ip(ip, reason: "internal_traffic", **)
  Reputation.ignore_ip(ip, reason: reason, **)
end

.lookup_asn(asn) ⇒ Object

Delegate ASN reputation methods to Reputation module



169
170
171
# File 'lib/reputable.rb', line 169

def lookup_asn(asn)
  Reputation.lookup_asn(asn)
end

.lookup_ip(ip) ⇒ Object



152
153
154
# File 'lib/reputable.rb', line 152

def lookup_ip(ip)
  Reputation.lookup_ip(ip)
end

.lookup_reputation(entity_type, entity_id) ⇒ Object

Delegate lookup methods to Reputation module (O(1) Redis lookups)



148
149
150
# File 'lib/reputable.rb', line 148

def lookup_reputation(entity_type, entity_id)
  Reputation.lookup(entity_type, entity_id)
end

.measurement_collect_url(token) ⇒ Object



205
206
207
# File 'lib/reputable.rb', line 205

def measurement_collect_url(token)
  Measurement.collect_url(token)
end

.measurement_redirect_url(token) ⇒ Object



209
210
211
# File 'lib/reputable.rb', line 209

def measurement_redirect_url(token)
  Measurement.redirect_url(token)
end

.measurement_token(**kwargs) ⇒ Object



201
202
203
# File 'lib/reputable.rb', line 201

def measurement_token(**kwargs)
  Measurement.measurement_token(**kwargs)
end

.operational?Boolean

Check if Reputable is operational (enabled AND circuit breaker is closed) Use this to guard redirect/gating decisions — when the circuit is open, the Reputable server is likely unreachable and redirects would fail.

Returns:

  • (Boolean)

    true if Reputable can be used, false otherwise



91
92
93
94
95
# File 'lib/reputable.rb', line 91

def operational?
  return false unless enabled?
  return false if Connection.circuit_open?
  true
end

.proof_challenge_passed?(proof, **kwargs) ⇒ Boolean

Legacy alias from the proof era.

Returns:

  • (Boolean)


245
246
247
# File 'lib/reputable.rb', line 245

def proof_challenge_passed?(proof, **kwargs)
  Verify.challenge_passed?(proof, **kwargs)
end

.reset!Object



63
64
65
66
# File 'lib/reputable.rb', line 63

def reset!
  @configuration = nil
  Connection.reset!
end

.signals_check(**kwargs) ⇒ Object

Bot Signals server API: signal report for an end user's request.



250
251
252
# File 'lib/reputable.rb', line 250

def signals_check(**kwargs)
  Signals.check(**kwargs)
end

.signals_redirect_url(**kwargs) ⇒ Object

Bot Signals redirect-flow URL (publishable key + registered return URL).



255
256
257
# File 'lib/reputable.rb', line 255

def signals_redirect_url(**kwargs)
  Signals.redirect_url(**kwargs)
end

.statusHash

Get a detailed status report for monitoring/debugging

Returns:

  • (Hash)

    status information



99
100
101
102
103
104
105
106
107
# File 'lib/reputable.rb', line 99

def status
  {
    enabled: enabled?,
    operational: operational?,
    circuit_breaker: Connection.circuit_open? ? "open" : "closed",
    failure_count: Connection.failure_count,
    circuit_opened_at: Connection.circuit_opened_at
  }
end

.ticket_challenge_passed?(ticket, **kwargs) ⇒ Boolean

Did the ticket clear a CAPTCHA challenge? Convenience predicate.

Returns:

  • (Boolean)


240
241
242
# File 'lib/reputable.rb', line 240

def ticket_challenge_passed?(ticket, **kwargs)
  Verify.challenge_passed?(ticket, **kwargs)
end

.track_request(ip:, path:, **options) ⇒ Object

Delegate tracking methods to Tracker



110
111
112
# File 'lib/reputable.rb', line 110

def track_request(ip:, path:, **options)
  Tracker.track_request(ip: ip, path: path, **options)
end

.track_request_async(ip:, path:, **options) ⇒ Object



114
115
116
# File 'lib/reputable.rb', line 114

def track_request_async(ip:, path:, **options)
  Tracker.track_request_async(ip: ip, path: path, **options)
end

.track_security_event(**kwargs) ⇒ Object



118
119
120
# File 'lib/reputable.rb', line 118

def track_security_event(**kwargs)
  Tracker.track_security_event(**kwargs)
end

.track_security_event_async(**kwargs) ⇒ Object



122
123
124
# File 'lib/reputable.rb', line 122

def track_security_event_async(**kwargs)
  Tracker.track_security_event_async(**kwargs)
end

.trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata) ⇒ Object



185
186
187
# File 'lib/reputable.rb', line 185

def trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **)
  Reputation.trust_asn(asn, reason: reason, status: status, ttl: ttl, **)
end

.trust_ip(ip, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata) ⇒ Object



131
132
133
# File 'lib/reputable.rb', line 131

def trust_ip(ip, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **)
  Reputation.trust_ip(ip, reason: reason, status: status, ttl: ttl, **)
end

.trusted_asn?(asn) ⇒ Boolean

Returns:

  • (Boolean)


173
174
175
# File 'lib/reputable.rb', line 173

def trusted_asn?(asn)
  Reputation.trusted_asn?(asn)
end

.trusted_ip?(ip) ⇒ Boolean

Returns:

  • (Boolean)


156
157
158
# File 'lib/reputable.rb', line 156

def trusted_ip?(ip)
  Reputation.trusted_ip?(ip)
end

.verbose?Boolean

Check if verbose logging is enabled

Returns:

  • (Boolean)


77
78
79
# File 'lib/reputable.rb', line 77

def verbose?
  configuration.verbose
end

.verification_url(return_url:, failure_url: nil, session_id: nil, force_challenge: false) ⇒ String

Generate a signed verification URL

Parameters:

  • return_url (String)

    URL to redirect to after successful verification

  • failure_url (String, nil) (defaults to: nil)

    URL to redirect to on failure (optional)

  • session_id (String, nil) (defaults to: nil)

    Session ID to bind the verification to (optional)

  • force_challenge (Boolean) (defaults to: false)

    If true, always show CAPTCHA even for trusted users (default: false)

Returns:

  • (String)

    The signed verification URL



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/reputable.rb', line 265

def verification_url(return_url:, failure_url: nil, session_id: nil, force_challenge: false)
  keys = configuration.trusted_keys
  if keys.nil? || keys.empty?
    logger&.warn "Reputable: Missing trusted_keys, cannot generate verification URL"
    return return_url
  end

  # Use the first key for signing new requests
  secret = keys.first

  base_url = configuration.base_url
  # Ensure base_url doesn't have a trailing slash, then append the verify path
  base_url = base_url.chomp("/")
  verify_url = "#{base_url}/_reputable/verify"
  failure_url ||= "#{base_url}/_reputable/verify/failure"

  # JWT Header
  header = { alg: "HS256", typ: "JWT" }
  encoded_header = base64url_encode(JSON.generate(header))

  # JWT Payload
  payload = {
    returnUrl: return_url,
    failureUrl: failure_url,
    sessionId: session_id,
    forceChallenge: force_challenge,
    iat: Time.now.to_i
  }
  encoded_payload = base64url_encode(JSON.generate(payload))

  # Signature
  data = "#{encoded_header}.#{encoded_payload}"
  signature = OpenSSL::HMAC.digest("SHA256", secret, data)
  encoded_signature = base64url_encode(signature)

  token = "#{data}.#{encoded_signature}"

  "#{verify_url}?token=#{token}"
end

.verify_measurement_result(token, **kwargs) ⇒ Object



217
218
219
# File 'lib/reputable.rb', line 217

def verify_measurement_result(token, **kwargs)
  Measurement.verify_result(token, **kwargs)
end

.verify_measurement_result_token(token, **kwargs) ⇒ Object



213
214
215
# File 'lib/reputable.rb', line 213

def verify_measurement_result_token(token, **kwargs)
  Measurement.verify_result_token(token, **kwargs)
end

.verify_proof(proof, **kwargs) ⇒ Object

Legacy alias from the proof era.



235
236
237
# File 'lib/reputable.rb', line 235

def verify_proof(proof, **kwargs)
  Verify.verify_ticket(proof, **kwargs)
end

.verify_redirect_return(params) ⇒ Boolean

Verify the signature of a redirect return Supports both new format (reputable_r + reputable_s) and legacy format

Parameters:

  • params (Hash)

    Request query parameters

Returns:

  • (Boolean)

    true if valid signature check



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/reputable.rb', line 317

def verify_redirect_return(params)
  keys = configuration.trusted_keys
  if keys.nil? || keys.empty?
    logger&.warn "Reputable: Missing trusted_keys, cannot verify redirect"
    return false
  end

  # Detect format: new (reputable_r) vs legacy (reputable_status)
  if params["reputable_r"] && params["reputable_s"]
    verify_new_format(params, keys)
  elsif params["reputable_status"] && params["reputable_signature"]
    verify_legacy_format(params, keys)
  else
    false
  end
end

.verify_ticket(ticket, **kwargs) ⇒ Object

Bot Signals: verify the signed ticket submitted by the widget. Returns the signal report hash (incl. "outcome" and "challengePassed") or nil.



223
224
225
# File 'lib/reputable.rb', line 223

def verify_ticket(ticket, **kwargs)
  Verify.verify_ticket(ticket, **kwargs)
end

.verify_ticket_claims(ticket, **kwargs) ⇒ Object

Bot Signals: verify and return all signed ticket claims. This is useful when an application must bind a route pass to key, mode, session, or return origin rather than reading only the report.



230
231
232
# File 'lib/reputable.rb', line 230

def verify_ticket_claims(ticket, **kwargs)
  Verify.verify_ticket_claims(ticket, **kwargs)
end