Business

Users & support

Three related surfaces: the Users page is the master Telegram customer record, the Support page is the admin inbox for 1-on-1 conversations, and panel Login is how you, the operator, authenticate into the admin console itself.

What it does

/_authed/users is the master customer record: every Telegram user who has interacted with the bot, merged with their VPN accounts, payments, wallet ledgers, referral relationships, and gift/coupon redemptions, plus admin tools to grant/deduct balances, message users, block/unblock, and run bulk actions. It supports ?tg=<telegramId> deep-linking to open a specific user (used from other pages such as a wallet ledger row).

Fields and merged data sources

SourceFeeds
api.users.list()BotUser[] — the base Telegram user records.
api.accounts.list()VPN accounts, matched to users.
api.payments.list('approved')Approved purchase history.
api.invoices.txns()Full wallet ledger across all tracks.
api.wallet.summary()Aggregate per-user balances and pending counts.
api.invoices.list()Invoice records linkable back to purchases.
api.gifts.list().redemptionsGift/coupon/redeem-code history.
api.logs.list(undefined, 5000)Audit log entries for cross-referencing admin actions.

Per-user wallet breakdown (real vs gifted vs coupon-discounted, spent vs held) is derived purely client-side by scanning ledger entries with note/kind/ref regex heuristics — this is the most detailed reporting surface in the app, but it is derived, not authoritative; the wallet ledger itself is the source of truth.

Linking accounts

A Telegram user is linked to their VPN account(s) by cross-referencing the accounts list against the user's Telegram identity — a VPN account purchased through the bot carries the buyer's Telegram id, which is how the Users page assembles "this Telegram user owns these accounts." The referral graph is built the same way, by cross-referencing referredBy (a raw referrer Telegram id or legacy referral code) against referralCode/telegramId, robust to both storage schemes.

Blocking

  1. 1
    Open the user's row/detail and click Block; type an internal reason.
  2. 2
    api.users.block(id, reason) blocks the Telegram id from the bot entirely — enforcement happens server-side; the reason is stored for audit.
  3. 3
    api.users.unblock(id) reverses this instantly. Blocked users appear under the dedicated Blocked users tab.

Roles and resellers

The panel itself is a single-operator admin console (see Panel login/authentication below) — there is no separate reseller/sub-admin role model visible in the Users page data. Instead, "admin rights inside Telegram" are bootstrapped through the bot: whoever is set as adminChatId in Bot → Main bot (or the first user to run /admin when that field is empty) gets the admin-side Telegram screens described in /docs/telegram-bot §10.9 — effectively acting as a reseller/operator role reachable from inside Telegram, mirroring the web panel's moderation actions (grant points, extend accounts, delete/reset accounts, DM users).

Per-user actions

ActionCallWhat it does
Reset trialapi.users.resetTrial(id)Lets the user claim a free trial again.
Block / Unblockapi.users.block(id, reason)Bars the Telegram id from the bot; reversible.
Grant/deduct walletapi.wallet.gift(telegramId, track, amount, reason, planId?)Adds/removes Points, Top-up money, or Prepaid credit, with a moneyScope for targeting real vs gifted cash specifically on deduction.
Direct messagedmOpen/dmTarget/dmText dialogDM this specific user via the bot.
Open invoicefindInvoice() fuzzy matchMatches a stored order:XXX ledger ref against invoices to open the right PDF.
Bulk actionsmessage / reset_traffic / gift_points / gift_topup / gift_prepaidRuns one action across either the current filtered search results or every user.

Business export

exportBusinessWorkbook(...) (from src/lib/business-export) generates a full workbook combining users, wallets, accounts, payments, invoices, gifts, and audit logs — for offline accounting/reporting. The totals row on the page separately aggregates real vs gifted vs coupon top-up/prepaid totals and total spend from real cash only, deliberately excluding internally-funded orders (paidWith of top-up/prepaid/points/gift) to avoid double-counting cash receipts vs internal transfers.

What it does

/_authed/support is a unified admin inbox for 1-on-1 Telegram support conversations, decoupled from real-time presence — admins can reply whenever, delivered via whichever bot (main or dedicated support bot) is configured.

Data and statuses

CallReturns / does
api.support.threads()SupportThread[] — telegramId, name, accountId?, total, unread, lastAt, lastSender, lastText. Polled every 12s.
api.support.messages(telegramId)SupportMessage[] for the selected thread: id, sender (admin|user), text, caption, fileId, mediaType, bot (main|support), createdAt, deliveredAt, deliveryError. Polled every 8s.
api.support.markRead(telegramId)Clears the unread count when a thread is opened.
api.support.reply(telegramId, text)Sends a text reply. If Telegram can't deliver immediately, it is queued and auto-retried.
api.support.replyMedia(telegramId, file, caption?)Sends an image/video/audio/document reply (images, video, audio, pdf, zip, doc/docx/txt).
api.support.fileUrl(fileId, bot)Resolves inbound media (photos, videos, voice, stickers, documents) for display.

Delivery status per message is one of:

Delivered
Sent successfully.
Queued
Not yet delivered, will retry — usually the user hasn't started the bot yet.
Undeliverable
Permanent failure: user blocked the bot, never started/opened it, or their account is deactivated — detected by keyword-matching deliveryError.

Second support bot relation

Reply-mode configuration lives in Bot → Support bot → Reply mode (see /docs/telegram-bot). That setting controls what the bot itself auto-sends before or regardless of a human reply (silent forwarding, an ETA acknowledgement, or nothing); this Support page is where the admin's actual human answers are typed and sent, whichever bot process is delivering them.

Setup / usage

  1. 1
    A user messages the bot (or a dedicated support bot) with a question; it becomes a thread.
  2. 2
    Click the thread in the left list to open the conversation.
  3. 3
    Type a reply and press Enter/Send, or attach a file with the paperclip icon.
  4. 4
    Threads with unread messages show a red counter badge; opening one marks it read.
  5. 5
    If a reply is flagged Queued/Undeliverable, ask the user to press Start on the relevant bot — delivery retries automatically once they do.

Mechanism

The panel itself (/login) is a simple username + password form — no MFA is visible in this codebase. It submits via api.auth.login(username, password) POST /auth/login with a JSON { username, password } body. On success the ["me"] query is invalidated and you're redirected to a safe relative ?next= path (must start with /, must not start with // or /api, preventing open-redirect / API-hijack), or to / by default.

Sessions

The _authed.* layout redirects to /login whenever there is no session, checked via hasSession() from src/lib/session. Session persistence is cookie-based, set on a successful /auth/login; logging out clears that session and returns you to /login.

2FA

No MFA in this codebase

There is no two-factor authentication flow visible in the panel's login route — it is a single-factor username/password console. Treat the admin password and server SSH access as your real security boundary.

Admin roles

This is a single-operator admin console: there is no visible user-registration or role-based access control for the web panel itself, and no self-signup flow — distinct from the many Telegram end-users managed through the bot (who do have a lightweight admin/user split, bootstrapped via adminChatId, as described in /docs/telegram-bot).

Error handling

ConditionMessage shown
401 / 403Incorrect username or password.
429Too many attempts. Wait a moment and try again. (server-side rate limiting)
503Panel is under maintenance. Try again in a minute.
>=500The panel service is not responding. Try again shortly.
Network errorCan't reach the panel. Check your connection.
OtherFalls back to the raw error message.

Setup — first admin login

  1. 1
    Navigate to /login.
  2. 2
    Enter the default/initial operator username and password provisioned during the autoscript installation.
  3. 3
    On success you land on the Dashboard (/_authed/).
  4. 4
    Immediately go to Settings → System → Change admin password (api.settings.changePassword(current, next)) to set a strong password — this is the only way to change the panel login credential in this codebase.