@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.
Files changed (146) hide show
  1. package/dist/api/util.d.ts +10 -0
  2. package/dist/auth-verifier.d.ts +2 -2
  3. package/dist/communication-service/template.d.ts +2 -2
  4. package/dist/config/config.d.ts +5 -0
  5. package/dist/config/env.d.ts +2 -0
  6. package/dist/context.d.ts +6 -0
  7. package/dist/daemon/blob-diverter.d.ts +26 -0
  8. package/dist/daemon/event-pusher.d.ts +4 -0
  9. package/dist/daemon/index.d.ts +1 -0
  10. package/dist/db/index.js +21 -1
  11. package/dist/db/index.js.map +3 -3
  12. package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
  13. package/dist/db/migrations/index.d.ts +1 -0
  14. package/dist/db/schema/index.d.ts +2 -1
  15. package/dist/db/schema/label.d.ts +4 -0
  16. package/dist/db/schema/moderation_event.d.ts +1 -1
  17. package/dist/db/schema/moderation_subject_status.d.ts +1 -1
  18. package/dist/db/schema/signing_key.d.ts +9 -0
  19. package/dist/index.js +10949 -10628
  20. package/dist/index.js.map +3 -3
  21. package/dist/lexicon/index.d.ts +48 -28
  22. package/dist/lexicon/lexicons.d.ts +4641 -4650
  23. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +7 -7
  24. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  25. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  26. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -305
  27. package/dist/lexicon/types/com/atproto/label/defs.d.ts +5 -0
  28. package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
  29. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
  30. package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
  31. package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
  33. package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
  34. package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
  35. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
  36. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
  37. package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
  38. package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
  39. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
  40. package/dist/mod-service/index.d.ts +16 -15
  41. package/dist/mod-service/subject.d.ts +1 -1
  42. package/dist/mod-service/types.d.ts +2 -2
  43. package/dist/mod-service/util.d.ts +6 -0
  44. package/dist/mod-service/views.d.ts +9 -3
  45. package/dist/sequencer/sequencer.d.ts +6 -4
  46. package/dist/util.d.ts +2 -0
  47. package/package.json +7 -7
  48. package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +1 -1
  49. package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +1 -1
  50. package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +1 -1
  51. package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +1 -1
  52. package/src/api/index.ts +21 -21
  53. package/src/api/{temp → label}/fetchLabels.ts +4 -2
  54. package/src/api/label/queryLabels.ts +4 -2
  55. package/src/api/moderation/emitEvent.ts +218 -0
  56. package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +1 -1
  57. package/src/api/{admin → moderation}/getRecord.ts +2 -2
  58. package/src/api/{admin → moderation}/getRepo.ts +2 -2
  59. package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +2 -2
  60. package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +2 -2
  61. package/src/api/{admin → moderation}/searchRepos.ts +1 -1
  62. package/src/api/{moderation → report}/createReport.ts +1 -1
  63. package/src/api/util.ts +119 -0
  64. package/src/auth-verifier.ts +2 -2
  65. package/src/communication-service/template.ts +2 -2
  66. package/src/config/config.ts +14 -0
  67. package/src/config/env.ts +4 -0
  68. package/src/context.ts +35 -9
  69. package/src/daemon/blob-diverter.ts +150 -0
  70. package/src/daemon/context.ts +9 -5
  71. package/src/daemon/event-pusher.ts +49 -14
  72. package/src/daemon/index.ts +1 -0
  73. package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
  74. package/src/db/migrations/index.ts +1 -0
  75. package/src/db/schema/index.ts +2 -0
  76. package/src/db/schema/label.ts +3 -0
  77. package/src/db/schema/moderation_event.ts +11 -11
  78. package/src/db/schema/moderation_subject_status.ts +1 -1
  79. package/src/db/schema/signing_key.ts +10 -0
  80. package/src/lexicon/index.ts +178 -138
  81. package/src/lexicon/lexicons.ts +6078 -6106
  82. package/src/lexicon/types/app/bsky/actor/defs.ts +11 -11
  83. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  84. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  85. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -697
  86. package/src/lexicon/types/com/atproto/label/defs.ts +10 -0
  87. package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
  88. package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
  89. package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
  90. package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
  91. package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
  92. package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
  93. package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
  94. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
  95. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
  96. package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
  97. package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
  98. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
  99. package/src/mod-service/index.ts +42 -47
  100. package/src/mod-service/lang.ts +1 -1
  101. package/src/mod-service/status.ts +19 -16
  102. package/src/mod-service/subject.ts +1 -1
  103. package/src/mod-service/types.ts +10 -10
  104. package/src/mod-service/util.ts +49 -5
  105. package/src/mod-service/views.ts +45 -18
  106. package/src/sequencer/sequencer.ts +12 -11
  107. package/src/util.ts +21 -0
  108. package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
  109. package/tests/__snapshots__/get-record.test.ts.snap +10 -2
  110. package/tests/__snapshots__/get-repo.test.ts.snap +5 -1
  111. package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
  112. package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
  113. package/tests/_util.ts +5 -0
  114. package/tests/blob-divert.test.ts +87 -0
  115. package/tests/communication-templates.test.ts +30 -34
  116. package/tests/db.test.ts +6 -6
  117. package/tests/get-record.test.ts +6 -6
  118. package/tests/get-repo.test.ts +11 -11
  119. package/tests/moderation-appeals.test.ts +28 -28
  120. package/tests/moderation-events.test.ts +44 -44
  121. package/tests/moderation-status-tags.test.ts +8 -10
  122. package/tests/moderation-statuses.test.ts +27 -27
  123. package/tests/moderation.test.ts +50 -57
  124. package/tests/query-labels.test.ts +86 -10
  125. package/tests/repo-search.test.ts +8 -8
  126. package/tests/sequencer.test.ts +6 -3
  127. package/dist/api/admin/util.d.ts +0 -5
  128. package/dist/api/moderation/util.d.ts +0 -4
  129. package/src/api/admin/emitModerationEvent.ts +0 -174
  130. package/src/api/admin/util.ts +0 -54
  131. package/src/api/moderation/util.ts +0 -67
  132. /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
  133. /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
  134. /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
  135. /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
  136. /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
  137. /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
  138. /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
  139. /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
  140. /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
  141. /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
  142. /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
  143. /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
  144. /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
  145. /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
  146. /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
