Add Cloudflare header support for real client IP

- Updated IP geolocation to check CF-Connecting-IP header first
- Added support for X-Real-IP header as additional fallback
- Updated rate limiter to use same header priority for consistency
- Headers checked in order: CF-Connecting-IP > X-Forwarded-For > X-Real-IP > remote_ip

This ensures proper client IP detection when the site is behind Cloudflare.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-24 20:29:48 -05:00
parent 31bd5f5749
commit fb34553fe9
No known key found for this signature in database
2 changed files with 36 additions and 7 deletions

View file

@ -68,12 +68,22 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
end
defp get_client_ip(conn) do
# Check for forwarded IP first (when behind proxy/load balancer)
# Check for Cloudflare header first (CF-Connecting-IP)
cf_ip = get_req_header(conn, "cf-connecting-ip")
# Then check for standard forwarded headers
forwarded_for = get_req_header(conn, "x-forwarded-for")
real_ip = get_req_header(conn, "x-real-ip")
ip =
case forwarded_for do
[forwarded | _] ->
case {cf_ip, forwarded_for, real_ip} do
{[cf | _], _, _} ->
# Cloudflare header takes precedence
ip_from_cf = String.trim(cf)
Logger.info("IP geolocation: Using Cloudflare CF-Connecting-IP: #{ip_from_cf}")
ip_from_cf
{[], [forwarded | _], _} ->
# Take the first IP from the X-Forwarded-For header
ip_from_header =
forwarded
@ -84,7 +94,13 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
Logger.info("IP geolocation: Using forwarded IP from X-Forwarded-For header: #{ip_from_header}")
ip_from_header
[] ->
{[], [], [real | _]} ->
# Use X-Real-IP header
ip_from_real = String.trim(real)
Logger.info("IP geolocation: Using X-Real-IP header: #{ip_from_real}")
ip_from_real
{[], [], []} ->
# Fall back to remote_ip
ip_from_remote =
case conn.remote_ip do

View file

@ -40,9 +40,22 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
end
defp get_key(conn, :ip) do
case get_req_header(conn, "x-forwarded-for") do
[ip | _] -> ip
[] -> conn.remote_ip |> :inet.ntoa() |> to_string()
# Check headers in order of preference
cond do
# Cloudflare header takes precedence
cf_ip = get_req_header(conn, "cf-connecting-ip") != [] ->
hd(cf_ip)
# Then standard forwarded headers
forwarded = get_req_header(conn, "x-forwarded-for") != [] ->
forwarded |> hd() |> String.split(",") |> List.first() |> String.trim()
real_ip = get_req_header(conn, "x-real-ip") != [] ->
hd(real_ip)
# Fall back to remote_ip
true ->
conn.remote_ip |> :inet.ntoa() |> to_string()
end
end