feat(account): VK ID web login to link a VK identity from a browser
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s

A browser has no signed VK Mini App launch params, so linking VK on the web uses
VK ID's raw OAuth 2.1 flow (PKCE, no @vkid/sdk): the SPA redirects to VK's hosted
login and returns with an authorization code, which the gateway exchanges
server-side (confidential, under the VK "Web" app's protected key) for the trusted
vk user id — then the existing link/merge machinery attaches or merges it.

- fbs LinkVKRequest{code, device_id, code_verifier}; codec + TS bindings.
- backend link.Service ConfirmVK/MergeVK/attachVK (KindVK, mirror Telegram),
  handleLinkVK[Merge], routes /user/link/vk[/merge], backendclient LinkVK[Merge].
- gateway internal/vkid confidential code exchange (id.vk.com/oauth2/auth);
  transcode link.vk.confirm/merge (registered only when configured) + config
  GATEWAY_VK_ID_{APP_ID,CLIENT_SECRET,REDIRECT_URL} + main wiring.
- UI lib/vkid (PKCE authorize redirect + callback), Profile "Link VK" control,
  boot callback handling; a merge re-authorizes for a fresh code (VK codes are
  single-use). Web-only (a redirect strands a Mini App webview).
- Deploy: VITE_VK_APP_ID + VITE_VK_ID_REDIRECT_URL build args + gateway env,
  ci.yaml/prod-deploy TEST_/PROD_ vars, compose/Dockerfile/.env.example/README.
- Tests: vkid exchange unit (string/number user_id, id_token fallback, errors),
  transcode link.vk, backend ConfirmVK/MergeVK inttest, codec encodeLinkVK.
- Docs: ARCHITECTURE §4, FUNCTIONAL(+ru), gateway README.
This commit is contained in:
Ilia Denisov
2026-07-03 17:59:33 +02:00
parent 60faa4f064
commit 2c465c01d2
36 changed files with 1131 additions and 16 deletions
+10 -2
View File
@@ -257,11 +257,19 @@ arrive from a platform rather than completing a mandatory registration).
control of the identity before attaching it: **email** through the confirm-code
flow, **Telegram** through the web **Login Widget** (validated by the validator,
HMAC under `SHA-256(bot_token)` — distinct from Mini App initData; the gateway
passes the trusted `external_id` to the backend, as for `auth.telegram`). The
passes the trusted `external_id` to the backend, as for `auth.telegram`), and **VK**
through **VK ID web login** (`gateway/internal/vkid`): the browser runs the VK ID raw
OAuth 2.1 flow with PKCE — a full-page redirect to VK's hosted login, no `@vkid/sdk`
and the gateway completes the **confidential code exchange** server-side under the VK
"Web" app's protected key (a distinct VK app from the Mini App) to obtain the trusted
`external_id`. A browser has no signed launch parameters, so unlike the VK Mini App
(offline HMAC, §12) this makes an outbound call to `id.vk.com`, and it is web-only — a
full-page redirect would strand a native Mini App webview. The
request step **always** sends/accepts the proof (no pre-send "already taken"
signal, so a probe cannot enumerate registered addresses); a required **merge**
is revealed **only after** the proof is verified and is performed behind an
explicit, irreversible confirmation. A free identity is simply attached (and a
explicit, irreversible confirmation (for VK, whose authorization code is single-use,
the merge re-authorizes for a fresh code). A free identity is simply attached (and a
guest is promoted to durable, clearing `is_guest`).
- **Unlink** detaches a platform identity (`telegram`/`vk`) from the profile. The
backend **refuses removing the last identity** (`ErrLastIdentity`), so an account
+4 -2
View File
@@ -108,8 +108,10 @@ account is kept and the guest's games move into it. A merge is blocked only whil
two accounts share a game still in progress.
The profile lists the account's **sign-in methods**. On the web a player can add
Telegram; adding VK on the web is not offered yet, and inside a Mini App the host
platform is already linked. A linked provider can be **unlinked** — except the last
Telegram (a login-widget popup) or VK (VK ID web login — a redirect to VK's sign-in and
back); inside a Mini App the host platform is already linked. Linking a provider that
already belongs to another account offers the same irreversible **merge** as email
linking. A linked provider can be **unlinked** — except the last
remaining sign-in method, which is refused so the account stays reachable. **Email is
never unlinked; it is changed**: the player enters a new address, confirms a code
mailed to it, and the account switches to it atomically, freeing the old address. A new
+4 -2
View File
@@ -112,8 +112,10 @@ Telegram держит **единого бота**: все игроки поль
запрещено, только пока у аккаунтов есть общая незавершённая игра.
В профиле перечислены **способы входа** аккаунта. В вебе игрок может добавить
Telegram; добавление VK в вебе пока не предлагается, а внутри Mini App платформа-хозяин
уже привязана. Привязанного провайдера можно **отвязать** — кроме последнего
Telegram (попап логин-виджета) или VK (веб-вход VK ID — редирект на страницу входа VK и
обратно); внутри Mini App платформа-хозяин уже привязана. Привязка провайдера, уже
принадлежащего другому аккаунту, предлагает то же необратимое **слияние**, что и привязка
email. Привязанного провайдера можно **отвязать** — кроме последнего
оставшегося способа входа: он не отвязывается, чтобы аккаунт оставался достижимым.
**Email не отвязывают — его меняют**: игрок вводит новый адрес, подтверждает код,
отправленный на него, и аккаунт атомарно переключается на новый адрес, освобождая