feat(account): unlink provider + change-email edges (backend + gateway)
Unlink: POST /user/link/unlink (telegram|vk) via account.RemoveIdentity, refusing
the last identity; email is never unlinked. New fbs LinkUnlinkRequest + gateway
link.unlink op, returning the refreshed profile.
Change-email: purposeChange confirm-codes (RequestChangeCode/ConfirmChange) that
atomically replace the account's confirmed email (account.replaceEmailIdentity);
a new address owned by another account is refused without disclosure, never merged.
The one-tap deeplink handles purposeChange too. Reuses the LinkEmail* fbs tables;
gateway link.email.change.{request,confirm} ops + backendclient methods; branded
ru/en change-email copy.
This commit is contained in:
@@ -335,6 +335,31 @@ func (c *Client) LinkTelegramMerge(ctx context.Context, userID, externalID strin
|
||||
return out, err
|
||||
}
|
||||
|
||||
// ChangeEmailRequest asks the backend to mail a confirm-code to a new address for an
|
||||
// authenticated email change.
|
||||
func (c *Client) ChangeEmailRequest(ctx context.Context, userID, email string) error {
|
||||
return c.do(ctx, http.MethodPost, "/api/v1/user/link/email/change/request", userID, "",
|
||||
map[string]string{"email": email}, nil)
|
||||
}
|
||||
|
||||
// ChangeEmailConfirm verifies the code and atomically switches the account's email,
|
||||
// returning the refreshed profile in the result.
|
||||
func (c *Client) ChangeEmailConfirm(ctx context.Context, userID, email, code string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/email/change/confirm", userID, "",
|
||||
map[string]string{"email": email, "code": code}, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// LinkUnlink detaches a platform identity (kind = "telegram" | "vk") from the caller
|
||||
// and returns the refreshed profile in the result.
|
||||
func (c *Client) LinkUnlink(ctx context.Context, userID, kind string) (LinkResultResp, error) {
|
||||
var out LinkResultResp
|
||||
err := c.do(ctx, http.MethodPost, "/api/v1/user/link/unlink", userID, "",
|
||||
map[string]string{"kind": kind}, &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
|
||||
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
// 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"
|
||||
MsgLinkEmailRequest = "link.email.request"
|
||||
MsgLinkEmailConfirm = "link.email.confirm"
|
||||
MsgLinkEmailMerge = "link.email.merge"
|
||||
MsgLinkTelegram = "link.telegram.confirm"
|
||||
MsgLinkTelegramMerge = "link.telegram.merge"
|
||||
MsgLinkUnlink = "link.unlink"
|
||||
MsgEmailChangeRequest = "link.email.change.request"
|
||||
MsgEmailChangeConfirm = "link.email.change.confirm"
|
||||
)
|
||||
|
||||
// registerLinkOps adds the linking & merge operations. The telegram ops need the
|
||||
@@ -26,6 +29,9 @@ func registerLinkOps(r *Registry, backend *backendclient.Client, tg TelegramVali
|
||||
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}
|
||||
r.ops[MsgLinkUnlink] = Op{Handler: linkUnlinkHandler(backend), Auth: true}
|
||||
r.ops[MsgEmailChangeRequest] = Op{Handler: changeEmailRequestHandler(backend), Auth: true, Email: true}
|
||||
r.ops[MsgEmailChangeConfirm] = Op{Handler: changeEmailConfirmHandler(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}
|
||||
@@ -64,6 +70,43 @@ func linkEmailMergeHandler(backend *backendclient.Client) Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// changeEmailRequestHandler mails a confirm-code to a new address for an email change.
|
||||
func changeEmailRequestHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkEmailRequest(req.Payload, 0)
|
||||
if err := backend.ChangeEmailRequest(ctx, req.UserID, string(in.Email())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeAck(true), nil
|
||||
}
|
||||
}
|
||||
|
||||
// changeEmailConfirmHandler verifies the code and switches the account's email, returning
|
||||
// the refreshed link result (status "changed" + the updated profile).
|
||||
func changeEmailConfirmHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkEmailConfirm(req.Payload, 0)
|
||||
res, err := backend.ChangeEmailConfirm(ctx, req.UserID, string(in.Email()), string(in.Code()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodeLinkResult(res), nil
|
||||
}
|
||||
}
|
||||
|
||||
// linkUnlinkHandler detaches a platform identity (telegram|vk) from the caller and
|
||||
// returns the refreshed link result (status "unlinked" + the updated profile).
|
||||
func linkUnlinkHandler(backend *backendclient.Client) Handler {
|
||||
return func(ctx context.Context, req Request) ([]byte, error) {
|
||||
in := fb.GetRootAsLinkUnlinkRequest(req.Payload, 0)
|
||||
res, err := backend.LinkUnlink(ctx, req.UserID, string(in.Kind()))
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user