tangled
alpha
login
or
join now
stream.place
/
streamplace
74
fork
atom
Live video on the AT Protocol
74
fork
atom
overview
issues
1
pulls
pipelines
moderation: we can ban people now yay
Eli Mallon
7 months ago
d22269d0
df08d16b
+187
-33
8 changed files
expand all
collapse all
unified
split
pkg
api
stream_key.go
atproto
labeler_firehose.go
labels.go
bus
bus.go
cmd
streamplace.go
media
key_revocation.go
model
label.go
model.go
+8
pkg/api/stream_key.go
···
77
77
}
78
78
79
79
ctx = log.WithLogValues(ctx, "did", did)
80
80
+
labels, err := a.Model.GetActiveLabels(did)
81
81
+
if err != nil {
82
82
+
return nil, fmt.Errorf("failed to get active labels: %w", err)
83
83
+
}
84
84
+
if atproto.IsBanned(labels...) {
85
85
+
log.Error(ctx, "user is banned", "did", did)
86
86
+
return nil, fmt.Errorf("user is banned")
87
87
+
}
80
88
81
89
var mediaSigner media.MediaSigner
82
90
if a.CLI.ExternalSigning {
+56
-11
pkg/atproto/labeler_firehose.go
···
10
10
11
11
comatproto "github.com/bluesky-social/indigo/api/atproto"
12
12
"github.com/bluesky-social/indigo/atproto/label"
13
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
13
14
"github.com/bluesky-social/indigo/events"
14
15
"github.com/bluesky-social/indigo/events/schedulers/parallel"
16
16
+
"github.com/bluesky-social/indigo/util"
15
17
"github.com/gorilla/websocket"
16
18
"golang.org/x/sync/errgroup"
17
19
"stream.place/streamplace/pkg/aqhttp"
···
108
110
109
111
rsc := &events.RepoStreamCallbacks{
110
112
LabelLabels: func(evt *comatproto.LabelSubscribeLabels_Labels) error {
111
111
-
log.Log(ctx, "labeler labels", "labels", evt.Labels, "seq", evt.Seq)
112
113
err = atsync.Model.UpdateLabelerCursor(did, evt.Seq)
113
114
if err != nil {
114
115
log.Error(ctx, "failed to update labeler cursor", "err", err)
···
131
132
log.Error(ctx, "failed to marshal label", "err", err)
132
133
continue
133
134
}
135
135
+
cts, err := time.Parse(util.ISO8601, l.CreatedAt)
136
136
+
if err != nil {
137
137
+
log.Error(ctx, "failed to parse label created time", "err", err)
138
138
+
continue
139
139
+
}
140
140
+
var exp time.Time
141
141
+
if l.ExpiresAt != nil {
142
142
+
e, err := time.Parse(util.ISO8601, *l.ExpiresAt)
143
143
+
if err != nil {
144
144
+
log.Error(ctx, "failed to parse label expiration time", "err", err)
145
145
+
continue
146
146
+
}
147
147
+
exp = e.UTC()
148
148
+
} else {
149
149
+
exp = time.Time{}
150
150
+
}
151
151
+
neg := false
152
152
+
if l.Negated != nil {
153
153
+
neg = *l.Negated
154
154
+
}
155
155
+
156
156
+
var targetDID string
157
157
+
158
158
+
// the URI can either be a true URI or a DID, so
159
159
+
aturi, err1 := syntax.ParseATURI(l.URI)
160
160
+
if err1 != nil {
161
161
+
did, err2 := syntax.ParseDID(l.URI)
162
162
+
if err2 != nil {
163
163
+
log.Error(ctx, "failed to parse label URI as either ATURI or DID", "err1", err1, "err2", err2)
164
164
+
continue
165
165
+
}
166
166
+
targetDID = did.String()
167
167
+
} else {
168
168
+
did, err := aturi.Authority().AsDID()
169
169
+
if err != nil {
170
170
+
log.Error(ctx, "failed to parse label URI as ATURI", "err", err)
171
171
+
continue
172
172
+
}
173
173
+
targetDID = did.String()
174
174
+
}
175
175
+
176
176
+
log.Log(ctx, "labeler label", "targetDID", targetDID, "uri", l.URI, "cid", l.CID, "createdAt", cts, "expiresAt", exp, "negated", neg, "sourceDID", l.SourceDID, "val", l.Val, "version", l.Version)
134
177
err = atsync.Model.CreateLabel(&model.Label{
135
135
-
Cid: l.CID,
136
136
-
Cts: l.CreatedAt,
137
137
-
Exp: l.ExpiresAt,
138
138
-
Neg: l.Negated,
139
139
-
Sig: l.Sig,
140
140
-
Src: l.SourceDID,
141
141
-
Uri: l.URI,
142
142
-
Val: l.Val,
143
143
-
Ver: &l.Version,
144
144
-
Record: bs.Bytes(),
178
178
+
Cid: l.CID,
179
179
+
Cts: cts.UTC(),
180
180
+
Exp: exp,
181
181
+
Neg: neg,
182
182
+
Sig: l.Sig,
183
183
+
Src: l.SourceDID,
184
184
+
Uri: l.URI,
185
185
+
Val: l.Val,
186
186
+
Ver: &l.Version,
187
187
+
Record: bs.Bytes(),
188
188
+
RepoDID: targetDID,
145
189
})
146
190
if err != nil {
147
191
log.Error(ctx, "failed to create label", "err", err)
148
192
continue
149
193
}
194
194
+
atsync.Bus.Publish(targetDID, labelLex)
150
195
}
151
196
return nil
152
197
},
+52
pkg/atproto/labels.go
···
1
1
+
package atproto
2
2
+
3
3
+
import (
4
4
+
"strings"
5
5
+
6
6
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
7
7
+
)
8
8
+
9
9
+
const (
10
10
+
// from com.atproto.label.defs
11
11
+
LabelHide = "!hide"
12
12
+
LabelNoPromote = "!no-promote"
13
13
+
LabelWarn = "!warn"
14
14
+
LabelNoUnauthenticated = "!no-unauthenticated"
15
15
+
LabelDMCAViolation = "dmca-violation"
16
16
+
LabelDoxxing = "doxxing"
17
17
+
LabelPorn = "porn"
18
18
+
LabelSexual = "sexual"
19
19
+
LabelNudity = "nudity"
20
20
+
LabelNSFL = "nsfl"
21
21
+
LabelGore = "gore"
22
22
+
23
23
+
// referenced in https://atproto.com/specs/label
24
24
+
LabelTakedown = "!takedown"
25
25
+
LabelSuspend = "!suspend"
26
26
+
)
27
27
+
28
28
+
var bannedLabels = map[string]bool{
29
29
+
LabelDMCAViolation: true,
30
30
+
LabelDoxxing: true,
31
31
+
LabelPorn: true,
32
32
+
LabelSexual: true,
33
33
+
LabelNudity: true,
34
34
+
LabelNSFL: true,
35
35
+
LabelGore: true,
36
36
+
LabelTakedown: true,
37
37
+
LabelSuspend: true,
38
38
+
}
39
39
+
40
40
+
// Given a users' labels, determine if they are banned
41
41
+
func IsBanned(labels ...*comatproto.LabelDefs_Label) bool {
42
42
+
for _, l := range labels {
43
43
+
if !strings.HasPrefix(l.Uri, "did:") {
44
44
+
// this is a label on a record, not a user
45
45
+
continue
46
46
+
}
47
47
+
if bannedLabels[l.Val] {
48
48
+
return true
49
49
+
}
50
50
+
}
51
51
+
return false
52
52
+
}
+5
-1
pkg/bus/bus.go
···
62
62
func (b *Bus) Publish(user string, msg Message) {
63
63
b.mu.Lock()
64
64
defer b.mu.Unlock()
65
65
-
for _, sub := range b.clients[user] {
65
65
+
subs, ok := b.clients[user]
66
66
+
if !ok {
67
67
+
return
68
68
+
}
69
69
+
for _, sub := range subs {
66
70
go func(sub Subscription) {
67
71
sub <- msg
68
72
}(sub)
+5
-5
pkg/cmd/streamplace.go
···
365
365
group.Go(func() error {
366
366
return atsync.StartFirehose(ctx)
367
367
})
368
368
-
for _, labeler := range cli.Labelers {
369
369
-
group.Go(func() error {
370
370
-
return atsync.StartLabelerFirehose(ctx, labeler)
371
371
-
})
372
372
-
}
368
368
+
}
369
369
+
for _, labeler := range cli.Labelers {
370
370
+
group.Go(func() error {
371
371
+
return atsync.StartLabelerFirehose(ctx, labeler)
372
372
+
})
373
373
}
374
374
375
375
group.Go(func() error {
+20
-11
pkg/media/key_revocation.go
···
4
4
"context"
5
5
"fmt"
6
6
7
7
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
7
8
"github.com/go-gst/go-gst/gst"
9
9
+
"stream.place/streamplace/pkg/atproto"
8
10
"stream.place/streamplace/pkg/model"
9
11
)
10
12
11
11
-
// Handle shutting down a pipeline when a signing key is revoked
13
13
+
// Handle shutting down a pipeline when a signing key is revoked or a user gets banned
12
14
func (mm *MediaManager) HandleKeyRevocation(ctx context.Context, ms MediaSigner, pipeline *gst.Pipeline) {
13
15
sub := mm.bus.Subscribe(ms.Streamer())
14
16
defer mm.bus.Unsubscribe(ms.Streamer(), sub)
···
17
19
case <-ctx.Done():
18
20
return
19
21
case msg := <-sub:
20
20
-
signingKey, ok := msg.(*model.SigningKey)
21
21
-
if !ok {
22
22
+
switch v := msg.(type) {
23
23
+
case *model.SigningKey:
24
24
+
if v.RevokedAt == nil {
25
25
+
continue
26
26
+
}
27
27
+
if v.DID == ms.DID() {
28
28
+
err := fmt.Errorf("signing key revoked, ending stream: %s", v.RKey)
29
29
+
pipeline.Error(err.Error(), err)
30
30
+
return
31
31
+
}
32
32
+
case *comatproto.LabelDefs_Label:
33
33
+
if atproto.IsBanned(v) {
34
34
+
err := fmt.Errorf("user banned, ending stream: %s", v.Uri)
35
35
+
pipeline.Error(err.Error(), err)
36
36
+
return
37
37
+
}
38
38
+
default:
22
39
continue
23
23
-
}
24
24
-
if signingKey.RevokedAt == nil {
25
25
-
continue
26
26
-
}
27
27
-
if signingKey.DID == ms.DID() {
28
28
-
err := fmt.Errorf("signing key revoked, ending stream: %s", signingKey.RKey)
29
29
-
pipeline.Error(err.Error(), err)
30
30
-
return
31
40
}
32
41
}
33
42
}
+40
-5
pkg/model/label.go
···
1
1
package model
2
2
3
3
-
import "gorm.io/gorm/clause"
3
3
+
import (
4
4
+
"bytes"
5
5
+
"time"
6
6
+
7
7
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
8
8
+
"gorm.io/gorm/clause"
9
9
+
)
4
10
5
11
type Label struct {
6
12
// cid: Optionally, CID specifying the specific version of 'uri' resource this label applies to.
7
13
Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty" gorm:"column:cid"`
8
14
// cts: Timestamp when this label was created.
9
9
-
Cts string `json:"cts" cborgen:"cts" gorm:"column:cts"`
15
15
+
Cts time.Time `json:"cts" cborgen:"cts" gorm:"column:cts"`
10
16
// exp: Timestamp at which this label expires (no longer applies).
11
11
-
Exp *string `json:"exp,omitempty" cborgen:"exp,omitempty" gorm:"column:exp"`
17
17
+
Exp time.Time `json:"exp,omitempty" cborgen:"exp,omitempty" gorm:"column:exp"`
12
18
// neg: If true, this is a negation label, overwriting a previous label.
13
13
-
Neg *bool `json:"neg,omitempty" cborgen:"neg,omitempty" gorm:"column:neg"`
19
19
+
Neg bool `json:"neg,omitempty" cborgen:"neg,omitempty" gorm:"column:neg"`
14
20
// sig: Signature of dag-cbor encoded label.
15
21
Sig []byte `json:"sig,omitempty" cborgen:"sig,omitempty" gorm:"column:sig"`
16
22
// src: DID of the actor who created this label.
···
22
28
// ver: The AT Protocol version of the label object.
23
29
Ver *int64 `json:"ver,omitempty" cborgen:"ver,omitempty" gorm:"column:ver"`
24
30
25
25
-
Record []byte `json:"record,omitempty" cborgen:"record,omitempty" gorm:"column:record"`
31
31
+
Record []byte `json:"record,omitempty" cborgen:"record,omitempty" gorm:"column:record"`
32
32
+
RepoDID string `json:"repoDID,omitempty" cborgen:"repoDID,omitempty" gorm:"column:repo_did"`
26
33
}
27
34
28
35
func (m *DBModel) CreateLabel(label *Label) error {
···
35
42
UpdateAll: true,
36
43
}).Create(label).Error
37
44
}
45
45
+
46
46
+
func (m *DBModel) GetActiveLabels(uri string) ([]*comatproto.LabelDefs_Label, error) {
47
47
+
now := time.Now().UTC()
48
48
+
var labels []Label
49
49
+
err := m.DB.Where("uri = ? AND (exp IS NULL OR exp < ?) AND neg = ?", uri, now, false).Find(&labels).Error
50
50
+
if err != nil {
51
51
+
return nil, err
52
52
+
}
53
53
+
lexs := make([]*comatproto.LabelDefs_Label, len(labels))
54
54
+
for i, l := range labels {
55
55
+
lex, err := l.ToLexicon()
56
56
+
if err != nil {
57
57
+
return nil, err
58
58
+
}
59
59
+
lexs[i] = lex
60
60
+
}
61
61
+
return lexs, nil
62
62
+
}
63
63
+
64
64
+
func (l Label) ToLexicon() (*comatproto.LabelDefs_Label, error) {
65
65
+
r := bytes.NewReader(l.Record)
66
66
+
var lex comatproto.LabelDefs_Label
67
67
+
err := lex.UnmarshalCBOR(r)
68
68
+
if err != nil {
69
69
+
return nil, err
70
70
+
}
71
71
+
return &lex, nil
72
72
+
}
+1
pkg/model/model.go
···
113
113
UpdateLabelerCursor(did string, cursor int64) error
114
114
115
115
CreateLabel(label *Label) error
116
116
+
GetActiveLabels(uri string) ([]*comatproto.LabelDefs_Label, error)
116
117
}
117
118
118
119
func MakeDB(dbURL string) (Model, error) {