Summary
Integrate ActivityPub federation into JSS to complete the SAND stack (Solid + ActivityPub + Nostr + DID). Use the microfed library for ActivityPub primitives.
Background
microfed (v0.0.14) - Modular AP library: auth, profile, webfinger, inbox, outbox
fedbox (v0.0.10) - Reference implementation using microfed
- Both are early prototypes but functional
Identity Alignment
The key insight: JSS WebIDs and AP Actors use the same pattern:
JSS WebID: /profile/card#me
AP Actor: /profile/card#me (same!)
Nostr: did:nostr:... (via alsoKnownAs)
All three protocols can share one identifier.
Implementation Plan
Phase 1: Core Infrastructure
-
Add dependencies
npm install microfed better-sqlite3
-
RSA Keypair generation (src/ap/keys.js)
- Generate on first run if not exists
- Store in
data/ap-keys.json
- 2048-bit RSA (same as fedbox)
-
SQLite storage (src/ap/store.js)
- Port fedbox's store.js pattern
- Tables: followers, following, activities, posts, actors (cache)
- Path:
data/activitypub.db
Phase 2: Endpoints
-
WebFinger (/.well-known/webfinger)
- Parse
?resource=acct:user@domain
- Return actor URL with
application/activity+json link
- Use microfed's
webfinger.parseResource() and webfinger.createResponse()
-
Actor endpoint (modify existing profile handler)
- Content negotiation:
Accept: application/activity+json → return Actor JSON
- Add
publicKey, inbox, outbox, followers, following to profile
- Add
alsoKnownAs: ["did:nostr:..."] if Nostr pubkey exists
-
Inbox (/inbox or /profile/card/inbox)
- POST only, verify HTTP signatures
- Handle: Follow, Undo, Accept, Create, Like, Announce
- Use microfed's
auth.verify() for signatures
- Auto-accept follows (like fedbox)
-
Outbox (/profile/card/outbox)
- GET: Return OrderedCollection of user's activities
- POST: Create new activity, deliver to followers
-
Collections (/profile/card/followers, /profile/card/following)
- Return OrderedCollection of actor IDs
Phase 3: Federation
-
Activity delivery
- Use microfed's
outbox.send() with HTTP signatures
outbox.deliver() for multi-inbox delivery
- Sign with user's private key
-
Actor fetching & caching
- Fetch remote actors on Follow/mention
- Cache in SQLite actors table
- Strip fragment from URL when fetching
Phase 4: CLI Commands
- sandymount CLI integration
sandymount follow user@mastodon.social
sandymount post "Hello Fediverse!"
sandymount followers
File Structure
src/ap/
index.js # Fastify plugin export
keys.js # RSA keypair management
store.js # SQLite storage (port from fedbox)
routes/
webfinger.js # /.well-known/webfinger
inbox.js # /inbox POST handler
outbox.js # /outbox GET/POST
collections.js # /followers, /following
handlers/
follow.js # Handle Follow/Undo Follow
create.js # Handle Create (posts)
accept.js # Handle Accept (follow confirmation)
Actor JSON-LD Structure
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Person",
"id": "https://example.com/profile/card#me",
"preferredUsername": "alice",
"inbox": "https://example.com/profile/card/inbox",
"outbox": "https://example.com/profile/card/outbox",
"followers": "https://example.com/profile/card/followers",
"following": "https://example.com/profile/card/following",
"publicKey": {
"id": "https://example.com/profile/card#main-key",
"owner": "https://example.com/profile/card#me",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n..."
},
"alsoKnownAs": ["did:nostr:abc123..."]
}
Testing Strategy
- WebFinger lookup from Mastodon
- Follow from Mastodon account
- Receive posts in inbox
- Post and verify delivery to followers
- Verify with
curl and HTTP signature tools
References
Labels
enhancement, SAND-stack
Summary
Integrate ActivityPub federation into JSS to complete the SAND stack (Solid + ActivityPub + Nostr + DID). Use the
microfedlibrary for ActivityPub primitives.Background
microfed(v0.0.14) - Modular AP library: auth, profile, webfinger, inbox, outboxfedbox(v0.0.10) - Reference implementation using microfedIdentity Alignment
The key insight: JSS WebIDs and AP Actors use the same pattern:
All three protocols can share one identifier.
Implementation Plan
Phase 1: Core Infrastructure
Add dependencies
RSA Keypair generation (
src/ap/keys.js)data/ap-keys.jsonSQLite storage (
src/ap/store.js)data/activitypub.dbPhase 2: Endpoints
WebFinger (
/.well-known/webfinger)?resource=acct:user@domainapplication/activity+jsonlinkwebfinger.parseResource()andwebfinger.createResponse()Actor endpoint (modify existing profile handler)
Accept: application/activity+json→ return Actor JSONpublicKey,inbox,outbox,followers,followingto profilealsoKnownAs: ["did:nostr:..."]if Nostr pubkey existsInbox (
/inboxor/profile/card/inbox)auth.verify()for signaturesOutbox (
/profile/card/outbox)Collections (
/profile/card/followers,/profile/card/following)Phase 3: Federation
Activity delivery
outbox.send()with HTTP signaturesoutbox.deliver()for multi-inbox deliveryActor fetching & caching
Phase 4: CLI Commands
sandymount follow user@mastodon.social sandymount post "Hello Fediverse!" sandymount followersFile Structure
Actor JSON-LD Structure
{ "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1" ], "type": "Person", "id": "https://example.com/profile/card#me", "preferredUsername": "alice", "inbox": "https://example.com/profile/card/inbox", "outbox": "https://example.com/profile/card/outbox", "followers": "https://example.com/profile/card/followers", "following": "https://example.com/profile/card/following", "publicKey": { "id": "https://example.com/profile/card#main-key", "owner": "https://example.com/profile/card#me", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n..." }, "alsoKnownAs": ["did:nostr:abc123..."] }Testing Strategy
curland HTTP signature toolsReferences
Labels
enhancement, SAND-stack