Module: Securial::Security::RequestRateLimiter

Extended by:
RequestRateLimiter
Included in:
RequestRateLimiter
Defined in:
lib/securial/security/request_rate_limiter.rb

Overview

Protects authentication endpoints with configurable rate limiting.

This module provides Rack::Attack-based rate limiting for sensitive Securial endpoints, preventing brute force attacks and abuse. It limits requests based on both IP address and credential values (like email address), providing multi-dimensional protection against different attack vectors.

Protected endpoints include:

  • Login attempts (/sessions/login)

  • Password reset requests (/password/forgot)

Instance Method Summary collapse

Instance Method Details

#apply!void

This method returns an undefined value.

Applies rate limiting rules to the Rack::Attack middleware.

This method configures Rack::Attack with throttling rules for sensitive endpoints and sets up a custom JSON response format for throttled requests. It should be called during application initialization, typically in a Rails initializer.

Rate limits are defined using settings from the Securial configuration:

  • rate_limit_requests_per_minute: Maximum requests allowed per minute

  • rate_limit_response_status: HTTP status code to return (typically 429)

  • rate_limit_response_message: Message to include in throttled responses

See Also:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/securial/security/request_rate_limiter.rb', line 55

def apply! # rubocop:disable Metrics/MethodLength
  resp_status = Securial.configuration.rate_limit_response_status
  resp_message = Securial.configuration.rate_limit_response_message
  throttle_configs = [
    { name: "securial/logins/ip", path: "sessions/login", key: ->(req) { req.ip } },
    { name: "securial/logins/email", path: "sessions/login", key: ->(req) { req.params["email_address"].to_s.downcase.strip } },
    { name: "securial/password_resets/ip", path: "password/forgot", key: ->(req) { req.ip } },
    { name: "securial/password_resets/email", path: "password/forgot", key: ->(req) { req.params["email_address"].to_s.downcase.strip } },
  ]

  throttle_configs.each do |config|
    Rack::Attack.throttle(config[:name],
                          limit: ->(_req) { Securial.configuration.rate_limit_requests_per_minute },
                          period: 1.minute
    ) do |req|
      if req.path.include?(config[:path]) && req.post?
        config[:key].call(req)
      end
    end
  end

  # Custom response for throttled requests
  Rack::Attack.throttled_responder = lambda do |request|
    retry_after = (request.env["rack.attack.match_data"] || {})[:period]
    [
      resp_status,
      {
        "Content-Type" => "application/json",
        "Retry-After" => retry_after.to_s,
      },
      [{ errors: [resp_message] }.to_json],
    ]
  end
end