@evanp/activitypub-bot 0.32.3 → 0.33.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/activitydeliverer.js +4 -2
- package/lib/activitydistributor.js +3 -1
- package/lib/activityhandler.js +4 -2
- package/lib/activitypubclient.js +5 -3
- package/lib/botcontext.js +3 -1
- package/lib/httpsignature.js +2 -1
- package/lib/httpsignatureauthenticator.js +1 -0
- package/lib/keystorage.js +3 -2
- package/lib/migrations/index.js +3 -3
- package/lib/remotekeystorage.js +80 -15
- package/lib/routes/collection.js +4 -2
- package/lib/routes/inbox.js +4 -2
- package/lib/routes/object.js +2 -1
- package/lib/routes/sharedinbox.js +4 -2
- package/lib/routes/webfinger.js +22 -0
- package/package.json +1 -1
package/lib/activitydeliverer.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import BotMaker from './botmaker.js'
|
|
2
1
|
import assert from 'node:assert'
|
|
3
|
-
|
|
2
|
+
|
|
4
3
|
import * as ttlcachePkg from '@isaacs/ttlcache'
|
|
5
4
|
|
|
5
|
+
import as2 from './activitystreams.js'
|
|
6
|
+
import BotMaker from './botmaker.js'
|
|
7
|
+
|
|
6
8
|
const TTLCache =
|
|
7
9
|
ttlcachePkg.TTLCache ?? ttlcachePkg.default ?? ttlcachePkg
|
|
8
10
|
|
package/lib/activityhandler.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import as2 from './activitystreams.js'
|
|
2
|
-
import { nanoid } from 'nanoid'
|
|
3
1
|
import assert from 'node:assert'
|
|
4
2
|
|
|
3
|
+
import { nanoid } from 'nanoid'
|
|
4
|
+
|
|
5
|
+
import as2 from './activitystreams.js'
|
|
6
|
+
|
|
5
7
|
const AS2 = 'https://www.w3.org/ns/activitystreams#'
|
|
6
8
|
|
|
7
9
|
const THREAD_PROP = 'https://purl.archive.org/socialweb/thread#thread'
|
package/lib/activitypubclient.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import as2 from './activitystreams.js'
|
|
2
|
-
import fetch from 'node-fetch'
|
|
3
1
|
import assert from 'node:assert'
|
|
4
|
-
import createHttpError from 'http-errors'
|
|
5
2
|
import fs from 'node:fs'
|
|
6
3
|
import path from 'node:path'
|
|
7
4
|
import { fileURLToPath } from 'node:url'
|
|
8
5
|
|
|
6
|
+
import fetch from 'node-fetch'
|
|
7
|
+
import createHttpError from 'http-errors'
|
|
8
|
+
|
|
9
|
+
import as2 from './activitystreams.js'
|
|
10
|
+
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url)
|
|
10
12
|
const __dirname = path.dirname(__filename)
|
|
11
13
|
|
package/lib/botcontext.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { nanoid } from 'nanoid'
|
|
4
4
|
import fetch from 'node-fetch'
|
|
5
5
|
|
|
6
|
+
import as2 from './activitystreams.js'
|
|
7
|
+
|
|
6
8
|
const AS2_TYPES = [
|
|
7
9
|
'application/activity+json',
|
|
8
10
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
package/lib/httpsignature.js
CHANGED
package/lib/keystorage.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { promisify } from 'util'
|
|
1
|
+
import { promisify } from 'node:util'
|
|
2
2
|
import crypto from 'node:crypto'
|
|
3
|
-
import HumanHasher from 'humanhash'
|
|
4
3
|
import assert from 'node:assert'
|
|
5
4
|
|
|
5
|
+
import HumanHasher from 'humanhash'
|
|
6
|
+
|
|
6
7
|
const generateKeyPair = promisify(crypto.generateKeyPair)
|
|
7
8
|
|
|
8
9
|
export class KeyStorage {
|
package/lib/migrations/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fileURLToPath } from 'url'
|
|
2
|
-
import { dirname, resolve } from 'path'
|
|
3
|
-
import { readdirSync } from 'fs'
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import { readdirSync } from 'node:fs'
|
|
4
4
|
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
6
6
|
|
package/lib/remotekeystorage.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
const SEC_NS = 'https://w3id.org/security#'
|
|
4
|
+
const DEFAULT_NS = '_:'
|
|
2
5
|
|
|
3
6
|
export class RemoteKeyStorage {
|
|
4
7
|
#client = null
|
|
@@ -27,6 +30,10 @@ export class RemoteKeyStorage {
|
|
|
27
30
|
this.debug(`getPublicKey(${id}) - remote not found`)
|
|
28
31
|
return null
|
|
29
32
|
}
|
|
33
|
+
if (!await this.#confirmPublicKey(remote.owner, id)) {
|
|
34
|
+
this.#logger.warn({ owner: remote.owner, id }, 'Mismatched owner and key')
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
30
37
|
await this.#cachePublicKey(id, remote.owner, remote.publicKeyPem)
|
|
31
38
|
return remote
|
|
32
39
|
}
|
|
@@ -55,21 +62,11 @@ export class RemoteKeyStorage {
|
|
|
55
62
|
if (!response) {
|
|
56
63
|
return null
|
|
57
64
|
}
|
|
58
|
-
this.debug(`getRemotePublicKey(${id}) - response: ${
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
owner = response.get(SEC_NS + 'owner')?.first?.id
|
|
64
|
-
publicKeyPem = response.get(SEC_NS + 'publicKeyPem')?.first
|
|
65
|
-
} else if (response.get(SEC_NS + 'publicKey')) {
|
|
66
|
-
this.debug(`getRemotePublicKey(${id}) - publicKey`)
|
|
67
|
-
const publicKey = response.get(SEC_NS + 'publicKey').first
|
|
68
|
-
if (publicKey) {
|
|
69
|
-
owner = publicKey.get(SEC_NS + 'owner')?.first?.id
|
|
70
|
-
publicKeyPem = publicKey.get(SEC_NS + 'publicKeyPem')?.first
|
|
71
|
-
}
|
|
72
|
-
}
|
|
65
|
+
this.debug(`getRemotePublicKey(${id}) - response: ${response.id}`)
|
|
66
|
+
|
|
67
|
+
const owner = this.#getOwner(response)
|
|
68
|
+
const publicKeyPem = this.#getPublicKeyPem(response)
|
|
69
|
+
|
|
73
70
|
if (!owner || !publicKeyPem) {
|
|
74
71
|
return null
|
|
75
72
|
}
|
|
@@ -89,4 +86,72 @@ export class RemoteKeyStorage {
|
|
|
89
86
|
this.#logger.debug(...args)
|
|
90
87
|
}
|
|
91
88
|
}
|
|
89
|
+
|
|
90
|
+
async #confirmPublicKey (owner, id) {
|
|
91
|
+
assert.equal(typeof owner, 'string')
|
|
92
|
+
assert.equal(typeof id, 'string')
|
|
93
|
+
let actor
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
actor = await this.#client.get(owner)
|
|
97
|
+
} catch (err) {
|
|
98
|
+
this.#logger.warn({ err, owner, id }, 'Error getting key owner')
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const publicKeyId = this.#getPublicKeyId(actor)
|
|
103
|
+
|
|
104
|
+
if (!publicKeyId) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return publicKeyId === id
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#getSecIdProp (obj, prop) {
|
|
112
|
+
assert.strictEqual(typeof obj, 'object')
|
|
113
|
+
assert.strictEqual(typeof prop, 'string')
|
|
114
|
+
let value = obj.get(SEC_NS + prop)
|
|
115
|
+
if (value) {
|
|
116
|
+
return value.first?.id
|
|
117
|
+
}
|
|
118
|
+
value = obj.get(DEFAULT_NS + prop)
|
|
119
|
+
if (value) {
|
|
120
|
+
this.#logger.warn(
|
|
121
|
+
{ objectId: obj.id, prop },
|
|
122
|
+
'security property in default namespace'
|
|
123
|
+
)
|
|
124
|
+
const first = value.first
|
|
125
|
+
return (typeof first === 'string') ? first : first?.id
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#getOwner (obj) {
|
|
131
|
+
assert.strictEqual(typeof obj, 'object')
|
|
132
|
+
return this.#getSecIdProp(obj, 'owner')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#getPublicKeyPem (obj) {
|
|
136
|
+
assert.strictEqual(typeof obj, 'object')
|
|
137
|
+
const prop = 'publicKeyPem'
|
|
138
|
+
let value = obj.get(SEC_NS + prop)
|
|
139
|
+
if (value) {
|
|
140
|
+
return value.first
|
|
141
|
+
}
|
|
142
|
+
value = obj.get(DEFAULT_NS + prop)
|
|
143
|
+
if (value) {
|
|
144
|
+
this.#logger.warn(
|
|
145
|
+
{ objectId: obj.id, prop },
|
|
146
|
+
'security property in default namespace'
|
|
147
|
+
)
|
|
148
|
+
return value.first
|
|
149
|
+
}
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#getPublicKeyId (obj) {
|
|
154
|
+
assert.strictEqual(typeof obj, 'object')
|
|
155
|
+
return this.#getSecIdProp(obj, 'publicKey')
|
|
156
|
+
}
|
|
92
157
|
}
|
package/lib/routes/collection.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import express from 'express'
|
|
2
|
-
import as2 from '../activitystreams.js'
|
|
3
4
|
import createHttpError from 'http-errors'
|
|
5
|
+
|
|
6
|
+
import as2 from '../activitystreams.js'
|
|
4
7
|
import BotMaker from '../botmaker.js'
|
|
5
|
-
import assert from 'node:assert'
|
|
6
8
|
|
|
7
9
|
const collections = ['outbox', 'liked', 'followers', 'following']
|
|
8
10
|
const router = express.Router()
|
package/lib/routes/inbox.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
|
|
1
3
|
import express from 'express'
|
|
2
|
-
import as2 from '../activitystreams.js'
|
|
3
4
|
import createHttpError from 'http-errors'
|
|
5
|
+
|
|
6
|
+
import as2 from '../activitystreams.js'
|
|
4
7
|
import BotMaker from '../botmaker.js'
|
|
5
|
-
import http from 'node:http'
|
|
6
8
|
|
|
7
9
|
const router = express.Router()
|
|
8
10
|
|
package/lib/routes/object.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
|
|
1
3
|
import express from 'express'
|
|
2
|
-
import as2 from '../activitystreams.js'
|
|
3
4
|
import createHttpError from 'http-errors'
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
import as2 from '../activitystreams.js'
|
|
5
7
|
|
|
6
8
|
const router = express.Router()
|
|
7
9
|
|
package/lib/routes/webfinger.js
CHANGED
|
@@ -33,6 +33,26 @@ async function botWebfinger (username, req, res, next) {
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
async function profileWebfinger (username, profileUrl, req, res, next) {
|
|
37
|
+
const { formatter, bots } = req.app.locals
|
|
38
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
39
|
+
if (!bot) {
|
|
40
|
+
return next(createHttpError(404, `No such bot '${username}'`))
|
|
41
|
+
}
|
|
42
|
+
res.status(200)
|
|
43
|
+
res.type('application/jrd+json')
|
|
44
|
+
res.json({
|
|
45
|
+
subject: profileUrl,
|
|
46
|
+
links: [
|
|
47
|
+
{
|
|
48
|
+
rel: 'alternate',
|
|
49
|
+
type: 'application/activity+json',
|
|
50
|
+
href: formatter.format({ username })
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
async function httpsWebfinger (resource, req, res, next) {
|
|
37
57
|
const { formatter } = req.app.locals
|
|
38
58
|
assert.ok(formatter)
|
|
@@ -42,6 +62,8 @@ async function httpsWebfinger (resource, req, res, next) {
|
|
|
42
62
|
const parts = formatter.unformat(resource)
|
|
43
63
|
if (parts.username && !parts.type && !parts.collection) {
|
|
44
64
|
return await botWebfinger(parts.username, req, res, next)
|
|
65
|
+
} else if (parts.username && parts.type === 'profile') {
|
|
66
|
+
return await profileWebfinger(parts.username, resource, req, res, next)
|
|
45
67
|
} else {
|
|
46
68
|
return next(createHttpError(400, `No webfinger lookup for url ${resource}`))
|
|
47
69
|
}
|