fix: 自助注册按 trusted_proxies 解析客户端 IP
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
||||
"git.asio.asia/nixevol/NixMsg/internal/httpx"
|
||||
"git.asio.asia/nixevol/NixMsg/internal/protocol"
|
||||
"git.asio.asia/nixevol/NixMsg/internal/store"
|
||||
)
|
||||
@@ -309,6 +310,83 @@ func TestRegisterF23_WrongCodeLock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterTrustedProxyClientIPLock 验证与管理接口相同的 httpx.ClientIP:
|
||||
// 受信代理的 X-Forwarded-For 按真实客户端 IP 计锁;非信任来源不采信转发头。
|
||||
func TestRegisterTrustedProxyClientIPLock(t *testing.T) {
|
||||
db, err := store.Open(t.TempDir(), "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
locks := newRegisterIPLocker(time.Now)
|
||||
trusted := httpx.ParseCIDRs([]string{"127.0.0.1/32"})
|
||||
handler := NewRegisterHandler(RegisterConfig{
|
||||
DB: db,
|
||||
Hash: auth.NewStubHashPool(),
|
||||
Locks: locks,
|
||||
ClientIP: func(r *http.Request) string {
|
||||
return httpx.ClientIP(r, trusted)
|
||||
},
|
||||
})
|
||||
env := &testEnv{db: db, hash: auth.NewStubHashPool(), locks: locks, handler: handler}
|
||||
env.setRegistration(t, true, "proxy-lock-1")
|
||||
|
||||
post := func(remote, xff, body string) (int, registerResp) {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/client/register", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.RemoteAddr = remote
|
||||
if xff != "" {
|
||||
req.Header.Set("X-Forwarded-For", xff)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
var resp registerResp
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode: %v body=%s", err, rr.Body.String())
|
||||
}
|
||||
return rr.Code, resp
|
||||
}
|
||||
|
||||
wrong := `{"registration_code":"wrong-code","id":"ep_px","login_password":"password1"}`
|
||||
good := `{"registration_code":"proxy-lock-1","id":"ep_px","login_password":"password1"}`
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
code, resp := post("127.0.0.1:9000", "198.51.100.7", wrong)
|
||||
if code != http.StatusForbidden || resp.Error == nil || resp.Error.Code != protocol.CodeRegistrationCodeInvalid {
|
||||
t.Fatalf("trusted fail #%d: status=%d resp=%+v", i+1, code, resp)
|
||||
}
|
||||
}
|
||||
code, resp := post("127.0.0.1:9000", "198.51.100.7", good)
|
||||
if code != http.StatusTooManyRequests || resp.Error == nil || resp.Error.Code != protocol.CodeRateLimited {
|
||||
t.Fatalf("real client should be locked: status=%d resp=%+v", code, resp)
|
||||
}
|
||||
code, resp = post("127.0.0.1:9000", "198.51.100.8", good)
|
||||
if code != http.StatusOK || !resp.OK || resp.Data.ID != "ep_px" {
|
||||
t.Fatalf("other XFF client must not share lock: status=%d resp=%+v", code, resp)
|
||||
}
|
||||
|
||||
// 非信任对端:忽略 XFF,按 RemoteAddr 计锁。
|
||||
locks.Clear(auth.LockKey{Kind: auth.LockRegisterIP, IP: "198.51.100.7"})
|
||||
locks.Clear(auth.LockKey{Kind: auth.LockRegisterIP, IP: "203.0.113.50"})
|
||||
for i := 0; i < 10; i++ {
|
||||
code, resp := post("203.0.113.50:4433", "198.51.100.7", wrong)
|
||||
if code != http.StatusForbidden || resp.Error == nil || resp.Error.Code != protocol.CodeRegistrationCodeInvalid {
|
||||
t.Fatalf("untrusted fail #%d: status=%d resp=%+v", i+1, code, resp)
|
||||
}
|
||||
}
|
||||
code, resp = post("203.0.113.50:4433", "198.51.100.7", `{"registration_code":"proxy-lock-1","id":"ep_px2","login_password":"password1"}`)
|
||||
if code != http.StatusTooManyRequests || resp.Error == nil || resp.Error.Code != protocol.CodeRateLimited {
|
||||
t.Fatalf("untrusted RemoteAddr should be locked: status=%d resp=%+v", code, resp)
|
||||
}
|
||||
// 若误采信 XFF,198.51.100.7 会已锁;直连该 IP 应仍可注册。
|
||||
code, resp = post("198.51.100.7:5555", "", `{"registration_code":"proxy-lock-1","id":"ep_px3","login_password":"password1"}`)
|
||||
if code != http.StatusOK || !resp.OK || resp.Data.ID != "ep_px3" {
|
||||
t.Fatalf("spoofed XFF must not lock real client: status=%d resp=%+v", code, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterF23_IDTakenKeepsOriginal(t *testing.T) {
|
||||
env := openTestEnv(t)
|
||||
env.setRegistration(t, true, "taken-code")
|
||||
|
||||
Reference in New Issue
Block a user