From 76e9ba0b3520000c59c21ddb3a11e98d42dc7cb4 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sat, 21 Mar 2026 19:16:05 +0100 Subject: [PATCH] fix: centralize unsigned fallback in lookupWithSecurity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some servers (e.g., tags.pub) return 400 for signed GET requests. Previously only followActor had an unsigned fallback — all other callers (resolve, unfollowActor, profile viewer, messages, post detail, OG unfurl) would silently fail. Fix: moved the fallback logic into lookupWithSecurity itself. When an authenticated documentLoader is provided and the lookup fails, it automatically retries without the loader (unsigned GET). This fixes ALL AP resolution paths in one place — resolve, follow, unfollow, profile viewing, message sending, quote fetching. Removed individual fallbacks in followActor and resolve controller since the central helper now handles it. --- index.js | 10 ++-------- lib/controllers/resolve.js | 3 ++- lib/lookup-helpers.js | 32 +++++++++++++++++++++++++++----- package.json | 2 +- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 9f2d331..de05988 100644 --- a/index.js +++ b/index.js @@ -721,19 +721,13 @@ export default class ActivityPubEndpoint { ); // Resolve the remote actor to get their inbox - // Try authenticated document loader first (for Authorized Fetch servers), - // fall back to unsigned if that fails (some servers reject signed GETs) + // lookupWithSecurity handles signed→unsigned fallback automatically const documentLoader = await ctx.getDocumentLoader({ identifier: handle, }); - let remoteActor = await lookupWithSecurity(ctx, actorUrl, { + const remoteActor = await lookupWithSecurity(ctx, actorUrl, { documentLoader, }); - if (!remoteActor) { - // Retry without authentication — some servers (e.g., tags.pub) - // may reject or mishandle signed GET requests - remoteActor = await lookupWithSecurity(ctx, actorUrl); - } if (!remoteActor) { return { ok: false, error: "Could not resolve remote actor" }; } diff --git a/lib/controllers/resolve.js b/lib/controllers/resolve.js index 466acde..4cf3f25 100644 --- a/lib/controllers/resolve.js +++ b/lib/controllers/resolve.js @@ -60,7 +60,8 @@ export function resolveController(mountPath, plugin) { let object; try { - object = await lookupWithSecurity(ctx,lookupInput, { documentLoader }); + // lookupWithSecurity handles signed→unsigned fallback automatically + object = await lookupWithSecurity(ctx, lookupInput, { documentLoader }); } catch (error) { console.warn( `[resolve] lookupObject failed for "${query}":`, diff --git a/lib/lookup-helpers.js b/lib/lookup-helpers.js index 149c932..c542fc5 100644 --- a/lib/lookup-helpers.js +++ b/lib/lookup-helpers.js @@ -14,14 +14,36 @@ * Using `crossOrigin: "ignore"` tells Fedify to silently discard objects * whose id doesn't match the fetch origin, rather than throwing. * + * When an authenticated document loader is provided (for Authorized Fetch + * compatibility), the lookup is tried with it first. If it fails (some + * servers like tags.pub return 400 for signed GETs), a fallback to the + * default unsigned loader is attempted automatically. + * * @param {object} ctx - Fedify Context * @param {string|URL} input - URL or handle to look up * @param {object} [options] - Additional options passed to lookupObject * @returns {Promise} Resolved object or null */ -export function lookupWithSecurity(ctx, input, options = {}) { - return ctx.lookupObject(input, { - crossOrigin: "ignore", - ...options, - }); +export async function lookupWithSecurity(ctx, input, options = {}) { + const baseOptions = { crossOrigin: "ignore", ...options }; + + let result = null; + try { + result = await ctx.lookupObject(input, baseOptions); + } catch { + // signed lookup threw — fall through to unsigned + } + + // If signed lookup failed and we used a custom documentLoader, + // retry without it (unsigned GET) + if (!result && options.documentLoader) { + try { + const { documentLoader: _, ...unsignedOptions } = baseOptions; + result = await ctx.lookupObject(input, unsignedOptions); + } catch { + // unsigned also failed — return null + } + } + + return result; } diff --git a/package.json b/package.json index 718f1a8..98784ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "3.7.3", + "version": "3.7.4", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit",