source: Klonkt/FEDERATION.md@ 3a3a882

main
Last change on this file since 3a3a882 was 8501ee8, checked in by Bart <bart@…>, 3 weeks ago

FEDERATION.md: OpenWebAuth erin, en de actor klopte niet meer

Twee dingen, en het tweede is een fout die er al stond.

OPENWEBAUTH (FEP-61cf) ontbrak volledig: niet bij de protocollen, niet in de
FEP-lijst, niet bij Authentication. Nu alle drie, met beide rollen apart
beschreven -- want dat is de vraag die een andere implementatie stelt: kan ik
hier binnenkomen, en kan ik van hieruit ergens anders heen. Met de vindbaarheid
(webfinger op de wortel voor het token-endpoint, #redirect op de actor), de
levensduur en eenmaligheid van tokens, en de drie dingen waar een implementatie
op stukloopt: Authorization in plaats van Signature, het mee-ondertekende
X-Open-Web-Auth, en dat ?zid= nooit een identiteit vaststelt.

EN DE ACTOR-SECTIE LOOG: "manuallyApprovesFollowers (currently always false)".

Dat is sinds Robins eigenaarspoort niet meer zo -- het is isWard

approve_followers. Een FEDERATION.md die een verkeerde bewering doet over wat er
op de draad staat is erger dan een die zwijgt, want een andere implementatie
richt zich ernaar. Meteen een korte sectie Follow approval erbij: wat er gebeurt
met een verzoek, wie beslist, en dat de guardianpoort vóórgaat.

Alles nagelopen tegen de code: de TTL, router.all op het token-endpoint, beide
webfinger-links, de uitdrukking voor manuallyApprovesFollowers, en dat accepteren
echt een Accept plus backfillNewFollower stuurt en weigeren een Reject.

Co-Authored-By: Claude Opus 5 <claude@…>

  • Property mode set to 100644
File size: 15.9 KB
Line 
1# Federation
2
3Klonkt is a small single-tenant fediverse site engine. Each site is one
4ActivityPub actor (a `Person`) that federates its posts, replies, boosts,
5likes, follows and polls, and that native or web apps can drive over
6ActivityPub Client-to-Server. This document describes what Klonkt speaks on the
7wire, following [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md).
8
9## Supported federation protocols and standards
10
11- [ActivityPub](https://www.w3.org/TR/activitypub/) Server-to-Server (S2S)
12- [ActivityPub](https://www.w3.org/TR/activitypub/) Client-to-Server (C2S), over OAuth 2.0
13- [WebFinger](https://webfinger.net/) ([RFC 7033](https://www.rfc-editor.org/rfc/rfc7033))
14- [HTTP Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) (draft-cavage), `rsa-sha256`
15- [NodeInfo](https://nodeinfo.diaspora.software/) 2.1
16- [OpenWebAuth](https://codeberg.org/fediverse/fep/src/branch/main/fep/61cf/fep-61cf.md) single sign-on, both roles (see "Authentication")
17- [OAuth 2.0](https://www.rfc-editor.org/rfc/rfc6749) public clients with PKCE ([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636), S256 only)
18- [OAuth 2.0 Authorization Server Metadata](https://www.rfc-editor.org/rfc/rfc8414) ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414))
19- [OAuth 2.0 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591) ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591))
20
21## Supported FEPs
22
23- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (FINAL): this file.
24- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md) (FINAL): `/.well-known/nodeinfo` links to a NodeInfo 2.1 document advertising software, version and the `activitypub` protocol.
25- [FEP-e232: Object Links](https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md) (FINAL): `Link` tags with `mediaType` `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` for object references inside content.
26- [FEP-044f: Consent-respecting quote posts](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md) (DRAFT): a quoted fediverse object carries real quote semantics — the `quote` property plus an FEP-e232 `Link` tag — and renders as a quote card rather than a bare link.
27- [FEP-9098: Custom emojis](https://codeberg.org/fediverse/fep/src/branch/main/fep/9098/fep-9098.md) (DRAFT): `:shortcode:` emoji tags on content and display names, preserved on the wire so connected apps can render them.
28- [FEP-c648: Blocked Collection](https://codeberg.org/fediverse/fep/src/branch/main/fep/c648/fep-c648.md) (DRAFT): inbound `Block` and `Undo(Block)` are honoured.
29- [FEP-7628: Move actor](https://codeberg.org/fediverse/fep/src/branch/main/fep/7628/fep-7628.md) (DRAFT): account migration — `alsoKnownAs`, `movedTo` and the `Move` activity, sent and received. See "Account migration".
30- [FEP-1580: Move Actor Objects with a `migration` Collection](https://codeberg.org/fediverse/fep/src/branch/main/fep/1580/fep-1580.md) (DRAFT): the objects a `Move` leaves behind, exposed as a `migration` collection alongside `moves`.
31- [FEP-61cf: The OpenWebAuth Protocol](https://codeberg.org/fediverse/fep/src/branch/main/fep/61cf/fep-61cf.md) (DRAFT): federated single sign-on, implemented in **both** roles. As a *target*, Klonkt lets a visitor from another server prove who they are and read follower-only posts without an account here. As a *home instance*, a site owner can sign in to another OpenWebAuth site using their own actor. See "Authentication".
32- [FEP-888d: Using `https://w3id.org/fep` as a base for FEP-specific namespaces](https://codeberg.org/fediverse/fep/src/branch/main/fep/888d/fep-888d.md) (DRAFT): the FEP-1580 terms are declared under `https://w3id.org/fep/1580/`, as that FEP registers them.
33
34Two further proposals are our own and are not (yet) part of the FEP index:
35
36- **FEP-633c: Guardians** — guardian-gated actors: wards, gated follows and
37 replies, the call-in flow, and a multi-party handshake. Implemented here and
38 submitted upstream as pull request
39 [#889](https://codeberg.org/fediverse/fep/pulls/889); the vocabulary is served
40 at [`https://ns.klonkt.com/shaer`](https://ns.klonkt.com/shaer).
41- **FEP-9876: enriched actor references** — collection members are bare URIs by
42 default and embedded objects on request, opt-in through
43 `Prefer: return=representation` ([RFC 7240](https://www.rfc-editor.org/rfc/rfc7240)).
44 Implemented; not yet submitted.
45
46Beyond these, Klonkt aims for de-facto Mastodon compatibility (the
47`http://joinmastodon.org/ns#` extension terms below). See "Under consideration"
48for FEPs we track but do not yet implement.
49
50## ActivityPub
51
52### Actor
53
54Each site exposes a `Person` actor at `/ap/users/:slug` with `inbox`, `outbox`,
55`followers`, `following` and `featured` collections, and an RSA-2048 public key
56under the legacy `publicKey` / `publicKeyPem` field
57([w3id security/v1](https://w3id.org/security/v1)). The actor advertises
58`discoverable`, `manuallyApprovesFollowers` (true for a ward, and for any site
59with owner approval switched on — see "Follow approval"),
60`featured`, and profile metadata as `schema:PropertyValue` links (including
61`rel="me"` verification links). Actor and object requests are
62content-negotiated: `application/activity+json` returns the AP document, other
63`Accept` values redirect to the human profile page.
64
65### Activities sent
66
67`Create`, `Update`, `Delete` (as `Tombstone`), `Follow`, `Accept`, `Like`,
68`Announce`, `Undo` (of `Follow` / `Like` / `Announce`), `Add` / `Remove`
69(featured-pin sync), `Flag` (moderation reports), `Move` (account migration),
70and `Offer` (the FEP-633c guardianship handshake). Posts, replies, boosts,
71likes and follows are delivered to remote inboxes with a signed HTTP request and
72a retrying delivery queue.
73
74### Activities received
75
76`Create`, `Update`, `Delete`, `Follow`, `Accept`, `Reject`, `Like`, `Announce`,
77`Undo`, `Flag`, `Block`, `Move` and `Offer`. Inbound follows are answered with
78`Accept` and backfilled with recent posts. Every inbound activity must carry a
79valid HTTP signature; unsigned or unverifiable requests are rejected.
80
81### Object types
82
83- `Note`: posts and replies, including rich replies with formatting and media.
84- `Question`: polls, single (`oneOf`) or multiple (`anyOf`) choice, with
85 `endTime`, `closed`, and Mastodon's `toot:votersCount`. Votes are received as a
86 `Note` with a `name` matching an option and `inReplyTo` the question. Tallies
87 are pushed to followers as `Update(Question)`.
88- `Image`, `Audio`, `Video`: media attachments with `mediaType`, `url` and `name`
89 (alt text). Audio attachments may carry cover art in `icon`.
90- `Tombstone`: in `Delete` activities.
91
92### Extensions and compatibility terms
93
94Every emitted object carries the full `@context`. Beyond AS2 core and
95security/v1, Klonkt declares and uses:
96
97- `as:sensitive` and `summary` for content warnings (blurred media, hidden text).
98- `as:Hashtag` tag objects (`#Tag`, linked to `/tag/:slug`).
99- `Mention` tag objects (`@user@host`, linked to the actor), resolved outbound
100 via WebFinger.
101- `contentMap`: BCP-47 language maps on posts and replies (Mastodon language
102 filter and translate).
103- `toot:discoverable`, `toot:featured`, `toot:votersCount`.
104- `schema:PropertyValue` / `schema:value` for profile metadata, and
105 `schema:embedUrl` for player-card embeds.
106
107### Collections
108
109- `followers` and `following` are count-only for the public. An authenticated
110 request from the site owner (a C2S bearer token scoped to that site) returns the
111 full list of actor URIs, so a connected app can build a contacts list.
112- `featured` lists pinned posts. Pin and unpin federate immediately as
113 `Add` / `Remove`, serialized per site to keep Mastodon's pin order.
114- A note's `replies` collection is served, and inbound threads are crawled one
115 level at a time (stale-while-revalidate, SSRF-guarded, budget-limited).
116
117## Account migration
118
119Klonkt implements account moves in both directions, following
120[FEP-7628](https://codeberg.org/fediverse/fep/src/branch/main/fep/7628/fep-7628.md)
121for the actor and
122[FEP-1580](https://codeberg.org/fediverse/fep/src/branch/main/fep/1580/fep-1580.md)
123for the objects the actor leaves behind.
124
125- **Actor terms.** `alsoKnownAs` and `movedTo` are declared with the same term
126 definitions Mastodon ships, so an existing implementation reads them without
127 special-casing. `alsoKnownAs` is reserved for former identities of the same
128 actor; a reference to an external register (a MusicBrainz artist, say) uses
129 `schema:sameAs` instead, precisely so that a move cannot be confused by it.
130- **The `Move` activity** is both sent and received. FEP-7628 moves *followers*
131 and says so explicitly; the objects are a separate problem, which is what
132 FEP-1580 addresses.
133- **Object migration.** A migrated site exposes a `migration` collection and a
134 `moves` collection, plus `migrationComplete`, `migratedFrom` and `migratedAt`.
135 The terms live under `https://w3id.org/fep/1580/`, the namespace that FEP
136 registers by way of FEP-888d. The FEP's own CURIE for the collection is
137 `migration:migration`; Klonkt emits the JSON key `migration`, because that is
138 what a consumer reads on.
139- **Importing.** An import from an export is not treated as a separate case: the
140 same path ingests from a source actor.
141
142One gap is worth stating plainly: the `moves` collection carries **no integrity
143proof**. Without [FEP-8b32](https://codeberg.org/fediverse/fep/src/branch/main/fep/8b32/fep-8b32.md)
144there is no signature under it, so a consumer has to trust the serving host
145rather than the claim itself. This is tracked and not yet resolved.
146
147## Client-to-Server (C2S)
148
149Native and web apps drive an account over ActivityPub C2S. This is not the
150Mastodon client API: Mastodon apps (Ivory and the like) are not supported here.
151
152- Discovery: the actor advertises its OAuth endpoints, and
153 `/.well-known/oauth-authorization-server` (RFC 8414) returns the authorization,
154 token and registration endpoints, `response_types=["code"]`,
155 `grant_types=["authorization_code"]`, `code_challenge_methods=["S256"]`,
156 `token_endpoint_auth_methods=["none"]` and `scopes_supported=["c2s"]`.
157- Registration: `POST /oauth/register` (RFC 7591), public clients only.
158- Authorization: PKCE authorization-code flow with a consent screen where the
159 user picks which site the app may act for. Tokens are hashed at rest and bound
160 to a single user and site.
161- Outbox: `POST /ap/users/:slug/outbox` with a bearer token accepts `Create`
162 (a bare `Note` is wrapped in a `Create` per spec), `Like`, `Announce`,
163 `Follow` and their `Undo`. Content is sanitized; a token cannot post for a
164 different site. `Delete` and `Update` over C2S are not yet implemented.
165
166## Authentication
167
168- S2S: HTTP Signatures (draft-cavage), `rsa-sha256`, over
169 `(request-target) host date digest`, with a configurable clock-skew tolerance
170 and reverse-proxy-aware host matching. Object Integrity Proofs
171 (FEP-8b32) are not used.
172- C2S: OAuth 2.0 bearer tokens, public clients with PKCE (S256), scope `c2s`.
173- Visitors: OpenWebAuth (FEP-61cf), described below.
174
175### OpenWebAuth (FEP-61cf)
176
177Klonkt implements both roles.
178
179**As a target instance.** A visitor enters their fediverse address; we WebFinger
180it for a `http://purl.org/openwebauth/v1#redirect` link (falling back to `/magic`
181on the same host, as Hubzilla and (streams) do) and send them there. Their server
182then makes a signed request to our token endpoint at `/owa/token`, which accepts
183both `GET` and `POST`. We verify the HTTP Signature with the same code path that
184verifies inbox deliveries — the key is pinned to the origin the actor document
185was fetched from, the signed `Date` must be recent, and a body requires a signed
186digest — then return a single-use token encrypted to the actor's public key
187(PKCS #1 v1.5, URL-safe Base64, unpadded). The visitor returns with `?owt=`, we
188redeem the token once and know who they are.
189
190Discovery for the other side: a WebFinger query for this server's root URL
191returns a `http://purl.org/openwebauth/v1` link pointing at `/owa/token`. An
192actor's WebFinger response carries the `#redirect` link pointing at `/magic`.
193
194**As a home instance.** `/magic` takes an OpenWebAuth request for a logged-in
195owner, discovers the target's token endpoint, requests a token over a signed
196request, decrypts it with the site's private key and returns the visitor with
197`?owt=`. On Klonkt the fediverse identity is the *site* actor, so an owner with
198several sites picks which one to present. There is a consent screen: FEP-61cf
199warns under "Information leakage" that OpenWebAuth hands a strong identity claim
200to any site that asks, so the detour through the home instance is where the user
201can decline.
202
203Notes for implementers:
204
205- The signature travels in `Authorization: Signature …`, as FEP-61cf requires,
206 not in the `Signature` header the rest of the fediverse uses. Our token
207 endpoint accepts either.
208- Signed requests we send also carry a signed `X-Open-Web-Auth` header with
209 random content, per the FEP.
210- `?zid=` may start the flow but never establishes identity; only a redeemed
211 `?owt=` does. `?owt=` is stripped from the URL after redemption.
212- Tokens are single-use and expire after three minutes; expired ones are swept
213 on every issue and redemption.
214- A discovered endpoint must share the origin it was discovered for. If
215 discovery fails, `/magic` returns an error rather than redirecting, so it
216 cannot be used as an open redirector.
217
218What this unlocks: `fan_only` posts. That gate used to ask for a local account,
219which is the wrong question — it excluded exactly the followers it was meant to
220admit. It now asks whether the proven actor follows this site.
221
222### Follow approval
223
224A site can require the owner to approve followers. With it on, the actor
225advertises `manuallyApprovesFollowers: true`, an inbound `Follow` is held
226pending instead of auto-accepted, and the owner accepts (sending `Accept` plus a
227backfill) or rejects (sending `Reject`). Ward actors always gate this way
228through their guardians, which takes precedence. A pending request is not a
229follower, and so does not open follower-only posts.
230
231## Moderation and safety
232
233- Inbound `Flag` reports are stored for the site owner; the owner can send an
234 outbound `Flag` to a remote actor's server.
235- The owner can remove an inbound reply from a thread; a tombstone prevents the
236 thread crawler from re-fetching it.
237- Actor and domain blocks silently drop matching activities (no error
238 disclosure) and purge existing content.
239- All outbound fetches are SSRF-guarded (private-range IP blocking on every
240 redirect hop, per-request timeout).
241
242## Under consideration (not yet implemented)
243
244Klonkt tracks the following proposals but does not implement them yet. Draft
245specs are marked; per project policy, drafts are only adopted deliberately and
246with a note in the changelog.
247
248- Reply control. FEP-5624 (per-object reply control) is WITHDRAWN as of
249 2025-06-24; its Mastodon terms `canReply` / `ApproveReply` / `RejectReply`
250 remain in production use. The live successor discussion is in drafts FEP-171b
251 (conversation containers), FEP-7458 (replies collection) and FEP-11dd (context
252 ownership). Klonkt currently accepts all replies to its posts.
253- Search-indexing consent: FEP-5feb (DRAFT). No `indexable` flag is emitted yet.
254- Actor public keys as Multikey: FEP-521a (FINAL). Klonkt still uses the legacy
255 `publicKey` representation.
256- Object Integrity Proofs: FEP-8b32 (DRAFT). Not used. This is also what leaves
257 the `moves` collection unsigned; see "Account migration".
258- Followers collection synchronization: FEP-8fcf (FINAL). Not implemented.
259
260## Additional documentation
261
262- Client-to-Server API for apps (the Shaer contract): `docs/shaer-c2s-api.md`.
263- Source code: `src/services/ActivityPubService.js` (core AP logic),
264 `src/routes/activitypub.js` (S2S, WebFinger, NodeInfo),
265 `src/routes/oauth.js` (C2S / OAuth),
266 `src/services/OpenWebAuthService.js` and `src/routes/openwebauth.js`
267 (OpenWebAuth, both roles).
268- Changelog: `CHANGELOG.md` (and `CHANGELOG.nl.md`, `CHANGELOG.de.md`).
Note: See TracBrowser for help on using the repository browser.