fix(backend): disable one-registration-per-IP by default
Shared NAT egress makes client IP unusable for uniqueness checks. Gate the behavior behind REGISTRATION_ENFORCE_IP (default false) for public/invite signup.
This commit is contained in:
@@ -130,16 +130,8 @@ func (s *Server) handleRegisterInvite(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ip := clientAddress(r)
|
||||
if ip == "" || ip == "unknown" {
|
||||
writeErr(w, http.StatusBadRequest, "آدرس شبکه شناسایی نشد")
|
||||
return
|
||||
}
|
||||
if taken, err := s.store.HasRegistrationIP(r.Context(), ip); err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "بررسی محدودیت ثبتنام ممکن نشد")
|
||||
return
|
||||
} else if taken {
|
||||
writeErr(w, http.StatusConflict, "از این شبکه قبلاً ثبتنام انجام شده است")
|
||||
ip, ok := s.registrationClientIP(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ func TestInviteRevoke(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInviteDuplicatePhoneAndIP(t *testing.T) {
|
||||
t.Setenv("REGISTRATION_ENFORCE_IP", "true")
|
||||
e := setup(t)
|
||||
inv := createInvite(t, e, 5, "", nil)
|
||||
token := inv["token"].(string)
|
||||
|
||||
@@ -27,6 +27,29 @@ func (s *Server) registrationRequireWhitelist(r *http.Request) bool {
|
||||
return s.settingTruthy(r, "registration_require_whitelist", true)
|
||||
}
|
||||
|
||||
// registrationClientIP returns the IP to bind to a registration when enforcement is on.
|
||||
// When REGISTRATION_ENFORCE_IP is false (default), returns "" so NAT / shared egress is ignored.
|
||||
func (s *Server) registrationClientIP(w http.ResponseWriter, r *http.Request) (string, bool) {
|
||||
if s.cfg == nil || !s.cfg.RegistrationEnforceIP {
|
||||
return "", true
|
||||
}
|
||||
ip := clientAddress(r)
|
||||
if ip == "" || ip == "unknown" {
|
||||
writeErr(w, http.StatusBadRequest, "آدرس شبکه شناسایی نشد")
|
||||
return "", false
|
||||
}
|
||||
taken, err := s.store.HasRegistrationIP(r.Context(), ip)
|
||||
if err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "بررسی محدودیت ثبتنام ممکن نشد")
|
||||
return "", false
|
||||
}
|
||||
if taken {
|
||||
writeErr(w, http.StatusConflict, "از این شبکه قبلاً ثبتنام انجام شده است")
|
||||
return "", false
|
||||
}
|
||||
return ip, true
|
||||
}
|
||||
|
||||
func (s *Server) handleRegisterStatus(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"enabled": s.registrationEnabled(r),
|
||||
@@ -83,16 +106,8 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ip := clientAddress(r)
|
||||
if ip == "" || ip == "unknown" {
|
||||
writeErr(w, http.StatusBadRequest, "آدرس شبکه شناسایی نشد")
|
||||
return
|
||||
}
|
||||
if taken, err := s.store.HasRegistrationIP(r.Context(), ip); err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "بررسی محدودیت ثبتنام ممکن نشد")
|
||||
return
|
||||
} else if taken {
|
||||
writeErr(w, http.StatusConflict, "از این شبکه قبلاً ثبتنام انجام شده است")
|
||||
ip, ok := s.registrationClientIP(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@ func TestRegisterDuplicatePhone(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegisterSameIPRejected(t *testing.T) {
|
||||
t.Setenv("REGISTRATION_ENFORCE_IP", "true")
|
||||
e := setup(t)
|
||||
enableRegistration(t, e)
|
||||
whitelistPhones(t, e, "09121110007", "09121110008")
|
||||
@@ -278,7 +279,27 @@ func TestRegisterSameIPRejected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterSameIPAllowedWhenEnforcementOff(t *testing.T) {
|
||||
t.Setenv("REGISTRATION_ENFORCE_IP", "false")
|
||||
e := setup(t)
|
||||
enableRegistration(t, e)
|
||||
whitelistPhones(t, e, "09121110007", "09121110008")
|
||||
|
||||
headers := map[string]string{"X-Forwarded-For": "203.0.113.40"}
|
||||
code, body := e.doHeaders(t, "POST", "/api/auth/register", "", headers,
|
||||
registerBody(t, e, "09121110007", "reg-ipoff1@test.dev", "regipoff1"))
|
||||
if code != http.StatusCreated {
|
||||
t.Fatalf("first: %d %v", code, body)
|
||||
}
|
||||
code, body = e.doHeaders(t, "POST", "/api/auth/register", "", headers,
|
||||
registerBody(t, e, "09121110008", "reg-ipoff2@test.dev", "regipoff2"))
|
||||
if code != http.StatusCreated {
|
||||
t.Fatalf("same IP must be allowed when REGISTRATION_ENFORCE_IP=false: %d %v", code, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterUsesForwardedIP(t *testing.T) {
|
||||
t.Setenv("REGISTRATION_ENFORCE_IP", "true")
|
||||
e := setup(t)
|
||||
enableRegistration(t, e)
|
||||
whitelistPhones(t, e, "09121110009", "09121110010", "09121110011")
|
||||
|
||||
Reference in New Issue
Block a user