action_ip_filter 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d061bab952784ba92d038b0a28d34418e9c360d6d6abd543621535170a9a21c
4
- data.tar.gz: 8a57d44aa950fb7c9bc0dad8a3493add8c626df62cecea9d0a8536d5ae7bc6d6
3
+ metadata.gz: a00718ef1f7bccb16f52d1b8a1e5f9e746e9ba15f933795be419ff3c0d303c3d
4
+ data.tar.gz: 5c0f2e216e5481ec40f6156dd512d6a5ea5af39de88d64e7c06504faa5966fd7
5
5
  SHA512:
6
- metadata.gz: 7f868950a961ac26cfebace46005f363f4cace350a63d0c58996b20966312a245794b8fe571b0abec32c216baae81e41fad39ea0eb393997348043e650263c3e
7
- data.tar.gz: 746dc0312e87366a4d6d4e36e0bd31648255d646fb6f7d103f6e00bb4d68d1863ef4c2154737db74f3655934da81a73737b2b6a920d3338a0e8c9b69faf09afd
6
+ metadata.gz: ed618b5060effa6e112c7cfce1616f5f43467728971503779caff3230ec1b3506c18585766d41df45088e45c74e1b3756522341e5ca873b96d0f73284e15ee4b
7
+ data.tar.gz: 355e3c64444d092f466166c11c2d9afad5dfedab274e4a80f56e47d6d724a64b63dc21774e781496889d07e435c675d1b528bd017e9c79ef7f568fb59e9a49e0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2025-12-01
4
+
5
+ - Simplify IpMatcher#match? by removing unnecessary branching [#3](https://github.com/smartbank-inc/action_ip_filter/pull/3)
6
+
7
+ ## [0.3.0] - 2025-11-29
8
+
9
+ ### Breaking Changes
10
+
11
+ #### Change `ip_resolver` to use controller context instead of request parameter [#1](https://github.com/smartbank-inc/action_ip_filter/pull/1)
12
+
13
+ Usage has changed. The `request` parameter in the Proc was previously required, but it is no longer needed. You can now access controller methods (`request`, `params`, etc.) directly instead of receiving `request` as an argument.
14
+
15
+ Before:
16
+
17
+ ```
18
+ config.ip_resolver = ->(request) {
19
+ request.headers["X-Forwarded-For"]&.split(",")&.first&.strip || request.remote_ip
20
+ }
21
+ ```
22
+
23
+ After:
24
+
25
+ ```
26
+ config.ip_resolver = -> {
27
+ request.headers["X-Forwarded-For"]&.split(",")&.first&.strip || request.remote_ip
28
+ }
29
+ ```
30
+
3
31
  ## [0.2.0] - 2025-11-28
4
32
 
5
33
  ### Breaking Changes
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # action_ip_filter
1
+ # action_ip_filter [![CI](https://github.com/smartbank-inc/action_ip_filter/actions/workflows/ci.yml/badge.svg)](https://github.com/smartbank-inc/action_ip_filter/actions/workflows/ci.yml) [![Gem Version](https://img.shields.io/gem/v/action_ip_filter)](https://rubygems.org/gems/action_ip_filter)
2
2
 
3
3
  A lightweight gem that provides IP address restrictions for Rails controllers at the action level.
4
4
 
@@ -18,6 +18,24 @@ Unlike Rack middleware solutions (e.g., `rack-attack`), action_ip_filter operate
18
18
  - Minimal overhead (no processing for unrestricted endpoints)
19
19
  - Simple, declarative configuration per controller
20
20
 
21
+ ### Why not Rails native `rate_limit`?
22
+
23
+ Rails 8.0 introduced `rate_limit`, which could be adapted for IP filtering:
24
+
25
+ ```ruby
26
+ # a slightly tricky approach involving rate_limit
27
+ rate_limit to: 0, within: 0.second, only: :create, unless: -> {
28
+ allowed_ips.include?(request.remote_ip)
29
+ }
30
+ ```
31
+
32
+ However, this approach has drawbacks:
33
+ - Semantic mismatch: `rate_limit` is designed for rate limiting, not access control
34
+ - Requires cache store: `rate_limit` needs a cache backend for counting, unnecessary for simple IP allowlists
35
+ - Inverted logic: You're "rate limiting everyone except allowed IPs" rather than "allowing specific IPs"
36
+
37
+ action_ip_filter provides a purpose-built, declarative API for IP-based access control.
38
+
21
39
  ## Installation
