@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.
- package/dist/api/util.d.ts +10 -0
- package/dist/auth-verifier.d.ts +8 -12
- package/dist/communication-service/template.d.ts +2 -2
- package/dist/config/config.d.ts +6 -0
- package/dist/config/env.d.ts +3 -2
- package/dist/config/secrets.d.ts +0 -2
- package/dist/context.d.ts +6 -0
- package/dist/daemon/blob-diverter.d.ts +26 -0
- package/dist/daemon/event-pusher.d.ts +6 -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 +2 -2
- package/dist/db/schema/signing_key.d.ts +9 -0
- package/dist/index.js +10400 -10313
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +55 -27
- package/dist/lexicon/lexicons.d.ts +5046 -4757
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
- package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
- 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/app/bsky/labeler/defs.d.ts +41 -0
- package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
- package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
- package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -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 +9 -8
- package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +2 -2
- package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +2 -2
- package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +2 -2
- package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +2 -2
- package/src/api/index.ts +21 -21
- package/src/api/{temp → label}/fetchLabels.ts +5 -3
- 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} +2 -2
- package/src/api/{admin → moderation}/getRecord.ts +3 -3
- package/src/api/{admin → moderation}/getRepo.ts +3 -3
- package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +3 -3
- package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +3 -3
- package/src/api/{admin → moderation}/searchRepos.ts +2 -2
- package/src/api/proxied.ts +8 -8
- package/src/api/{moderation → report}/createReport.ts +2 -3
- package/src/api/util.ts +119 -0
- package/src/auth-verifier.ts +20 -30
- package/src/communication-service/template.ts +2 -2
- package/src/config/config.ts +24 -7
- package/src/config/env.ts +6 -4
- package/src/config/secrets.ts +0 -6
- package/src/context.ts +36 -12
- package/src/daemon/blob-diverter.ts +150 -0
- package/src/daemon/context.ts +11 -7
- package/src/daemon/event-pusher.ts +58 -15
- 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 +7 -2
- package/src/db/schema/signing_key.ts +10 -0
- package/src/lexicon/index.ts +200 -137
- package/src/lexicon/lexicons.ts +6310 -6012
- package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
- package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
- 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/app/bsky/labeler/defs.ts +93 -0
- package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
- package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
- package/src/lexicon/types/com/atproto/label/defs.ts +78 -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 +46 -50
- package/src/mod-service/lang.ts +1 -1
- package/src/mod-service/status.ts +60 -41
- 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 +14 -6
- package/tests/__snapshots__/get-repo.test.ts.snap +7 -3
- 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 +33 -37
- package/tests/db.test.ts +6 -6
- package/tests/get-record.test.ts +22 -12
- package/tests/get-repo.test.ts +33 -21
- package/tests/moderation-appeals.test.ts +39 -67
- package/tests/moderation-events.test.ts +99 -142
- package/tests/moderation-status-tags.test.ts +20 -37
- package/tests/moderation-statuses.test.ts +132 -65
- package/tests/moderation.test.ts +147 -301
- package/tests/query-labels.test.ts +86 -10
- package/tests/repo-search.test.ts +18 -11
- 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 -170
- 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
|
@@ -3,8 +3,8 @@ import { Server } from '../../lexicon'
|
|
|
3
3
|
import AppContext from '../../context'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.communication.createTemplate({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ input, auth }) => {
|
|
9
9
|
const access = auth.credentials
|
|
10
10
|
const db = ctx.db
|
|
@@ -3,8 +3,8 @@ import { Server } from '../../lexicon'
|
|
|
3
3
|
import AppContext from '../../context'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.communication.deleteTemplate({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ input, auth }) => {
|
|
9
9
|
const access = auth.credentials
|
|
10
10
|
const db = ctx.db
|
|
@@ -3,8 +3,8 @@ import { Server } from '../../lexicon'
|
|
|
3
3
|
import AppContext from '../../context'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.communication.listTemplates({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ auth }) => {
|
|
9
9
|
const access = auth.credentials
|
|
10
10
|
const db = ctx.db
|
|
@@ -3,8 +3,8 @@ import { Server } from '../../lexicon'
|
|
|
3
3
|
import AppContext from '../../context'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.communication.updateTemplate({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ input, auth }) => {
|
|
9
9
|
const access = auth.credentials
|
|
10
10
|
const db = ctx.db
|
package/src/api/index.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Server } from '../lexicon'
|
|
2
2
|
import AppContext from '../context'
|
|
3
|
-
import createReport from './
|
|
4
|
-
import
|
|
5
|
-
import searchRepos from './
|
|
6
|
-
import adminGetRecord from './
|
|
7
|
-
import getRepo from './
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
3
|
+
import createReport from './report/createReport'
|
|
4
|
+
import emitEvent from './moderation/emitEvent'
|
|
5
|
+
import searchRepos from './moderation/searchRepos'
|
|
6
|
+
import adminGetRecord from './moderation/getRecord'
|
|
7
|
+
import getRepo from './moderation/getRepo'
|
|
8
|
+
import queryStatuses from './moderation/queryStatuses'
|
|
9
|
+
import queryEvents from './moderation/queryEvents'
|
|
10
|
+
import getEvent from './moderation/getEvent'
|
|
11
11
|
import queryLabels from './label/queryLabels'
|
|
12
12
|
import subscribeLabels from './label/subscribeLabels'
|
|
13
|
-
import fetchLabels from './
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
13
|
+
import fetchLabels from './label/fetchLabels'
|
|
14
|
+
import createTemplate from './communication/createTemplate'
|
|
15
|
+
import updateTemplate from './communication/updateTemplate'
|
|
16
|
+
import deleteTemplate from './communication/deleteTemplate'
|
|
17
|
+
import listTemplates from './communication/listTemplates'
|
|
18
18
|
import proxied from './proxied'
|
|
19
19
|
|
|
20
20
|
export * as health from './health'
|
|
@@ -23,20 +23,20 @@ export * as wellKnown from './well-known'
|
|
|
23
23
|
|
|
24
24
|
export default function (server: Server, ctx: AppContext) {
|
|
25
25
|
createReport(server, ctx)
|
|
26
|
-
|
|
26
|
+
emitEvent(server, ctx)
|
|
27
27
|
searchRepos(server, ctx)
|
|
28
28
|
adminGetRecord(server, ctx)
|
|
29
29
|
getRepo(server, ctx)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
getEvent(server, ctx)
|
|
31
|
+
queryEvents(server, ctx)
|
|
32
|
+
queryStatuses(server, ctx)
|
|
33
33
|
queryLabels(server, ctx)
|
|
34
34
|
subscribeLabels(server, ctx)
|
|
35
35
|
fetchLabels(server, ctx)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
listTemplates(server, ctx)
|
|
37
|
+
createTemplate(server, ctx)
|
|
38
|
+
updateTemplate(server, ctx)
|
|
39
|
+
deleteTemplate(server, ctx)
|
|
40
40
|
proxied(server, ctx)
|
|
41
41
|
return server
|
|
42
42
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Server } from '../../lexicon'
|
|
2
2
|
import AppContext from '../../context'
|
|
3
|
-
import { formatLabel } from '../../mod-service/util'
|
|
4
3
|
import {
|
|
5
4
|
UNSPECCED_TAKEDOWN_BLOBS_LABEL,
|
|
6
5
|
UNSPECCED_TAKEDOWN_LABEL,
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
|
|
9
8
|
export default function (server: Server, ctx: AppContext) {
|
|
10
9
|
server.com.atproto.temp.fetchLabels({
|
|
11
|
-
auth: ctx.authVerifier.
|
|
10
|
+
auth: ctx.authVerifier.standardOptionalOrAdminToken,
|
|
12
11
|
handler: async ({ auth, params }) => {
|
|
13
12
|
const { limit } = params
|
|
14
13
|
const since =
|
|
@@ -29,7 +28,10 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
29
28
|
.limit(limit)
|
|
30
29
|
.execute()
|
|
31
30
|
|
|
32
|
-
const
|
|
31
|
+
const modSrvc = ctx.modService(ctx.db)
|
|
32
|
+
const labels = await Promise.all(
|
|
33
|
+
labelRes.map((l) => modSrvc.views.formatLabelAndEnsureSig(l)),
|
|
34
|
+
)
|
|
33
35
|
|
|
34
36
|
return {
|
|
35
37
|
encoding: 'application/json',
|
|
@@ -2,7 +2,6 @@ import { Server } from '../../lexicon'
|
|
|
2
2
|
import AppContext from '../../context'
|
|
3
3
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
4
4
|
import { sql } from 'kysely'
|
|
5
|
-
import { formatLabel } from '../../mod-service/util'
|
|
6
5
|
|
|
7
6
|
export default function (server: Server, ctx: AppContext) {
|
|
8
7
|
server.com.atproto.label.queryLabels(async ({ params }) => {
|
|
@@ -44,7 +43,10 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
44
43
|
|
|
45
44
|
const res = await builder.execute()
|
|
46
45
|
|
|
47
|
-
const
|
|
46
|
+
const modSrvc = ctx.modService(ctx.db)
|
|
47
|
+
const labels = await Promise.all(
|
|
48
|
+
res.map((l) => modSrvc.views.formatLabelAndEnsureSig(l)),
|
|
49
|
+
)
|
|
48
50
|
const resCursor = res.at(-1)?.id.toString(10)
|
|
49
51
|
|
|
50
52
|
return {
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
|
+
import { Server } from '../../lexicon'
|
|
3
|
+
import AppContext from '../../context'
|
|
4
|
+
import {
|
|
5
|
+
isModEventDivert,
|
|
6
|
+
isModEventEmail,
|
|
7
|
+
isModEventLabel,
|
|
8
|
+
isModEventReverseTakedown,
|
|
9
|
+
isModEventTakedown,
|
|
10
|
+
} from '../../lexicon/types/tools/ozone/moderation/defs'
|
|
11
|
+
import { HandlerInput } from '../../lexicon/types/tools/ozone/moderation/emitEvent'
|
|
12
|
+
import { subjectFromInput } from '../../mod-service/subject'
|
|
13
|
+
import { ModerationLangService } from '../../mod-service/lang'
|
|
14
|
+
import { retryHttp } from '../../util'
|
|
15
|
+
import { ModeratorOutput, AdminTokenOutput } from '../../auth-verifier'
|
|
16
|
+
|
|
17
|
+
const handleModerationEvent = async ({
|
|
18
|
+
ctx,
|
|
19
|
+
input,
|
|
20
|
+
auth,
|
|
21
|
+
}: {
|
|
22
|
+
ctx: AppContext
|
|
23
|
+
input: HandlerInput
|
|
24
|
+
auth: ModeratorOutput | AdminTokenOutput
|
|
25
|
+
}) => {
|
|
26
|
+
const access = auth.credentials
|
|
27
|
+
const createdBy =
|
|
28
|
+
auth.credentials.type === 'moderator'
|
|
29
|
+
? auth.credentials.iss
|
|
30
|
+
: input.body.createdBy
|
|
31
|
+
const db = ctx.db
|
|
32
|
+
const moderationService = ctx.modService(db)
|
|
33
|
+
const { event } = input.body
|
|
34
|
+
const isTakedownEvent = isModEventTakedown(event)
|
|
35
|
+
const isReverseTakedownEvent = isModEventReverseTakedown(event)
|
|
36
|
+
const isLabelEvent = isModEventLabel(event)
|
|
37
|
+
const subject = subjectFromInput(
|
|
38
|
+
input.body.subject,
|
|
39
|
+
input.body.subjectBlobCids,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// apply access rules
|
|
43
|
+
|
|
44
|
+
// if less than moderator access then can only take ack and escalation actions
|
|
45
|
+
if (isTakedownEvent || isReverseTakedownEvent) {
|
|
46
|
+
if (!access.isModerator) {
|
|
47
|
+
throw new AuthRequiredError(
|
|
48
|
+
'Must be a full moderator to take this type of action',
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Non admins should not be able to take down feed generators
|
|
53
|
+
if (
|
|
54
|
+
!access.isAdmin &&
|
|
55
|
+
subject.recordPath?.includes('app.bsky.feed.generator/')
|
|
56
|
+
) {
|
|
57
|
+
throw new AuthRequiredError(
|
|
58
|
+
'Must be a full admin to take this type of action on feed generators',
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// if less than moderator access then can not apply labels
|
|
63
|
+
if (!access.isModerator && isLabelEvent) {
|
|
64
|
+
throw new AuthRequiredError('Must be a full moderator to label content')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isLabelEvent) {
|
|
68
|
+
validateLabels([
|
|
69
|
+
...(event.createLabelVals ?? []),
|
|
70
|
+
...(event.negateLabelVals ?? []),
|
|
71
|
+
])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isTakedownEvent || isReverseTakedownEvent) {
|
|
75
|
+
const status = await moderationService.getStatus(subject)
|
|
76
|
+
|
|
77
|
+
if (status?.takendown && isTakedownEvent) {
|
|
78
|
+
throw new InvalidRequestError(`Subject is already taken down`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!status?.takendown && isReverseTakedownEvent) {
|
|
82
|
+
throw new InvalidRequestError(`Subject is not taken down`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (status?.takendown && isReverseTakedownEvent && subject.isRecord()) {
|
|
86
|
+
// due to the way blob status is modeled, we should reverse takedown on all
|
|
87
|
+
// blobs for the record being restored, which aren't taken down on another record.
|
|
88
|
+
subject.blobCids = status.blobCids ?? []
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isModEventEmail(event) && event.content) {
|
|
93
|
+
// sending email prior to logging the event to avoid a long transaction below
|
|
94
|
+
if (!subject.isRepo()) {
|
|
95
|
+
throw new InvalidRequestError('Email can only be sent to a repo subject')
|
|
96
|
+
}
|
|
97
|
+
const { content, subjectLine } = event
|
|
98
|
+
await retryHttp(() =>
|
|
99
|
+
ctx.modService(db).sendEmail({
|
|
100
|
+
subject: subjectLine,
|
|
101
|
+
content,
|
|
102
|
+
recipientDid: subject.did,
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isModEventDivert(event) && subject.isRecord()) {
|
|
108
|
+
if (!ctx.blobDiverter) {
|
|
109
|
+
throw new InvalidRequestError(
|
|
110
|
+
'BlobDiverter not configured for this service',
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
await ctx.blobDiverter.uploadBlobOnService(subject.info())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const moderationEvent = await db.transaction(async (dbTxn) => {
|
|
117
|
+
const moderationTxn = ctx.modService(dbTxn)
|
|
118
|
+
|
|
119
|
+
const result = await moderationTxn.logEvent({
|
|
120
|
+
event,
|
|
121
|
+
subject,
|
|
122
|
+
createdBy,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const moderationLangService = new ModerationLangService(moderationTxn)
|
|
126
|
+
await moderationLangService.tagSubjectWithLang({
|
|
127
|
+
subject,
|
|
128
|
+
createdBy: ctx.cfg.service.did,
|
|
129
|
+
subjectStatus: result.subjectStatus,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (subject.isRepo()) {
|
|
133
|
+
if (isTakedownEvent) {
|
|
134
|
+
const isSuspend = !!result.event.durationInHours
|
|
135
|
+
await moderationTxn.takedownRepo(subject, result.event.id, isSuspend)
|
|
136
|
+
} else if (isReverseTakedownEvent) {
|
|
137
|
+
await moderationTxn.reverseTakedownRepo(subject)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (subject.isRecord()) {
|
|
142
|
+
if (isTakedownEvent) {
|
|
143
|
+
await moderationTxn.takedownRecord(subject, result.event.id)
|
|
144
|
+
} else if (isReverseTakedownEvent) {
|
|
145
|
+
await moderationTxn.reverseTakedownRecord(subject)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isLabelEvent) {
|
|
150
|
+
await moderationTxn.formatAndCreateLabels(
|
|
151
|
+
result.event.subjectUri ?? result.event.subjectDid,
|
|
152
|
+
result.event.subjectCid,
|
|
153
|
+
{
|
|
154
|
+
create: result.event.createLabelVals?.length
|
|
155
|
+
? result.event.createLabelVals.split(' ')
|
|
156
|
+
: undefined,
|
|
157
|
+
negate: result.event.negateLabelVals?.length
|
|
158
|
+
? result.event.negateLabelVals.split(' ')
|
|
159
|
+
: undefined,
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result.event
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return moderationService.views.formatEvent(moderationEvent)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default function (server: Server, ctx: AppContext) {
|
|
171
|
+
server.tools.ozone.moderation.emitEvent({
|
|
172
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
173
|
+
handler: async ({ input, auth }) => {
|
|
174
|
+
const moderationEvent = await handleModerationEvent({
|
|
175
|
+
input,
|
|
176
|
+
auth,
|
|
177
|
+
ctx,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// On divert events, we need to automatically take down the blobs
|
|
181
|
+
if (isModEventDivert(input.body.event)) {
|
|
182
|
+
await handleModerationEvent({
|
|
183
|
+
auth,
|
|
184
|
+
ctx,
|
|
185
|
+
input: {
|
|
186
|
+
...input,
|
|
187
|
+
body: {
|
|
188
|
+
...input.body,
|
|
189
|
+
event: {
|
|
190
|
+
...input.body.event,
|
|
191
|
+
$type: 'tools.ozone.moderation.defs#modEventTakedown',
|
|
192
|
+
comment:
|
|
193
|
+
'[DIVERT_SIDE_EFFECT]: Automatically taking down after divert event',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
encoding: 'application/json',
|
|
202
|
+
body: moderationEvent,
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const validateLabels = (labels: string[]) => {
|
|
209
|
+
for (const label of labels) {
|
|
210
|
+
for (const char of badChars) {
|
|
211
|
+
if (label.includes(char)) {
|
|
212
|
+
throw new InvalidRequestError(`Invalid label: ${label}`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const badChars = [' ', ',', ';', `'`, `"`]
|
|
@@ -2,8 +2,8 @@ import { Server } from '../../lexicon'
|
|
|
2
2
|
import AppContext from '../../context'
|
|
3
3
|
|
|
4
4
|
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.
|
|
6
|
-
auth: ctx.authVerifier.
|
|
5
|
+
server.tools.ozone.moderation.getEvent({
|
|
6
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
7
7
|
handler: async ({ params }) => {
|
|
8
8
|
const { id } = params
|
|
9
9
|
const db = ctx.db
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { Server } from '../../lexicon'
|
|
3
3
|
import AppContext from '../../context'
|
|
4
|
-
import { addAccountInfoToRepoView, getPdsAccountInfo } from '
|
|
4
|
+
import { addAccountInfoToRepoView, getPdsAccountInfo } from '../util'
|
|
5
5
|
import { AtUri } from '@atproto/syntax'
|
|
6
6
|
|
|
7
7
|
export default function (server: Server, ctx: AppContext) {
|
|
8
|
-
server.
|
|
9
|
-
auth: ctx.authVerifier.
|
|
8
|
+
server.tools.ozone.moderation.getRecord({
|
|
9
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
10
10
|
handler: async ({ params, auth }) => {
|
|
11
11
|
const db = ctx.db
|
|
12
12
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { Server } from '../../lexicon'
|
|
3
3
|
import AppContext from '../../context'
|
|
4
|
-
import { addAccountInfoToRepoViewDetail, getPdsAccountInfo } from '
|
|
4
|
+
import { addAccountInfoToRepoViewDetail, getPdsAccountInfo } from '../util'
|
|
5
5
|
|
|
6
6
|
export default function (server: Server, ctx: AppContext) {
|
|
7
|
-
server.
|
|
8
|
-
auth: ctx.authVerifier.
|
|
7
|
+
server.tools.ozone.moderation.getRepo({
|
|
8
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
9
9
|
handler: async ({ params, auth }) => {
|
|
10
10
|
const { did } = params
|
|
11
11
|
const db = ctx.db
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Server } from '../../lexicon'
|
|
2
2
|
import AppContext from '../../context'
|
|
3
|
-
import { getEventType } from '../
|
|
3
|
+
import { getEventType } from '../util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.moderation.queryEvents({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ params }) => {
|
|
9
9
|
const {
|
|
10
10
|
subject,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Server } from '../../lexicon'
|
|
2
2
|
import AppContext from '../../context'
|
|
3
|
-
import { getReviewState } from '../
|
|
3
|
+
import { getReviewState } from '../util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.moderation.queryStatuses({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ params }) => {
|
|
9
9
|
const {
|
|
10
10
|
subject,
|
|
@@ -3,8 +3,8 @@ import AppContext from '../../context'
|
|
|
3
3
|
import { mapDefined } from '@atproto/common'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
server.
|
|
7
|
-
auth: ctx.authVerifier.
|
|
6
|
+
server.tools.ozone.moderation.searchRepos({
|
|
7
|
+
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
8
|
handler: async ({ params }) => {
|
|
9
9
|
const modService = ctx.modService(ctx.db)
|
|
10
10
|
|
package/src/api/proxied.ts
CHANGED
|
@@ -3,7 +3,7 @@ import AppContext from '../context'
|
|
|
3
3
|
|
|
4
4
|
export default function (server: Server, ctx: AppContext) {
|
|
5
5
|
server.app.bsky.actor.getProfile({
|
|
6
|
-
auth: ctx.authVerifier.
|
|
6
|
+
auth: ctx.authVerifier.moderator,
|
|
7
7
|
handler: async (request) => {
|
|
8
8
|
const res = await ctx.appviewAgent.api.app.bsky.actor.getProfile(
|
|
9
9
|
request.params,
|
|
@@ -17,7 +17,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
server.app.bsky.actor.getProfiles({
|
|
20
|
-
auth: ctx.authVerifier.
|
|
20
|
+
auth: ctx.authVerifier.moderator,
|
|
21
21
|
handler: async (request) => {
|
|
22
22
|
const res = await ctx.appviewAgent.api.app.bsky.actor.getProfiles(
|
|
23
23
|
request.params,
|
|
@@ -31,7 +31,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
server.app.bsky.feed.getAuthorFeed({
|
|
34
|
-
auth: ctx.authVerifier.
|
|
34
|
+
auth: ctx.authVerifier.moderator,
|
|
35
35
|
handler: async (request) => {
|
|
36
36
|
const res = await ctx.appviewAgent.api.app.bsky.feed.getAuthorFeed(
|
|
37
37
|
request.params,
|
|
@@ -45,7 +45,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
server.app.bsky.feed.getPostThread({
|
|
48
|
-
auth: ctx.authVerifier.
|
|
48
|
+
auth: ctx.authVerifier.moderator,
|
|
49
49
|
handler: async (request) => {
|
|
50
50
|
const res = await ctx.appviewAgent.api.app.bsky.feed.getPostThread(
|
|
51
51
|
request.params,
|
|
@@ -59,7 +59,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
server.app.bsky.feed.getFeedGenerator({
|
|
62
|
-
auth: ctx.authVerifier.
|
|
62
|
+
auth: ctx.authVerifier.moderator,
|
|
63
63
|
handler: async (request) => {
|
|
64
64
|
const res = await ctx.appviewAgent.api.app.bsky.feed.getFeedGenerator(
|
|
65
65
|
request.params,
|
|
@@ -73,7 +73,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
73
73
|
})
|
|
74
74
|
|
|
75
75
|
server.app.bsky.graph.getFollows({
|
|
76
|
-
auth: ctx.authVerifier.
|
|
76
|
+
auth: ctx.authVerifier.moderator,
|
|
77
77
|
handler: async (request) => {
|
|
78
78
|
const res = await ctx.appviewAgent.api.app.bsky.graph.getFollows(
|
|
79
79
|
request.params,
|
|
@@ -87,7 +87,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
server.app.bsky.graph.getFollowers({
|
|
90
|
-
auth: ctx.authVerifier.
|
|
90
|
+
auth: ctx.authVerifier.moderator,
|
|
91
91
|
handler: async (request) => {
|
|
92
92
|
const res = await ctx.appviewAgent.api.app.bsky.graph.getFollowers(
|
|
93
93
|
request.params,
|
|
@@ -101,7 +101,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
server.app.bsky.graph.getList({
|
|
104
|
-
auth: ctx.authVerifier.
|
|
104
|
+
auth: ctx.authVerifier.moderator,
|
|
105
105
|
handler: async (request) => {
|
|
106
106
|
const res = await ctx.appviewAgent.api.app.bsky.graph.getList(
|
|
107
107
|
request.params,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server } from '../../lexicon'
|
|
2
2
|
import AppContext from '../../context'
|
|
3
|
-
import { getReasonType } from '
|
|
3
|
+
import { getReasonType } from '../util'
|
|
4
4
|
import { subjectFromInput } from '../../mod-service/subject'
|
|
5
5
|
import { REASONAPPEAL } from '../../lexicon/types/com/atproto/moderation/defs'
|
|
6
6
|
import { ForbiddenError } from '@atproto/xrpc-server'
|
|
@@ -8,8 +8,7 @@ import { ModerationLangService } from '../../mod-service/lang'
|
|
|
8
8
|
|
|
9
9
|
export default function (server: Server, ctx: AppContext) {
|
|
10
10
|
server.com.atproto.moderation.createReport({
|
|
11
|
-
|
|
12
|
-
auth: ctx.authVerifier.standardOptionalOrRole,
|
|
11
|
+
auth: ctx.authVerifier.standard,
|
|
13
12
|
handler: async ({ input, auth }) => {
|
|
14
13
|
const requester =
|
|
15
14
|
'iss' in auth.credentials ? auth.credentials.iss : ctx.cfg.service.did
|
package/src/api/util.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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 { AccountView } from '../lexicon/types/com/atproto/admin/defs'
|
|
13
|
+
import {
|
|
14
|
+
RepoView,
|
|
15
|
+
RepoViewDetail,
|
|
16
|
+
REVIEWCLOSED,
|
|
17
|
+
REVIEWESCALATED,
|
|
18
|
+
REVIEWOPEN,
|
|
19
|
+
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
20
|
+
import { ModerationEvent } from '../db/schema/moderation_event'
|
|
21
|
+
import { ModerationSubjectStatusRow } from '../mod-service/types'
|
|
22
|
+
import AppContext from '../context'
|
|
23
|
+
|
|
24
|
+
export const getPdsAccountInfo = async (
|
|
25
|
+
ctx: AppContext,
|
|
26
|
+
did: string,
|
|
27
|
+
): Promise<AccountView | null> => {
|
|
28
|
+
const agent = ctx.pdsAgent
|
|
29
|
+
if (!agent) return null
|
|
30
|
+
const auth = await ctx.pdsAuth()
|
|
31
|
+
if (!auth) return null
|
|
32
|
+
try {
|
|
33
|
+
const res = await agent.api.com.atproto.admin.getAccountInfo({ did }, auth)
|
|
34
|
+
return res.data
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const addAccountInfoToRepoViewDetail = (
|
|
41
|
+
repoView: RepoViewDetail,
|
|
42
|
+
accountInfo: AccountView | null,
|
|
43
|
+
includeEmail = false,
|
|
44
|
+
): RepoViewDetail => {
|
|
45
|
+
if (!accountInfo) return repoView
|
|
46
|
+
return {
|
|
47
|
+
...repoView,
|
|
48
|
+
email: includeEmail ? accountInfo.email : undefined,
|
|
49
|
+
invitedBy: accountInfo.invitedBy,
|
|
50
|
+
invitesDisabled: accountInfo.invitesDisabled,
|
|
51
|
+
inviteNote: accountInfo.inviteNote,
|
|
52
|
+
invites: accountInfo.invites,
|
|
53
|
+
emailConfirmedAt: accountInfo.emailConfirmedAt,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const addAccountInfoToRepoView = (
|
|
58
|
+
repoView: RepoView,
|
|
59
|
+
accountInfo: AccountView | null,
|
|
60
|
+
includeEmail = false,
|
|
61
|
+
): RepoView => {
|
|
62
|
+
if (!accountInfo) return repoView
|
|
63
|
+
return {
|
|
64
|
+
...repoView,
|
|
65
|
+
email: includeEmail ? accountInfo.email : undefined,
|
|
66
|
+
invitedBy: accountInfo.invitedBy,
|
|
67
|
+
invitesDisabled: accountInfo.invitesDisabled,
|
|
68
|
+
inviteNote: accountInfo.inviteNote,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const getReasonType = (reasonType: ReportInput['reasonType']) => {
|
|
73
|
+
if (reasonTypes.has(reasonType)) {
|
|
74
|
+
return reasonType as NonNullable<ModerationEvent['meta']>['reportType']
|
|
75
|
+
}
|
|
76
|
+
throw new InvalidRequestError('Invalid reason type')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const getEventType = (type: string) => {
|
|
80
|
+
if (eventTypes.has(type)) {
|
|
81
|
+
return type as ModerationEvent['action']
|
|
82
|
+
}
|
|
83
|
+
throw new InvalidRequestError('Invalid event type')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const getReviewState = (reviewState?: string) => {
|
|
87
|
+
if (!reviewState) return undefined
|
|
88
|
+
if (reviewStates.has(reviewState)) {
|
|
89
|
+
return reviewState as ModerationSubjectStatusRow['reviewState']
|
|
90
|
+
}
|
|
91
|
+
throw new InvalidRequestError('Invalid review state')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const reviewStates = new Set([REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN])
|
|
95
|
+
|
|
96
|
+
const reasonTypes = new Set([
|
|
97
|
+
REASONOTHER,
|
|
98
|
+
REASONSPAM,
|
|
99
|
+
REASONMISLEADING,
|
|
100
|
+
REASONRUDE,
|
|
101
|
+
REASONSEXUAL,
|
|
102
|
+
REASONVIOLATION,
|
|
103
|
+
REASONAPPEAL,
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
const eventTypes = new Set([
|
|
107
|
+
'tools.ozone.moderation.defs#modEventTakedown',
|
|
108
|
+
'tools.ozone.moderation.defs#modEventAcknowledge',
|
|
109
|
+
'tools.ozone.moderation.defs#modEventEscalate',
|
|
110
|
+
'tools.ozone.moderation.defs#modEventComment',
|
|
111
|
+
'tools.ozone.moderation.defs#modEventLabel',
|
|
112
|
+
'tools.ozone.moderation.defs#modEventReport',
|
|
113
|
+
'tools.ozone.moderation.defs#modEventMute',
|
|
114
|
+
'tools.ozone.moderation.defs#modEventUnmute',
|
|
115
|
+
'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
116
|
+
'tools.ozone.moderation.defs#modEventEmail',
|
|
117
|
+
'tools.ozone.moderation.defs#modEventResolveAppeal',
|
|
118
|
+
'tools.ozone.moderation.defs#modEventTag',
|
|
119
|
+
])
|