Stage 11: account linking & merge (email + Telegram Login Widget)
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Link an email (confirm-code) or Telegram (web Login Widget) to the current account; if the identity already has its own account, merge the two into the one in use (the current account is primary, except a guest initiator whose durable counterpart wins). The merge runs in one transaction (internal/accountmerge): stats + hint wallet summed, paid_account ORed, identities/games/chat/complaints transferred, friends/blocks de-duplicated, the secondary kept as a merged_into tombstone so a shared finished game's no-cascade FKs hold; a shared active game blocks the merge. - migration 00009: accounts.paid_account, merged_into, merged_at (+ jetgen) - internal/link orchestrator; session.RevokeAllForAccount on merge - connector ValidateLoginWidget RPC + loginwidget HMAC validator - edge ops link.email.request/confirm/merge, link.telegram.confirm/merge; supersedes the Stage 8 email.bind.* surface (request never reveals 'taken' before the code is verified, so a probe cannot enumerate addresses) - UI Profile link section + irreversible-merge dialog; Telegram web sign-in - focused regression tests (merge core, guest inversion, active-game refusal, finished-shared-game kept), gateway transcode + connector + UI codec/e2e - docs: PLAN, ARCHITECTURE 3/4/9, FUNCTIONAL(+ru), module READMEs
This commit is contained in:
+6
-2
@@ -51,8 +51,12 @@ The Stage 6 message-type slice: `auth.telegram`, `auth.guest`,
|
||||
added the play-loop ops; **Stage 8** added the social/account/history ops —
|
||||
`friends.*` (list/incoming/request/respond/cancel/unfriend/code.issue/code.redeem),
|
||||
`blocks.*`, `invitation.*` (list/create/accept/decline/cancel), `profile.update`,
|
||||
`email.bind.*`, `stats.get`, `game.gcg`, and the `notify` live event — all via the
|
||||
identical transcode pattern (`transcode_social.go`).
|
||||
`stats.get`, `game.gcg`, and the `notify` live event — all via the identical
|
||||
transcode pattern (`transcode_social.go`). **Stage 11** added account linking & merge
|
||||
— `link.email.request/confirm/merge` and `link.telegram.confirm/merge`
|
||||
(`transcode_link.go`); the telegram ops validate the **Login Widget** payload via the
|
||||
connector (`ValidateLoginWidget`) and forward the trusted `external_id`. These
|
||||
**superseded** the Stage 8 `email.bind.*` ops, which were removed.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -33,6 +33,20 @@ type ProfileResp struct {
|
||||
NotificationsInAppOnly bool `json:"notifications_in_app_only"`
|
||||
}
|
||||
|
||||
// LinkResultResp is the result of an account link/merge step (Stage 11). Status is
|
||||
// "linked", "merge_required" (the secondary_* fields summarise the other account) or
|
||||
// "merged". Token is a switched-session token (a guest initiator's durable
|
||||
// counterpart won); Profile is the surviving/active account's profile.
|
||||
type LinkResultResp struct {
|
||||
Status string `json:"status"`
|
||||
SecondaryUserID string `json:"secondary_user_id"`
|
||||
SecondaryName string `json:"secondary_display_name"`
|
||||
SecondaryGames int `json:"secondary_games"`
|
||||
SecondaryFriends int `json:"secondary_friends"`
|
||||
Token string `json:"token"`
|
||||
Profile *ProfileResp `json:"profile"`
|
||||
}
|
||||
|
||||
// TileJSON is one placed tile, used in both play requests and move responses.
|
||||
type TileJSON struct {
|
||||
Row int `json:"row"`
|
||||
|
||||
@@ -228,20 +228,47 @@ func (c *Client) UpdateProfile(ctx context.Context, userID string, p ProfileResp
|
||||
return out, err
|
||||
}
|
||||
|
||||
// EmailBindRequest asks the backend to mail a confirm-code binding email.
|
||||
func (c *Client) EmailBindRequest(ctx context.Context, userID, email string) error {
|
||||
return c.do(ctx, http.MethodPost, "/api/v1/user/email/request", userID, "",
|
||||
// LinkEmailRequest asks the backend to mail a confirm-code for a link or merge.
|
||||
func (c *Client) LinkEmailRequest(ctx context.Context, userID, email string) error {
|
||||
return c.do(ctx, http.MethodPost, "/api/v1/user/link/email/request", userID, "",
|
||||
map[string]string{"email": email}, nil)
|
||||
}
|
||||
|
||||
// EmailBindConfirm verifies the code and binds the email, returning the profile.
|
||||
func (c *Client) EmailBindConfirm(ctx context.Context, userID, email, code string) (ProfileResp, error) {
|
||||
var out ProfileResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/email/confirm", userID, "",
|
||||
// LinkEmailConfirm verifies the code and binds a free email or reports a required
|
||||
// merge (Stage 11).
|
||||
func (c *Client) LinkEmailConfirm(ctx context.Context, userID, email, code string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/email/confirm", userID, "",
|
||||
map[string]string{"email": email, "code": code}, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// LinkEmailMerge re-verifies the code and performs the merge (Stage 11).
|
||||
func (c *Client) LinkEmailMerge(ctx context.Context, userID, email, code string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/email/merge", userID, "",
|
||||
map[string]string{"email": email, "code": code}, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// LinkTelegram attaches a gateway-validated Telegram identity to the caller or
|
||||
// reports a required merge (Stage 11).
|
||||
func (c *Client) LinkTelegram(ctx context.Context, userID, externalID string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/telegram", userID, "",
|
||||
map[string]string{"external_id": externalID}, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// LinkTelegramMerge merges the account owning a gateway-validated Telegram identity
|
||||
// into the caller's (Stage 11).
|
||||
func (c *Client) LinkTelegramMerge(ctx context.Context, userID, externalID string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/telegram/merge", userID, "",
|
||||
map[string]string{"external_id": externalID}, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Stats returns the caller's lifetime statistics.
|
||||
func (c *Client) Stats(ctx context.Context, userID string) (StatsResp, error) {
|
||||
var out StatsResp
|
||||
|
||||
@@ -22,6 +22,10 @@ import (
|
||||
// result code.
|
||||
var ErrInvalidInitData = errors.New("connector: invalid telegram init data")
|
||||
|
||||
// ErrInvalidLoginWidget is returned by ValidateLoginWidget when the connector
|
||||
// rejects the Login Widget data (a gRPC InvalidArgument).
|
||||
var ErrInvalidLoginWidget = errors.New("connector: invalid telegram login widget data")
|
||||
|
||||
// User is a validated Mini App identity.
|
||||
type User struct {
|
||||
ExternalID string
|
||||
@@ -66,6 +70,24 @@ func (c *Client) ValidateInitData(ctx context.Context, initData string) (User, e
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateLoginWidget verifies Telegram Login Widget data and returns the user
|
||||
// identity, mapping a connector InvalidArgument to ErrInvalidLoginWidget. It backs
|
||||
// the link.telegram edge operation (Stage 11).
|
||||
func (c *Client) ValidateLoginWidget(ctx context.Context, data string) (User, error) {
|
||||
resp, err := c.c.ValidateLoginWidget(ctx, &telegramv1.ValidateLoginWidgetRequest{Data: data})
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.InvalidArgument {
|
||||
return User{}, ErrInvalidLoginWidget
|
||||
}
|
||||
return User{}, err
|
||||
}
|
||||
return User{
|
||||
ExternalID: resp.GetExternalId(),
|
||||
Username: resp.GetUsername(),
|
||||
FirstName: resp.GetFirstName(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Notify delivers an out-of-app notification for a push event; delivered reports
|
||||
// whether a message was actually sent.
|
||||
func (c *Client) Notify(ctx context.Context, externalID, kind string, payload []byte, language string) (bool, error) {
|
||||
|
||||
@@ -61,6 +61,40 @@ func encodeProfile(p backendclient.ProfileResp) []byte {
|
||||
return b.FinishedBytes()
|
||||
}
|
||||
|
||||
// encodeLinkResult builds a LinkResult payload (Stage 11). A switched-session token
|
||||
// (a guest initiator whose durable counterpart won) is carried as a nested Session
|
||||
// for the client to adopt; it is omitted otherwise.
|
||||
func encodeLinkResult(r backendclient.LinkResultResp) []byte {
|
||||
b := flatbuffers.NewBuilder(256)
|
||||
status := b.CreateString(r.Status)
|
||||
secID := b.CreateString(r.SecondaryUserID)
|
||||
secName := b.CreateString(r.SecondaryName)
|
||||
hasSession := r.Token != "" && r.Profile != nil
|
||||
var sess flatbuffers.UOffsetT
|
||||
if hasSession {
|
||||
token := b.CreateString(r.Token)
|
||||
uid := b.CreateString(r.Profile.UserID)
|
||||
name := b.CreateString(r.Profile.DisplayName)
|
||||
fb.SessionStart(b)
|
||||
fb.SessionAddToken(b, token)
|
||||
fb.SessionAddUserId(b, uid)
|
||||
fb.SessionAddIsGuest(b, r.Profile.IsGuest)
|
||||
fb.SessionAddDisplayName(b, name)
|
||||
sess = fb.SessionEnd(b)
|
||||
}
|
||||
fb.LinkResultStart(b)
|
||||
fb.LinkResultAddStatus(b, status)
|
||||
fb.LinkResultAddSecondaryUserId(b, secID)
|
||||
fb.LinkResultAddSecondaryDisplayName(b, secName)
|
||||
fb.LinkResultAddSecondaryGames(b, int32(r.SecondaryGames))
|
||||
fb.LinkResultAddSecondaryFriends(b, int32(r.SecondaryFriends))
|
||||
if hasSession {
|
||||
fb.LinkResultAddSession(b, sess)
|
||||
}
|
||||
b.Finish(fb.LinkResultEnd(b))
|
||||
return b.FinishedBytes()
|
||||
}
|
||||
|
||||
// encodeMoveResult builds a MoveResult payload.
|
||||
func encodeMoveResult(r backendclient.MoveResultResp) []byte {
|
||||
b := flatbuffers.NewBuilder(512)
|
||||
|
||||
@@ -63,10 +63,13 @@ type Registry struct {
|
||||
ops map[string]Op
|
||||
}
|
||||
|
||||
// TelegramValidator validates Mini App launch data via the connector side-service.
|
||||
// *connector.Client implements it; a nil value disables the telegram auth path.
|
||||
// TelegramValidator validates Telegram credentials via the connector side-service:
|
||||
// Mini App launch data (auth) and Login Widget data (linking, Stage 11).
|
||||
// *connector.Client implements it; a nil value disables the telegram auth and
|
||||
// telegram-link paths.
|
||||
type TelegramValidator interface {
|
||||
ValidateInitData(ctx context.Context, initData string) (connector.User, error)
|
||||
ValidateLoginWidget(ctx context.Context, data string) (connector.User, error)
|
||||
}
|
||||
|
||||
// NewRegistry builds the slice's message-type catalog over the backend client.
|
||||
@@ -98,6 +101,7 @@ func NewRegistry(backend *backendclient.Client, tg TelegramValidator) *Registry
|
||||
r.ops[MsgChatList] = Op{Handler: chatListHandler(backend), Auth: true}
|
||||
r.ops[MsgChatNudge] = Op{Handler: nudgeHandler(backend), Auth: true}
|
||||
registerStage8(r, backend)
|
||||
registerStage11(r, backend, tg)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -118,6 +122,9 @@ func DomainCode(err error) (string, bool) {
|
||||
if errors.Is(err, connector.ErrInvalidInitData) {
|
||||
return "invalid_init_data", true
|
||||
}
|
||||
if errors.Is(err, connector.ErrInvalidLoginWidget) {
|
||||
return "invalid_login_widget", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"scrabble/gateway/internal/backendclient"
|
||||
fb "scrabble/pkg/fbs/scrabblefb"
|
||||
)
|
||||
|
||||
// Stage 11 account linking & merge message types. The email ops carry the costly-
|
||||
// email rate flag; the telegram ops validate Login Widget data through the
|
||||
// connector (registered only when the connector is configured). All are
|
||||
// authenticated. The merge ops are the explicit irreversible step, gated in the UI
|
||||
// after a merge_required confirm.
|
||||
const (
|
||||
MsgLinkEmailRequest = "link.email.request"
|
||||
MsgLinkEmailConfirm = "link.email.confirm"
|
||||
MsgLinkEmailMerge = "link.email.merge"
|
||||
MsgLinkTelegram = "link.telegram.confirm"
|
||||
MsgLinkTelegramMerge = "link.telegram.merge"
|
||||
)
|
||||
|
||||
// registerStage11 adds the linking & merge operations. The telegram ops need the
|
||||
// connector's Login Widget validator, so they are registered only when tg is set.
|
||||
func registerStage11(r *Registry, backend *backendclient.Client, tg TelegramValidator) {
|
||||
r.ops[MsgLinkEmailRequest] = Op{Handler: linkEmailRequestHandler(backend), Auth: true, Email: true}
|
||||
r.ops[MsgLinkEmailConfirm] = Op{Handler: linkEmailConfirmHandler(backend), Auth: true, Email: true}
|
||||
r.ops[MsgLinkEmailMerge] = Op{Handler: linkEmailMergeHandler(backend), Auth: true, Email: true}
|
||||
if tg != nil {
|
||||
r.ops[MsgLinkTelegram] = Op{Handler: linkTelegramHandler(backend, tg, false), Auth: true}
|
||||
r.ops[MsgLinkTelegramMerge] = Op{Handler: linkTelegramHandler(backend, tg, true), Auth: true}
|
||||
}
|
||||
}
|
||||
|
||||
func linkEmailRequestHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkEmailRequest(req.Payload, 0)
|
||||
if err := backend.LinkEmailRequest(ctx, req.UserID, string(in.Email())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeAck(true), nil
|
||||
}
|
||||
}
|
||||
|
||||
func linkEmailConfirmHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkEmailConfirm(req.Payload, 0)
|
||||
res, err := backend.LinkEmailConfirm(ctx, req.UserID, string(in.Email()), string(in.Code()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeLinkResult(res), nil
|
||||
}
|
||||
}
|
||||
|
||||
func linkEmailMergeHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkEmailConfirm(req.Payload, 0)
|
||||
res, err := backend.LinkEmailMerge(ctx, req.UserID, string(in.Email()), string(in.Code()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeLinkResult(res), nil
|
||||
}
|
||||
}
|
||||
|
||||
// linkTelegramHandler validates Login Widget data via the connector and then calls
|
||||
// the backend's link or merge endpoint with the trusted Telegram external id.
|
||||
func linkTelegramHandler(backend *backendclient.Client, tg TelegramValidator, merge bool) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkTelegramRequest(req.Payload, 0)
|
||||
user, err := tg.ValidateLoginWidget(ctx, string(in.Data()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res backendclient.LinkResultResp
|
||||
if merge {
|
||||
res, err = backend.LinkTelegramMerge(ctx, req.UserID, user.ExternalID)
|
||||
} else {
|
||||
res, err = backend.LinkTelegram(ctx, req.UserID, user.ExternalID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeLinkResult(res), nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package transcode_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
"scrabble/gateway/internal/connector"
|
||||
"scrabble/gateway/internal/transcode"
|
||||
fb "scrabble/pkg/fbs/scrabblefb"
|
||||
)
|
||||
|
||||
func linkEmailConfirmPayload(email, code string) []byte {
|
||||
b := flatbuffers.NewBuilder(64)
|
||||
e := b.CreateString(email)
|
||||
c := b.CreateString(code)
|
||||
fb.LinkEmailConfirmStart(b)
|
||||
fb.LinkEmailConfirmAddEmail(b, e)
|
||||
fb.LinkEmailConfirmAddCode(b, c)
|
||||
b.Finish(fb.LinkEmailConfirmEnd(b))
|
||||
return b.FinishedBytes()
|
||||
}
|
||||
|
||||
func linkTelegramPayload(data string) []byte {
|
||||
b := flatbuffers.NewBuilder(64)
|
||||
d := b.CreateString(data)
|
||||
fb.LinkTelegramRequestStart(b)
|
||||
fb.LinkTelegramRequestAddData(b, d)
|
||||
b.Finish(fb.LinkTelegramRequestEnd(b))
|
||||
return b.FinishedBytes()
|
||||
}
|
||||
|
||||
func TestLinkEmailConfirmMergeRequired(t *testing.T) {
|
||||
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/user/link/email/confirm" {
|
||||
t.Errorf("path = %q", r.URL.Path)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"status":"merge_required","secondary_user_id":"b-1","secondary_display_name":"Ann","secondary_games":7,"secondary_friends":3}`))
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
reg := transcode.NewRegistry(backend, nil)
|
||||
op, ok := reg.Lookup(transcode.MsgLinkEmailConfirm)
|
||||
if !ok {
|
||||
t.Fatal("link.email.confirm not registered")
|
||||
}
|
||||
payload, err := op.Handler(context.Background(), transcode.Request{UserID: "u-1", Payload: linkEmailConfirmPayload("e@x.com", "123456")})
|
||||
if err != nil {
|
||||
t.Fatalf("handler: %v", err)
|
||||
}
|
||||
lr := fb.GetRootAsLinkResult(payload, 0)
|
||||
if string(lr.Status()) != "merge_required" || string(lr.SecondaryUserId()) != "b-1" ||
|
||||
string(lr.SecondaryDisplayName()) != "Ann" || lr.SecondaryGames() != 7 || lr.SecondaryFriends() != 3 {
|
||||
t.Fatalf("link result = %q/%q/%q/%d/%d", lr.Status(), lr.SecondaryUserId(), lr.SecondaryDisplayName(), lr.SecondaryGames(), lr.SecondaryFriends())
|
||||
}
|
||||
if lr.Session(nil) != nil {
|
||||
t.Error("a merge_required result must not carry a session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkEmailMergeSwitchesSession(t *testing.T) {
|
||||
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/user/link/email/merge" {
|
||||
t.Errorf("path = %q", r.URL.Path)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"status":"merged","token":"tok-9","profile":{"user_id":"a-1","display_name":"Kaya","is_guest":false}}`))
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
reg := transcode.NewRegistry(backend, nil)
|
||||
op, _ := reg.Lookup(transcode.MsgLinkEmailMerge)
|
||||
payload, err := op.Handler(context.Background(), transcode.Request{UserID: "u-1", Payload: linkEmailConfirmPayload("e@x.com", "123456")})
|
||||
if err != nil {
|
||||
t.Fatalf("handler: %v", err)
|
||||
}
|
||||
lr := fb.GetRootAsLinkResult(payload, 0)
|
||||
if string(lr.Status()) != "merged" {
|
||||
t.Fatalf("status = %q, want merged", lr.Status())
|
||||
}
|
||||
sess := lr.Session(nil)
|
||||
if sess == nil {
|
||||
t.Fatal("a switched merge must carry a session")
|
||||
}
|
||||
if string(sess.Token()) != "tok-9" || string(sess.UserId()) != "a-1" {
|
||||
t.Fatalf("session = %q/%q, want tok-9/a-1", sess.Token(), sess.UserId())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkTelegramValidatesAndForwards(t *testing.T) {
|
||||
var gotExternalID string
|
||||
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/user/link/telegram" {
|
||||
t.Errorf("path = %q", r.URL.Path)
|
||||
}
|
||||
var body struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||
gotExternalID = body.ExternalID
|
||||
_, _ = w.Write([]byte(`{"status":"linked"}`))
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
reg := transcode.NewRegistry(backend, fakeValidator{user: connector.User{ExternalID: "42"}})
|
||||
op, ok := reg.Lookup(transcode.MsgLinkTelegram)
|
||||
if !ok {
|
||||
t.Fatal("link.telegram.confirm not registered")
|
||||
}
|
||||
payload, err := op.Handler(context.Background(), transcode.Request{UserID: "u-1", Payload: linkTelegramPayload("id=42&hash=x")})
|
||||
if err != nil {
|
||||
t.Fatalf("handler: %v", err)
|
||||
}
|
||||
if gotExternalID != "42" {
|
||||
t.Errorf("backend external_id = %q, want 42 (the gateway-validated id)", gotExternalID)
|
||||
}
|
||||
if string(fb.GetRootAsLinkResult(payload, 0).Status()) != "linked" {
|
||||
t.Error("expected a linked result")
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ const (
|
||||
MsgInvitationDecline = "invitation.decline"
|
||||
MsgInvitationCancel = "invitation.cancel"
|
||||
MsgProfileUpdate = "profile.update"
|
||||
MsgEmailBindReq = "email.bind.request"
|
||||
MsgEmailBindConfirm = "email.bind.confirm"
|
||||
MsgStatsGet = "stats.get"
|
||||
MsgGameGCG = "game.gcg"
|
||||
)
|
||||
@@ -54,8 +52,6 @@ func registerStage8(r *Registry, backend *backendclient.Client) {
|
||||
r.ops[MsgInvitationDecline] = Op{Handler: invitationRespondHandler(backend, false), Auth: true}
|
||||
r.ops[MsgInvitationCancel] = Op{Handler: invitationCancelHandler(backend), Auth: true}
|
||||
r.ops[MsgProfileUpdate] = Op{Handler: profileUpdateHandler(backend), Auth: true}
|
||||
r.ops[MsgEmailBindReq] = Op{Handler: emailBindRequestHandler(backend), Auth: true, Email: true}
|
||||
r.ops[MsgEmailBindConfirm] = Op{Handler: emailBindConfirmHandler(backend), Auth: true, Email: true}
|
||||
r.ops[MsgStatsGet] = Op{Handler: statsHandler(backend), Auth: true}
|
||||
r.ops[MsgGameGCG] = Op{Handler: gcgHandler(backend), Auth: true}
|
||||
}
|
||||
@@ -250,27 +246,6 @@ func profileUpdateHandler(backend *backendclient.Client) Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func emailBindRequestHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsEmailBindRequest(req.Payload, 0)
|
||||
if err := backend.EmailBindRequest(ctx, req.UserID, string(in.Email())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeAck(true), nil
|
||||
}
|
||||
}
|
||||
|
||||
func emailBindConfirmHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsEmailConfirmRequest(req.Payload, 0)
|
||||
out, err := backend.EmailBindConfirm(ctx, req.UserID, string(in.Email()), string(in.Code()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeProfile(out), nil
|
||||
}
|
||||
}
|
||||
|
||||
func statsHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
res, err := backend.Stats(ctx, req.UserID)
|
||||
|
||||
@@ -23,6 +23,10 @@ func (f fakeValidator) ValidateInitData(context.Context, string) (connector.User
|
||||
return f.user, f.err
|
||||
}
|
||||
|
||||
func (f fakeValidator) ValidateLoginWidget(context.Context, string) (connector.User, error) {
|
||||
return f.user, f.err
|
||||
}
|
||||
|
||||
func telegramLoginPayload(initData string) []byte {
|
||||
b := flatbuffers.NewBuilder(0)
|
||||
off := b.CreateString(initData)
|
||||
|
||||
Reference in New Issue
Block a user