rate-limit 0.0.1 → 0.2.0
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/CHANGELOG.md +70 -0
- data/README.md +25 -66
- data/lib/rate-limit.rb +3 -0
- data/lib/rate_limit/base.rb +6 -14
- data/lib/rate_limit/config.rb +10 -0
- data/lib/rate_limit/errors/limit_exceeded_error.rb +5 -5
- data/lib/rate_limit/result.rb +28 -0
- data/lib/rate_limit/throttler.rb +7 -48
- data/lib/rate_limit/version.rb +1 -1
- data/lib/rate_limit/window.rb +10 -6
- data/lib/rate_limit/worker.rb +49 -0
- data/lib/rate_limit.rb +2 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0f1786c2b5fde684e916bcc8733929b7d0d35cdc2807d8f511190f179019611
|
4
|
+
data.tar.gz: c216ce4a4f8e2e479854b61b895c87d226cceba9f8a3d6fb476852709a308ed2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25bbef68dd61abd1cb2353367af910ee5d61d2ce9f1408c66a36a2741abab62bc9b3c212552e01187c5a95121c611eeb9b16232b1b1bdcc4e486fcdc780b85e0
|
7
|
+
data.tar.gz: 5232a6c6f559c5161576b1f54d27a17b590c3b2d25376f8b6f0011d725dbf029bbc7b75e78d4afc6babeaea3a0acac4b9c3b643738f4e9730fbc5105241f208c
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog],
|
6
|
+
and this project adheres to [Semantic Versioning].
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [v0.2.0] - 2022-10-05
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- added `RateLimit::Errors::LimitExceededError#result`
|
15
|
+
- added `Worker#raise_errors`
|
16
|
+
- added `Worker#only_failures`
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- changed `RateLimit::Result.initialize`
|
21
|
+
|
22
|
+
### Removed
|
23
|
+
|
24
|
+
- removed `RateLimit::Result#namespace`
|
25
|
+
- removed `RateLimit::Worker#namespace`
|
26
|
+
- removed `RateLimit::Errors::LimitExceededError#namespace`
|
27
|
+
- removed `RateLimit::Errors::LimitExceededError#worker`
|
28
|
+
- removed `RateLimit.throttle_with_block!` and `Worker#throttle_with_block!` in favour of `.throttle` with `{ raise_errors: true }` option
|
29
|
+
- removed `RateLimit.throttle_only_failures_with_block!` and `Worker#throttle_only_failures_with_block!` in favour of `.throttle` with `{ only_failures: true }` option
|
30
|
+
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
|
34
|
+
- https://github.com/catawiki/rate-limit/issues/16 String Values are not respected
|
35
|
+
|
36
|
+
## [v0.1.0] - 2022-09-19
|
37
|
+
|
38
|
+
|
39
|
+
### Added
|
40
|
+
|
41
|
+
- https://github.com/catawiki/rate-limit/pull/11 `RateLimit::Result` class
|
42
|
+
- https://github.com/catawiki/rate-limit/pull/12 `RateLimit::Worker` class
|
43
|
+
- https://github.com/catawiki/rate-limit/pull/13 `RateLimit::Config#on_success` and `RateLimit::Config#on_failure`
|
44
|
+
|
45
|
+
### Changed
|
46
|
+
|
47
|
+
- `RateLimit.throttle` to not accept block
|
48
|
+
- `RateLimit.throttle` to return `RateLimit::Result` object
|
49
|
+
- `RateLimit::Throttler` from class to module while moving responsibilities to `RateLimit::Worker` class
|
50
|
+
- renamed `RateLimit.throttle!` to `RateLimit.throttle_with_block!`
|
51
|
+
- renamed `RateLimit.throttle_only_failures` to `RateLimit.throttle_only_failures_with_block!`
|
52
|
+
|
53
|
+
### Fixed
|
54
|
+
|
55
|
+
- https://github.com/catawiki/rate-limit/issues/7 Symbol topic names does not load the correct limits
|
56
|
+
- https://github.com/catawiki/rate-limit/issues/6 Main Module (RateLimit) fails to autoload
|
57
|
+
|
58
|
+
|
59
|
+
## v0.0.1 - 2022-09-09
|
60
|
+
|
61
|
+
- Initial gem release
|
62
|
+
|
63
|
+
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
|
64
|
+
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
|
65
|
+
|
66
|
+
<!-- versions -->
|
67
|
+
|
68
|
+
[Unreleased]: https://github.com/catawiki/rate-limit/compare/v0.1.0...HEAD
|
69
|
+
[v0.2.0]: https://github.com/catawiki/rate-limit/compare/v0.1.0...v0.2.0
|
70
|
+
[v0.1.0]: https://github.com/catawiki/rate-limit/compare/v0.0.1...v0.1.0
|
data/README.md
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
|
4
4
|
Protect your Ruby apps from bad actors. RateLimit allows you to set permissions as to whether certain number of feature calls are valid or not for a specific entity (user, phone number, email address, etc...).
|
5
5
|
|
6
|
-
This gem mainly provides brute-force protection by throttling
|
6
|
+
This gem mainly provides brute-force protection by throttling attempts for a specific entity id (i.e user_id). However it could also be used to throttle based on ip address (we recommend that you consider using [Rack::Attack](https://github.com/rack/rack-attack) for more optimized ip throttling)
|
7
7
|
|
8
8
|
#### Common Use Cases
|
9
9
|
* [Login] Brute-force attempts for a spefic account
|
10
|
-
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a
|
11
|
-
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a
|
10
|
+
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a specific user_id
|
11
|
+
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a specific phone_number
|
12
12
|
* [Verifications] Brute-force attempts for entering verification codes
|
13
13
|
* [Redeem] Brute-force attempts to redeem voucher codes from a specific account
|
14
14
|
|
@@ -28,81 +28,32 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
$ gem install rate-limit
|
30
30
|
|
31
|
-
## Usage
|
31
|
+
## Basic Usage
|
32
32
|
|
33
|
-
|
33
|
+
### [`RateLimit.throttle`](https://github.com/catawiki/rate-limit/wiki/Throttling)
|
34
|
+
The throttle method expects the following options
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
```
|
40
|
-
or
|
36
|
+
| Option | Description | Examples |
|
37
|
+
| ---------------- | ------------------------------------------------------------------------------- | ------------------------------------- |
|
38
|
+
| topic | The topic you would like to throttle | "login", "send_sms", "redeem_voucher" |
|
39
|
+
| value | The identifier of the unique entity that is throttled based on the topic limits | user_id, phone_number, voucher_code |
|
41
40
|
|
42
|
-
```ruby
|
43
|
-
if RateLimit.throttle(topic: :login, value: id)
|
44
|
-
# Do something
|
45
|
-
end
|
46
|
-
```
|
47
41
|
|
48
|
-
|
42
|
+
The `throttle` method checks if the given value did exceed the defined limits for the given topic. If the limit is exceeded then it returns [RateLimit::Result](https://github.com/catawiki/rate-limit/wiki/RateLimit::Result) Object, where `result.success?` will be `false`. Otherwise, it increments the attempts counter in the cache and sets `result.success?` to `true`.
|
49
43
|
|
44
|
+
#### Example
|
50
45
|
```ruby
|
51
|
-
|
52
|
-
RateLimit.throttle!(topic: :send_sms, namespace: :user_id, value: id) do
|
53
|
-
# Logic goes Here
|
54
|
-
end
|
55
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
56
|
-
# Error Handling Logic goes here
|
57
|
-
e.topic # :login
|
58
|
-
e.namespace # :user_id
|
59
|
-
e.value # id
|
60
|
-
e.threshold # 2
|
61
|
-
e.interval # 60
|
62
|
-
end
|
63
|
-
```
|
64
|
-
|
65
|
-
#### Advanced
|
46
|
+
result = RateLimit.throttle(topic: :login, value: 123)
|
66
47
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
begin
|
71
|
-
throttler.perform! do
|
72
|
-
# Logic goes Here
|
73
|
-
end
|
74
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
75
|
-
# Error Handling Logic goes here
|
76
|
-
end
|
77
|
-
```
|
78
|
-
|
79
|
-
#### Manual
|
80
|
-
|
81
|
-
```ruby
|
82
|
-
throttler = RateLimit::Throttler.new(topic: :login, namespace: :user_id, value: id)
|
83
|
-
|
84
|
-
unless throttler.limit_exceeded?
|
85
|
-
# Logic goes Here
|
86
|
-
|
87
|
-
throttler.increment_counters
|
48
|
+
if result.success?
|
49
|
+
# Do something
|
88
50
|
end
|
89
51
|
```
|
90
52
|
|
91
|
-
#### Nested throttles
|
92
53
|
|
93
|
-
|
94
|
-
begin
|
95
|
-
RateLimit.throttle!(topic: :send_sms, namespace: :user_id, value: id) do
|
96
|
-
RateLimit.throttle!(topic: :send_sms, namespace: :phone_number, value: number) do
|
97
|
-
# Logic goes Here
|
98
|
-
end
|
99
|
-
end
|
100
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
101
|
-
# Error Handling Logic goes here
|
102
|
-
end
|
103
|
-
```
|
54
|
+
Please check the [Wiki](https://github.com/catawiki/rate-limit/wiki) for advanced throttling and options.
|
104
55
|
|
105
|
-
|
56
|
+
## [Configuration](https://github.com/catawiki/rate-limit/wiki/Configuration)
|
106
57
|
|
107
58
|
Customize the configuration by adding the following block to `config/initializers/rate_limit.rb`
|
108
59
|
|
@@ -113,6 +64,14 @@ RateLimit.configure do |config|
|
|
113
64
|
config.default_interval = 60
|
114
65
|
config.default_threshold = 2
|
115
66
|
config.limits_file_path = 'config/rate-limit.yml'
|
67
|
+
config.on_success = proc { |result|
|
68
|
+
# Success Logic Goes HERE
|
69
|
+
# result.topic, result.value
|
70
|
+
}
|
71
|
+
config.on_failure = proc { |result|
|
72
|
+
# Failure Logic Goes HERE
|
73
|
+
# result.topic, result.value, result.threshold, result.interval
|
74
|
+
}
|
116
75
|
end
|
117
76
|
```
|
118
77
|
|
data/lib/rate-limit.rb
ADDED
data/lib/rate_limit/base.rb
CHANGED
@@ -3,29 +3,21 @@
|
|
3
3
|
module RateLimit
|
4
4
|
module Base
|
5
5
|
def throttle(**args)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def throttle!(**args)
|
12
|
-
Throttler.new(**args).perform! { yield if block_given? }
|
13
|
-
end
|
14
|
-
|
15
|
-
def throttle_only_failures!(**args)
|
16
|
-
Throttler.new(**args).perform_only_failures! { yield if block_given? }
|
6
|
+
worker = Worker.new(**args)
|
7
|
+
worker.throttle { yield if block_given? }
|
8
|
+
worker.result
|
17
9
|
end
|
18
10
|
|
19
11
|
def limit_exceeded?(**args)
|
20
|
-
|
12
|
+
Worker.new(**args).reloaded_limit_exceeded?
|
21
13
|
end
|
22
14
|
|
23
15
|
def reset_counters(**args)
|
24
|
-
|
16
|
+
Worker.new(**args).clear_cache_counter
|
25
17
|
end
|
26
18
|
|
27
19
|
def increment_counters(**args)
|
28
|
-
|
20
|
+
Worker.new(**args).increment_cache_counter
|
29
21
|
end
|
30
22
|
end
|
31
23
|
end
|
data/lib/rate_limit/config.rb
CHANGED
@@ -9,6 +9,8 @@ module RateLimit
|
|
9
9
|
attr_accessor :default_interval,
|
10
10
|
:default_threshold,
|
11
11
|
:limits_file_path,
|
12
|
+
:on_success,
|
13
|
+
:on_failure,
|
12
14
|
:fail_safe,
|
13
15
|
:redis
|
14
16
|
|
@@ -24,6 +26,14 @@ module RateLimit
|
|
24
26
|
raw_limits[topic] || Defaults.raw_limits
|
25
27
|
end
|
26
28
|
|
29
|
+
def success_callback(*args)
|
30
|
+
on_success&.call(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure_callback(*args)
|
34
|
+
on_failure&.call(*args)
|
35
|
+
end
|
36
|
+
|
27
37
|
private
|
28
38
|
|
29
39
|
def raw_limits
|
@@ -3,18 +3,18 @@
|
|
3
3
|
module RateLimit
|
4
4
|
module Errors
|
5
5
|
class LimitExceededError < StandardError
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :result
|
7
7
|
|
8
|
-
delegate :topic, :
|
8
|
+
delegate :topic, :value, :threshold, :interval, to: :result
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(result)
|
11
|
+
@result = result
|
12
12
|
|
13
13
|
super(custom_message)
|
14
14
|
end
|
15
15
|
|
16
16
|
def custom_message
|
17
|
-
"#{topic}:
|
17
|
+
"#{result.topic}: has exceeded #{result.threshold} in #{result.interval} seconds"
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Result
|
5
|
+
# Attributes
|
6
|
+
attr_accessor :topic, :value, :threshold, :interval
|
7
|
+
|
8
|
+
# Methods
|
9
|
+
def initialize(topic:, value:)
|
10
|
+
@topic = topic
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
@success
|
16
|
+
end
|
17
|
+
|
18
|
+
def success!
|
19
|
+
@success = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def failure!(worker)
|
23
|
+
@success = false
|
24
|
+
@threshold = worker.exceeded_window&.threshold
|
25
|
+
@interval = worker.exceeded_window&.interval
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/rate_limit/throttler.rb
CHANGED
@@ -1,57 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RateLimit
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(topic:, value:, namespace: nil)
|
8
|
-
@topic = topic.to_s
|
9
|
-
@value = value.to_i
|
10
|
-
@namespace = namespace&.to_s
|
11
|
-
@windows = Limit.fetch(topic).map { |limit| Window.new(self, limit) }
|
12
|
-
end
|
13
|
-
|
14
|
-
def perform!
|
15
|
-
validate_limit!
|
4
|
+
module Throttler
|
5
|
+
def throttle
|
6
|
+
return failure! if reloaded_limit_exceeded?
|
16
7
|
|
17
8
|
yield if block_given?
|
18
9
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def perform_only_failures!
|
25
|
-
validate_limit!
|
26
|
-
|
27
|
-
begin
|
28
|
-
yield if block_given?
|
29
|
-
rescue StandardError => e
|
30
|
-
increment_cache_counter
|
31
|
-
raise e
|
32
|
-
end
|
33
|
-
|
34
|
-
true
|
35
|
-
end
|
36
|
-
|
37
|
-
def limit_exceeded?
|
38
|
-
Window.find_exceeded(windows).present?
|
39
|
-
end
|
40
|
-
|
41
|
-
def increment_cache_counter
|
42
|
-
Window.increment_cache_counter(windows)
|
43
|
-
end
|
44
|
-
|
45
|
-
def clear_cache_counter
|
46
|
-
Window.clear_cache_counter(windows)
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def validate_limit!
|
52
|
-
exceeded_window = Window.find_exceeded(windows)
|
53
|
-
|
54
|
-
raise Errors::LimitExceededError, exceeded_window if exceeded_window
|
10
|
+
return success! unless only_failures
|
11
|
+
rescue StandardError => e
|
12
|
+
success! unless e.is_a?(Errors::LimitExceededError)
|
13
|
+
raise e
|
55
14
|
end
|
56
15
|
end
|
57
16
|
end
|
data/lib/rate_limit/version.rb
CHANGED
data/lib/rate_limit/window.rb
CHANGED
@@ -2,18 +2,18 @@
|
|
2
2
|
|
3
3
|
module RateLimit
|
4
4
|
class Window
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :worker, :limit
|
6
6
|
|
7
|
-
delegate :topic, :
|
7
|
+
delegate :topic, :value, to: :worker
|
8
8
|
delegate :threshold, :interval, to: :limit
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
@limit
|
10
|
+
def initialize(worker, limit)
|
11
|
+
@worker = worker
|
12
|
+
@limit = limit
|
13
13
|
end
|
14
14
|
|
15
15
|
def key
|
16
|
-
@key ||= [topic,
|
16
|
+
@key ||= [topic, value, interval].join(':')
|
17
17
|
end
|
18
18
|
|
19
19
|
def cached_counter
|
@@ -21,6 +21,10 @@ module RateLimit
|
|
21
21
|
end
|
22
22
|
|
23
23
|
class << self
|
24
|
+
def find_all(topic:, worker:)
|
25
|
+
Limit.fetch(topic).map { |limit| Window.new(worker, limit) }
|
26
|
+
end
|
27
|
+
|
24
28
|
def find_exceeded(windows)
|
25
29
|
windows.find { |w| w.cached_counter >= w.threshold }
|
26
30
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Worker
|
5
|
+
include Throttler
|
6
|
+
|
7
|
+
attr_accessor :topic, :value, :limits, :windows, :exceeded_window, :result, :raise_errors, :only_failures
|
8
|
+
|
9
|
+
def initialize(topic:, value:, raise_errors: false, only_failures: false)
|
10
|
+
@topic = topic.to_s
|
11
|
+
@value = value.to_s
|
12
|
+
@windows = Window.find_all(worker: self, topic: @topic)
|
13
|
+
@result = Result.new(topic: @topic, value: @value)
|
14
|
+
@raise_errors = raise_errors
|
15
|
+
@only_failures = only_failures
|
16
|
+
end
|
17
|
+
|
18
|
+
def increment_cache_counter
|
19
|
+
Window.increment_cache_counter(windows)
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_cache_counter
|
23
|
+
Window.clear_cache_counter(windows)
|
24
|
+
end
|
25
|
+
|
26
|
+
def reloaded_limit_exceeded?
|
27
|
+
@exceeded_window = Window.find_exceeded(windows)
|
28
|
+
|
29
|
+
limit_exceeded?
|
30
|
+
end
|
31
|
+
|
32
|
+
def limit_exceeded?
|
33
|
+
exceeded_window.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def success!
|
37
|
+
increment_cache_counter
|
38
|
+
result.success!
|
39
|
+
RateLimit.config.success_callback(result)
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure!
|
43
|
+
result.failure!(self)
|
44
|
+
RateLimit.config.failure_callback(result)
|
45
|
+
|
46
|
+
raise Errors::LimitExceededError, result if raise_errors
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/rate_limit.rb
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'active_support/core_ext/module'
|
4
4
|
require_relative 'rate_limit/configurable'
|
5
|
+
require_relative 'rate_limit/result'
|
5
6
|
require_relative 'rate_limit/cache'
|
6
7
|
require_relative 'rate_limit/window'
|
7
8
|
require_relative 'rate_limit/throttler'
|
9
|
+
require_relative 'rate_limit/worker'
|
8
10
|
require_relative 'rate_limit/limit'
|
9
11
|
require_relative 'rate_limit/errors/limit_exceeded_error'
|
10
12
|
require_relative 'rate_limit/base'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rate-limit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mohamed Motaweh
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- CHANGELOG.md
|
61
61
|
- LICENSE
|
62
62
|
- README.md
|
63
|
+
- lib/rate-limit.rb
|
63
64
|
- lib/rate_limit.rb
|
64
65
|
- lib/rate_limit/base.rb
|
65
66
|
- lib/rate_limit/cache.rb
|
@@ -69,9 +70,11 @@ files:
|
|
69
70
|
- lib/rate_limit/configurable.rb
|
70
71
|
- lib/rate_limit/errors/limit_exceeded_error.rb
|
71
72
|
- lib/rate_limit/limit.rb
|
73
|
+
- lib/rate_limit/result.rb
|
72
74
|
- lib/rate_limit/throttler.rb
|
73
75
|
- lib/rate_limit/version.rb
|
74
76
|
- lib/rate_limit/window.rb
|
77
|
+
- lib/rate_limit/worker.rb
|
75
78
|
homepage: https://github.com/catawiki/rate-limit
|
76
79
|
licenses:
|
77
80
|
- MIT
|
@@ -95,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
98
|
- !ruby/object:Gem::Version
|
96
99
|
version: '0'
|
97
100
|
requirements: []
|
98
|
-
rubygems_version: 3.
|
101
|
+
rubygems_version: 3.2.3
|
99
102
|
signing_key:
|
100
103
|
specification_version: 4
|
101
104
|
summary: A Rate Limiting Gem
|