Rate limit by user instead of IP when API user is authenticated (#5923)
* Fix #668 - Rate limit by user instead of IP when API user is authenticated * Fix code style issue * Use request decorator provided by Doorkeeper
This commit is contained in:
		| @@ -44,7 +44,8 @@ module RateLimitHeaders | ||||
|   end | ||||
|  | ||||
|   def api_throttle_data | ||||
|     request.env['rack.attack.throttle_data']['api'] | ||||
|     request.env['rack.attack.throttle_data']['throttle_authenticated_api'] || | ||||
|       request.env['rack.attack.throttle_data']['throttle_unauthenticated_api'] | ||||
|   end | ||||
|  | ||||
|   def request_time | ||||
|   | ||||
| @@ -1,6 +1,41 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Rack::Attack | ||||
|   class Request | ||||
|     def authenticated_token | ||||
|       return @token if defined?(@token) | ||||
|  | ||||
|       @token = Doorkeeper::OAuth::Token.authenticate( | ||||
|         Doorkeeper::Grape::AuthorizationDecorator.new(self), | ||||
|         *Doorkeeper.configuration.access_token_methods | ||||
|       ) | ||||
|     end | ||||
|  | ||||
|     def authenticated_user_id | ||||
|       authenticated_token&.resource_owner_id | ||||
|     end | ||||
|  | ||||
|     def unauthenticated? | ||||
|       !authenticated_user_id | ||||
|     end | ||||
|  | ||||
|     def api_request? | ||||
|       path.start_with?('/api') | ||||
|     end | ||||
|  | ||||
|     def web_request? | ||||
|       !api_request? | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   PROTECTED_PATHS = %w( | ||||
|     /auth/sign_in | ||||
|     /auth | ||||
|     /auth/password | ||||
|   ).freeze | ||||
|  | ||||
|   PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ }) | ||||
|  | ||||
|   # Always allow requests from localhost | ||||
|   # (blocklist & throttles are skipped) | ||||
|   Rack::Attack.safelist('allow from localhost') do |req| | ||||
| @@ -8,24 +43,16 @@ class Rack::Attack | ||||
|     '127.0.0.1' == req.ip || '::1' == req.ip | ||||
|   end | ||||
|  | ||||
|   # Rate limits for the API | ||||
|   throttle('api', limit: 300, period: 5.minutes) do |req| | ||||
|     req.ip if req.path =~ /\A\/api\/v/ | ||||
|   throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req| | ||||
|     req.api_request? && req.authenticated_user_id | ||||
|   end | ||||
|  | ||||
|   # Rate limit logins | ||||
|   throttle('login', limit: 5, period: 5.minutes) do |req| | ||||
|     req.ip if req.path == '/auth/sign_in' && req.post? | ||||
|   throttle('throttle_unauthenticated_api', limit: 300, period: 5.minutes) do |req| | ||||
|     req.ip if req.api_request? && req.unauthenticated? | ||||
|   end | ||||
|  | ||||
|   # Rate limit sign-ups | ||||
|   throttle('register', limit: 5, period: 5.minutes) do |req| | ||||
|     req.ip if req.path == '/auth' && req.post? | ||||
|   end | ||||
|  | ||||
|   # Rate limit forgotten passwords | ||||
|   throttle('reminder', limit: 5, period: 5.minutes) do |req| | ||||
|     req.ip if req.path == '/auth/password' && req.post? | ||||
|   throttle('protected_paths', limit: 5, period: 5.minutes) do |req| | ||||
|     req.ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX | ||||
|   end | ||||
|  | ||||
|   self.throttled_response = lambda do |env| | ||||
|   | ||||
| @@ -34,7 +34,7 @@ describe ApplicationController do | ||||
|       let(:start_time) { DateTime.new(2017, 1, 1, 12, 0, 0).utc } | ||||
|  | ||||
|       before do | ||||
|         request.env['rack.attack.throttle_data'] = { 'api' => { limit: 100, count: 20, period: 10 } } | ||||
|         request.env['rack.attack.throttle_data'] = { 'throttle_authenticated_api' => { limit: 100, count: 20, period: 10 } } | ||||
|         travel_to start_time do | ||||
|           get 'show' | ||||
|         end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user