@evanp/activitypub-bot 0.39.0 → 0.39.2
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/lib/activitypubclient.js +49 -12
- package/lib/app.js +39 -24
- package/lib/httpmessagesignature.js +1 -1
- package/lib/ratelimiter.js +1 -1
- package/lib/remoteobjectcache.js +1 -1
- package/lib/signaturepolicystorage.js +1 -1
- package/package.json +1 -1
package/lib/activitypubclient.js
CHANGED
|
@@ -4,7 +4,6 @@ import path from 'node:path'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
|
|
6
6
|
import fetch from 'node-fetch'
|
|
7
|
-
import { ProblemDetailsError } from './errors.js'
|
|
8
7
|
|
|
9
8
|
import as2 from './activitystreams.js'
|
|
10
9
|
import { SignaturePolicyStorage } from './signaturepolicystorage.js'
|
|
@@ -23,6 +22,32 @@ const COLLECTION_TYPES = [
|
|
|
23
22
|
`${NS}OrderedCollection`
|
|
24
23
|
]
|
|
25
24
|
|
|
25
|
+
function normalizeHeaders (headers) {
|
|
26
|
+
if (!headers) {
|
|
27
|
+
return headers
|
|
28
|
+
}
|
|
29
|
+
if (typeof headers.forEach === 'function') {
|
|
30
|
+
const result = {}
|
|
31
|
+
headers.forEach((value, key) => {
|
|
32
|
+
result[key] = value
|
|
33
|
+
})
|
|
34
|
+
return result
|
|
35
|
+
}
|
|
36
|
+
return headers
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ActivityPubClientError extends Error {
|
|
40
|
+
constructor (status, message, { url, method, headers, body } = {}) {
|
|
41
|
+
super(message)
|
|
42
|
+
this.name = 'ActivityPubClientError'
|
|
43
|
+
this.status = status
|
|
44
|
+
this.url = url
|
|
45
|
+
this.method = method
|
|
46
|
+
this.headers = normalizeHeaders(headers)
|
|
47
|
+
this.body = body
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
export class ActivityPubClient {
|
|
27
52
|
static #githubUrl = 'https://github.com/evanp/activitypub-bot'
|
|
28
53
|
static #userAgent = `activitypub.bot/${version} (${ActivityPubClient.#githubUrl})`
|
|
@@ -188,10 +213,10 @@ export class ActivityPubClient {
|
|
|
188
213
|
{ status: res.status, body, url: baseUrl },
|
|
189
214
|
'Could not fetch url'
|
|
190
215
|
)
|
|
191
|
-
throw new
|
|
192
|
-
|
|
216
|
+
throw new ActivityPubClientError(
|
|
217
|
+
res.status,
|
|
193
218
|
`Could not fetch ${baseUrl}`,
|
|
194
|
-
{ headers: res.headers }
|
|
219
|
+
{ url: baseUrl, method, headers: res.headers, body }
|
|
195
220
|
)
|
|
196
221
|
}
|
|
197
222
|
|
|
@@ -247,23 +272,32 @@ export class ActivityPubClient {
|
|
|
247
272
|
const json = await obj.export()
|
|
248
273
|
this.#fixupJson(json)
|
|
249
274
|
const body = JSON.stringify(json)
|
|
250
|
-
const
|
|
275
|
+
const digest = await this.#digester.digest(body)
|
|
276
|
+
const contentDigest = await this.#digester.contentDigest(body)
|
|
277
|
+
const baseHeaders = {
|
|
251
278
|
date: new Date().toUTCString(),
|
|
252
279
|
'user-agent': ActivityPubClient.#userAgent,
|
|
253
|
-
'content-type': 'application/activity+json'
|
|
254
|
-
digest: await this.#digester.digest(body)
|
|
280
|
+
'content-type': 'application/activity+json'
|
|
255
281
|
}
|
|
256
282
|
const method = 'POST'
|
|
257
|
-
|
|
283
|
+
let headers
|
|
258
284
|
this.#logger.debug({ url }, 'Signing POST')
|
|
259
285
|
let lastPolicy
|
|
260
286
|
const storedPolicy = await this.#policyStorage.get(parsed.origin)
|
|
261
287
|
if (!storedPolicy || storedPolicy === SignaturePolicyStorage.RFC9421) {
|
|
262
288
|
lastPolicy = SignaturePolicyStorage.RFC9421
|
|
289
|
+
headers = {
|
|
290
|
+
...baseHeaders,
|
|
291
|
+
'content-digest': contentDigest
|
|
292
|
+
}
|
|
263
293
|
const sigHeaders = await this.#messageSign({ username, url, method, headers })
|
|
264
294
|
Object.assign(headers, sigHeaders || {})
|
|
265
295
|
} else if (storedPolicy === SignaturePolicyStorage.DRAFT_CAVAGE_12) {
|
|
266
296
|
lastPolicy = SignaturePolicyStorage.DRAFT_CAVAGE_12
|
|
297
|
+
headers = {
|
|
298
|
+
...baseHeaders,
|
|
299
|
+
digest
|
|
300
|
+
}
|
|
267
301
|
headers.signature =
|
|
268
302
|
await this.#sign({ username, url, method, headers })
|
|
269
303
|
} else {
|
|
@@ -282,7 +316,10 @@ export class ActivityPubClient {
|
|
|
282
316
|
)
|
|
283
317
|
if ([401, 403].includes(res.status) && !storedPolicy) {
|
|
284
318
|
lastPolicy = SignaturePolicyStorage.DRAFT_CAVAGE_12
|
|
285
|
-
|
|
319
|
+
headers = {
|
|
320
|
+
...baseHeaders,
|
|
321
|
+
digest
|
|
322
|
+
}
|
|
286
323
|
headers.signature =
|
|
287
324
|
await this.#sign({ username, url, method, headers })
|
|
288
325
|
res = await fetch(url,
|
|
@@ -297,10 +334,10 @@ export class ActivityPubClient {
|
|
|
297
334
|
await this.#limiter.update(hostname, res.headers)
|
|
298
335
|
this.#logger.debug({ url }, 'Done fetching POST')
|
|
299
336
|
if (res.status < 200 || res.status > 299) {
|
|
300
|
-
throw new
|
|
301
|
-
|
|
337
|
+
throw new ActivityPubClientError(
|
|
338
|
+
res.status,
|
|
302
339
|
`Could not post to ${url}`,
|
|
303
|
-
{ headers: res.headers }
|
|
340
|
+
{ url, method, headers: res.headers }
|
|
304
341
|
)
|
|
305
342
|
}
|
|
306
343
|
if (!storedPolicy && lastPolicy) {
|
package/lib/app.js
CHANGED
|
@@ -9,7 +9,7 @@ import Logger from 'pino'
|
|
|
9
9
|
import HTTPLogger from 'pino-http'
|
|
10
10
|
|
|
11
11
|
import { ActivityDistributor } from './activitydistributor.js'
|
|
12
|
-
import { ActivityPubClient } from './activitypubclient.js'
|
|
12
|
+
import { ActivityPubClient, ActivityPubClientError } from './activitypubclient.js'
|
|
13
13
|
import { ActorStorage } from './actorstorage.js'
|
|
14
14
|
import { BotDataStorage } from './botdatastorage.js'
|
|
15
15
|
import { KeyStorage } from './keystorage.js'
|
|
@@ -255,31 +255,46 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
|
|
|
255
255
|
|
|
256
256
|
app.use((err, req, res, next) => {
|
|
257
257
|
const { logger } = req.app.locals
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
258
|
+
if (err instanceof ActivityPubClientError) {
|
|
259
|
+
logger.error({
|
|
260
|
+
err,
|
|
261
|
+
status: err.status,
|
|
262
|
+
url: err.url,
|
|
263
|
+
method: err.method
|
|
264
|
+
}, 'ActivityPub client request failed')
|
|
265
|
+
res.status(500).type('application/problem+json').json({
|
|
266
|
+
type: 'about:blank',
|
|
267
|
+
title: 'Internal Server Error',
|
|
268
|
+
status: 500,
|
|
269
|
+
detail: 'Internal Server Error'
|
|
270
|
+
})
|
|
270
271
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
let status = 500
|
|
273
|
+
if (err.status) {
|
|
274
|
+
status = err.status
|
|
275
|
+
}
|
|
276
|
+
const title = (http.STATUS_CODES[status])
|
|
277
|
+
? http.STATUS_CODES[status]
|
|
278
|
+
: 'Unknown Status'
|
|
279
|
+
|
|
280
|
+
if (status >= 500 && status < 600) {
|
|
281
|
+
logger.error(err)
|
|
282
|
+
} else if (status >= 400 && status < 500) {
|
|
283
|
+
logger.warn(err)
|
|
284
|
+
} else {
|
|
285
|
+
logger.debug(err)
|
|
286
|
+
}
|
|
287
|
+
const type = err.type || 'about:blank'
|
|
288
|
+
const problemTitle = err.title || title
|
|
289
|
+
const extra = Object.fromEntries(
|
|
290
|
+
Object.entries(err).filter(
|
|
291
|
+
([k]) => !['status', 'type', 'title', 'detail', 'stack'].includes(k)
|
|
292
|
+
)
|
|
278
293
|
)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
res.status(status)
|
|
295
|
+
res.type('application/problem+json')
|
|
296
|
+
res.json({ type, title: problemTitle, status, detail: err.message, ...extra })
|
|
297
|
+
}
|
|
283
298
|
})
|
|
284
299
|
|
|
285
300
|
app.onIdle = async () => {
|
package/lib/ratelimiter.js
CHANGED
|
@@ -11,7 +11,7 @@ export class RateLimiter {
|
|
|
11
11
|
assert.strictEqual(typeof connection, 'object')
|
|
12
12
|
assert.strictEqual(typeof logger, 'object')
|
|
13
13
|
this.#connection = connection
|
|
14
|
-
this.#logger = logger
|
|
14
|
+
this.#logger = logger.child({ class: this.constructor.name })
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async limit (host, maxWaitTime = 30000) {
|
package/lib/remoteobjectcache.js
CHANGED
|
@@ -18,7 +18,7 @@ export class SignaturePolicyStorage {
|
|
|
18
18
|
assert.ok(logger)
|
|
19
19
|
assert.strictEqual(typeof logger, 'object')
|
|
20
20
|
this.#connection = connection
|
|
21
|
-
this.#logger = logger
|
|
21
|
+
this.#logger = logger.child({ class: this.constructor.name })
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async get (origin) {
|