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:
Ilia Denisov
2026-07-03 09:47:42 +02:00
parent a3eb4719de
commit b918217497
11 changed files with 419 additions and 10 deletions
+1
View File
@@ -51,6 +51,7 @@ export { LinkEmailConfirm } from './scrabblefb/link-email-confirm.js';
export { LinkEmailRequest } from './scrabblefb/link-email-request.js';
export { LinkResult } from './scrabblefb/link-result.js';
export { LinkTelegramRequest } from './scrabblefb/link-telegram-request.js';
export { LinkUnlinkRequest } from './scrabblefb/link-unlink-request.js';
export { MatchFoundEvent } from './scrabblefb/match-found-event.js';
export { MatchResult } from './scrabblefb/match-result.js';
export { MoveRecord } from './scrabblefb/move-record.js';
@@ -0,0 +1,48 @@
// automatically generated by the FlatBuffers compiler, do not modify
import * as flatbuffers from 'flatbuffers';
export class LinkUnlinkRequest {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):LinkUnlinkRequest {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsLinkUnlinkRequest(bb:flatbuffers.ByteBuffer, obj?:LinkUnlinkRequest):LinkUnlinkRequest {
return (obj || new LinkUnlinkRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsLinkUnlinkRequest(bb:flatbuffers.ByteBuffer, obj?:LinkUnlinkRequest):LinkUnlinkRequest {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new LinkUnlinkRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
kind():string|null
kind(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
kind(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startLinkUnlinkRequest(builder:flatbuffers.Builder) {
builder.startObject(1);
}
static addKind(builder:flatbuffers.Builder, kindOffset:flatbuffers.Offset) {
builder.addFieldOffset(0, kindOffset, 0);
}
static endLinkUnlinkRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createLinkUnlinkRequest(builder:flatbuffers.Builder, kindOffset:flatbuffers.Offset):flatbuffers.Offset {
LinkUnlinkRequest.startLinkUnlinkRequest(builder);
LinkUnlinkRequest.addKind(builder, kindOffset);
return LinkUnlinkRequest.endLinkUnlinkRequest(builder);
}
}