admin: allow shorter operator-assigned usernames, add reference docs

Lower the username floor to 3 characters on the admin-only write path
(account/channel set-username), separate from the 5-character self-service
minimum, since a deliberately short operator handle isn't the squatting the
higher floor guards against. Channel admin username assignment previously
had no length/format validation at all; this adds it.

Also adds three reference docs: the client's deep-link (tg://, owpg://)
surface, an OpenAPI 3.1 spec for both admin HTTP APIs (built from source,
not the stale docs/admin-panel-api.en.md), and a starter reserved-username
blocklist.
This commit is contained in:
Astra 2026-09-17 15:24:31 +01:00
parent 6af4576fed
commit c39011e542
5 changed files with 3861 additions and 5 deletions

119
docs/deep-links.md Normal file
View file

@ -0,0 +1,119 @@
# Client deep links
The desktop client (`owpengram-desktop-client`, a Telegram Desktop fork) resolves
`tg://<path>` URIs and `https://t.me/<path>` links through the same handler table:
`Telegram/SourceFiles/core/local_url_handlers.cpp`. Both schemes are normalized to the
same `<path>?<query>` form before dispatch, so every entry below works as either
`tg://join?invite=...` or `t.me/join/...` (the client also accepts the shorter
`t.me/+...` / `t.me/joinchat/...` aliases, which get rewritten to `join?invite=`
before reaching this table).
Patterns are matched in order top to bottom; the first regex match wins.
## Path handlers
| Path | Handler | Purpose |
|---|---|---|
| `join?invite=<hash>` | `JoinGroupByHash` | Join a chat/channel via invite link hash. |
| `addlist?slug=<slug>` | `JoinFilterBySlug` | Import a shareable chat folder. |
| `addstickers?set=<name>` / `addemoji?set=<name>` | `ShowStickerSet` | Open a sticker or custom-emoji set for install. |
| `addtheme?slug=<slug>` | `ShowTheme` | Open a shared theme for install. |
| `addstyle?slug=<slug>` | `ShowAiStyle` | Open a shared AI compose style. |
| `setlanguage?lang=<code>` | `SetLanguage` | Switch/install the app's interface language. |
| `msg_url?<params>` | `ShareUrl` | Open the "share to chat" composer for an external URL. |
| `confirmphone?<params>` | `ConfirmPhone` | Confirm a phone number transfer/login code sent by SMS. |
| `socks?<params>` | `ApplySocksProxy` | Add/apply a SOCKS5 proxy config. |
| `proxy?<params>` | `ApplyMtprotoProxy` | Add/apply an MTProto proxy config. |
| `webproxy?<params>` | `ApplyWebProxy` | Add/apply a web proxy config. |
| `passport?<params>` | `ShowPassport` | Telegram Passport authorization request. |
| `bg?<params>` | `ShowWallPaper` | Open a shared chat wallpaper for install. |
| `resolve?<params>` | `ResolveUsernameOrPhone` | Resolve `@username`/phone to a peer; see [resolve parameters](#resolve-parameters) below. This is the most common deep link kind. |
| `privatepost?<params>` | `ResolvePrivatePost` | Open a specific message inside a private channel by internal channel id + message id. |
| `test_chat_theme?<params>` | `ResolveTestChatTheme` | Preview a chat theme without installing it. |
| `invoice?<params>` | `ResolveInvoice` | Open a payment invoice (Telegram Payments / Stars). |
| `premium_offer[?<params>]` | `ResolvePremiumOffer` | Open the Premium subscription offer screen. |
| `premium_multigift?<params>` | `ResolvePremiumMultigift` | Open the bulk/multi-recipient Premium gifting screen. |
| `login?code=<n>` | `ResolveLoginCode` | Apply a login confirmation code (QR/code login flow). |
| `boost?<params>` | `ResolveBoost` | Open the "boost this channel" flow. |
| `message?slug=<slug>` | `ResolveChatLink` | Open a pre-composed shareable chat/business link. |
| `stars_topup?<params>` | `ResolveTopUp` | Open the Telegram Stars top-up screen. |
| `nft?slug=<slug>` | `ResolveUniqueGift` | Open a collectible/unique gift by slug. |
| `stargift_auction?slug=<slug>` | `ResolveGiftAuction` | Open a Star Gift auction listing. |
| `call?slug=<slug>` | `ResolveConferenceCall` | Join a group/conference call by invite slug. |
| `stars[?<params>]` | `ResolveStarsSettings` | Open Telegram Stars balance/settings. |
| `ton[?<params>]` / `grams[?<params>]` | `ResolveTonSettings` | Open TON wallet settings. |
| `oauth?<params>` | `ResolveOAuth` | Third-party OAuth authorization via Telegram. |
| *(anything else)* | `HandleUnknown` | Fallback — shown as an unrecognized-link message. |
## Resolve parameters
`resolve?domain=<username>` (or the bare `t.me/<username>` shorthand, which the
client rewrites to this form) accepts additional query parameters that change what
opens once the peer is resolved:
| Parameter | Effect |
|---|---|
| `domain` | The `@username` to resolve (required unless using the bare `t.me/<username>` form). |
| `phone` | Resolve by phone number instead of username. |
| `appname` | Open a Telegram Mini App by name instead of the peer's chat. |
| `startapp` | Start-parameter passed into a Mini App launch. |
| `attach` | Open the target's attach-menu bot alongside the chat. |
| `start` | Start-parameter for a bot (`/start <token>`). |
| `startgroup` | Add a bot to a group with this start token (bot-in-group flow). |
| `startchannel` | Add a bot to a channel with this start token. |
| `post` | Jump to a specific message id in the resolved peer. |
| `story` | Open a specific story id from the resolved peer. |
| `comment` | Jump to a specific comment/reply message id (discussion groups). |
| `topic` | Open a specific forum topic id. |
| `thread` | Open a specific reply thread/topic id (older alias of `topic`). |
| `game` | Launch a specific inline game short name from a bot. |
| `text` | Pre-fill the message composer with this text. |
| `voicechat` / `videochat` / `livestream` | Join the peer's active voice chat / video chat / live stream by hash. |
## Internal-only links
`InternalUrlHandlers()` in the same file (`internal:<path>`) covers links the app
generates for its own in-app UI (e.g. `internal:edit_peer/<id>`,
`internal:common_groups/<id>`, `internal:collectible_username/<name>@<id>`). These
are not meant to be shared externally and are not part of the public deep-link
surface — they only work as links the client itself produces inside its own UI.
## OwpenGram (`owpg://`) links
This fork adds a second scheme, handled in `Telegram/SourceFiles/core/application.cpp`
(`Application::openLocalUrl` / `Application::openOwpengramUrl`), for operating across
multiple OwpenGram-compatible backends rather than only the official Telegram DCs.
| Form | Purpose |
|---|---|
| `owpg://addserver?host=<host>&port=<port>` | Opens `AddServerBox` pre-filled with just the address. **Deliberately carries nothing else** — no name, description, RSA key, or DC id. |
| `owpg://oauth?<params>` / `owpg://resolve?<params>` | Rewritten to the equivalent `tg://oauth` / `tg://resolve` command and dispatched on the current session — lets those two flows work on an OwpenGram account, not just official Telegram. |
| `owpg://<host>/<rest>` | Routes a normal `t.me`-style path (`<rest>` = `+<invite>`, `<username>`, etc.) to whichever logged-in account is on that server's `<host>`, bypassing the official-Telegram-only guard. If no account on the device is logged into `<host>`, the client shows an inform box naming the server instead of silently failing. |
### Why `addserver` only takes host+port
The RSA public key, main DC id, display name, and description are **never** accepted
from the link itself — see the comment above `kOwpgAddServerRe` in `application.cpp`.
Letting a link author supply the key/identity directly would be a MITM vector: whoever
crafts the link would control what the victim ends up trusting as that server's
identity, regardless of who actually operates the host. Only host+port travel in the
link; everything else is fetched live and is exactly as trustworthy as the TCP
connection to that host:port already is.
Once `AddServerBox` has host+port, it calls `Owpengram::FetchServerInfo(host, port, …)`,
which does a plain HTTP `GET` against the server's own well-known endpoints on the
same port:
| Endpoint | Returns |
|---|---|
| `GET <host>:<port>/owpengram/server-info` | RSA public key (PEM), main DC id, and optional operator-set name/description/icon-presence flag. |
| `GET <host>:<port>/owpengram/server-icon` | Raw icon bytes, only fetched when `server-info` reported `hasIcon: true`. |
`RefreshServersInfo()` re-polls `server-info`/`server-icon` for every already-added
custom server to keep a cosmetic name/icon change in sync — it never touches host,
port, RSA key, DC id, or the server's local id, for the same MITM reason as above (see
the doc comment on `RefreshServersInfo` in `owpengram_servers.h`).
This also means the RSA-key-in-a-URL scheme discussed earlier in this doc's history
is intentionally **not** how this client adds a server — that was a plausible-looking
design that this project already considered and rejected in favor of fetch-after-add.