reputable 0.1.1 → 0.1.2

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: 58cd625b57ec78df69ca3a6f46e810f3870d30d2c708c61ded9c028737eef1e8
4
- data.tar.gz: b73a0ad0cfffead0089d870bfd51dee53b3ee4af4305992ec9285f37f0e18e4b
3
+ metadata.gz: 5cfe6bbd421f190d87e5f0be242142988e269d8df74186f85f66c8f5996f115f
4
+ data.tar.gz: 4db1343a290ef95d775b0694a50d84d48c861af293bf0d3e4d388e44c031c9d0
5
5
  SHA512:
6
- metadata.gz: 8e99d9b81e8fe4ee5594ad30637eac3387a89e4073644ae8a54a25c211e6c2a0cae9dcf38b03a6767474845f0b63437e719a2f9bc4748b208afe617c3d43a9e5
7
- data.tar.gz: 5e6e0d1a50150f7e5acec89be721be877ee17e1a1bb9f388c5ab4c70b14e9b2a6efcd816087e4a177e712723081550d7af704cc8f212961131f92cfdc8063de8
6
+ metadata.gz: 211cb949190c13bd0f3d8f6c4f0296a84c92653e64c2971d5866e0606cc777ebfe356a8a73d10e5a269f6701105372f9c4c8203f90bf75f279958b385874c3ad
7
+ data.tar.gz: 14dbcf3338f725de441618100e0b57e99314974e0ff89b787c45141dcec56fa6c230c9acaa69feaed42233a87cf10add2cc2ccca2e80883eddb656779821ace6
data/.DS_Store ADDED
Binary file
data/Gemfile.lock ADDED
@@ -0,0 +1,76 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ reputable (0.1.2)
5
+ connection_pool (~> 2.2)
6
+ redis (>= 4.0, < 6.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.3)
12
+ connection_pool (2.5.5)
13
+ diff-lcs (1.6.2)
14
+ json (2.18.0)
15
+ language_server-protocol (3.17.0.5)
16
+ lint_roller (1.1.0)
17
+ parallel (1.27.0)
18
+ parser (3.3.10.0)
19
+ ast (~> 2.4.1)
20
+ racc
21
+ prism (1.7.0)
22
+ racc (1.8.1)
23
+ rack (2.2.21)
24
+ rainbow (3.1.1)
25
+ rake (13.3.1)
26
+ redis (5.4.1)
27
+ redis-client (>= 0.22.0)
28
+ redis-client (0.26.2)
29
+ connection_pool
30
+ regexp_parser (2.11.3)
31
+ rspec (3.13.2)
32
+ rspec-core (~> 3.13.0)
33
+ rspec-expectations (~> 3.13.0)
34
+ rspec-mocks (~> 3.13.0)
35
+ rspec-core (3.13.6)
36
+ rspec-support (~> 3.13.0)
37
+ rspec-expectations (3.13.5)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.13.0)
40
+ rspec-mocks (3.13.7)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.13.0)
43
+ rspec-support (3.13.6)
44
+ rubocop (1.82.0)
45
+ json (~> 2.3)
46
+ language_server-protocol (~> 3.17.0.2)
47
+ lint_roller (~> 1.1.0)
48
+ parallel (~> 1.10)
49
+ parser (>= 3.3.0.2)
50
+ rainbow (>= 2.2.2, < 4.0)
51
+ regexp_parser (>= 2.9.3, < 3.0)
52
+ rubocop-ast (>= 1.48.0, < 2.0)
53
+ ruby-progressbar (~> 1.7)
54
+ unicode-display_width (>= 2.4.0, < 4.0)
55
+ rubocop-ast (1.48.0)
56
+ parser (>= 3.3.7.2)
57
+ prism (~> 1.4)
58
+ ruby-progressbar (1.13.0)
59
+ unicode-display_width (3.2.0)
60
+ unicode-emoji (~> 4.1)
61
+ unicode-emoji (4.2.0)
62
+
63
+ PLATFORMS
64
+ arm64-darwin-25
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ bundler (~> 2.0)
69
+ rack (~> 2.0)
70
+ rake (~> 13.0)
71
+ reputable!
72
+ rspec (~> 3.0)
73
+ rubocop (~> 1.21)
74
+
75
+ BUNDLED WITH
76
+ 2.5.9
data/README.md CHANGED
@@ -28,9 +28,6 @@ Reputable.configure do |config|
28
28
  # Required: Redis/Dragonfly URL (TLS supported via rediss://)
