Files
indiekit-server/scripts/patch-ap-object-url-trailing-slash.mjs
Sven 1ad3bae3fc fix(patches): restore lost AP patches and fix broken patch chain
The signatureTimeWindow patch was deleted in e52e98c5c (assumed fixed
in fork), but the lockfile still pins the fork to v2.10.1 which lacks
it. This broke the patch-ap-allow-private-address patch chain: it
expected signatureTimeWindow in its OLD_SNIPPET, never matched, and
silently skipped — leaving the server without both signatureTimeWindow
AND allowPrivateAddress. Without allowPrivateAddress, Fedify's SSRF
guard blocks own-site URL resolution (blog.giersig.eu → 10.100.0.10),
breaking federation delivery.

- Fix patch-ap-allow-private-address to handle fresh v2.10.1 (adds
  both signatureTimeWindow and allowPrivateAddress in one step)
- Restore patch-ap-object-url-trailing-slash (also lost in e52e98c5c)
- Add both patches to postinstall and serve scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 19:18:40 +01:00

82 lines
2.6 KiB
JavaScript

/**
* Patch: make the Fedify object dispatcher's post lookup tolerate trailing-slash
* differences between the AP object URL and the stored post URL.
*
* Root cause:
* setupObjectDispatchers resolvePost() builds postUrl from the {+id} template
* variable (e.g. "replies/bd78a") and does an exact findOne() match against
* posts.properties.url. Posts in MongoDB are stored with a trailing slash
* ("https://blog.giersig.eu/replies/bd78a/"), but the AP object URL returned
* by the /api/ap-url lookup endpoint has no trailing slash. The exact match
* fails → Fedify returns 404 → remote instance shows "Could not connect".
*
* Fix:
* Replace the single-value findOne() with a $in query that tries both the
* bare URL and the URL with a trailing slash appended.
*/
import { access, readFile, writeFile } from "node:fs/promises";
const candidates = [
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
];
const MARKER = "// trailing-slash url fix";
const OLD_SNIPPET = ` const postUrl = \`\${publicationUrl.replace(/\\/$/, "")}/\${id}\`;
const post = await collections.posts.findOne({ "properties.url": postUrl });`;
const NEW_SNIPPET = ` const postUrl = \`\${publicationUrl.replace(/\\/$/, "")}/\${id}\`; // trailing-slash url fix
const post = await collections.posts.findOne({
"properties.url": { $in: [postUrl, postUrl + "/"] },
});`;
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
let checked = 0;
let patched = 0;
for (const filePath of candidates) {
if (!(await exists(filePath))) {
continue;
}
checked += 1;
const source = await readFile(filePath, "utf8");
if (source.includes(MARKER)) {
continue;
}
if (!source.includes(OLD_SNIPPET)) {
console.log(`[postinstall] patch-ap-object-url-trailing-slash: snippet not found in ${filePath}`);
continue;
}
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
if (updated === source) {
continue;
}
await writeFile(filePath, updated, "utf8");
patched += 1;
console.log(`[postinstall] Applied patch-ap-object-url-trailing-slash to ${filePath}`);
}
if (checked === 0) {
console.log("[postinstall] patch-ap-object-url-trailing-slash: no target files found");
} else if (patched === 0) {
console.log("[postinstall] patch-ap-object-url-trailing-slash: already up to date");
} else {
console.log(`[postinstall] patch-ap-object-url-trailing-slash: patched ${patched}/${checked} file(s)`);
}