robust_server_socket 0.4.2 → 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,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../cacher'
2
4
  require_relative '../rate_limiter'
3
5
 
4
6
  module RobustServerSocket
5
7
  module Modules
6
- module DosAttackProtection
8
+ module RateLimitProtection
7
9
  def self.included(_base)
8
10
  RobustServerSocket._push_modules_check_code('validate_rate_limit')
9
11
  RobustServerSocket._push_bang_modules_check_code("validate_rate_limit!\n")
10
12
  end
11
13
 
12
14
  def validate_rate_limit
13
- !!RateLimiter.check(client)
15
+ RateLimiter.check(client)
14
16
  end
15
17
 
16
18
  def validate_rate_limit!
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../cacher'
2
4
 
3
5
  module RobustServerSocket
@@ -13,28 +15,15 @@ module RobustServerSocket
13
15
 
14
16
  def atomic_validate_and_log_token!
15
17
  result = Cacher.atomic_validate_and_log(
16
- decrypted_token,
17
- store_used_token_time,
18
- timestamp,
19
- token_expiration_time
18
+ decrypted_token, store_used_token_time, timestamp, token_expiration_time
20
19
  )
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
20
+ handle_validation_result!(result)
32
21
  end
33
22
 
34
23
  def atomic_validate_and_log_token
35
24
  Cacher.atomic_validate_and_log(
36
25
  decrypted_token,
37
- store_used_token_time, # window for storing used token
26
+ store_used_token_time,
38
27
  timestamp,
39
28
  token_expiration_time
40
29
  ) == 'ok'
@@ -42,8 +31,17 @@ module RobustServerSocket
42
31
 
43
32
  private
44
33
 
34
+ def handle_validation_result!(result)
35
+ case result
36
+ when 'ok' then true
37
+ when 'stale' then raise StaleToken
38
+ when 'used' then raise UsedToken
39
+ else raise StandardError, "Unexpected result: #{result}"
40
+ end
41
+ end
42
+
45
43
  def store_used_token_time
46
- RobustServerSocket.configuration.store_used_token_time
44
+ RobustServerSocket.configuration.token_expiration_time + Cacher::CLOCK_SKEW_MS / 1000
47
45
  end
48
46
  end
49
47
  end
@@ -1,30 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RobustServerSocket
2
4
  class RateLimiter
3
5
  RateLimitExceeded = Class.new(StandardError)
4
6
 
5
7
  class << self
6
8
  def check!(client_name)
7
- unless (attempts = check(client_name))
8
- actual_attempts = current_attempts(client_name)
9
-
10
- raise RateLimitExceeded, "Rate limit exceeded for #{client_name}: #{actual_attempts}/#{max_requests} requests per #{window_seconds}s"
11
- end
9
+ return if check(client_name)
12
10
 
13
- attempts
11
+ raise RateLimitExceeded,
12
+ "Rate limit exceeded for #{client_name}: max #{max_requests} per #{window_seconds}s"
14
13
  end
15
14
 
16
15
  def check(client_name)
17
- key = rate_limit_key(client_name)
18
- attempts = increment_attempts(key)
19
-
20
- return false if attempts > max_requests
21
-
22
- attempts
23
- end
24
-
25
- def current_attempts(client_name)
26
- key = rate_limit_key(client_name)
27
- Cacher.get(key).to_i
16
+ attempts = record_attempt(client_name)
17
+ attempts <= max_requests
28
18
  end
29
19
 
30
20
  def reset!(client_name)
@@ -39,16 +29,11 @@ module RobustServerSocket
39
29
 
40
30
  private
41
31
 
42
- def increment_attempts(key)
43
- Cacher.with_redis do |conn|
44
- attempts = conn.incr(key)
45
- # Set expiration only on first attempt to ensure atomic window
46
- conn.expire(key, window_seconds) if attempts == 1
47
- attempts
48
- end
32
+ def record_attempt(client_name)
33
+ Cacher.incr_sliding_window_count(rate_limit_key(client_name), window_seconds)
49
34
  rescue Cacher::RedisConnectionError => e
50
- handle_redis_error(e, 'increment_attempts')
51
- 0 # Fail open: allow request if Redis is down
35
+ handle_redis_error(e, 'record_attempt')
36
+ 0
52
37
  end
53
38
 
54
39
  def rate_limit_key(client_name)
@@ -1,20 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RobustServerSocket
2
4
  module SecureToken
3
- BASE64_REGEXP = /\A[A-Za-z0-9+\/]*={0,2}\z/.freeze
5
+ BASE64_REGEXP = %r{\A[A-Za-z0-9+/]+={0,2}\z}.freeze
4
6
  InvalidToken = Class.new(StandardError)
