Class: RateAttack
- Inherits:
-
Object
- Object
- RateAttack
- Defined in:
- lib/rate_attack/request.rb,
lib/rate_attack.rb,
lib/rate_attack/cache.rb,
lib/rate_attack/railtie.rb,
lib/rate_attack/version.rb,
lib/rate_attack/configuration.rb
Overview
RateAttack::Request is the same as ::Rack::Request by default.
Defined Under Namespace
Classes: Cache, Configuration, Error, Railtie, Request
Constant Summary collapse
- VERSION =
"0.0.1"
Class Attribute Summary collapse
-
.configuration ⇒ Object
readonly
Returns the value of attribute configuration.
-
.enabled ⇒ Object
Returns the value of attribute enabled.
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Returns the value of attribute configuration.
Class Method Summary collapse
Instance Method Summary collapse
- #call(env) ⇒ Object
- #get_route_ratelimit_info(env, rateattack_config) ⇒ Object
- #guess_ip(request) ⇒ Object
- #handle_rateattack_ratelimit_info(env) ⇒ Object
- #handle_rateattack_request(env, route) ⇒ Object
-
#initialize(app) ⇒ RateAttack
constructor
A new instance of RateAttack.
- #rateattack_request_key(env, info) ⇒ Object
- #remove_prefix_postfix_slash(str) ⇒ Object
- #sanitized_path(path) ⇒ Object
- #set_rate_limit_and_merge_headers(rateattack_info, headers: {}) ⇒ Object
- #wrapped_routes ⇒ Object
Constructor Details
#initialize(app) ⇒ RateAttack
Returns a new instance of RateAttack.
27 28 29 30 |
# File 'lib/rate_attack.rb', line 27 def initialize(app) @app = app @configuration = self.class.configuration end |
Class Attribute Details
.configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
14 15 16 |
# File 'lib/rate_attack.rb', line 14 def configuration @configuration end |
.enabled ⇒ Object
Returns the value of attribute enabled.
13 14 15 |
# File 'lib/rate_attack.rb', line 13 def enabled @enabled end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
25 26 27 |
# File 'lib/rate_attack.rb', line 25 def configuration @configuration end |
Class Method Details
Instance Method Details
#call(env) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/rate_attack.rb', line 32 def call(env) return @app.call(env) if !self.class.enabled || env["rate_attack.called"] env["rate_attack.called"] = true # request = RateAttack::Request.new(env) s_path = sanitized_path(env['PATH_INFO']) if s_path =~ /rateattack/ handle_rateattack_ratelimit_info(env) else r_path = Regexp.new(s_path) route = wrapped_routes.filter { sanitized_path(_1.path) =~ r_path }&.first route ? handle_rateattack_request(env, route) : @app.call(env) end end |
#get_route_ratelimit_info(env, rateattack_config) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/rate_attack.rb', line 62 def get_route_ratelimit_info(env, rateattack_config) count = Rails .cache .read(rateattack_request_key(env, rateattack_config), raw: true) .to_i { limit: rateattack_config[:limit], count: count, remaining: (rateattack_config[:limit] - count), action: (rateattack_config[:action] || sanitized_path(env['PATH_INFO'])), } end |
#guess_ip(request) ⇒ Object
140 141 142 143 144 145 146 147 148 |
# File 'lib/rate_attack.rb', line 140 def guess_ip(request) ip = request.remote_ip || request.ip ip = '127.0.0.1' if ip == '::1' begin IPAddress.valid?(ip) rescue StandardError nil end end |
#handle_rateattack_ratelimit_info(env) ⇒ Object
80 81 82 83 84 85 86 87 |
# File 'lib/rate_attack.rb', line 80 def handle_rateattack_ratelimit_info(env) ra_ratelimit_infos = wrapped_routes.map do get_route_ratelimit_info(env, _1.defaults[:rateattack]) end [200, {}, StringIO.new({ data: ra_ratelimit_infos }.to_json)] end |
#handle_rateattack_request(env, route) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/rate_attack.rb', line 89 def handle_rateattack_request(env, route) limit_key = rateattack_request_key(env, route.defaults[:rateattack]) current_info = get_route_ratelimit_info(env, route.defaults[:rateattack]) if (current_info[:limit] - current_info[:count]) <= 0 return [ 429, set_rate_limit_and_merge_headers(current_info), StringIO.new(I18n.t('general.ratelimit.exceeded')) ] end @status, @headers, @response = @app.call(env) if @status >= 200 && @status <= 400 current_info[:count] = Rails.cache.increment( limit_key, expires_in: route.defaults[:rateattack][:duration], ) end [ @status, set_rate_limit_and_merge_headers(current_info, headers: @headers), @response, ] end |
#rateattack_request_key(env, info) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/rate_attack.rb', line 48 def rateattack_request_key(env, info) request = ActionDispatch::Request.new env keys = [] if info[:uniqueness].is_a?(Array) keys = info[:uniqueness].map do |item| item.to_s == 'ip' ? guess_ip(request) : env[item.to_s] end end keys.insert(0, 'RateAttack') keys.push(info[:action] || sanitized_path(env['PATH_INFO'])) keys.join('::') end |
#remove_prefix_postfix_slash(str) ⇒ Object
123 124 125 |
# File 'lib/rate_attack.rb', line 123 def remove_prefix_postfix_slash(str) str.gsub(%r{^\/}, '').gsub(%r{\/$}, '') end |
#sanitized_path(path) ⇒ Object
76 77 78 |
# File 'lib/rate_attack.rb', line 76 def sanitized_path(path) path.gsub(%r{^\/}, '').gsub(/\(.:format\)/, '') end |
#set_rate_limit_and_merge_headers(rateattack_info, headers: {}) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/rate_attack.rb', line 127 def set_rate_limit_and_merge_headers(rateattack_info, headers: {}) headers.merge( { I18n.t('general.ratelimit.headers.remaining') => [ rateattack_info[:limit] - rateattack_info[:count], 0, ].max.to_s, I18n.t('general.ratelimit.headers.limit') => rateattack_info[:limit].to_s, }, ) end |
#wrapped_routes ⇒ Object
118 119 120 121 |
# File 'lib/rate_attack.rb', line 118 def wrapped_routes Rails.application.routes.routes.filter { _1.defaults[:rateattack] }.compact .map { ActionDispatch::Routing::RouteWrapper.new(_1) } end |