@rmdes/indiekit-endpoint-activitypub 3.13.10 → 3.13.12
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 +29 -806
- package/lib/defaults.js +37 -0
- package/lib/endpoint-federation.js +407 -0
- package/lib/mastodon/routes/oauth.js +9 -0
- package/lib/navigation.js +22 -0
- package/lib/routes/admin-routes.js +204 -0
- package/lib/routes/public-routes.js +175 -0
- package/package.json +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public (federation-facing) route getters, extracted from index.js's
|
|
3
|
+
* `get routesPublic()` + `get contentNegotiationRoutes()` (Phase 2 god-entry split).
|
|
4
|
+
* `self` is the ActivityPubEndpoint instance.
|
|
5
|
+
*/
|
|
6
|
+
import express from "express";
|
|
7
|
+
|
|
8
|
+
import { authorizeInteractionController } from "../controllers/authorize-interaction.js";
|
|
9
|
+
import { publicProfileController } from "../controllers/public-profile.js";
|
|
10
|
+
import { jf2ToActivityStreams } from "../jf2-to-as2.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Public routes — Fedify bridge for actor/inbox/collections, plus HTML
|
|
14
|
+
* fallbacks. Mounted at mountPath, in front of the authenticated admin routes.
|
|
15
|
+
* @param {object} self - the ActivityPubEndpoint instance
|
|
16
|
+
* @returns {import("express").Router}
|
|
17
|
+
*/
|
|
18
|
+
export function buildRoutesPublic(self) {
|
|
19
|
+
const router = express.Router(); // eslint-disable-line new-cap
|
|
20
|
+
|
|
21
|
+
router.use((req, res, next) => {
|
|
22
|
+
if (!self._fedifyMiddleware) return next();
|
|
23
|
+
// Skip Fedify for admin UI routes — they're handled by the
|
|
24
|
+
// authenticated `routes` getter, not the federation layer.
|
|
25
|
+
if (req.path.startsWith("/admin")) return next();
|
|
26
|
+
|
|
27
|
+
// Fedify's acceptsJsonLd() treats Accept: */* as NOT accepting JSON-LD
|
|
28
|
+
// (it only returns true for explicit application/activity+json etc.).
|
|
29
|
+
// Remote servers fetching actor URLs for HTTP Signature verification
|
|
30
|
+
// (e.g. tags.pub) often omit Accept or use */* — they get HTML back
|
|
31
|
+
// instead of the actor JSON, causing "public key not found" errors.
|
|
32
|
+
// Fix: for GET requests to actor paths, upgrade ambiguous Accept headers
|
|
33
|
+
// to application/activity+json so Fedify serves JSON-LD. Explicit
|
|
34
|
+
// text/html requests (browsers) are unaffected.
|
|
35
|
+
if (req.method === "GET" && /^\/users\/[^/]+\/?$/.test(req.path)) {
|
|
36
|
+
const accept = req.get("accept") || "";
|
|
37
|
+
if (!accept.includes("text/html") && !accept.includes("application/xhtml+xml")) {
|
|
38
|
+
req.headers["accept"] = "application/activity+json";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return self._fedifyMiddleware(req, res, next);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Authorize interaction — remote follow / subscribe endpoint.
|
|
46
|
+
// Remote servers redirect users here via the WebFinger subscribe template.
|
|
47
|
+
router.get("/authorize_interaction", authorizeInteractionController(self));
|
|
48
|
+
|
|
49
|
+
// HTML fallback for actor URL — serve a public profile page.
|
|
50
|
+
// Fedify only serves JSON-LD; browsers get 406 and fall through here.
|
|
51
|
+
router.get("/users/:identifier", publicProfileController(self));
|
|
52
|
+
|
|
53
|
+
// Catch-all for federation paths that Fedify didn't handle (e.g. GET
|
|
54
|
+
// on inbox). Without this, they fall through to Indiekit's auth
|
|
55
|
+
// middleware and redirect to the login page.
|
|
56
|
+
router.all("/users/:identifier/inbox", (req, res) => {
|
|
57
|
+
res
|
|
58
|
+
.status(405)
|
|
59
|
+
.set("Allow", "POST")
|
|
60
|
+
.type("application/activity+json")
|
|
61
|
+
.json({
|
|
62
|
+
error: "Method Not Allowed",
|
|
63
|
+
message: "The inbox only accepts POST requests",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
router.all("/inbox", (req, res) => {
|
|
67
|
+
res
|
|
68
|
+
.status(405)
|
|
69
|
+
.set("Allow", "POST")
|
|
70
|
+
.type("application/activity+json")
|
|
71
|
+
.json({
|
|
72
|
+
error: "Method Not Allowed",
|
|
73
|
+
message: "The shared inbox only accepts POST requests",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Content negotiation — serves AS2 JSON for ActivityPub clients requesting
|
|
82
|
+
* individual post URLs; delegates /nodeinfo/2.1 to Fedify.
|
|
83
|
+
* @param {object} self - the ActivityPubEndpoint instance
|
|
84
|
+
* @returns {import("express").Router}
|
|
85
|
+
*/
|
|
86
|
+
export function buildContentNegotiationRoutes(self) {
|
|
87
|
+
const router = express.Router(); // eslint-disable-line new-cap
|
|
88
|
+
|
|
89
|
+
// Let Fedify handle NodeInfo data (/nodeinfo/2.1)
|
|
90
|
+
// Only pass GET/HEAD requests — POST/PUT/DELETE must not go through
|
|
91
|
+
// Fedify here, because fromExpressRequest() consumes the body stream,
|
|
92
|
+
// breaking Express body-parsed routes downstream (e.g. admin forms).
|
|
93
|
+
router.use((req, res, next) => {
|
|
94
|
+
if (!self._fedifyMiddleware) return next();
|
|
95
|
+
if (req.method !== "GET" && req.method !== "HEAD") return next();
|
|
96
|
+
// Only delegate to Fedify for NodeInfo data endpoint (/nodeinfo/2.1).
|
|
97
|
+
// All other paths in this root-mounted router are handled by the
|
|
98
|
+
// content negotiation catch-all below. Passing arbitrary paths like
|
|
99
|
+
// /notes/... to Fedify causes harmless but noisy 404 warnings.
|
|
100
|
+
if (!req.path.startsWith("/nodeinfo/")) return next();
|
|
101
|
+
return self._fedifyMiddleware(req, res, next);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Content negotiation for AP clients on regular URLs
|
|
105
|
+
router.get("{*path}", async (req, res, next) => {
|
|
106
|
+
const accept = req.headers.accept || "";
|
|
107
|
+
const isActivityPub =
|
|
108
|
+
accept.includes("application/activity+json") ||
|
|
109
|
+
accept.includes("application/ld+json");
|
|
110
|
+
|
|
111
|
+
if (!isActivityPub) {
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Root URL — redirect to Fedify actor
|
|
117
|
+
if (req.path === "/") {
|
|
118
|
+
const actorPath = `${self.options.mountPath}/users/${self.options.actor.handle}`;
|
|
119
|
+
return res.redirect(actorPath);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Post URLs — look up in database and convert to AS2
|
|
123
|
+
const { application } = req.app.locals;
|
|
124
|
+
const postsCollection = application?.collections?.get("posts");
|
|
125
|
+
if (!postsCollection) {
|
|
126
|
+
return next();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const requestUrl = `${self._publicationUrl}${req.path.slice(1)}`;
|
|
130
|
+
const post = await postsCollection.findOne({
|
|
131
|
+
"properties.url": requestUrl,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!post || post.properties?.deleted) {
|
|
135
|
+
// FEP-4f05: Serve Tombstone for deleted posts
|
|
136
|
+
const { getTombstone } = await import("../storage/tombstones.js");
|
|
137
|
+
const tombstone = await getTombstone(self._collections, requestUrl);
|
|
138
|
+
if (tombstone) {
|
|
139
|
+
res.status(410).set("Content-Type", "application/activity+json").json({
|
|
140
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
141
|
+
type: "Tombstone",
|
|
142
|
+
id: requestUrl,
|
|
143
|
+
formerType: tombstone.formerType,
|
|
144
|
+
published: tombstone.published || undefined,
|
|
145
|
+
deleted: tombstone.deleted,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
return next();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const actorUrl = self._getActorUrl();
|
|
153
|
+
const activity = jf2ToActivityStreams(
|
|
154
|
+
post.properties,
|
|
155
|
+
actorUrl,
|
|
156
|
+
self._publicationUrl,
|
|
157
|
+
{ visibility: self.options.defaultVisibility },
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const object = activity.object || activity;
|
|
161
|
+
res.set("Content-Type", "application/activity+json");
|
|
162
|
+
return res.json({
|
|
163
|
+
"@context": [
|
|
164
|
+
"https://www.w3.org/ns/activitystreams",
|
|
165
|
+
"https://w3id.org/security/v1",
|
|
166
|
+
],
|
|
167
|
+
...object,
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
return next();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return router;
|
|
175
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.12",
|
|
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",
|