Skip to content

Commit 9240824

Browse files
authored
Merge pull request #17 from aluitink/s2s-compliance
S2s compliance
2 parents 381ebd7 + 6ae980d commit 9240824

62 files changed

Lines changed: 5326 additions & 560 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,36 @@ When developing UI components:
2020
- Do not add code comments that explain what code does — write self-explanatory code instead.
2121
- The project is in early development and changes frequently; keep implementations simple and avoid over-engineering.
2222
- If an opportunity to simplify or refactor existing code is noticed, ask the user before making those changes.
23+
- Be targeted with your test execution.
2324

25+
## ActivityStreams `IObjectOrLink` Type Evaluation
26+
27+
Properties typed as `IEnumerable<IObjectOrLink>` (e.g. `Actor`, `Object`, `AttributedTo`, `InReplyTo`, `To`, etc.) can hold a mix of inline objects and unresolved references. The concrete runtime type is determined by the JSON:
28+
29+
| JSON value | Runtime type | Meaning |
30+
|---|---|---|
31+
| Plain string (`"https://..."`) | `ILink` (Href set, `Type=["Link"]`) | Unresolved IRI reference |
32+
| `{"type":"Link",...}` or `{"type":"Mention",...}` | `ILink` | Qualified link |
33+
| `{"type":"Note",...}` (or any known type) | `IObject` (concrete subtype) | Inline object |
34+
| Object with no `type` | `ObjectOrLink` | Anonymous object |
35+
36+
**Always check `ILink` first** — a plain URL string is the most common form of an unresolved actor/object reference:
37+
38+
```csharp
39+
var ref = activity.Actor?.FirstOrDefault();
40+
if (ref is ILink link)
41+
actorId = link.Href?.ToString(); // unresolved — fetch if needed
42+
else if (ref is Actor actor)
43+
actorId = actor.Id; // already inline
44+
```
45+
46+
**`ILink` serializes back to a plain string** when `Href` is the only property set — so `is ILink` (not `is Link`) is the correct check for "unresolved reference".
47+
48+
**`IEnumerable<ILink>`** (e.g. `Object.Url`) always contains links — no need to type-check.
49+
50+
**`IImageOrLink`** (used for `Icon`/`Image`) only ever holds `Image` or `Link`.
51+
52+
**To dereference:** use `IActivityPubClient.GetAsync<T>(link.Href)`. Never assume an `IObjectOrLink` is a full object without checking `is IObject` first.
53+
54+
## 3rd Party Libraries
55+
- If we need details for Kristoffer Strube's ActivityStreams .NET library, refer to the official GitHub repository: https://github.com/KristofferStrube/ActivityStreams

