@atproto/bsky 0.0.14 → 0.0.16
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/CHANGELOG.md +22 -0
- package/dist/api/app/bsky/feed/searchPosts.d.ts +3 -0
- package/dist/api/com/atproto/moderation/util.d.ts +4 -3
- package/dist/config.d.ts +2 -2
- package/dist/context.d.ts +16 -1
- package/dist/db/index.js +26 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/pagination.d.ts +2 -1
- package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
- package/dist/db/tables/moderation.d.ts +24 -34
- package/dist/feed-gen/types.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3332 -2430
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +18 -18
- package/dist/lexicon/lexicons.d.ts +460 -385
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +116 -48
- package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
- package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
- package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/fetchLabels.d.ts} +7 -3
- package/dist/migrate-moderation-data.d.ts +1 -0
- package/dist/services/actor/views.d.ts +2 -5
- package/dist/services/feed/index.d.ts +1 -0
- package/dist/services/feed/util.d.ts +9 -1
- package/dist/services/feed/views.d.ts +6 -17
- package/dist/services/graph/index.d.ts +5 -29
- package/dist/services/graph/types.d.ts +1 -0
- package/dist/services/moderation/index.d.ts +135 -72
- package/dist/services/moderation/pagination.d.ts +36 -0
- package/dist/services/moderation/status.d.ts +13 -0
- package/dist/services/moderation/types.d.ts +35 -0
- package/dist/services/moderation/views.d.ts +18 -14
- package/dist/util/debug.d.ts +1 -1
- package/package.json +14 -15
- package/src/api/app/bsky/actor/getSuggestions.ts +45 -21
- package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
- package/src/api/app/bsky/feed/getFeed.ts +9 -9
- package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
- package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
- package/src/api/app/bsky/feed/getListFeed.ts +1 -3
- package/src/api/app/bsky/feed/getPostThread.ts +31 -58
- package/src/api/app/bsky/feed/getPosts.ts +21 -18
- package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getTimeline.ts +1 -3
- package/src/api/app/bsky/feed/searchPosts.ts +130 -0
- package/src/api/app/bsky/graph/getList.ts +6 -3
- package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
- package/src/api/app/bsky/graph/getListMutes.ts +2 -1
- package/src/api/app/bsky/graph/getLists.ts +2 -1
- package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
- package/src/api/blob-resolver.ts +6 -11
- package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
- package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
- package/src/api/com/atproto/admin/getRecord.ts +1 -0
- package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
- package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
- package/src/api/com/atproto/admin/util.ts +3 -1
- package/src/api/com/atproto/moderation/createReport.ts +9 -7
- package/src/api/com/atproto/moderation/util.ts +38 -20
- package/src/api/com/atproto/temp/fetchLabels.ts +30 -0
- package/src/api/index.ts +12 -14
- package/src/auth.ts +29 -21
- package/src/auto-moderator/index.ts +26 -19
- package/src/config.ts +6 -6
- package/src/context.ts +15 -9
- package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/pagination.ts +26 -3
- package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +51 -55
- package/src/db/tables/moderation.ts +35 -52
- package/src/feed-gen/best-of-follows.ts +6 -3
- package/src/feed-gen/bsky-team.ts +1 -1
- package/src/feed-gen/hot-classic.ts +1 -1
- package/src/feed-gen/mutuals.ts +6 -2
- package/src/feed-gen/types.ts +1 -1
- package/src/feed-gen/whats-hot.ts +1 -1
- package/src/feed-gen/with-friends.ts +7 -3
- package/src/index.ts +2 -1
- package/src/lexicon/index.ts +52 -67
- package/src/lexicon/lexicons.ts +674 -579
- package/src/lexicon/types/app/bsky/actor/defs.ts +2 -2
- package/src/lexicon/types/app/bsky/actor/searchActors.ts +2 -2
- package/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +2 -2
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
- package/src/lexicon/types/app/bsky/feed/searchPosts.ts +3 -3
- package/src/lexicon/types/app/bsky/graph/defs.ts +3 -2
- package/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts +4 -4
- package/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts +3 -3
- package/src/lexicon/types/com/atproto/admin/defs.ts +278 -84
- package/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +1 -1
- package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
- package/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +1 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +8 -15
- package/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +70 -0
- package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
- package/src/lexicon/types/com/atproto/label/defs.ts +9 -9
- package/src/lexicon/types/com/atproto/label/queryLabels.ts +2 -2
- package/src/lexicon/types/com/atproto/repo/applyWrites.ts +1 -1
- package/src/lexicon/types/com/atproto/repo/createRecord.ts +2 -2
- package/src/lexicon/types/com/atproto/repo/deleteRecord.ts +2 -2
- package/src/lexicon/types/com/atproto/repo/listRecords.ts +1 -1
- package/src/lexicon/types/com/atproto/repo/putRecord.ts +3 -3
- package/src/lexicon/types/com/atproto/sync/listBlobs.ts +1 -1
- package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +4 -4
- package/src/lexicon/types/com/atproto/{admin/getModerationActions.ts → temp/fetchLabels.ts} +3 -5
- package/src/migrate-moderation-data.ts +414 -0
- package/src/services/actor/views.ts +5 -14
- package/src/services/feed/index.ts +26 -7
- package/src/services/feed/util.ts +47 -19
- package/src/services/feed/views.ts +68 -4
- package/src/services/graph/index.ts +21 -3
- package/src/services/graph/types.ts +1 -0
- package/src/services/indexing/plugins/block.ts +2 -3
- package/src/services/indexing/plugins/feed-generator.ts +2 -3
- package/src/services/indexing/plugins/follow.ts +2 -3
- package/src/services/indexing/plugins/like.ts +2 -3
- package/src/services/indexing/plugins/list-block.ts +2 -3
- package/src/services/indexing/plugins/list-item.ts +2 -3
- package/src/services/indexing/plugins/list.ts +2 -3
- package/src/services/indexing/plugins/post.ts +3 -4
- package/src/services/indexing/plugins/repost.ts +2 -3
- package/src/services/indexing/plugins/thread-gate.ts +2 -3
- package/src/services/label/index.ts +2 -3
- package/src/services/moderation/index.ts +380 -395
- package/src/services/moderation/pagination.ts +96 -0
- package/src/services/moderation/status.ts +244 -0
- package/src/services/moderation/types.ts +49 -0
- package/src/services/moderation/views.ts +278 -329
- package/src/util/debug.ts +2 -2
- package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
- package/tests/__snapshots__/indexing.test.ts.snap +0 -6
- package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
- package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
- package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
- package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
- package/tests/admin/get-record.test.ts +5 -9
- package/tests/admin/get-repo.test.ts +38 -9
- package/tests/admin/moderation-events.test.ts +221 -0
- package/tests/admin/moderation-statuses.test.ts +145 -0
- package/tests/admin/moderation.test.ts +512 -860
- package/tests/admin/repo-search.test.ts +2 -3
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
- package/tests/auto-moderator/takedowns.test.ts +45 -18
- package/tests/feed-generation.test.ts +57 -9
- package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
- package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
- package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
- package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
- package/tests/views/actor-search.test.ts +2 -3
- package/tests/views/author-feed.test.ts +42 -36
- package/tests/views/follows.test.ts +40 -35
- package/tests/views/list-feed.test.ts +17 -9
- package/tests/views/notifications.test.ts +13 -9
- package/tests/views/profile.test.ts +20 -18
- package/tests/views/suggestions.test.ts +15 -7
- package/tests/views/thread.test.ts +54 -26
- package/tests/views/threadgating.test.ts +51 -19
- package/tests/views/timeline.test.ts +21 -13
- package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
- package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
- package/dist/lexicon/types/com/atproto/admin/resolveModerationReports.d.ts +0 -36
- package/dist/lexicon/types/com/atproto/admin/reverseModerationAction.d.ts +0 -36
- package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
- package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
- package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
- package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
- package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
- package/src/lexicon/types/com/atproto/admin/getModerationAction.ts +0 -41
- package/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +0 -49
- package/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +0 -49
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
- package/tests/admin/get-moderation-action.test.ts +0 -100
- package/tests/admin/get-moderation-actions.test.ts +0 -164
- package/tests/admin/get-moderation-report.test.ts +0 -100
- package/tests/admin/get-moderation-reports.test.ts +0 -332
- /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
- /package/dist/api/com/atproto/{admin/resolveModerationReports.d.ts → temp/fetchLabels.d.ts} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { AtUri } from '@atproto/syntax'
|
|
3
|
+
import {
|
|
4
|
+
AuthRequiredError,
|
|
5
|
+
InvalidRequestError,
|
|
6
|
+
UpstreamFailureError,
|
|
7
|
+
} from '@atproto/xrpc-server'
|
|
8
|
+
import { Server } from '../../../../lexicon'
|
|
9
|
+
import AppContext from '../../../../context'
|
|
10
|
+
import { getSubject } from '../moderation/util'
|
|
11
|
+
import {
|
|
12
|
+
isModEventLabel,
|
|
13
|
+
isModEventReverseTakedown,
|
|
14
|
+
isModEventTakedown,
|
|
15
|
+
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
16
|
+
import { TakedownSubjects } from '../../../../services/moderation'
|
|
17
|
+
import { retryHttp } from '../../../../util/retry'
|
|
18
|
+
|
|
19
|
+
export default function (server: Server, ctx: AppContext) {
|
|
20
|
+
server.com.atproto.admin.emitModerationEvent({
|
|
21
|
+
auth: ctx.roleVerifier,
|
|
22
|
+
handler: async ({ input, auth }) => {
|
|
23
|
+
const access = auth.credentials
|
|
24
|
+
const db = ctx.db.getPrimary()
|
|
25
|
+
const moderationService = ctx.services.moderation(db)
|
|
26
|
+
const { subject, createdBy, subjectBlobCids, event } = input.body
|
|
27
|
+
const isTakedownEvent = isModEventTakedown(event)
|
|
28
|
+
const isReverseTakedownEvent = isModEventReverseTakedown(event)
|
|
29
|
+
const isLabelEvent = isModEventLabel(event)
|
|
30
|
+
|
|
31
|
+
// apply access rules
|
|
32
|
+
|
|
33
|
+
// if less than moderator access then can not takedown an account
|
|
34
|
+
if (!access.moderator && isTakedownEvent && 'did' in subject) {
|
|
35
|
+
throw new AuthRequiredError(
|
|
36
|
+
'Must be a full moderator to perform an account takedown',
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
// if less than moderator access then can only take ack and escalation actions
|
|
40
|
+
if (!access.moderator && (isTakedownEvent || isReverseTakedownEvent)) {
|
|
41
|
+
throw new AuthRequiredError(
|
|
42
|
+
'Must be a full moderator to take this type of action',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
// if less than moderator access then can not apply labels
|
|
46
|
+
if (!access.moderator && isLabelEvent) {
|
|
47
|
+
throw new AuthRequiredError('Must be a full moderator to label content')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isLabelEvent) {
|
|
51
|
+
validateLabels([
|
|
52
|
+
...(event.createLabelVals ?? []),
|
|
53
|
+
...(event.negateLabelVals ?? []),
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const subjectInfo = getSubject(subject)
|
|
58
|
+
|
|
59
|
+
if (isTakedownEvent || isReverseTakedownEvent) {
|
|
60
|
+
const isSubjectTakendown = await moderationService.isSubjectTakendown(
|
|
61
|
+
subjectInfo,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (isSubjectTakendown && isTakedownEvent) {
|
|
65
|
+
throw new InvalidRequestError(`Subject is already taken down`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isSubjectTakendown && isReverseTakedownEvent) {
|
|
69
|
+
throw new InvalidRequestError(`Subject is not taken down`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { result: moderationEvent, takenDown } = await db.transaction(
|
|
74
|
+
async (dbTxn) => {
|
|
75
|
+
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
76
|
+
const labelTxn = ctx.services.label(dbTxn)
|
|
77
|
+
|
|
78
|
+
const result = await moderationTxn.logEvent({
|
|
79
|
+
event,
|
|
80
|
+
subject: subjectInfo,
|
|
81
|
+
subjectBlobCids:
|
|
82
|
+
subjectBlobCids?.map((cid) => CID.parse(cid)) ?? [],
|
|
83
|
+
createdBy,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
let takenDown: TakedownSubjects | undefined
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
result.subjectType === 'com.atproto.admin.defs#repoRef' &&
|
|
90
|
+
result.subjectDid
|
|
91
|
+
) {
|
|
92
|
+
// No credentials to revoke on appview
|
|
93
|
+
if (isTakedownEvent) {
|
|
94
|
+
takenDown = await moderationTxn.takedownRepo({
|
|
95
|
+
takedownId: result.id,
|
|
96
|
+
did: result.subjectDid,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isReverseTakedownEvent) {
|
|
101
|
+
await moderationTxn.reverseTakedownRepo({
|
|
102
|
+
did: result.subjectDid,
|
|
103
|
+
})
|
|
104
|
+
takenDown = {
|
|
105
|
+
subjects: [
|
|
106
|
+
{
|
|
107
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
108
|
+
did: result.subjectDid,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
did: result.subjectDid,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
result.subjectType === 'com.atproto.repo.strongRef' &&
|
|
118
|
+
result.subjectUri
|
|
119
|
+
) {
|
|
120
|
+
const blobCids = subjectBlobCids?.map((cid) => CID.parse(cid)) ?? []
|
|
121
|
+
if (isTakedownEvent) {
|
|
122
|
+
takenDown = await moderationTxn.takedownRecord({
|
|
123
|
+
takedownId: result.id,
|
|
124
|
+
uri: new AtUri(result.subjectUri),
|
|
125
|
+
// TODO: I think this will always be available for strongRefs?
|
|
126
|
+
cid: CID.parse(result.subjectCid as string),
|
|
127
|
+
blobCids,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isReverseTakedownEvent) {
|
|
132
|
+
await moderationTxn.reverseTakedownRecord({
|
|
133
|
+
uri: new AtUri(result.subjectUri),
|
|
134
|
+
})
|
|
135
|
+
takenDown = {
|
|
136
|
+
did: result.subjectDid,
|
|
137
|
+
subjects: [
|
|
138
|
+
{
|
|
139
|
+
$type: 'com.atproto.repo.strongRef',
|
|
140
|
+
uri: result.subjectUri,
|
|
141
|
+
cid: result.subjectCid ?? '',
|
|
142
|
+
},
|
|
143
|
+
...blobCids.map((cid) => ({
|
|
144
|
+
$type: 'com.atproto.admin.defs#repoBlobRef',
|
|
145
|
+
did: result.subjectDid,
|
|
146
|
+
cid: cid.toString(),
|
|
147
|
+
recordUri: result.subjectUri,
|
|
148
|
+
})),
|
|
149
|
+
],
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isLabelEvent) {
|
|
155
|
+
await labelTxn.formatAndCreate(
|
|
156
|
+
ctx.cfg.labelerDid,
|
|
157
|
+
result.subjectUri ?? result.subjectDid,
|
|
158
|
+
result.subjectCid,
|
|
159
|
+
{
|
|
160
|
+
create: result.createLabelVals?.length
|
|
161
|
+
? result.createLabelVals.split(' ')
|
|
162
|
+
: undefined,
|
|
163
|
+
negate: result.negateLabelVals?.length
|
|
164
|
+
? result.negateLabelVals.split(' ')
|
|
165
|
+
: undefined,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { result, takenDown }
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (takenDown && ctx.moderationPushAgent) {
|
|
175
|
+
const { did, subjects } = takenDown
|
|
176
|
+
if (did && subjects.length > 0) {
|
|
177
|
+
const agent = ctx.moderationPushAgent
|
|
178
|
+
const results = await Promise.allSettled(
|
|
179
|
+
subjects.map((subject) =>
|
|
180
|
+
retryHttp(() =>
|
|
181
|
+
agent.api.com.atproto.admin.updateSubjectStatus({
|
|
182
|
+
subject,
|
|
183
|
+
takedown: isTakedownEvent
|
|
184
|
+
? {
|
|
185
|
+
applied: true,
|
|
186
|
+
ref: moderationEvent.id.toString(),
|
|
187
|
+
}
|
|
188
|
+
: {
|
|
189
|
+
applied: false,
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
const hadFailure = results.some((r) => r.status === 'rejected')
|
|
196
|
+
if (hadFailure) {
|
|
197
|
+
throw new UpstreamFailureError('failed to apply action on PDS')
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
encoding: 'application/json',
|
|
204
|
+
body: await moderationService.views.event(moderationEvent),
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const validateLabels = (labels: string[]) => {
|
|
211
|
+
for (const label of labels) {
|
|
212
|
+
for (const char of badChars) {
|
|
213
|
+
if (label.includes(char)) {
|
|
214
|
+
throw new InvalidRequestError(`Invalid label: ${label}`)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const badChars = [' ', ',', ';', `'`, `"`]
|
|
@@ -2,23 +2,17 @@ import { Server } from '../../../../lexicon'
|
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
3
|
|
|
4
4
|
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.com.atproto.admin.
|
|
5
|
+
server.com.atproto.admin.getModerationEvent({
|
|
6
6
|
auth: ctx.roleVerifier,
|
|
7
7
|
handler: async ({ params }) => {
|
|
8
|
-
const {
|
|
8
|
+
const { id } = params
|
|
9
9
|
const db = ctx.db.getPrimary()
|
|
10
10
|
const moderationService = ctx.services.moderation(db)
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
limit,
|
|
14
|
-
cursor,
|
|
15
|
-
})
|
|
11
|
+
const event = await moderationService.getEventOrThrow(id)
|
|
12
|
+
const eventDetail = await moderationService.views.eventDetail(event)
|
|
16
13
|
return {
|
|
17
14
|
encoding: 'application/json',
|
|
18
|
-
body:
|
|
19
|
-
cursor: results.at(-1)?.id.toString() ?? undefined,
|
|
20
|
-
actions: await moderationService.views.action(results),
|
|
21
|
-
},
|
|
15
|
+
body: eventDetail,
|
|
22
16
|
}
|
|
23
17
|
},
|
|
24
18
|
})
|
|
@@ -18,6 +18,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
18
18
|
if (!result) {
|
|
19
19
|
throw new InvalidRequestError('Record not found', 'RecordNotFound')
|
|
20
20
|
}
|
|
21
|
+
|
|
21
22
|
const [record, accountInfo] = await Promise.all([
|
|
22
23
|
ctx.services.moderation(db).views.recordDetail(result),
|
|
23
24
|
getPdsAccountInfo(ctx, result.did),
|
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
import { Server } from '../../../../lexicon'
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
|
+
import { getEventType } from '../moderation/util'
|
|
3
4
|
|
|
4
5
|
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.com.atproto.admin.
|
|
6
|
+
server.com.atproto.admin.queryModerationEvents({
|
|
6
7
|
auth: ctx.roleVerifier,
|
|
7
8
|
handler: async ({ params }) => {
|
|
8
9
|
const {
|
|
9
10
|
subject,
|
|
10
|
-
resolved,
|
|
11
|
-
actionType,
|
|
12
11
|
limit = 50,
|
|
13
12
|
cursor,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
sortDirection = 'desc',
|
|
14
|
+
types,
|
|
15
|
+
includeAllUserRecords = false,
|
|
16
|
+
createdBy,
|
|
18
17
|
} = params
|
|
19
18
|
const db = ctx.db.getPrimary()
|
|
20
19
|
const moderationService = ctx.services.moderation(db)
|
|
21
|
-
const results = await moderationService.
|
|
20
|
+
const results = await moderationService.getEvents({
|
|
21
|
+
types: types?.length ? types.map(getEventType) : [],
|
|
22
22
|
subject,
|
|
23
|
-
|
|
24
|
-
actionType,
|
|
23
|
+
createdBy,
|
|
25
24
|
limit,
|
|
26
25
|
cursor,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
reporters,
|
|
30
|
-
actionedBy,
|
|
26
|
+
sortDirection,
|
|
27
|
+
includeAllUserRecords,
|
|
31
28
|
})
|
|
32
29
|
return {
|
|
33
30
|
encoding: 'application/json',
|
|
34
31
|
body: {
|
|
35
|
-
cursor: results.
|
|
36
|
-
|
|
32
|
+
cursor: results.cursor,
|
|
33
|
+
events: await moderationService.views.event(results.events),
|
|
37
34
|
},
|
|
38
35
|
}
|
|
39
36
|
},
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Server } from '../../../../lexicon'
|
|
2
|
+
import AppContext from '../../../../context'
|
|
3
|
+
import { getReviewState } from '../moderation/util'
|
|
4
|
+
|
|
5
|
+
export default function (server: Server, ctx: AppContext) {
|
|
6
|
+
server.com.atproto.admin.queryModerationStatuses({
|
|
7
|
+
auth: ctx.roleVerifier,
|
|
8
|
+
handler: async ({ params }) => {
|
|
9
|
+
const {
|
|
10
|
+
subject,
|
|
11
|
+
takendown,
|
|
12
|
+
reviewState,
|
|
13
|
+
reviewedAfter,
|
|
14
|
+
reviewedBefore,
|
|
15
|
+
reportedAfter,
|
|
16
|
+
reportedBefore,
|
|
17
|
+
ignoreSubjects,
|
|
18
|
+
lastReviewedBy,
|
|
19
|
+
sortDirection = 'desc',
|
|
20
|
+
sortField = 'lastReportedAt',
|
|
21
|
+
includeMuted = false,
|
|
22
|
+
limit = 50,
|
|
23
|
+
cursor,
|
|
24
|
+
} = params
|
|
25
|
+
const db = ctx.db.getPrimary()
|
|
26
|
+
const moderationService = ctx.services.moderation(db)
|
|
27
|
+
const results = await moderationService.getSubjectStatuses({
|
|
28
|
+
reviewState: getReviewState(reviewState),
|
|
29
|
+
subject,
|
|
30
|
+
takendown,
|
|
31
|
+
reviewedAfter,
|
|
32
|
+
reviewedBefore,
|
|
33
|
+
reportedAfter,
|
|
34
|
+
reportedBefore,
|
|
35
|
+
includeMuted,
|
|
36
|
+
ignoreSubjects,
|
|
37
|
+
sortDirection,
|
|
38
|
+
lastReviewedBy,
|
|
39
|
+
sortField,
|
|
40
|
+
limit,
|
|
41
|
+
cursor,
|
|
42
|
+
})
|
|
43
|
+
const subjectStatuses = moderationService.views.subjectStatus(
|
|
44
|
+
results.statuses,
|
|
45
|
+
)
|
|
46
|
+
return {
|
|
47
|
+
encoding: 'application/json',
|
|
48
|
+
body: {
|
|
49
|
+
cursor: results.cursor,
|
|
50
|
+
subjectStatuses,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -9,8 +9,9 @@ export const getPdsAccountInfo = async (
|
|
|
9
9
|
ctx: AppContext,
|
|
10
10
|
did: string,
|
|
11
11
|
): Promise<AccountView | null> => {
|
|
12
|
+
const agent = ctx.moderationPushAgent
|
|
13
|
+
if (!agent) return null
|
|
12
14
|
try {
|
|
13
|
-
const agent = await ctx.pdsAdminAgent(did)
|
|
14
15
|
const res = await agent.api.com.atproto.admin.getAccountInfo({ did })
|
|
15
16
|
return res.data
|
|
16
17
|
} catch (err) {
|
|
@@ -31,6 +32,7 @@ export const addAccountInfoToRepoViewDetail = (
|
|
|
31
32
|
invitesDisabled: accountInfo.invitesDisabled,
|
|
32
33
|
inviteNote: accountInfo.inviteNote,
|
|
33
34
|
invites: accountInfo.invites,
|
|
35
|
+
emailConfirmedAt: accountInfo.emailConfirmedAt,
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -22,15 +22,17 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const report = await db.transaction(async (dbTxn) => {
|
|
26
|
+
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
27
|
+
return moderationTxn.report({
|
|
28
|
+
reasonType: getReasonType(reasonType),
|
|
29
|
+
reason,
|
|
30
|
+
subject: getSubject(subject),
|
|
31
|
+
reportedBy: requester || ctx.cfg.serverDid,
|
|
32
|
+
})
|
|
32
33
|
})
|
|
33
34
|
|
|
35
|
+
const moderationService = ctx.services.moderation(db)
|
|
34
36
|
return {
|
|
35
37
|
encoding: 'application/json',
|
|
36
38
|
body: moderationService.views.reportPublic(report),
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
2
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
3
|
import { AtUri } from '@atproto/syntax'
|
|
4
|
-
import { ModerationAction } from '../../../../db/tables/moderation'
|
|
5
|
-
import { ModerationReport } from '../../../../db/tables/moderation'
|
|
6
4
|
import { InputSchema as ReportInput } from '../../../../lexicon/types/com/atproto/moderation/createReport'
|
|
7
|
-
import { InputSchema as ActionInput } from '../../../../lexicon/types/com/atproto/admin/
|
|
8
|
-
import {
|
|
9
|
-
ACKNOWLEDGE,
|
|
10
|
-
FLAG,
|
|
11
|
-
TAKEDOWN,
|
|
12
|
-
ESCALATE,
|
|
13
|
-
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
5
|
+
import { InputSchema as ActionInput } from '../../../../lexicon/types/com/atproto/admin/emitModerationEvent'
|
|
14
6
|
import {
|
|
15
7
|
REASONOTHER,
|
|
16
8
|
REASONSPAM,
|
|
@@ -19,6 +11,13 @@ import {
|
|
|
19
11
|
REASONSEXUAL,
|
|
20
12
|
REASONVIOLATION,
|
|
21
13
|
} from '../../../../lexicon/types/com/atproto/moderation/defs'
|
|
14
|
+
import {
|
|
15
|
+
REVIEWCLOSED,
|
|
16
|
+
REVIEWESCALATED,
|
|
17
|
+
REVIEWOPEN,
|
|
18
|
+
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
19
|
+
import { ModerationEvent } from '../../../../db/tables/moderation'
|
|
20
|
+
import { ModerationSubjectStatusRow } from '../../../../services/moderation/types'
|
|
22
21
|
|
|
23
22
|
type SubjectInput = ReportInput['subject'] | ActionInput['subject']
|
|
24
23
|
|
|
@@ -34,8 +33,9 @@ export const getSubject = (subject: SubjectInput) => {
|
|
|
34
33
|
typeof subject.uri === 'string' &&
|
|
35
34
|
typeof subject.cid === 'string'
|
|
36
35
|
) {
|
|
36
|
+
const uri = new AtUri(subject.uri)
|
|
37
37
|
return {
|
|
38
|
-
uri
|
|
38
|
+
uri,
|
|
39
39
|
cid: CID.parse(subject.cid),
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -44,23 +44,28 @@ export const getSubject = (subject: SubjectInput) => {
|
|
|
44
44
|
|
|
45
45
|
export const getReasonType = (reasonType: ReportInput['reasonType']) => {
|
|
46
46
|
if (reasonTypes.has(reasonType)) {
|
|
47
|
-
return reasonType as
|
|
47
|
+
return reasonType as NonNullable<ModerationEvent['meta']>['reportType']
|
|
48
48
|
}
|
|
49
49
|
throw new InvalidRequestError('Invalid reason type')
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export const
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
action === FLAG ||
|
|
56
|
-
action === ACKNOWLEDGE ||
|
|
57
|
-
action === ESCALATE
|
|
58
|
-
) {
|
|
59
|
-
return action as ModerationAction['action']
|
|
52
|
+
export const getEventType = (type: string) => {
|
|
53
|
+
if (eventTypes.has(type)) {
|
|
54
|
+
return type as ModerationEvent['action']
|
|
60
55
|
}
|
|
61
|
-
throw new InvalidRequestError('Invalid
|
|
56
|
+
throw new InvalidRequestError('Invalid event type')
|
|
62
57
|
}
|
|
63
58
|
|
|
59
|
+
export const getReviewState = (reviewState?: string) => {
|
|
60
|
+
if (!reviewState) return undefined
|
|
61
|
+
if (reviewStates.has(reviewState)) {
|
|
62
|
+
return reviewState as ModerationSubjectStatusRow['reviewState']
|
|
63
|
+
}
|
|
64
|
+
throw new InvalidRequestError('Invalid review state')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const reviewStates = new Set([REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN])
|
|
68
|
+
|
|
64
69
|
const reasonTypes = new Set([
|
|
65
70
|
REASONOTHER,
|
|
66
71
|
REASONSPAM,
|
|
@@ -69,3 +74,16 @@ const reasonTypes = new Set([
|
|
|
69
74
|
REASONSEXUAL,
|
|
70
75
|
REASONVIOLATION,
|
|
71
76
|
])
|
|
77
|
+
|
|
78
|
+
const eventTypes = new Set([
|
|
79
|
+
'com.atproto.admin.defs#modEventTakedown',
|
|
80
|
+
'com.atproto.admin.defs#modEventAcknowledge',
|
|
81
|
+
'com.atproto.admin.defs#modEventEscalate',
|
|
82
|
+
'com.atproto.admin.defs#modEventComment',
|
|
83
|
+
'com.atproto.admin.defs#modEventLabel',
|
|
84
|
+
'com.atproto.admin.defs#modEventReport',
|
|
85
|
+
'com.atproto.admin.defs#modEventMute',
|
|
86
|
+
'com.atproto.admin.defs#modEventUnmute',
|
|
87
|
+
'com.atproto.admin.defs#modEventReverseTakedown',
|
|
88
|
+
'com.atproto.admin.defs#modEventEmail',
|
|
89
|
+
])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Server } from '../../../../lexicon'
|
|
2
|
+
import AppContext from '../../../../context'
|
|
3
|
+
|
|
4
|
+
export default function (server: Server, ctx: AppContext) {
|
|
5
|
+
server.com.atproto.temp.fetchLabels(async ({ params }) => {
|
|
6
|
+
const { limit } = params
|
|
7
|
+
const db = ctx.db.getReplica()
|
|
8
|
+
const since =
|
|
9
|
+
params.since !== undefined ? new Date(params.since).toISOString() : ''
|
|
10
|
+
const labelRes = await db.db
|
|
11
|
+
.selectFrom('label')
|
|
12
|
+
.selectAll()
|
|
13
|
+
.orderBy('label.cts', 'asc')
|
|
14
|
+
.where('cts', '>', since)
|
|
15
|
+
.limit(limit)
|
|
16
|
+
.execute()
|
|
17
|
+
|
|
18
|
+
const labels = labelRes.map((l) => ({
|
|
19
|
+
...l,
|
|
20
|
+
cid: l.cid === '' ? undefined : l.cid,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
encoding: 'application/json',
|
|
25
|
+
body: {
|
|
26
|
+
labels,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
package/src/api/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import getLikes from './app/bsky/feed/getLikes'
|
|
|
13
13
|
import getListFeed from './app/bsky/feed/getListFeed'
|
|
14
14
|
import getPostThread from './app/bsky/feed/getPostThread'
|
|
15
15
|
import getPosts from './app/bsky/feed/getPosts'
|
|
16
|
+
import searchPosts from './app/bsky/feed/searchPosts'
|
|
16
17
|
import getActorLikes from './app/bsky/feed/getActorLikes'
|
|
17
18
|
import getProfile from './app/bsky/actor/getProfile'
|
|
18
19
|
import getProfiles from './app/bsky/actor/getProfiles'
|
|
@@ -40,18 +41,16 @@ import registerPush from './app/bsky/notification/registerPush'
|
|
|
40
41
|
import getPopularFeedGenerators from './app/bsky/unspecced/getPopularFeedGenerators'
|
|
41
42
|
import getTimelineSkeleton from './app/bsky/unspecced/getTimelineSkeleton'
|
|
42
43
|
import createReport from './com/atproto/moderation/createReport'
|
|
43
|
-
import
|
|
44
|
-
import reverseModerationAction from './com/atproto/admin/reverseModerationAction'
|
|
45
|
-
import takeModerationAction from './com/atproto/admin/takeModerationAction'
|
|
44
|
+
import emitModerationEvent from './com/atproto/admin/emitModerationEvent'
|
|
46
45
|
import searchRepos from './com/atproto/admin/searchRepos'
|
|
47
46
|
import adminGetRecord from './com/atproto/admin/getRecord'
|
|
48
47
|
import getRepo from './com/atproto/admin/getRepo'
|
|
49
|
-
import
|
|
50
|
-
import getModerationActions from './com/atproto/admin/getModerationActions'
|
|
51
|
-
import getModerationReport from './com/atproto/admin/getModerationReport'
|
|
52
|
-
import getModerationReports from './com/atproto/admin/getModerationReports'
|
|
48
|
+
import queryModerationStatuses from './com/atproto/admin/queryModerationStatuses'
|
|
53
49
|
import resolveHandle from './com/atproto/identity/resolveHandle'
|
|
54
50
|
import getRecord from './com/atproto/repo/getRecord'
|
|
51
|
+
import queryModerationEvents from './com/atproto/admin/queryModerationEvents'
|
|
52
|
+
import getModerationEvent from './com/atproto/admin/getModerationEvent'
|
|
53
|
+
import fetchLabels from './com/atproto/temp/fetchLabels'
|
|
55
54
|
|
|
56
55
|
export * as health from './health'
|
|
57
56
|
|
|
@@ -74,6 +73,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
74
73
|
getListFeed(server, ctx)
|
|
75
74
|
getPostThread(server, ctx)
|
|
76
75
|
getPosts(server, ctx)
|
|
76
|
+
searchPosts(server, ctx)
|
|
77
77
|
getActorLikes(server, ctx)
|
|
78
78
|
getProfile(server, ctx)
|
|
79
79
|
getProfiles(server, ctx)
|
|
@@ -102,17 +102,15 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
102
102
|
getTimelineSkeleton(server, ctx)
|
|
103
103
|
// com.atproto
|
|
104
104
|
createReport(server, ctx)
|
|
105
|
-
|
|
106
|
-
reverseModerationAction(server, ctx)
|
|
107
|
-
takeModerationAction(server, ctx)
|
|
105
|
+
emitModerationEvent(server, ctx)
|
|
108
106
|
searchRepos(server, ctx)
|
|
109
107
|
adminGetRecord(server, ctx)
|
|
110
108
|
getRepo(server, ctx)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
getModerationReports(server, ctx)
|
|
109
|
+
getModerationEvent(server, ctx)
|
|
110
|
+
queryModerationEvents(server, ctx)
|
|
111
|
+
queryModerationStatuses(server, ctx)
|
|
115
112
|
resolveHandle(server, ctx)
|
|
116
113
|
getRecord(server, ctx)
|
|
114
|
+
fetchLabels(server, ctx)
|
|
117
115
|
return server
|
|
118
116
|
}
|
package/src/auth.ts
CHANGED
|
@@ -7,35 +7,43 @@ import { ServerConfig } from './config'
|
|
|
7
7
|
const BASIC = 'Basic '
|
|
8
8
|
const BEARER = 'Bearer '
|
|
9
9
|
|
|
10
|
-
export const authVerifier =
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export const authVerifier = (
|
|
11
|
+
idResolver: IdResolver,
|
|
12
|
+
opts: { aud: string | null },
|
|
13
|
+
) => {
|
|
14
|
+
const getSigningKey = async (
|
|
15
|
+
did: string,
|
|
16
|
+
forceRefresh: boolean,
|
|
17
|
+
): Promise<string> => {
|
|
18
|
+
const atprotoData = await idResolver.did.resolveAtprotoData(
|
|
19
|
+
did,
|
|
20
|
+
forceRefresh,
|
|
21
|
+
)
|
|
22
|
+
return atprotoData.signingKey
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return async (reqCtx: { req: express.Request; res: express.Response }) => {
|
|
13
26
|
const jwtStr = getJwtStrFromReq(reqCtx.req)
|
|
14
27
|
if (!jwtStr) {
|
|
15
28
|
throw new AuthRequiredError('missing jwt', 'MissingJwt')
|
|
16
29
|
}
|
|
17
|
-
const payload = await verifyJwt(
|
|
18
|
-
jwtStr,
|
|
19
|
-
opts.aud,
|
|
20
|
-
async (did, forceRefresh) => {
|
|
21
|
-
const atprotoData = await idResolver.did.resolveAtprotoData(
|
|
22
|
-
did,
|
|
23
|
-
forceRefresh,
|
|
24
|
-
)
|
|
25
|
-
return atprotoData.signingKey
|
|
26
|
-
},
|
|
27
|
-
)
|
|
30
|
+
const payload = await verifyJwt(jwtStr, opts.aud, getSigningKey)
|
|
28
31
|
return { credentials: { did: payload.iss }, artifacts: { aud: opts.aud } }
|
|
29
32
|
}
|
|
33
|
+
}
|
|
30
34
|
|
|
31
|
-
export const authOptionalVerifier =
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
export const authOptionalVerifier = (
|
|
36
|
+
idResolver: IdResolver,
|
|
37
|
+
opts: { aud: string | null },
|
|
38
|
+
) => {
|
|
39
|
+
const verifyAccess = authVerifier(idResolver, opts)
|
|
40
|
+
return async (reqCtx: { req: express.Request; res: express.Response }) => {
|
|
34
41
|
if (!reqCtx.req.headers.authorization) {
|
|
35
42
|
return { credentials: { did: null } }
|
|
36
43
|
}
|
|
37
|
-
return
|
|
44
|
+
return verifyAccess(reqCtx)
|
|
38
45
|
}
|
|
46
|
+
}
|
|
39
47
|
|
|
40
48
|
export const authOptionalAccessOrRoleVerifier = (
|
|
41
49
|
idResolver: IdResolver,
|
|
@@ -127,9 +135,9 @@ export const buildBasicAuth = (username: string, password: string): string => {
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
export const getJwtStrFromReq = (req: express.Request): string | null => {
|
|
130
|
-
const { authorization
|
|
131
|
-
if (!authorization
|
|
138
|
+
const { authorization } = req.headers
|
|
139
|
+
if (!authorization?.startsWith(BEARER)) {
|
|
132
140
|
return null
|
|
133
141
|
}
|
|
134
|
-
return authorization.
|
|
142
|
+
return authorization.slice(BEARER.length).trim()
|
|
135
143
|
}
|