@evanp/activitypub-bot 0.12.1 → 0.13.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 (46) hide show
  1. package/lib/app.js +19 -1
  2. package/lib/index.js +2 -0
  3. package/lib/routes/collection.js +83 -67
  4. package/lib/routes/inbox.js +8 -0
  5. package/lib/routes/object.js +25 -5
  6. package/package.json +3 -3
  7. package/.github/dependabot.yml +0 -11
  8. package/.github/workflows/main.yml +0 -34
  9. package/.github/workflows/tag.yml +0 -106
  10. package/.nvmrc +0 -1
  11. package/Dockerfile +0 -17
  12. package/docs/activitypub.bot.drawio +0 -110
  13. package/tests/activitydistributor.test.js +0 -606
  14. package/tests/activityhandler.test.js +0 -2276
  15. package/tests/activitypubclient.test.js +0 -210
  16. package/tests/actorstorage.test.js +0 -283
  17. package/tests/app.test.js +0 -17
  18. package/tests/authorizer.test.js +0 -301
  19. package/tests/bot.donothing.test.js +0 -30
  20. package/tests/bot.ok.test.js +0 -101
  21. package/tests/botcontext.test.js +0 -720
  22. package/tests/botdatastorage.test.js +0 -88
  23. package/tests/botfactory.provincebotfactory.test.js +0 -430
  24. package/tests/digester.test.js +0 -56
  25. package/tests/fixtures/bots.js +0 -27
  26. package/tests/fixtures/eventloggingbot.js +0 -57
  27. package/tests/fixtures/provincebotfactory.js +0 -53
  28. package/tests/httpsignature.test.js +0 -199
  29. package/tests/httpsignatureauthenticator.test.js +0 -463
  30. package/tests/index.test.js +0 -10
  31. package/tests/keystorage.test.js +0 -124
  32. package/tests/microsyntax.test.js +0 -123
  33. package/tests/objectcache.test.js +0 -133
  34. package/tests/objectstorage.test.js +0 -149
  35. package/tests/remotekeystorage.test.js +0 -78
  36. package/tests/routes.actor.test.js +0 -214
  37. package/tests/routes.collection.test.js +0 -433
  38. package/tests/routes.health.test.js +0 -41
  39. package/tests/routes.inbox.test.js +0 -136
  40. package/tests/routes.object.test.js +0 -525
  41. package/tests/routes.server.test.js +0 -69
  42. package/tests/routes.sharedinbox.test.js +0 -473
  43. package/tests/routes.webfinger.test.js +0 -68
  44. package/tests/urlformatter.test.js +0 -164
  45. package/tests/utils/digest.js +0 -7
  46. package/tests/utils/nock.js +0 -499
