phase 4: connectrpc on the gateway authenticated edge

Replace the native-gRPC server bootstrap with a single
`connectrpc.com/connect` HTTP/h2c listener. Connect-Go natively
serves Connect, gRPC, and gRPC-Web on the same port, so browsers can
now reach the authenticated surface without giving up the gRPC
framing native and desktop clients may use later. The decorator
stack (envelope → session → payload-hash → signature →
freshness/replay → rate-limit → routing/push) is reused unchanged
behind a small Connect → gRPC adapter and a `grpc.ServerStream`
shim around `*connect.ServerStream`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 11:49:28 +02:00
parent 39b7b2ef29
commit 118f7c17a2
30 changed files with 1009 additions and 772 deletions
+9
View File
@@ -531,6 +531,15 @@ This section describes the secure exchange model between client and
gateway. It applies at the public boundary and does not rely on backend
behaviour for any of its guarantees.
The authenticated edge listener is built on `connectrpc.com/connect` and
natively serves the Connect, gRPC, and gRPC-Web protocols on a single
HTTP/2 cleartext (`h2c`) port. Browser clients use Connect via
`@connectrpc/connect-web`; native iOS / Android / desktop clients can
use either Connect or raw gRPC framing against the same listener.
Envelope, signature, freshness, and anti-replay rules below are
protocol-agnostic — they apply identically to every supported wire
framing.
### Principles
- No browser cookies.
+10 -8
View File
@@ -139,9 +139,10 @@ consumed exactly once.
### 1.4 Per-request session lookup
Once the client holds a device session id and a private key, every
authenticated call is a signed gRPC request to gateway. Gateway is the
only component that ever sees the request signature; backend trusts
gateway's verdict.
authenticated call is a signed request to gateway over the
authenticated edge listener (Connect / gRPC / gRPC-Web on a single
HTTP/h2c port). Gateway is the only component that ever sees the
request signature; backend trusts gateway's verdict.
Gateway needs the session's public key to verify the signature, so each
authenticated request resolves the device session through an in-memory
@@ -602,8 +603,8 @@ not duplicated here.
### 6.2 Backend's role: pass-through with authorisation
The signed-gRPC pipeline for in-game traffic uses three message types
on the authenticated surface — `user.games.command`,
The signed authenticated-edge pipeline for in-game traffic uses three
message types on the authenticated surface — `user.games.command`,
`user.games.order`, `user.games.report` — each with a typed
FlatBuffers payload. Gateway transcodes the FB request into the JSON
shape backend expects, forwards over plain REST to the corresponding
@@ -680,9 +681,10 @@ session invalidations).
### 7.1 Scope
In scope: the gRPC stream a client opens against gateway, the
bootstrap event, the framing of forwarded events, and the
backend → gateway control channel that produces those events.
In scope: the server-streaming subscription a client opens against
gateway (Connect / gRPC / gRPC-Web framing all map to the same
endpoint), the bootstrap event, the framing of forwarded events, and
the backend → gateway control channel that produces those events.
Out of scope: the catalog of event kinds — see [Section 8](#8-notifications-and-mail) for the
notification side and [`backend/README.md` §10](../backend/README.md#10-notification-catalog) for the closed list.
+12 -10
View File
@@ -138,9 +138,10 @@ Throttle-переиспользование на стороне send означ
### 1.4 Поиск сессии для каждого запроса
Когда у клиента есть идентификатор устройства-сессии и приватный ключ,
каждый аутентифицированный вызов — это подписанный gRPC-запрос к
gateway. Gateway — единственный компонент, который видит подпись
запроса; backend доверяет вердикту gateway.
каждый аутентифицированный вызов — это подписанный запрос к gateway
по аутентифицированному edge-листенеру (Connect / gRPC / gRPC-Web на
одном HTTP/h2c-порту). Gateway — единственный компонент, который видит
подпись запроса; backend доверяет вердикту gateway.
Gateway нужен публичный ключ сессии для проверки подписи, поэтому
каждый аутентифицированный запрос разрешает устройство-сессию через
@@ -618,10 +619,10 @@ Wire-формат команд, приказов и отчётов — собс
### 6.2 Роль backend: pass-through с авторизацией
Signed-gRPC-конвейер для in-game-трафика использует три message
types на аутентифицированной поверхности — `user.games.command`,
`user.games.order`, `user.games.report` у каждого типизированный
FlatBuffers-payload. Gateway транскодирует FB-запрос в JSON-форму,
Подписанный конвейер аутентифицированного edge для in-game-трафика
использует три message types на аутентифицированной поверхности —
`user.games.command`, `user.games.order`, `user.games.report`
у каждого типизированный FlatBuffers-payload. Gateway транскодирует FB-запрос в JSON-форму,
которую ждёт backend, форвардит её REST'ом в соответствующий
`/api/v1/user/games/{game_id}/*` endpoint, после чего транскодирует
JSON-ответ обратно в FB перед подписью.
@@ -697,9 +698,10 @@ notification-каталог явно их опускает
### 7.1 Состав
В составе: gRPC-стрим, который клиент открывает к gateway,
bootstrap-событие, фрейминг форварднутых событий, control-канал
backend → gateway, который производит эти события.
В составе: server-streaming-подписка, которую клиент открывает к
gateway (Connect / gRPC / gRPC-Web фреймы все маршрутизируются на
одну точку), bootstrap-событие, фрейминг форварднутых событий,
control-канал backend → gateway, который производит эти события.
Вне состава: каталог видов событий — см.
[Раздел 8](#8-уведомления-и-почта) для notification-стороны и