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
This commit is contained in:
Augusto Xavier 2026-06-15 11:27:28 -03:00
parent a6a8e8bfc0
commit 6e541e20ca
2 changed files with 54 additions and 3 deletions

View File

@ -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

View File

@ -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