@@ -1,301 +0,0 @@
1
- import { describe, it, before, after } from 'node:test'
2
- import { Authorizer } from '../lib/authorizer.js'
3
- import { ActorStorage } from '../lib/actorstorage.js'
4
- import { Sequelize } from 'sequelize'
5
- import { UrlFormatter } from '../lib/urlformatter.js'
6
- import { KeyStorage } from '../lib/keystorage.js'
7
- import { ActivityPubClient } from '../lib/activitypubclient.js'
8
- import as2 from '../lib/activitystreams.js'
9
- import assert from 'node:assert/strict'
10
- import { nanoid } from 'nanoid'
11
- import { HTTPSignature } from '../lib/httpsignature.js'
12
- import Logger from 'pino'
13
- import { Digester } from '../lib/digester.js'
14
- import { runMigrations } from '../lib/migrations/index.js'
15
-
16
- describe('Authorizer', () => {
17
- let authorizer = null
18
- let actorStorage = null
19
- let formatter = null
20
- let connection = null
21
- let keyStorage = null
22
- let client = null
23
-
24
- let actor1 = null
25
- let actor2 = null
26
- let actor3 = null
27
- let publicObject = null
28
- let followersOnlyObject = null
29
- let privateObject = null
30
- let remoteUnconnected = null
31
- let remoteFollower = null
32
- let remoteAddressee = null
33
- let remotePublicObject = null
34
- let remotePrivateObject = null
35
-
36
- before(async () => {
37
- const logger = Logger({
38
- level: 'silent'
39
- })
40
- formatter = new UrlFormatter('https://activitypubbot.example')
41
- connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
42
- await connection.authenticate()
43
- await runMigrations(connection)
44
- actorStorage = new ActorStorage(connection, formatter)
45
- keyStorage = new KeyStorage(connection, logger)
46
- const signer = new HTTPSignature(logger)
47
- const digester = new Digester(logger)
48
- client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
49
- actor1 = await actorStorage.getActor('test1')
50
- actor2 = await actorStorage.getActor('test2')
51
- await actorStorage.addToCollection(
52
- 'test1',
53
- 'followers',
54
- actor2
55
- )
56
- actor3 = await actorStorage.getActor('test3')
57
- remoteUnconnected = await as2.import({
58
- id: 'https://remote.example/user/remote1',
59
- type: 'Person',
60
- preferredUsername: 'remote1',
61
- to: 'as:Public'
62
- })
63
- remoteFollower = await as2.import({
64
- id: 'https://remote.example/user/remote2',
65
- type: 'Person',
66
- preferredUsername: 'remote2',
67
- to: 'as:Public'
68
- })
69
- await actorStorage.addToCollection(
70
- 'test1',
71
- 'followers',
72
- remoteFollower
73
- )
74
- remoteAddressee = await as2.import({
75
- id: 'https://remote.example/user/remote3',
76
- type: 'Person',
77
- preferredUsername: 'remote3',
78
- to: 'as:Public'
79
- })
80
- publicObject = await as2.import({
81
- id: formatter.format({
82
- username: 'test1',
83
- type: 'object',
84
- nanoid: nanoid()
85
- }),
86
- type: 'Object',
87
- attributedTo: actor1.id,
88
- to: 'as:Public'
89
- })
90
- followersOnlyObject = await as2.import({
91
- id: formatter.format({
92
- username: 'test1',
93
- type: 'object',
94
- nanoid: nanoid()
95
- }),
96
- type: 'Object',
97
- attributedTo: actor1.id,
98
- to: formatter.format({
99
- username: 'test1',
100
- collection: 'followers'
101
- })
102
- })
103
- privateObject = await as2.import({
104
- id: formatter.format({
105
- username: 'test1',
106
- type: 'object',
107
- nanoid: nanoid()
108
- }),
109
- type: 'Object',
110
- attributedTo: actor1.id,
111
- to: [actor2.id, remoteAddressee.id]
112
- })
113
- remotePublicObject = await as2.import({
114
- id: 'https://remote.example/user/remote1/object/1',
115
- type: 'Object',
116
- attributedTo: remoteUnconnected.id,
117
- to: 'as:Public'
118
- })
119
- remotePrivateObject = await as2.import({
120
- id: 'https://remote.example/user/remote1/object/2',
121
- type: 'Object',
122
- attributedTo: remoteUnconnected.id,
123
- to: actor2.id
124
- })
125
- })
126
-
127
- after(async () => {
128
- await connection.close()
129
- formatter = null
130
- actorStorage = null
131
- connection = null
132
- authorizer = null
133
- })
134
-
135
- it('should be a class', async () => {
136
- assert.strictEqual(typeof Authorizer, 'function')
137
- })
138
-
139
- it('can be instantiated', async () => {
140
- try {
141
- authorizer = new Authorizer(actorStorage, formatter, client)
142
- assert.strictEqual(typeof authorizer, 'object')
143
- } catch (error) {
144
- assert.fail(error)
145
- }
146
- })
147
-
148
- it('can check the creator can read a public local object', async () => {
149
- assert.strictEqual(true, await authorizer.canRead(actor1, publicObject))
150
- })
151
-
152
- it('can check the creator can read a followers-only local object', async () => {
153
- assert.strictEqual(
154
- true,
155
- await authorizer.canRead(actor1, followersOnlyObject)
156
- )
157
- })
158
-
159
- it('can check the creator can read a private local object', async () => {
160
- assert.strictEqual(
161
- true,
162
- await authorizer.canRead(actor1, privateObject)
163
- )
164
- })
165
-
166
- it('can check if a local follower can read a public local object', async () => {
167
- assert.strictEqual(true, await authorizer.canRead(actor2, publicObject))
168
- })
169
-
170
- it('can check if a local follower can read a followers-only local object', async () => {
171
- assert.strictEqual(true, await authorizer.canRead(actor2, followersOnlyObject))
172
- })
173
-
174
- it('can check if a local addressee can read a private local object', async () => {
175
- assert.strictEqual(true, await authorizer.canRead(actor2, privateObject))
176
- })
177
-
178
- it('can check if a local non-follower can read a public local object', async () => {
179
- assert.strictEqual(true, await authorizer.canRead(actor3, publicObject))
180
- })
181
-
182
- it('can check if a local non-follower can read a followers-only local object', async () => {
183
- assert.strictEqual(false, await authorizer.canRead(actor3, followersOnlyObject))
184
- })
185
-
186
- it('can check if a local non-addressee can read a private local object', async () => {
187
- assert.strictEqual(false, await authorizer.canRead(actor3, privateObject))
188
- })
189
-
190
- it('can check if the null actor can read a public local object', async () => {
191
- assert.strictEqual(true, await authorizer.canRead(null, publicObject))
192
- })
193
-
194
- it('can check if the null actor can read a followers-only local object', async () => {
195
- assert.strictEqual(false, await authorizer.canRead(null, followersOnlyObject))
196
- })
197
-
198
- it('can check if the null actor can read a private local object', async () => {
199
- assert.strictEqual(false, await authorizer.canRead(null, privateObject))
200
- })
201
-
202
- it('can check that an unconnected remote actor can read a public local object', async () => {
203
- assert.strictEqual(true, await authorizer.canRead(remoteUnconnected, publicObject))
204
- })
205
-
206
- it('can check that an unconnected remote actor cannot read a followers-only local object', async () => {
207
- assert.strictEqual(
208
- false,
209
- await authorizer.canRead(remoteUnconnected, followersOnlyObject)
210
- )
211
- })
212
-
213
- it('can check that an unconnected remote actor cannot read a private local object', async () => {
214
- assert.strictEqual(
215
- false,
216
- await authorizer.canRead(remoteUnconnected, privateObject)
217
- )
218
- })
219
-
220
- it('can check that a remote follower can read a public local object', async () => {
221
- assert.strictEqual(true, await authorizer.canRead(remoteFollower, publicObject))
222
- })
223
-
224
- it('can check that a remote follower can read a followers-only local object', async () => {
225
- assert.strictEqual(
226
- true,
227
- await authorizer.canRead(remoteFollower, followersOnlyObject)
228
- )
229
- })
230
-
231
- it('can check that a remote follower cannot read a private local object', async () => {
232
- assert.strictEqual(
233
- false,
234
- await authorizer.canRead(remoteFollower, privateObject)
235
- )
236
- })
237
-
238
- it('can check that a remote addressee can read a private local object', async () => {
239
- assert.strictEqual(true, await authorizer.canRead(remoteAddressee, privateObject))
240
- })
241
-
242
- it('can check that a local actor can read a public remote object', async () => {
243
- assert.strictEqual(true, await authorizer.canRead(actor1, remotePublicObject))
244
- })
245
-
246
- it('can check that a local non-addressee cannot read a private remote object', async () => {
247
- assert.strictEqual(null, await authorizer.canRead(actor1, remotePrivateObject))
248
- })
249
-
250
- it('can check that a local addressee can read a private remote object', async () => {
251
- assert.strictEqual(true, await authorizer.canRead(actor2, remotePrivateObject))
252
- })
253
-
254
- it('can check that two objects have the same origin', async () => {
255
- const object1 = await as2.import({
256
- id: 'https://example.com/object/1',
257
- type: 'Object'
258
- })
259
- const object2 = await as2.import({
260
- id: 'https://example.com/object/2',
261
- type: 'Object'
262
- })
263
- assert.strictEqual(true, await authorizer.sameOrigin(object1, object2))
264
- })
265
-
266
- it('can check that two objects have different origins', async () => {
267
- const object1 = await as2.import({
268
- id: 'https://example.com/object/1',
269
- type: 'Object'
270
- })
271
- const object2 = await as2.import({
272
- id: 'https://other.example/object/2',
273
- type: 'Object'
274
- })
275
- assert.strictEqual(false, await authorizer.sameOrigin(object1, object2))
276
- })
277
-
278
- it('can check that two objects have different origins by port', async () => {
279
- const object1 = await as2.import({
280
- id: 'https://example.com/object/1',
281
- type: 'Object'
282
- })
283
- const object2 = await as2.import({
284
- id: 'https://example.com:8000/object/2',
285
- type: 'Object'
286
- })
287
- assert.strictEqual(false, await authorizer.sameOrigin(object1, object2))
288
- })
289
-
290
- it('can check that two objects have different origins by protocol', async () => {
291
- const object1 = await as2.import({
292
- id: 'https://example.com/object/1',
293
- type: 'Object'
294
- })
295
- const object2 = await as2.import({
296
- id: 'http://example.com/object/2',
297
- type: 'Object'
298
- })
299
- assert.strictEqual(false, await authorizer.sameOrigin(object1, object2))
300
- })
301
- })
@@ -1,30 +0,0 @@
1
- import { describe, it, before } from 'node:test'
2
- import assert from 'node:assert'
3
- import request from 'supertest'
4
-
5
- import { makeApp } from '../lib/app.js'
6
-
7
- import { nockSetup } from './utils/nock.js'
8
- import bots from './fixtures/bots.js'
9
-
10
- describe('DoNothing bot', async () => {
11
- const host = 'activitypubbot.example'
12
- const origin = `https://${host}`
13
- const databaseUrl = 'sqlite::memory:'
14
- let app = null
15
-
16
- before(async () => {
17
- nockSetup('social.example')
18
- app = await makeApp(databaseUrl, origin, bots, 'silent')
19
- })
20
-
21
- describe('Bot exists', async () => {
22
- let response = null
23
- it('should work without an error', async () => {
24
- response = await request(app).get('/user/null')
25
- })
26
- it('should return 200 OK', async () => {
27
- assert.strictEqual(response.status, 200)
28
- })
29
- })
30
- })
@@ -1,101 +0,0 @@
1
- import { describe, it, before } from 'node:test'
2
- import assert from 'node:assert'
3
- import as2 from '../lib/activitystreams.js'
4
- import request from 'supertest'
5
-
6
- import { makeApp } from '../lib/app.js'
7
-
8
- import { nockSetup, nockSignature, nockFormat, postInbox } from './utils/nock.js'
9
- import { makeDigest } from './utils/digest.js'
10
- import bots from './fixtures/bots.js'
11
-
12
- describe('OK bot', async () => {
13
- const host = 'activitypubbot.example'
14
- const origin = `https://${host}`
15
- const databaseUrl = 'sqlite::memory:'
16
- let app = null
17
-
18
- before(async () => {
19
- nockSetup('social.example')
20
- app = await makeApp(databaseUrl, origin, bots, 'silent')
21
- })
22
-
23
- describe('responds to a mention', async () => {
24
- const username = 'actor2'
25
- const path = '/user/ok/inbox'
26
- const url = `${origin}${path}`
27
- const date = new Date().toUTCString()
28
- const activity = await as2.import({
29
- type: 'Create',
30
- actor: nockFormat({ username }),
31
- id: nockFormat({ username, type: 'create', num: 1 }),
32
- object: {
33
- id: nockFormat({ username, type: 'note', num: 1 }),
34
- type: 'Note',
35
- source: 'Hello, @ok!',
36
- content: `Hello, @<a href="${origin}/user/ok">ok</a>!`,
37
- to: `${origin}/user/ok`,
38
- cc: 'as:Public',
39
- attributedTo: nockFormat({ username }),
40
- tag: [
41
- {
42
- type: 'Mention',
43
- href: `${origin}/user/ok`,
44
- name: `@ok@${host}`
45
- }
46
- ]
47
- },
48
- to: `${origin}/user/ok`,
49
- cc: 'as:Public'
50
- })
51
- const body = await activity.write()
52
- const digest = makeDigest(body)
53
- const signature = await nockSignature({
54
- method: 'POST',
55
- username,
56
- url,
57
- digest,
58
- date
59
- })
60
- let response = null
61
- it('should work without an error', async () => {
62
- response = await request(app)
63
- .post(path)
64
- .send(body)
65
- .set('Signature', signature)
66
- .set('Date', date)
67
- .set('Host', host)
68
- .set('Digest', digest)
69
- .set('Content-Type', 'application/activity+json')
70
- assert.ok(response)
71
- await app.onIdle()
72
- })
73
- it('should return a 200 status', async () => {
74
- assert.strictEqual(response.status, 200, JSON.stringify(response.body))
75
- })
76
- it('should deliver the reply to the mentioned actor', async () => {
77
- assert.strictEqual(postInbox.actor2, 1)
78
- })
79
- let reply = null
80
- let note = null
81
- it('should have the reply in its outbox', async () => {
82
- const { actorStorage, objectStorage } = app.locals
83
- const outbox = await actorStorage.getCollection('ok', 'outbox')
84
- assert.strictEqual(outbox.totalItems, 1)
85
- const outboxPage = await actorStorage.getCollectionPage('ok', 'outbox', 1)
86
- assert.strictEqual(outboxPage.items.length, 1)
87
- const arry = Array.from(outboxPage.items)
88
- reply = await objectStorage.read(arry[0].id)
89
- assert.ok(reply)
90
- const objects = Array.from(reply.object)
91
- note = await objectStorage.read(objects[0].id)
92
- assert.ok(note)
93
- })
94
- it('should have the inReplyTo property', async () => {
95
- assert.strictEqual(
96
- Array.from(note.inReplyTo)[0].id,
97
- Array.from(activity.object)[0].id
98
- )
99
- })
100
- })
101
- })