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.
data/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # RobustServerSocket
2
+
3
+ Gem для межсервисной аутентификации, используется в паре с RobustClientSocket
4
+
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
69
+
70
+ ## 📋 Содержание
71
+
72
+ - [Функции безопасности](#функции-безопасности)
73
+ - [Установка](#установка)
74
+ - [Конфигурация](#конфигурация)
75
+ - [Использование](#использование)
76
+ - [Обработка ошибок](#обработка-ошибок)
77
+
78
+ ## 🔒 Функции безопасности
79
+
80
+ RobustServerSocket реализует многоуровневую систему защиты для межсервисных коммуникаций:
81
+
82
+ ### 1. Криптографическая защита
83
+ - **RSA-2048 шифрование**: Используется пара ключей RSA с минимальной длиной 2048 бит
84
+ - **Валидация ключей**: Автоматическая проверка размера ключа при конфигурации
85
+
86
+ ### 2. Контроль доступа
87
+ - **Whitelist клиентов**: Только авторизованные сервисы могут подключаться, при включенном модуле `:client_auth_protection`
88
+ - **Идентификация по имени**: Каждый клиент должен быть явно указан в `allowed_services`
89
+
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**: Распределение запросов во времени
100
+ - **Fail-open стратегия**: Если Redis недоступен, запросы пропускаются (для надёжности)
101
+
102
+ -
103
+ ### 5. Защита от SSL stripping MITM attack
104
+ - **Принудительное HTTPS на сервере**: Все запросы должны быть совершены по HTTPS, чтобы защитить токены от перехвата
105
+ - **Включается на RobustClientSoket, ключём `ssl_verify: true`**
106
+
107
+ ## 📦 Установка
108
+
109
+ ```ruby
110
+ gem 'robust_server_socket'
111
+ ```
112
+
113
+ и на клиенте:
114
+ ```ruby
115
+ gem 'robust_client_socket'
116
+ ```
117
+ ## ⚙️ Конфигурация
118
+
119
+ Создайте файл `config/initializers/robust_server_socket.rb`:
120
+
121
+ ```ruby
122
+ RobustServerSocket.configure do |c|
123
+ c.using_modules = %i[
124
+ :client_auth_protection
125
+ :replay_attack_protection
126
+ :dos_attack_protection
127
+ ]
128
+
129
+ # Приватный ключ сервиса (RSA-2048 или выше)
130
+ c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
131
+ c.token_expiration_time = 3
132
+
133
+ # Список разрешённых сервисов (whitelist)
134
+ # Должен совпадать с именами RobustClientSocket клиента
135
+ # Для client_auth_protection
136
+ c.allowed_services = %w[core payments notifications]
137
+
138
+ # Redis для работы replay_attack_protection и ddos_attack_protection
139
+ c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
140
+ c.redis_pass = ENV['REDIS_PASSWORD']
141
+
142
+ # ddos_attack_protection
143
+ # Максимальное количество запросов в окне времени (по умолчанию: 100)
144
+ c.rate_limit_max_requests = 100
145
+ # Размер временного окна в секундах (по умолчанию: 60)
146
+ c.rate_limit_window_seconds = 60
147
+ end
148
+
149
+ # Загрузка конфигурации с валидацией
150
+ RobustServerSocket.load!
151
+ ```
152
+ `using_modules` - это используемые модули, добавление или удаление которых изменит поведение гема.
153
+
154
+ ### Опции конфигурации сервиса
155
+
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 | Размер временного окна в секундах |
167
+
168
+ ## 🚀 Использование
169
+
170
+ ### Базовая авторизация
171
+
172
+ ```ruby
173
+ # В контроллере или middleware
174
+ class ApiController < ApplicationController
175
+ before_action :authenticate_service!
176
+
177
+ private
178
+
179
+ def authenticate_service!
180
+ # Хедер, прописанный в RobustClientSocket (SECURE-TOKEN default)
181
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
182
+
183
+ @current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (рейзит ошибки)
184
+ rescue RobustServerSocket::ClientToken::InvalidToken
185
+ render json: { error: 'Invalid token' }, status: :unauthorized
186
+ rescue RobustServerSocket::ClientToken::UnauthorizedClient
187
+ render json: { error: 'Unauthorized service' }, status: :forbidden
188
+ rescue RobustServerSocket::ClientToken::UsedToken
189
+ render json: { error: 'Token already used' }, status: :unauthorized
190
+ rescue RobustServerSocket::ClientToken::StaleToken
191
+ render json: { error: 'Token expired' }, status: :unauthorized
192
+ rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
193
+ render json: { error: e.message }, status: :too_many_requests
194
+ end
195
+
196
+ def authenticate_service
197
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
198
+ @current_service = RobustServerSocket::ClientToken.valid?(token) # не рейзит
199
+
200
+ if @current_service
201
+ # Токен валиден
202
+ else
203
+ # Токен невалиден
204
+ render json: { error: 'Unauthorized' }, status: :unauthorized
205
+ end
206
+ end
207
+ end
208
+ ```
209
+
210
+ ### Расширенное использование
211
+
212
+ ```ruby
213
+ # Создание объекта токена
214
+ token_string = request.headers['Authorization']&.sub(/^Bearer /, '')
215
+ client_token = RobustServerSocket::ClientToken.new(token_string)
216
+
217
+ # Проверка валидности (возвращает true/false)
218
+ if client_token.valid?
219
+ # Получение имени клиента
220
+ client_name = client_token.client
221
+ puts "Authorized client: #{client_name}"
222
+ else
223
+ # Токен невалиден
224
+ render json: { error: 'Unauthorized' }, status: :unauthorized
225
+ end
226
+
227
+ # Быстрая валидация с исключениями
228
+ begin
229
+ service_token = RobustServerSocket::ClientToken.validate!(token_string)
230
+ client_name = service_token.client
231
+ rescue => e
232
+ # Обработка специфичных ошибок
233
+ end
234
+ ```
235
+
236
+ ## ❌ Обработка ошибок
237
+
238
+ ### Типы исключений
239
+
240
+ | Исключение | Причина | HTTP статус | Действие |
241
+ |-----------|---------|-------------|----------|
242
+ | `InvalidToken` | Токен не может быть расшифрован или имеет неверный формат | 401 | Проверьте корректность токена и ключей |
243
+ | `UnauthorizedClient` | Клиент не в whitelist | 403 | Добавьте клиента в `allowed_services` |
244
+ | `UsedToken` | Токен уже был использован | 401 | Клиент должен запросить новый токен |
245
+ | `StaleToken` | Токен истёк | 401 | Клиент должен запросить новый токен |
246
+ | `RateLimitExceeded` | Превышен лимит запросов | 429 | Клиент должен подождать или ретраить позже |
247
+
248
+ ### Централизованная обработка
249
+
250
+ ```ruby
251
+ # В ApplicationController
252
+ rescue_from RobustServerSocket::ClientToken::InvalidToken,
253
+ RobustServerSocket::ClientToken::UsedToken,
254
+ RobustServerSocket::ClientToken::StaleToken,
255
+ with: :unauthorized_response
256
+
257
+ rescue_from RobustServerSocket::ClientToken::UnauthorizedClient,
258
+ with: :forbidden_response
259
+
260
+ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
261
+ with: :rate_limit_response
262
+
263
+ private
264
+
265
+ def unauthorized_response(exception)
266
+ render json: {
267
+ error: 'Authentication failed',
268
+ message: exception.message,
269
+ type: exception.class.name
270
+ }, status: :unauthorized
271
+ end
272
+
273
+ def forbidden_response(exception)
274
+ render json: {
275
+ error: 'Access denied',
276
+ message: exception.message,
277
+ type: exception.class.name
278
+ }, status: :forbidden
279
+ end
280
+
281
+ def rate_limit_response(exception)
282
+ render json: {
283
+ error: 'Too many requests',
284
+ message: exception.message,
285
+ type: exception.class.name,
286
+ retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
287
+ }, status: :too_many_requests
288
+ end
289
+ ```
290
+
291
+ ## 🤝 Интеграция с RobustClientSocket
292
+
293
+ Для полноценной работы необходимо настроить клиентскую часть:
294
+
295
+ ```ruby
296
+ # На клиенте (RobustClientSocket)
297
+ RobustClientSocket.configure do |c|
298
+ c.service_name = 'core' # ← Должно быть в allowed_services сервера
299
+ c.keychain = {
300
+ payments: {
301
+ base_uri: 'https://payments.example.com',
302
+ public_key: '-----BEGIN PUBLIC KEY-----...' # Публичный ключ сервера payments
303
+ }
304
+ }
305
+ end
306
+
307
+ # На сервере (RobustServerSocket)
308
+ RobustServerSocket.configure do |c|
309
+ c.allowed_services = %w[core] # ← Соответствует service_name клиента
310
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Приватная пара к public_key
311
+ end
312
+ ```
313
+
314
+ ## 📚 Дополнительные ресурсы
315
+
316
+ - [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
317
+ - [RSA encryption best practices](https://www.openssl.org/docs/)
318
+ - [Redis security guide](https://redis.io/topics/security)
319
+
320
+ ## 📝 Лицензия
321
+
322
+ См. файл [MIT-LICENSE](MIT-LICENSE)
323
+
324
+ ## 🐛 Баги и предложения
325
+
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,35 +1,12 @@
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|
16
- raise InvalidToken unless instance.decrypted_token
17
- raise UnauthorizedClient unless instance.client
18
-
19
- RateLimiter.check!(instance.client)
20
-
21
- result = instance.atomic_validate_and_log_token
22
-
23
- case result
24
- when 'stale'
25
- raise StaleToken
26
- when 'used'
27
- raise UsedToken
28
- when 'ok'
29
- true
30
- else
31
- raise InvalidToken, "Unexpected validation result: #{result}"
32
- end
9
+ instance.validate!
33
10
  end
34
11
  end
35
12
 
@@ -38,15 +15,25 @@ module RobustServerSocket
38
15
  @client = nil
39
16
  end
40
17
 
18
+ def validate!
19
+ raise InvalidToken unless validate_decrypted_token
20
+ modules_checks!
21
+ end
22
+
41
23
  def valid?
42
- !!(decrypted_token &&
43
- client &&
44
- RateLimiter.check(client) &&
45
- atomic_validate_and_log_token == 'ok')
24
+ validate_decrypted_token && modules_checks
46
25
  rescue StandardError
47
26
  false
48
27
  end
49
28
 
29
+ def modules_checks
30
+ true
31
+ end
32
+
33
+ def modules_checks!
34
+ true
35
+ end
36
+
50
37
  def client
51
38
  @client ||= begin
52
39
  target = client_name.strip
@@ -54,23 +41,14 @@ module RobustServerSocket
54
41
  end
55
42
  end
56
43
 
57
- def token_not_expired?
58
- token_expiration_time > Time.now.utc.to_i - timestamp
59
- end
60
-
61
- def atomic_validate_and_log_token
62
- SecureToken::Cacher.atomic_validate_and_log(
63
- decrypted_token,
64
- token_expiration_time + 300,
65
- timestamp,
66
- token_expiration_time
67
- )
68
- end
69
-
70
44
  def decrypted_token
71
45
  @decrypted_token ||= SecureToken::Decrypt.call(@secure_token)
72
46
  end
73
47
 
48
+ def validate_decrypted_token
49
+ !!decrypted_token
50
+ end
51
+
74
52
  private
75
53
 
76
54
  def allowed_clients
@@ -97,6 +75,7 @@ module RobustServerSocket
97
75
  RobustServerSocket.configuration.token_expiration_time
98
76
  end
99
77
 
78
+
100
79
  # Do we need it? It would be useful only if public_key compromised
101
80
  # def secure_compare(a, b)
102
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