rate-limit 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +47 -8
- 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 +15 -11
- 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: 11a836041207b190f37ebc39fc5f3dad8f550fee8193c4a1bb7a0ec1add638f1
|
4
|
+
data.tar.gz: a213f8f17a0630f7a8f4d43ec8c7072e4f0f1ecbc015723aa06aeb1ea802883d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77dd6eefc19f5af422efd9f1d31db21a0697c0325cf6d39bc02798112fef5706604c6ee15418df49ef75ee7f6801dcd576f6a8ebfaa306c01defaf7f378f8017
|
7
|
+
data.tar.gz: 5580d905fcbf812e925ea22a4b203043ee12d68996cbe4bb0589cbd74ecaf85a04cb2d6ea1a37cf3f23b5861e0bae76f921269de3fe24ce5dcfc405325b74bea
|
data/CHANGELOG.md
CHANGED
@@ -7,14 +7,51 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
-
## [v0.1
|
10
|
+
## [v0.2.1] - 2022-11-09
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- https://github.com/catawiki/rate-limit/pull/28 added Integration Tests
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
|
18
|
+
- https://github.com/catawiki/rate-limit/issues/29 Calling throttle with only_failures: true does not call success callback
|
19
|
+
|
20
|
+
|
21
|
+
## [v0.2.0] - 2022-10-05
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- added `RateLimit::Errors::LimitExceededError#result`
|
26
|
+
- added `Worker#raise_errors`
|
27
|
+
- added `Worker#only_failures`
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
|
31
|
+
- changed `RateLimit::Result.initialize`
|
32
|
+
|
33
|
+
### Removed
|
34
|
+
|
35
|
+
- removed `RateLimit::Result#namespace`
|
36
|
+
- removed `RateLimit::Worker#namespace`
|
37
|
+
- removed `RateLimit::Errors::LimitExceededError#namespace`
|
38
|
+
- removed `RateLimit::Errors::LimitExceededError#worker`
|
39
|
+
- removed `RateLimit.throttle_with_block!` and `Worker#throttle_with_block!` in favour of `.throttle` with `{ raise_errors: true }` option
|
40
|
+
- removed `RateLimit.throttle_only_failures_with_block!` and `Worker#throttle_only_failures_with_block!` in favour of `.throttle` with `{ only_failures: true }` option
|
41
|
+
|
42
|
+
|
43
|
+
### Fixed
|
44
|
+
|
45
|
+
- https://github.com/catawiki/rate-limit/issues/16 String Values are not respected
|
46
|
+
|
47
|
+
## [v0.1.0] - 2022-09-19
|
11
48
|
|
12
49
|
|
13
50
|
### Added
|
14
51
|
|
15
|
-
-
|
16
|
-
-
|
17
|
-
-
|
52
|
+
- https://github.com/catawiki/rate-limit/pull/11 `RateLimit::Result` class
|
53
|
+
- https://github.com/catawiki/rate-limit/pull/12 `RateLimit::Worker` class
|
54
|
+
- https://github.com/catawiki/rate-limit/pull/13 `RateLimit::Config#on_success` and `RateLimit::Config#on_failure`
|
18
55
|
|
19
56
|
### Changed
|
20
57
|
|
@@ -26,8 +63,8 @@ and this project adheres to [Semantic Versioning].
|
|
26
63
|
|
27
64
|
### Fixed
|
28
65
|
|
29
|
-
-
|
30
|
-
-
|
66
|
+
- https://github.com/catawiki/rate-limit/issues/7 Symbol topic names does not load the correct limits
|
67
|
+
- https://github.com/catawiki/rate-limit/issues/6 Main Module (RateLimit) fails to autoload
|
31
68
|
|
32
69
|
|
33
70
|
## v0.0.1 - 2022-09-09
|
@@ -39,5 +76,7 @@ and this project adheres to [Semantic Versioning].
|
|
39
76
|
|
40
77
|
<!-- versions -->
|
41
78
|
|
42
|
-
[Unreleased]: https://github.com/catawiki/rate-limit/compare/v0.1
|
43
|
-
[v0.1
|
79
|
+
[Unreleased]: https://github.com/catawiki/rate-limit/compare/v0.2.1...HEAD
|
80
|
+
[v0.2.1]: https://github.com/catawiki/rate-limit/compare/v0.2.0...v0.2.1
|
81
|
+
[v0.2.0]: https://github.com/catawiki/rate-limit/compare/v0.1.0...v0.2.0
|
82
|
+
[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
|
+
success!(skip_increment_cache: 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
|
@@ -31,15 +33,17 @@ module RateLimit
|
|
31
33
|
exceeded_window.present?
|
32
34
|
end
|
33
35
|
|
34
|
-
def success!
|
35
|
-
increment_cache_counter
|
36
|
-
|
36
|
+
def success!(skip_increment_cache: false)
|
37
|
+
increment_cache_counter unless skip_increment_cache
|
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.1
|
4
|
+
version: 0.2.1
|
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-09
|
11
|
+
date: 2022-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|