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
+105 -1
View File
@@ -1225,6 +1225,9 @@
"keepAlive": {
"type": "integer"
},
"limitHwid": {
"type": "integer"
},
"limitIp": {
"type": "integer"
},
@@ -1282,6 +1285,7 @@
"group",
"id",
"keepAlive",
"limitHwid",
"limitIp",
"password",
"preSharedKey",
@@ -5813,6 +5817,7 @@
"totalGB": 53687091200,
"expiryTime": 1735689600000,
"limitIp": 0,
"limitHwid": 0,
"reset": 0,
"inboundIds": [
3,
@@ -5958,6 +5963,7 @@
"expiryTime": 1735689600000,
"tgId": 0,
"limitIp": 0,
"limitHwid": 0,
"enable": true
},
"inboundIds": [
@@ -6024,6 +6030,7 @@
"email": "alice@example.com",
"totalGB": 107374182400,
"expiryTime": 1767225600000,
"limitHwid": 2,
"tgId": 123456789,
"enable": true
}
@@ -6372,7 +6379,7 @@
"tags": [
"Clients"
],
"summary": "Delete every client that is not attached to any inbound, along with its traffic record, IP log, and external links. Useful for clearing clients left unattached after their inbounds were removed. Returns the deleted count. Cannot be undone.",
"summary": "Delete every client that is not attached to any inbound, along with its traffic record, IP log, HWID devices, and external links. Useful for clearing clients left unattached after their inbounds were removed. Returns the deleted count. Cannot be undone.",
"operationId": "post_panel_api_clients_delOrphans",
"responses": {
"200": {
@@ -6436,6 +6443,7 @@
"id": "...",
"totalGB": 53687091200,
"expiryTime": 0,
"limitHwid": 2,
"enable": true,
"subId": "..."
},
@@ -6763,6 +6771,7 @@
"email": "alice@example.com",
"totalGB": 53687091200,
"expiryTime": 0,
"limitHwid": 2,
"enable": true
},
"inboundIds": [
@@ -6774,6 +6783,7 @@
"email": "bob@example.com",
"totalGB": 53687091200,
"expiryTime": 0,
"limitHwid": 0,
"enable": true
},
"inboundIds": [
@@ -7568,6 +7578,100 @@
}
}
},
"/panel/api/clients/hwids/{email}": {
"post": {
"tags": [
"Clients"
],
"summary": "List registered HWID devices for a client. Hashes are not exposed.",
"operationId": "post_panel_api_clients_hwids_email",
"parameters": [
{
"name": "email",
"in": "path",
"required": true,
"description": "Client email.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"obj": {}
}
},
"example": {
"success": true,
"obj": [
{
"id": 1,
"firstSeen": 1735000000000,
"lastSeen": 1735100000000,
"userAgent": "Happ/1.0",
"deviceOs": "android",
"osVersion": "15",
"deviceModel": "Pixel 9"
}
]
}
}
}
}
}
},
"delete": {
"tags": [
"Clients"
],
"summary": "Clear all registered HWID devices for a client so new devices can register again.",
"operationId": "delete_panel_api_clients_hwids_email",
"parameters": [
{
"name": "email",
"in": "path",
"required": true,
"description": "Client email.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"obj": {}
}
}
}
}
}
}
}
},
"/panel/api/clients/onlines": {
"post": {
"tags": [