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
|
|
@ -77,6 +77,67 @@ func frozenMethodRequiresWriteGate(method string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// restrictedAlwaysBlockedMethods covers the narrower spam restriction: unlike
|
||||
// freeze it does not touch reads or existing-membership actions, it only
|
||||
// blocks starting a NEW channel/group membership (public join or private
|
||||
// invite link). Non-contact messaging is peer-dependent and is gated
|
||||
// separately in the send path, not here.
|
||||
var restrictedAlwaysBlockedMethods = map[string]struct{}{
|
||||
"channels.joinChannel": {},
|
||||
"messages.importChatInvite": {},
|
||||
}
|
||||
|
||||
func (r *Router) checkRestrictedRPC(ctx context.Context, method string) error {
|
||||
if r == nil || r.deps.AccountRestriction == nil {
|
||||
return nil
|
||||
}
|
||||
if _, blocked := restrictedAlwaysBlockedMethods[method]; !blocked {
|
||||
return nil
|
||||
}
|
||||
userID, authorized := UserIDFrom(ctx)
|
||||
if !authorized || userID == 0 {
|
||||
return nil
|
||||
}
|
||||
restriction, found, err := r.deps.AccountRestriction.AccountRestriction(ctx, userID)
|
||||
if err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
if found && restriction.Restricted {
|
||||
return restrictedMethodInvalidErr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureNotRestrictedFromMessaging enforces the peer-dependent half of the
|
||||
// spam restriction: a restricted sender may still message existing contacts,
|
||||
// but not start a new conversation with a peer outside their own contact
|
||||
// list. Unlike checkRestrictedRPC this cannot be a method-name gate, since the
|
||||
// same method (messages.sendMessage) is allowed or blocked depending on who
|
||||
// the peer is.
|
||||
func (r *Router) ensureNotRestrictedFromMessaging(ctx context.Context, senderUserID, recipientUserID int64) error {
|
||||
if r == nil || r.deps.AccountRestriction == nil || senderUserID == 0 || recipientUserID == 0 || senderUserID == recipientUserID {
|
||||
return nil
|
||||
}
|
||||
restriction, found, err := r.deps.AccountRestriction.AccountRestriction(ctx, senderUserID)
|
||||
if err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
if !found || !restriction.Restricted {
|
||||
return nil
|
||||
}
|
||||
if r.deps.Contacts == nil {
|
||||
return restrictedNoncontactErr()
|
||||
}
|
||||
isContact, err := r.deps.Contacts.IsContact(ctx, senderUserID, recipientUserID)
|
||||
if err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
if isContact {
|
||||
return nil
|
||||
}
|
||||
return restrictedNoncontactErr()
|
||||
}
|
||||
|
||||
func (r *Router) checkFrozenRPC(ctx context.Context, method string) error {
|
||||
if r == nil || r.deps.AccountFreeze == nil || !frozenMethodRequiresWriteGate(method) {
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue