@rmdes/indiekit-endpoint-activitypub 3.8.4 → 3.8.5
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/index.js
CHANGED
|
@@ -744,7 +744,7 @@ export default class ActivityPubEndpoint {
|
|
|
744
744
|
"pkcs8",
|
|
745
745
|
Buffer.from(pemBody, "base64"),
|
|
746
746
|
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
747
|
-
|
|
747
|
+
true,
|
|
748
748
|
["sign"],
|
|
749
749
|
);
|
|
750
750
|
} catch (error) {
|
|
@@ -1589,6 +1589,7 @@ export default class ActivityPubEndpoint {
|
|
|
1589
1589
|
federation: this._federation,
|
|
1590
1590
|
followActor: (url, info) => pluginRef.followActor(url, info),
|
|
1591
1591
|
unfollowActor: (url) => pluginRef.unfollowActor(url),
|
|
1592
|
+
loadRsaKey: () => pluginRef._loadRsaPrivateKey(),
|
|
1592
1593
|
},
|
|
1593
1594
|
});
|
|
1594
1595
|
Indiekit.addEndpoint({
|
|
@@ -72,11 +72,16 @@ export function boostController(mountPath, plugin) {
|
|
|
72
72
|
identifier: handle,
|
|
73
73
|
});
|
|
74
74
|
const { application } = request.app.locals;
|
|
75
|
+
const rsaKey = await plugin._loadRsaPrivateKey();
|
|
75
76
|
const recipient = await resolveAuthor(
|
|
76
77
|
url,
|
|
77
78
|
ctx,
|
|
78
79
|
documentLoader,
|
|
79
80
|
application?.collections,
|
|
81
|
+
{
|
|
82
|
+
privateKey: rsaKey,
|
|
83
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
84
|
+
},
|
|
80
85
|
);
|
|
81
86
|
|
|
82
87
|
if (recipient) {
|
|
@@ -49,11 +49,16 @@ export function likeController(mountPath, plugin) {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const { application } = request.app.locals;
|
|
52
|
+
const rsaKey = await plugin._loadRsaPrivateKey();
|
|
52
53
|
const recipient = await resolveAuthor(
|
|
53
54
|
url,
|
|
54
55
|
ctx,
|
|
55
56
|
documentLoader,
|
|
56
57
|
application?.collections,
|
|
58
|
+
{
|
|
59
|
+
privateKey: rsaKey,
|
|
60
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
61
|
+
},
|
|
57
62
|
);
|
|
58
63
|
|
|
59
64
|
if (!recipient) {
|
|
@@ -170,11 +175,16 @@ export function unlikeController(mountPath, plugin) {
|
|
|
170
175
|
identifier: handle,
|
|
171
176
|
});
|
|
172
177
|
|
|
178
|
+
const rsaKey2 = await plugin._loadRsaPrivateKey();
|
|
173
179
|
const recipient = await resolveAuthor(
|
|
174
180
|
url,
|
|
175
181
|
ctx,
|
|
176
182
|
documentLoader,
|
|
177
183
|
application?.collections,
|
|
184
|
+
{
|
|
185
|
+
privateKey: rsaKey2,
|
|
186
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
187
|
+
},
|
|
178
188
|
);
|
|
179
189
|
|
|
180
190
|
if (!recipient) {
|
|
@@ -22,7 +22,7 @@ import { resolveAuthor } from "../../resolve-author.js";
|
|
|
22
22
|
* @param {object} params.interactions - ap_interactions collection
|
|
23
23
|
* @returns {Promise<{ activityId: string }>}
|
|
24
24
|
*/
|
|
25
|
-
export async function likePost({ targetUrl, federation, handle, publicationUrl, collections, interactions }) {
|
|
25
|
+
export async function likePost({ targetUrl, federation, handle, publicationUrl, collections, interactions, loadRsaKey }) {
|
|
26
26
|
const { Like } = await import("@fedify/fedify/vocab");
|
|
27
27
|
const ctx = federation.createContext(
|
|
28
28
|
new URL(publicationUrl),
|
|
@@ -30,7 +30,11 @@ export async function likePost({ targetUrl, federation, handle, publicationUrl,
|
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
|
33
|
-
const
|
|
33
|
+
const rsaKey = loadRsaKey ? await loadRsaKey() : null;
|
|
34
|
+
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections, {
|
|
35
|
+
privateKey: rsaKey,
|
|
36
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
37
|
+
});
|
|
34
38
|
|
|
35
39
|
const uuid = crypto.randomUUID();
|
|
36
40
|
const baseUrl = publicationUrl.replace(/\/$/, "");
|
|
@@ -79,7 +83,7 @@ export async function likePost({ targetUrl, federation, handle, publicationUrl,
|
|
|
79
83
|
* @param {object} params.interactions - ap_interactions collection
|
|
80
84
|
* @returns {Promise<void>}
|
|
81
85
|
*/
|
|
82
|
-
export async function unlikePost({ targetUrl, federation, handle, publicationUrl, collections, interactions }) {
|
|
86
|
+
export async function unlikePost({ targetUrl, federation, handle, publicationUrl, collections, interactions, loadRsaKey }) {
|
|
83
87
|
const existing = interactions
|
|
84
88
|
? await interactions.findOne({ objectUrl: targetUrl, type: "like" })
|
|
85
89
|
: null;
|
|
@@ -95,7 +99,11 @@ export async function unlikePost({ targetUrl, federation, handle, publicationUrl
|
|
|
95
99
|
);
|
|
96
100
|
|
|
97
101
|
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
|
98
|
-
const
|
|
102
|
+
const rsaKey = loadRsaKey ? await loadRsaKey() : null;
|
|
103
|
+
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections, {
|
|
104
|
+
privateKey: rsaKey,
|
|
105
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
106
|
+
});
|
|
99
107
|
|
|
100
108
|
if (recipient) {
|
|
101
109
|
const like = new Like({
|
|
@@ -131,7 +139,7 @@ export async function unlikePost({ targetUrl, federation, handle, publicationUrl
|
|
|
131
139
|
* @param {object} params.interactions - ap_interactions collection
|
|
132
140
|
* @returns {Promise<{ activityId: string }>}
|
|
133
141
|
*/
|
|
134
|
-
export async function boostPost({ targetUrl, federation, handle, publicationUrl, collections, interactions }) {
|
|
142
|
+
export async function boostPost({ targetUrl, federation, handle, publicationUrl, collections, interactions, loadRsaKey }) {
|
|
135
143
|
const { Announce } = await import("@fedify/fedify/vocab");
|
|
136
144
|
const ctx = federation.createContext(
|
|
137
145
|
new URL(publicationUrl),
|
|
@@ -162,7 +170,11 @@ export async function boostPost({ targetUrl, federation, handle, publicationUrl,
|
|
|
162
170
|
|
|
163
171
|
// Also send directly to the original post author
|
|
164
172
|
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
|
165
|
-
const
|
|
173
|
+
const rsaKey = loadRsaKey ? await loadRsaKey() : null;
|
|
174
|
+
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections, {
|
|
175
|
+
privateKey: rsaKey,
|
|
176
|
+
keyId: `${ctx.getActorUri(handle).href}#main-key`,
|
|
177
|
+
});
|
|
166
178
|
if (recipient) {
|
|
167
179
|
try {
|
|
168
180
|
await ctx.sendActivity({ identifier: handle }, recipient, announce, {
|
package/lib/resolve-author.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Strategies (tried in order):
|
|
8
8
|
* 1. lookupObject on post URL → getAttributedTo
|
|
9
|
+
* 1b. Raw signed fetch fallback (for servers like wafrn that return
|
|
10
|
+
* non-standard JSON-LD that Fedify can't parse)
|
|
9
11
|
* 2. Timeline/notification DB lookup → lookupObject on stored author URL
|
|
10
12
|
* 3. Extract author URL from post URL pattern → lookupObject
|
|
11
13
|
*/
|
|
@@ -60,6 +62,9 @@ export function extractAuthorUrl(postUrl) {
|
|
|
60
62
|
* @param {object} ctx - Fedify context
|
|
61
63
|
* @param {object} documentLoader - Authenticated document loader
|
|
62
64
|
* @param {object} [collections] - Optional MongoDB collections map (application.collections)
|
|
65
|
+
* @param {object} [options] - Additional options
|
|
66
|
+
* @param {CryptoKey} [options.privateKey] - RSA private key for raw signed fetch fallback
|
|
67
|
+
* @param {string} [options.keyId] - Key ID for HTTP Signature (e.g. "...#main-key")
|
|
63
68
|
* @returns {Promise<object|null>} - Fedify Actor object or null
|
|
64
69
|
*/
|
|
65
70
|
export async function resolveAuthor(
|
|
@@ -67,6 +72,7 @@ export async function resolveAuthor(
|
|
|
67
72
|
ctx,
|
|
68
73
|
documentLoader,
|
|
69
74
|
collections,
|
|
75
|
+
options = {},
|
|
70
76
|
) {
|
|
71
77
|
// Strategy 1: Look up remote post via Fedify (signed request)
|
|
72
78
|
try {
|
|
@@ -90,6 +96,46 @@ export async function resolveAuthor(
|
|
|
90
96
|
);
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
// Strategy 1b: Raw signed fetch fallback
|
|
100
|
+
// Some servers (e.g. wafrn) return AP JSON without @context, which Fedify's
|
|
101
|
+
// JSON-LD processor rejects. A raw fetch can still extract attributedTo/actor.
|
|
102
|
+
if (options.privateKey && options.keyId) {
|
|
103
|
+
try {
|
|
104
|
+
const { signRequest } = await import("@fedify/fedify/sig");
|
|
105
|
+
const request = new Request(postUrl, {
|
|
106
|
+
method: "GET",
|
|
107
|
+
headers: { Accept: "application/activity+json" },
|
|
108
|
+
});
|
|
109
|
+
const signed = await signRequest(request, options.privateKey, new URL(options.keyId), {
|
|
110
|
+
spec: "draft-cavage-http-signatures-12",
|
|
111
|
+
});
|
|
112
|
+
const res = await fetch(signed, { redirect: "follow" });
|
|
113
|
+
if (res.ok) {
|
|
114
|
+
const contentType = res.headers.get("content-type") || "";
|
|
115
|
+
if (contentType.includes("json")) {
|
|
116
|
+
const json = await res.json();
|
|
117
|
+
const authorUrl = json.attributedTo || json.actor;
|
|
118
|
+
if (authorUrl && typeof authorUrl === "string") {
|
|
119
|
+
const actor = await lookupWithSecurity(ctx, new URL(authorUrl), {
|
|
120
|
+
documentLoader,
|
|
121
|
+
});
|
|
122
|
+
if (actor) {
|
|
123
|
+
console.info(
|
|
124
|
+
`[ActivityPub] Resolved author via raw fetch for ${postUrl} → ${authorUrl}`,
|
|
125
|
+
);
|
|
126
|
+
return actor;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn(
|
|
133
|
+
`[ActivityPub] Raw fetch fallback failed for ${postUrl}:`,
|
|
134
|
+
error.message,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
93
139
|
// Strategy 2: Use author URL from timeline or notifications
|
|
94
140
|
if (collections) {
|
|
95
141
|
const ap_timeline = collections.get("ap_timeline");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.5",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|