A remote handle on its own domain, once that domain's WebFinger agrees (FEP-2c59)

An account on a server whose handles are not its host's (Mastodon's LOCAL_DOMAIN apart from WEB_DOMAIN) showed as
user@host. The actor's `webfinger` names the handle; its domain is kept once WebFinger there points back to the actor,
and asked again when the name changes. A persona's blocked servers match a handle's domain as well as the actor's host,
as the lists Mastodon exports name handles' domains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-06 11:49:32 +02:00
1 parent e708f1ad2e
commit 02928caac1
8 files changed
+182 -15

No files matched your search

@@ -190,8 +190,25 @@ namespace PrivaPub.Federation.Actors
return true;
}
static async Task<ForeignAvatar> Upsert(ActorDocument actor, ActorKey key, CancellationToken token)
// the domain of its handle: its own host, or the one its `webfinger` names (FEP-2c59; a server whose handles are not
// its host's, as Mastodon's LOCAL_DOMAIN) once that domain's WebFinger says the handle is this actor. Asked again
// only when the handle changes, a rename among them.
async Task<string> HandleDomain(ActorDocument actor, CancellationToken token)
{
var host = new Uri(actor.Id).Authority;
var named = actor.WebFinger?.Split('@');
if (named is not { Length: 2 } || named[0].Length == 0 || named[1].Length == 0 || named[1].Equals(host, StringComparison.OrdinalIgnoreCase))
return host;
var domain = named[1].ToLowerInvariant();
var stored = await _dbEntities.ForeignAvatars.Match(a => a.ActorURI == actor.Id).ExecuteFirstAsync(token);
if (stored?.Domain == domain && stored.UserName == actor.PreferredUsername)
return domain;
return await ResolveHandle($"{named[0]}@{domain}", token) == actor.Id ? domain : host;
}
async Task<ForeignAvatar> Upsert(ActorDocument actor, ActorKey key, CancellationToken token)
{
var domain = await HandleDomain(actor, token);
var now = DateTime.UtcNow;
return await DB.Default.UpdateAndGet<ForeignAvatar>()
.Match(a => a.ActorURI == actor.Id)
@@ -199,7 +216,7 @@ namespace PrivaPub.Federation.Actors
.Modify(a => a.Name, actor.Name)
.Modify(a => a.Biography, ContentSanitizer.Html(actor.Summary))
.Modify(a => a.Url, actor.Url)
.Modify(a => a.Domain, new Uri(actor.Id).Authority)
.Modify(a => a.Domain, domain)
.Modify(a => a.InboxURL, actor.Inbox)
.Modify(a => a.OutboxURL, actor.Outbox)
.Modify(a => a.FollowersURL, actor.Followers)