fix(admin): close PR review blockers
Keep bot credentials out of durable command results, fail bot deletion closed when session revocation fails, reject invalid scam/fake states at every write boundary, and make direct collectible grants a single replayable PostgreSQL aggregate. Also lock admin gift sender/message limits and add regression coverage for rollback, replay, moderation constraints, and credential redaction.
This commit is contained in:
parent
90792cdfab
commit
234061ef83
30 changed files with 859 additions and 93 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
|
|
@ -46,6 +47,296 @@ func NewStarGiftUpgradeStore(db sqlcgen.DBTX, messages *MessageStore, opts ...St
|
|||
return s
|
||||
}
|
||||
|
||||
// GrantUniqueStarGift atomically assigns a newly minted collectible from the
|
||||
// official system account. The saved gift, unique issuance, service message,
|
||||
// pts/outbox and immutable command receipt share MessageStore's transaction.
|
||||
func (s *StarGiftUpgradeStore) GrantUniqueStarGift(ctx context.Context, req domain.AdminStarGiftGrant) (domain.AdminStarGiftGrantResult, error) {
|
||||
req.CommandKey = strings.TrimSpace(req.CommandKey)
|
||||
req.Message = strings.TrimSpace(req.Message)
|
||||
if s == nil || s.db == nil || s.messages == nil || req.SenderID != domain.OfficialSystemUserID ||
|
||||
req.Recipient.Type != domain.PeerTypeUser || req.Recipient.ID <= 0 || req.GiftID <= 0 || !req.Upgrade ||
|
||||
req.CommandKey == "" || len(req.CommandKey) > 256 || req.Date <= 0 || len([]rune(req.Message)) > 128 ||
|
||||
req.ModelAttributeID < 0 || req.PatternAttributeID < 0 || req.BackdropAttributeID < 0 {
|
||||
return domain.AdminStarGiftGrantResult{}, domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
fingerprint := adminStarGiftGrantFingerprint(req)
|
||||
if replay, found, err := s.loadAdminStarGiftGrantReplay(ctx, req, fingerprint, domain.SendPrivateTextResult{}); err != nil || found {
|
||||
return replay, err
|
||||
}
|
||||
|
||||
placeholder := &domain.MessageMedia{Kind: domain.MessageMediaKindService, ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGiftUnique,
|
||||
StarGiftUnique: &domain.MessageStarGiftUniqueAction{
|
||||
Assigned: true,
|
||||
Saved: true,
|
||||
},
|
||||
}}
|
||||
messageReq := domain.SendPrivateTextRequest{
|
||||
SenderUserID: req.SenderID,
|
||||
RecipientUserID: req.Recipient.ID,
|
||||
RandomID: lifecycleCommandRandomID("admin-collectible-grant", req.Recipient.ID, req.CommandKey),
|
||||
Date: req.Date,
|
||||
OriginUserID: req.SenderID,
|
||||
RecipientBlocked: req.RecipientBlocked,
|
||||
IdempotencyFingerprint: fingerprint[:],
|
||||
Media: placeholder,
|
||||
}
|
||||
|
||||
var result domain.AdminStarGiftGrantResult
|
||||
hooks := privateSendTxHooks{
|
||||
afterAllocate: func(ctx context.Context, tx pgx.Tx, send *domain.SendPrivateTextRequest, senderBoxID, recipientBoxID int) error {
|
||||
ownerMessageID := recipientBoxID
|
||||
if req.SenderID == req.Recipient.ID {
|
||||
ownerMessageID = senderBoxID
|
||||
}
|
||||
if ownerMessageID <= 0 {
|
||||
return fmt.Errorf("admin collectible grant missing owner message id")
|
||||
}
|
||||
|
||||
var revisionID int64
|
||||
var enabled bool
|
||||
if err := tx.QueryRow(ctx, `SELECT active_revision_id,enabled
|
||||
FROM star_gift_catalog WHERE gift_id=$1 FOR UPDATE`, req.GiftID).Scan(&revisionID, &enabled); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.ErrStarGiftNotFound
|
||||
}
|
||||
return fmt.Errorf("lock admin collectible catalog gift: %w", err)
|
||||
}
|
||||
gift, found, err := NewStarGiftStore(tx).CatalogRevision(ctx, revisionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || !enabled || gift.ID != req.GiftID {
|
||||
return domain.ErrStarGiftNotFound
|
||||
}
|
||||
revision, err := lockActiveCollectibleRevision(ctx, tx, gift.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if revision.Issued >= revision.SupplyTotal {
|
||||
return domain.ErrStarGiftCollectibleSoldOut
|
||||
}
|
||||
modelID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_models", revision.ID, req.ModelAttributeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patternID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_patterns", revision.ID, req.PatternAttributeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backdropID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_backdrops", revision.ID, req.BackdropAttributeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var craftable bool
|
||||
if err := tx.QueryRow(ctx, `SELECT EXISTS (
|
||||
SELECT 1 FROM star_gift_collectible_models
|
||||
WHERE collectible_revision_id=$1 AND crafted
|
||||
)`, revision.ID).Scan(&craftable); err != nil {
|
||||
return fmt.Errorf("load admin collectible craft capability: %w", err)
|
||||
}
|
||||
craftChancePermille, canCraftAt := 0, 0
|
||||
if craftable {
|
||||
craftChancePermille = s.lifecycle.CraftChancePermille
|
||||
if craftChancePermille > 0 {
|
||||
canCraftAt = starGiftCraftReadyAt(req.Date, s.lifecycle.CraftDelaySeconds)
|
||||
}
|
||||
}
|
||||
|
||||
saved := domain.SavedStarGift{
|
||||
Owner: req.Recipient,
|
||||
FromUserID: req.SenderID,
|
||||
GiftID: gift.ID,
|
||||
RevisionID: gift.RevisionID,
|
||||
MsgID: ownerMessageID,
|
||||
Date: req.Date,
|
||||
NameHidden: req.HideName,
|
||||
LifecycleStatus: domain.StarGiftLifecycleActive,
|
||||
Message: req.Message,
|
||||
TransferStars: s.lifecycle.TransferStars,
|
||||
CanExportAt: starGiftReadyAt(req.Date, s.lifecycle.ExportDelaySeconds),
|
||||
CanTransferAt: starGiftReadyAt(req.Date, s.lifecycle.TransferDelaySeconds),
|
||||
CanResellAt: starGiftReadyAt(req.Date, s.lifecycle.ResellDelaySeconds),
|
||||
DropOriginalDetailsStars: s.lifecycle.DropOriginalDetailsStars,
|
||||
CanCraftAt: canCraftAt,
|
||||
UpgradeMsgID: ownerMessageID,
|
||||
}
|
||||
savedID, err := NewStarGiftStore(tx).Create(ctx, saved)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
saved.ID = savedID
|
||||
|
||||
num := revision.Issued + 1
|
||||
var uniqueID int64
|
||||
if err := tx.QueryRow(ctx, `SELECT nextval('unique_star_gift_id_seq')`).Scan(&uniqueID); err != nil {
|
||||
return fmt.Errorf("allocate admin unique star gift id: %w", err)
|
||||
}
|
||||
slug := fmt.Sprintf("%s-%d", revision.SlugPrefix, num)
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO unique_star_gifts
|
||||
(id, gift_id, collectible_revision_id, source_saved_gift_id, title, slug, num,
|
||||
owner_peer_type, owner_peer_id, model_attribute_id, pattern_attribute_id,
|
||||
backdrop_attribute_id, keep_original_details, original_owner_peer_type, original_owner_peer_id,
|
||||
craft_chance_permille, offer_min_stars)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,true,$13,$14,$15,$16)`,
|
||||
uniqueID, gift.ID, revision.ID, savedID, gift.Title, slug, num,
|
||||
string(req.Recipient.Type), req.Recipient.ID, modelID, patternID, backdropID,
|
||||
string(req.Recipient.Type), req.Recipient.ID, craftChancePermille, s.lifecycle.OfferMinStars); err != nil {
|
||||
return fmt.Errorf("insert admin unique star gift: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `UPDATE star_gift_collectible_revisions SET issued=issued+1 WHERE id=$1`, revision.ID); err != nil {
|
||||
return fmt.Errorf("increment admin collectible issuance: %w", err)
|
||||
}
|
||||
tag, err := tx.Exec(ctx, `
|
||||
UPDATE peer_star_gifts
|
||||
SET unique_gift_id=$2,upgrade_msg_id=$3,convert_stars=0,prepaid_upgrade_stars=0,prepaid_upgrade_hash='',
|
||||
transfer_stars=$4,can_export_at=$5,can_transfer_at=$6,can_resell_at=$7,
|
||||
drop_original_details_stars=$8,can_craft_at=$9
|
||||
WHERE id=$1 AND unique_gift_id IS NULL AND lifecycle_status='active'`,
|
||||
savedID, uniqueID, ownerMessageID, s.lifecycle.TransferStars, saved.CanExportAt,
|
||||
saved.CanTransferAt, saved.CanResellAt, s.lifecycle.DropOriginalDetailsStars, canCraftAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("link admin unique star gift: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() != 1 {
|
||||
return fmt.Errorf("link admin unique star gift lost aggregate row")
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO star_gift_admin_grant_commands
|
||||
(recipient_user_id,command_key,request_fingerprint,sender_user_id,gift_id,saved_gift_id,unique_gift_id,created_at)
|
||||
VALUES($1,$2,$3,$4,$5,$6,$7,to_timestamp($8))`,
|
||||
req.Recipient.ID, req.CommandKey, fingerprint[:], req.SenderID, gift.ID, savedID, uniqueID, req.Date); err != nil {
|
||||
return fmt.Errorf("insert admin collectible grant command: %w", err)
|
||||
}
|
||||
|
||||
unique, found, err := NewStarGiftStore(tx).UniqueByID(ctx, uniqueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("new admin unique star gift %d disappeared", uniqueID)
|
||||
}
|
||||
saved.UniqueGiftID = uniqueID
|
||||
saved.Unique = &unique
|
||||
if err := registerUserStarGiftMessageRef(ctx, tx, req.Recipient.ID, ownerMessageID, savedID, uniqueID); err != nil {
|
||||
return err
|
||||
}
|
||||
result.Saved, result.Unique = saved, unique
|
||||
send.Media = adminStarGiftUniqueMedia(saved, unique)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
sent, err := s.messages.sendPrivateTextWithHooks(ctx, messageReq, hooks)
|
||||
if err != nil {
|
||||
if isUniqueViolation(err) {
|
||||
if replay, found, replayErr := s.loadAdminStarGiftGrantReplay(ctx, req, fingerprint, sent); replayErr != nil || found {
|
||||
return replay, replayErr
|
||||
}
|
||||
}
|
||||
return domain.AdminStarGiftGrantResult{}, err
|
||||
}
|
||||
result.Send, result.Duplicate = sent, sent.Duplicate
|
||||
if sent.Duplicate {
|
||||
replay, found, replayErr := s.loadAdminStarGiftGrantReplay(ctx, req, fingerprint, sent)
|
||||
if replayErr != nil {
|
||||
return domain.AdminStarGiftGrantResult{}, replayErr
|
||||
}
|
||||
if !found {
|
||||
return domain.AdminStarGiftGrantResult{}, domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
return replay, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func adminStarGiftUniqueMedia(saved domain.SavedStarGift, unique domain.UniqueStarGift) *domain.MessageMedia {
|
||||
fromUserID := saved.FromUserID
|
||||
if saved.NameHidden {
|
||||
fromUserID = 0
|
||||
}
|
||||
return &domain.MessageMedia{Kind: domain.MessageMediaKindService, ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGiftUnique,
|
||||
StarGiftUnique: &domain.MessageStarGiftUniqueAction{
|
||||
Gift: unique, FromUserID: fromUserID, Assigned: true, Saved: true,
|
||||
CanExportAt: saved.CanExportAt, TransferStars: saved.TransferStars,
|
||||
CanTransferAt: saved.CanTransferAt, CanResellAt: saved.CanResellAt,
|
||||
DropOriginalDetailsStars: saved.DropOriginalDetailsStars, CanCraftAt: saved.CanCraftAt,
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func adminStarGiftGrantFingerprint(req domain.AdminStarGiftGrant) [32]byte {
|
||||
return sha256.Sum256([]byte(fmt.Sprintf(
|
||||
"telesrv:admin-star-gift-grant:v1:%d:%s:%d:%d:%t:%q:%d:%d:%d",
|
||||
req.SenderID, req.Recipient.Type, req.Recipient.ID, req.GiftID, req.HideName, req.Message,
|
||||
req.ModelAttributeID, req.PatternAttributeID, req.BackdropAttributeID,
|
||||
)))
|
||||
}
|
||||
|
||||
func (s *StarGiftUpgradeStore) loadAdminStarGiftGrantReplay(
|
||||
ctx context.Context,
|
||||
req domain.AdminStarGiftGrant,
|
||||
fingerprint [32]byte,
|
||||
sent domain.SendPrivateTextResult,
|
||||
) (domain.AdminStarGiftGrantResult, bool, error) {
|
||||
var storedFingerprint []byte
|
||||
var senderID, giftID, savedID, uniqueID int64
|
||||
err := s.db.QueryRow(ctx, `
|
||||
SELECT request_fingerprint,sender_user_id,gift_id,saved_gift_id,unique_gift_id
|
||||
FROM star_gift_admin_grant_commands
|
||||
WHERE recipient_user_id=$1 AND command_key=$2`, req.Recipient.ID, req.CommandKey).Scan(
|
||||
&storedFingerprint, &senderID, &giftID, &savedID, &uniqueID)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.AdminStarGiftGrantResult{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return domain.AdminStarGiftGrantResult{}, false, err
|
||||
}
|
||||
if senderID != req.SenderID || giftID != req.GiftID || !bytes.Equal(storedFingerprint, fingerprint[:]) {
|
||||
return domain.AdminStarGiftGrantResult{}, false, domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
saved, found, err := savedStarGiftByID(ctx, s.db, savedID)
|
||||
if err != nil || !found {
|
||||
if err == nil {
|
||||
err = domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
return domain.AdminStarGiftGrantResult{}, false, err
|
||||
}
|
||||
unique, found, err := NewStarGiftStore(s.db).UniqueByID(ctx, uniqueID)
|
||||
if err != nil || !found {
|
||||
if err == nil {
|
||||
err = domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
return domain.AdminStarGiftGrantResult{}, false, err
|
||||
}
|
||||
if saved.Owner != req.Recipient || saved.FromUserID != req.SenderID || saved.GiftID != req.GiftID ||
|
||||
saved.UniqueGiftID != uniqueID || saved.MsgID <= 0 || saved.UpgradeMsgID != saved.MsgID ||
|
||||
unique.SourceSavedGiftID != savedID || unique.Owner != req.Recipient {
|
||||
return domain.AdminStarGiftGrantResult{}, false, domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
if sent.SenderMessage.ID == 0 {
|
||||
replay, replayFound, replayErr := s.messages.LookupPrivateSendReplay(ctx, domain.PrivateSendReplayRequest{
|
||||
SenderUserID: req.SenderID, RecipientUserID: req.Recipient.ID,
|
||||
RandomID: lifecycleCommandRandomID("admin-collectible-grant", req.Recipient.ID, req.CommandKey),
|
||||
IdempotencyFingerprint: fingerprint[:],
|
||||
})
|
||||
if replayErr != nil {
|
||||
return domain.AdminStarGiftGrantResult{}, false, replayErr
|
||||
}
|
||||
if !replayFound {
|
||||
return domain.AdminStarGiftGrantResult{}, false, domain.ErrStarGiftCollectibleInvalid
|
||||
}
|
||||
sent = replay
|
||||
}
|
||||
uniqueCopy := unique
|
||||
saved.Unique = &uniqueCopy
|
||||
return domain.AdminStarGiftGrantResult{
|
||||
Saved: saved, Unique: unique, Send: sent, Duplicate: true,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func (s *StarGiftUpgradeStore) UpgradeStarGift(ctx context.Context, req domain.StarGiftUpgradeRequest) (domain.StarGiftUpgradeResult, error) {
|
||||
if s == nil || s.db == nil || s.messages == nil || req.UserID <= 0 || !req.Ref.Valid() ||
|
||||
(req.Ref.Owner.Type == domain.PeerTypeUser && req.Ref.Owner.ID != req.UserID) ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue