robust_server_socket 0.3.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 626993a0fda547dc940291eb09015b688a1f761cb5345664488996ed28a1906c
4
- data.tar.gz: c5bb810563a9e6682c021b6553296477b3e34ecffeeecc2121534b8e397c7377
3
+ metadata.gz: b7b680d77f57104621b01b7abca736c21d819abc4e59355f5e70c3dff05df2eb
4
+ data.tar.gz: 15d86badaa9f0c76c97f330886188a3517b7d4dfd38d94c27f7c000cee1088d3
5
5
  SHA512:
6
- metadata.gz: 9361de5026c683ce6e4a92aad34b8a3358248f4774bff96eb423148ee5daf2556fe1c16f7bd98658acba11218b1e6eae67f8f3af714970474070aa256132e2e7
7
- data.tar.gz: a24681663c355f6117040fcaa538b73fa5bf9a4ff43edf6e962c9d8a3727e2d107bc222233efddffefd5fdb76a282c16c56e1c7eb323488168274175e0f29993
6
+ metadata.gz: cbc390327dc4b6d4f382333c822cc367d0b936958d6ad8def8149e6b55a97d4137ff30690b3c3f4d9156120726a02ef12550e65065effb827a0adbe1bd196597
7
+ data.tar.gz: f8bc821809cabf3d0769c37549160f3d7b1456c0bc5bb3b787530171ba0eb83389fb9b4a4f65f94abc182fea34cabc740c1ca54ccd0f08d7578d5407956b0865
data/README.en.md CHANGED
@@ -2,7 +2,70 @@
2
2
 
3
3
  Gem for inter-service authorization, used in pair with RobustClientSocket
4
4
 
5
- ### ⚠️ Not Production Tested (yet)
5
+ ### ⚠️ Not Production Tested (yet) but tested in staging environment
6
+
7
+ `Not vibecoded`
8
+
9
+ ## WHY
10
+
11
+ ### The Problem
12
+
13
+ When building microservice architecture, the server side faces:
14
+
15
+ - **Lack of verification**: How to verify that a request came from a trusted service?
16
+ - **Replay attacks**: Intercepted requests can be replayed
17
+ - **DDoS attacks**: Need to limit request frequency
18
+ - **Boilerplate code**: Repetitive validation logic in every service
19
+
20
+ ### The Solution
21
+
22
+ RobustServerSocket provides:
23
+
24
+ - **RSA decryption**: Token authenticity verification
25
+ - **Client whitelist**: Only authorized services allowed
26
+ - **Replay protection**: Blacklist of used tokens in Redis
27
+ - **Rate limiting**: Per-client request limits
28
+
29
+ ## HOW IT WORKS
30
+
31
+ ### Architecture
32
+
33
+ ```
34
+ Incoming request with Secure-Token
35
+
36
+ v
37
+ ┌──────────────────────────────┐
38
+ │ RobustServerSocket │
39
+ │ │
40
+ │ 1. RSA Decrypt │
41
+ │ 2. Validate Format │
42
+ │ 3. Check Client Whitelist │
43
+ │ 4. Check Rate Limit │
44
+ │ 5. Check Token Reuse │
45
+ │ 6. Check Token Expiration │
46
+ └──────────────┬───────────────┘
47
+
48
+ ┌────────┼────────┐
49
+ v v
50
+ ✅ Success ❌ Error
51
+ (continue) (401/403/429)
52
+ ```
53
+
54
+ ### Validation Flow
55
+
56
+ 1. **Decryption**: Base64 decode → RSA decrypt with private key
57
+ 2. **Parsing**: Extract `{client_name}_{timestamp}` from token
58
+ 3. **Whitelist**: Verify client_name is in `allowed_services`
59
+ 4. **Rate limit**: Check request count within window
60
+ 5. **Replay check**: Verify token hasn't been used (Redis)
61
+ 6. **Staleness**: Verify timestamp is current
62
+
63
+ ### Modular System
64
+
65
+ Checks are enabled via `using_modules`:
66
+ - `:client_auth_protection` — client whitelist
67
+ - `:replay_attack_protection` — prevent token reuse
68
+ - `:dos_attack_protection` — rate limiting
6
69
 
7
70
  ## 📋 Table of Contents
8
71
 
@@ -84,12 +147,7 @@ RobustServerSocket.configure do |c|
84
147
  # Redis for storing used tokens
85
148
  c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
86
149
  c.redis_pass = ENV['REDIS_PASSWORD'] # can be nil for local development
87
-
88
- # OPTIONAL PARAMETERS: Rate Limiting
89
-
90
- # Enable rate limiting (default: false)
91
- c.rate_limit_enabled = true
92
-
150
+
93
151
  # Maximum requests per time window (default: 100)
94
152
  c.rate_limit_max_requests = 100
95
153
 
@@ -169,74 +227,6 @@ rescue => e
169
227
  end
170
228
  ```
171
229
 
172
- ### Manual Rate Limiting
173
-
174
- ```ruby
175
- # Check current attempt count
176
- attempts = RobustServerSocket::RateLimiter.current_attempts('core')
177
- puts "Core service made #{attempts} requests"
178
-
179
- # Reset counter for specific client
180
- RobustServerSocket::RateLimiter.reset!('core')
181
-
182
- # Check with exception on exceeded limit
183
- begin
184
- RobustServerSocket::RateLimiter.check!('core')
185
- rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
186
- puts e.message # "Rate limit exceeded for core: 101/100 requests per 60s"
187
- end
188
-
189
- # Check without exception (returns false when exceeded)
190
- if RobustServerSocket::RateLimiter.check('core')
191
- # Limit not exceeded
192
- else
193
- # Limit exceeded
194
- end
195
- ```
196
-
197
- ## 🚦 Rate Limiting (Request Rate Limiting)
198
-
199
- ### How It Works
200
-
201
- Rate Limiter protects your service from overload by limiting the number of requests from each client within a time window.
202
-
203
- **Characteristics:**
204
- - **Per-client counters**: Separate counter for each service
205
- - **Sliding window**: Window resets automatically after time expires
206
- - **Atomicity**: Increment and check are performed atomically (Redis LUA script)
207
- - **Fail-open**: When Redis is unavailable, requests are allowed (not blocked)
208
-
209
- ### Limit Configuration
210
-
211
- ```ruby
212
- RobustServerSocket.configure do |c|
213
- # Enable rate limiting
214
- c.rate_limit_enabled = true
215
-
216
- # For low-traffic microservices
217
- c.rate_limit_max_requests = 50
218
- c.rate_limit_window_seconds = 60
219
- end
220
- ```
221
-
222
- ### Monitoring
223
-
224
- ```ruby
225
- # Check current state
226
- clients = ['core', 'payments', 'notifications']
227
- clients.each do |client|
228
- attempts = RobustServerSocket::RateLimiter.current_attempts(client)
229
- max = RobustServerSocket.configuration.rate_limit_max_requests
230
- puts "#{client}: #{attempts}/#{max}"
231
- end
232
-
233
- # In metrics (Prometheus, StatsD, etc.)
234
- clients.each do |client|
235
- attempts = RobustServerSocket::RateLimiter.current_attempts(client)
236
- Metrics.gauge("rate_limiter.attempts.#{client}", attempts)
237
- end
238
- ```
239
-
240
230
  ## ❌ Error Handling
241
231
 
242
232
  ### Exception Types
@@ -292,32 +282,6 @@ def rate_limit_response(exception)
292
282
  end
293
283
  ```
294
284
 
295
- ## 💡 Usage Recommendations
296
-
297
- ### 1. Key Management
298
-
299
- **✅ DO:**
300
- ```ruby
301
- # Store keys in environment variables
302
- c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
303
-
304
- # Use secrets management (AWS Secrets Manager, Vault, etc.)
305
- c.private_key = Rails.application.credentials.dig(:robust_server, :private_key)
306
-
307
- # Generate keys correctly
308
- # openssl genrsa -out private_key.pem 2048
309
- # openssl rsa -in private_key.pem -pubout -out public_key.pem
310
- ```
311
-
312
- **❌ DON'T:**
313
- ```ruby
314
- # DON'T commit keys to git
315
- c.private_key = "-----BEGIN PRIVATE KEY-----\nMII..."
316
-
317
- # DON'T use weak keys
318
- # Minimum RSA-2048, RSA-4096 recommended for high security
319
- ```
320
-
321
285
  ### 2. Redis Configuration
322
286
 
323
287
  **✅ DO:**
@@ -482,7 +446,7 @@ REDIS_POOL = ConnectionPool.new(size: 25, timeout: 5) do
482
446
  )
483
447
  end
484
448
 
485
- # In RobustServerSocket::SecureToken::Cacher
449
+ # In RobustServerSocket::Cacher
486
450
  def self.with_redis
487
451
  REDIS_POOL.with do |redis|
488
452
  yield redis
@@ -505,7 +469,6 @@ end
505
469
  ```ruby
506
470
  RobustServerSocket.configure do |c|
507
471
  # For high-load systems
508
- c.rate_limit_enabled = true
509
472
  c.rate_limit_max_requests = 1000 # Increase limit
510
473
  c.rate_limit_window_seconds = 60
511
474
 
@@ -638,7 +601,6 @@ end
638
601
 
639
602
  ## 📚 Additional Resources
640
603
 
641
- - [BENCHMARK_ANALYSIS.md](BENCHMARK_ANALYSIS.md)
642
604
  - [RobustClientSocket documentation](../robust_client_socket/README.md)
643
605
  - [RSA encryption best practices](https://www.openssl.org/docs/)
644
606
  - [Redis security guide](https://redis.io/topics/security)
@@ -649,4 +611,4 @@ See [MIT-LICENSE](MIT-LICENSE) file
649
611
 
650
612
  ## 🐛 Bugs and Suggestions
651
613
 
652
- Report issues through your repository's issue tracker.
614
+ Report issues to my telegram @cruel_mango or to email tee0zed@gmail.com
data/README.md CHANGED
@@ -1,8 +1,71 @@
1
1
  # RobustServerSocket
2
2
 
3
- Gem для межсервисной авторизации, используется в паре с RobustClientSocket
3
+ Gem для межсервисной аутентификации, используется в паре с RobustClientSocket
4
4
 
5
- ### ⚠️ Not Production Tested (yet)
5
+ ### ⚠️ Not Production Tested (yet) but tested in staging environment
6
+
7
+ `Not vibecoded`
8
+
9
+ ## ПОЧЕМУ (WHY)
10
+
11
+ ### Проблема
12
+
13
+ При построении микросервисной архитектуры серверная сторона сталкивается с:
14
+
15
+ - **Отсутствием верификации**: Как проверить, что запрос пришёл от доверенного сервиса?
16
+ - **Replay-атаками**: Перехваченные запросы могут быть повторены
17
+ - **DDoS-атаками**: Необходимость ограничения частоты запросов
18
+ - **Boilerplate кодом**: Повторяющаяся логика валидации в каждом сервисе
19
+
20
+ ### Решение
21
+
22
+ RobustServerSocket предоставляет:
23
+
24
+ - **RSA-дешифрование**: Проверка подлинности токенов
25
+ - **Whitelist клиентов**: Только разрешённые сервисы
26
+ - **Защиту от replay**: Блэклист использованных токенов в Redis
27
+ - **Rate limiting**: Ограничение запросов на клиента
28
+
29
+ ## КАК ЭТО РАБОТАЕТ (HOW)
30
+
31
+ ### Архитектура
32
+
33
+ ```
34
+ Входящий запрос с Secure-Token
35
+
36
+ v
37
+ ┌──────────────────────────────┐
38
+ │ RobustServerSocket │
39
+ │ │
40
+ │ 1. RSA Decrypt │
41
+ │ 2. Validate Format │
42
+ │ 3. Check Client Whitelist │
43
+ │ 4. Check Rate Limit │
44
+ │ 5. Check Token Reuse │
45
+ │ 6. Check Token Expiration │
46
+ └──────────────┬───────────────┘
47
+
48
+ ┌────────┼────────┐
49
+ v v
50
+ ✅ Success ❌ Error
51
+ (continue) (401/403/429)
52
+ ```
53
+
54
+ ### Поток валидации
55
+
56
+ 1. **Расшифровка**: Base64 decode → RSA decrypt с приватным ключом
57
+ 2. **Парсинг**: Извлечение `{client_name}_{timestamp}` из токена
58
+ 3. **Whitelist**: Проверка client_name в `allowed_services`
59
+ 4. **Rate limit**: Проверка количества запросов в окне
60
+ 5. **Replay check**: Проверка, что токен не использован (Redis)
61
+ 6. **Staleness**: Проверка timestamp на актуальность
62
+
63
+ ### Модульная система
64
+
65
+ Проверки подключаются через `using_modules`:
66
+ - `:client_auth_protection` — whitelist клиентов
67
+ - `:replay_attack_protection` — защита от повторного использования
68
+ - `:dos_attack_protection` — rate limiting
6
69
 
7
70
  ## 📋 Содержание
8
71
 
@@ -19,33 +82,27 @@ RobustServerSocket реализует многоуровневую систем
19
82
  ### 1. Криптографическая защита
20
83
  - **RSA-2048 шифрование**: Используется пара ключей RSA с минимальной длиной 2048 бит
21
84
  - **Валидация ключей**: Автоматическая проверка размера ключа при конфигурации
22
- - **Асимметричное шифрование**: Приватный ключ на сервере, публичный — у клиентов
23
-
24
- ### 2. Защита от повторного использования токенов
25
- - **Одноразовые токены**: Каждый токен может быть использован только один раз
26
- - **Blacklist в Redis**: Использованные токены автоматически добавляются в черный список
27
- - **Атомарная проверка**: Race condition защищена благодаря Redis Lua скриптам
28
-
29
- ### 3. Временные ограничения
30
- - **Expiration time**: Настраиваемое время жизни токена
31
- - **Автоматическое истечение**: Токены автоматически становятся недействительными после истечения времени
32
- - **Защита от replay attacks**: Старые токены не могут быть использованы повторно
33
85
 
34
- ### 4. Контроль доступа
35
- - **Whitelist клиентов**: Только авторизованные сервисы могут подключаться
86
+ ### 2. Контроль доступа
87
+ - **Whitelist клиентов**: Только авторизованные сервисы могут подключаться, при включенном модуле `:client_auth_protection`
36
88
  - **Идентификация по имени**: Каждый клиент должен быть явно указан в `allowed_services`
37
- - **Валидация формата токена**: Строгая проверка структуры токена
38
89
 
39
- ### 5. Rate Limiting (опционально)
40
- - **Защита от DDoS**: Ограничение количества запросов от каждого клиента
41
- - **Sliding window**: Справедливое распределение запросов во времени
90
+ ### 3. Защита от перехвата токенов (replay-attack)
91
+ - **Защита от replay-attack**: использованные токены добавляются в черный список и имеют время жизни, при включенном модуле `:replay_attack_protection`
92
+ - **Staleness**: Токены автоматически становятся недействительными после истечения времени
93
+ - **Blacklisting использованных токенов**: Redis как хранилище черного списка
94
+ - **Настраиваемое время жизни токенов в черном списке**: по умолчанию 10 минут
95
+ - **Настраиваемое ttl токена**: Должно быть в окно ответа между серверами, по умолчанию 10сек
96
+
97
+ ### 4. Защита от DoS
98
+ - **Защита от DDoS**: Ограничение количества запросов от каждого клиента, при включенном модуле `:dos_attack_protection`
99
+ - **Sliding window**: Распределение запросов во времени
42
100
  - **Fail-open стратегия**: Если Redis недоступен, запросы пропускаются (для надёжности)
43
- - **Per-client лимиты**: Индивидуальные счётчики для каждого клиента
44
101
 
45
- ### 6. Защита от инъекций
46
- - **Валидация входных данных**: Проверка типа, длины и формата токенов
47
- - **Максимальная длина токена**: Ограничение 2048 символов
48
- - **Проверка на пустые значения**: Отклонение пустых или некорректных токенов
102
+ -
103
+ ### 5. Защита от SSL stripping MITM attack
104
+ - **Принудительное HTTPS на сервере**: Все запросы должны быть совершены по HTTPS, чтобы защитить токены от перехвата
105
+ - **Включается на RobustClientSoket, ключём `ssl_verify: true`**
49
106
 
50
107
  ## 📦 Установка
51
108
 
@@ -53,13 +110,21 @@ RobustServerSocket реализует многоуровневую систем
53
110
  gem 'robust_server_socket'
54
111
  ```
