@atproto/ozone 0.0.16 → 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 (157) hide show
  1. package/dist/api/util.d.ts +10 -0
  2. package/dist/auth-verifier.d.ts +8 -12
  3. package/dist/communication-service/template.d.ts +2 -2
  4. package/dist/config/config.d.ts +6 -0
  5. package/dist/config/env.d.ts +3 -2
  6. package/dist/config/secrets.d.ts +0 -2
  7. package/dist/context.d.ts +6 -0
  8. package/dist/daemon/blob-diverter.d.ts +26 -0
  9. package/dist/daemon/event-pusher.d.ts +6 -0
  10. package/dist/daemon/index.d.ts +1 -0
  11. package/dist/db/index.js +21 -1
  12. package/dist/db/index.js.map +3 -3
  13. package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
  14. package/dist/db/migrations/index.d.ts +1 -0
  15. package/dist/db/schema/index.d.ts +2 -1
  16. package/dist/db/schema/label.d.ts +4 -0
  17. package/dist/db/schema/moderation_event.d.ts +1 -1
  18. package/dist/db/schema/moderation_subject_status.d.ts +2 -2
  19. package/dist/db/schema/signing_key.d.ts +9 -0
  20. package/dist/index.js +10400 -10313
  21. package/dist/index.js.map +3 -3
  22. package/dist/lexicon/index.d.ts +55 -27
  23. package/dist/lexicon/lexicons.d.ts +5046 -4757
  24. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
  25. package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
  26. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  27. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  28. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
  29. package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
  30. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
  31. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
  32. package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -0
  33. package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
  34. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
  35. package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
  36. package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
  37. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
  38. package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
  39. package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
  40. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
  41. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
  42. package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
  43. package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
  44. package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
  45. package/dist/mod-service/index.d.ts +16 -15
  46. package/dist/mod-service/subject.d.ts +1 -1
  47. package/dist/mod-service/types.d.ts +2 -2
  48. package/dist/mod-service/util.d.ts +6 -0
  49. package/dist/mod-service/views.d.ts +9 -3
  50. package/dist/sequencer/sequencer.d.ts +6 -4
  51. package/dist/util.d.ts +2 -0
  52. package/package.json +9 -8
  53. package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +2 -2
  54. package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +2 -2
  55. package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +2 -2
  56. package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +2 -2
  57. package/src/api/index.ts +21 -21
  58. package/src/api/{temp → label}/fetchLabels.ts +5 -3
  59. package/src/api/label/queryLabels.ts +4 -2
  60. package/src/api/moderation/emitEvent.ts +218 -0
  61. package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +2 -2
  62. package/src/api/{admin → moderation}/getRecord.ts +3 -3
  63. package/src/api/{admin → moderation}/getRepo.ts +3 -3
  64. package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +3 -3
  65. package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +3 -3
  66. package/src/api/{admin → moderation}/searchRepos.ts +2 -2
  67. package/src/api/proxied.ts +8 -8
  68. package/src/api/{moderation → report}/createReport.ts +2 -3
  69. package/src/api/util.ts +119 -0
  70. package/src/auth-verifier.ts +20 -30
  71. package/src/communication-service/template.ts +2 -2
  72. package/src/config/config.ts +24 -7
  73. package/src/config/env.ts +6 -4
  74. package/src/config/secrets.ts +0 -6
  75. package/src/context.ts +36 -12
  76. package/src/daemon/blob-diverter.ts +150 -0
  77. package/src/daemon/context.ts +11 -7
  78. package/src/daemon/event-pusher.ts +58 -15
  79. package/src/daemon/index.ts +1 -0
  80. package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
  81. package/src/db/migrations/index.ts +1 -0
  82. package/src/db/schema/index.ts +2 -0
  83. package/src/db/schema/label.ts +3 -0
  84. package/src/db/schema/moderation_event.ts +11 -11
  85. package/src/db/schema/moderation_subject_status.ts +7 -2
  86. package/src/db/schema/signing_key.ts +10 -0
  87. package/src/lexicon/index.ts +200 -137
  88. package/src/lexicon/lexicons.ts +6310 -6012
  89. package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
  90. package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
  91. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  92. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  93. package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
  94. package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
  95. package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
  96. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
  97. package/src/lexicon/types/com/atproto/label/defs.ts +78 -0
  98. package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
  99. package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
  100. package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
  101. package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
  102. package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
  103. package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
  104. package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
  105. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
  106. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
  107. package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
  108. package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
  109. package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
  110. package/src/mod-service/index.ts +46 -50
  111. package/src/mod-service/lang.ts +1 -1
  112. package/src/mod-service/status.ts +60 -41
  113. package/src/mod-service/subject.ts +1 -1
  114. package/src/mod-service/types.ts +10 -10
  115. package/src/mod-service/util.ts +49 -5
  116. package/src/mod-service/views.ts +45 -18
  117. package/src/sequencer/sequencer.ts +12 -11
  118. package/src/util.ts +21 -0
  119. package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
  120. package/tests/__snapshots__/get-record.test.ts.snap +14 -6
  121. package/tests/__snapshots__/get-repo.test.ts.snap +7 -3
  122. package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
  123. package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
  124. package/tests/_util.ts +5 -0
  125. package/tests/blob-divert.test.ts +87 -0
  126. package/tests/communication-templates.test.ts +33 -37
  127. package/tests/db.test.ts +6 -6
  128. package/tests/get-record.test.ts +22 -12
  129. package/tests/get-repo.test.ts +33 -21
  130. package/tests/moderation-appeals.test.ts +39 -67
  131. package/tests/moderation-events.test.ts +99 -142
  132. package/tests/moderation-status-tags.test.ts +20 -37
  133. package/tests/moderation-statuses.test.ts +132 -65
  134. package/tests/moderation.test.ts +147 -301
  135. package/tests/query-labels.test.ts +86 -10
  136. package/tests/repo-search.test.ts +18 -11
  137. package/tests/sequencer.test.ts +6 -3
  138. package/dist/api/admin/util.d.ts +0 -5
  139. package/dist/api/moderation/util.d.ts +0 -4
  140. package/src/api/admin/emitModerationEvent.ts +0 -170
  141. package/src/api/admin/util.ts +0 -54
  142. package/src/api/moderation/util.ts +0 -67
  143. /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
  144. /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
  145. /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
  146. /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
  147. /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
  148. /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
  149. /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
  150. /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
  151. /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
  152. /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
  153. /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
  154. /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
  155. /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
  156. /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
  157. /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
