prorate 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89ad7bfadc58561263e35de3de2a0eef9c4b5aca
4
- data.tar.gz: 51591b0aabe76204c4a9029a095150faf434c84d
3
+ metadata.gz: dab868f09fd0b191abe56fdfdb156271331d397e
4
+ data.tar.gz: 0c9d8670999217b9b14f662a53b589f7c3b0b93d
5
5
  SHA512:
6
- metadata.gz: 5393f7eb13d4bd9236f3809c90215ae1a9700dd20a17c195db4180a526502278055365fc8e5dfce4644bba737a808cd738e7e6d3120f07ff733d2a8d214a7749
7
- data.tar.gz: 604f4fd70fd8d9506cdccff6e819d91d2e26eae94e70ba00f3cf212118ca5d2fb2eeab8052c0702bea4a76d423be93c860dded6de64cd140ad1ddacad9ea1e2e
6
+ metadata.gz: c7c9909a1ebf831a56b3216a3387598d1c72f20463962329a9d61ea993533f5298769af63f6e16d837a2b22f1f1d1008b7ae2b1413ac80ae58b264f08b72eeb5
7
+ data.tar.gz: b9c3b0b86240e6d8dd9b504a3c8bc2341bd230138e263edad0fc8e03db900cb9c1ca8e777f3ac22bea5e44f00750bad13568d47c315315d09608ab4bc597cc1c
@@ -1,7 +1,10 @@
1
1
  rvm:
2
- - 2.2.5
3
- - 2.3.3
4
- - 2.4.1
2
+ - 2.2
3
+ - 2.3
4
+ - 2.4
5
+ - 2.5
6
+ - 2.6
7
+ - 2.7
5
8
 
6
9
  services:
7
10
  - redis
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Prorate
2
2
 
