robust_server_socket 0.3.2 → 0.4.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.
@@ -1,5 +1,3 @@
1
- require_relative 'secure_token/cacher'
2
-
3
1
  module RobustServerSocket
4
2
  class RateLimiter
5
3
  RateLimitExceeded = Class.new(StandardError)
@@ -8,6 +6,7 @@ module RobustServerSocket
8
6
  def check!(client_name)
9
7
  unless (attempts = check(client_name))
10
8
  actual_attempts = current_attempts(client_name)
9
+
11
10
  raise RateLimitExceeded, "Rate limit exceeded for #{client_name}: #{actual_attempts}/#{max_requests} requests per #{window_seconds}s"
12
11
  end
13
12
 
@@ -15,8 +14,6 @@ module RobustServerSocket
15
14
  end
16
15
 
17
16
  def check(client_name)
18
- return 0 unless rate_limit_enabled?
19
-
20
17
  key = rate_limit_key(client_name)
21
18
  attempts = increment_attempts(key)
22
19
 
@@ -26,18 +23,16 @@ module RobustServerSocket
26
23
  end
27
24
 
28
25
  def current_attempts(client_name)
29
- return 0 unless rate_limit_enabled?
30
-
31
26
  key = rate_limit_key(client_name)
32
- SecureToken::Cacher.get(key).to_i
27
+ Cacher.get(key).to_i
33
28
  end
34
29
 
35
30
  def reset!(client_name)
36
31
  key = rate_limit_key(client_name)
37
- SecureToken::Cacher.with_redis do |conn|
32
+ Cacher.with_redis do |conn|
38
33
  conn.del(key)
39
34
  end
40
- rescue SecureToken::Cacher::RedisConnectionError => e
35
+ rescue Cacher::RedisConnectionError => e
41
36
  handle_redis_error(e, 'reset')
42
37
  nil
43
38
  end
@@ -45,13 +40,13 @@ module RobustServerSocket
45
40
  private
46
41
 
47
42
  def increment_attempts(key)
48
- SecureToken::Cacher.with_redis do |conn|
43
+ Cacher.with_redis do |conn|
49
44
  attempts = conn.incr(key)
50
45
  # Set expiration only on first attempt to ensure atomic window
51
46
  conn.expire(key, window_seconds) if attempts == 1
52
47
  attempts
53
48
  end
54
- rescue SecureToken::Cacher::RedisConnectionError => e
49
+ rescue Cacher::RedisConnectionError => e
55
50
  handle_redis_error(e, 'increment_attempts')
56
51
  0 # Fail open: allow request if Redis is down
57
52
  end
@@ -60,10 +55,6 @@ module RobustServerSocket
60
55
  "rate_limit:#{client_name}"
61
56
  end
62
57
 
63
- def rate_limit_enabled?
64
- RobustServerSocket.configuration.rate_limit_enabled
65
- end
66
-
67
58
  def max_requests
68
59
  RobustServerSocket.configuration.rate_limit_max_requests
69
60
  end
@@ -1,5 +1,3 @@
1
- require 'openssl'
2
-
3
1
  module RobustServerSocket
4
2
  module SecureToken
5
3
  BASE64_REGEXP = /\A[A-Za-z0-9+\/]*={0,2}\z/.freeze
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'base64'
4
+ require 'openssl'
5
+ require 'redis'
6
+ require 'connection_pool'
7
+
3
8
  require_relative 'robust_server_socket/configuration'
9
+ require_relative 'robust_server_socket/secure_token/decrypt'
10
+ require_relative 'robust_server_socket/client_token'
4
11
 
5
12
  module RobustServerSocket
6
13
  extend RobustServerSocket::Configuration
@@ -10,12 +17,21 @@ module RobustServerSocket
10
17
  def load!
11
18
  raise 'You must correctly configure RobustServerSocket first!' unless configured?
12
19
 
13
- require 'openssl'
14
- require 'base64'
15
- require 'redis'
16
- require 'connection_pool'
20
+ configuration.using_modules.each do |mod|
21
+ raise ArgumentError, 'Module must be a Symbol!' unless mod.is_a?(Symbol)
22
+
23
+ require_relative "robust_server_socket/modules/#{mod}"
24
+ ClientToken.include eval(mod.to_s.split('_').map(&:capitalize).unshift('Modules::').join)
25
+ end
26
+
27
+ ClientToken.class_eval(<<~METHOD)
28
+ def modules_checks
29
+ #{(RobustServerSocket.configuration._modules_check_rows.empty? ? ['true'] : RobustServerSocket.configuration._modules_check_rows.map(&:strip)).join(' && ')}
30
+ end
17
31
 
18
- require_relative 'robust_server_socket/rate_limiter'
19
- require_relative 'robust_server_socket/client_token'
32
+ def modules_checks!
33
+ #{(RobustServerSocket.configuration._bang_modules_check_rows.empty? ? ['true'] : RobustServerSocket.configuration._bang_modules_check_rows).join}
34
+ end
35
+ METHOD
20
36
  end
21
37
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RobustServerSocket
4
- VERSION = '0.3.2'
4
+ VERSION = '0.4.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robust_server_socket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - tee_zed
@@ -75,12 +75,17 @@ files:
75
75
  - ".rspec"
76
76
  - CODE_OF_CONDUCT.md
77
77
  - LICENSE.txt
78
+ - README.en.md
79
+ - README.md
78
80
  - Rakefile
79
81
  - lib/robust_server_socket.rb
82
+ - lib/robust_server_socket/cacher.rb
80
83
  - lib/robust_server_socket/client_token.rb
81
84
  - lib/robust_server_socket/configuration.rb
85
+ - lib/robust_server_socket/modules/client_auth_protection.rb
86
+ - lib/robust_server_socket/modules/dos_attack_protection.rb
87
+ - lib/robust_server_socket/modules/replay_attack_protection.rb
82
88
  - lib/robust_server_socket/rate_limiter.rb
83
- - lib/robust_server_socket/secure_token/cacher.rb
84
89
  - lib/robust_server_socket/secure_token/decrypt.rb
85
90
  - lib/version.rb
86
91
  - robust_server_socket.gemspec
@@ -101,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
106
  - !ruby/object:Gem::Version
102
107
  version: '0'
103
108
  requirements: []
104
- rubygems_version: 3.6.9
109
+ rubygems_version: 4.0.6
105
110
  specification_version: 4
106
111
  summary: Robust Server Socket gem for RobustPro
107
112
  test_files: []
@@ -1,138 +0,0 @@
1
- require 'redis'
2
- require 'connection_pool'
3
-
4
- module RobustServerSocket
5
- module SecureToken
6
- module Cacher
7
- class RedisConnectionError < StandardError; end
8
-
9
- class << self
10
- # Atomically validate token: check expiration and usage, then mark as used
11
- # Returns: 'ok', 'stale', or 'used'
12
- def atomic_validate_and_log(key, ttl, timestamp, expiration_time)
13
- current_time = Time.now.utc.to_i
14
-
15
- redis.with do |conn|
16
- conn.eval(
17
- lua_atomic_validate,
18
- keys: [key],
19
- argv: [ttl, timestamp, expiration_time, current_time]
20
- )
21
- end
22
- rescue ::Redis::BaseConnectionError => e
23
- handle_redis_error(e, 'atomic_validate_and_log')
24
- raise RedisConnectionError, "Failed to validate token: #{e.message}"
25
- end
26
-
27
- def incr(key, ttl = nil)
28
- ttl_value = ttl || ttl_seconds
29
-
30
- redis.with do |conn|
31
- conn.pipelined do |pipeline|
32
- pipeline.incrby(key, 1)
33
- pipeline.expire(key, ttl_value)
34
- end
35
- end
36
- rescue ::Redis::BaseConnectionError => e
37
- handle_redis_error(e, 'incr')
38
- raise RedisConnectionError, "Failed to increment key: #{e.message}"
39
- end
40
-
41
- def get(key)
42
- redis.with do |conn|
43
- conn.get(key)
44
- end
45
- rescue ::Redis::BaseConnectionError => e
46
- handle_redis_error(e, 'get')
47
- nil # Fallback for reads
48
- end
49
-
50
- def health_check
51
- redis.with do |conn|
52
- conn.ping == 'PONG'
53
- end
54
- rescue ::Redis::BaseConnectionError
55
- false
56
- end
57
-
58
- def with_redis(&block)
59
- redis.with(&block)
60
- rescue ::Redis::BaseConnectionError => e
61
- handle_redis_error(e, 'with_redis')
62
- raise ::RedisConnectionError, "Redis operation failed: #{e.message}"
63
- end
64
-
65
- # Clear cached Redis connection pool (useful for hot reloading in development)
66
- def clear_redis_pool_cache!
67
- @pool = nil
68
- end
69
-
70
- private
71
-
72
- def lua_atomic_validate
73
- <<~LUA
74
- local key = KEYS[1]
75
- local ttl = tonumber(ARGV[1])
76
- local timestamp = tonumber(ARGV[2])
77
- local expiration_time = tonumber(ARGV[3])
78
- local current_time = tonumber(ARGV[4])
79
-
80
- -- Check if token is expired
81
- if expiration_time <= (current_time - timestamp) then
82
- return 'stale'
83
- end
84
-
85
- -- Check if token was already used
86
- local current = redis.call('GET', key)
87
- if current and tonumber(current) > 0 then
88
- return 'used'
89
- end
90
-
91
- -- Mark token as used
92
- redis.call('INCRBY', key, 1)
93
- redis.call('EXPIRE', key, ttl)
94
-
95
- return 'ok'
96
- LUA
97
- end
98
-
99
- def ttl_seconds
100
- ::RobustServerSocket.configuration.token_expiration_time + 60
101
- end
102
-
103
- # Cache Redis connection pool at module level for the lifetime of the Rails process
104
- # This avoids recreating the connection pool on every Redis operation
105
- def redis
106
- @pool ||= ::ConnectionPool::Wrapper.new(**pool_config) do
107
- ::Redis.new(redis_config)
108
- end
109
- end
110
-
111
- def pool_config
112
- {
113
- size: ENV.fetch('REDIS_POOL_SIZE', 25).to_i,
114
- timeout: ENV.fetch('REDIS_POOL_TIMEOUT', 1).to_f
115
- }
116
- end
117
-
118
- def redis_config
119
- config = {
120
- url: ::RobustServerSocket.configuration.redis_url,
121
- reconnect_attempts: 3,
122
- timeout: 1.0,
123
- connect_timeout: 2.0
124
- }
125
-
126
- password = ::RobustServerSocket.configuration.redis_pass
127
- config[:password] = password if password && !password.empty?
128
-
129
- config
130
- end
131
-
132
- def handle_redis_error(error, operation)
133
- warn "Redis operation '#{operation}' failed: #{error.class} - #{error.message}"
134
- end
135
- end
136
- end
137
- end
138
- end