fix: admin panel showed raw JSON error envelopes instead of the actual message
Found while adding one more input check (webhook URL scheme) and
noticing the error would render as literal {"detail":"..."} text in
the UI. Root cause is in the shared api() JS helper, not any individual
route: on a non-ok response it did `throw new Error(await res.text())`
— the raw response body, not the parsed message. FastAPI's default
HTTPException handler returns {"detail": "message"} as JSON, so every
`esc(e.message)` display in the panel (roughly 15 call sites) was
showing the whole JSON envelope, curly braces and quotes included, not
just the message inside it. Confirmed this wasn't already handled by
checking login()'s own catch block — it hardcodes a fixed string
instead of showing e.message at all, which only makes sense if e.message
was never fit to show directly.
This affects every validation message added the last several commits
(prices, HWID settings, node creation, provision-guide, hwid-limit) and
plenty from before tonight too — not something introduced by this
session, but something this session's run of new validation made worth
actually fixing rather than shipping another error message into a
broken display path.
api(): on error, try to JSON.parse the body and use .detail if it's a
string; anything that doesn't match that exact shape (plain text body,
malformed JSON, FastAPI's array-shaped 422 validation-error detail)
falls through to the original raw-text behavior unchanged, so nothing
that worked before regresses.
Also added the actual check that prompted this: webhook URL must start
with http:// or https://, rejecting things like a bare hostname or a
file:// URL (webhooks.send() never reads or forwards the response body,
so this was never a real exfiltration path, but it's an essentially
free guard against both a fat-fingered URL that would otherwise silently
never deliver anything, and the more deliberate file://-style misuse).
Verification: the api() fix is pure client-side logic with no backend
dependency, so tested directly under Node against a mocked fetch — 6
cases: the exact FastAPI {detail: string} shape extracting cleanly, a
non-JSON error body falling back unchanged, the Pydantic array-detail
422 shape not crashing the parser, malformed JSON falling back to raw
text, the 401/showLogin path completely unchanged, and the successful-
response happy path unaffected. AST-extracted admin_set_webhook_settings
out of api.py (still can't import it directly) and ran it against a
fake legal/env module — 6 cases covering both accepted schemes, both
rejected ones (ftp://, file://), a scheme-less bare hostname, and
confirming clearing the webhook with an empty string still works.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
59f67b8e4e
commit
8343a66a14
2 changed files with 11 additions and 1 deletions
10
admin.html
10
admin.html
|
|
@ -752,7 +752,15 @@ function esc(s) {
|
|||
async function api(path, opts) {
|
||||
const res = await fetch(path, { ...opts, headers: { "Content-Type": "application/json", ...(opts && opts.headers) } });
|
||||
if (res.status === 401) { showLogin(); throw new Error("unauthorized"); }
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
let message = text;
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (parsed && typeof parsed.detail === "string") message = parsed.detail;
|
||||
} catch (e) {}
|
||||
throw new Error(message);
|
||||
}
|
||||
const ct = res.headers.get("content-type") || "";
|
||||
return ct.includes("application/json") ? res.json() : res.text();
|
||||
}
|
||||
|
|
|
|||
2
api.py
2
api.py
|
|
@ -530,6 +530,8 @@ def admin_get_webhook_settings(request: Request):
|
|||
def admin_set_webhook_settings(request: Request, body: dict = Body(...)):
|
||||
require_admin(request)
|
||||
url = (body.get("url") or "").strip()
|
||||
if url and not (url.startswith("http://") or url.startswith("https://")):
|
||||
raise HTTPException(400, "URL должен начинаться с http:// или https://")
|
||||
_update_env_var("WEBHOOK_URL", url)
|
||||
if url and not legal.read_env_var("WEBHOOK_SECRET", ""):
|
||||
_update_env_var("WEBHOOK_SECRET", secrets.token_hex(24))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue