@atproto/bsky 0.0.19 → 0.0.20
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 +7 -0
- package/dist/index.js +22 -4
- package/dist/index.js.map +2 -2
- package/dist/lexicon/lexicons.d.ts +4 -0
- package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +1 -0
- package/dist/services/moderation/index.d.ts +1 -0
- package/package.json +5 -5
- package/src/api/app/bsky/actor/getProfile.ts +17 -6
- package/src/api/app/bsky/notification/listNotifications.ts +1 -1
- package/src/lexicon/lexicons.ts +4 -0
- package/src/lexicon/types/app/bsky/notification/listNotifications.ts +1 -0
- package/src/services/moderation/index.ts +12 -0
- package/tests/views/notifications.test.ts +7 -0
- package/tests/views/profile.test.ts +46 -0
|
@@ -89,6 +89,7 @@ export declare class ModerationService {
|
|
|
89
89
|
legacyRefId: number | null;
|
|
90
90
|
} | null | undefined>;
|
|
91
91
|
getSubjectsDueForReversal(): Promise<ModerationSubjectStatusRow[]>;
|
|
92
|
+
isSubjectSuspended(did: string): Promise<boolean>;
|
|
92
93
|
revertState({ createdBy, createdAt, comment, action, subject, }: ReversibleModerationEvent): Promise<{
|
|
93
94
|
result: ModerationEventRow;
|
|
94
95
|
restored?: TakedownSubjects;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"sharp": "^0.32.6",
|
|
36
36
|
"typed-emitter": "^2.1.0",
|
|
37
37
|
"uint8arrays": "3.0.0",
|
|
38
|
-
"@atproto/api": "^0.7.
|
|
38
|
+
"@atproto/api": "^0.7.3",
|
|
39
39
|
"@atproto/common": "^0.3.3",
|
|
40
40
|
"@atproto/crypto": "^0.3.0",
|
|
41
41
|
"@atproto/syntax": "^0.1.5",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"@types/pg": "^8.6.6",
|
|
53
53
|
"@types/qs": "^6.9.7",
|
|
54
54
|
"axios": "^0.27.2",
|
|
55
|
-
"@atproto/api": "^0.7.
|
|
56
|
-
"@atproto/dev-env": "^0.2.
|
|
55
|
+
"@atproto/api": "^0.7.3",
|
|
56
|
+
"@atproto/dev-env": "^0.2.20",
|
|
57
57
|
"@atproto/lex-cli": "^0.2.5",
|
|
58
|
-
"@atproto/pds": "^0.3.
|
|
58
|
+
"@atproto/pds": "^0.3.8",
|
|
59
59
|
"@atproto/xrpc": "^0.4.1"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../../../services/actor'
|
|
12
12
|
import { setRepoRev } from '../../../util'
|
|
13
13
|
import { createPipeline, noRules } from '../../../../pipeline'
|
|
14
|
+
import { ModerationService } from '../../../../services/moderation'
|
|
14
15
|
|
|
15
16
|
export default function (server: Server, ctx: AppContext) {
|
|
16
17
|
const getProfile = createPipeline(skeleton, hydration, noRules, presentation)
|
|
@@ -19,6 +20,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
20
|
handler: async ({ auth, params, res }) => {
|
|
20
21
|
const db = ctx.db.getReplica()
|
|
21
22
|
const actorService = ctx.services.actor(db)
|
|
23
|
+
const modService = ctx.services.moderation(ctx.db.getPrimary())
|
|
22
24
|
const viewer = 'did' in auth.credentials ? auth.credentials.did : null
|
|
23
25
|
const canViewTakendownProfile =
|
|
24
26
|
auth.credentials.type === 'role' && auth.credentials.triage
|
|
@@ -26,7 +28,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
26
28
|
const [result, repoRev] = await Promise.allSettled([
|
|
27
29
|
getProfile(
|
|
28
30
|
{ ...params, viewer, canViewTakendownProfile },
|
|
29
|
-
{ db, actorService },
|
|
31
|
+
{ db, actorService, modService },
|
|
30
32
|
),
|
|
31
33
|
actorService.getRepoRev(viewer),
|
|
32
34
|
])
|
|
@@ -50,17 +52,25 @@ const skeleton = async (
|
|
|
50
52
|
params: Params,
|
|
51
53
|
ctx: Context,
|
|
52
54
|
): Promise<SkeletonState> => {
|
|
53
|
-
const { actorService } = ctx
|
|
55
|
+
const { actorService, modService } = ctx
|
|
54
56
|
const { canViewTakendownProfile } = params
|
|
55
57
|
const actor = await actorService.getActor(params.actor, true)
|
|
56
58
|
if (!actor) {
|
|
57
59
|
throw new InvalidRequestError('Profile not found')
|
|
58
60
|
}
|
|
59
61
|
if (!canViewTakendownProfile && softDeleted(actor)) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const isSuspended = await modService.isSubjectSuspended(actor.did)
|
|
63
|
+
if (isSuspended) {
|
|
64
|
+
throw new InvalidRequestError(
|
|
65
|
+
'Account has been temporarily suspended',
|
|
66
|
+
'AccountTakedown',
|
|
67
|
+
)
|
|
68
|
+
} else {
|
|
69
|
+
throw new InvalidRequestError(
|
|
70
|
+
'Account has been taken down',
|
|
71
|
+
'AccountTakedown',
|
|
72
|
+
)
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
return { params, actor }
|
|
66
76
|
}
|
|
@@ -95,6 +105,7 @@ const presentation = (state: HydrationState, ctx: Context) => {
|
|
|
95
105
|
type Context = {
|
|
96
106
|
db: Database
|
|
97
107
|
actorService: ActorService
|
|
108
|
+
modService: ModerationService
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
type Params = QueryParams & {
|
|
@@ -148,7 +148,7 @@ const presentation = (state: HydrationState) => {
|
|
|
148
148
|
labels: [...recordLabels, ...recordSelfLabels],
|
|
149
149
|
}
|
|
150
150
|
})
|
|
151
|
-
return { notifications, cursor }
|
|
151
|
+
return { notifications, cursor, seenAt: lastSeenNotifs }
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
const getRecordMap = async (
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -323,6 +323,18 @@ export class ModerationService {
|
|
|
323
323
|
return subjectsDueForReversal
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
async isSubjectSuspended(did: string): Promise<boolean> {
|
|
327
|
+
const res = await this.db.db
|
|
328
|
+
.selectFrom('moderation_subject_status')
|
|
329
|
+
.where('did', '=', did)
|
|
330
|
+
.where('recordPath', '=', '')
|
|
331
|
+
.where('suspendUntil', '>', new Date().toISOString())
|
|
332
|
+
.select('did')
|
|
333
|
+
.limit(1)
|
|
334
|
+
.executeTakeFirst()
|
|
335
|
+
return !!res
|
|
336
|
+
}
|
|
337
|
+
|
|
326
338
|
async revertState({
|
|
327
339
|
createdBy,
|
|
328
340
|
createdAt,
|
|
@@ -176,6 +176,13 @@ describe('notification views', () => {
|
|
|
176
176
|
encoding: 'application/json',
|
|
177
177
|
},
|
|
178
178
|
)
|
|
179
|
+
const full2 = await agent.api.app.bsky.notification.listNotifications(
|
|
180
|
+
{},
|
|
181
|
+
{ headers: await network.serviceHeaders(alice) },
|
|
182
|
+
)
|
|
183
|
+
expect(full2.data.notifications.length).toBe(full.data.notifications.length)
|
|
184
|
+
expect(full2.data.seenAt).toEqual(seenAt)
|
|
185
|
+
|
|
179
186
|
const notifCount = await agent.api.app.bsky.notification.getUnreadCount(
|
|
180
187
|
{},
|
|
181
188
|
{ headers: await network.serviceHeaders(alice) },
|
|
@@ -224,6 +224,52 @@ describe('pds profile views', () => {
|
|
|
224
224
|
)
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
+
it('blocked by actor suspension', async () => {
|
|
228
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
229
|
+
{
|
|
230
|
+
event: {
|
|
231
|
+
$type: 'com.atproto.admin.defs#modEventTakedown',
|
|
232
|
+
durationInHours: 1,
|
|
233
|
+
},
|
|
234
|
+
subject: {
|
|
235
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
236
|
+
did: alice,
|
|
237
|
+
},
|
|
238
|
+
createdBy: 'did:example:admin',
|
|
239
|
+
reason: 'Y',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
encoding: 'application/json',
|
|
243
|
+
headers: network.pds.adminAuthHeaders(),
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
const promise = agent.api.app.bsky.actor.getProfile(
|
|
247
|
+
{ actor: alice },
|
|
248
|
+
{ headers: await network.serviceHeaders(bob) },
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
await expect(promise).rejects.toThrow(
|
|
252
|
+
'Account has been temporarily suspended',
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// Cleanup
|
|
256
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
257
|
+
{
|
|
258
|
+
event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
|
|
259
|
+
subject: {
|
|
260
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
261
|
+
did: alice,
|
|
262
|
+
},
|
|
263
|
+
createdBy: 'did:example:admin',
|
|
264
|
+
reason: 'Y',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
encoding: 'application/json',
|
|
268
|
+
headers: network.pds.adminAuthHeaders(),
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
})
|
|
272
|
+
|
|
227
273
|
async function updateProfile(did: string, record: Record<string, unknown>) {
|
|
228
274
|
return await pdsAgent.api.com.atproto.repo.putRecord(
|
|
229
275
|
{
|