Broca.ActivityPub.sln

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Microsoft Visual Studio Solution File, Format Version 12.00
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
23
# Visual Studio Version 17
34
VisualStudioVersion = 17.0.31903.59
45
MinimumVisualStudioVersion = 10.0.40219.1
@@ -26,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Broca.Web", "src\Broca.Web\
2627
EndProject
2728
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Broca.ActivityPub.Client.WebCrypto", "src\Broca.ActivityPub.Client.WebCrypto\Broca.ActivityPub.Client.WebCrypto.csproj", "{20644776-4EBD-4B61-8007-0A6430BF6CEB}"
2829
EndProject
30+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}"
31+
EndProject
32+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Broca.SampleData", "tools\Broca.SampleData\Broca.SampleData.csproj", "{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}"
33+
EndProject
2934
Global
3035
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3136
Debug|Any CPU = Debug|Any CPU
@@ -96,30 +101,6 @@ Global
96101
{05958A18-DF51-4FA8-BB35-97C59C8E2799}.Release|x64.Build.0 = Release|Any CPU
97102
{05958A18-DF51-4FA8-BB35-97C59C8E2799}.Release|x86.ActiveCfg = Release|Any CPU
98103
{05958A18-DF51-4FA8-BB35-97C59C8E2799}.Release|x86.Build.0 = Release|Any CPU
99-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
100-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
101-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|x64.ActiveCfg = Debug|Any CPU
102-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|x64.Build.0 = Debug|Any CPU
103-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|x86.ActiveCfg = Debug|Any CPU
104-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Debug|x86.Build.0 = Debug|Any CPU
105-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
106-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|Any CPU.Build.0 = Release|Any CPU
107-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|x64.ActiveCfg = Release|Any CPU
108-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|x64.Build.0 = Release|Any CPU
109-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|x86.ActiveCfg = Release|Any CPU
110-
{3C5B8D80-AC03-4EE9-A8C8-4BBC96CB6DDE}.Release|x86.Build.0 = Release|Any CPU
111-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
112-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
113-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|x64.ActiveCfg = Debug|Any CPU
114-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|x64.Build.0 = Debug|Any CPU
115-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|x86.ActiveCfg = Debug|Any CPU
116-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Debug|x86.Build.0 = Debug|Any CPU
117-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
118-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|Any CPU.Build.0 = Release|Any CPU
119-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|x64.ActiveCfg = Release|Any CPU
120-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|x64.Build.0 = Release|Any CPU
121-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|x86.ActiveCfg = Release|Any CPU
122-
{F4075288-D484-4F94-BA64-3B9EA24E21C3}.Release|x86.Build.0 = Release|Any CPU
123104
{D035D003-DA02-4F12-BD6C-47B338E53C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
124105
{D035D003-DA02-4F12-BD6C-47B338E53C03}.Debug|Any CPU.Build.0 = Debug|Any CPU
125106
{D035D003-DA02-4F12-BD6C-47B338E53C03}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -180,6 +161,18 @@ Global
180161
{20644776-4EBD-4B61-8007-0A6430BF6CEB}.Release|x64.Build.0 = Release|Any CPU
181162
{20644776-4EBD-4B61-8007-0A6430BF6CEB}.Release|x86.ActiveCfg = Release|Any CPU
182163
{20644776-4EBD-4B61-8007-0A6430BF6CEB}.Release|x86.Build.0 = Release|Any CPU
164+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
165+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|Any CPU.Build.0 = Debug|Any CPU
166+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|x64.ActiveCfg = Debug|Any CPU
167+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|x64.Build.0 = Debug|Any CPU
168+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|x86.ActiveCfg = Debug|Any CPU
169+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Debug|x86.Build.0 = Debug|Any CPU
170+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|Any CPU.ActiveCfg = Release|Any CPU
171+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|Any CPU.Build.0 = Release|Any CPU
172+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|x64.ActiveCfg = Release|Any CPU
173+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|x64.Build.0 = Release|Any CPU
174+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|x86.ActiveCfg = Release|Any CPU
175+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E}.Release|x86.Build.0 = Release|Any CPU
183176
EndGlobalSection
184177
GlobalSection(SolutionProperties) = preSolution
185178
HideSolutionNode = FALSE
@@ -195,5 +188,6 @@ Global
195188
{71238507-B44F-4FF7-848C-FEDF4CDA29FD} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
196189
{664ECEF2-8938-4BEA-9D1E-BC3D0C5EC4D0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
197190
{20644776-4EBD-4B61-8007-0A6430BF6CEB} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
191+
{EA3C5007-B9B1-431F-A44D-409FA8FBB11E} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84}
198192
EndGlobalSection
199193
EndGlobal

README.md

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,104 @@ A modular .NET library for building ActivityPub-enabled applications.
66

77
Broca provides a complete, standards-compliant implementation of the ActivityPub protocol for .NET developers. It offers both client and server capabilities with a clean API that makes federation simple.
88

