Skip to content

feat: add ability to users to manage their vpn connections via Telegram and web portal - #92

Open
vikbaranov wants to merge 11 commits into
PRVTPRO:mainfrom
vikbaranov:feature/self-service-upstream
Open

feat: add ability to users to manage their vpn connections via Telegram and web portal#92
vikbaranov wants to merge 11 commits into
PRVTPRO:mainfrom
vikbaranov:feature/self-service-upstream

Conversation

@vikbaranov

Copy link
Copy Markdown

Core service

  • ConnectionService — orchestrates connection creation/deletion with per-user locks, rate limiting (sliding window), and ownership validation
  • SelfServiceError / RateLimitError — typed exceptions with HTTP status codes
  • Default settings: max 5 connections/user, 3 requests per 60s, protocols awg/awg2

Telegram bot

  • /connect command — interactive flow: select server → protocol → confirm → deploy
  • /disconnect command — list owned connections → confirm → delete
  • Full i18n via TG_TRANSLATIONS dict (~50 keys) with _tt(lang, key) for every message, button, and keyboard
  • lang propagation through _dispatch and all handler/keyboard builder functions
  • Rate limiting per user, connection ownership checks, inline keyboard confirmations

API

  • GET /api/my/connections/options — available servers, protocols, remaining quota
  • POST /api/my/connections/add — create a connection (server_id, protocol, name)
  • POST /api/my/connections/{id}/delete — delete an owned connection
  • GET /api/my/connections — list user's connections (extended with self-service metadata)

Web UI

  • Add my_connections.html page

Settings

  • Self-service toggle in global settings (web + telegram channels)
  • Per-server self_service_enabled flag in server edit form
  • Settings model: enabled, web_enabled, telegram_enabled, max_connections_per_user, rate_limit_count, rate_limit_window_seconds, allowed_protocols

Translations

  • Added self-service keys to all 5 languages: EN, RU, FR, FA, ZH

@helldweller helldweller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review — self-service connections

Reviewed the whole branch against main with a focus on what a regular (non-admin) user can reach, and specifically on whether any of this can be escalated into control of a VPN server through the Telegram bot.

The self-service core itself holds up well. ConnectionService validates ownership, intersects the protocol against a hard-coded {awg, awg2} allow-list twice, re-validates inside the lock (so the quota/status TOCTOU is closed), rolls the remote peer back when save_data fails, restricts deletion to created_by == 'self_service', and moves the blocking SSH work into asyncio.to_thread — better than the existing admin path, which blocks the event loop. The feature is off by default both globally and per server. The connection name reaches the server as json.dumps over SFTP (managers/awg_manager.py:804), never through a shell, so there is no command injection there. Test coverage is genuinely good.

The findings below are ordered by severity; details are in the inline comments.

Blockers, in my view

  1. _find_user() now resolves users by Telegram @username (telegram_bot.py:409). This is the one item that is not really about self-service. A username is mutable, released on rename/deletion, and re-claimable by anyone. _require_admin() uses the same lookup, so an admin whose telegramId field holds a handle rather than a numeric ID can be taken over by whoever claims that handle — which yields /addserver, every client config on every server (PrivateKey included), and docker start/stop of protocol containers. Recommendation: match on the numeric ID only. Users can obtain their own ID in one message via @userinfobot, so the onboarding cost is negligible.

  2. Stored XSS via the connection name. templates/users.html:863 interpolates c.name into innerHTML unescaped. That file is untouched here, but this PR is what makes the field user-controlled, so it becomes a user → admin-session escalation path. The new client-side rendering in templates/my_connections.html:203-224 also regresses from Jinja auto-escaping to partial manual escaping.

  3. The panel-wide DATA_LOCK is held across the whole SSH provisioning run (connection_service.py:97). Any user can stall every write in the panel for minutes by targeting a slow or unreachable server.

Worth fixing, but could be a follow-up

  1. Rate limiting counts only successful creations, so failed attempts — the ones that actually hammer the VPN node over SSH — are unlimited; deletion is not limited at all.
  2. Raw exception text is returned to API clients and Telegram users.
  3. The bot never checks chat["type"], so /connect in a group posts a config containing PrivateKey into the group.
  4. Fail-open when expiration_date cannot be parsed.

Two notes that are not inline

  • Stale-write race. save_data() rewrites the entire data.json, and several admin handlers (api_delete_server at app.py:2366, api_edit_server) write without taking DATA_LOCK. create_user_connection reads data, spends up to a couple of minutes on SSH, then writes the whole blob back — clobbering anything an admin changed meanwhile, including "disable user" or "delete server" (which would resurrect the entry together with its stored SSH credentials). A long user-triggered read → SSH → write-whole-blob cycle did not exist before this PR. Worth at least re-reading and merging just before the write. Separately, save_data writes in place with no tmp+rename, so a crash mid-write truncates the file that holds every server's SSH password and private key.

  • Blast radius of the feature itself. A self-service peer gets network access into the VPN subnet. If the panel or its admin UI is reachable from there, any linked user can issue themselves a route to it. Probably worth stating explicitly in the docs and/or constraining via AllowedIPs/firewall.

Happy to look again once the identity question in (1) is settled — that one changes the threat model for everything else here.

Comment thread telegram_bot.py Outdated
Comment thread connection_service.py Outdated
Comment thread connection_service.py Outdated
Comment thread connection_service.py
Comment thread connection_service.py
Comment thread app.py Outdated
Comment thread telegram_bot.py Outdated
Comment thread telegram_bot.py Outdated
Comment thread telegram_bot.py
Comment thread app.py Outdated
@PRVTPRO

PRVTPRO commented Aug 26, 2026

Copy link
Copy Markdown
Owner

We should consider potential improvements to the current implementation. I also came across this project, which looks relevant and may be worth reviewing as a reference:

https://github.com/v3new/amnezia-prvtpro-miniapp

It could be useful to compare its approach with our current implementation and identify any ideas, architectural improvements, or features that we could adopt.

@vikbaranov

Copy link
Copy Markdown
Author

Thank you for the detailed review process. I'll carefully study all issues and fix them soon
I agree that Telegram mini-app is a more convenient way for self-management. But we should also consider a case when the user doesn't have access to Telegram and there is only one way to issue VPN config through the web portal

@helldweller

Copy link
Copy Markdown
Contributor

We should consider potential improvements to the current implementation. I also came across this project, which looks relevant and may be worth reviewing as a reference:

https://github.com/v3new/amnezia-prvtpro-miniapp

It could be useful to compare its approach with our current implementation and identify any ideas, architectural improvements, or features that we could adopt.

Some ai research
(en) https://claude.ai/code/artifact/592aa6de-bd1c-42f2-abfd-e22262126312?via=auto_preview
(ru) https://claude.ai/code/artifact/b925c5ba-79e3-4148-b7a6-10c935d59814?via=auto_preview

@vikbaranov

Copy link
Copy Markdown
Author

@helldweller Good day!
I have resolved all issues. Could you review my pr again?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants