Ver Fonte

fix(client): refuse a bulk quota reduction that would fall to or below zero

BulkAdjust clamped a client's new traffic limit with max(total+addBytes, 0).
Because 0 is the unlimited sentinel, reducing a client's quota by more than
it had left silently granted that client unlimited traffic. The sibling
expiry branch already refuses an over-reduction; mirror it for quota so the
adjustment is skipped with a clear reason instead of crossing the sentinel.
MHSanaei há 1 dia atrás
pai
commit
7b266a9001

+ 9 - 3
internal/web/service/client_bulk.go

@@ -359,9 +359,15 @@ func (s *ClientService) BulkAdjust(inboundSvc *InboundService, emails []string,
 					skippedReasons[email] = "unlimited traffic"
 				}
 			} else {
-				next := max(rec.TotalGB+addBytes, 0)
-				entry.applyTotal = true
-				entry.newTotal = next
+				next := rec.TotalGB + addBytes
+				if next <= 0 {
+					if _, exists := skippedReasons[email]; !exists {
+						skippedReasons[email] = "reduction exceeds remaining quota"
+					}
+				} else {
+					entry.applyTotal = true
+					entry.newTotal = next
+				}
 			}
 		}
 		if entry.applyExpiry || entry.applyTotal || adjustFlow {

+ 25 - 0
internal/web/service/client_bulk_reenable_test.go

@@ -164,6 +164,31 @@ func TestBulkAdjust_ReenablesOverQuota_WhenAddBytesClearsQuota(t *testing.T) {
 	}
 }
 
+func TestBulkAdjust_QuotaReductionBelowZeroSkipsInsteadOfUnlimited(t *testing.T) {
+	setupBulkDB(t)
+	svc := &ClientService{}
+	inboundSvc := &InboundService{}
+
+	email := "qr@x"
+	c := model.Client{Email: email, ID: "11111111-1111-1111-1111-111111111111", SubID: email, Enable: true, TotalGB: 10}
+	ib := mkInbound(t, 52020, model.VLESS, clientsSettings(t, []model.Client{c}))
+	if err := svc.SyncInbound(nil, ib.Id, []model.Client{c}); err != nil {
+		t.Fatalf("seed linkage: %v", err)
+	}
+	mkTraffic(t, ib.Id, email, 0, 0, 10, 0, true)
+
+	res, _, err := svc.BulkAdjust(inboundSvc, []string{email}, 0, -20, "")
+	if err != nil {
+		t.Fatalf("BulkAdjust: %v", err)
+	}
+	if res.Adjusted != 0 {
+		t.Fatalf("over-reduction should not adjust, got Adjusted=%d skipped=%v", res.Adjusted, res.Skipped)
+	}
+	if got := trafficOf(t, email).Total; got != 10 {
+		t.Fatalf("quota reduced past zero was written as %d (0 means unlimited); want it left at 10", got)
+	}
+}
+
 func TestBulkAdjust_OverQuota_DaysOnly_StaysDisabled(t *testing.T) {
 	setupBulkDB(t)
 	svc := &ClientService{}