@@ -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
- AccountView,
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 { httpLogger as log } from '../logger'
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
- log.error({ err, dids }, 'failed to resolve account infos from appview')
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
- 'com.atproto.admin.defs#modEventTakedown',
105
- 'com.atproto.admin.defs#modEventMute',
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 === 'com.atproto.admin.defs#modEventLabel') {
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
- 'com.atproto.admin.defs#modEventAcknowledge',
130
- 'com.atproto.admin.defs#modEventTakedown',
131
- 'com.atproto.admin.defs#modEventEscalate',
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 === 'com.atproto.admin.defs#modEventReport') {
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 === 'com.atproto.admin.defs#modEventEmail') {
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 === 'com.atproto.admin.defs#modEventComment' &&
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 === 'com.atproto.admin.defs#modEventTag') {
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) => formatLabel(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'] & ReportViewDetail['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 { Selectable } from 'kysely'
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 db: Database, public lastSeen = 0) {
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
- for (const row of rows) {
94
- evts.push({
95
- seq: row.id,
96
- labels: [formatLabel(row)],
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": "com.atproto.admin.defs#reviewClosed",
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": "com.atproto.admin.defs#reviewClosed",
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": "com.atproto.admin.defs#reviewClosed",
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": "com.atproto.admin.defs#modEventReport",
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": "com.atproto.admin.defs#reviewEscalated",
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": "com.atproto.admin.defs#modEventReport",
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": "com.atproto.admin.defs#modEventTag",
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": "com.atproto.admin.defs#modEventReport",
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": "com.atproto.admin.defs#modEventReport",
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": "com.atproto.admin.defs#modEventTag",
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": "com.atproto.admin.defs#modEventReport",
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": "com.atproto.admin.defs#reviewOpen",
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": "com.atproto.admin.defs#reviewOpen",
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": "com.atproto.admin.defs#reviewOpen",
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": "com.atproto.admin.defs#reviewOpen",
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": "com.atproto.admin.defs#reviewOpen",
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": "com.atproto.admin.defs#reviewOpen",
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
- await agent.api.com.atproto.admin.listCommunicationTemplates(
32
- {},
33
- {
34
- headers: await network.ozone.modHeaders('moderator'),
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
- agent.api.com.atproto.admin.createCommunicationTemplate(
44
- { ...templateOne, createdBy: sc.dids.bob },
45
- {
46
- encoding: 'application/json',
47
- headers: await network.ozone.modHeaders('moderator'),
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
- await agent.api.com.atproto.admin.createCommunicationTemplate(
55
- { ...templateOne, createdBy: sc.dids.bob },
56
- {
57
- encoding: 'application/json',
58
- headers: await network.ozone.modHeaders('admin'),
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.com.atproto.admin.createCommunicationTemplate(
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
- await agent.api.com.atproto.admin.updateCommunicationTemplate(
95
- { id: '1', updatedBy: sc.dids.bob, name: '1 Test template' },
96
- {
97
- encoding: 'application/json',
98
- headers: await network.ozone.modHeaders('admin'),
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.com.atproto.admin.deleteCommunicationTemplate(
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.com.atproto.admin.deleteCommunicationTemplate(
116
+ await agent.api.tools.ozone.communication.deleteTemplate(
121
117
  { id: '1' },
122
118
  {
123
119
  encoding: 'application/json',