@@ -1,12 +1,16 @@
1
1
  import AtpAgent from '@atproto/api'
2
- import { TestNetwork } from '@atproto/dev-env'
2
+ import { EXAMPLE_LABELER, TestNetwork } from '@atproto/dev-env'
3
3
  import { DisconnectError, Subscription } from '@atproto/xrpc-server'
4
4
  import { ids, lexicons } from '../src/lexicon/lexicons'
5
5
  import { Label } from '../src/lexicon/types/com/atproto/label/defs'
6
+ import { Secp256k1Keypair, verifySignature } from '@atproto/crypto'
7
+ import { cborEncode } from '@atproto/common'
8
+ import { ModerationService } from '../src/mod-service'
6
9
  import {
7
10
  OutputSchema as LabelMessage,
8
11
  isLabels,
9
12
  } from '../src/lexicon/types/com/atproto/label/subscribeLabels'
13
+ import { getSigningKeyId } from '../src/util'
10
14
 
11
15
  describe('ozone query labels', () => {
12
16
  let network: TestNetwork
@@ -21,44 +25,44 @@ describe('ozone query labels', () => {
21
25
 
22
26
  agent = network.ozone.getClient()
23
27
 
24
- labels = [
28
+ const toCreate = [
25
29
  {
26
- src: 'did:example:labeler',
30
+ src: EXAMPLE_LABELER,
27
31
  uri: 'did:example:blah',
28
32
  val: 'spam',
29
33
  neg: false,
30
34
  cts: new Date().toISOString(),
31
35
  },
32
36
  {
33
- src: 'did:example:labeler',
37
+ src: EXAMPLE_LABELER,
34
38
  uri: 'did:example:blah',
35
39
  val: 'impersonation',
36
40
  neg: false,
37
41
  cts: new Date().toISOString(),
38
42
  },
39
43
  {
40
- src: 'did:example:labeler',
44
+ src: EXAMPLE_LABELER,
41
45
  uri: 'at://did:example:blah/app.bsky.feed.post/1234abcde',
42
46
  val: 'spam',
43
47
  neg: false,
44
48
  cts: new Date().toISOString(),
45
49
  },
46
50
  {
47
- src: 'did:example:labeler',
51
+ src: EXAMPLE_LABELER,
48
52
  uri: 'at://did:example:blah/app.bsky.feed.post/1234abcfg',
49
53
  val: 'spam',
50
54
  neg: false,
51
55
  cts: new Date().toISOString(),
52
56
  },
53
57
  {
54
- src: 'did:example:labeler',
58
+ src: EXAMPLE_LABELER,
55
59
  uri: 'at://did:example:blah/app.bsky.actor.profile/self',
56
60
  val: 'spam',
57
61
  neg: false,
58
62
  cts: new Date().toISOString(),
59
63
  },
60
64
  {
61
- src: 'did:example:labeler',
65
+ src: EXAMPLE_LABELER,
62
66
  uri: 'did:example:thing',
63
67
  val: 'spam',
64
68
  neg: false,
@@ -67,7 +71,7 @@ describe('ozone query labels', () => {
67
71
  ]
68
72
 
69
73
  const modService = network.ozone.ctx.modService(network.ozone.ctx.db)
70
- await modService.createLabels(labels)
74
+ labels = await modService.createLabels(toCreate)
71
75
  })
72
76
 
73
77
  afterAll(async () => {
@@ -128,6 +132,72 @@ describe('ozone query labels', () => {
128
132
  )
129
133
  })
130
134
 
135
+ it('returns validly signed labels', async () => {
136
+ const res = await agent.api.com.atproto.label.queryLabels({
137
+ uriPatterns: ['*'],
138
+ })
139
+ const signingKey = network.ozone.ctx.signingKey.did()
140
+ for (const label of res.data.labels) {
141
+ const { sig, ...rest } = label
142
+ if (!sig) {
143
+ throw new Error('Missing signature')
144
+ }
145
+ const encodedLabel = cborEncode(rest)
146
+ const isValid = await verifySignature(signingKey, encodedLabel, sig)
147
+ expect(isValid).toBe(true)
148
+ }
149
+ })
150
+
151
+ it('resigns labels if the signingKey changes', async () => {
152
+ // mock changing the signing key for the service
153
+ const ctx = network.ozone.ctx
154
+ const origModServiceFn = ctx.modService
155
+
156
+ const modSrvc = ctx.modService(ctx.db)
157
+ const newSigningKey = await Secp256k1Keypair.create()
158
+ const newSigningKeyId = await getSigningKeyId(ctx.db, newSigningKey.did())
159
+ ctx.devOverride({
160
+ modService: ModerationService.creator(
161
+ newSigningKey,
162
+ newSigningKeyId,
163
+ ctx.cfg,
164
+ modSrvc.backgroundQueue,
165
+ ctx.idResolver,
166
+ modSrvc.eventPusher,
167
+ modSrvc.appviewAgent,
168
+ ctx.serviceAuthHeaders,
169
+ ),
170
+ })
171
+
172
+ const res = await agent.api.com.atproto.label.queryLabels({
173
+ uriPatterns: ['*'],
174
+ })
175
+ for (const label of res.data.labels) {
176
+ const { sig, ...rest } = label
177
+ if (!sig) {
178
+ throw new Error('Missing signature')
179
+ }
180
+ const encodedLabel = cborEncode(rest)
181
+ const isValid = await verifySignature(
182
+ newSigningKey.did(),
183
+ encodedLabel,
184
+ sig,
185
+ )
186
+ expect(isValid).toBe(true)
187
+ }
188
+
189
+ await network.ozone.processAll()
190
+
191
+ const fromDb = await ctx.db.db.selectFrom('label').selectAll().execute()
192
+ expect(fromDb.every((row) => row.signingKeyId === newSigningKeyId)).toBe(
193
+ true,
194
+ )
195
+
196
+ ctx.devOverride({
197
+ modService: origModServiceFn,
198
+ })
199
+ })
200
+
131
201
  describe('subscribeLabels', () => {
132
202
  it('streams all labels from initial cursor.', async () => {
133
203
  const ac = new AbortController()
@@ -154,7 +224,13 @@ describe('ozone query labels', () => {
154
224
  for await (const message of sub) {
155
225
  resetDoneTimer()
156
226
  if (isLabels(message)) {
157
- streamedLabels.push(...message.labels)
227
+ for (const label of message.labels) {
228
+ // sigs are currently parsed as a Buffer which is a Uint8Array under the hood, but fails our equality test so we cast to Uint8Array
229
+ streamedLabels.push({
230
+ ...label,
231
+ sig: label.sig ? new Uint8Array(label.sig) : undefined,
232
+ })
233
+ }
158
234
  }
159
235
  }
160
236
  expect(streamedLabels).toEqual(labels)
@@ -1,4 +1,9 @@
1
- import { SeedClient, TestNetwork, usersBulkSeed } from '@atproto/dev-env'
1
+ import {
2
+ ModeratorClient,
3
+ SeedClient,
4
+ TestNetwork,
5
+ usersBulkSeed,
6
+ } from '@atproto/dev-env'
2
7
  import AtpAgent from '@atproto/api'
3
8
  import { paginateAll } from './_util'
4
9
 
@@ -6,16 +11,18 @@ describe('admin repo search view', () => {
6
11
  let network: TestNetwork
7
12
  let agent: AtpAgent
8
13
  let sc: SeedClient
14
+ let modClient: ModeratorClient
9
15
  let headers: { [s: string]: string }
10
16
 
11
17
  beforeAll(async () => {
12
18
  network = await TestNetwork.create({
13
19
  dbPostgresSchema: 'ozone_admin_repo_search',
14
20
  })
15
- agent = network.pds.getClient()
21
+ agent = network.ozone.getClient()
16
22
  sc = network.getSeedClient()
23
+ modClient = network.ozone.getModClient()
17
24
  await usersBulkSeed(sc)
18
- headers = network.pds.adminAuthHeaders()
25
+ headers = await network.ozone.modHeaders()
19
26
  await network.processAll()
20
27
  })
21
28
 
@@ -24,8 +31,8 @@ describe('admin repo search view', () => {
24
31
  })
25
32
 
26
33
  beforeAll(async () => {
27
- await sc.emitModerationEvent({
28
- event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
34
+ await modClient.emitEvent({
35
+ event: { $type: 'tools.ozone.moderation.defs#modEventTakedown' },
29
36
  subject: {
30
37
  $type: 'com.atproto.admin.defs#repoRef',
31
38
  did: sc.dids['cara-wiegand69.test'],
@@ -34,7 +41,7 @@ describe('admin repo search view', () => {
34
41
  })
35
42
 
36
43
  it('gives relevant results', async () => {
37
- const result = await agent.api.com.atproto.admin.searchRepos(
44
+ const result = await agent.api.tools.ozone.moderation.searchRepos(
38
45
  { term: 'car' },
39
46
  { headers },
40
47
  )
@@ -64,7 +71,7 @@ describe('admin repo search view', () => {
64
71
 
65
72
  it('finds repo by did', async () => {
66
73
  const term = sc.dids['cara-wiegand69.test']
67
- const res = await agent.api.com.atproto.admin.searchRepos(
74
+ const res = await agent.api.tools.ozone.moderation.searchRepos(
68
75
  { term },
69
76
  { headers },
70
77
  )
@@ -76,7 +83,7 @@ describe('admin repo search view', () => {
76
83
  it('paginates with term', async () => {
77
84
  const results = (results) => results.flatMap((res) => res.users)
78
85
  const paginator = async (cursor?: string) => {
79
- const res = await agent.api.com.atproto.admin.searchRepos(
86
+ const res = await agent.api.tools.ozone.moderation.searchRepos(
80
87
  { term: 'p', cursor, limit: 3 },
81
88
  { headers },
82
89
  )
@@ -88,7 +95,7 @@ describe('admin repo search view', () => {
88
95
  expect(res.repos.length).toBeLessThanOrEqual(3),
89
96
  )
90
97
 
91
- const full = await agent.api.com.atproto.admin.searchRepos(
98
+ const full = await agent.api.tools.ozone.moderation.searchRepos(
92
99
  { term: 'p' },
93
100
  { headers },
94
101
  )
@@ -100,7 +107,7 @@ describe('admin repo search view', () => {
100
107
  it('paginates without term', async () => {
101
108
  const results = (results) => results.flatMap((res) => res.repos)
102
109
  const paginator = async (cursor?: string) => {
103
- const res = await agent.api.com.atproto.admin.searchRepos(
110
+ const res = await agent.api.tools.ozone.moderation.searchRepos(
104
111
  { cursor, limit: 3 },
105
112
  { headers },
106
113
  )
@@ -112,7 +119,7 @@ describe('admin repo search view', () => {
112
119
  expect(res.repos.length).toBeLessThanOrEqual(3),
113
120
  )
114
121
 
115
- const full = await agent.api.com.atproto.admin.searchRepos(
122
+ const full = await agent.api.tools.ozone.moderation.searchRepos(
116
123
  { limit: 15 },
117
124
  { headers },
118
125
  )
@@ -1,4 +1,4 @@
1
- import { TestNetwork } from '@atproto/dev-env'
1
+ import { EXAMPLE_LABELER, TestNetwork } from '@atproto/dev-env'
2
2
  import { readFromGenerator, wait } from '@atproto/common'
3
3
  import { LabelsEvt, Sequencer } from '../src/sequencer'
4
4
  import Outbox from '../src/sequencer/outbox'
@@ -33,11 +33,14 @@ describe('sequencer', () => {
33
33
  }
34
34
 
35
35
  const evtToDbRow = (e: LabelsEvt) => {
36
- const label = e.labels[0]
36
+ const { ver: _, ...label } = e.labels[0]
37
37
  return {
38
38
  id: e.seq,
39
39
  ...label,
40
40
  cid: label.cid ? label.cid : '',
41
+ exp: null,
42
+ sig: label.sig ? Buffer.from(label.sig) : null,
43
+ signingKeyId: network.ozone.ctx.signingKeyId,
41
44
  }
42
45
  }
43
46
 
@@ -54,7 +57,7 @@ describe('sequencer', () => {
54
57
  for (let i = 0; i < count; i++) {
55
58
  const did = `did:example:${randomStr(10, 'base32')}`
56
59
  const label = {
57
- src: 'did:example:labeler',
60
+ src: EXAMPLE_LABELER,
58
61
  uri: did,
59
62
  val: 'spam',
60
63
  neg: false,
@@ -1,5 +0,0 @@
1
- import AppContext from '../../context';
2
- import { RepoView, RepoViewDetail, AccountView } from '../../lexicon/types/com/atproto/admin/defs';
3
- export declare const getPdsAccountInfo: (ctx: AppContext, did: string) => Promise<AccountView | null>;
4
- export declare const addAccountInfoToRepoViewDetail: (repoView: RepoViewDetail, accountInfo: AccountView | null, includeEmail?: boolean) => RepoViewDetail;
5
- export declare const addAccountInfoToRepoView: (repoView: RepoView, accountInfo: AccountView | null, includeEmail?: boolean) => RepoView;
@@ -1,4 +0,0 @@
1
- import { InputSchema as ReportInput } from '../../lexicon/types/com/atproto/moderation/createReport';
2
- export declare const getReasonType: (reasonType: ReportInput['reasonType']) => string | boolean;
3
- export declare const getEventType: (type: string) => "com.atproto.admin.defs#modEventTakedown" | "com.atproto.admin.defs#modEventAcknowledge" | "com.atproto.admin.defs#modEventEscalate" | "com.atproto.admin.defs#modEventComment" | "com.atproto.admin.defs#modEventLabel" | "com.atproto.admin.defs#modEventReport" | "com.atproto.admin.defs#modEventMute" | "com.atproto.admin.defs#modEventReverseTakedown" | "com.atproto.admin.defs#modEventEmail" | "com.atproto.admin.defs#modEventResolveAppeal" | "com.atproto.admin.defs#modEventTag";
4
- export declare const getReviewState: (reviewState?: string) => "com.atproto.admin.defs#reviewOpen" | "com.atproto.admin.defs#reviewEscalated" | "com.atproto.admin.defs#reviewClosed" | undefined;
@@ -1,170 +0,0 @@
1
- import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
2
- import { Server } from '../../lexicon'
3
- import AppContext from '../../context'
4
- import {
5
- isModEventEmail,
6
- isModEventLabel,
7
- isModEventReverseTakedown,
8
- isModEventTakedown,
9
- } from '../../lexicon/types/com/atproto/admin/defs'
10
- import { subjectFromInput } from '../../mod-service/subject'
11
- import { ModerationLangService } from '../../mod-service/lang'
12
- import { retryHttp } from '../../util'
13
-
14
- export default function (server: Server, ctx: AppContext) {
15
- server.com.atproto.admin.emitModerationEvent({
16
- auth: ctx.authVerifier.modOrRole,
17
- handler: async ({ input, auth }) => {
18
- const access = auth.credentials
19
- const db = ctx.db
20
- const moderationService = ctx.modService(db)
21
- const { createdBy, event } = input.body
22
- const isTakedownEvent = isModEventTakedown(event)
23
- const isReverseTakedownEvent = isModEventReverseTakedown(event)
24
- const isLabelEvent = isModEventLabel(event)
25
- const subject = subjectFromInput(
26
- input.body.subject,
27
- input.body.subjectBlobCids,
28
- )
29
-
30
- // apply access rules
31
-
32
- // if less than moderator access then can only take ack and escalation actions
33
- if (isTakedownEvent || isReverseTakedownEvent) {
34
- if (!access.isModerator) {
35
- throw new AuthRequiredError(
36
- 'Must be a full moderator to take this type of action',
37
- )
38
- }
39
-
40
- // Non admins should not be able to take down feed generators
41
- if (
42
- !access.isAdmin &&
43
- subject.recordPath?.includes('app.bsky.feed.generator/')
44
- ) {
45
- throw new AuthRequiredError(
46
- 'Must be a full admin to take this type of action on feed generators',
47
- )
48
- }
49
- }
50
- // if less than moderator access then can not apply labels
51
- if (!access.isModerator && isLabelEvent) {
52
- throw new AuthRequiredError('Must be a full moderator to label content')
53
- }
54
-
55
- if (isLabelEvent) {
56
- validateLabels([
57
- ...(event.createLabelVals ?? []),
58
- ...(event.negateLabelVals ?? []),
59
- ])
60
- }
61
-
62
- if (isTakedownEvent || isReverseTakedownEvent) {
63
- const status = await moderationService.getStatus(subject)
64
-
65
- if (status?.takendown && isTakedownEvent) {
66
- throw new InvalidRequestError(`Subject is already taken down`)
67
- }
68
-
69
- if (!status?.takendown && isReverseTakedownEvent) {
70
- throw new InvalidRequestError(`Subject is not taken down`)
71
- }
72
-
73
- if (status?.takendown && isReverseTakedownEvent && subject.isRecord()) {
74
- // due to the way blob status is modeled, we should reverse takedown on all
75
- // blobs for the record being restored, which aren't taken down on another record.
76
- subject.blobCids = status.blobCids ?? []
77
- }
78
- }
79
-
80
- if (isModEventEmail(event) && event.content) {
81
- // sending email prior to logging the event to avoid a long transaction below
82
- if (!subject.isRepo()) {
83
- throw new InvalidRequestError(
84
- 'Email can only be sent to a repo subject',
85
- )
86
- }
87
- const { content, subjectLine } = event
88
- await retryHttp(() =>
89
- ctx.modService(db).sendEmail({
90
- subject: subjectLine,
91
- content,
92
- recipientDid: subject.did,
93
- }),
94
- )
95
- }
96
-
97
- const moderationEvent = await db.transaction(async (dbTxn) => {
98
- const moderationTxn = ctx.modService(dbTxn)
99
-
100
- const result = await moderationTxn.logEvent({
101
- event,
102
- subject,
103
- createdBy,
104
- })
105
-
106
- const moderationLangService = new ModerationLangService(moderationTxn)
107
- await moderationLangService.tagSubjectWithLang({
108
- subject,
109
- createdBy: ctx.cfg.service.did,
110
- subjectStatus: result.subjectStatus,
111
- })
112
-
113
- if (subject.isRepo()) {
114
- if (isTakedownEvent) {
115
- const isSuspend = !!result.event.durationInHours
116
- await moderationTxn.takedownRepo(
117
- subject,
118
- result.event.id,
119
- isSuspend,
120
- )
121
- } else if (isReverseTakedownEvent) {
122
- await moderationTxn.reverseTakedownRepo(subject)
123
- }
124
- }
125
-
126
- if (subject.isRecord()) {
127
- if (isTakedownEvent) {
128
- await moderationTxn.takedownRecord(subject, result.event.id)
129
- } else if (isReverseTakedownEvent) {
130
- await moderationTxn.reverseTakedownRecord(subject)
131
- }
132
- }
133
-
134
- if (isLabelEvent) {
135
- await moderationTxn.formatAndCreateLabels(
136
- result.event.subjectUri ?? result.event.subjectDid,
137
- result.event.subjectCid,
138
- {
139
- create: result.event.createLabelVals?.length
140
- ? result.event.createLabelVals.split(' ')
141
- : undefined,
142
- negate: result.event.negateLabelVals?.length
143
- ? result.event.negateLabelVals.split(' ')
144
- : undefined,
145
- },
146
- )
147
- }
148
-
149
- return result.event
150
- })
151
-
152
- return {
153
- encoding: 'application/json',
154
- body: moderationService.views.formatEvent(moderationEvent),
155
- }
156
- },
157
- })
158
- }
159
-
160
- const validateLabels = (labels: string[]) => {
161
- for (const label of labels) {
162
- for (const char of badChars) {
163
- if (label.includes(char)) {
164
- throw new InvalidRequestError(`Invalid label: ${label}`)
165
- }
166
- }
167
- }
168
- }
169
-
170
- const badChars = [' ', ',', ';', `'`, `"`]
@@ -1,54 +0,0 @@
1
- import AppContext from '../../context'
2
- import {
3
- RepoView,
4
- RepoViewDetail,
5
- AccountView,
6
- } from '../../lexicon/types/com/atproto/admin/defs'
7
-
8
- export const getPdsAccountInfo = async (
9
- ctx: AppContext,
10
- did: string,
11
- ): Promise<AccountView | null> => {
12
- const agent = ctx.pdsAgent
13
- if (!agent) return null
14
- const auth = await ctx.pdsAuth()
15
- if (!auth) return null
16
- try {
17
- const res = await agent.api.com.atproto.admin.getAccountInfo({ did }, auth)
18
- return res.data
19
- } catch (err) {
20
- return null
21
- }
22
- }
23
-
24
- export const addAccountInfoToRepoViewDetail = (
25
- repoView: RepoViewDetail,
26
- accountInfo: AccountView | null,
27
- includeEmail = false,
28
- ): RepoViewDetail => {
29
- if (!accountInfo) return repoView
30
- return {
31
- ...repoView,
32
- email: includeEmail ? accountInfo.email : undefined,
33
- invitedBy: accountInfo.invitedBy,
34
- invitesDisabled: accountInfo.invitesDisabled,
35
- inviteNote: accountInfo.inviteNote,
36
- invites: accountInfo.invites,
37
- emailConfirmedAt: accountInfo.emailConfirmedAt,
38
- }
39
- }
40
-
41
- export const addAccountInfoToRepoView = (
42
- repoView: RepoView,
43
- accountInfo: AccountView | null,
44
- includeEmail = false,
45
- ): RepoView => {
46
- if (!accountInfo) return repoView
47
- return {
48
- ...repoView,
49
- email: includeEmail ? accountInfo.email : undefined,
50
- invitedBy: accountInfo.invitedBy,
51
- invitesDisabled: accountInfo.invitesDisabled,
52
- inviteNote: accountInfo.inviteNote,
53
- }
54
- }
@@ -1,67 +0,0 @@
1
- import { InvalidRequestError } from '@atproto/xrpc-server'
2
- import { InputSchema as ReportInput } from '../../lexicon/types/com/atproto/moderation/createReport'
3
- import {
4
- REASONOTHER,
5
- REASONSPAM,
6
- REASONMISLEADING,
7
- REASONRUDE,
8
- REASONSEXUAL,
9
- REASONVIOLATION,
10
- REASONAPPEAL,
11
- } from '../../lexicon/types/com/atproto/moderation/defs'
12
- import {
13
- REVIEWCLOSED,
14
- REVIEWESCALATED,
15
- REVIEWOPEN,
16
- } from '../../lexicon/types/com/atproto/admin/defs'
17
- import { ModerationEvent } from '../../db/schema/moderation_event'
18
- import { ModerationSubjectStatusRow } from '../../mod-service/types'
19
-
20
- export const getReasonType = (reasonType: ReportInput['reasonType']) => {
21
- if (reasonTypes.has(reasonType)) {
22
- return reasonType as NonNullable<ModerationEvent['meta']>['reportType']
23
- }
24
- throw new InvalidRequestError('Invalid reason type')
25
- }
26
-
27
- export const getEventType = (type: string) => {
28
- if (eventTypes.has(type)) {
29
- return type as ModerationEvent['action']
30
- }
31
- throw new InvalidRequestError('Invalid event type')
32
- }
33
-
34
- export const getReviewState = (reviewState?: string) => {
35
- if (!reviewState) return undefined
36
- if (reviewStates.has(reviewState)) {
37
- return reviewState as ModerationSubjectStatusRow['reviewState']
38
- }
39
- throw new InvalidRequestError('Invalid review state')
40
- }
41
-
42
- const reviewStates = new Set([REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN])
43
-
44
- const reasonTypes = new Set([
45
- REASONOTHER,
46
- REASONSPAM,
47
- REASONMISLEADING,
48
- REASONRUDE,
49
- REASONSEXUAL,
50
- REASONVIOLATION,
51
- REASONAPPEAL,
52
- ])
53
-
54
- const eventTypes = new Set([
55
- 'com.atproto.admin.defs#modEventTakedown',
56
- 'com.atproto.admin.defs#modEventAcknowledge',
57
- 'com.atproto.admin.defs#modEventEscalate',
58
- 'com.atproto.admin.defs#modEventComment',
59
- 'com.atproto.admin.defs#modEventLabel',
60
- 'com.atproto.admin.defs#modEventReport',
61
- 'com.atproto.admin.defs#modEventMute',
62
- 'com.atproto.admin.defs#modEventUnmute',
63
- 'com.atproto.admin.defs#modEventReverseTakedown',
64
- 'com.atproto.admin.defs#modEventEmail',
65
- 'com.atproto.admin.defs#modEventResolveAppeal',
66
- 'com.atproto.admin.defs#modEventTag',
67
- ])
File without changes