rate_limiter 0.0.6 → 0.1.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 +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +18 -11
- data/lib/generators/rate_limiter/install_generator.rb +22 -0
- data/lib/generators/rate_limiter/templates/rate_limiter.rb +6 -0
- data/lib/rate_limiter.rb +51 -51
- data/lib/rate_limiter/config.rb +18 -3
- data/lib/rate_limiter/frameworks/rails.rb +3 -0
- data/lib/rate_limiter/frameworks/rails/controller.rb +69 -0
- data/lib/rate_limiter/model.rb +30 -48
- data/lib/rate_limiter/model_config.rb +34 -0
- data/lib/rate_limiter/request.rb +112 -0
- data/lib/rate_limiter/throttle.rb +79 -0
- data/lib/rate_limiter/validator.rb +29 -0
- data/lib/rate_limiter/version.rb +3 -1
- data/rate_limiter.gemspec +24 -18
- metadata +98 -183
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.rvmrc +0 -80
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -150
- data/Rakefile +0 -6
- data/lib/rate_limiter/controller.rb +0 -36
- data/spec/dummy/Rakefile +0 -7
- data/spec/dummy/app/assets/images/rails.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +0 -15
- data/spec/dummy/app/assets/stylesheets/application.css +0 -13
- data/spec/dummy/app/controllers/application_controller.rb +0 -7
- data/spec/dummy/app/controllers/messages_controller.rb +0 -75
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/message.rb +0 -5
- data/spec/dummy/app/views/layouts/application.html.erb +0 -15
- data/spec/dummy/app/views/messages/_form.html.erb +0 -22
- data/spec/dummy/app/views/messages/_message.html.erb +0 -8
- data/spec/dummy/app/views/messages/edit.html.erb +0 -3
- data/spec/dummy/app/views/messages/index.html.erb +0 -6
- data/spec/dummy/app/views/messages/new.html.erb +0 -3
- data/spec/dummy/app/views/messages/show.html.erb +0 -4
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -70
- data/spec/dummy/config/boot.rb +0 -11
- data/spec/dummy/config/database.yml +0 -25
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -37
- data/spec/dummy/config/environments/production.rb +0 -67
- data/spec/dummy/config/environments/test.rb +0 -37
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/inflections.rb +0 -15
- data/spec/dummy/config/initializers/mime_types.rb +0 -5
- data/spec/dummy/config/initializers/secret_token.rb +0 -7
- data/spec/dummy/config/initializers/session_store.rb +0 -8
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/en.yml +0 -5
- data/spec/dummy/config/routes.rb +0 -5
- data/spec/dummy/db/migrate/20121213101512_create_messages.rb +0 -21
- data/spec/dummy/db/schema.rb +0 -33
- data/spec/dummy/db/seeds.rb +0 -7
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +0 -26
- data/spec/dummy/public/422.html +0 -26
- data/spec/dummy/public/500.html +0 -25
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +0 -6
- data/spec/rate_limiter/model_spec.rb +0 -20
- data/spec/rate_limiter_spec.rb +0 -10
- data/spec/spec_helper.rb +0 -30
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 59402eb858231d2bb8f7478a18c82a49a30268a5d9d7c4842f383a67d9e43ce8
|
4
|
+
data.tar.gz: ee860589d525fb08ab112d60065fc5d44aa76ef2a8f1df32c5edb0f334d0f95a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fced90a375bdc2787e8a45da94e054379556a747bd38bdca81b46d7055332e267382fd2ef52e9151d0f7e627b61f7d3404b47354c2e42885a875fab93f964a29
|
7
|
+
data.tar.gz: 3deedcb193a1472ef5bdc47da33f995d848c43616165512836ff898d57f6eb82851dd3fcf53be210cc34970528c7944c8c45d387203b155b733cd30836906ff1
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012-2019 Sean Eshbaugh
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
# Rate Limiter
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/rate_limiter)
|
4
|
+
[](https://travis-ci.org/seaneshbaugh/rate_limiter)
|
5
|
+
[](https://codeclimate.com/github/seaneshbaugh/rate_limiter/test_coverage)
|
6
|
+
[](https://codeclimate.com/github/seaneshbaugh/rate_limiter/maintainability)
|
4
7
|
|
5
|
-
|
8
|
+
Limit the rate at which ActiveRecord model instances can be created.
|
6
9
|
|
7
|
-
|
10
|
+
## Compatibility
|
11
|
+
|
12
|
+
This gem is compatible with Ruby 2.5 or greater and Rails 5 or greater. For Ruby versions older than 2.5 or for Rails 3 and 4 use [version 0.0.6](https://rubygems.org/gems/rate_limiter/versions/0.0.6).
|
8
13
|
|
9
14
|
## Installation
|
10
15
|
|
@@ -14,19 +19,19 @@ Add the gem to your project's Gemfile:
|
|
14
19
|
|
15
20
|
## Basic Usage
|
16
21
|
|
17
|
-
In the models you want to rate limit simply call the `rate_limit` method inside the model.
|
22
|
+
In the models you want to rate limit simply call the `rate_limit` class method inside the model.
|
18
23
|
|
19
24
|
```ruby
|
20
|
-
class ProductReview <
|
25
|
+
class ProductReview < ApplicationRecord
|
21
26
|
rate_limit
|
22
27
|
end
|
23
28
|
```
|
24
29
|
|
25
|
-
By default this will rate limit creation of instances using the `ip_address` attribute with an interval of one minute. This is
|
30
|
+
By default this will rate limit creation of instances using the `ip_address` attribute with an interval of one minute. This is a pretty big assumption that may change in future versions. To use a different attribute do the following:
|
26
31
|
|
27
32
|
```ruby
|
28
|
-
class ProductReview <
|
29
|
-
rate_limit :
|
33
|
+
class ProductReview < ApplicationRecord
|
34
|
+
rate_limit on: :username
|
30
35
|
end
|
31
36
|
```
|
32
37
|
|
@@ -36,10 +41,14 @@ Because you may want to increase or decrease the interval between creating insta
|
|
36
41
|
|
37
42
|
```ruby
|
38
43
|
class ProductReview < ActiveRecord::Base
|
39
|
-
rate_limit :
|
44
|
+
rate_limit interval: 3.hours
|
40
45
|
end
|
41
46
|
```
|
42
47
|
|
48
|
+
## Credit Where Credit Is Due
|
49
|
+
|
50
|
+
Large portions of this gem are copied almost verbatim from the excellent [paper_trail](https://github.com/paper-trail-gem/paper_trail) gem; in particular the overall structure and all of the stuff that handles whether or not rate limiting is active.
|
51
|
+
|
43
52
|
## Contributing
|
44
53
|
|
45
54
|
If you feel like you can add something useful to rate_limiter then don't hesitate to contribute! To make sure your fix/feature has a high chance of being included, please do the following:
|
@@ -61,5 +70,3 @@ Some things that will increase the chance that your pull request is accepted, ta
|
|
61
70
|
* Use Rails idioms and helpers
|
62
71
|
* Include tests that fail without your code, and pass with it
|
63
72
|
* Update the documentation, guides, or whatever is affected by your contribution
|
64
|
-
|
65
|
-
Yes, I am well aware of the irony of asking for tests when there are effectively none right now. This gem is a work in progress.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
module RateLimiter
|
6
|
+
module Generators
|
7
|
+
# Rails generator for installing the default RateLimiter initializer.
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
9
|
+
source_root File.expand_path('./templates', __dir__)
|
10
|
+
|
11
|
+
desc 'Creates a RateLimiter initializer and copies a default locale file to your application.'
|
12
|
+
|
13
|
+
def copy_initializer
|
14
|
+
template('rate_limiter.rb', 'config/initializers/rate_limiter.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_locale
|
18
|
+
copy_file('../../../../config/locales/en.yml', 'config/locales/rate_limiter.en.yml')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/rate_limiter.rb
CHANGED
@@ -1,63 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'active_record'
|
5
|
+
|
1
6
|
require 'rate_limiter/config'
|
2
|
-
require 'rate_limiter/controller'
|
3
7
|
require 'rate_limiter/model'
|
8
|
+
require 'rate_limiter/request'
|
4
9
|
|
10
|
+
# Extend your ActiveRecord models with the ability to limit the rate at which
|
11
|
+
# they are saved.
|
5
12
|
module RateLimiter
|
6
|
-
|
7
|
-
RateLimiter.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
rate_limiter_store[:controller_info] = value
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.controller_info
|
43
|
-
rate_limiter_store[:controller_info]
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def self.rate_limiter_store
|
49
|
-
Thread.current[:rate_limiter] ||= { :request_enabled_for_controller => true }
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.config
|
53
|
-
@@config ||= RateLimiter::Config.instance
|
13
|
+
class << self
|
14
|
+
# Return the RateLimiter singleton configuration object. This is for all
|
15
|
+
# threads.
|
16
|
+
def config
|
17
|
+
@config ||= Config.instance
|
18
|
+
yield @config if block_given?
|
19
|
+
@config
|
20
|
+
end
|
21
|
+
alias configure config
|
22
|
+
|
23
|
+
# Switches RateLimiter on or off, for all threads.
|
24
|
+
def enabled=(value)
|
25
|
+
config.enabled = value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns `true` if RateLimiter is on, `false if it is off. This is for all
|
29
|
+
# threads.
|
30
|
+
def enabled?
|
31
|
+
config.enabled
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets the options local to the current request.
|
35
|
+
#
|
36
|
+
# If given a block the options passed in are set, the block is executed,
|
37
|
+
# previous options are restored, and the return value of the block is
|
38
|
+
# returned.
|
39
|
+
def request(options = nil, &block)
|
40
|
+
if options.nil? && !block_given?
|
41
|
+
Request
|
42
|
+
else
|
43
|
+
Request.with(options, &block)
|
44
|
+
end
|
45
|
+
end
|
54
46
|
end
|
55
47
|
end
|
56
48
|
|
49
|
+
# See https://guides.rubyonrails.org/engines.html#what-are-on-load-hooks-questionmark
|
50
|
+
# for more information on `on_load` hooks.
|
57
51
|
ActiveSupport.on_load(:active_record) do
|
58
52
|
include RateLimiter::Model
|
59
53
|
end
|
60
54
|
|
61
|
-
|
62
|
-
|
55
|
+
# Load Rails controller extensions if RateLimiter is being used in a Rails
|
56
|
+
# application.
|
57
|
+
if defined?(::Rails)
|
58
|
+
if defined?(::Rails.application)
|
59
|
+
require 'rate_limiter/frameworks/rails'
|
60
|
+
else
|
61
|
+
Kernel.warn('RateLimiter has been loaded before Rails.')
|
62
|
+
end
|
63
63
|
end
|
data/lib/rate_limiter/config.rb
CHANGED
@@ -1,13 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
|
3
5
|
module RateLimiter
|
6
|
+
# Global configuration that affects all threads. Thread-specific configuration
|
7
|
+
# can be found in `/lib/rate_limiter.rb` and in
|
8
|
+
# `/lib/rate_limite/frameworks/rails/controller.rb`.
|
4
9
|
class Config
|
5
10
|
include Singleton
|
6
|
-
|
11
|
+
|
12
|
+
attr_accessor :rate_limit_defaults
|
7
13
|
|
8
14
|
def initialize
|
9
|
-
@
|
10
|
-
@
|
15
|
+
@mutex = Mutex.new
|
16
|
+
@enabled = true
|
17
|
+
@rate_limit_defaults = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def enabled
|
21
|
+
@mutex.synchronize { !!@enabled }
|
22
|
+
end
|
23
|
+
|
24
|
+
def enabled=(enable)
|
25
|
+
@mutex.synchronize { @enabled = enable }
|
11
26
|
end
|
12
27
|
end
|
13
28
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RateLimiter
|
4
|
+
module Rails
|
5
|
+
# Extensions for Rails controllers. Allows for rate limiting to be turned on
|
6
|
+
# or off without disabling it on the model.
|
7
|
+
module Controller
|
8
|
+
def self.included(base)
|
9
|
+
base.before_action(
|
10
|
+
:set_rate_limiter_enabled_for_controller,
|
11
|
+
:set_rate_limiter_source
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
# Get the user to use for the source for the current request. By default this will
|
18
|
+
# attempt to return the value of `current_user` since that is what Devise uses. If
|
19
|
+
# that assumption is incorrect this method can be overridden to return the correct
|
20
|
+
# user or ID (or nothing at all).
|
21
|
+
#
|
22
|
+
# ```
|
23
|
+
# def user_for_rate_limiter
|
24
|
+
# logged_in_user.id
|
25
|
+
# end
|
26
|
+
# ```
|
27
|
+
def user_for_rate_limiter
|
28
|
+
return nil unless respond_to?(:current_user)
|
29
|
+
|
30
|
+
current_user
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns `true` or `false` depending on whether rate imiting should be
|
34
|
+
# active for the current request for all models.
|
35
|
+
#
|
36
|
+
# Override this method in your controller to turn rate limiting on or off.
|
37
|
+
#
|
38
|
+
# ```
|
39
|
+
# def rate_limiter_enabled_for_controller
|
40
|
+
# # It is recommended that you always call `super` here unless simply
|
41
|
+
# # returning `false`.
|
42
|
+
# super && !user_for_rate_limiter.has_role?(:admin)
|
43
|
+
# end
|
44
|
+
# ```
|
45
|
+
def rate_limiter_enabled_for_controller
|
46
|
+
RateLimiter.enabled?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Tells RateLimiter whether rate limiting should be enabled for the
|
52
|
+
# current request.
|
53
|
+
def set_rate_limiter_enabled_for_controller
|
54
|
+
RateLimiter.request.enabled = rate_limiter_enabled_for_controller
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set the request store's source.
|
58
|
+
def set_rate_limiter_source
|
59
|
+
RateLimiter.request.source = user_for_rate_limiter if rate_limiter_enabled_for_controller
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if defined?(::ActionController)
|
66
|
+
::ActiveSupport.on_load(:action_controller) do
|
67
|
+
include RateLimiter::Rails::Controller
|
68
|
+
end
|
69
|
+
end
|
data/lib/rate_limiter/model.rb
CHANGED
@@ -1,66 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rate_limiter/model_config'
|
4
|
+
require 'rate_limiter/throttle'
|
5
|
+
|
1
6
|
module RateLimiter
|
7
|
+
# Mixin module for ActiveRecord models.
|
2
8
|
module Model
|
3
9
|
def self.included(base)
|
4
|
-
base.send
|
10
|
+
base.send(:extend, ClassMethods)
|
5
11
|
end
|
6
12
|
|
13
|
+
# Class methods available to models after RateLimiter has been loaded.
|
7
14
|
module ClassMethods
|
15
|
+
# Tell the model to limit creation of records based on an attribute for a
|
16
|
+
# given interval of time.
|
17
|
+
#
|
18
|
+
# Options:
|
19
|
+
#
|
20
|
+
# - :on - The attribute to limit on. Defaults to `:ip_address`. Set to an
|
21
|
+
# array to limit on multiple attributes (e.g. `:ip_address` or `:user_id`.
|
22
|
+
# - :interval - The amount of time that must have elapses since the last
|
23
|
+
# record that has the same value as the attribute indicated by the `:on`
|
24
|
+
# option in seconds. Defaults to 1 minute.
|
25
|
+
# - :if, :unless - Procs that specify the conditions for when record
|
26
|
+
# creation rate limiting should occur.
|
8
27
|
def rate_limit(options = {})
|
9
|
-
|
10
|
-
|
11
|
-
class_attribute :rate_limit_on
|
12
|
-
self.rate_limit_on = options[:on] || :ip_address
|
13
|
-
|
14
|
-
class_attribute :rate_limit_interval
|
15
|
-
self.rate_limit_interval = options[:interval] || 1.minute
|
16
|
-
|
17
|
-
class_attribute :rate_limit_if_condition
|
18
|
-
self.rate_limit_if_condition = options[:if]
|
19
|
-
|
20
|
-
class_attribute :rate_limit_unless_condition
|
21
|
-
self.rate_limit_unless_condition = options[:unless]
|
22
|
-
|
23
|
-
class_attribute :rate_limit_enabled_for_model
|
24
|
-
self.rate_limit_enabled_for_model = true
|
25
|
-
|
26
|
-
self.before_create :check_rate_limit
|
28
|
+
defaults = RateLimiter.config.rate_limit_defaults
|
29
|
+
rate_limiter.setup(defaults.merge(options))
|
27
30
|
end
|
28
31
|
|
29
|
-
def
|
30
|
-
self
|
31
|
-
end
|
32
|
-
|
33
|
-
def rate_limit_on
|
34
|
-
self.rate_limit_enabled_for_model = true
|
32
|
+
def rate_limiter
|
33
|
+
ModelConfig.new(self)
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
37
|
+
# Instance methods available to models after RateLimiter has been initialized by
|
38
|
+
# calling `rate_limit`.
|
38
39
|
module InstanceMethods
|
39
|
-
def
|
40
|
-
|
41
|
-
klass = self.class
|
42
|
-
|
43
|
-
others = klass.where("#{klass.rate_limit_on.to_s} = ? AND #{RateLimiter.config.timestamp_field.to_s} >= ?", self.send(klass.rate_limit_on), Time.now - klass.rate_limit_interval)
|
44
|
-
|
45
|
-
if others.present?
|
46
|
-
# TODO: Come up with a better error message.
|
47
|
-
self.errors.add(:base, "You cannot create a new #{klass.name.downcase} yet.")
|
48
|
-
|
49
|
-
false
|
50
|
-
else
|
51
|
-
true
|
52
|
-
end
|
53
|
-
else
|
54
|
-
true
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def switched_on?
|
59
|
-
RateLimiter.enabled? && RateLimiter.enabled_for_controller? && self.class.rate_limit_enabled_for_model
|
40
|
+
def rate_limit_exceeded?
|
41
|
+
throttle.exceeded?
|
60
42
|
end
|
61
43
|
|
62
|
-
def
|
63
|
-
|
44
|
+
def throttle
|
45
|
+
Throttle.new(self, self.class.rate_limiter_options)
|
64
46
|
end
|
65
47
|
end
|
66
48
|
end
|