55
112
 
113
+ и на клиенте:
114
+ ```ruby
115
+ gem 'robust_client_socket'
116
+ ```
56
117
  ## ⚙️ Конфигурация
57
118
 
58
119
  Создайте файл `config/initializers/robust_server_socket.rb`:
59
120
 
60
121
  ```ruby
61
122
  RobustServerSocket.configure do |c|
62
- # ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ
123
+ c.using_modules = %i[
124
+ :client_auth_protection
125
+ :replay_attack_protection
126
+ :dos_attack_protection
127
+ ]
63
128
 
64
129
  # Приватный ключ сервиса (RSA-2048 или выше)
65
130
  c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
@@ -67,15 +132,14 @@ RobustServerSocket.configure do |c|
67
132
 
68
133
  # Список разрешённых сервисов (whitelist)
69
134
  # Должен совпадать с именами RobustClientSocket клиента
135
+ # Для client_auth_protection
70
136
  c.allowed_services = %w[core payments notifications]
71
137
 
72
- # Redis для работы replay-attack protection и throttling
138
+ # Redis для работы replay_attack_protection и ddos_attack_protection
73
139
  c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
74
140
  c.redis_pass = ENV['REDIS_PASSWORD']
75
141
 
76
- # НЕОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ
77
- # Включить ограничение частоты запросов (по умолчанию: false)
78
- c.rate_limit_enabled = true
142
+ # ddos_attack_protection
79
143
  # Максимальное количество запросов в окне времени (по умолчанию: 100)
80
144
  c.rate_limit_max_requests = 100
81
145
  # Размер временного окна в секундах (по умолчанию: 60)
@@ -85,19 +149,21 @@ end
85
149
  # Загрузка конфигурации с валидацией
86
150
  RobustServerSocket.load!
87
151
  ```
152
+ `using_modules` - это используемые модули, добавление или удаление которых изменит поведение гема.
88
153
 
89
154
  ### Опции конфигурации сервиса
90
155
 
91
- | Параметр | Тип | Обязательный | Default | Описание |
92
- |----------|-----|--------------|---------|----------|
93
- | `private_key` | String | ✅ | - | Приватный RSA ключ сервиса (RSA-2048 или выше) |
94
- | `token_expiration_time` | Integer | ✅ | - | Время жизни токена в секундах |
95
- | `allowed_services` | Array | ✅ | - | Список разрешённых сервисов (whitelist) |
96
- | `redis_url` | String | | - | URL для подключения к Redis |
97
- | `redis_pass` | String | | nil | Пароль для Redis (если требуется) |
98
- | `rate_limit_enabled` | Boolean | ❌ | false | Включить ограничение частоты запросов |
99
- | `rate_limit_max_requests` | Integer | ❌ | 100 | Максимальное количество запросов в окне времени |
100
- | `rate_limit_window_seconds` | Integer | ❌ | 60 | Размер временного окна в секундах |
156
+ | Параметр | Тип | Обязательный | Default | Описание |
157
+ |-----------------------------|-----|-------------|------------------------------------------------------------------------------|-------------------------------------------------|
158
+ | `private_key` | String | ✅ | - | Приватный RSA ключ сервиса (RSA-2048 или выше) |
159
+ | `token_expiration_time` | Integer | ✅ | 10 | Время жизни токена в секундах |
160
+ | `store_used_token_time` | Integer | ✅ | 600 | Время жизни токена в блеклисте в секундах |
161
+ | `allowed_services` | Array | | - | Список разрешённых сервисов (whitelist) |
162
+ | `redis_url` | String | | - | URL для подключения к Redis |
163
+ | `using_modules` | Array | ❌ | [:client_auth_protection, :replay_attack_protection, :dos_attack_protection] | Используемые модули |
164
+ | `redis_pass` | String | ❌ | nil | Пароль для Redis (если требуется) |
165
+ | `rate_limit_max_requests` | Integer | ❌ | 100 | Максимальное количество запросов в окне времени |
166
+ | `rate_limit_window_seconds` | Integer | ❌ | 60 | Размер временного окна в секундах |
101
167
 
