fix: 18-point audit pass — payment races, hwid limit bugs, blocking SSH/HTTP in event loops, N+1 queries, ssh host-key pinning, dead code
payments: _grant_paid_subscription now validates plan/node exist before marking a payment paid instead of after (was leaving charged-but-ungranted payments with no error trail); mark_payment_paid is now a single atomic UPDATE ... WHERE status='pending' instead of check-then-act, closing a double-grant race between webhooks and the periodic reconciler; yookassa webhook now re-verifies payment status server-side via the API instead of trusting the posted body (platega already had HMAC verification). hwid: 'user["hwid_limit"] or FALLBACK' treated an explicit 0 (admin fully blocking a user) as unset — now an explicit None check. Device count-check and insert are now one atomic transaction (db.add_device_if_under_limit) instead of two raceable statements. perf: payment webhooks and _grant_paid_subscription's SSH/HTTP calls now run via asyncio.to_thread instead of blocking the event loop; same for bot.py's periodic_sync/reconcile_pending_payments and the manual admin sync button. Admin endpoints (traffic/subscriptions/payments/gift-codes/ user-card) now resolve node labels from one db.list_nodes() call instead of a fresh db.get_node() per row. revoke/reset-traffic use a direct PK lookup instead of scanning up to 5000 rows. Dashboard now asks the API for 8 rows instead of fetching 200 and slicing client-side. security: mbs.db (and -wal/-shm) now chmod 600 right after creation — it held session tokens and subscription bearer tokens world-readable by default. Node SSH connections now pin host keys via a persisted known_hosts file (TOFU) instead of accepting any key on every connection. delete_node now refuses to delete a node with active subscriptions instead of silently orphaning their xray clients. deadcode: removed unused xray_manager.list_client_ids and admin.html's superseded staggerReveal (rows animate via rowAttr() inline now). Also guards gift-code redemption against a plan/node deleted after the code was created (was an unhandled KeyError/TypeError crash). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2604c2dfe7
commit
f9ee4f5933
6 changed files with 103 additions and 59 deletions
|
|
@ -873,11 +873,6 @@ function statusBadge(active, daysLeft) {
|
|||
if (daysLeft <= 2) return '<span class="badge warn">' + daysLeft + ' дн.</span>';
|
||||
return '<span class="badge ok">' + daysLeft + ' дн.</span>';
|
||||
}
|
||||
function staggerReveal(container) {
|
||||
const items = container.querySelectorAll(".reveal");
|
||||
items.forEach((el, i) => { el.style.animationDelay = (i * 0.04) + "s"; });
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
const stats = await api("/admin/api/stats");
|
||||
const grid = document.getElementById("stat-grid");
|
||||
|
|
@ -888,9 +883,8 @@ async function loadDashboard() {
|
|||
["Гифт-коды (созд./исп.)", stats.gifts_created + " / " + stats.gifts_used, "var(--pink)", ICONS.gift],
|
||||
].map(([l, v, c, ic], i) => statCard(l, v, c, ic, i * 0.05)).join("");
|
||||
|
||||
const subs = await api("/admin/api/subscriptions");
|
||||
const recent = await api("/admin/api/subscriptions?limit=8");
|
||||
const body = document.getElementById("recent-subs-body");
|
||||
const recent = subs.slice(0, 8);
|
||||
body.innerHTML = recent.length ? recent.map((s, i) => `
|
||||
<tr ${rowAttr(i)}><td>${s.username ? "@" + esc(s.username) : "tg" + s.tg_id}</td><td>${esc(s.node_label)}</td><td>${esc(s.plan_label)}</td>
|
||||
<td>${fmtDate(s.expires_at)}</td><td>${statusBadge(s.active, s.days_left)}</td></tr>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue