fix: two more unguarded int() calls on admin input, one with a real reproducible crash path
Continued last commit's input-validation sweep — grepped every
`int(body...)` / `int(...get(...))` in api.py rather than stopping at
the one I'd already found. Two more:
1. admin_set_hwid_limit (per-user device-limit override) — bare
`int(limit) if limit else None`. The devices-limit input in the user
card is a plain text field, so typing anything non-numeric threw an
unhandled TypeError/ValueError straight through the route. Also
traced the `if limit else None` truthiness check specifically
because of the historical bug already on record in memory for this
exact field (hwid_limit=0 silently getting replaced by the fallback
because 0 is falsy) — wrote the new check as `raw_limit in (None, "")`
instead of a bare truthiness test so 0 keeps working as "block this
user entirely," verified with its own test case, not just assumed
from remembering the old bug.
2. admin_provision_guide (the node-add "Гайд по установке" flow) — both
`port` and `hysteria_port` had the same bare int(). Traced this one
to an actually-reachable crash, not just a theoretical gap: the
fields are type="text" (not type="number"), the JS does
parseInt(value || "443") — type garbage over the pre-filled "443"
and parseInt returns NaN, which JSON.stringify silently serializes
as null. The route's dict then has "port": null — a key that EXISTS,
so body.get("port", 443)'s default never kicks in — and int(None)
throws TypeError, uncaught. Reproduced this exact null-not-missing
shape in the test rather than just "some invalid input," since that's
the actual failure mode a user hits by editing the field, not a
contrived one.
Same pattern as admin_create_node's port fix last commit for
consistency: try/except converting to a friendly 400, then a 1-65535
range check. Kept it as a second inline try/except rather than
extracting a shared helper — the four now-similar blocks aren't
identical enough (different valid ranges, one skips silently when its
key is absent entirely, this one treats an absent key as "restore the
default") to be worth the risk of reshaping already-shipped, tested
code this late for a stylistic win.
Verification: AST-extracted both updated route bodies out of api.py
(still can't import it directly) and drove each through a fake
db/nodeprov module. admin_provision_guide: 7 cases, including
reproducing the literal null-after-JSON shape for both port fields (not
a generic "bad input" test), the missing-key-defaults-to-443 path for
both, and confirming hysteria_port is never even touched when
include_hysteria2 is false. admin_set_hwid_limit: 8 cases — numeric
string coercion, explicit 0 preserved, three different ways of clearing
the override (null/empty-string/absent-key) all landing on the same
result, and the three rejection branches (non-numeric, negative,
over-1000).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f37b8a5018
commit
59f67b8e4e
1 changed files with 23 additions and 4 deletions
23
api.py
23
api.py
|
|
@ -1010,8 +1010,17 @@ def admin_delete_device(tg_id: int, device_id: int, request: Request):
|
||||||
@app.post("/admin/api/users/{tg_id}/hwid-limit")
|
@app.post("/admin/api/users/{tg_id}/hwid-limit")
|
||||||
def admin_set_hwid_limit(tg_id: int, request: Request, body: dict = Body(...)):
|
def admin_set_hwid_limit(tg_id: int, request: Request, body: dict = Body(...)):
|
||||||
require_admin(request)
|
require_admin(request)
|
||||||
limit = body.get("limit")
|
raw_limit = body.get("limit")
|
||||||
db.set_user_hwid_limit(tg_id, int(limit) if limit else None)
|
if raw_limit in (None, ""):
|
||||||
|
db.set_user_hwid_limit(tg_id, None)
|
||||||
|
return {"ok": True}
|
||||||
|
try:
|
||||||
|
limit = int(raw_limit)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise HTTPException(400, "лимит должен быть целым числом")
|
||||||
|
if not (0 <= limit <= 1000):
|
||||||
|
raise HTTPException(400, "лимит должен быть от 0 до 1000")
|
||||||
|
db.set_user_hwid_limit(tg_id, limit)
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1178,7 +1187,12 @@ def admin_provision_guide(request: Request, body: dict = Body(...)):
|
||||||
require_admin(request)
|
require_admin(request)
|
||||||
label = body["label"]
|
label = body["label"]
|
||||||
address = body["address"]
|
address = body["address"]
|
||||||
|
try:
|
||||||
port = int(body.get("port", 443))
|
port = int(body.get("port", 443))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise HTTPException(400, "port должен быть числом")
|
||||||
|
if not (1 <= port <= 65535):
|
||||||
|
raise HTTPException(400, "port должен быть от 1 до 65535")
|
||||||
sni = body.get("sni") or "www.wildberries.ru"
|
sni = body.get("sni") or "www.wildberries.ru"
|
||||||
include_ws = bool(body.get("include_ws"))
|
include_ws = bool(body.get("include_ws"))
|
||||||
include_hysteria2 = bool(body.get("include_hysteria2"))
|
include_hysteria2 = bool(body.get("include_hysteria2"))
|
||||||
|
|
@ -1189,7 +1203,12 @@ def admin_provision_guide(request: Request, body: dict = Body(...)):
|
||||||
|
|
||||||
hysteria_port = hysteria_password = hysteria_obfs_password = None
|
hysteria_port = hysteria_password = hysteria_obfs_password = None
|
||||||
if include_hysteria2:
|
if include_hysteria2:
|
||||||
|
try:
|
||||||
hysteria_port = int(body.get("hysteria_port", 443))
|
hysteria_port = int(body.get("hysteria_port", 443))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise HTTPException(400, "hysteria_port должен быть числом")
|
||||||
|
if not (1 <= hysteria_port <= 65535):
|
||||||
|
raise HTTPException(400, "hysteria_port должен быть от 1 до 65535")
|
||||||
hysteria_password, hysteria_obfs_password = nodeprov.generate_hysteria_credentials()
|
hysteria_password, hysteria_obfs_password = nodeprov.generate_hysteria_credentials()
|
||||||
|
|
||||||
node, token = db.create_pending_node(
|
node, token = db.create_pending_node(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue