fear(client): extract client's IP and store in database and structured logs
This commit is contained in:
143
backend/internal/httpapi/client_ip_test.go
Normal file
143
backend/internal/httpapi/client_ip_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractClientIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
request *http.Request
|
||||
headers map[string]string
|
||||
remoteAddr string
|
||||
wantIP *string
|
||||
}{
|
||||
{
|
||||
name: "nil request",
|
||||
request: nil,
|
||||
wantIP: nil,
|
||||
},
|
||||
{
|
||||
name: "CF-Connecting-IP takes precedence",
|
||||
headers: map[string]string{
|
||||
"CF-Connecting-IP": "203.0.113.10",
|
||||
"X-Real-IP": "198.51.100.20",
|
||||
"X-Forwarded-For": "192.0.2.30",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("203.0.113.10"),
|
||||
},
|
||||
{
|
||||
name: "True-Client-IP takes precedence over X-Real-IP",
|
||||
headers: map[string]string{
|
||||
"True-Client-IP": "203.0.113.11",
|
||||
"X-Real-IP": "198.51.100.20",
|
||||
"X-Forwarded-For": "192.0.2.30",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("203.0.113.11"),
|
||||
},
|
||||
{
|
||||
name: "X-Real-IP takes precedence over X-Forwarded-For",
|
||||
headers: map[string]string{
|
||||
"X-Real-IP": "198.51.100.20",
|
||||
"X-Forwarded-For": "192.0.2.30",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("198.51.100.20"),
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For first entry is extracted",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "192.0.2.30, 10.0.0.2, 172.16.0.5",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("192.0.2.30"),
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For single entry",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": " 192.0.2.31 ",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("192.0.2.31"),
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For skips invalid first entry to find valid IP",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "unknown, 192.0.2.32",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("192.0.2.32"),
|
||||
},
|
||||
{
|
||||
name: "Fallback to RemoteAddr with port",
|
||||
headers: map[string]string{},
|
||||
remoteAddr: "192.168.1.100:12345",
|
||||
wantIP: ptr("192.168.1.100"),
|
||||
},
|
||||
{
|
||||
name: "Fallback to RemoteAddr without port",
|
||||
headers: map[string]string{},
|
||||
remoteAddr: "192.168.1.101",
|
||||
wantIP: ptr("192.168.1.101"),
|
||||
},
|
||||
{
|
||||
name: "IPv6 in RemoteAddr with port",
|
||||
headers: map[string]string{},
|
||||
remoteAddr: "[2001:db8::1]:8080",
|
||||
wantIP: ptr("2001:db8::1"),
|
||||
},
|
||||
{
|
||||
name: "IPv6 in header",
|
||||
headers: map[string]string{
|
||||
"X-Real-IP": "2001:db8::2",
|
||||
},
|
||||
remoteAddr: "10.0.0.1:54321",
|
||||
wantIP: ptr("2001:db8::2"),
|
||||
},
|
||||
{
|
||||
name: "Invalid headers fall back to RemoteAddr",
|
||||
headers: map[string]string{
|
||||
"X-Real-IP": "not-an-ip",
|
||||
"X-Forwarded-For": "also-not-an-ip",
|
||||
},
|
||||
remoteAddr: "192.168.1.102:443",
|
||||
wantIP: ptr("192.168.1.102"),
|
||||
},
|
||||
{
|
||||
name: "No headers and empty RemoteAddr",
|
||||
headers: map[string]string{},
|
||||
remoteAddr: "",
|
||||
wantIP: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var req *http.Request
|
||||
if tt.name != "nil request" {
|
||||
req = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
for k, v := range tt.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
req.RemoteAddr = tt.remoteAddr
|
||||
}
|
||||
|
||||
got := extractClientIP(req)
|
||||
if (got == nil) != (tt.wantIP == nil) {
|
||||
t.Fatalf("extractClientIP() = %v, want %v", got, tt.wantIP)
|
||||
}
|
||||
if got != nil && tt.wantIP != nil && *got != *tt.wantIP {
|
||||
t.Errorf("extractClientIP() = %q, want %q", *got, *tt.wantIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ptr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user