6aeb529f13
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 53s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Failing after 2m6s
Move all Telegram egress off the main host. The single connector held the bot token, long-polled Telegram and answered the gateway/backend over the trusted internal network, so the whole component (including login validation) shared fate with its VPN sidecar. Split it into two binaries that share the token: - cmd/validator (home, no VPN): Mini App initData + Login Widget HMAC only, never calls the Bot API. The gateway dials it for Telegram auth, so game login is now independent of Telegram reachability. - cmd/bot (remote): Bot API long-poll + sendMessage, the only component reaching Telegram. It holds no inbound port — it dials the gateway over a new reverse mTLS bot-link (pkg/proto/botlink/v1) and executes the send commands the gateway pushes. The gateway funnels sends to the bot-link: out-of-app push is fire-and-forget (at-most-once, dropped if no bot is connected); the backend admin broadcasts reach a gateway-served relay that forwards them and awaits the bot's ack (SendToUser/SendToGameChannel contract preserved). mTLS (pkg/mtls) is the one inter-service link that leaves the trusted segment; validator<->gateway and the relay stay plaintext internal. The bot is Telegram-rate-limited. One bot now; the gateway bot registry, an owns_updates flag and per-command ids leave seams for N later. Webhook rejected (one URL per token, adds inbound + a static address). The unified test contour runs the split (the bot keeps its VPN sidecar and dials the gateway by its internal name; bot-link certs from deploy/gen-certs.sh, generated in CI). The prod wiring — the bot on a separate host (no VPN), the gateway bot-link port published, PROD_ certs with scheduled rotation, an SSH deploy of both hosts together — is the deferred final stage (PRERELEASE.md TX, Stage 18). Docs: ARCHITECTURE, PRERELEASE (phase TX), platform/telegram + gateway + backend + deploy READMEs, FUNCTIONAL(+ru), CLAUDE.md, .env.example.
62 lines
2.4 KiB
Go
62 lines
2.4 KiB
Go
// Package validator implements the home-side Telegram validator gRPC service: it
|
|
// verifies Mini App initData and Login Widget data with the bot token's HMAC and
|
|
// never calls the Bot API. The gateway calls it during the auth.telegram and
|
|
// link.telegram edge operations. It holds the token only as the HMAC secret, so it
|
|
// can run on the main host with no VPN and no Telegram egress (ARCHITECTURE.md §12).
|
|
package validator
|
|
|
|
import (
|
|
"context"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
telegramv1 "scrabble/pkg/proto/telegram/v1"
|
|
"scrabble/platform/telegram/internal/initdata"
|
|
"scrabble/platform/telegram/internal/loginwidget"
|
|
)
|
|
|
|
// Server implements the validation subset of telegramv1.TelegramServer; the
|
|
// delivery methods (Notify, SendToUser, SendToGameChannel) are intentionally
|
|
// unimplemented here — those run on the remote bot over the bot-link.
|
|
type Server struct {
|
|
telegramv1.UnimplementedTelegramServer
|
|
initValidator initdata.Validator
|
|
widgetValidator loginwidget.Validator
|
|
}
|
|
|
|
// NewServer builds the validator from the two HMAC validators bound to the token.
|
|
func NewServer(iv initdata.Validator, wv loginwidget.Validator) *Server {
|
|
return &Server{initValidator: iv, widgetValidator: wv}
|
|
}
|
|
|
|
// ValidateInitData verifies Mini App launch data against the bot's token and
|
|
// returns the user identity.
|
|
func (s *Server) ValidateInitData(_ context.Context, req *telegramv1.ValidateInitDataRequest) (*telegramv1.ValidateInitDataResponse, error) {
|
|
u, err := s.initValidator.Validate(req.GetInitData())
|
|
if err != nil {
|
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
|
}
|
|
return &telegramv1.ValidateInitDataResponse{
|
|
ExternalId: u.ExternalID,
|
|
Username: u.Username,
|
|
FirstName: u.FirstName,
|
|
LanguageCode: u.LanguageCode,
|
|
}, nil
|
|
}
|
|
|
|
// ValidateLoginWidget verifies Login Widget authorization data against the bot's
|
|
// token and returns the user identity, for attaching a Telegram identity to an
|
|
// existing account.
|
|
func (s *Server) ValidateLoginWidget(_ context.Context, req *telegramv1.ValidateLoginWidgetRequest) (*telegramv1.ValidateLoginWidgetResponse, error) {
|
|
u, err := s.widgetValidator.Validate(req.GetData())
|
|
if err != nil {
|
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
|
}
|
|
return &telegramv1.ValidateLoginWidgetResponse{
|
|
ExternalId: u.ExternalID,
|
|
Username: u.Username,
|
|
FirstName: u.FirstName,
|
|
}, nil
|
|
}
|