mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(hwid): track devices only for subscriptions a client owns
The subscription routes run the HWID gate before they look up the subId, and the gate read "no device limit" and "no such subscription" alike, so a request naming any path segment with an X-HWID header was answered 404 yet stored that segment, up to about 1 MiB, in client_hwids for good. Anyone reaching the subscription port could fill the disk until the panel could no longer save anything. An unlimited subscription is tracked only while an enabled client owns its subId, and a one-time seeder drops the rows already stored for subIds no client owns.
This commit is contained in:
@@ -101,6 +101,16 @@ func effectiveHwidLimitForSubID(tx *gorm.DB, subID string) (int, error) {
|
||||
return limit, err
|
||||
}
|
||||
|
||||
// subIDHasEnabledClient separates "no device limit" from "no such subscription":
|
||||
// effectiveHwidLimitForSubID yields 0 for both.
|
||||
func subIDHasEnabledClient(tx *gorm.DB, subID string) (bool, error) {
|
||||
var enabled int64
|
||||
err := tx.Model(&model.ClientRecord{}).
|
||||
Where("sub_id = ? AND enable = ?", subID, true).
|
||||
Count(&enabled).Error
|
||||
return enabled > 0, err
|
||||
}
|
||||
|
||||
func (s *ClientService) EnforceHwidForSubID(subID string, req HwidRequest) (HwidGateResult, error) {
|
||||
var res HwidGateResult
|
||||
subID = strings.TrimSpace(subID)
|
||||
@@ -117,7 +127,15 @@ func (s *ClientService) EnforceHwidForSubID(subID string, req HwidRequest) (Hwid
|
||||
req = normalizeHwidRequest(req)
|
||||
if limit <= 0 {
|
||||
res.Allowed = true
|
||||
if len(req.Hwid) >= minHwidLength {
|
||||
if len(req.Hwid) < minHwidLength {
|
||||
return res, nil
|
||||
}
|
||||
// The gate runs before the sub lookup; a subId no client owns gets no row.
|
||||
known, err := subIDHasEnabledClient(db, subID)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if known {
|
||||
trackUnlimitedHwid(db, subID, req)
|
||||
}
|
||||
return res, nil
|
||||
@@ -206,15 +224,10 @@ func (s *ClientService) HwidSlotStatusForSubID(subID string) (status HwidSlotSta
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
var enabled int64
|
||||
if err := db.Model(&model.ClientRecord{}).
|
||||
Where("sub_id = ? AND enable = ?", subID, true).
|
||||
Count(&enabled).Error; err != nil {
|
||||
known, err := subIDHasEnabledClient(db, subID)
|
||||
if err != nil || !known {
|
||||
return status, false, err
|
||||
}
|
||||
if enabled == 0 {
|
||||
return status, false, nil
|
||||
}
|
||||
|
||||
limit, err := effectiveHwidLimitForSubID(db, subID)
|
||||
if err != nil {
|
||||
|
||||
@@ -62,6 +62,54 @@ func TestClientHwidGate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The gate runs before the subscription lookup, so a subId no enabled client
|
||||
// owns must pass through without leaving a client_hwids row behind.
|
||||
func TestClientHwidGateDoesNotTrackUnknownSubID(t *testing.T) {
|
||||
initClientHwidTestDB(t)
|
||||
svc := &ClientService{}
|
||||
db := database.GetDB()
|
||||
rec := seedHwidClient(t, 0)
|
||||
|
||||
countRows := func(subID string) int64 {
|
||||
t.Helper()
|
||||
var rows int64
|
||||
if err := db.Model(&model.ClientHwid{}).Where("sub_id = ?", subID).Count(&rows).Error; err != nil {
|
||||
t.Fatalf("count rows for %q: %v", subID, err)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
res, err := svc.EnforceHwidForSubID("no-such-sub", HwidRequest{Hwid: "device-ghost", UserAgent: "Happ/1.0"})
|
||||
if err != nil || res != (HwidGateResult{Allowed: true}) {
|
||||
t.Fatalf("unknown subId gate: err=%v res=%+v", err, res)
|
||||
}
|
||||
if rows := countRows("no-such-sub"); rows != 0 {
|
||||
t.Fatalf("client_hwids rows for an unknown subId = %d, want 0", rows)
|
||||
}
|
||||
|
||||
if err := db.Model(&model.ClientRecord{}).Where("id = ?", rec.Id).UpdateColumn("enable", false).Error; err != nil {
|
||||
t.Fatalf("disable client: %v", err)
|
||||
}
|
||||
res, err = svc.EnforceHwidForSubID(rec.SubID, HwidRequest{Hwid: "device-ghost"})
|
||||
if err != nil || res != (HwidGateResult{Allowed: true}) {
|
||||
t.Fatalf("disabled subId gate: err=%v res=%+v", err, res)
|
||||
}
|
||||
if rows := countRows(rec.SubID); rows != 0 {
|
||||
t.Fatalf("client_hwids rows for a disabled subId = %d, want 0", rows)
|
||||
}
|
||||
|
||||
if err := db.Model(&model.ClientRecord{}).Where("id = ?", rec.Id).UpdateColumn("enable", true).Error; err != nil {
|
||||
t.Fatalf("enable client: %v", err)
|
||||
}
|
||||
res, err = svc.EnforceHwidForSubID(rec.SubID, HwidRequest{Hwid: "device-ghost"})
|
||||
if err != nil || res != (HwidGateResult{Allowed: true}) {
|
||||
t.Fatalf("known subId gate: err=%v res=%+v", err, res)
|
||||
}
|
||||
if rows := countRows(rec.SubID); rows != 1 {
|
||||
t.Fatalf("client_hwids rows for a known unlimited subId = %d, want 1", rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHwidGateRegistersAndBlocks(t *testing.T) {
|
||||
initClientHwidTestDB(t)
|
||||
svc := &ClientService{}
|
||||
|
||||
Reference in New Issue
Block a user