@evanp/activitypub-bot 0.37.1 → 0.38.0

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/app.js CHANGED
@@ -45,10 +45,12 @@ import { FanoutWorker } from './fanoutworker.js'
45
45
  import { IntakeWorker } from './intakeworker.js'
46
46
  import { RemoteObjectCache } from './remoteobjectcache.js'
47
47
  import { HTTPMessageSignature } from './httpmessagesignature.js'
48
+ import { randomUUID } from 'node:crypto'
48
49
 
49
50
  const currentDir = dirname(fileURLToPath(import.meta.url))
50
51
  const DEFAULT_INDEX_FILENAME = resolve(currentDir, '..', 'web', 'index.html')
51
52
  const DEFAULT_PROFILE_FILENAME = resolve(currentDir, '..', 'web', 'profile.html')
53
+ const UUID_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
52
54
 
53
55
  function createWorkers (logger, count, WorkerClass, ...args) {
54
56
  const workers = Array.from({ length: count }, () => new WorkerClass(...args))
@@ -182,9 +184,20 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
182
184
  profileFileName
183
185
  }
184
186
 
187
+ app.use(async (req, res, next) => {
188
+ let id = req.get('x-request-id')
189
+ if (!id || !id.match(UUID_REGEXP)) {
190
+ id = randomUUID()
191
+ }
192
+ req.id = id
193
+ res.set('X-Request-ID', id)
194
+ next()
195
+ })
196
+
185
197
  app.use(HTTPLogger({
186
198
  logger,
187
- level: logLevel
199
+ level: logLevel,
200
+ genReqId: (req) => req.id
188
201
  }))
189
202
 
190
203
  app.use(express.json({
@@ -39,6 +39,8 @@ export class HTTPMessageSignature {
39
39
  }
40
40
 
41
41
  async sign ({ privateKey, keyId, url, method, headers }) {
42
+ this.#logger.debug({ privateKey, keyId, url, method, headers }, 'signing')
43
+
42
44
  const parsed = new URL(url)
43
45
 
44
46
  const signatureInput = []
@@ -61,30 +63,50 @@ export class HTTPMessageSignature {
61
63
 
62
64
  signatureInput.push(['@signature-params', signatureParams])
63
65
 
66
+ this.#logger.debug({ signatureInput }, 'built data structure')
67
+
64
68
  const data = signatureInput.map(pair => `"${pair[0]}": ${pair[1]}`).join('\n')
65
69
 
70
+ this.#logger.debug({ data }, 'data')
71
+
66
72
  const signer = crypto.createSign('sha256')
67
73
  signer.update(data)
68
74
  const signature = signer.sign(privateKey).toString('base64')
69
75
  signer.end()
70
76
 
71
- return {
77
+ const result = {
72
78
  'signature-input': `sig1=${signatureParams}`,
73
79
  signature: `sig1=:${signature}:`
74
80
  }
81
+
82
+ this.#logger.debug({ result }, 'returning headers')
83
+
84
+ return result
75
85
  }
76
86
 
77
87
  async validate (publicKeyPem, signatureInput, signature, method, url, headers) {
88
+ this.#logger.debug(
89
+ { publicKeyPem, signatureInput, signature, method, url, headers }, 'validating signature'
90
+ )
78
91
  const inputs = this.#parseSignatureInput(signatureInput)
92
+ this.#logger.debug(
93
+ { inputs }, 'validating signature'
94
+ )
79
95
  const input = this.#bestInput(inputs)
80
96
  if (!input) {
81
97
  throw new Error('No input with supported algorithms')
82
98
  }
99
+ this.#logger.debug(
100
+ { input }, 'best input'
101
+ )
83
102
  const bytes = this.#sigBytes(signature, input.name)
84
103
  if (!bytes) {
85
104
  throw new Error('No input with supported algorithms')
86
105
  }
87
106
  const data = this.#inputData(input, method, url, headers)
107
+ this.#logger.debug(
108
+ { data }, 'input data'
109
+ )
88
110
  const verifier = this.#getVerifier(input.alg)
89
111
  verifier.update(data)
90
112
  const options = this.#getVerifierOptions(input.alg)
@@ -130,6 +130,15 @@ export class HTTPSignatureAuthenticator {
130
130
  }
131
131
 
132
132
  async #authenticateMessageSignature (signature, signatureInput, originalUrl, req, res, next) {
133
+ this.#logger.debug(
134
+ { signature, signatureInput, originalUrl },
135
+ 'authenticating message signature'
136
+ )
137
+ const date = req.get('Date')
138
+ if (date && Math.abs(Date.parse(date) - Date.now()) >
139
+ HTTPSignatureAuthenticator.#maxDateDiff) {
140
+ throw createHttpError(400, 'Time skew too large')
141
+ }
133
142
  if (req.rawBodyText && req.rawBodyText.length > 0) {
134
143
  const digest = req.get('Content-Digest')
135
144
  if (!digest) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.37.1",
3
+ "version": "0.38.0",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",