@evanp/activitypub-bot 0.37.0 → 0.37.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.
@@ -24,7 +24,7 @@ export class HTTPMessageSignature {
24
24
  const inputs = this.#parseSignatureInput(signatureInput)
25
25
  const input = this.#bestInput(inputs)
26
26
  return (input)
27
- ? input.keyId
27
+ ? input.keyid
28
28
  : null
29
29
  }
30
30
 
@@ -43,8 +43,8 @@ export class HTTPMessageSignature {
43
43
 
44
44
  const signatureInput = []
45
45
 
46
- signatureInput.push(['@method', method])
47
- signatureInput.push(['@authority', parsed.hostname])
46
+ signatureInput.push(['@method', method.toUpperCase()])
47
+ signatureInput.push(['@authority', parsed.host])
48
48
  signatureInput.push(['@path', parsed.pathname])
49
49
  if (parsed.search) {
50
50
  signatureInput.push(['@query', parsed.search])
@@ -57,7 +57,7 @@ export class HTTPMessageSignature {
57
57
 
58
58
  const created = Math.floor(Date.now() / 1000)
59
59
  const componentList = signatureInput.map(([name]) => `"${name}"`).join(' ')
60
- const signatureParams = `(${componentList});keyId="${keyId}";alg="rsa-v1_5-sha256";created=${created}`
60
+ const signatureParams = `(${componentList});keyid="${keyId}";alg="rsa-v1_5-sha256";created=${created}`
61
61
 
62
62
  signatureInput.push(['@signature-params', signatureParams])
63
63
 
@@ -74,7 +74,7 @@ export class HTTPMessageSignature {
74
74
  }
75
75
  }
76
76
 
77
- async validate (publicKeyPem, signatureInput, signature, method, path, query, headers) {
77
+ async validate (publicKeyPem, signatureInput, signature, method, url, headers) {
78
78
  const inputs = this.#parseSignatureInput(signatureInput)
79
79
  const input = this.#bestInput(inputs)
80
80
  if (!input) {
@@ -84,7 +84,7 @@ export class HTTPMessageSignature {
84
84
  if (!bytes) {
85
85
  throw new Error('No input with supported algorithms')
86
86
  }
87
- const data = this.#inputData(input, method, path, query, headers)
87
+ const data = this.#inputData(input, method, url, headers)
88
88
  const verifier = this.#getVerifier(input.alg)
89
89
  verifier.update(data)
90
90
  const options = this.#getVerifierOptions(input.alg)
@@ -101,7 +101,11 @@ export class HTTPMessageSignature {
101
101
  const params = match[2].slice(1, -1)
102
102
  .split(' ')
103
103
  .filter(s => s.length > 0)
104
- .map(quoted => quoted.slice(1, -1))
104
+ .map(token => {
105
+ const m = token.match(/^"([^"]+)"(.*)$/)
106
+ if (!m) return token
107
+ return m[2] ? `${m[1]}${m[2]}` : m[1]
108
+ })
105
109
  const sigvals = {}
106
110
  for (const paramMatch of match[3].matchAll(PARAM_RE)) {
107
111
  const k = paramMatch[1]
@@ -123,8 +127,9 @@ export class HTTPMessageSignature {
123
127
  return null
124
128
  }
125
129
 
126
- #inputData (input, method, path, query, headers) {
130
+ #inputData (input, method, url, headers) {
127
131
  const signatureParams = []
132
+ const parsed = URL.parse(url)
128
133
  for (const param of input.params) {
129
134
  let value
130
135
  switch (param) {
@@ -135,18 +140,43 @@ export class HTTPMessageSignature {
135
140
  value = headers.host
136
141
  break
137
142
  case '@path':
138
- value = path
143
+ value = parsed.pathname
139
144
  break
140
145
  case '@query':
141
- value = query
146
+ value = parsed.search
147
+ break
148
+ case '@target-uri':
149
+ value = url
150
+ break
151
+ case '@scheme':
152
+ value = parsed.protocol.slice(0, -1)
153
+ break
154
+ case '@request-target':
155
+ value = (parsed.search)
156
+ ? `${parsed.pathname}${parsed.search}`
157
+ : parsed.pathname
142
158
  break
143
159
  default:
160
+ if (param.startsWith('@query-param')) {
161
+ const nameMatch = param.match(/;name="([^"]+)"/)
162
+ if (!nameMatch) throw new Error('Missing name for @query-param')
163
+ const paramName = nameMatch[1]
164
+ signatureParams.push(['@query-param', parsed.searchParams.get(paramName), `name="${paramName}"`])
165
+ continue
166
+ }
167
+ if (param.length > 0 && param[0] === '@') {
168
+ throw new Error(`Unrecognized derived component ${param}`)
169
+ }
144
170
  value = headers[param]
145
171
  }
146
172
  signatureParams.push([param, value])
147
173
  }
148
174
  signatureParams.push(['@signature-params', input.attrStr])
149
- return signatureParams.map(pair => `"${pair[0]}": ${pair[1]}`).join('\n')
175
+ return signatureParams.map(
176
+ arr => arr.length === 3
177
+ ? `"${arr[0]}";${arr[2]}: ${arr[1]}`
178
+ : `"${arr[0]}": ${arr[1]}`
179
+ ).join('\n')
150
180
  }
151
181
 
152
182
  #getVerifier (alg) {
@@ -150,7 +150,7 @@ export class HTTPSignatureAuthenticator {
150
150
  }
151
151
  const { method, headers } = req
152
152
  const { origin } = req.app.locals
153
- const { pathname: path, search: query } = URL.parse(`${origin}${originalUrl}`)
153
+ const url = `${origin}${originalUrl}`
154
154
 
155
155
  const keyId = this.#messageSigner.keyId(signatureInput)
156
156
  if (!keyId) {
@@ -163,7 +163,7 @@ export class HTTPSignatureAuthenticator {
163
163
  }
164
164
  let owner = ok.owner
165
165
  let publicKeyPem = ok.publicKeyPem
166
- let result = await this.#messageSigner.validate(publicKeyPem, signatureInput, signature, method, path, query, headers)
166
+ let result = await this.#messageSigner.validate(publicKeyPem, signatureInput, signature, method, url, headers)
167
167
  this.#logger.debug(`First validation result: ${result}`)
168
168
  if (!result) {
169
169
  // May be key rotation. Try again with uncached key
@@ -174,7 +174,7 @@ export class HTTPSignatureAuthenticator {
174
174
  this.#logger.debug('different keys')
175
175
  owner = ok2.owner
176
176
  publicKeyPem = ok2.publicKeyPem
177
- result = await this.#messageSigner.validate(publicKeyPem, signatureInput, signature, method, path, query, headers)
177
+ result = await this.#messageSigner.validate(publicKeyPem, signatureInput, signature, method, url, headers)
178
178
  this.#logger.debug(`Validation result: ${result}`)
179
179
  }
180
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -43,7 +43,7 @@
43
43
  "sequelize": "^6.37.7"
44
44
  },
45
45
  "devDependencies": {
46
- "@evanp/activitypub-nock": "^0.8.1",
46
+ "@evanp/activitypub-nock": "^0.9.2",
47
47
  "eslint": "^8.57.1",
48
48
  "eslint-config-standard": "^17.1.0",
49
49
  "eslint-plugin-import": "^2.29.1",