feat(sub): add per-client subscription HWID limits (#5802)

* feat(sub): add per-client subscription HWID limits

* fix(sub): address HWID review on shared subId and bulk create

* fix(sub): store HWID devices by sub_id and drop anchor client workaround

* fix(sub): restore UA auto-detect and HTML page routing in subs()

The cherry-pick of the HWID gate onto main's refactored SUBController
had dropped main's UA-based format auto-detection and sub-page handling
from subs(). Restore those branches, slotting enforceHwid after the
HTML page and before format detection so the gate only applies to
machine-readable subscription bodies.

Also adapt tests to main's options-struct constructor and to the
ClientService.Update signature extended with limitHwid.

* fix(frontend): drop axios from HttpUtil.delete

The bulk-delete rework's committed version still referenced axios,
which this file no longer imports, breaking typecheck in CI. Use the
httpRequest wrapper like the other verbs.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Rouzbeh†
2026-08-15 18:20:20 +03:30
committed by GitHub
parent 1793a9b8b4
commit 694ad6deae
45 changed files with 1212 additions and 50 deletions
+9 -1
View File
@@ -54,6 +54,7 @@ func (s *ClientService) ExportAll() ([]ClientCreatePayload, error) {
out = append(out, ClientCreatePayload{
Client: *client,
InboundIds: attachments[rows[i].Id],
LimitHwid: rows[i].LimitHwid,
})
}
return out, nil
@@ -151,7 +152,9 @@ func (s *ClientService) ImportClients(inboundSvc *InboundService, items []Client
}
client.UpdatedAt = now
if err := db.Create(client.ToRecord()).Error; err != nil {
rec := client.ToRecord()
rec.LimitHwid = orphans[i].LimitHwid
if err := db.Create(rec).Error; err != nil {
skip(email, err.Error())
continue
}
@@ -178,11 +181,13 @@ func (s *ClientService) DeleteOrphans() (int, error) {
ids := make([]int, 0, len(rows))
emails := make([]string, 0, len(rows))
subIDs := make([]string, 0, len(rows))
for i := range rows {
ids = append(ids, rows[i].Id)
if rows[i].Email != "" {
emails = append(emails, rows[i].Email)
}
subIDs = append(subIDs, rows[i].SubID)
}
tombstoneClientEmails(emails)
@@ -190,6 +195,9 @@ func (s *ClientService) DeleteOrphans() (int, error) {
if e := adjustGroupBaselinesForRemovedTraffic(tx, emails); e != nil {
return e
}
if e := clearClientHwidsBySubIDTx(tx, subIDs...); e != nil {
return e
}
for _, batch := range chunkInts(ids, sqlInChunk) {
if e := tx.Where("client_id IN ?", batch).Delete(&model.ClientInbound{}).Error; e != nil {
return e