From 6e541e20ca07f7b90be802435edb001cbe303b2f Mon Sep 17 00:00:00 2001 From: Augusto Xavier Date: Mon, 15 Jun 2026 11:27:28 -0300 Subject: [PATCH] Fix authorize_interaction not resolving collections from unknown accounts Opening an interaction-authorization link for a collection hosted on a server the instance had not encountered before failed with a "not found" error, because resolving the remote collection only looked its owning account up locally and gave up when that account was unknown. The server now fetches the owning account when it is not already known before resolving the collection, so these links work on first visit. This completes the partial fix from #39287. Fixes #39281. Assisted-by: Claude Code --- ...etch_remote_featured_collection_service.rb | 14 ++++-- ...remote_featured_collection_service_spec.rb | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/services/activitypub/fetch_remote_featured_collection_service.rb b/app/services/activitypub/fetch_remote_featured_collection_service.rb index 968dc6811ec..3afb4093b8f 100644 --- a/app/services/activitypub/fetch_remote_featured_collection_service.rb +++ b/app/services/activitypub/fetch_remote_featured_collection_service.rb @@ -13,9 +13,9 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService return unless supported_context?(json) return unless json['type'] == 'FeaturedCollection' - # Fetching an unknown account should eventually also fetch its - # collections, so it should be OK to only handle known accounts here - account = Account.find_by(uri: json['attributedTo']) + # A collection can be resolved on its own (e.g. through authorize_interaction) + # before its account is known, so the account is fetched if necessary + account = account_from_uri(value_or_id(first_of_value(json['attributedTo'])), request_id) return unless account existing_collection = account.collections.find_by(uri:) @@ -23,4 +23,12 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService ActivityPub::ProcessFeaturedCollectionService.new.call(account, json, request_id:) end + + private + + def account_from_uri(uri, request_id) + account = ActivityPub::TagManager.instance.uri_to_resource(uri, Account) + account ||= ActivityPub::FetchRemoteAccountService.new.call(uri, request_id:) + account + end end diff --git a/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb index f5cb9194b2e..f623f5648e2 100644 --- a/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb @@ -61,4 +61,47 @@ RSpec.describe ActivityPub::FetchRemoteFeaturedCollectionService do expect(subject.call(uri)).to be_nil end end + + context 'when the attributed account is not known yet' do + let(:actor_uri) { 'https://example.com/alice' } + let(:response) do + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => uri, + 'type' => 'FeaturedCollection', + 'name' => 'Incredible people', + 'attributedTo' => actor_uri, + 'sensitive' => false, + 'discoverable' => true, + 'totalItems' => 0, + } + end + let(:actor) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: actor_uri, + type: 'Person', + preferredUsername: 'alice', + inbox: 'https://example.com/alice/inbox', + } + end + let(:webfinger) do + { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: actor_uri, type: 'application/activity+json' }] } + end + + before do + stub_request(:get, actor_uri) + .to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com') + .to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches the account and creates the collection' do + collection = nil + expect { collection = subject.call(uri) }.to change(Collection, :count).by(1) + + expect(collection.uri).to eq uri + expect(collection.account.uri).to eq actor_uri + end + end end