robust_server_socket 0.3.3 → 0.4.3
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/.rubocop.yml +9 -0
- data/.ruby-version +1 -0
- data/README.en.md +170 -494
- data/README.md +156 -244
- data/Rakefile +4 -8
- data/lib/robust_server_socket/cacher.rb +142 -0
- data/lib/robust_server_socket/client_token.rb +18 -43
- data/lib/robust_server_socket/configuration.rb +35 -4
- data/lib/robust_server_socket/modules/client_auth_protection.rb +22 -0
- data/lib/robust_server_socket/modules/rate_limit_protection.rb +23 -0
- data/lib/robust_server_socket/modules/replay_attack_protection.rb +48 -0
- data/lib/robust_server_socket/rate_limiter.rb +13 -37
- data/lib/robust_server_socket/secure_token/decrypt.rb +4 -8
- data/lib/robust_server_socket.rb +23 -7
- data/lib/version.rb +1 -1
- data/robust_server_socket.gemspec +12 -12
- metadata +10 -5
- data/lib/robust_server_socket/secure_token/cacher.rb +0 -138
|
@@ -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
|