29
29
  config.redis_url = ENV['REPUTABLE_REDIS_URL']
30
30
 
31
- # Required: Your tenant ID
32
- config.tenant_id = ENV['REPUTABLE_TENANT_ID']
33
-
34
31
  # Optional: Enable logging (logs at debug level)
35
32
  Reputable.logger = Rails.logger
36
33
  end
@@ -82,7 +79,6 @@ All configuration can be set via environment variables:
82
79
  ```bash
83
80
  # Required
84
81
  REPUTABLE_REDIS_URL=rediss://user:password@your-dragonfly.example.com:6379
85
- REPUTABLE_TENANT_ID=your-tenant-id
86
82
 
87
83
  # Optional: Disable entirely (useful for test environments)
88
84
  REPUTABLE_ENABLED=false
@@ -105,9 +101,6 @@ Reputable.configure do |config|
105
101
  # Redis connection (supports redis:// and rediss:// for TLS)
106
102
  config.redis_url = ENV['REPUTABLE_REDIS_URL']
107
103
 
108
- # Your tenant identifier
109
- config.tenant_id = ENV['REPUTABLE_TENANT_ID']
110
-
111
104
  # Connection pool settings
112
105
  config.pool_size = 5 # Number of Redis connections
113
106
  config.pool_timeout = 1.0 # Max wait for connection (seconds)
@@ -284,8 +277,6 @@ track_reputable_request(tags: ['custom:tag'])
284
277
 
285
278
  # Trust methods (after successful actions)
286
279
  trust_current_ip(reason: 'payment_completed', order_id: '123')
287
- trust_current_user(reason: 'email_verified')
288
- trust_current_session(reason: 'captcha_passed')
289
280
 
290
281
  # Challenge/Block methods
291
282
  challenge_current_ip(reason: 'suspicious_activity')
@@ -319,11 +310,9 @@ Reputable.track_request(
319
310
  path: request.path,
320
311
  query: request.query_string,
321
312
  method: request.request_method,
322
- session_id: session.id,
323
- session_present: true,
324
313
  user_agent: request.user_agent,
325
314
  referer: request.referer,
326
- tags: ['ctx:page:product']
315
+ tags: ['view:page:product']
327
316
  )
328
317
 
329
318
  # Asynchronous (fire-and-forget, recommended)
@@ -339,12 +328,6 @@ Reputable.track_request_async(
339
328
  # Trust IP forever (after payment, verification, etc.)
340
329
  Reputable.trust_ip(request.ip, reason: 'payment_completed', order_id: order.id)
341
330
 
342
- # Trust a user
343
- Reputable.trust_user(current_user.id, reason: 'email_verified')
344
-
345
- # Trust a session (default: 24 hour TTL)
346
- Reputable.trust_session(session.id, reason: 'captcha_passed')
347
-
348
331
  # Challenge (require CAPTCHA, etc.)
349
332
  Reputable.challenge_ip(request.ip, reason: 'unusual_activity')
350
333
 
@@ -372,10 +355,6 @@ Reputable.lookup_reputation(:ip, request.ip)
372
355
  # => { status: "trusted_verified", reason: "payment_completed",
373
356
  # source: "app_server", updated_at: 1703123456789,
374
357
  # expires_at: 0, metadata: { order_id: "123" } }
375
-
376
- # User/Session lookups
377
- Reputable.trusted_user?(current_user.id)
378
- Reputable.trusted_session?(session.id)
379
358
  ```
380
359
 
381
360
  ---
@@ -438,10 +417,10 @@ Use tags to classify requests for behavioral analysis:
438
417
 