5
7
 
6
8
  module Decrypt
7
9
  class << self
8
10
  def call(token)
9
- unless token.is_a?(String) && token.match?(BASE64_REGEXP)
10
- raise InvalidToken, 'Invalid token format'
11
- end
11
+ raise InvalidToken, 'Invalid token format' unless token.is_a?(String) && token.match?(BASE64_REGEXP)
12
12
 
13
13
  decoded_token = ::Base64.strict_decode64(token)
14
14
 
15
- if decoded_token.bytesize > 1024
16
- raise InvalidToken, 'Token too large'
17
- end
15
+ raise InvalidToken, 'Token too large' if decoded_token.bytesize > 1024
18
16
 
19
17
  private_key.private_decrypt(decoded_token, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING).force_encoding('UTF-8')
20
18
  rescue ::OpenSSL::PKey::RSAError, ArgumentError
@@ -14,17 +14,17 @@ module RobustServerSocket
14
14
 
15
15
  module_function
16
16
 
17
- def load!
17
+ def load! # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
18
18
  raise 'You must correctly configure RobustServerSocket first!' unless configured?
19
19
 
20
20
  configuration.using_modules.each do |mod|
21
21
  raise ArgumentError, 'Module must be a Symbol!' unless mod.is_a?(Symbol)
22
22
 
23
23
  require_relative "robust_server_socket/modules/#{mod}"
24
- ClientToken.include eval(mod.to_s.split('_').map(&:capitalize).unshift('Modules::').join)
24
+ ClientToken.include const_get(mod.to_s.split('_').map(&:capitalize).unshift('Modules::').join)
25
25
  end
26
26
 
27
- ClientToken.class_eval(<<~METHOD)
27
+ ClientToken.class_eval(<<~METHOD, __FILE__, __LINE__ + 1)
28
28
  def modules_checks
29
29
  #{(RobustServerSocket.configuration._modules_check_rows.empty? ? ['true'] : RobustServerSocket.configuration._modules_check_rows.map(&:strip)).join(' && ')}
30
30
  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.4.2'
4
+ VERSION = '0.4.3'
5
5
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "./lib/version"
3
+ require './lib/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "robust_server_socket"
6
+ spec.name = 'robust_server_socket'
7
7
  spec.version = RobustServerSocket::VERSION
8
- spec.authors = ["tee_zed"]
9
- spec.email = ["tee0zed@gmail.com"]
10
- spec.description = "Robust Server Socket"
11
- spec.summary = "Robust Server Socket gem for RobustPro"
12
- spec.license = "MIT"
13
- spec.required_ruby_version = ">= 2.7.0"
8
+ spec.authors = ['tee_zed']
9
+ spec.email = ['tee0zed@gmail.com']
10
+ spec.description = 'Robust Server Socket'
11
+ spec.summary = 'Robust Server Socket gem for RobustPro'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = '>= 2.7.0'
14
14
 
15
15
  # Specify which files should be added to the gem when it is released.
16
16
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -20,13 +20,13 @@ Gem::Specification.new do |spec|
20
20
  f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
21
21
  end
22
22
  end
23
- spec.bindir = "exe"
23
+ spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
26
- spec.add_dependency 'rspec'
25
+ spec.require_paths = ['lib']
26
+ spec.add_dependency 'hiredis'
27
27
  spec.add_dependency 'rake'
28
28
  spec.add_dependency 'redis'
29
- spec.add_dependency 'hiredis'
29
+ spec.add_dependency 'rspec'
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
32
  # spec.add_dependency "example-gem", "~> 1.0"
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.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - tee_zed
@@ -10,7 +10,7 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rspec
13
+ name: hiredis
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
@@ -52,7 +52,7 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: hiredis
55
+ name: rspec
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
@@ -73,6 +73,8 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - ".rspec"
76
+ - ".rubocop.yml"
77
+ - ".ruby-version"
76
78
  - CODE_OF_CONDUCT.md
77
79
  - LICENSE.txt
78
80
  - README.en.md
@@ -83,7 +85,7 @@ files:
83
85
  - lib/robust_server_socket/client_token.rb
84
86
  - lib/robust_server_socket/configuration.rb
85
87
  - lib/robust_server_socket/modules/client_auth_protection.rb
86
- - lib/robust_server_socket/modules/dos_attack_protection.rb
88
+ - lib/robust_server_socket/modules/rate_limit_protection.rb
87
89
  - lib/robust_server_socket/modules/replay_attack_protection.rb
88
90
  - lib/robust_server_socket/rate_limiter.rb
89
91
  - lib/robust_server_socket/secure_token/decrypt.rb