admin: add account spam restriction (join/message gate)
Adds a narrower spam sanction alongside the existing account freeze: a restricted account keeps every existing membership and conversation, but cannot join new channels/groups (public join or invite link) and cannot start a new conversation with a non-contact. Reachable both as a standalone admin action and as a decision on a reported user's moderation case, with the same idempotent-supersession and appeal wiring freeze already has.
This commit is contained in:
parent
3ca8ef1a16
commit
f33e25af8d
32 changed files with 750 additions and 44 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
type moderationAdminActions interface {
|
||||
SetAccountFrozen(ctx context.Context, req admin.SetAccountFrozenRequest) (admin.CommandResult, error)
|
||||
SetAccountRestricted(ctx context.Context, req admin.SetAccountRestrictedRequest) (admin.CommandResult, error)
|
||||
SetUserFlags(ctx context.Context, req admin.SetUserFlagsRequest) (admin.CommandResult, error)
|
||||
SetChannelFlags(ctx context.Context, req admin.SetChannelFlagsRequest) (admin.CommandResult, error)
|
||||
DeletePrivateMessages(ctx context.Context, req admin.DeletePrivateMessagesRequest) (admin.CommandResult, error)
|
||||
|
|
@ -99,6 +100,10 @@ type freezeAccountActionPayload struct {
|
|||
AppealURL string `json:"appeal_url,omitempty"`
|
||||
}
|
||||
|
||||
type restrictAccountActionPayload struct {
|
||||
Until time.Time `json:"until,omitempty"`
|
||||
}
|
||||
|
||||
type deletePrivateMessageActionPayload struct {
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
IDs []int `json:"ids"`
|
||||
|
|
@ -181,6 +186,23 @@ func (e *ActionExecutor) Execute(ctx context.Context, detail domain.ModerationCa
|
|||
Until: payload.Until, AppealURL: payload.AppealURL,
|
||||
})
|
||||
return err
|
||||
case domain.ModerationActionRestrictAccount, domain.ModerationActionUnrestrictAccount:
|
||||
if e.admin == nil || detail.Case.Target.Type != domain.PeerTypeUser {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
var payload restrictAccountActionPayload
|
||||
if err := decodeStrictActionPayload(action.Payload, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
restricted := action.Kind == domain.ModerationActionRestrictAccount
|
||||
if !restricted && !payload.Until.IsZero() {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
_, err := e.admin.SetAccountRestricted(ctx, admin.SetAccountRestrictedRequest{
|
||||
CommandMeta: meta, UserID: detail.Case.Target.ID, Restricted: restricted,
|
||||
Until: payload.Until,
|
||||
})
|
||||
return err
|
||||
case domain.ModerationActionDeletePrivateMessage:
|
||||
if e.admin == nil || detail.Case.Target.Type != domain.PeerTypeUser {
|
||||
return domain.ErrModerationActionInvalid
|
||||
|
|
@ -252,6 +274,7 @@ func (s *Service) validateDecisionActions(ctx context.Context, detail domain.Mod
|
|||
seen := make(map[domain.ModerationActionKind]struct{}, len(actions))
|
||||
flagActions := 0
|
||||
freezeActions := 0
|
||||
restrictActions := 0
|
||||
hasDeleteAccount := false
|
||||
for _, action := range actions {
|
||||
if _, duplicate := seen[action.Kind]; duplicate {
|
||||
|
|
@ -278,6 +301,18 @@ func (s *Service) validateDecisionActions(ctx context.Context, detail domain.Mod
|
|||
(!payload.Until.IsZero() || payload.AppealURL != "") {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
case domain.ModerationActionRestrictAccount, domain.ModerationActionUnrestrictAccount:
|
||||
restrictActions++
|
||||
if detail.Case.Target.Type != domain.PeerTypeUser {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
var payload restrictAccountActionPayload
|
||||
if err := decodeStrictActionPayload(action.Payload, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if action.Kind == domain.ModerationActionUnrestrictAccount && !payload.Until.IsZero() {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
case domain.ModerationActionDeletePrivateMessage:
|
||||
var payload deletePrivateMessageActionPayload
|
||||
if err := decodeStrictActionPayload(action.Payload, &payload); err != nil {
|
||||
|
|
@ -311,7 +346,7 @@ func (s *Service) validateDecisionActions(ctx context.Context, detail domain.Mod
|
|||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
}
|
||||
if flagActions > 1 || freezeActions > 1 ||
|
||||
if flagActions > 1 || freezeActions > 1 || restrictActions > 1 ||
|
||||
(hasDeleteAccount && len(actions) != 1) {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue