better_rate_limit 0.1.2 → 0.1.6

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
  SHA256:
3
- metadata.gz: b8202a0675862d02c311bc2c05f294d5adf1a1add2e5cf65aa401edcb0c159d5
4
- data.tar.gz: b94eb54339bdd7347dbc83f4ac95f87f413671227c89e072bbc0d56bc5a5fb65
3
+ metadata.gz: fd081be677a01d7f69d7e6e6ada8a3172d84d6ee74fbf7ccab5d5fbecb89ee58
4
+ data.tar.gz: 2817642bfee1165564273b8b36ae434e02e7135c6e63a887a4c178c4558d3752
5
5
  SHA512:
6
- metadata.gz: '07802df810af301f0d5d1fd8f1bd4f924b535e1dddc98575faa20ec20093d88777f27dc9f99cd6d17d7dd9163ea5760300af5b266ae5a5e120cb03c31c0ee2ea'
7
- data.tar.gz: 2659222d45eb6a1d2a6559ef78c8a64fefd7e0c66a06d38b96810782063059037d6f8da8a6453b55aa8adc9c7d6e70b7c98797405c9b4258b908ed339c788d89
6
+ metadata.gz: bdd3fda987a39c8027d47ddec7b6112e089a609aaabd3a924e71a76417619b69284416183d68744ea05fa55b47e1cead0d04cb99ddb33937e6db97f578d784db
7
+ data.tar.gz: 7b03b579af9be74f8669af5531d254580b0f384232dc5cb295f6d4cbcea7ca3525d42ee41105f0c08dd121c6a68d91544e06dd38a7044bd0b4062cc6007077c5
data/.gitignore CHANGED
@@ -1,61 +1,3 @@
1
- <<<<<<< HEAD
2
- *.gem
3
- *.rbc
4
- /.config
5
- /coverage/
6
- /InstalledFiles
7
- /pkg/
8
- /spec/reports/
9
- /spec/examples.txt
10
- /test/tmp/
11
- /test/version_tmp/
12
- /tmp/
13
-
14
- # Used by dotenv library to load environment variables.
15
- # .env
16
-
17
- # Ignore Byebug command history file.
18
- .byebug_history
19
-
20
- ## Specific to RubyMotion:
21
- .dat*
22
- .repl_history
23
- build/
24
- *.bridgesupport
25
- build-iPhoneOS/
26
- build-iPhoneSimulator/
27
-
28
- ## Specific to RubyMotion (use of CocoaPods):
29
- #
30
- # We recommend against adding the Pods directory to your .gitignore. However
31
- # you should judge for yourself, the pros and cons are mentioned at:
32
- # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
33
- #
34
- # vendor/Pods/
35
-
36
- ## Documentation cache and generated files:
37
- /.yardoc/
38
- /_yardoc/
39
- /doc/
40
- /rdoc/
41
-
42
- ## Environment normalization:
43
- /.bundle/
44
- /vendor/bundle
45
- /lib/bundler/man/
46
-
47
- # for a library or gem, you might want to ignore these files since the code is
48
- # intended to run in multiple environments; otherwise, check them in:
49
- # Gemfile.lock
50
- # .ruby-version
51
- # .ruby-gemset
52
-
53
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
54
- .rvmrc
55
-
56
- # Used by RuboCop. Remote config files pulled in from inherit_from directive.
57
- # .rubocop-https?--*
58
- =======
59
1
  /.bundle/
60
2
  /.yardoc
61
3
  /_yardoc/
@@ -64,7 +6,5 @@ build-iPhoneSimulator/
64
6
  /pkg/
65
7
  /spec/reports/
66
8
  /tmp/
67
-
68
- # rspec failure tracking
69
- .rspec_status
70
- >>>>>>> init gem
9
+ *.gem
10
+ .byebug_history
data/.travis.yml CHANGED
@@ -2,5 +2,5 @@
2
2
  language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.6.5
5
+ - 2.6.6
6
6
  before_install: gem install bundler -v 2.1.4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- better_rate_limit (0.1.2)
4
+ better_rate_limit (0.1.5)
5
5
  actionpack (>= 5.0)
6
6
  redis (>= 3.3)
7
7
 
@@ -59,7 +59,7 @@ GEM
59
59
  rails-html-sanitizer (1.3.0)
60
60
  loofah (~> 2.3)
61
61
  rake (12.3.3)
62
- redis (4.2.1)
62
+ redis (4.2.5)
63
63
  thread_safe (0.3.6)
64
64
  timecop (0.9.1)
65
65
  tzinfo (1.2.7)
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # BetterRateLimit
2
+ [![Build Status](https://travis-ci.org/upscopeio/better_rate_limit.svg?branch=master)](https://travis-ci.org/upscopeio/better_rate_limit)
3
+ [![Gem Version](https://badge.fury.io/rb/better_rate_limit.svg)](https://badge.fury.io/rb/better_rate_limit)
2
4
 
3
5
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/better_rate_limit`. To experiment with that code, run `bin/console` for an interactive prompt.
4
6
 
@@ -7,7 +9,7 @@ Welcome to your new gem! In this directory, you'll find the files you need to be
7
9
  Add this line to your application's Gemfile:
8
10
 
9
11
  ```ruby
10
- gem 'better_rate_limit', git: 'https://github.com/upscopeio/better_rate_limit'
12
+ gem 'better_rate_limit'
11
13
  ```
12
14
 
13
15
  And then execute:
@@ -39,6 +41,15 @@ ActiveSupport::Notifications.subscribe /rate_limit/ do |_name, _start, _finish,
39
41
  end
40
42
  ```
41
43
 
44
+ ### Configuration
45
+ You can ignore the throttle by using the following configuration
46
+ ```ruby
47
+ BetterRateLimit.configure do |config|
48
+ config.ignore = true
49
+ end
50
+ ```
51
+ Useful to ignore the throttle on tests.
52
+
42
53
  ## Development
43
54
 
44
55
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,23 +1,26 @@
1
1
  require 'ostruct'
2
2
  require 'better_rate_limit/throttle'
3
+ require 'better_rate_limit/limit'
3
4
 
4
5
  module ActionController
5
6
  module BetterRateLimit
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  module ClassMethods
9
- def rate_limit(max, every:, name: nil, scope: -> { real_ip }, only: [], except: [])
10
- rate_limits << OpenStruct.new({
11
- max: max,
12
- every: every,
13
- name: name || controller_path,
14
- scope: scope,
15
- only: only,
16
- except: except,
17
- controller_path: controller_path
10
+ def rate_limit(max, options)
11
+ rate_limits << Limit.build(max, controller_path, {
12
+ if: options[:if],
13
+ unless: options[:unless],
14
+ every: options[:every],
15
+ name: options[:name] || controller_path,
16
+ scope: options[:scope] || -> { real_ip },
17
+ only: options[:only] || [],
18
+ except: options[:except] || [],
19
+ clear_if: options[:clear_if]
18
20
  })
19
21
 
20
22
  before_action :perform_rate_limiting
23
+ after_action :clear_keys
21
24
  end
22
25
 
23
26
  def rate_limits
@@ -41,6 +44,20 @@ module ActionController
41
44
  render file: 'public/429.html', status: :too_many_requests, layout: false
42
45
  end
43
46
 
47
+ def clear_keys
48
+ rate_limits = self.class.all_rate_limits.filter(&:clear_if_present?)
49
+ return if rate_limits.empty?
50
+
51
+ rate_limits.each do |rate_limit|
52
+ should_clear = rate_limit.clear_if.is_a?(Proc) ? instance_exec(&rate_limit.clear_if) : send(rate_limit.clear_if)
53
+ next unless should_clear
54
+
55
+ scope = rate_limit.scope.is_a?(Proc) ? instance_exec(&rate_limit.scope) : send(rate_limit.scope)
56
+ scope = scope.to_param if scope.respond_to?(:to_param)
57
+ ::BetterRateLimit::Throttle.clear(rate_limit.key(scope))
58
+ end
59
+ end
60
+
44
61
  private
45
62
 
46
63
  def json?
@@ -52,7 +69,17 @@ module ActionController
52
69
  end
53
70
 
54
71
  def under_rate_limit?(limit)
55
- if limit.controller_path == controller_path
72
+ if limit.has_if_condition?
73
+ if_condition = limit._if.is_a?(Proc) ? instance_exec(&limit._if) : send(limit._if)
74
+ return true unless if_condition
75
+ end
76
+
77
+ if limit.has_unless_condition?
78
+ unless_condition = limit._unless.is_a?(Proc) ? instance_exec(&limit._unless) : send(limit._unless)
79
+ return true if unless_condition
80
+ end
81
+
82
+ if limit.controller_path_is?(controller_path)
56
83
  return true if action_name.to_sym.in?([limit.except].flatten)
57
84
  return true if !limit.only.empty? && !action_name.to_sym.in?([limit.only].flatten)
58
85
  end
@@ -60,9 +87,7 @@ module ActionController
60
87
  scope = limit.scope.is_a?(Proc) ? instance_exec(&limit.scope) : send(limit.scope)
61
88
  scope = scope.to_param if scope.respond_to?(:to_param)
62
89
 
63
- key = ['controller_throttle', limit.name, limit.max, limit.every, scope].join(':')
64
-
65
- ::BetterRateLimit::Throttle.allow? key, limit: limit.max, time_window: limit.every
90
+ ::BetterRateLimit::Throttle.allow? limit.key(scope), limit: limit.max, time_window: limit.every
66
91
  end
67
92
  end
68
93
  end
@@ -1,4 +1,21 @@
1
1
  require 'action_controller'
2
+ require 'better_rate_limit/configuration'
3
+
4
+ module BetterRateLimit
5
+ class << self
6
+ def configuration
7
+ @configuration ||= Configuration.new
8
+ end
9
+
10
+ def configure
11
+ yield configuration
12
+ end
13
+
14
+ def reset_configuration
15
+ @configuration = Configuration.new
16
+ end
17
+ end
18
+ end
2
19
 
3
20
  module ActionController
4
21
  autoload :BetterRateLimit, 'action_controller/better_rate_limit'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterRateLimit
4
+ class Configuration
5
+ attr_accessor :ignore
6
+
7
+ def initialize
8
+ @ignore = false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,75 @@
1
+ class Limit
2
+ def self.build(max, controller_path, options)
3
+ options.assert_valid_keys(:if, :unless, :every, :name, :scope, :only, :except, :clear_if)
4
+ new(max, controller_path, {
5
+ if: options[:if],
6
+ unless: options[:unless],
7
+ every: options[:every],
8
+ name: options[:name],
9
+ scope: options[:scope],
10
+ only: options[:only],
11
+ except: options[:except],
12
+ clear_if: options[:clear_if]
13
+ })
14
+ end
15
+
16
+ attr_reader :max, :controller_path
17
+
18
+ def initialize(max, controller_path, options)
19
+ @max = max
20
+ @controller_path = controller_path
21
+ @options = options
22
+ end
23
+
24
+ def _if
25
+ @options[:if]
26
+ end
27
+
28
+ def name
29
+ @options[:name]
30
+ end
31
+
32
+ def every
33
+ @options[:every]
34
+ end
35
+
36
+ def _unless
37
+ @options[:unless]
38
+ end
39
+
40
+ def except
41
+ @options[:except]
42
+ end
43
+
44
+ def only
45
+ @options[:only]
46
+ end
47
+
48
+ def scope
49
+ @options[:scope]
50
+ end
51
+
52
+ def clear_if
53
+ @options[:clear_if]
54
+ end
55
+
56
+ def clear_if_present?
57
+ @options[:clear_if].present?
58
+ end
59
+
60
+ def has_if_condition?
61
+ _if.present?
62
+ end
63
+
64
+ def has_unless_condition?
65
+ _unless.present?
66
+ end
67
+
68
+ def controller_path_is?(controller_path)
69
+ self.controller_path == controller_path
70
+ end
71
+
72
+ def key(key_scope)
73
+ ['controller_throttle', name, max, every, key_scope].join(':')
74
+ end
75
+ end
@@ -10,6 +10,8 @@ module BetterRateLimit
10
10
 
11
11
  class << self
12
12
  def throttle(key, limit:, time_window:)
13
+ return true if BetterRateLimit.configuration.ignore
14
+
13
15
  now = Time.now.utc
14
16
  timestamps_count = redis_client.llen key
15
17
 
@@ -21,11 +23,14 @@ module BetterRateLimit
21
23
  true
22
24
  else
23
25
  first = redis_client.lpop(key)
26
+
24
27
  redis_client.multi do
25
28
  redis_client.rpush key, now
26
29
  redis_client.expire key, time_window.to_i
27
30
  end
28
31
 
32
+ return false unless first
33
+
29
34
  passing = first.to_time(:utc) < time_window.ago
30
35
 
31
36
  unless passing
@@ -37,6 +42,11 @@ module BetterRateLimit
37
42
  end
38
43
  end
39
44
 
45
+ def clear(key)
46
+ redis_client.del(key)
47
+ redis_client.del("failing-rate-limits:#{key}")
48
+ end
49
+
40
50
  alias allow? throttle
41
51
 
42
52
  def notify(key)
@@ -1,3 +1,3 @@
1
1
  module BetterRateLimit
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_rate_limit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Fonseca
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-08-10 00:00:00.000000000 Z
12
+ date: 2021-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -58,6 +58,8 @@ files:
58
58
  - bin/setup
59
59
  - lib/action_controller/better_rate_limit.rb
60
60
  - lib/better_rate_limit.rb
61
+ - lib/better_rate_limit/configuration.rb
62
+ - lib/better_rate_limit/limit.rb
61
63
  - lib/better_rate_limit/redis_connection.rb
62
64
  - lib/better_rate_limit/throttle.rb
63
65
  - lib/better_rate_limit/version.rb