22
40
 
23
41
  Add to your Gemfile:
@@ -114,7 +132,7 @@ Configure global settings in an initializer:
114
132
  # config/initializers/action_ip_filter.rb
115
133
  ActionIpFilter.configure do |config|
116
134
  # Custom IP resolver (default: request.remote_ip)
117
- config.ip_resolver = ->(request) {
135
+ config.ip_resolver = -> {
118
136
  request.headers["X-Forwarded-For"]&.split(",")&.first&.strip || request.remote_ip
119
137
  }
120
138
 
@@ -133,7 +151,7 @@ end
133
151
 
134
152
  | Option | Default | Description |
135
153
  |--------|-------------------------------------|----------------------------------------------------|
136
- | `ip_resolver` | `->(request) { request.remote_ip }` | Proc that extracts client IP from request |
154
+ | `ip_resolver` | `-> { request.remote_ip }` | Proc that extracts client IP from request |
137
155
  | `on_denied` | `-> { head :forbidden }` | Handler called when access is denied (returns 403) |
138
156
  | `logger` | `Rails.logger` | Logger instance for denied request logging |
139
157
  | `log_denials` | `true` | Whether to log denied requests as warn level |
@@ -2,19 +2,19 @@
2
2
 
3
3
  module ActionIpFilter
4
4
  class Configuration
5
- # @rbs @ip_resolver: ^(ActionDispatch::Request) -> String?
5
+ # @rbs @ip_resolver: ^() -> String?
6
6
  # @rbs @on_denied: ^() -> void
7
7
  # @rbs @logger: Logger?
8
8
  # @rbs @log_denials: bool
9
9
 
10
- attr_accessor :ip_resolver #: ^(ActionDispatch::Request) -> String?
10
+ attr_accessor :ip_resolver #: ^() -> String?
11
11
  attr_accessor :on_denied #: ^() -> void
12
12
  attr_accessor :logger #: Logger?
13
13
  attr_accessor :log_denials #: bool
14
14
 
15
15
  # @rbs return: void
16
16
  def initialize
17
- @ip_resolver = ->(request) { request.remote_ip }
17
+ @ip_resolver = -> { request.remote_ip } # steep:ignore NoMethod
18
18
  @on_denied = -> { head :forbidden } # steep:ignore NoMethod
19
19
  @logger = nil
20
20
  @log_denials = true
@@ -62,7 +62,7 @@ module ActionIpFilter
62
62
  def verify_ip_access(restriction)
63
63
  return if restriction.nil? || ActionIpFilter.test_mode?
64
64
 
65
- client_ip = ActionIpFilter.configuration.ip_resolver.call(request)
65
+ client_ip = instance_exec(&ActionIpFilter.configuration.ip_resolver) #: String?
66
66
  allowed_ips = resolve_allowed_ips(restriction[:allowed_ips])
67
67
 
68
68
  unless IpMatcher.allowed?(client_ip, allowed_ips)
@@ -33,13 +33,8 @@ module ActionIpFilter
33
33
  # @rbs allowed_ip: String
34
34
  # @rbs return: bool
35
35
  def match?(client_addr, allowed_ip)
36
- if allowed_ip.include?("/")
37
- range = IPAddr.new(allowed_ip)
38
- range.include?(client_addr)
39
- else
40
- allowed_addr = IPAddr.new(allowed_ip)
41
- client_addr == allowed_addr
42
- end
36
+ allowed_addr = IPAddr.new(allowed_ip)
37
+ allowed_addr.include?(client_addr)
43
38
  rescue IPAddr::InvalidAddressError
44
39
  false
45
40
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionIpFilter
4
- VERSION = "0.2.0" #: String
4
+ VERSION = "0.3.1" #: String
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_ip_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartBank, Inc.