From 60076225dbdfd4a8f8c82563c4ee2abb48a7d498 Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 15 Apr 2026 20:15:02 +0200 Subject: [PATCH] fix: use URL-hash ID for Mastodon account route guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mastodon API GET /accounts/:id/followers and /following always returned [] because the guards compared profile._id.toString() against the client-provided id, which comes from verify_credentials via remoteActorId(profile.url) — a sha256 hash. ObjectId and hash never match. Patch ap-accounts-id-hash replaces profile._id.toString() with remoteActorId(profile.url) in all three affected account routes. Co-Authored-By: Claude Sonnet 4.6 --- scripts/patch-ap-mastodon-misc.mjs | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/scripts/patch-ap-mastodon-misc.mjs b/scripts/patch-ap-mastodon-misc.mjs index 745857fd..4f8de1ab 100644 --- a/scripts/patch-ap-mastodon-misc.mjs +++ b/scripts/patch-ap-mastodon-misc.mjs @@ -103,4 +103,53 @@ for (const f of OG_CANDIDATES) { } if (!ogDone) console.log(`[postinstall] ${SCRIPT}: ap-og-image — no target file found or no changes`); +// ── patch-ap-accounts-id-hash: use URL-hash ID instead of MongoDB _id ───────── +// GET /api/v1/accounts/:id, /followers, /following all checked profile._id.toString() +// against the client-provided id, but verify_credentials returns remoteActorId(profile.url) +// (sha256 hash). The ObjectId and the hash never match → followers/following always []. + +const HASH_MARKER = "// [patch] ap-accounts-id-hash"; +const ACCOUNTS_CANDIDATES = apPath("lib/mastodon/routes/accounts.js"); + +const HASH_PATCHES = [ + { + old: ` if (profile && profile._id.toString() === id) {`, + new: ` if (profile && remoteActorId(profile.url) === id) { ${HASH_MARKER}`, + }, + { + old: ` // Only serve followers for the local account\n if (!profile || profile._id.toString() !== id) {`, + new: ` // Only serve followers for the local account\n if (!profile || remoteActorId(profile.url) !== id) { ${HASH_MARKER}`, + }, + { + old: ` // Only serve following for the local account\n if (!profile || profile._id.toString() !== id) {`, + new: ` // Only serve following for the local account\n if (!profile || remoteActorId(profile.url) !== id) { ${HASH_MARKER}`, + }, +]; + +let hashDone = false; +for (const f of ACCOUNTS_CANDIDATES) { + if (!(await fileExists(f))) continue; + const src = await readFile(f, "utf8"); + if (src.includes(HASH_MARKER)) { + console.log(`[postinstall] ${SCRIPT}: ap-accounts-id-hash already applied in ${f}`); + hashDone = true; break; + } + let updated = src; + let changed = false; + for (const { old, new: replacement } of HASH_PATCHES) { + if (updated.includes(old)) { + updated = updated.replace(old, replacement); + changed = true; + } else { + console.warn(`[postinstall] ${SCRIPT}: ap-accounts-id-hash — snippet not found in ${f}: ${old.slice(0, 60)}...`); + } + } + if (changed && updated !== src) { + await writeFile(f, updated, "utf8"); + console.log(`[postinstall] ${SCRIPT}: applied ap-accounts-id-hash to ${f}`); + total++; hashDone = true; break; + } +} +if (!hashDone) console.log(`[postinstall] ${SCRIPT}: ap-accounts-id-hash — no target file found or no changes`); + console.log(`[postinstall] ${SCRIPT}: done (${total} patch(es) applied)`);