mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-18 16:17:16 +00:00
fix(clients): list HWID devices when the HWID limit is 0
EnforceHwidForSubID returned before recording anything when a sub had no limit, so the panel's HWID Devices list stayed empty for every unlimited client. Devices are now upserted on the (sub_id, hwid_hash) index without enforcement or X-Hwid-* headers; the write is best-effort and only logs on failure, so tracking can never deny a subscription nothing restricts.
This commit is contained in:
@@ -9,8 +9,10 @@ import (
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type HwidRequest struct {
|
||||
@@ -110,12 +112,15 @@ func (s *ClientService) EnforceHwidForSubID(subID string, req HwidRequest) (Hwid
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
req = normalizeHwidRequest(req)
|
||||
if limit <= 0 {
|
||||
res.Allowed = true
|
||||
if len(req.Hwid) >= minHwidLength {
|
||||
trackUnlimitedHwid(db, subID, req)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
req = normalizeHwidRequest(req)
|
||||
res.Active = true
|
||||
res.Limit = limit
|
||||
if len(req.Hwid) < minHwidLength {
|
||||
@@ -177,6 +182,19 @@ func (s *ClientService) EnforceHwidForSubID(subID string, req HwidRequest) (Hwid
|
||||
return res, err
|
||||
}
|
||||
|
||||
// trackUnlimitedHwid lists devices of a sub with no HWID limit in the panel. It is
|
||||
// best-effort: a failed write must not deny a subscription nothing restricts.
|
||||
func trackUnlimitedHwid(db *gorm.DB, subID string, req HwidRequest) {
|
||||
now := time.Now().UnixMilli()
|
||||
err := db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "sub_id"}, {Name: "hwid_hash"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"last_seen", "user_agent", "device_os", "os_version", "device_model"}),
|
||||
}).Create(&model.ClientHwid{SubID: subID, HwidHash: hashHwid(req.Hwid), FirstSeen: now, LastSeen: now, UserAgent: req.UserAgent, DeviceOS: req.DeviceOS, OsVersion: req.OsVersion, DeviceModel: req.DeviceModel}).Error
|
||||
if err != nil {
|
||||
logger.Warning("track HWID for unlimited subscription failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// HwidSlotStatusForSubID is SELECT-only: it must never write client_hwids or
|
||||
// last_seen. Enabled-clients scope mirrors the gate, so limit == limit enforced.
|
||||
func (s *ClientService) HwidSlotStatusForSubID(subID string) (status HwidSlotStatus, found bool, err error) {
|
||||
|
||||
@@ -45,6 +45,23 @@ func TestClientHwidGate(t *testing.T) {
|
||||
if !res.Allowed || res.Active {
|
||||
t.Fatalf("no limit should allow missing HWID without active headers: %+v", res)
|
||||
}
|
||||
|
||||
for _, ua := range []string{"Happ/1.0", "Happ/2.0"} {
|
||||
res, err = svc.EnforceHwidForSubID("sub-hwid", HwidRequest{Hwid: "device-one", UserAgent: ua})
|
||||
if err != nil {
|
||||
t.Fatalf("no-limit gate with HWID: %v", err)
|
||||
}
|
||||
if res != (HwidGateResult{Allowed: true}) {
|
||||
t.Fatalf("no limit should allow HWID without active headers: %+v", res)
|
||||
}
|
||||
}
|
||||
list, err := svc.ListClientHwids("hwid@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("list HWIDs: %v", err)
|
||||
}
|
||||
if len(list) != 1 || list[0].UserAgent != "Happ/2.0" {
|
||||
t.Fatalf("no limit should still track one device with fresh metadata, got %+v", list)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHwidGateRegistersAndBlocks(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user