@atproto/ozone 0.0.17-next.0 → 0.0.17-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/util.d.ts +10 -0
- package/dist/auth-verifier.d.ts +2 -2
- package/dist/communication-service/template.d.ts +2 -2
- package/dist/config/config.d.ts +5 -0
- package/dist/config/env.d.ts +2 -0
- package/dist/context.d.ts +6 -0
- package/dist/daemon/blob-diverter.d.ts +26 -0
- package/dist/daemon/event-pusher.d.ts +4 -0
- package/dist/daemon/index.d.ts +1 -0
- package/dist/db/index.js +21 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/schema/index.d.ts +2 -1
- package/dist/db/schema/label.d.ts +4 -0
- package/dist/db/schema/moderation_event.d.ts +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +1 -1
- package/dist/db/schema/signing_key.d.ts +9 -0
- package/dist/index.js +10949 -10628
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +48 -28
- package/dist/lexicon/lexicons.d.ts +4641 -4650
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +7 -7
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -305
- package/dist/lexicon/types/com/atproto/label/defs.d.ts +5 -0
- package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
- package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
- package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
- package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
- package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
- package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
- package/dist/mod-service/index.d.ts +16 -15
- package/dist/mod-service/subject.d.ts +1 -1
- package/dist/mod-service/types.d.ts +2 -2
- package/dist/mod-service/util.d.ts +6 -0
- package/dist/mod-service/views.d.ts +9 -3
- package/dist/sequencer/sequencer.d.ts +6 -4
- package/dist/util.d.ts +2 -0
- package/package.json +7 -7
- package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +1 -1
- package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +1 -1
- package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +1 -1
- package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +1 -1
- package/src/api/index.ts +21 -21
- package/src/api/{temp → label}/fetchLabels.ts +4 -2
- package/src/api/label/queryLabels.ts +4 -2
- package/src/api/moderation/emitEvent.ts +218 -0
- package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +1 -1
- package/src/api/{admin → moderation}/getRecord.ts +2 -2
- package/src/api/{admin → moderation}/getRepo.ts +2 -2
- package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +2 -2
- package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +2 -2
- package/src/api/{admin → moderation}/searchRepos.ts +1 -1
- package/src/api/{moderation → report}/createReport.ts +1 -1
- package/src/api/util.ts +119 -0
- package/src/auth-verifier.ts +2 -2
- package/src/communication-service/template.ts +2 -2
- package/src/config/config.ts +14 -0
- package/src/config/env.ts +4 -0
- package/src/context.ts +35 -9
- package/src/daemon/blob-diverter.ts +150 -0
- package/src/daemon/context.ts +9 -5
- package/src/daemon/event-pusher.ts +49 -14
- package/src/daemon/index.ts +1 -0
- package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/index.ts +2 -0
- package/src/db/schema/label.ts +3 -0
- package/src/db/schema/moderation_event.ts +11 -11
- package/src/db/schema/moderation_subject_status.ts +1 -1
- package/src/db/schema/signing_key.ts +10 -0
- package/src/lexicon/index.ts +178 -138
- package/src/lexicon/lexicons.ts +6078 -6106
- package/src/lexicon/types/app/bsky/actor/defs.ts +11 -11
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +0 -697
- package/src/lexicon/types/com/atproto/label/defs.ts +10 -0
- package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
- package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
- package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
- package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
- package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
- package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
- package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
- package/src/mod-service/index.ts +42 -47
- package/src/mod-service/lang.ts +1 -1
- package/src/mod-service/status.ts +19 -16
- package/src/mod-service/subject.ts +1 -1
- package/src/mod-service/types.ts +10 -10
- package/src/mod-service/util.ts +49 -5
- package/src/mod-service/views.ts +45 -18
- package/src/sequencer/sequencer.ts +12 -11
- package/src/util.ts +21 -0
- package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
- package/tests/__snapshots__/get-record.test.ts.snap +10 -2
- package/tests/__snapshots__/get-repo.test.ts.snap +5 -1
- package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
- package/tests/_util.ts +5 -0
- package/tests/blob-divert.test.ts +87 -0
- package/tests/communication-templates.test.ts +30 -34
- package/tests/db.test.ts +6 -6
- package/tests/get-record.test.ts +6 -6
- package/tests/get-repo.test.ts +11 -11
- package/tests/moderation-appeals.test.ts +28 -28
- package/tests/moderation-events.test.ts +44 -44
- package/tests/moderation-status-tags.test.ts +8 -10
- package/tests/moderation-statuses.test.ts +27 -27
- package/tests/moderation.test.ts +50 -57
- package/tests/query-labels.test.ts +86 -10
- package/tests/repo-search.test.ts +8 -8
- package/tests/sequencer.test.ts +6 -3
- package/dist/api/admin/util.d.ts +0 -5
- package/dist/api/moderation/util.d.ts +0 -4
- package/src/api/admin/emitModerationEvent.ts +0 -174
- package/src/api/admin/util.ts +0 -54
- package/src/api/moderation/util.ts +0 -67
- /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
- /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
- /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
- /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
- /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
- /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
- /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
- /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
- /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
- /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
- /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
- /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
- /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
- /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
- /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
package/src/mod-service/views.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax'
|
|
|
3
3
|
import AtpAgent, { AppBskyFeedDefs } from '@atproto/api'
|
|
4
4
|
import { dedupeStrs } from '@atproto/common'
|
|
5
5
|
import { BlobRef } from '@atproto/lexicon'
|
|
6
|
+
import { Keypair } from '@atproto/crypto'
|
|
6
7
|
import { Database } from '../db'
|
|
7
8
|
import {
|
|
8
9
|
ModEventView,
|
|
@@ -10,12 +11,11 @@ import {
|
|
|
10
11
|
RepoViewDetail,
|
|
11
12
|
RecordView,
|
|
12
13
|
RecordViewDetail,
|
|
13
|
-
ReportViewDetail,
|
|
14
14
|
BlobView,
|
|
15
15
|
SubjectStatusView,
|
|
16
16
|
ModEventViewDetail,
|
|
17
|
-
|
|
18
|
-
} from '../lexicon/types/com/atproto/admin/defs'
|
|
17
|
+
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
18
|
+
import { AccountView } from '../lexicon/types/com/atproto/admin/defs'
|
|
19
19
|
import { OutputSchema as ReportOutput } from '../lexicon/types/com/atproto/moderation/createReport'
|
|
20
20
|
import { Label, isSelfLabels } from '../lexicon/types/com/atproto/label/defs'
|
|
21
21
|
import {
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
} from './types'
|
|
25
25
|
import { REASONOTHER } from '../lexicon/types/com/atproto/moderation/defs'
|
|
26
26
|
import { subjectFromEventRow, subjectFromStatusRow } from './subject'
|
|
27
|
-
import { formatLabel } from './util'
|
|
28
|
-
import {
|
|
27
|
+
import { formatLabel, signLabel } from './util'
|
|
28
|
+
import { LabelRow } from '../db/schema/label'
|
|
29
|
+
import { dbLogger } from '../logger'
|
|
30
|
+
import { httpLogger } from '../logger'
|
|
29
31
|
|
|
30
32
|
export type AuthHeaders = {
|
|
31
33
|
headers: {
|
|
@@ -36,6 +38,8 @@ export type AuthHeaders = {
|
|
|
36
38
|
export class ModerationViews {
|
|
37
39
|
constructor(
|
|
38
40
|
private db: Database,
|
|
41
|
+
private signingKey: Keypair,
|
|
42
|
+
private signingKeyId: number,
|
|
39
43
|
private appviewAgent: AtpAgent,
|
|
40
44
|
private appviewAuth: () => Promise<AuthHeaders>,
|
|
41
45
|
) {}
|
|
@@ -55,7 +59,10 @@ export class ModerationViews {
|
|
|
55
59
|
return acc.set(cur.did, cur)
|
|
56
60
|
}, new Map<string, AccountView>())
|
|
57
61
|
} catch (err) {
|
|
58
|
-
|
|
62
|
+
httpLogger.error(
|
|
63
|
+
{ err, dids },
|
|
64
|
+
'failed to resolve account infos from appview',
|
|
65
|
+
)
|
|
59
66
|
return new Map()
|
|
60
67
|
}
|
|
61
68
|
}
|
|
@@ -101,8 +108,8 @@ export class ModerationViews {
|
|
|
101
108
|
|
|
102
109
|
if (
|
|
103
110
|
[
|
|
104
|
-
'
|
|
105
|
-
'
|
|
111
|
+
'tools.ozone.moderation.defs#modEventTakedown',
|
|
112
|
+
'tools.ozone.moderation.defs#modEventMute',
|
|
106
113
|
].includes(event.action)
|
|
107
114
|
) {
|
|
108
115
|
eventView.event = {
|
|
@@ -111,7 +118,7 @@ export class ModerationViews {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
if (event.action === '
|
|
121
|
+
if (event.action === 'tools.ozone.moderation.defs#modEventLabel') {
|
|
115
122
|
eventView.event = {
|
|
116
123
|
...eventView.event,
|
|
117
124
|
createLabelVals: event.createLabelVals?.length
|
|
@@ -126,9 +133,9 @@ export class ModerationViews {
|
|
|
126
133
|
// This is for legacy data only, for new events, these types of events won't have labels attached
|
|
127
134
|
if (
|
|
128
135
|
[
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
136
|
+
'tools.ozone.moderation.defs#modEventAcknowledge',
|
|
137
|
+
'tools.ozone.moderation.defs#modEventTakedown',
|
|
138
|
+
'tools.ozone.moderation.defs#modEventEscalate',
|
|
132
139
|
].includes(event.action)
|
|
133
140
|
) {
|
|
134
141
|
if (event.createLabelVals?.length) {
|
|
@@ -146,14 +153,14 @@ export class ModerationViews {
|
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
|
|
149
|
-
if (event.action === '
|
|
156
|
+
if (event.action === 'tools.ozone.moderation.defs#modEventReport') {
|
|
150
157
|
eventView.event = {
|
|
151
158
|
...eventView.event,
|
|
152
159
|
reportType: event.meta?.reportType ?? undefined,
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
if (event.action === '
|
|
163
|
+
if (event.action === 'tools.ozone.moderation.defs#modEventEmail') {
|
|
157
164
|
eventView.event = {
|
|
158
165
|
...eventView.event,
|
|
159
166
|
subjectLine: event.meta?.subjectLine ?? '',
|
|
@@ -162,13 +169,13 @@ export class ModerationViews {
|
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
if (
|
|
165
|
-
event.action === '
|
|
172
|
+
event.action === 'tools.ozone.moderation.defs#modEventComment' &&
|
|
166
173
|
event.meta?.sticky
|
|
167
174
|
) {
|
|
168
175
|
eventView.event.sticky = true
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
if (event.action === '
|
|
178
|
+
if (event.action === 'tools.ozone.moderation.defs#modEventTag') {
|
|
172
179
|
eventView.event.add = event.addedTags || []
|
|
173
180
|
eventView.event.remove = event.removedTags || []
|
|
174
181
|
}
|
|
@@ -408,7 +415,25 @@ export class ModerationViews {
|
|
|
408
415
|
.if(!includeNeg, (qb) => qb.where('neg', '=', false))
|
|
409
416
|
.selectAll()
|
|
410
417
|
.execute()
|
|
411
|
-
return res.map((l) =>
|
|
418
|
+
return Promise.all(res.map((l) => this.formatLabelAndEnsureSig(l)))
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async formatLabelAndEnsureSig(row: LabelRow) {
|
|
422
|
+
const formatted = formatLabel(row)
|
|
423
|
+
if (!!row.sig && row.signingKeyId === this.signingKeyId) {
|
|
424
|
+
return formatted
|
|
425
|
+
}
|
|
426
|
+
const signed = await signLabel(formatted, this.signingKey)
|
|
427
|
+
try {
|
|
428
|
+
await this.db.db
|
|
429
|
+
.updateTable('label')
|
|
430
|
+
.set({ sig: Buffer.from(signed.sig), signingKeyId: this.signingKeyId })
|
|
431
|
+
.where('id', '=', row.id)
|
|
432
|
+
.execute()
|
|
433
|
+
} catch (err) {
|
|
434
|
+
dbLogger.error({ err, label: row }, 'failed to update resigned label')
|
|
435
|
+
}
|
|
436
|
+
return signed
|
|
412
437
|
}
|
|
413
438
|
|
|
414
439
|
async getSubjectStatus(
|
|
@@ -500,7 +525,9 @@ export class ModerationViews {
|
|
|
500
525
|
|
|
501
526
|
type RecordSubject = { uri: string; cid?: string }
|
|
502
527
|
|
|
503
|
-
type SubjectView = ModEventViewDetail['subject']
|
|
528
|
+
type SubjectView = ModEventViewDetail['subject']
|
|
529
|
+
// @TODO tidy
|
|
530
|
+
// type SubjectView = ModEventViewDetail['subject'] & ReportViewDetail['subject']
|
|
504
531
|
|
|
505
532
|
type RecordInfo = {
|
|
506
533
|
uri: string
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import EventEmitter from 'events'
|
|
2
2
|
import TypedEmitter from 'typed-emitter'
|
|
3
|
+
import { Selectable } from 'kysely'
|
|
4
|
+
import { PoolClient } from 'pg'
|
|
3
5
|
import { seqLogger as log } from '../logger'
|
|
4
6
|
import Database from '../db'
|
|
5
7
|
import { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels'
|
|
6
8
|
import { LabelChannel, Label as LabelTable } from '../db/schema/label'
|
|
7
|
-
import {
|
|
8
|
-
import { formatLabel } from '../mod-service/util'
|
|
9
|
-
import { PoolClient } from 'pg'
|
|
9
|
+
import { ModerationService } from '../mod-service'
|
|
10
10
|
|
|
11
11
|
export type { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels'
|
|
12
12
|
type LabelRow = Selectable<LabelTable>
|
|
13
13
|
|
|
14
14
|
export class Sequencer extends (EventEmitter as new () => SequencerEmitter) {
|
|
15
|
+
db: Database
|
|
15
16
|
destroyed = false
|
|
16
17
|
pollPromise: Promise<void> | undefined
|
|
17
18
|
queued = false
|
|
18
19
|
conn: PoolClient | undefined
|
|
19
20
|
|
|
20
|
-
constructor(public
|
|
21
|
+
constructor(public modSrvc: ModerationService, public lastSeen = 0) {
|
|
21
22
|
super()
|
|
23
|
+
this.db = modSrvc.db
|
|
22
24
|
// note: this does not err when surpassed, just prints a warning to stderr
|
|
23
25
|
this.setMaxListeners(100)
|
|
24
26
|
}
|
|
@@ -89,13 +91,12 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) {
|
|
|
89
91
|
return []
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const evts: LabelsEvt[] =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
seq: row.id,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
94
|
+
const evts: LabelsEvt[] = await Promise.all(
|
|
95
|
+
rows.map(async (row) => {
|
|
96
|
+
const formatted = await this.modSrvc.views.formatLabelAndEnsureSig(row)
|
|
97
|
+
return { seq: row.id, labels: [formatted] }
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
99
100
|
|
|
100
101
|
return evts
|
|
101
102
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { AxiosError } from 'axios'
|
|
2
2
|
import { XRPCError, ResponseType } from '@atproto/xrpc'
|
|
3
3
|
import { RetryOptions, retry } from '@atproto/common'
|
|
4
|
+
import Database from './db'
|
|
5
|
+
|
|
6
|
+
export const getSigningKeyId = async (
|
|
7
|
+
db: Database,
|
|
8
|
+
signingKey: string,
|
|
9
|
+
): Promise<number> => {
|
|
10
|
+
const selectRes = await db.db
|
|
11
|
+
.selectFrom('signing_key')
|
|
12
|
+
.selectAll()
|
|
13
|
+
.where('key', '=', signingKey)
|
|
14
|
+
.executeTakeFirst()
|
|
15
|
+
if (selectRes) {
|
|
16
|
+
return selectRes.id
|
|
17
|
+
}
|
|
18
|
+
const insertRes = await db.db
|
|
19
|
+
.insertInto('signing_key')
|
|
20
|
+
.values({ key: signingKey })
|
|
21
|
+
.returningAll()
|
|
22
|
+
.executeTakeFirstOrThrow()
|
|
23
|
+
return insertRes.id
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
export async function retryHttp<T>(
|
|
6
27
|
fn: () => Promise<T>,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`blob divert sends blobs to configured divert service and marks divert date 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
6
|
+
"createdBy": "user(0)",
|
|
7
|
+
"event": Object {
|
|
8
|
+
"$type": "tools.ozone.moderation.defs#modEventDivert",
|
|
9
|
+
"comment": "Diverting for test",
|
|
10
|
+
},
|
|
11
|
+
"id": 1,
|
|
12
|
+
"subject": Object {
|
|
13
|
+
"$type": "com.atproto.repo.strongRef",
|
|
14
|
+
"cid": "cids(0)",
|
|
15
|
+
"uri": "record(0)",
|
|
16
|
+
},
|
|
17
|
+
"subjectBlobCids": Array [
|
|
18
|
+
"cids(1)",
|
|
19
|
+
"cids(2)",
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
@@ -11,9 +11,13 @@ Object {
|
|
|
11
11
|
"cid": "cids(0)",
|
|
12
12
|
"cts": "1970-01-01T00:00:00.000Z",
|
|
13
13
|
"neg": false,
|
|
14
|
+
"sig": Object {
|
|
15
|
+
"$bytes": "sig(0)",
|
|
16
|
+
},
|
|
14
17
|
"src": "user(2)",
|
|
15
18
|
"uri": "record(0)",
|
|
16
19
|
"val": "!unspecced-takedown",
|
|
20
|
+
"ver": 1,
|
|
17
21
|
},
|
|
18
22
|
Object {
|
|
19
23
|
"cid": "cids(0)",
|
|
@@ -31,7 +35,7 @@ Object {
|
|
|
31
35
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
32
36
|
"lastReviewedAt": "1970-01-01T00:00:00.000Z",
|
|
33
37
|
"lastReviewedBy": "user(1)",
|
|
34
|
-
"reviewState": "
|
|
38
|
+
"reviewState": "tools.ozone.moderation.defs#reviewClosed",
|
|
35
39
|
"subject": Object {
|
|
36
40
|
"$type": "com.atproto.repo.strongRef",
|
|
37
41
|
"cid": "cids(0)",
|
|
@@ -108,9 +112,13 @@ Object {
|
|
|
108
112
|
"cid": "cids(0)",
|
|
109
113
|
"cts": "1970-01-01T00:00:00.000Z",
|
|
110
114
|
"neg": false,
|
|
115
|
+
"sig": Object {
|
|
116
|
+
"$bytes": "sig(0)",
|
|
117
|
+
},
|
|
111
118
|
"src": "user(2)",
|
|
112
119
|
"uri": "record(0)",
|
|
113
120
|
"val": "!unspecced-takedown",
|
|
121
|
+
"ver": 1,
|
|
114
122
|
},
|
|
115
123
|
Object {
|
|
116
124
|
"cid": "cids(0)",
|
|
@@ -128,7 +136,7 @@ Object {
|
|
|
128
136
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
129
137
|
"lastReviewedAt": "1970-01-01T00:00:00.000Z",
|
|
130
138
|
"lastReviewedBy": "user(1)",
|
|
131
|
-
"reviewState": "
|
|
139
|
+
"reviewState": "tools.ozone.moderation.defs#reviewClosed",
|
|
132
140
|
"subject": Object {
|
|
133
141
|
"$type": "com.atproto.repo.strongRef",
|
|
134
142
|
"cid": "cids(0)",
|
|
@@ -12,9 +12,13 @@ Object {
|
|
|
12
12
|
Object {
|
|
13
13
|
"cts": "1970-01-01T00:00:00.000Z",
|
|
14
14
|
"neg": false,
|
|
15
|
+
"sig": Object {
|
|
16
|
+
"$bytes": "sig(0)",
|
|
17
|
+
},
|
|
15
18
|
"src": "user(2)",
|
|
16
19
|
"uri": "user(0)",
|
|
17
20
|
"val": "!unspecced-takedown",
|
|
21
|
+
"ver": 1,
|
|
18
22
|
},
|
|
19
23
|
],
|
|
20
24
|
"moderation": Object {
|
|
@@ -24,7 +28,7 @@ Object {
|
|
|
24
28
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
25
29
|
"lastReviewedAt": "1970-01-01T00:00:00.000Z",
|
|
26
30
|
"lastReviewedBy": "user(1)",
|
|
27
|
-
"reviewState": "
|
|
31
|
+
"reviewState": "tools.ozone.moderation.defs#reviewClosed",
|
|
28
32
|
"subject": Object {
|
|
29
33
|
"$type": "com.atproto.admin.defs#repoRef",
|
|
30
34
|
"did": "user(0)",
|
|
@@ -5,7 +5,7 @@ Object {
|
|
|
5
5
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
6
6
|
"createdBy": "user(2)",
|
|
7
7
|
"event": Object {
|
|
8
|
-
"$type": "
|
|
8
|
+
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
9
9
|
"comment": "X",
|
|
10
10
|
"reportType": "com.atproto.moderation.defs#reasonMisleading",
|
|
11
11
|
},
|
|
@@ -22,7 +22,7 @@ Object {
|
|
|
22
22
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
23
23
|
"lastReviewedAt": "1970-01-01T00:00:00.000Z",
|
|
24
24
|
"lastReviewedBy": "user(1)",
|
|
25
|
-
"reviewState": "
|
|
25
|
+
"reviewState": "tools.ozone.moderation.defs#reviewEscalated",
|
|
26
26
|
"subject": Object {
|
|
27
27
|
"$type": "com.atproto.admin.defs#repoRef",
|
|
28
28
|
"did": "user(0)",
|
|
@@ -75,7 +75,7 @@ Array [
|
|
|
75
75
|
"createdBy": "user(1)",
|
|
76
76
|
"creatorHandle": "alice.test",
|
|
77
77
|
"event": Object {
|
|
78
|
-
"$type": "
|
|
78
|
+
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
79
79
|
"comment": "X",
|
|
80
80
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
81
81
|
},
|
|
@@ -91,7 +91,7 @@ Array [
|
|
|
91
91
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
92
92
|
"createdBy": "user(2)",
|
|
93
93
|
"event": Object {
|
|
94
|
-
"$type": "
|
|
94
|
+
"$type": "tools.ozone.moderation.defs#modEventTag",
|
|
95
95
|
"add": Array [
|
|
96
96
|
"lang:en",
|
|
97
97
|
"lang:i",
|
|
@@ -111,7 +111,7 @@ Array [
|
|
|
111
111
|
"createdBy": "user(1)",
|
|
112
112
|
"creatorHandle": "alice.test",
|
|
113
113
|
"event": Object {
|
|
114
|
-
"$type": "
|
|
114
|
+
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
115
115
|
"comment": "X",
|
|
116
116
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
117
117
|
},
|
|
@@ -133,7 +133,7 @@ Array [
|
|
|
133
133
|
"createdBy": "user(0)",
|
|
134
134
|
"creatorHandle": "bob.test",
|
|
135
135
|
"event": Object {
|
|
136
|
-
"$type": "
|
|
136
|
+
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
137
137
|
"comment": "X",
|
|
138
138
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
139
139
|
},
|
|
@@ -150,7 +150,7 @@ Array [
|
|
|
150
150
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
151
151
|
"createdBy": "user(1)",
|
|
152
152
|
"event": Object {
|
|
153
|
-
"$type": "
|
|
153
|
+
"$type": "tools.ozone.moderation.defs#modEventTag",
|
|
154
154
|
"add": Array [
|
|
155
155
|
"lang:und",
|
|
156
156
|
],
|
|
@@ -170,7 +170,7 @@ Array [
|
|
|
170
170
|
"createdBy": "user(0)",
|
|
171
171
|
"creatorHandle": "bob.test",
|
|
172
172
|
"event": Object {
|
|
173
|
-
"$type": "
|
|
173
|
+
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
174
174
|
"comment": "X",
|
|
175
175
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
176
176
|
},
|
|
@@ -6,7 +6,7 @@ Array [
|
|
|
6
6
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
7
7
|
"id": 7,
|
|
8
8
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
9
|
-
"reviewState": "
|
|
9
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
10
10
|
"subject": Object {
|
|
11
11
|
"$type": "com.atproto.repo.strongRef",
|
|
12
12
|
"cid": "cids(0)",
|
|
@@ -25,7 +25,7 @@ Array [
|
|
|
25
25
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
26
26
|
"id": 5,
|
|
27
27
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
28
|
-
"reviewState": "
|
|
28
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
29
29
|
"subject": Object {
|
|
30
30
|
"$type": "com.atproto.admin.defs#repoRef",
|
|
31
31
|
"did": "user(0)",
|
|
@@ -48,7 +48,7 @@ Array [
|
|
|
48
48
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
49
49
|
"id": 7,
|
|
50
50
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
51
|
-
"reviewState": "
|
|
51
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
52
52
|
"subject": Object {
|
|
53
53
|
"$type": "com.atproto.repo.strongRef",
|
|
54
54
|
"cid": "cids(0)",
|
|
@@ -67,7 +67,7 @@ Array [
|
|
|
67
67
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
68
68
|
"id": 5,
|
|
69
69
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
70
|
-
"reviewState": "
|
|
70
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
71
71
|
"subject": Object {
|
|
72
72
|
"$type": "com.atproto.admin.defs#repoRef",
|
|
73
73
|
"did": "user(0)",
|
|
@@ -85,7 +85,7 @@ Array [
|
|
|
85
85
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
86
86
|
"id": 3,
|
|
87
87
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
88
|
-
"reviewState": "
|
|
88
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
89
89
|
"subject": Object {
|
|
90
90
|
"$type": "com.atproto.repo.strongRef",
|
|
91
91
|
"cid": "cids(1)",
|
|
@@ -103,7 +103,7 @@ Array [
|
|
|
103
103
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
104
104
|
"id": 1,
|
|
105
105
|
"lastReportedAt": "1970-01-01T00:00:00.000Z",
|
|
106
|
-
"reviewState": "
|
|
106
|
+
"reviewState": "tools.ozone.moderation.defs#reviewOpen",
|
|
107
107
|
"subject": Object {
|
|
108
108
|
"$type": "com.atproto.admin.defs#repoRef",
|
|
109
109
|
"did": "user(1)",
|
package/tests/_util.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const forSnapshot = (obj: unknown) => {
|
|
|
16
16
|
const collections = { [kTake]: 'collection' }
|
|
17
17
|
const users = { [kTake]: 'user' }
|
|
18
18
|
const cids = { [kTake]: 'cids' }
|
|
19
|
+
const sigs = { [kTake]: 'sig' }
|
|
19
20
|
const unknown = { [kTake]: 'unknown' }
|
|
20
21
|
const toWalk = lexToJson(obj as any) // remove any blobrefs/cids
|
|
21
22
|
return mapLeafValues(toWalk, (item) => {
|
|
@@ -61,6 +62,10 @@ export const forSnapshot = (obj: unknown) => {
|
|
|
61
62
|
const [, did, cid] = match
|
|
62
63
|
return str.replace(did, take(users, did)).replace(cid, take(cids, cid))
|
|
63
64
|
}
|
|
65
|
+
// decent check for 64-byte base64 encoded signatures
|
|
66
|
+
if (str.length === 86 && !str.includes(' ')) {
|
|
67
|
+
return take(sigs, str)
|
|
68
|
+
}
|
|
64
69
|
let isCid: boolean
|
|
65
70
|
try {
|
|
66
71
|
CID.parse(str)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ModeratorClient,
|
|
3
|
+
SeedClient,
|
|
4
|
+
TestNetwork,
|
|
5
|
+
basicSeed,
|
|
6
|
+
} from '@atproto/dev-env'
|
|
7
|
+
import { BlobDiverter } from '../src/daemon'
|
|
8
|
+
import { forSnapshot } from './_util'
|
|
9
|
+
|
|
10
|
+
describe('blob divert', () => {
|
|
11
|
+
let network: TestNetwork
|
|
12
|
+
let sc: SeedClient
|
|
13
|
+
let modClient: ModeratorClient
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
network = await TestNetwork.create({
|
|
17
|
+
dbPostgresSchema: 'ozone_blob_divert_test',
|
|
18
|
+
ozone: {
|
|
19
|
+
blobDivertUrl: `https://blob-report.com`,
|
|
20
|
+
blobDivertAdminPassword: 'test-auth-token',
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
sc = network.getSeedClient()
|
|
24
|
+
modClient = network.ozone.getModClient()
|
|
25
|
+
await basicSeed(sc)
|
|
26
|
+
await network.processAll()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await network.close()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const mockReportServiceResponse = (result: boolean) => {
|
|
34
|
+
return jest
|
|
35
|
+
.spyOn(BlobDiverter.prototype, 'sendImage')
|
|
36
|
+
.mockImplementation(async () => {
|
|
37
|
+
return result
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getSubject = () => ({
|
|
42
|
+
$type: 'com.atproto.repo.strongRef',
|
|
43
|
+
uri: sc.posts[sc.dids.carol][0].ref.uriStr,
|
|
44
|
+
cid: sc.posts[sc.dids.carol][0].ref.cidStr,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const emitDivertEvent = async () =>
|
|
48
|
+
modClient.emitEvent(
|
|
49
|
+
{
|
|
50
|
+
subject: getSubject(),
|
|
51
|
+
event: {
|
|
52
|
+
$type: 'tools.ozone.moderation.defs#modEventDivert',
|
|
53
|
+
comment: 'Diverting for test',
|
|
54
|
+
},
|
|
55
|
+
createdBy: sc.dids.alice,
|
|
56
|
+
subjectBlobCids: sc.posts[sc.dids.carol][0].images.map((img) =>
|
|
57
|
+
img.image.ref.toString(),
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
'moderator',
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
it('fails and keeps attempt count when report service fails to accept upload.', async () => {
|
|
64
|
+
// Simulate failure to fail upload
|
|
65
|
+
const reportServiceRequest = mockReportServiceResponse(false)
|
|
66
|
+
|
|
67
|
+
await expect(emitDivertEvent()).rejects.toThrow()
|
|
68
|
+
|
|
69
|
+
expect(reportServiceRequest).toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('sends blobs to configured divert service and marks divert date', async () => {
|
|
73
|
+
// Simulate failure to accept upload
|
|
74
|
+
const reportServiceRequest = mockReportServiceResponse(true)
|
|
75
|
+
|
|
76
|
+
const divertEvent = await emitDivertEvent()
|
|
77
|
+
|
|
78
|
+
expect(reportServiceRequest).toHaveBeenCalled()
|
|
79
|
+
expect(forSnapshot(divertEvent)).toMatchSnapshot()
|
|
80
|
+
|
|
81
|
+
const { subjectStatuses } = await modClient.queryStatuses({
|
|
82
|
+
subject: getSubject().uri,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(subjectStatuses[0].takendown).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -27,37 +27,34 @@ describe('communication-templates', () => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const listTemplates = async () => {
|
|
30
|
-
const { data } =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
30
|
+
const { data } = await agent.api.tools.ozone.communication.listTemplates(
|
|
31
|
+
{},
|
|
32
|
+
{
|
|
33
|
+
headers: await network.ozone.modHeaders('moderator'),
|
|
34
|
+
},
|
|
35
|
+
)
|
|
37
36
|
return data.communicationTemplates
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
describe('create templates', () => {
|
|
41
40
|
it('only allows admins to create new templates', async () => {
|
|
42
|
-
const moderatorReq =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
41
|
+
const moderatorReq = agent.api.tools.ozone.communication.createTemplate(
|
|
42
|
+
{ ...templateOne, createdBy: sc.dids.bob },
|
|
43
|
+
{
|
|
44
|
+
encoding: 'application/json',
|
|
45
|
+
headers: await network.ozone.modHeaders('moderator'),
|
|
46
|
+
},
|
|
47
|
+
)
|
|
50
48
|
await expect(moderatorReq).rejects.toThrow(
|
|
51
49
|
'Must be an admin to create a communication template',
|
|
52
50
|
)
|
|
53
|
-
const modReq =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
51
|
+
const modReq = await agent.api.tools.ozone.communication.createTemplate(
|
|
52
|
+
{ ...templateOne, createdBy: sc.dids.bob },
|
|
53
|
+
{
|
|
54
|
+
encoding: 'application/json',
|
|
55
|
+
headers: await network.ozone.modHeaders('admin'),
|
|
56
|
+
},
|
|
57
|
+
)
|
|
61
58
|
|
|
62
59
|
expect(modReq.data).toMatchObject({
|
|
63
60
|
...templateOne,
|
|
@@ -75,7 +72,7 @@ describe('communication-templates', () => {
|
|
|
75
72
|
...templateOne,
|
|
76
73
|
name: 'Test template 2',
|
|
77
74
|
}
|
|
78
|
-
await agent.api.
|
|
75
|
+
await agent.api.tools.ozone.communication.createTemplate(
|
|
79
76
|
{ ...templateTwo, createdBy: sc.dids.bob },
|
|
80
77
|
{
|
|
81
78
|
encoding: 'application/json',
|
|
@@ -90,14 +87,13 @@ describe('communication-templates', () => {
|
|
|
90
87
|
})
|
|
91
88
|
describe('update template', () => {
|
|
92
89
|
it('allows moderators to update a template by id', async () => {
|
|
93
|
-
const { data } =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
90
|
+
const { data } = await agent.api.tools.ozone.communication.updateTemplate(
|
|
91
|
+
{ id: '1', updatedBy: sc.dids.bob, name: '1 Test template' },
|
|
92
|
+
{
|
|
93
|
+
encoding: 'application/json',
|
|
94
|
+
headers: await network.ozone.modHeaders('admin'),
|
|
95
|
+
},
|
|
96
|
+
)
|
|
101
97
|
|
|
102
98
|
expect(data.name).not.toEqual(templateOne.name)
|
|
103
99
|
expect(data.name).toEqual('1 Test template')
|
|
@@ -105,7 +101,7 @@ describe('communication-templates', () => {
|
|
|
105
101
|
})
|
|
106
102
|
describe('delete template', () => {
|
|
107
103
|
it('allows admins to remove a template by id', async () => {
|
|
108
|
-
const modReq = agent.api.
|
|
104
|
+
const modReq = agent.api.tools.ozone.communication.deleteTemplate(
|
|
109
105
|
{ id: '1' },
|
|
110
106
|
{
|
|
111
107
|
encoding: 'application/json',
|
|
@@ -117,7 +113,7 @@ describe('communication-templates', () => {
|
|
|
117
113
|
'Must be an admin to delete a communication template',
|
|
118
114
|
)
|
|
119
115
|
|
|
120
|
-
await agent.api.
|
|
116
|
+
await agent.api.tools.ozone.communication.deleteTemplate(
|
|
121
117
|
{ id: '1' },
|
|
122
118
|
{
|
|
123
119
|
encoding: 'application/json',
|