rate-limit 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -7
- data/README.md +18 -71
- data/lib/rate_limit/base.rb +1 -15
- data/lib/rate_limit/errors/limit_exceeded_error.rb +5 -5
- data/lib/rate_limit/result.rb +14 -8
- data/lib/rate_limit/throttler.rb +5 -22
- data/lib/rate_limit/version.rb +1 -1
- data/lib/rate_limit/window.rb +2 -2
- data/lib/rate_limit/worker.rb +13 -9
- metadata +2 -2
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
@@ -7,14 +7,40 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
-
## [v0.
|
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
|
11
37
|
|
12
38
|
|
13
39
|
### Added
|
14
40
|
|
15
|
-
-
|
16
|
-
-
|
17
|
-
-
|
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`
|
18
44
|
|
19
45
|
### Changed
|
20
46
|
|
@@ -26,8 +52,8 @@ and this project adheres to [Semantic Versioning].
|
|
26
52
|
|
27
53
|
### Fixed
|
28
54
|
|
29
|
-
-
|
30
|
-
-
|
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
|
31
57
|
|
32
58
|
|
33
59
|
## v0.0.1 - 2022-09-09
|
@@ -40,4 +66,5 @@ and this project adheres to [Semantic Versioning].
|
|
40
66
|
<!-- versions -->
|
41
67
|
|
42
68
|
[Unreleased]: https://github.com/catawiki/rate-limit/compare/v0.1.0...HEAD
|
43
|
-
[v0.
|
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,85 +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
|
-
|
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 |
|
37
40
|
|
38
|
-
if result.success?
|
39
|
-
# Do something
|
40
|
-
end
|
41
|
-
```
|
42
|
-
or
|
43
41
|
|
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`.
|
43
|
+
|
44
|
+
#### Example
|
44
45
|
```ruby
|
45
|
-
result = RateLimit.throttle(topic: :login,
|
46
|
+
result = RateLimit.throttle(topic: :login, value: 123)
|
46
47
|
|
47
48
|
if result.success?
|
48
49
|
# Do something
|
49
50
|
end
|
50
51
|
```
|
51
52
|
|
52
|
-
#### Basic with exception `RateLimit.throttle!`
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
begin
|
56
|
-
RateLimit.throttle_with_block!(topic: :send_sms, namespace: :user_id, value: id) do
|
57
|
-
# Logic goes Here
|
58
|
-
end
|
59
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
60
|
-
# Error Handling Logic goes here
|
61
|
-
e.topic # :login
|
62
|
-
e.namespace # :user_id
|
63
|
-
e.value # id
|
64
|
-
e.threshold # 2
|
65
|
-
e.interval # 60
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
#### Advanced
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
throttler = RateLimit::Worker.new(topic: :login, namespace: :user_id, value: id)
|
73
|
-
|
74
|
-
begin
|
75
|
-
throttler.throttle_with_block! do
|
76
|
-
# Logic goes Here
|
77
|
-
end
|
78
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
79
|
-
# Error Handling Logic goes here
|
80
|
-
end
|
81
|
-
```
|
82
|
-
|
83
|
-
#### Manual
|
84
53
|
|
85
|
-
|
86
|
-
throttler = RateLimit::Worker.new(topic: :login, namespace: :user_id, value: id)
|
87
|
-
|
88
|
-
unless throttler.limit_exceeded?
|
89
|
-
# Logic goes Here
|
90
|
-
|
91
|
-
throttler.increment_counters
|
92
|
-
end
|
93
|
-
```
|
94
|
-
|
95
|
-
#### Nested throttles
|
96
|
-
|
97
|
-
```ruby
|
98
|
-
begin
|
99
|
-
RateLimit.throttle_with_block!(topic: :send_sms, namespace: :user_id, value: id) do
|
100
|
-
RateLimit.throttle_with_block!(topic: :send_sms, namespace: :phone_number, value: number) do
|
101
|
-
# Logic goes Here
|
102
|
-
end
|
103
|
-
end
|
104
|
-
rescue RateLimit::Errors::LimitExceededError => e
|
105
|
-
# Error Handling Logic goes here
|
106
|
-
end
|
107
|
-
```
|
54
|
+
Please check the [Wiki](https://github.com/catawiki/rate-limit/wiki) for advanced throttling and options.
|
108
55
|
|
109
|
-
|
56
|
+
## [Configuration](https://github.com/catawiki/rate-limit/wiki/Configuration)
|
110
57
|
|
111
58
|
Customize the configuration by adding the following block to `config/initializers/rate_limit.rb`
|
112
59
|
|
@@ -119,11 +66,11 @@ RateLimit.configure do |config|
|
|
119
66
|
config.limits_file_path = 'config/rate-limit.yml'
|
120
67
|
config.on_success = proc { |result|
|
121
68
|
# Success Logic Goes HERE
|
122
|
-
# result.topic, result.
|
69
|
+
# result.topic, result.value
|
123
70
|
}
|
124
71
|
config.on_failure = proc { |result|
|
125
72
|
# Failure Logic Goes HERE
|
126
|
-
# result.topic, result.
|
73
|
+
# result.topic, result.value, result.threshold, result.interval
|
127
74
|
}
|
128
75
|
end
|
129
76
|
```
|
data/lib/rate_limit/base.rb
CHANGED
@@ -4,21 +4,7 @@ module RateLimit
|
|
4
4
|
module Base
|
5
5
|
def throttle(**args)
|
6
6
|
worker = Worker.new(**args)
|
7
|
-
worker.throttle
|
8
|
-
worker.result
|
9
|
-
end
|
10
|
-
|
11
|
-
def throttle_with_block!(**args, &block)
|
12
|
-
worker = Worker.new(**args)
|
13
|
-
|
14
|
-
worker.throttle_with_block!(&block)
|
15
|
-
worker.result
|
16
|
-
end
|
17
|
-
|
18
|
-
def throttle_only_failures_with_block!(**args, &block)
|
19
|
-
worker = Worker.new(**args)
|
20
|
-
|
21
|
-
worker.throttle_only_failures_with_block!(&block)
|
7
|
+
worker.throttle { yield if block_given? }
|
22
8
|
worker.result
|
23
9
|
end
|
24
10
|
|
@@ -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
|
data/lib/rate_limit/result.rb
CHANGED
@@ -3,20 +3,26 @@
|
|
3
3
|
module RateLimit
|
4
4
|
class Result
|
5
5
|
# Attributes
|
6
|
-
attr_accessor :topic, :
|
6
|
+
attr_accessor :topic, :value, :threshold, :interval
|
7
7
|
|
8
8
|
# Methods
|
9
|
-
def initialize(
|
10
|
-
@topic
|
11
|
-
@
|
12
|
-
@value = worker.value
|
13
|
-
@threshold = worker.exceeded_window&.threshold
|
14
|
-
@interval = worker.exceeded_window&.interval
|
15
|
-
@success = success
|
9
|
+
def initialize(topic:, value:)
|
10
|
+
@topic = topic
|
11
|
+
@value = value
|
16
12
|
end
|
17
13
|
|
18
14
|
def success?
|
19
15
|
@success
|
20
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
|
21
27
|
end
|
22
28
|
end
|
data/lib/rate_limit/throttler.rb
CHANGED
@@ -5,29 +5,12 @@ module RateLimit
|
|
5
5
|
def throttle
|
6
6
|
return failure! if reloaded_limit_exceeded?
|
7
7
|
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def throttle_with_block!
|
12
|
-
if reloaded_limit_exceeded?
|
13
|
-
failure!
|
14
|
-
raise Errors::LimitExceededError, exceeded_window
|
15
|
-
end
|
16
|
-
|
17
|
-
yield
|
18
|
-
|
19
|
-
success!
|
20
|
-
end
|
21
|
-
|
22
|
-
def throttle_only_failures_with_block!
|
23
|
-
return failure! if reloaded_limit_exceeded?
|
8
|
+
yield if block_given?
|
24
9
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
raise e
|
30
|
-
end
|
10
|
+
return success! unless only_failures
|
11
|
+
rescue StandardError => e
|
12
|
+
success! unless e.is_a?(Errors::LimitExceededError)
|
13
|
+
raise e
|
31
14
|
end
|
32
15
|
end
|
33
16
|
end
|
data/lib/rate_limit/version.rb
CHANGED
data/lib/rate_limit/window.rb
CHANGED
@@ -4,7 +4,7 @@ module RateLimit
|
|
4
4
|
class Window
|
5
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
10
|
def initialize(worker, limit)
|
@@ -13,7 +13,7 @@ module RateLimit
|
|
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
|
data/lib/rate_limit/worker.rb
CHANGED
@@ -4,13 +4,15 @@ module RateLimit
|
|
4
4
|
class Worker
|
5
5
|
include Throttler
|
6
6
|
|
7
|
-
attr_accessor :topic, :
|
8
|
-
|
9
|
-
def initialize(topic:, value:,
|
10
|
-
@topic
|
11
|
-
@value
|
12
|
-
@
|
13
|
-
@
|
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
|
14
16
|
end
|
15
17
|
|
16
18
|
def increment_cache_counter
|
@@ -33,13 +35,15 @@ module RateLimit
|
|
33
35
|
|
34
36
|
def success!
|
35
37
|
increment_cache_counter
|
36
|
-
|
38
|
+
result.success!
|
37
39
|
RateLimit.config.success_callback(result)
|
38
40
|
end
|
39
41
|
|
40
42
|
def failure!
|
41
|
-
|
43
|
+
result.failure!(self)
|
42
44
|
RateLimit.config.failure_callback(result)
|
45
|
+
|
46
|
+
raise Errors::LimitExceededError, result if raise_errors
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
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.
|
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
|