3
- Provides a low-level time-based throttle. Is mainly meant for situations where using something like Rack::Attack is not very
4
- useful since you need access to more variables. Under the hood, this uses a Lua script that implements the
5
- [Leaky Bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm in a single threaded and race condition safe way.
3
+ Provides a low-level time-based throttle. Is mainly meant for situations where
4
+ using something like Rack::Attack is not very useful since you need access to
5
+ more variables. Under the hood, this uses a Lua script that implements the
6
+ [Leaky Bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm in a single
7
+ threaded and race condition safe way.
6
8
 
7
9
  [![Build Status](https://travis-ci.org/WeTransfer/prorate.svg?branch=master)](https://travis-ci.org/WeTransfer/prorate)
8
10
  [![Gem Version](https://badge.fury.io/rb/prorate.svg)](https://badge.fury.io/rb/prorate)
@@ -17,29 +19,106 @@ gem 'prorate'
17
19
 
18
20
  And then execute:
19
21
 
20
- $ bundle
22
+ ```shell
23
+ bundle install
24
+ ```
21
25
 
22
26
  Or install it yourself as:
23
27
 
24
- $ gem install prorate
28
+ ```shell
29
+ gem install prorate
30
+ ```
25
31
 
26
32
  ## Usage
27
33
 
34
+ The simplest mode of operation is throttling an endpoint, using the throttler
35
+ before the action happens.
36
+
28
37
  Within your Rails controller:
29
38
 
30
- t = Prorate::Throttle.new(redis: Redis.new, logger: Rails.logger,
31
- name: "throttle-login-email", limit: 20, period: 5.seconds)
32
- # Add all the parameters that function as a discriminator
33
- t << request.ip << params.require(:email)
34
- # ...and call the throttle! method
35
- t.throttle! # Will raise a Prorate::Throttled exception if the limit has been reached
39
+ ```ruby
40
+ t = Prorate::Throttle.new(
41
+ redis: Redis.new,
42
+ logger: Rails.logger,
43
+ name: "throttle-login-email",
44
+ limit: 20,
45
+ period: 5.seconds
46
+ )
47
+ # Add all the parameters that function as a discriminator.
48
+ t << request.ip << params.require(:email)
49
+ # ...and call the throttle! method
50
+ t.throttle! # Will raise a Prorate::Throttled exception if the limit has been reached
51
+ #
52
+ # Your regular action happens after this point
53
+ ```
54
+
55
+ To capture that exception, in the controller
56
+
57
+ ```ruby
58
+ rescue_from Prorate::Throttled do |e|
59
+ response.set_header('Retry-After', e.retry_in_seconds.to_s)
60
+ render nothing: true, status: 429
61
+ end
62
+ ```
63
+
64
+ ### Throttling and checking of its status
65
+
66
+ More exquisite control can be achieved by combining throttling (see previous
67
+ step) and - in subsequent calls - checking the status of the throttle before
68
+ invoking the throttle.
69
+
70
+ Let's say you have an endpoint that not only needs throttling, but you want to
71
+ ban [credential stuffers](https://en.wikipedia.org/wiki/Credential_stuffing)
72
+ outright. This is a multi-step process:
73
+
74
+ 1. Respond with a 429 if the discriminators of the request would land in an
75
+ already blocking 'credential-stuffing'-throttle
76
+ 1. Run your regular throttling
77
+ 1. Perform your sign in action
78
+ 1. If the sign in was unsuccessful, add the discriminators to the
79
+ 'credential-stuffing'-throttle
80
+
81
+ In your controller that would look like this:
82
+
83
+ ```ruby
84
+ t = Prorate::Throttle.new(
85
+ redis: Redis.new,
86
+ logger: Rails.logger,
87
+ name: "credential-stuffing",
88
+ limit: 20,
89
+ period: 20.minutes
90
+ )
91
+ # Add all the parameters that function as a discriminator.
92
+ t << request.ip
93
+ # And before anything else, check whether it is throttled
94
+ if t.status.throttled?
95
+ response.set_header('Retry-After', t.status.remaining_throttle_seconds.to_s)
96
+ render(nothing: true, status: 429) and return
97
+ end
98
+
99
+ # run your regular throttles for the endpoint
100
+ other_throttles.map(:throttle!)
101
+ # Perform your sign in logic..
102
+
103
+ user = YourSignInLogic.valid?(
104
+ email: params[:email],
105
+ password: params[:password]
106
+ )
107
+
108
+ # Add the request to the credential stuffing throttle if we didn't succeed
109
+ t.throttle! unless user
110
+
111
+ # the rest of your action
112
+ ```
36
113
 
37
114
  To capture that exception, in the controller
38
115
 
39
- rescue_from Prorate::Throttled do |e|
40
- response.set_header('Retry-After', e.retry_in_seconds.to_s)
41
- render nothing: true, status: 429
42
- end
116
+ ```ruby
117
+ rescue_from Prorate::Throttled do |e|
118
+ response.set_header('Retry-After', e.retry_in_seconds.to_s)
119
+ render nothing: true, status: 429
120
+ end
121
+ ```
43
122
 
44
123
  ## Development
45
124
 
@@ -51,8 +130,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
51
130
 
52
131
  Bug reports and pull requests are welcome on GitHub at https://github.com/WeTransfer/prorate.
53
132
 
54
-
55
133
  ## License
56
134
 
57
135
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
58
-
@@ -1,5 +1,5 @@
1
1
  -- Single threaded Leaky Bucket implementation.
2
- -- args: key_base, leak_rate, max_bucket_capacity, block_duration
2
+ -- args: key_base, leak_rate, max_bucket_capacity, block_duration, n_tokens
3
3
  -- returns: an array of two integers, the first of which indicates the remaining block time.
4
4
  -- if the block time is nonzero, the second integer is always zero. If the block time is zero,
5
5
  -- the second integer indicates the level of the bucket
@@ -96,6 +96,19 @@ module Prorate
96
96
  end
97
97
  end
98
98
 
99
+ def status
100
+ discriminator = Digest::SHA1.hexdigest(Marshal.dump(@discriminators))
101
+ identifier = [name, discriminator].join(':')
102
+
103
+ redis.with do |r|
104
+ is_blocked = r.exists("#{identifier}.block")
105
+ return Status.new(is_throttled: false, remaining_throttle_seconds: 0) unless is_blocked
106
+
107
+ remaining_seconds = r.get("#{identifier}.block").to_i - Time.now.to_i
108
+ Status.new(is_throttled: true, remaining_throttle_seconds: remaining_seconds)
109
+ end
110
+ end
111
+
99
112
  private
100
113
 
101
114
  def run_lua_throttler(redis:, identifier:, bucket_capacity:, leak_rate:, block_for:, n_tokens:)
@@ -110,5 +123,11 @@ module Prorate
110
123
  raise e
111
124
  end
112
125
  end
126
+
127
+ class Status < Ks.strict(:is_throttled, :remaining_throttle_seconds)
128
+ def throttled?
129
+ is_throttled
130
+ end
131
+ end
113
132
  end
114
133
  end
@@ -1,3 +1,3 @@
1
1
  module Prorate
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -30,9 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency "ks"
31
31
  spec.add_dependency "redis", ">= 2"
32
32
  spec.add_development_dependency "connection_pool", "~> 2"
33
- spec.add_development_dependency "bundler", "~> 1.12"
34
- spec.add_development_dependency "rake", "~> 12.3"
33
+ spec.add_development_dependency "bundler"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
35
  spec.add_development_dependency "rspec", "~> 3.0"
36
- spec.add_development_dependency 'wetransfer_style', '0.6.0'
36
+ spec.add_development_dependency 'wetransfer_style', '0.6.5'
37
37
  spec.add_development_dependency 'yard', '~> 0.9'
38
+ spec.add_development_dependency 'pry', '~> 0.12.2'
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prorate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-13 00:00:00.000000000 Z
11
+ date: 2020-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ks
@@ -56,30 +56,30 @@ dependencies:
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.12'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '1.12'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '12.3'
75
+ version: '13.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '12.3'
82
+ version: '13.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: 0.6.0
103
+ version: 0.6.5
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 0.6.0
110
+ version: 0.6.5
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: yard
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.12.2
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.12.2
125
139
  description: Can be used to implement all kinds of throttles
126
140
  email:
127
141
  - me@julik.nl