Skip to content

Commit

Permalink
Adding a new remote_ip method (#1059)
Browse files Browse the repository at this point in the history
* Adding a new remote_ip method to get the ip of the client with some fallbacks. Fixes #1000

* moving the logic for the remote_address from Lucky::Action in to a handler so the value is accessible to anything that has access to the context

* using the X-Forwarded-For instead of the HTTP_X_FORWARDED_FOR. It seems the HTTP_ was residule from the old CGI days and should no longer be used.
  • Loading branch information
jwoertink authored Mar 26, 2020
1 parent e27937d commit 4ce801b
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
49 changes: 49 additions & 0 deletions spec/lucky/remote_ip_handler_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "../spec_helper"

include ContextHelper

describe Lucky::RemoteIpHandler do
describe "getting the remote_address" do
it "returns nil when no remote IP is found" do
context = build_context(path: "/path")

run_remote_ip_handler(context)
context.request.remote_address.should eq nil
end

it "returns the X_FORWARDED_FOR address" do
headers = HTTP::Headers.new
headers["X_FORWARDED_FOR"] = "1.2.3.4,127.0.0.1"
request = HTTP::Request.new("GET", "/remote-ip", body: "", headers: headers)
context = build_context(request)

run_remote_ip_handler(context)
context.request.remote_address.should eq "1.2.3.4"
end

it "returns nil if the X_FORWARDED_FOR is an empty string, and no default remote_address is found" do
headers = HTTP::Headers.new
headers["X_FORWARDED_FOR"] = ""
request = HTTP::Request.new("GET", "/remote-ip", body: "", headers: headers)
context = build_context(request)

run_remote_ip_handler(context)
context.request.remote_address.should eq nil
end

it "returns the original remote_address" do
request = HTTP::Request.new("GET", "/remote-ip", body: "", headers: HTTP::Headers.new)
request.remote_address = "255.255.255.255"
context = build_context(request)

run_remote_ip_handler(context)
context.request.remote_address.should eq "255.255.255.255"
end
end
end

private def run_remote_ip_handler(context)
handler = Lucky::RemoteIpHandler.new
handler.next = ->(_ctx : HTTP::Server::Context) {}
handler.call(context)
end
24 changes: 24 additions & 0 deletions src/lucky/remote_ip_handler.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Sets the HTTP::Request#remote_address value
# to the value of the first IP in the `X-Forwarded-For`
# header, or fallback to the default `remote_address`.
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
#
# This Handler does a best guess for the IP which is generally good
# enough. If you require IP based Authentication, then you may want
# to handle this on your own as there will be edge cases when related
# to mobile clients on the go, and potential IP spoofing attacks.
class Lucky::RemoteIpHandler
include HTTP::Handler

def call(context)
context.request.remote_address = fetch_remote_ip(context)
call_next(context)
end

private def fetch_remote_ip(context : HTTP::Server::Context) : String?
request = context.request

x_forwarded = request.headers["X_FORWARDED_FOR"]?.try(&.split(',').first?)
x_forwarded.blank? ? request.remote_address : x_forwarded
end
end

0 comments on commit 4ce801b

Please sign in to comment.