102
168
  ## 🚀 Использование
103
169
 
@@ -167,61 +233,6 @@ rescue => e
167
233
  end
168
234
  ```
169
235
 
170
- ### Rate Limiting вручную
171
-
172
- ```ruby
173
- # Проверка текущего количества попыток
174
- attempts = RobustServerSocket::RateLimiter.current_attempts('core')
175
- puts "Core service made #{attempts} requests"
176
-
177
- # Сброс счётчика для конкретного клиента
178
- RobustServerSocket::RateLimiter.reset!('core')
179
-
180
- # Проверка с исключением при превышении
181
- begin
182
- RobustServerSocket::RateLimiter.check!('core')
183
- rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
184
- puts e.message # "Rate limit exceeded for core: 101/100 requests per 60s"
185
- end
186
-
187
- # Проверка без исключения (возвращает false при превышении)
188
- if RobustServerSocket::RateLimiter.check('core')
189
- # Лимит не превышен
190
- else
191
- # Лимит превышен
192
- end
193
- ```
194
-
195
- ## 🚦 Rate Limiting (Ограничение частоты запросов)
196
-
197
- ### Принцип работы
198
-
199
- Rate Limiter защищает ваш сервис от перегрузки, ограничивая количество запросов от каждого клиента в определённом временном окне.
200
-
201
- **Характеристики:**
202
- - **Per-client counters**: Отдельный счётчик для каждого сервиса
203
- - **Sliding window**: Окно сбрасывается автоматически после истечения времени
204
- - **Атомарность**: Инкремент и проверка выполняются атомарно (Redis LUA script)
205
- - **Fail-open**: При недоступности Redis запросы пропускаются (не блокируются)
206
-
207
- ### Мониторинг
208
-
209
- ```ruby
210
- # Проверка текущего состояния
211
- clients = ['core', 'payments', 'notifications']
212
- clients.each do |client|
213
- attempts = RobustServerSocket::RateLimiter.current_attempts(client)
214
- max = RobustServerSocket.configuration.rate_limit_max_requests
215
- puts "#{client}: #{attempts}/#{max}"
216
- end
217
-
218
- # В метриках (Prometheus, StatsD и т.д.)
219
- clients.each do |client|
220
- attempts = RobustServerSocket::RateLimiter.current_attempts(client)
221
- Metrics.gauge("rate_limiter.attempts.#{client}", attempts)
222
- end
223
- ```
224
-
225
236
  ## ❌ Обработка ошибок
226
237
 
227
238
  ### Типы исключений
@@ -277,77 +288,6 @@ def rate_limit_response(exception)
277
288
  end
278
289
  ```
279
290
 
280
- ## 💡 Рекомендации по использованию
281
-
282
- ### 1. Управление ключами
283
-
284
- **✅ DO:**
285
- ```ruby
286
- # Храните ключи в переменных окружения
287
- c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
288
-
289
- # Используйте secrets management (AWS Secrets Manager, Vault, и т.д.)
290
- c.private_key = Rails.application.credentials.dig(:robust_server, :private_key)
291
-
292
- # Генерируйте ключи правильно
293
- # openssl genrsa -out private_key.pem 2048
294
- # openssl rsa -in private_key.pem -pubout -out public_key.pem
295
- ```
296
-
297
- **❌ DON'T:**
298
- ```ruby
299
- # НЕ коммитьте ключи в git
300
- c.private_key = "-----BEGIN PRIVATE KEY-----\nMII..."
301
-
302
- # НЕ используйте слабые ключи
303
- # Минимум RSA-2048, рекомендуется RSA-4096 для высокой безопасности
304
- ```
305
-
306
- ### 2. Конфигурация Redis
307
-
308
- **✅ DO:**
309
- ```ruby
310
- # Используйте отдельный namespace для каждого окружения
311
- c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
312
-
313
- # Настройте connection pool в production
314
- # В config/initializers/redis.rb
315
- Redis.current = ConnectionPool.new(size: 5, timeout: 5) do
316
- Redis.new(url: ENV['REDIS_URL'], password: ENV['REDIS_PASSWORD'])
317
- end
318
-
319
- # Мониторьте состояние Redis
320
- # Используйте Redis Sentinel или Cluster для высокой доступности
321
- ```
322
-
323
- **❌ DON'T:**
324
- ```ruby
325
- # НЕ используйте одну БД Redis для всех окружений, используйте отдельную bd redis
326
- # НЕ игнорируйте ошибки Redis (rate limiter уже fail-open, но логируйте их)
327
- ```
328
-
329
- ### 5. Whitelist сервисов
330
-
331
- ```ruby
332
- # Явно указывайте только необходимые сервисы
333
- c.allowed_services = %w[core payments] # ✅
334
-
335
- # НЕ используйте wildcards или регулярные выражения
336
- c.allowed_services = %w[*] # ❌ ОПАСНО!
337
-
338
- # Синхронизируйте с keychain клиента
339
- # Server (robust_server_socket):
340
- c.allowed_services = %w[core]
341
-
342
- # Client (robust_client_socket):
343
- c.keychain = {
344
- core: { # ← Должно совпадать
345
- base_uri: 'https://core.example.com',
346
- public_key: '-----BEGIN PUBLIC KEY-----...'
347
- }
348
- }
349
- ```
350
-
351
291
  ## 🤝 Интеграция с RobustClientSocket
