From f7f670b75e92e78e56269cfd051f22b3b4fd4b3b Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Sat, 21 Mar 2020 12:46:36 -0700 Subject: [PATCH] Adding a new remote_ip method to get the ip of the client with some fallbacks. Fixes #1000 --- spec/lucky/action_spec.cr | 32 ++++++++++++++++++++++++++++++++ src/lucky/action.cr | 1 + src/lucky/remote_ip_address.cr | 15 +++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/lucky/remote_ip_address.cr diff --git a/spec/lucky/action_spec.cr b/spec/lucky/action_spec.cr index fff61e7b5..60ef3eb29 100644 --- a/spec/lucky/action_spec.cr +++ b/spec/lucky/action_spec.cr @@ -143,6 +143,12 @@ class ParamsWithDefaultParamsLast < TestAction end end +class TestActionWithRemoteIp < TestAction + get "/remote-ip" do + plain_text remote_ip + end +end + describe Lucky::Action do it "has a url helper" do Lucky::RouteHelper.temp_config(base_uri: "example.com") do @@ -323,6 +329,32 @@ describe Lucky::Action do end end end + + describe "getting the remote_ip" do + it "returns the fallback local address" do + response = TestActionWithRemoteIp.new(build_context(path: "/remote-ip"), params).call + response.body.to_s.should eq "127.0.0.1" + end + + it "returns the X_FORWARDED_FOR address" do + headers = HTTP::Headers.new + headers["HTTP_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) + + response = TestActionWithRemoteIp.new(context, params).call + response.body.to_s.should eq "1.2.3.4" + end + + it "returns the original remote_address" do + request = HTTP::Request.new("GET", "/remote-ip", body: "", headers: HTTP::Headers.new) + request.remote_address = "1.2.3.4" + context = build_context(request) + + response = TestActionWithRemoteIp.new(context, params).call + response.body.to_s.should eq "1.2.3.4" + end + end end private def assert_route_added?(expected_route) diff --git a/src/lucky/action.cr b/src/lucky/action.cr index bf96e771d..8e4728eab 100644 --- a/src/lucky/action.cr +++ b/src/lucky/action.cr @@ -18,6 +18,7 @@ abstract class Lucky::Action include Lucky::ActionCallbacks include Lucky::Redirectable include Lucky::VerifyAcceptsFormat + include Lucky::RemoteIpAddress # Must be defined here instead of in Renderable # Otherwise it clashes with ErrorAction#render diff --git a/src/lucky/remote_ip_address.cr b/src/lucky/remote_ip_address.cr new file mode 100644 index 000000000..c317445b0 --- /dev/null +++ b/src/lucky/remote_ip_address.cr @@ -0,0 +1,15 @@ +module Lucky::RemoteIpAddress + # Returns a `String` IP Address of the remote client by + # looking at a few different possible options. + # + # * HTTP_X_FORWARDED_FOR header + # * REMOTE_ADDRESS header + # * Fallback to localhost 127.0.0.1 + def remote_ip : String + request = @context.request + + request.headers["HTTP_X_FORWARDED_FOR"]?.try(&.split(',').first) || + request.remote_address || + "127.0.0.1" + end +end