Communities follow FEP-1b12, circles federate to their members only
Communities: - a post addressed to a community (to, cc or audience) is accepted according to its posting policy - followers, anyone, or moderators - and GroupDistributor announces the whole activity with `audience` to the community's followers, plus the object for new posts so Mastodon shows them; updates and deletes of community content are announced too; - top-level posts are Pages with a name (the title, or a headline from the text); /flock counts members, /wardens lists moderators; - a Mastodon client posts into a community by mentioning it, or into a remote group, which sets `audience`; - an Announce of an activity from a remote group a persona follows (Lemmy) is followed through: the object is fetched from its own origin, kept with its AudienceURI, and fanned out to the group's local followers; updates are applied in place and deletes checked against the origin. Circles stop being local-only: an undiscoverable Group actor whose follows are all requests the owner approves; posts addressed to the circle and its /flock and delivered to members' own inboxes, never announced, never public; a remote member's post into the circle is accepted from members only. SignedFetchAuthorizer serves circle posts and collections only to a signed request from a member or a member server's instance actor - 404 for anyone else. Circles never surface in search, lookups, mentions, account ids or profile pages. Federation:SecureMode requires a valid signature on every GET under /peasants except the instance actor. Group forms take a posting policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
0ccbcd558f
commit
a5d9a89445
32 files changed
+652
-58
No files matched your search
@@ -41,6 +41,7 @@ namespace PrivaPub.Tests.Support
|
||||
Remote = new RemoteActorService(Peer.Http(cache), Local, cache, Db);
|
||||
Delivery = new DeliveryService(Db, Queue);
|
||||
Fanout = new Fanout(Db);
|
||||
Groups = new GroupDistributor(Delivery);
|
||||
RemotePosts = new RemotePosts(Db, Local, Remote, new NoBlocks(), Queue);
|
||||
Receiver = new InboxReceiver(Local, Remote, Queue, new NoBlocks(), NullLogger<InboxReceiver>.Instance);
|
||||
Processor = new InboxProcessor(Remote, new IActivityHandler[]
|
||||
@@ -50,10 +51,10 @@ namespace PrivaPub.Tests.Support
|
||||
new RejectHandler(Db, Local),
|
||||
new UndoHandler(Db, Local),
|
||||
new LikeHandler(Db),
|
||||
new AnnounceHandler(Db, Local, RemotePosts, Fanout),
|
||||
new CreateHandler(Db, Local, Remote, Delivery, new NoBlocks(), Fanout, RemotePosts),
|
||||
new DeleteHandler(Db, Local, Remote, Delivery),
|
||||
new UpdateHandler(Db, Local, Remote),
|
||||
new AnnounceHandler(Db, Local, RemotePosts, Fanout, Remote),
|
||||
new CreateHandler(Db, Local, Remote, Delivery, new NoBlocks(), Fanout, RemotePosts, Groups),
|
||||
new DeleteHandler(Db, Local, Remote, Delivery, Groups),
|
||||
new UpdateHandler(Db, Local, Remote, Groups),
|
||||
new FlagHandler(Db, Local)
|
||||
}, NullLogger<InboxProcessor>.Instance);
|
||||
Follows = new FollowService(Db, Local, Remote, Delivery, new KeyLocalizer<GenericRes>(), NullLogger<FollowService>.Instance);
|
||||
@@ -61,7 +62,7 @@ namespace PrivaPub.Tests.Support
|
||||
Outbox = new OutboxPublisher(Db, Local, Delivery);
|
||||
Media = new MediaService(new StaticOptions<MediaOptions>(new MediaOptions { Root = Path.Combine(Path.GetTempPath(), $"privapub-media-{Guid.NewGuid():N}") }),
|
||||
Local, default, NullLogger<MediaService>.Instance);
|
||||
Statuses = new StatusService(Db, Local, Remote, Delivery, Content, Outbox, Fanout, Media);
|
||||
Statuses = new StatusService(Db, Local, Remote, Delivery, Content, Outbox, Fanout, Media, Groups);
|
||||
Posts = new PostsService(Db, Local, Statuses, new KeyLocalizer<GenericRes>(), NullLogger<PostsService>.Instance);
|
||||
Timelines = new TimelineService(Db, new KeyLocalizer<GenericRes>());
|
||||
Relationships = new RelationshipService(Db, Follows, Delivery);
|
||||
@@ -82,6 +83,7 @@ namespace PrivaPub.Tests.Support
|
||||
public PostsService Posts { get; }
|
||||
public StatusService Statuses { get; }
|
||||
public MediaService Media { get; }
|
||||
public GroupDistributor Groups { get; }
|
||||
public Fanout Fanout { get; }
|
||||
public RemotePosts RemotePosts { get; }
|
||||
public TimelineService Timelines { get; }
|
||||
|
||||
@@ -14,13 +14,16 @@ namespace PrivaPub.Tests.Support
|
||||
{
|
||||
readonly RSA _key = RSA.Create(2048);
|
||||
|
||||
public RemoteActor(Peer peer, string name, string origin = default)
|
||||
public RemoteActor(Peer peer, string name, string origin = default, string type = "Person")
|
||||
{
|
||||
Name = $"{name}{Guid.NewGuid():N}"[..20];
|
||||
Id = $"{origin ?? peer.A}/users/{Name}";
|
||||
Type = type;
|
||||
peer.Serve($"/users/{Name}", Document().ToJsonString());
|
||||
}
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public string Name { get; }
|
||||
public string Id { get; }
|
||||
public string KeyId => Id + "#main-key";
|
||||
@@ -29,7 +32,7 @@ namespace PrivaPub.Tests.Support
|
||||
public JsonObject Document() => new()
|
||||
{
|
||||
["id"] = Id,
|
||||
["type"] = "Person",
|
||||
["type"] = Type,
|
||||
["preferredUsername"] = Name,
|
||||
["inbox"] = Id + "/inbox",
|
||||
["followers"] = Id + "/followers",
|
||||
@@ -41,6 +44,21 @@ namespace PrivaPub.Tests.Support
|
||||
}
|
||||
};
|
||||
|
||||
public HttpRequest Get(string host, string path)
|
||||
{
|
||||
var date = DateTimeOffset.UtcNow.ToString("r", CultureInfo.InvariantCulture);
|
||||
var signingString = $"(request-target): get {path}\nhost: {host}\ndate: {date}";
|
||||
var signature = Convert.ToBase64String(_key.SignData(Encoding.UTF8.GetBytes(signingString), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Method = "GET";
|
||||
context.Request.Host = new HostString(host);
|
||||
context.Request.Path = path;
|
||||
context.Features.Get<IHttpRequestFeature>().RawTarget = path;
|
||||
context.Request.Headers["Date"] = date;
|
||||
context.Request.Headers["Signature"] = $"keyId=\"{KeyId}\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\",signature=\"{signature}\"";
|
||||
return context.Request;
|
||||
}
|
||||
|
||||
public HttpRequest Post(string host, string path, JsonNode activity)
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes(activity.ToJsonString());
|
||||
|
||||
Reference in new issue
Block a user