352
292
 
353
293
  Для полноценной работы необходимо настроить клиентскую часть:
@@ -373,7 +313,6 @@ end
373
313
 
374
314
  ## 📚 Дополнительные ресурсы
375
315
 
376
- - [BENCHMARK_ANALYSIS.md](BENCHMARK_ANALYSIS.md)
377
316
  - [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
378
317
  - [RSA encryption best practices](https://www.openssl.org/docs/)
379
318
  - [Redis security guide](https://redis.io/topics/security)
@@ -384,4 +323,4 @@ end
384
323
 
385
324
  ## 🐛 Баги и предложения
386
325
 
387
- Сообщайте о проблемах через issue tracker вашего репозитория.
326
+ Сообщайте о багах через ишью, или напрямую тг @cruel_mango или email tee0zed@gmail.com
@@ -0,0 +1,132 @@
1
+ module RobustServerSocket
2
+ module Cacher
3
+ class RedisConnectionError < StandardError; end
4
+
5
+ class << self
6
+ # Atomically validate token: check expiration and usage, then mark as used
7
+ # Returns: 'ok', 'stale', or 'used'
8
+ def atomic_validate_and_log(key, ttl, timestamp, expiration_time)
9
+ current_time = Time.now.utc.to_i
10
+
11
+ redis.with do |conn|
12
+ conn.eval(
13
+ lua_atomic_validate_and_log,
14
+ keys: [key],
15
+ argv: [ttl, timestamp, expiration_time, current_time]
16
+ )
17
+ end
18
+ rescue ::Redis::BaseConnectionError => e
19
+ handle_redis_error(e, 'atomic_validate_and_log')
20
+ raise RedisConnectionError, "Failed to validate token: #{e.message}"
21
+ end
22
+
23
+ def incr(key)
24
+ redis.with do |conn|
25
+ conn.pipelined do |pipeline|
26
+ pipeline.incrby(key, 1)
27
+ pipeline.expire(key, ttl_seconds)
28
+ end
29
+ end
30
+ rescue ::Redis::BaseConnectionError => e
31
+ handle_redis_error(e, 'incr')
32
+ raise RedisConnectionError, "Failed to increment key: #{e.message}"
33
+ end
34
+
35
+ def get(key)
36
+ redis.with do |conn|
37
+ conn.get(key)
38
+ end
39
+ rescue ::Redis::BaseConnectionError => e
40
+ handle_redis_error(e, 'get')
41
+ nil
42
+ end
43
+
44
+ def health_check
45
+ redis.with do |conn|
46
+ conn.ping == 'PONG'
47
+ end
48
+ rescue ::Redis::BaseConnectionError
49
+ false
50
+ end
51
+
52
+ def with_redis(&block)
53
+ redis.with(&block)
54
+ rescue ::Redis::BaseConnectionError => e
55
+ handle_redis_error(e, 'with_redis')
56
+ raise ::RedisConnectionError, "Redis operation failed: #{e.message}"
57
+ end
58
+
59
+ # Clear cached Redis connection pool (useful for hot reloading in development)
60
+ def clear_redis_pool_cache!
61
+ @pool = nil
62
+ end
63
+
64
+ private
65
+
66
+ def lua_atomic_validate_and_log
67
+ <<~LUA
68
+ local key = KEYS[1]
69
+ local ttl = tonumber(ARGV[1])
70
+ local timestamp = tonumber(ARGV[2])
71
+ local expiration_time = tonumber(ARGV[3])
72
+ local current_time = tonumber(ARGV[4])
73
+
74
+ -- Check if token is expired
75
+ if expiration_time <= (current_time - timestamp) then
76
+ return 'stale'
77
+ end
78
+
79
+ -- Check if token was already used
80
+ local current = redis.call('GET', key)
81
+ if current and tonumber(current) > 0 then
82
+ return 'used'
83
+ end
84
+
85
+ -- Mark token as used
86
+ redis.call('INCRBY', key, 1)
87
+ redis.call('EXPIRE', key, ttl)
88
+
89
+ return 'ok'
90
+ LUA
91
+ end
92
+
93
+ def ttl_seconds
94
+ # `+ 10` secs, for token storing and expiration check validity
95
+ ::RobustServerSocket.configuration.token_expiration_time + 10
96
+ end
97
+
98
+ # Cache Redis connection pool at module level for the lifetime of the Rails process
99
+ # This avoids recreating the connection pool on every Redis operation
100
+ def redis
101
+ @pool ||= ::ConnectionPool::Wrapper.new(**pool_config) do
102
+ ::Redis.new(redis_config)
103
+ end
104
+ end
105
+
106
+ def pool_config
107
+ {
108
+ size: ENV.fetch('REDIS_POOL_SIZE', 25).to_i,
109
+ timeout: ENV.fetch('REDIS_POOL_TIMEOUT', 1).to_f
110
+ }
111
+ end
112
+
113
+ def redis_config
114
+ config = {
115
+ url: ::RobustServerSocket.configuration.redis_url,
116
+ reconnect_attempts: 3,
117
+ timeout: 1.0,
118
+ connect_timeout: 2.0
119
+ }
120
+
121
+ password = ::RobustServerSocket.configuration.redis_pass
122
+ config[:password] = password if password && !password.empty?
123
+
124
+ config
125
+ end
126
+
127
+ def handle_redis_error(error, operation)
128
+ warn "Redis operation '#{operation}' failed: #{error.class} - #{error.message}"
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,15 +1,8 @@
1
- require_relative 'secure_token/cacher'
2
- require_relative 'secure_token/decrypt'
3
- require_relative 'rate_limiter'
4
-
5
1
  module RobustServerSocket
6
2
  class ClientToken
7
3
  TOKEN_REGEXP = /\A(.+)_(\d{10,})\z/.freeze
8
4
 
9
5
  InvalidToken = Class.new(StandardError)
10
- UnauthorizedClient = Class.new(StandardError)
11
- UsedToken = Class.new(StandardError)
12
- StaleToken = Class.new(StandardError)
13
6
 
14
7
  def self.validate!(secure_token)
15
8
  new(secure_token).tap do |instance|
@@ -23,34 +16,24 @@ module RobustServerSocket
23
16
  end
24
17
 
25
18
  def validate!
26
- raise InvalidToken unless decrypted_token
27
- raise UnauthorizedClient unless client
28
-
29
- RateLimiter.check!(client)
30
-
31
- result = atomic_validate_and_log_token
32
-
33
- case result
34
- when 'stale'
35
- raise StaleToken
36
- when 'used'
37
- raise UsedToken
38
- when 'ok'
39
- true
40
- else
41
- raise InvalidToken, "Unexpected validation result: #{result}"
42
- end
19
+ raise InvalidToken unless validate_decrypted_token
20
+ modules_checks!
43
21
  end
44
22
 
45
23
  def valid?
46
- !!(decrypted_token &&
47
- client &&
48
- RateLimiter.check(client) &&
49
- atomic_validate_and_log_token == 'ok')
24
+ validate_decrypted_token && modules_checks
50
25
  rescue StandardError
51
26
  false
52
27
  end
53
28
 
29
+ def modules_checks
30
+ true
31
+ end
32
+
33
+ def modules_checks!
34
+ true
35
+ end
36
+
54
37
  def client
55
38
  @client ||= begin
56
39
  target = client_name.strip
@@ -58,23 +41,14 @@ module RobustServerSocket
58
41
  end
59
42
  end
60
43
 
61
- def token_not_expired?
62
- token_expiration_time > Time.now.utc.to_i - timestamp
63
- end
64
-
65
- def atomic_validate_and_log_token
66
- SecureToken::Cacher.atomic_validate_and_log(
67
- decrypted_token,
68
- token_expiration_time + 300,
69
- timestamp,
70
- token_expiration_time
71
- )
72
- end
73
-
74
44
  def decrypted_token
75
45
  @decrypted_token ||= SecureToken::Decrypt.call(@secure_token)
76
46
  end
77
47
 
48
+ def validate_decrypted_token
49
+ !!decrypted_token
50
+ end
51
+
78
52
  private
79
53
 
80
54
  def allowed_clients
@@ -101,6 +75,7 @@ module RobustServerSocket
101
75
  RobustServerSocket.configuration.token_expiration_time
102
76
  end
103
77
 
78
+
104
79
  # Do we need it? It would be useful only if public_key compromised
105
80
  # def secure_compare(a, b)
106
81
  # return false unless a.bytesize == b.bytesize
@@ -4,6 +4,14 @@ module RobustServerSocket
4
4
 
5
5
  attr_reader :configuration, :configured
6
6
 
7
+ def _push_modules_check_code(code)
8
+ configuration._modules_check_rows.push(code)
9
+ end
10
+
11
+ def _push_bang_modules_check_code(code)
12
+ configuration._bang_modules_check_rows.push(code)
13
+ end
14
+
7
15
  def configure
8
16
  @configuration ||= ConfigStore.new
9
17
  yield(configuration)
@@ -43,13 +51,24 @@ module RobustServerSocket
43
51
  end
44
52
 
45
53
  class ConfigStore
46
- attr_accessor :allowed_services, :private_key, :token_expiration_time, :redis_url, :redis_pass,
47
- :rate_limit_enabled, :rate_limit_max_requests, :rate_limit_window_seconds
54
+ attr_accessor :allowed_services, :private_key, :token_expiration_time, :store_used_token_time, :redis_url, :redis_pass,
55
+ :rate_limit_max_requests, :rate_limit_window_seconds, :using_modules
56
+
57
+ attr_reader :_modules_check_rows, :_bang_modules_check_rows
48
58
 
49
59
  def initialize
50
- @rate_limit_enabled = false
51
60
  @rate_limit_max_requests = 100
52
61
  @rate_limit_window_seconds = 60
62
+ @store_used_token_time = 600
63
+ @token_expiration_time = 10
64
+ @using_modules = %i[
65
+ client_auth_protection
66
+ dos_attack_protection
67
+ replay_attack_protection
68
+ ]
69
+
70
+ @_modules_check_rows = []
71
+ @_bang_modules_check_rows = []
53
72
  end
54
73
  end
55
74
  end
@@ -0,0 +1,20 @@
1
+ module RobustServerSocket
2
+ module Modules
3
+ module ClientAuthProtection
4
+ UnauthorizedClient = Class.new(StandardError)
5
+
6
+ def self.included(_base)
7
+ RobustServerSocket._push_modules_check_code('validate_client')
8
+ RobustServerSocket._push_bang_modules_check_code("validate_client!\n")
9
+ end
10
+
11
+ def validate_client
12
+ !!client
13
+ end
14
+
15
+ def validate_client!
16
+ raise UnauthorizedClient unless validate_client
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../cacher'
2
+ require_relative '../rate_limiter'
3
+
4
+ module RobustServerSocket
5
+ module Modules
6
+ module DosAttackProtection
7
+ def self.included(_base)
8
+ RobustServerSocket._push_modules_check_code('validate_rate_limit')
9
+ RobustServerSocket._push_bang_modules_check_code("validate_rate_limit!\n")
10
+ end
11
+
12
+ def validate_rate_limit
13
+ !!RateLimiter.check(client)
14
+ end
15
+
16
+ def validate_rate_limit!
17
+ RateLimiter.check!(client)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../cacher'
2
+
3
+ module RobustServerSocket
4
+ module Modules
5
+ module ReplayAttackProtection
6
+ UsedToken = Class.new(StandardError)
7
+ StaleToken = Class.new(StandardError)
8
+
9
+ def self.included(_base)
10
+ RobustServerSocket._push_modules_check_code('atomic_validate_and_log_token')
11
+ RobustServerSocket._push_bang_modules_check_code("atomic_validate_and_log_token!\n")
12
+ end
13
+
14
+ def atomic_validate_and_log_token!
15
+ result = Cacher.atomic_validate_and_log(
16
+ decrypted_token,
17
+ store_used_token_time,
18
+ timestamp,
19
+ token_expiration_time
20
+ )
21
+
22
+ case result
23
+ when 'ok'
24
+ true
25
+ when 'stale'
26
+ raise StaleToken
27
+ when 'used'
28
+ raise UsedToken
29
+ else
30
+ raise StandardError, "Unexpected result: #{result}"
31
+ end
32
+ end
33
+
34
+ def atomic_validate_and_log_token
35
+ Cacher.atomic_validate_and_log(
36
+ decrypted_token,
37
+ store_used_token_time, # window for storing used token
38
+ timestamp,
39
+ token_expiration_time
40
+ ) == 'ok'
41
+ end
42
+
43
+ private
44
+
45
+ def store_used_token_time
46
+ RobustServerSocket.configuration.store_used_token_time
47
+ end
48
+ end
49
+ end
50
+ end
@@ -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.3'
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.3
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - tee_zed
@@ -79,10 +79,13 @@ files:
79
79
  - README.md
80
80
  - Rakefile
81
81
  - lib/robust_server_socket.rb
82
+ - lib/robust_server_socket/cacher.rb
82
83
  - lib/robust_server_socket/client_token.rb
83
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
84
88
  - lib/robust_server_socket/rate_limiter.rb
85
- - lib/robust_server_socket/secure_token/cacher.rb
86
89
  - lib/robust_server_socket/secure_token/decrypt.rb
87
90
  - lib/version.rb
88
91
  - robust_server_socket.gemspec
@@ -103,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
106
  - !ruby/object:Gem::Version
104
107
  version: '0'
105
108
  requirements: []
106
- rubygems_version: 3.6.9
109
+ rubygems_version: 4.0.6
107
110
  specification_version: 4
108
111
  summary: Robust Server Socket gem for RobustPro
109
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