rate-limit 0.0.1
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 +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +21 -0
- data/README.md +160 -0
- data/lib/rate_limit/base.rb +31 -0
- data/lib/rate_limit/cache.rb +38 -0
- data/lib/rate_limit/config/defaults.rb +23 -0
- data/lib/rate_limit/config/file_loader.rb +15 -0
- data/lib/rate_limit/config.rb +33 -0
- data/lib/rate_limit/configurable.rb +15 -0
- data/lib/rate_limit/errors/limit_exceeded_error.rb +21 -0
- data/lib/rate_limit/limit.rb +20 -0
- data/lib/rate_limit/throttler.rb +57 -0
- data/lib/rate_limit/version.rb +5 -0
- data/lib/rate_limit/window.rb +39 -0
- data/lib/rate_limit.rb +16 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9b6d225525d8976ef1c3c8e7d0fdb1c71a48071357cff8f64633117a13ab714e
|
4
|
+
data.tar.gz: 057bf093d1271c83795c34472cf31e273353a24621bddebb86a901b48d451e8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2e6a6fe077419b22d5dcc5357fdf127ab7c944aff6e1bf9d44ece39cf56f1fc1b6b12ac400aa1a2ffdeab9964f90f7739952cda796b3f23a82748410c64b324b
|
7
|
+
data.tar.gz: 19f68ffd995fca03b3c6787a208649b2c11c2206dd598a6b82feea25b51451817a3f354ca5de7fd9435a6fb6a61b8ac763db37a9dc2535425627eeccfc976333
|
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 Catawiki B.V.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# RateLimit
|
2
|
+

|
3
|
+
|
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
|
+
|
6
|
+
This gem mainly provides brute-force protection by throttling attepmts 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
|
+
|
8
|
+
#### Common Use Cases
|
9
|
+
* [Login] Brute-force attempts for a spefic account
|
10
|
+
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a spefic user_id
|
11
|
+
* [SMS Spam] Brute-force attempts for requesting Phone Verification SMS for a spefic phone_number
|
12
|
+
* [Verifications] Brute-force attempts for entering verification codes
|
13
|
+
* [Redeem] Brute-force attempts to redeem voucher codes from a specific account
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'rate-limit'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle install
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install rate-limit
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
#### Basic `RateLimit.throttle`
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
if RateLimit.throttle(topic: :login, namespace: :user_id, value: id)
|
37
|
+
# Do something
|
38
|
+
end
|
39
|
+
```
|
40
|
+
or
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
if RateLimit.throttle(topic: :login, value: id)
|
44
|
+
# Do something
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
#### Basic with exception `RateLimit.throttle!`
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
begin
|
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
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
throttler = RateLimit::Throttler.new(topic: :login, namespace: :user_id, value: id)
|
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
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Nested throttles
|
92
|
+
|
93
|
+
```ruby
|
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
|
+
```
|
104
|
+
|
105
|
+
### Config
|
106
|
+
|
107
|
+
Customize the configuration by adding the following block to `config/initializers/rate_limit.rb`
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
RateLimit.configure do |config|
|
111
|
+
config.redis = Redis.new
|
112
|
+
config.fail_safe = true
|
113
|
+
config.default_interval = 60
|
114
|
+
config.default_threshold = 2
|
115
|
+
config.limits_file_path = 'config/rate-limit.yml'
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
#### Define Limits
|
120
|
+
|
121
|
+
The `config/rate-limit.yml` should include the limits you want to enforce on each given topic. In the following format:
|
122
|
+
|
123
|
+
```yaml
|
124
|
+
topic:
|
125
|
+
threshold: interval
|
126
|
+
```
|
127
|
+
|
128
|
+
##### Example
|
129
|
+
|
130
|
+
* maximum `2` login attempts per `60` seconds
|
131
|
+
* maximum `1` send sms attempts per `60` seconds
|
132
|
+
* maximum `5` send sms attempts per `300` seconds
|
133
|
+
* maximum `10` send sms attempts per `3000` seconds
|
134
|
+
|
135
|
+
```yaml
|
136
|
+
login:
|
137
|
+
2: 60
|
138
|
+
send_sms:
|
139
|
+
1: 60
|
140
|
+
5: 300
|
141
|
+
10: 3000
|
142
|
+
```
|
143
|
+
|
144
|
+
## Development
|
145
|
+
|
146
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
147
|
+
|
148
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
149
|
+
|
150
|
+
## Contributing
|
151
|
+
|
152
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/catawiki/rate-limit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/catawiki/rate-limit/blob/master/CODE_OF_CONDUCT.md).
|
153
|
+
|
154
|
+
## License
|
155
|
+
|
156
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
157
|
+
|
158
|
+
## Code of Conduct
|
159
|
+
|
160
|
+
Everyone interacting in the RateLimit project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/catawiki/rate-limit/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
module Base
|
5
|
+
def throttle(**args)
|
6
|
+
throttle!(**args) { yield if block_given? }
|
7
|
+
rescue Errors::LimitExceededError => _e
|
8
|
+
false
|
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? }
|
17
|
+
end
|
18
|
+
|
19
|
+
def limit_exceeded?(**args)
|
20
|
+
Throttler.new(**args).limit_exceeded?
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset_counters(**args)
|
24
|
+
Throttler.new(**args).clear_cache_counter
|
25
|
+
end
|
26
|
+
|
27
|
+
def increment_counters(**args)
|
28
|
+
Throttler.new(**args).increment_cache_counter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
module Cache
|
5
|
+
class << self
|
6
|
+
def write(options)
|
7
|
+
RateLimit.config.redis.multi do |redis|
|
8
|
+
options.each do |key, value|
|
9
|
+
redis.incr(key)
|
10
|
+
redis.expire(key, value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
rescue ::Redis::BaseError => e
|
14
|
+
return true if RateLimit.config.fail_safe
|
15
|
+
|
16
|
+
raise e
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(key)
|
20
|
+
RateLimit.config.redis.get(key)
|
21
|
+
rescue ::Redis::BaseError => e
|
22
|
+
return 0 if RateLimit.config.fail_safe
|
23
|
+
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear(keys)
|
28
|
+
RateLimit.config.redis.multi do |redis|
|
29
|
+
keys.each { |k| redis.del(k) }
|
30
|
+
end
|
31
|
+
rescue ::Redis::BaseError => e
|
32
|
+
return true if RateLimit.config.fail_safe
|
33
|
+
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Config
|
5
|
+
module Defaults
|
6
|
+
# Limits File Path
|
7
|
+
LIMITS_FILE_PATH = 'config/rate-limit.yml'
|
8
|
+
|
9
|
+
# Fixed Window Defaults
|
10
|
+
WINDOW_INTERVAL = 60
|
11
|
+
WINDOW_THRESHOLD = 2
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def raw_limits
|
15
|
+
{
|
16
|
+
RateLimit.config.default_threshold => \
|
17
|
+
RateLimit.config.default_interval
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module RateLimit
|
6
|
+
class Config
|
7
|
+
module FileLoader
|
8
|
+
def self.fetch
|
9
|
+
return {} unless File.exist?(RateLimit.config.limits_file_path)
|
10
|
+
|
11
|
+
YAML.load_file(RateLimit.config.limits_file_path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis'
|
4
|
+
require_relative 'config/defaults'
|
5
|
+
require_relative 'config/file_loader'
|
6
|
+
|
7
|
+
module RateLimit
|
8
|
+
class Config
|
9
|
+
attr_accessor :default_interval,
|
10
|
+
:default_threshold,
|
11
|
+
:limits_file_path,
|
12
|
+
:fail_safe,
|
13
|
+
:redis
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@redis = Redis.new
|
17
|
+
@fail_safe = true
|
18
|
+
@limits_file_path = Defaults::LIMITS_FILE_PATH
|
19
|
+
@default_interval = Defaults::WINDOW_INTERVAL
|
20
|
+
@default_threshold = Defaults::WINDOW_THRESHOLD
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw_limits_for(topic)
|
24
|
+
raw_limits[topic] || Defaults.raw_limits
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def raw_limits
|
30
|
+
@raw_limits ||= FileLoader.fetch
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
module Errors
|
5
|
+
class LimitExceededError < StandardError
|
6
|
+
attr_reader :window
|
7
|
+
|
8
|
+
delegate :topic, :namespace, :value, :threshold, :interval, to: :window
|
9
|
+
|
10
|
+
def initialize(window)
|
11
|
+
@window = window
|
12
|
+
|
13
|
+
super(custom_message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def custom_message
|
17
|
+
"#{topic}: #{namespace} has exceeded #{threshold} in #{interval} seconds"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Limit
|
5
|
+
attr_accessor :threshold, :interval
|
6
|
+
|
7
|
+
def initialize(threshold, interval)
|
8
|
+
@threshold = threshold
|
9
|
+
@interval = interval
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def fetch(topic)
|
14
|
+
RateLimit.config.raw_limits_for(topic).map do |threshold, interval|
|
15
|
+
Limit.new(threshold, interval)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Throttler
|
5
|
+
attr_accessor :topic, :namespace, :value, :limits, :windows
|
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!
|
16
|
+
|
17
|
+
yield if block_given?
|
18
|
+
|
19
|
+
increment_cache_counter
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
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
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimit
|
4
|
+
class Window
|
5
|
+
attr_accessor :throttler, :limit
|
6
|
+
|
7
|
+
delegate :topic, :namespace, :value, to: :throttler
|
8
|
+
delegate :threshold, :interval, to: :limit
|
9
|
+
|
10
|
+
def initialize(throttler, limit)
|
11
|
+
@throttler = throttler
|
12
|
+
@limit = limit
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
@key ||= [topic, namespace, value, interval].join(':')
|
17
|
+
end
|
18
|
+
|
19
|
+
def cached_counter
|
20
|
+
Cache.read(key).to_i || 0
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def find_exceeded(windows)
|
25
|
+
windows.find { |w| w.cached_counter >= w.threshold }
|
26
|
+
end
|
27
|
+
|
28
|
+
def increment_cache_counter(windows)
|
29
|
+
Cache.write(
|
30
|
+
windows.each_with_object({}) { |w, h| h[w.key] = w.interval }
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear_cache_counter(windows)
|
35
|
+
Cache.clear(windows.map(&:key))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/rate_limit.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module'
|
4
|
+
require_relative 'rate_limit/configurable'
|
5
|
+
require_relative 'rate_limit/cache'
|
6
|
+
require_relative 'rate_limit/window'
|
7
|
+
require_relative 'rate_limit/throttler'
|
8
|
+
require_relative 'rate_limit/limit'
|
9
|
+
require_relative 'rate_limit/errors/limit_exceeded_error'
|
10
|
+
require_relative 'rate_limit/base'
|
11
|
+
require_relative 'rate_limit/version'
|
12
|
+
|
13
|
+
module RateLimit
|
14
|
+
extend RateLimit::Configurable
|
15
|
+
extend RateLimit::Base
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rate-limit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mohamed Motaweh
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-09-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
- - "<="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.4
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.2'
|
30
|
+
- - "<="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.0.4
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: redis
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 3.0.0
|
40
|
+
- - "<="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.1.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 3.0.0
|
50
|
+
- - "<="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 5.1.0
|
53
|
+
description:
|
54
|
+
email:
|
55
|
+
- opensource@catawiki.nl
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- CHANGELOG.md
|
61
|
+
- LICENSE
|
62
|
+
- README.md
|
63
|
+
- lib/rate_limit.rb
|
64
|
+
- lib/rate_limit/base.rb
|
65
|
+
- lib/rate_limit/cache.rb
|
66
|
+
- lib/rate_limit/config.rb
|
67
|
+
- lib/rate_limit/config/defaults.rb
|
68
|
+
- lib/rate_limit/config/file_loader.rb
|
69
|
+
- lib/rate_limit/configurable.rb
|
70
|
+
- lib/rate_limit/errors/limit_exceeded_error.rb
|
71
|
+
- lib/rate_limit/limit.rb
|
72
|
+
- lib/rate_limit/throttler.rb
|
73
|
+
- lib/rate_limit/version.rb
|
74
|
+
- lib/rate_limit/window.rb
|
75
|
+
homepage: https://github.com/catawiki/rate-limit
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata:
|
79
|
+
homepage_uri: https://github.com/catawiki/rate-limit
|
80
|
+
source_code_uri: https://github.com/catawiki/rate-limit
|
81
|
+
changelog_uri: https://github.com/catawiki/rate-limit/blob/master/CHANGELOG.md
|
82
|
+
rubygems_mfa_required: 'true'
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '2.7'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.1.6
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: A Rate Limiting Gem
|
102
|
+
test_files: []
|