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.
@@ -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