439
418
  ```ruby
440
419
  # Page context
441
- tags: ['ctx:page:product']
442
- tags: ['ctx:page:checkout']
443
- tags: ['ctx:page:cart']
444
- tags: ['ctx:page:login']
420
+ tags: ['view:page:product']
421
+ tags: ['view:page:checkout']
422
+ tags: ['view:page:cart']
423
+ tags: ['view:page:login']
445
424
 
446
425
  # Traffic source
447
426
  tags: ['trust:channel:email']
@@ -493,7 +472,6 @@ expect(Reputable.lookup_ip('1.2.3.4')).to be_nil
493
472
  ### What's Available from Rails
494
473
 
495
474
  - IP reputation and history
496
- - Session tracking
497
475
  - Request classification
498
476
  - UA churn detection
499
477
  - Cross-request pattern analysis
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -9,12 +9,12 @@ module Reputable
9
9
  # Supports TLS connections with automatic SSL error handling.
10
10
  # All SSL/connection errors are caught and logged, never breaking your app.
11
11
  class Configuration
12
- attr_accessor :redis_url, :redis_options, :tenant_id, :buffer_prefix,
12
+ attr_accessor :redis_url, :redis_options, :buffer_prefix,
13
13
  :request_buffer_key, :reputation_buffer_key,
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
17
+ :on_error, :secret_key, :verification_base_url
18
18
 
19
19
  # Default TTLs in seconds (0 = forever)
20
20
  DEFAULT_TTLS = {
@@ -52,7 +52,6 @@ module Reputable
52
52
  def initialize
53
53
  @redis_url = ENV.fetch("REPUTABLE_REDIS_URL", "redis://127.0.0.1:6379")
54
54
  @redis_options = {}
55
- @tenant_id = ENV.fetch("REPUTABLE_TENANT_ID", "default")
56
55
  @buffer_prefix = "reputable:buffer"
57
56
  @pool_size = Integer(ENV.fetch("REPUTABLE_POOL_SIZE", "5"))
58
57
  @pool_timeout = Float(ENV.fetch("REPUTABLE_POOL_TIMEOUT", "1.0"))
@@ -64,6 +63,8 @@ module Reputable
64
63
  @trusted_proxies = nil # Additional trusted proxy IPs/ranges
65
64
  @ip_header_priority = DEFAULT_IP_HEADERS.dup
66
65
  @on_error = nil # Optional error callback: ->(error, context) { ... }
66
+ @secret_key = ENV["REPUTABLE_SECRET_KEY"]
67
+ @verification_base_url = ENV.fetch("REPUTABLE_VERIFY_URL", "https://api.reputable.click/_reputable/verify")
67
68
  end
68
69
 
69
70
  # Check if Reputable is enabled (can be disabled via ENV)
@@ -75,11 +76,11 @@ module Reputable
75
76
  end
76
77
 
77
78
  def request_buffer_key
78
- @request_buffer_key || "#{buffer_prefix}:requests:#{tenant_id}"
79
+ @request_buffer_key || "#{buffer_prefix}:requests"
79
80
  end
80
81
 
81
82
  def reputation_buffer_key
82
- @reputation_buffer_key || "#{buffer_prefix}:reputation:#{tenant_id}"
83
+ @reputation_buffer_key || "#{buffer_prefix}:reputation"
83
84
  end
84
85
 
85
86
  def default_ttl_for(status)
@@ -45,6 +45,9 @@ module Reputable
45
45
  end
46
46
 
47
47
  def call(env)
48
+ # Check for verification return parameters and verify signature if present
49
+ handle_verification_return(env)
50
+
48
51
  # ALWAYS process the request first - tracking must never block
49
52
  status, headers, response = @app.call(env)
50
53
 
@@ -70,6 +73,26 @@ module Reputable
70
73
  Reputable.logger&.debug("Reputable middleware: #{e.class} - #{e.message}")
71
74
  end
72
75
 
76
+ def handle_verification_return(env)
77
+ request = Rack::Request.new(env)
78
+ # Quick check to avoid overhead
79
+ return unless request.query_string.include?("reputable_status")
80
+
81
+ return unless request.params["reputable_status"] == "pass"
82
+
83
+ if Reputable.verify_redirect_return(request.params)
84
+ env["reputable.verified"] = true
85
+
86
+ # Store in session if available
87
+ if env["rack.session"]
88
+ env["rack.session"]["reputable_verified"] = true
89
+ env["rack.session"]["reputable_verified_at"] = Time.now.to_i
90
+ end
91
+ end
92
+ rescue StandardError => e
93
+ Reputable.logger&.debug("Reputable verification error: #{e.message}")
94
+ end
95
+
73
96
  def skip_request?(env)
74
97
  return true if @skip_if&.call(env)
75
98
 
@@ -109,8 +132,6 @@ module Reputable
109
132
  path: request.path,
110
133
  query: request.query_string.empty? ? nil : request.query_string,
111
134
  method: request.request_method,
112
- session_id: extract_session_id(env),
113
- session_present: session_present?(env),
114
135
  user_agent: env["HTTP_USER_AGENT"],
115
136
  referer: env["HTTP_REFERER"],
116
137
  tags: build_tags(env)
@@ -190,29 +211,6 @@ module Reputable
190
211
  nil
191
212
  end
192
213
 
193
- def extract_session_id(env)
194
- # Try rack.session.id first
195
- return env["rack.session.id"] if env["rack.session.id"]
196
-
197
- # Try to get from rack.session
198
- session = env["rack.session"]
199
- return session.id.to_s if session.respond_to?(:id)
200
-
201
- nil
202
- rescue StandardError
203
- nil
204
- end
205
-
206
- def session_present?(env)
207
- return true if env["rack.session.id"]
208
- return true if env["rack.session"]&.any?
209
- return true if env["HTTP_COOKIE"]&.include?("_session")
210
-
211
- false
212
- rescue StandardError
213
- false
214
- end
215
-
216
214
  def build_tags(env)
217
215
  tags = []
218
216
 
@@ -14,8 +14,6 @@ module Reputable
14
14
  path: request.path,
15
15
  query: request.query_string,
16
16
  method: request.method,
17
- session_id: session.id.to_s,
18
- session_present: session.any?,
19
17
  user_agent: request.user_agent,
20
18
  referer: request.referer,
21
19
  tags: tags,
@@ -32,26 +30,6 @@ module Reputable
32
30
  )
33
31
  end
34
32
 
35
- # Trust the current user
36
- def trust_current_user(reason:, **metadata)
37
- return false unless respond_to?(:current_user) && current_user
38
-
39
- Reputable::Reputation.trust_user(
40
- current_user.id,
41
- reason: reason,
42
- **metadata
43
- )
44
- end
45
-
46
- # Trust the current session
47
- def trust_current_session(reason:, **metadata)
48
- Reputable::Reputation.trust_session(
49
- session.id.to_s,
50
- reason: reason,
51
- **metadata
52
- )
53
- end
54
-
55
33
  # Challenge the current IP
56
34
  def challenge_current_ip(reason:, **metadata)
57
35
  Reputable::Reputation.challenge_ip(
@@ -103,20 +81,6 @@ module Reputable
103
81
  def current_ip_status
104
82
  Reputable::Reputation.lookup_ip(request.remote_ip)
105
83
  end
106
-
107
- # Check if current user is trusted
108
- # @return [Boolean]
109
- def current_user_trusted?
110
- return false unless respond_to?(:current_user) && current_user
111
-
112
- Reputable::Reputation.trusted_user?(current_user.id)
113
- end
114
-
115
- # Check if current session is trusted
116
- # @return [Boolean]
117
- def current_session_trusted?
118
- Reputable::Reputation.trusted_session?(session.id.to_s)
119
- end
120
84
  end
121
85
 
122
86
  # Railtie for automatic Rails integration (only defined when Rails is present)
@@ -130,7 +94,6 @@ module Reputable
130
94
  creds = ::Rails.application.credentials.reputable rescue nil
131
95
  if creds
132
96
  config.redis_url = creds[:redis_url] if creds[:redis_url]
133
- config.tenant_id = creds[:tenant_id] if creds[:tenant_id]
134
97
  end
135
98
  end
136
99
  end
@@ -19,12 +19,12 @@ module Reputable
19
19
  untrusted_ignore
20
20
  ].freeze
21
21
 
22
- VALID_ENTITY_TYPES = %i[ip asn ja4 session user].freeze
22
+ VALID_ENTITY_TYPES = %i[ip asn ja4].freeze
23
23
 
24
24
  class << self
25
25
  # Apply a reputation status to an entity
26
26
  #
27
- # @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4, :session, :user)
27
+ # @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4)
28
28
  # @param entity_id [String] The entity identifier (IP address, ASN, etc.)
29
29
  # @param status [Symbol] Reputation status to apply
30
30
  # @param options [Hash] Additional options
@@ -110,30 +110,6 @@ module Reputable
110
110
  )
111
111
  end
112
112
 
113
- # Trust a user (by internal user ID)
114
- def trust_user(user_id, reason: "verified_user", **metadata)
115
- apply(
116
- entity_type: :user,
117
- entity_id: user_id.to_s,
118
- status: :trusted_verified,
119
- reason: reason,
120
- ttl: 0,
121
- metadata: metadata
122
- )
123
- end
124
-
125
- # Trust a session
126
- def trust_session(session_id, reason: "verified_session", ttl: nil, **metadata)
127
- apply(
128
- entity_type: :session,
129
- entity_id: session_id,
130
- status: :trusted_verified,
131
- reason: reason,
132
- ttl: ttl || (24 * 3600), # Default 24 hours for sessions
133
- metadata: metadata
134
- )
135
- end
136
-
137
113
  # ========================================================================
138
114
  # LOOKUP METHODS (O(1) Redis hash lookups)
139
115
  # ========================================================================
@@ -141,7 +117,7 @@ module Reputable
141
117
  # Lookup the current reputation status for an entity
142
118
  # This is an O(1) Redis HGETALL operation
143
119
  #
144
- # @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4, :session, :user)
120
+ # @param entity_type [Symbol] Type of entity (:ip, :asn, :ja4)
145
121
  # @param entity_id [String] The entity identifier
146
122
  # @return [Hash, nil] Reputation data or nil if not found/expired/error
147
123
  #
@@ -227,42 +203,6 @@ module Reputable
227
203
  lookup_ip(ip) == "untrusted_challenge"
228
204
  end
229
205
 
230
- # Lookup user reputation
231
- #
232
- # @param user_id [String, Integer] User ID
233
- # @return [String, nil] Status string or nil
234
- def lookup_user(user_id)
235
- result = lookup(:user, user_id.to_s)
236
- result&.dig(:status)
237
- end
238
-
239
- # Check if a user is trusted
240
- #
241
- # @param user_id [String, Integer] User ID
242
- # @return [Boolean]
243
- def trusted_user?(user_id)
244
- status = lookup_user(user_id)
245
- status&.start_with?("trusted")
246
- end
247
-
248
- # Lookup session reputation
249
- #
250
- # @param session_id [String] Session ID
251
- # @return [String, nil] Status string or nil
252
- def lookup_session(session_id)
253
- result = lookup(:session, session_id)
254
- result&.dig(:status)
255
- end
256
-
257
- # Check if a session is trusted
258
- #
259
- # @param session_id [String] Session ID
260
- # @return [Boolean]
261
- def trusted_session?(session_id)
262
- status = lookup_session(session_id)
263
- status&.start_with?("trusted")
264
- end
265
-
266
206
  private
267
207
 
268
208
  def valid_entity_type?(entity_type)
@@ -18,9 +18,6 @@ module Reputable
18
18
  # @param options [Hash] Additional options
19
19
  # @option options [String] :query Query string
20
20
  # @option options [String] :method HTTP method (GET, POST, etc.)
21
- # @option options [String] :session_id Session identifier
22
- # @option options [Boolean] :session_present Whether a session cookie exists
23
- # @option options [String] :user_id Internal user ID (for logged-in users)
24
21
  # @option options [String] :fingerprint Browser fingerprint hash
25
22
  # @option options [String] :user_agent User-Agent header
26
23
  # @option options [String] :referer Referer header
@@ -41,11 +38,9 @@ module Reputable
41
38
  # path: request.path,
42
39
  # query: request.query_string,
43
40
  # method: request.request_method,
44
- # session_id: session.id,
45
- # session_present: true,
46
41
  # user_agent: request.user_agent,
47
42
  # referer: request.referer,
48
- # tags: ["ctx:page:product", "trust:channel:organic"]
43
+ # tags: ["view:page:product", "trust:channel:organic"]
49
44
  # )
50
45
  def track_request(ip:, path:, **options)
51
46
  return false unless Reputable.enabled?
@@ -80,9 +75,6 @@ module Reputable
80
75
  path: path,
81
76
  query: options[:query],
82
77
  method: options[:method] || "GET",
83
- session_id: options[:session_id],
84
- session_present: options[:session_present],
85
- user_id: options[:user_id],
86
78
  fingerprint: options[:fingerprint],
87
79
  user_agent: options[:user_agent],
88
80
  referer: options[:referer],
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reputable
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/reputable.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "reputable/version"
4
+ require "json"
5
+ require "base64"
4
6
  require_relative "reputable/configuration"
5
7
  require_relative "reputable/connection"
6
8
  require_relative "reputable/tracker"
@@ -23,14 +25,12 @@ end
23
25
  # @example Basic setup
24
26
  # Reputable.configure do |config|
25
27
  # config.redis_url = "rediss://user:pass@dragonfly.example.com:6379"
26
- # config.tenant_id = "my-tenant"
27
28
  # end
28
29
  #
29
30
  # @example Track a request
30
31
  # Reputable.track_request(
31
32
  # ip: "203.0.113.45",
32
- # path: "/products/123",
33
- # session_present: true
33
+ # path: "/products/123"
34
34
  # )
35
35
  #
36
36
  # @example Apply reputation after payment
@@ -104,14 +104,6 @@ module Reputable
104
104
  Reputation.ignore_ip(ip, reason: reason, **metadata)
105
105
  end
106
106
 
107
- def trust_user(user_id, reason: "verified_user", **metadata)
108
- Reputation.trust_user(user_id, reason: reason, **metadata)
109
- end
110
-
111
- def trust_session(session_id, reason: "verified_session", **metadata)
112
- Reputation.trust_session(session_id, reason: reason, **metadata)
113
- end
114
-
115
107
  # Delegate lookup methods to Reputation module (O(1) Redis lookups)
116
108
  def lookup_reputation(entity_type, entity_id)
117
109
  Reputation.lookup(entity_type, entity_id)
@@ -133,20 +125,73 @@ module Reputable
133
125
  Reputation.challenged_ip?(ip)
134
126
  end
135
127
 
136
- def lookup_user(user_id)
137
- Reputation.lookup_user(user_id)
138
- end
139
-
140
- def trusted_user?(user_id)
141
- Reputation.trusted_user?(user_id)
142
- end
143
-
144
- def lookup_session(session_id)
145
- Reputation.lookup_session(session_id)
146
- end
147
-
148
- def trusted_session?(session_id)
149
- Reputation.trusted_session?(session_id)
128
+ # Generate a signed verification URL
129
+ def verification_url(return_url:, failure_url: nil, session_id: nil)
130
+ secret = configuration.secret_key
131
+ unless secret
132
+ logger&.warn "Reputable: Missing secret_key, cannot generate verification URL"
133
+ return return_url
134
+ end
135
+
136
+ base_url = configuration.verification_base_url
137
+
138
+ # JWT Header
139
+ header = { alg: "HS256", typ: "JWT" }
140
+ encoded_header = base64url_encode(JSON.generate(header))
141
+
142
+ # JWT Payload
143
+ payload = {
144
+ returnUrl: return_url,
145
+ failureUrl: failure_url,
146
+ sessionId: session_id,
147
+ iat: Time.now.to_i
148
+ }
149
+ encoded_payload = base64url_encode(JSON.generate(payload))
150
+
151
+ # Signature
152
+ data = "#{encoded_header}.#{encoded_payload}"
153
+ signature = OpenSSL::HMAC.digest("SHA256", secret, data)
154
+ encoded_signature = base64url_encode(signature)
155
+
156
+ token = "#{data}.#{encoded_signature}"
157
+
158
+ "#{base_url}?token=#{token}"
159
+ end
160
+
161
+ # Verify the signature of a redirect return
162
+ # @param params [Hash] Request query parameters
163
+ # @return [Boolean] true if valid logic and passed signature check
164
+ def verify_redirect_return(params)
165
+ status = params["reputable_status"]
166
+ session_id = params["reputable_session_id"]
167
+ signature = params["reputable_signature"]
168
+
169
+ return false unless status && session_id && signature
170
+
171
+ secret = configuration.secret_key
172
+ unless secret
173
+ logger&.warn "Reputable: Missing secret_key, cannot verify redirect"
174
+ return false
175
+ end
176
+
177
+ data = "#{status}:#{session_id}"
178
+ expected_signature = OpenSSL::HMAC.hexdigest("SHA256", secret, data)
179
+
180
+ secure_compare(expected_signature, signature)
181
+ end
182
+
183
+ private
184
+
185
+ def base64url_encode(str)
186
+ Base64.urlsafe_encode64(str).gsub("=", "")
187
+ end
188
+
189
+ def secure_compare(a, b)
190
+ return false unless a.bytesize == b.bytesize
191
+ l = a.unpack "C#{a.bytesize}"
192
+ res = 0
193
+ b.each_byte { |byte| res |= byte ^ l.shift }
194
+ res == 0
150
195
  end
151
196
  end
152
197
  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.1
4
+ version: 0.1.2
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-20 00:00:00.000000000 Z
11
+ date: 2025-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -122,8 +122,11 @@ executables: []
122
122
  extensions: []
123
123
  extra_rdoc_files: []
124
124
  files:
125
+ - ".DS_Store"
125
126
  - Gemfile
127
+ - Gemfile.lock
126
128
  - README.md
129
+ - Rakefile
127
130
  - lib/reputable.rb
128
131
  - lib/reputable/configuration.rb
129
132
  - lib/reputable/connection.rb