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.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -0
- data/.ruby-version +1 -0
- data/README.en.md +110 -396
- data/README.md +88 -115
- data/Rakefile +4 -8
- data/lib/robust_server_socket/cacher.rb +45 -35
- data/lib/robust_server_socket/client_token.rb +10 -10
- data/lib/robust_server_socket/configuration.rb +21 -9
- data/lib/robust_server_socket/modules/client_auth_protection.rb +2 -0
- data/lib/robust_server_socket/modules/{dos_attack_protection.rb → rate_limit_protection.rb} +4 -2
- data/lib/robust_server_socket/modules/replay_attack_protection.rb +15 -17
- data/lib/robust_server_socket/rate_limiter.rb +11 -26
- data/lib/robust_server_socket/secure_token/decrypt.rb +5 -7
- data/lib/robust_server_socket.rb +3 -3
- data/lib/version.rb +1 -1
- data/robust_server_socket.gemspec +12 -12
- metadata +6 -4
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
attempts
|
|
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
|
|
43
|
-
Cacher.
|
|
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, '
|
|
51
|
-
0
|
|
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 =
|
|
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
|
data/lib/robust_server_socket.rb
CHANGED
|
@@ -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
|
|
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,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require './lib/version'
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = 'robust_server_socket'
|
|
7
7
|
spec.version = RobustServerSocket::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
10
|
-
spec.description =
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.license =
|
|
13
|
-
spec.required_ruby_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'
|
|
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 =
|
|
23
|
+
spec.bindir = 'exe'
|
|
24
24
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
25
|
-
spec.require_paths = [
|
|
26
|
-
spec.add_dependency '
|
|
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 '
|
|
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.
|
|
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:
|
|
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:
|
|
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/
|
|
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
|