mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(hwid): keep a subscription's device limit while its client is disabled
effectiveHwidLimitForSubID took the limit over enabled clients only. A client switched off for expiry, quota or by hand therefore had no limit at all, while the subscription body is still served for disabled clients, so any number of devices registered and stayed past the renewal. The limit now follows the subId whatever the enable flag, and the gate, the slot status and the trim keep sharing that one query.
This commit is contained in:
@@ -92,10 +92,12 @@ func normalizeHwidRequest(req HwidRequest) HwidRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The subscription body is built for disabled clients too, so an expired,
|
||||||
|
// depleted or suspended client must keep holding the limit of its subId.
|
||||||
func effectiveHwidLimitForSubID(tx *gorm.DB, subID string) (int, error) {
|
func effectiveHwidLimitForSubID(tx *gorm.DB, subID string) (int, error) {
|
||||||
var limit int
|
var limit int
|
||||||
err := tx.Model(&model.ClientRecord{}).
|
err := tx.Model(&model.ClientRecord{}).
|
||||||
Where("sub_id = ? AND enable = ?", subID, true).
|
Where("sub_id = ?", subID).
|
||||||
Select("COALESCE(MAX(limit_hwid), 0)").
|
Select("COALESCE(MAX(limit_hwid), 0)").
|
||||||
Scan(&limit).Error
|
Scan(&limit).Error
|
||||||
return limit, err
|
return limit, err
|
||||||
@@ -216,7 +218,7 @@ func trackUnlimitedHwid(db *gorm.DB, subID string, req HwidRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HwidSlotStatusForSubID is SELECT-only: it must never write client_hwids or
|
// HwidSlotStatusForSubID is SELECT-only: it must never write client_hwids or
|
||||||
// last_seen. Enabled-clients scope mirrors the gate, so limit == limit enforced.
|
// last_seen. It shares the gate's limit query, so limit == limit enforced.
|
||||||
func (s *ClientService) HwidSlotStatusForSubID(subID string) (status HwidSlotStatus, found bool, err error) {
|
func (s *ClientService) HwidSlotStatusForSubID(subID string) (status HwidSlotStatus, found bool, err error) {
|
||||||
subID = strings.TrimSpace(subID)
|
subID = strings.TrimSpace(subID)
|
||||||
if subID == "" {
|
if subID == "" {
|
||||||
|
|||||||
@@ -288,6 +288,48 @@ func TestClientHwidGateSharedSubIdUsesMaxLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientHwidGateHoldsWhileClientDisabled(t *testing.T) {
|
||||||
|
initClientHwidTestDB(t)
|
||||||
|
svc := &ClientService{}
|
||||||
|
db := database.GetDB()
|
||||||
|
rec := seedHwidClient(t, 1)
|
||||||
|
|
||||||
|
if res, err := svc.EnforceHwidForSubID(rec.SubID, HwidRequest{Hwid: "device-one"}); err != nil || !res.Allowed {
|
||||||
|
t.Fatalf("register first device = %+v, %v", res, err)
|
||||||
|
}
|
||||||
|
if err := db.Model(&model.ClientRecord{}).Where("id = ?", rec.Id).UpdateColumn("enable", false).Error; err != nil {
|
||||||
|
t.Fatalf("disable client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStillLimited := func(stage string) {
|
||||||
|
t.Helper()
|
||||||
|
res, err := svc.EnforceHwidForSubID(rec.SubID, HwidRequest{Hwid: "device-two"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: second device: %v", stage, err)
|
||||||
|
}
|
||||||
|
if res.Allowed || !res.MaxDevicesReached || res.Limit != 1 {
|
||||||
|
t.Fatalf("%s: disabled client must keep its device limit: %+v", stage, res)
|
||||||
|
}
|
||||||
|
if res, err = svc.EnforceHwidForSubID(rec.SubID, HwidRequest{Hwid: "device-one"}); err != nil || !res.Allowed {
|
||||||
|
t.Fatalf("%s: registered device = %+v, %v", stage, res, err)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := db.Model(&model.ClientHwid{}).Where("sub_id = ?", rec.SubID).Count(&count).Error; err != nil {
|
||||||
|
t.Fatalf("%s: count HWIDs: %v", stage, err)
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
t.Fatalf("%s: stored HWIDs = %d, want 1", stage, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertStillLimited("only client disabled")
|
||||||
|
|
||||||
|
// An enabled sibling without a limit must not lift it either.
|
||||||
|
if err := db.Create(&model.ClientRecord{Email: "second@example.com", SubID: rec.SubID, UUID: "22222222-2222-4333-8444-555555555555", Enable: true, LimitHwid: 0}).Error; err != nil {
|
||||||
|
t.Fatalf("seed second client: %v", err)
|
||||||
|
}
|
||||||
|
assertStillLimited("unlimited sibling enabled")
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientHwidSlotStatus(t *testing.T) {
|
func TestClientHwidSlotStatus(t *testing.T) {
|
||||||
initClientHwidTestDB(t)
|
initClientHwidTestDB(t)
|
||||||
svc := &ClientService{}
|
svc := &ClientService{}
|
||||||
|
|||||||
Reference in New Issue
Block a user