9-
**Key Features:**
10-
- ✅ Full ActivityPub client and server implementation
11-
- ✅ HTTP Signatures for authenticated federation
9+
## Features
10+
11+
### Core ActivityPub Protocol
12+
13+
**Client-to-Server (C2S)**
14+
- ✅ Post activities to outbox (Create, Like, Follow, Announce, Undo)
15+
- ✅ Server-assigned activity IDs with proper URL structure
16+
- ✅ Follow/Unfollow relationship management
17+
- ✅ HTTP Signature authentication for outbox operations
18+
- ✅ API key-based client authentication
19+
20+
**Server-to-Server (S2S) Federation**
21+
- ✅ Cross-server activity delivery with HTTP Signatures
22+
- ✅ Background delivery queue with retry logic
23+
- ✅ Follow/Accept/Reject workflow (auto-accept and manual approval modes)
24+
- ✅ Undo operations (Follow, Like, Announce)
25+
- ✅ Delete activities with Tombstone support
26+
- ✅ Update Person for profile changes
27+
- ✅ Move activity for account migration with `alsoKnownAs` validation
28+
- ✅ Date validation (reject stale or future-dated requests)
29+
- ✅ Actor caching and refresh
30+
31+
**Shared Inbox**
32+
- ✅ Efficient batch delivery to multiple local users
33+
- ✅ To, Cc, and Bcc addressing support
34+
- ✅ Public addressing (`https://www.w3.org/ns/activitystreams#Public`)
35+
- ✅ Followers collection addressing
36+
- ✅ Smart routing (mixed local/remote recipient handling)
37+
38+
### Collections & Discovery
39+
40+
- ✅ Followers and Following collections
41+
- ✅ Custom collections (manual curation and query-based)
42+
- ✅ Ordered collection pagination
1243
- ✅ WebFinger support for @user@domain discovery
13-
- ✅ Background activity delivery with retry logic
14-
- ✅ Modular persistence (in-memory, file-based, or custom)
15-
- ✅ Production-ready with Docker and Let's Encrypt support
44+
- ✅ NodeInfo 2.0 and 2.1 (server metadata and statistics)
45+
46+
### Media & Attachments
47+
48+
- ✅ Blob storage for media files
49+
- ✅ Media endpoint (`/users/:username/media/:blobId`)
50+
- ✅ Attachments in Create activities
51+
- ✅ Content-Type preservation and validation
52+
53+
### Security & Authentication
54+
55+
- ✅ HTTP Signatures (draft-cavage-http-signatures-12)
56+
- ✅ Public key infrastructure with cryptographic key generation
57+
- ✅ Request date validation (prevents replay attacks)
58+
- ✅ Outbox authentication (users can only post as themselves)
59+
- ✅ Signature verification for incoming federation
60+
61+
### Content Type Handling
62+
63+
-`application/activity+json`
64+
-`application/ld+json`
65+
- ✅ Profile parameter support (`application/ld+json; profile="..."`)
66+
- ✅ Mastodon compatibility
67+
68+
### Administration
69+
70+
- ✅ Back-channel user management via ActivityPub protocol
71+
- ✅ System actor for server-level operations
72+
- ✅ Create/Update/Delete users through admin endpoints
73+
- ✅ Automatic key pair generation for new actors
74+
75+
### Persistence & Storage
76+
77+
- ✅ Modular storage abstractions (`IActorRepository`, `IActivityRepository`, `IBlobStorageService`)
78+
- ✅ In-memory implementation (development/testing)
79+
- ✅ File-based implementation (production-ready)
80+
- ✅ Easy integration with custom storage backends
81+
82+
### Developer Experience
83+
84+
- ✅ Comprehensive integration test suite (88+ tests)
85+
- ✅ Multi-server federation testing infrastructure
86+
- ✅ Activity builder API for constructing valid ActivityStreams objects
87+
- ✅ Blazor component library for building federated UIs
88+
- ✅ Docker and Let's Encrypt support for production deployment
89+
90+
## Standards Compliance
91+
92+
Broca implements the core [ActivityPub W3C Recommendation](https://www.w3.org/TR/activitypub/) with extensions for real-world interoperability:
93+
94+
-**ActivityPub** - Client-to-Server and Server-to-Server protocols
95+
-**ActivityStreams 2.0** - Core vocabulary and extended types
96+
-**HTTP Signatures** - Request authentication (draft-cavage-http-signatures-12)
97+
-**WebFinger** (RFC 7033) - User discovery via @username@domain
98+
-**NodeInfo 2.0/2.1** - Server metadata and statistics
99+
100+
**Interoperability Target:** Mastodon (primary), with support for Threads, Pixelfed, and Pleroma.
101+
102+
**Known Limitations:**
103+
- `featured` collection not yet exposed on actor documents (pinned posts)
104+
- Follow/Following collections for locked accounts publicly visible (privacy enhancement pending)
105+
106+
All critical and high-priority federation features are complete. See [docs/s2s-compliance-todo.md](docs/s2s-compliance-todo.md) for detailed compliance tracking.
16107

17108
## Library Components
18109

@@ -309,6 +400,29 @@ builder.Services.AddSingleton<IIdentityProvider, CustomIdentityProvider>();
309400
- Docker (for containerized deployment)
310401
- A domain name with HTTPS for production federation
311402

403+
## Testing & Quality Assurance
404+
405+
Broca includes a comprehensive test suite to ensure reliability and standards compliance:
406+
407+
**Integration Tests** (88+ tests across 10 test suites)
408+
- `ServerToServerTests` - Cross-server federation scenarios (22 tests)
409+
- `SharedInboxTests` - Efficient multi-user delivery (11 tests)
410+
- `ClientToServerTests` - Outbox posting and activity creation (7 tests)
411+
- `CustomCollectionsTests` - Manual and query-based collections (15 tests)
412+
- `AdminOperationsTests` - Back-channel user management (8 tests)
413+
- `ClientAuthenticationTests` - API key and HTTP Signature auth (5 tests)
414+
- `ContentTypeHandlingTests` - Mastodon compatibility (7 tests)
415+
- `BlobStorageTests` - Media upload and retrieval (4 tests)
416+
- `NodeInfoStatisticsTests` - Server metadata endpoints (5 tests)
417+
- `OutboxAuthenticationTests` - Security validation (3 tests)
418+
419+
**Unit Tests**
420+
- Repository implementations (actors, activities, delivery queue)
421+
- Blob storage service functionality
422+
- Component rendering logic
423+
424+
All tests use real HTTP clients and in-memory servers to validate end-to-end behavior, not mocked implementations. This ensures that Broca works correctly with actual ActivityPub clients and servers in the fediverse.
425+
312426
## Contributing
313427

314428
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for development workflow and guidelines.

docs/s2s-compliance-todo.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# ActivityPub Server-to-Server Compliance — Work Items
2+
3+
Last reviewed: 2026-02-28. Mastodon is the primary target for interop, with secondary targets including Threads, Pixelfed, and Pleroma.
4+
5+
---
6+
7+
## Status Summary
8+
9+
**Critical items:** 0 remaining (all resolved)
10+
**High priority items:** 0 remaining (all resolved)
11+
**Medium priority items:** 3 remaining
12+
**Low priority items:** 2 remaining
13+
14+
15+
## 🟡 Medium — Missing Features / Spec Gaps
16+
17+
### ✅ M1 · `Move` activity handled (COMPLETED)
18+
19+
**File:** `InboxProcessor.cs`
20+
21+
`Move` is used by Mastodon for account portability. When a user migrates to a new account, followers are automatically migrated.
22+
23+
**Implementation:** Handles `Move` by updating the follower's following list: replaces the old actor IRI with the new one if the new actor's `alsoKnownAs` references the old one (prevents spoofing).
24+
25+
**Status:** ✅ Implemented and tested with security validation.
26+
27+
---
28+
29+
### M2 · Actor document missing `featured` collection (pinned posts)
30+
31+
**File:** `ActorController.cs``Get`
32+
33+
Mastodon expects a `featured` property on the actor document pointing to an `OrderedCollection` of pinned post URIs. Without it, Mastodon logs warnings and will never show pinned posts.
34+
35+
**Fix:** Add `featured` to the actor's extension data pointing to `{baseUrl}/users/{username}/collections/featured`. The collections infrastructure exists; needs to be exposed on the actor document.
36+
37+
**Priority:** Medium - affects user features but doesn't break federation.
38+
39+
---
40+
41+
### ✅ M3 · `alsoKnownAs` field supported (COMPLETED)
42+
43+
**File:** `ActorController.cs`
44+
45+
Required for `Move`-based account migration. Actors can declare aliases via the `alsoKnownAs` property.
46+
47+
**Implementation:** Supported via actor's ExtensionData dictionary. Set as a JSON array of actor URI strings.
48+
49+
**Status:** ✅ No code changes required - ExtensionData already serializes arbitrary fields in actor documents.
50+
51+
---
52+
53+
### M4 · Followers / Following collections expose full member list without authentication
54+
55+
**File:** `ActorController.cs``GetFollowers`, `GetFollowing`
56+
57+
For locked accounts (`manuallyApprovesFollowers = true`) this leaks the full social graph. Mastodon hides this behind authentication for locked actors.
58+
59+
**Fix:** Check if the actor has `manuallyApprovesFollowers = true` and, if so, require the requester to be authenticated (or return only the count with no items).
60+
61+
**Priority:** Medium - privacy concern for locked accounts.
62+
63+
---
64+
65+
## 🟢 Low / Cosmetic
66+
67+
### L4 · HTTP signature `ParseSignatureParts` splits on first `=` only
68+
69+
**File:** `HttpSignatureService.cs``ParseSignatureParts`
70+
71+
The outer `Split(',')` then inner `Split('=', 2)` approach is correct for most cases, but any Signature header component whose *value* legitimately contains a comma (after line-folding) would be mishandled.
72+
73+
**Fix:** Consider a more robust parser that handles quoted-string values per RFC 7230.
74+
75+
**Priority:** Low - rarely encountered in practice.
76+
77+
---
78+
79+
## Priority Order (recommended)
80+
81+
| Order | Item | Effort | Notes |
82+
|-------|------|--------|-------|
83+
| 1 | ~~M1 — `Move` activity~~ | ~~Medium~~ |**Completed** |
84+
| 2 | ~~M3 — `alsoKnownAs`~~ | ~~Small~~ |**Completed** (no code changes needed) |
85+
| 3 | M2 — `featured` collection on actor | Small | Quick win — collection infrastructure exists |
86+
| 4 | M4 — Followers list auth for locked accounts | Small | Privacy fix |
87+
| 5 | M5 — NodeInfo real stats | Medium | Requires repo changes |
88+
| 6 | L2 — Content-type testing | Small | Verification only |
89+
| 7 | L4 — HTTP signature parser robustness | Small | Edge case |
90+
91+
---
92+
93+
## New Gaps Identified
94+
95+
None at this time. All critical and high-priority federation issues have been resolved.
96+
97+
---
98+
99+
## Test Coverage
100+
101+
The following test suites cover the completed items:
102+
103+
- **ServerToServerTests.cs** — S2S federation, Follow/Undo/Reject, Update{Actor}, clock-skew validation
104+
- **SharedInboxTests.cs** — Shared inbox delivery, addressing, signature validation, follower fan-out
105+
- **ClientToServerTests.cs** — C2S operations including Undo/Reject by IRI reference
106+
- **OutboxAuthenticationTests.cs** — Outbox POST authentication
107+
108+
---
109+
110+
## Notes
111+
112+
- All critical (🔴) and high-priority (🟠) items have been resolved and tested.
113+
- Remaining items are medium (🟡) and low (🟢) priority enhancements.
114+
- The project now has solid baseline S2S federation compatibility with Mastodon.
115+

0 commit comments

Comments
 (0)