feat: sync durable moderation and appeals
This commit is contained in:
parent
e1a95c7318
commit
9f467f4be7
140 changed files with 13730 additions and 316 deletions
156
internal/domain/moderation_registry.go
Normal file
156
internal/domain/moderation_registry.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxSponsoredImpressionLifetime = 30 * 24 * time.Hour
|
||||
|
||||
// SponsoredMessageImpression is the server-issued fact required before a
|
||||
// random_id may enter the human moderation pipeline.
|
||||
type SponsoredMessageImpression struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
RandomIDHash [sha256.Size]byte
|
||||
Target Peer
|
||||
AuthorUserID int64
|
||||
EvidenceSchemaVersion int
|
||||
Evidence json.RawMessage
|
||||
EvidenceHash [sha256.Size]byte
|
||||
ReportID int64
|
||||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func NewSponsoredMessageImpression(userID int64, randomID []byte, target Peer, authorUserID int64, evidence json.RawMessage, createdAt, expiresAt time.Time) (SponsoredMessageImpression, error) {
|
||||
canonical, err := CanonicalModerationEvidence(evidence)
|
||||
if err != nil {
|
||||
return SponsoredMessageImpression{}, ErrModerationReportInvalid
|
||||
}
|
||||
impression := SponsoredMessageImpression{
|
||||
UserID: userID, RandomIDHash: sha256.Sum256(randomID),
|
||||
Target: target, AuthorUserID: authorUserID,
|
||||
EvidenceSchemaVersion: 1, Evidence: canonical,
|
||||
EvidenceHash: sha256.Sum256(canonical),
|
||||
CreatedAt: createdAt.UTC(), ExpiresAt: expiresAt.UTC(),
|
||||
}
|
||||
if err := impression.Validate(); err != nil {
|
||||
return SponsoredMessageImpression{}, err
|
||||
}
|
||||
return impression, nil
|
||||
}
|
||||
|
||||
func (i SponsoredMessageImpression) Validate() error {
|
||||
canonical, err := CanonicalModerationEvidence(i.Evidence)
|
||||
if i.ID < 0 || i.UserID <= 0 ||
|
||||
i.RandomIDHash == ([sha256.Size]byte{}) ||
|
||||
!moderationPeerValid(i.Target) || i.AuthorUserID < 0 ||
|
||||
i.EvidenceSchemaVersion <= 0 ||
|
||||
i.EvidenceHash != sha256.Sum256(i.Evidence) ||
|
||||
err != nil || !bytes.Equal(canonical, i.Evidence) ||
|
||||
i.ReportID < 0 || i.CreatedAt.IsZero() ||
|
||||
!i.ExpiresAt.After(i.CreatedAt) ||
|
||||
i.ExpiresAt.Sub(i.CreatedAt) > MaxSponsoredImpressionLifetime {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateSponsoredModerationReport(impression SponsoredMessageImpression, report ModerationReport) error {
|
||||
if err := impression.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := report.Validate(); err != nil ||
|
||||
report.ReporterUserID != impression.UserID ||
|
||||
report.Source != ModerationSourceSponsored ||
|
||||
report.Target != impression.Target ||
|
||||
report.CreatedAt.Before(impression.CreatedAt) ||
|
||||
!report.CreatedAt.Before(impression.ExpiresAt) ||
|
||||
len(report.Items) != 1 || len(report.MediaHolds) != 0 {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
item := report.Items[0]
|
||||
if item.Kind != ModerationItemSponsored ||
|
||||
item.Peer != impression.Target ||
|
||||
item.ItemID != impression.ID || item.SecondaryID != 0 ||
|
||||
item.AuthorUserID != impression.AuthorUserID ||
|
||||
item.EvidenceSchemaVersion != impression.EvidenceSchemaVersion ||
|
||||
item.EvidenceHash != impression.EvidenceHash ||
|
||||
!bytes.Equal(item.Evidence, impression.Evidence) {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChannelAntiSpamDecision is immutable evidence that native anti-spam
|
||||
// actually removed the referenced message. A false-positive report without
|
||||
// this fact must fail closed.
|
||||
type ChannelAntiSpamDecision struct {
|
||||
ID int64
|
||||
ChannelID int64
|
||||
MessageID int
|
||||
AuthorUserID int64
|
||||
EvidenceSchemaVersion int
|
||||
Evidence json.RawMessage
|
||||
EvidenceHash [sha256.Size]byte
|
||||
ReportID int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func NewChannelAntiSpamDecision(channelID int64, messageID int, authorUserID int64, evidence json.RawMessage, createdAt time.Time) (ChannelAntiSpamDecision, error) {
|
||||
canonical, err := CanonicalModerationEvidence(evidence)
|
||||
if err != nil {
|
||||
return ChannelAntiSpamDecision{}, ErrModerationReportInvalid
|
||||
}
|
||||
decision := ChannelAntiSpamDecision{
|
||||
ChannelID: channelID, MessageID: messageID,
|
||||
AuthorUserID: authorUserID, EvidenceSchemaVersion: 1,
|
||||
Evidence: canonical, EvidenceHash: sha256.Sum256(canonical),
|
||||
CreatedAt: createdAt.UTC(),
|
||||
}
|
||||
if err := decision.Validate(); err != nil {
|
||||
return ChannelAntiSpamDecision{}, err
|
||||
}
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
func (d ChannelAntiSpamDecision) Validate() error {
|
||||
canonical, err := CanonicalModerationEvidence(d.Evidence)
|
||||
if d.ID < 0 || d.ChannelID <= 0 || d.MessageID <= 0 ||
|
||||
d.MessageID > MaxMessageBoxID || d.AuthorUserID <= 0 ||
|
||||
d.EvidenceSchemaVersion <= 0 ||
|
||||
d.EvidenceHash != sha256.Sum256(d.Evidence) ||
|
||||
err != nil || !bytes.Equal(canonical, d.Evidence) ||
|
||||
d.ReportID < 0 || d.CreatedAt.IsZero() {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAntiSpamFalsePositiveReport(decision ChannelAntiSpamDecision, report ModerationReport) error {
|
||||
if err := decision.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
target := Peer{Type: PeerTypeChannel, ID: decision.ChannelID}
|
||||
if err := report.Validate(); err != nil ||
|
||||
report.Source != ModerationSourceAntiSpamFalsePositive ||
|
||||
report.Target != target ||
|
||||
report.CreatedAt.Before(decision.CreatedAt) ||
|
||||
len(report.Items) != 1 || len(report.MediaHolds) != 0 {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
item := report.Items[0]
|
||||
if item.Kind != ModerationItemAntiSpamDecision ||
|
||||
item.Peer != target || item.ItemID != decision.ID ||
|
||||
item.SecondaryID != int64(decision.MessageID) ||
|
||||
item.AuthorUserID != decision.AuthorUserID ||
|
||||
item.EvidenceSchemaVersion != decision.EvidenceSchemaVersion ||
|
||||
item.EvidenceHash != decision.EvidenceHash ||
|
||||
!bytes.Equal(item.Evidence, decision.Evidence) {
|
||||
return ErrModerationReportInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue