-
-
Notifications
You must be signed in to change notification settings - Fork 130
Update relay documents and add tests #996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e4b37d1
36a2b49
b2ec5eb
d78cb92
acbd922
0e19b1e
1d9f8c9
c44b7fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| - Fixed the relay documentation to use the canonical actor and shared inbox | ||
| URIs, distinguish Mastodon-style and LitePub-style subscription behavior, | ||
| and explain which deployment responsibilities remain with applications. | ||
| [[#899], [#996] by Jiwon Kwon] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,28 +140,29 @@ Configuration options | |
|
|
||
| `queue` | ||
| : A [`MessageQueue`](./mq.md) for background activity processing. Recommended | ||
| for production: | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
| import { MemoryKvStore, InProcessMessageQueue } from "@fedify/fedify"; | ||
| // ---cut-before--- | ||
| const relay = createRelay("mastodon", { | ||
| kv: new MemoryKvStore(), | ||
| origin: "https://relay.example.com", | ||
| queue: new InProcessMessageQueue(), | ||
| subscriptionHandler: async (ctx, actor) => true, | ||
| }); | ||
| ~~~~ | ||
| for production. | ||
|
|
||
| > [!NOTE] | ||
| > For production, use [`RedisMessageQueue`], [`PostgresMessageQueue`], | ||
| > or [`MysqlMessageQueue`]. | ||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
| import { MemoryKvStore, InProcessMessageQueue } from "@fedify/fedify"; | ||
| // ---cut-before--- | ||
| const relay = createRelay("mastodon", { | ||
| kv: new MemoryKvStore(), | ||
| origin: "https://relay.example.com", | ||
| queue: new InProcessMessageQueue(), | ||
| subscriptionHandler: async (ctx, actor) => true, | ||
| }); | ||
| ~~~~ | ||
|
|
||
| > [!NOTE] | ||
| > For production, use [`RedisMessageQueue`], [`PostgresMessageQueue`], | ||
| > or [`MysqlMessageQueue`]. | ||
|
|
||
|
Comment on lines
+145
to
160
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think indents are necessary.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is that? indentation breaks the codes rendering on my editor.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's weird. There should be no problem with that. The indentation is needed for formatting, like in lines 167–169.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, that might be a bug of Hongdown?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Strange. It doesn't seem reproduced on my machine.
Yeah, because the admonition is about the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It because editor previews don't support the |
||
| `subscriptionHandler` (required) | ||
| : Callback to approve or reject subscription requests. See | ||
| [*Handling subscriptions*](#handling-subscriptions). To create an open relay | ||
| that accepts all subscriptions: | ||
| that accepts all subscriptions, set `subscriptionHandler` to always return | ||
| `true`. | ||
|
|
||
| ~~~~ typescript | ||
| subscriptionHandler: async (ctx, actor) => true | ||
|
|
@@ -185,19 +186,17 @@ Configuration options | |
| Relay types | ||
| ----------- | ||
|
|
||
| The first parameter to `createRelay()` specifies the relay protocol. | ||
| For detailed protocol specifications, see [FEP-ae0c]. | ||
|
|
||
| | Feature | `"mastodon"` | `"litepub"` | | ||
| | ---------------------- | ---------------------------- | -------------------------- | | ||
| | Activity forwarding | Direct | Wrapped in `Announce` | | ||
| | Following relationship | One-way | Bidirectional | | ||
| | Subscription state | Immediate `"accepted"` | `"pending"` → `"accepted"` | | ||
| | Compatibility | Broad (most implementations) | LitePub-aware servers | | ||
| The first parameter to `createRelay()` selects how this relay server handles | ||
| subscriptions and forwards activities. The package implements the server side | ||
| of the Mastodon-style and LitePub-style protocols described by [FEP-ae0c]; it | ||
| does not configure an existing ActivityPub application as a relay client. | ||
|
|
||
| > [!TIP] | ||
| > Use `"mastodon"` for broader compatibility. Switch to `"litepub"` only if | ||
| > you need its specific features. | ||
| | Feature | `"mastodon"` | `"litepub"` | | ||
| | ------------------------- | ---------------------- | -------------------------- | | ||
| | Activity forwarding | Direct | Wrapped in `Announce` | | ||
| | Following relationship | One-way | Bidirectional | | ||
| | Subscription state | Immediate `"accepted"` | `"pending"` → `"accepted"` | | ||
| | Canonical `Follow` object | Public collection | Relay actor | | ||
|
|
||
| [FEP-ae0c]: https://w3id.org/fep/ae0c | ||
|
|
||
|
|
@@ -244,55 +243,61 @@ in their server settings. The URL format differs depending on the relay type. | |
|
|
||
| ### Subscription URL | ||
|
|
||
| The subscription URL differs between Mastodon-style and LitePub-style relays: | ||
|
|
||
| | Relay type | Subscription URL | Example | | ||
| | ------------ | --------------------------- | --------------------------------- | | ||
| | `"mastodon"` | Inbox URL: `{origin}/inbox` | `https://relay.example.com/inbox` | | ||
| | `"litepub"` | Actor URL: `{origin}/actor` | `https://relay.example.com/actor` | | ||
|
|
||
| For more details on the protocol differences, see [FEP-ae0c]. | ||
|
|
||
| ### Subscribing from Mastodon | ||
|
|
||
| To subscribe from a Mastodon instance: | ||
|
|
||
| 1. Go to **Preferences** → **Administration** → **Relays** | ||
| 2. Click **Add new relay** | ||
| 3. Enter the relay inbox URL (e.g., `https://relay.example.com/inbox`) | ||
| 4. Click **Save and enable** | ||
| Retrieve subscription URLs from the relay instance rather than constructing | ||
| them from assumed paths. | ||
|
|
||
| The relay will receive a `Follow` activity from the instance. If the | ||
| `subscriptionHandler` approves the request, the relay sends back an `Accept` | ||
| activity, and the instance becomes a subscriber. | ||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
| import { MemoryKvStore } from "@fedify/fedify"; | ||
| const relay = createRelay("mastodon", { | ||
| kv: new MemoryKvStore(), | ||
| origin: "https://relay.example.com", | ||
| subscriptionHandler: async (ctx, actor) => true, | ||
| }); | ||
| // ---cut-before--- | ||
| const actorUri = await relay.getActorUri(); | ||
| const sharedInboxUri = await relay.getSharedInboxUri(); | ||
| ~~~~ | ||
|
|
||
| > [!NOTE] | ||
| > Mastodon only supports Mastodon-style relays. Use the inbox URL | ||
| > (`https://{domain}/inbox`) when subscribing from Mastodon. | ||
| | Relay type | Give clients | Default URI | | ||
| | ------------ | ---------------- | --------------------------------------- | | ||
| | `"mastodon"` | `sharedInboxUri` | `https://relay.example.com/inbox` | | ||
| | `"litepub"` | `actorUri` | `https://relay.example.com/users/relay` | | ||
|
|
||
| ### Subscribing from Pleroma/Akkoma | ||
| For more details on the protocol differences, see [FEP-ae0c]. | ||
|
|
||
| Pleroma and Akkoma use LitePub-style relays by default. To subscribe: | ||
|
|
||
| 1. Use the admin CLI or MIX task to add the relay | ||
| 2. Enter the relay actor URL (e.g., `https://relay.example.com/actor`) | ||
| Application responsibilities | ||
| ---------------------------- | ||
|
|
||
| ### Subscribing from other software | ||
| `createRelay()` provides the relay actor, inboxes, subscription handshake, | ||
| activity forwarding, cryptographic keys, and follower storage. The surrounding | ||
| application still needs to implement the following. | ||
|
|
||
| Consult your server software's documentation for specific instructions. | ||
| The general process is: | ||
| - Route requests for the configured `origin` to `relay.fetch()` without | ||
| rewriting the relay's paths. | ||
| - Terminate HTTPS and use a persistent `KvStore` in production. | ||
| - Configure a durable `MessageQueue` when delivery should survive process | ||
| restarts. | ||
| - Implement subscription policy and infrastructure-level rate limiting, | ||
| monitoring, and moderation. | ||
| - Provide WebFinger or NodeInfo separately when deployed clients require | ||
| those discovery endpoints. | ||
|
|
||
| 1. Find the relay settings in your server's administration panel | ||
| 2. Add the appropriate relay URL (inbox URL for Mastodon-style, actor URL | ||
| for LitePub-style) | ||
| 3. Wait for the subscription to be approved | ||
| The `subscriptionHandler` decides who is stored as a delivery recipient. It is | ||
| not authorization for publishing to the relay. The relay verifies incoming | ||
| activities using Fedify's federation pipeline, but it does not require the | ||
| sender to be a stored follower or check that an activity addresses the Public | ||
| collection. Deployments should account for that behavior in their access and | ||
| moderation policies. | ||
|
|
||
|
|
||
| Handling subscriptions | ||
| ---------------------- | ||
|
|
||
| The `subscriptionHandler` is required and determines whether to approve or | ||
| reject subscription requests. For an open relay that accepts all subscriptions: | ||
| reject subscription requests. The following example creates an open relay that | ||
| accepts all subscriptions. | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
|
|
@@ -305,7 +310,7 @@ const relay = createRelay("mastodon", { | |
| }); | ||
| ~~~~ | ||
|
|
||
| To implement approval logic with blocklists: | ||
| Approval logic can also be implemented with a domain block list. | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
|
|
@@ -326,13 +331,11 @@ const relay = createRelay("mastodon", { | |
| }); | ||
| ~~~~ | ||
|
|
||
| The handler receives: | ||
|
|
||
| - `ctx`: The `Context<RelayOptions>` object | ||
| - `actor`: The `Actor` requesting subscription | ||
| The handler receives the `Context<RelayOptions>` object as `ctx` and the `Actor` | ||
| requesting the subscription as `actor`. | ||
|
|
||
| Return `true` to approve or `false` to reject. Rejected requests receive a | ||
| `Reject` activity. | ||
| Return `true` to approve the request or `false` to reject it. The relay | ||
| responds to rejected requests with a `Reject` activity. | ||
|
|
||
|
|
||
| Managing followers | ||
|
|
@@ -343,7 +346,7 @@ interface. | |
|
|
||
| ### Listing all followers | ||
|
|
||
| Use `listFollowers()` to iterate over all followers: | ||
| Use `listFollowers()` to iterate over all followers. | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
|
|
@@ -363,7 +366,7 @@ for await (const follower of relay.listFollowers()) { | |
|
|
||
| ### Getting a specific follower | ||
|
|
||
| Use `getFollower()` to retrieve a specific follower by actor ID: | ||
| Use `getFollower()` to retrieve a specific follower by actor ID. | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { createRelay } from "@fedify/relay"; | ||
|
|
@@ -385,7 +388,7 @@ if (follower != null) { | |
|
|
||
| ### `RelayFollower` type | ||
|
|
||
| Each follower entry contains: | ||
| Each follower entry contains the following. | ||
|
|
||
| - `actorId`: The actor's ID (URL) as a string | ||
| - `actor`: The validated `Actor` object | ||
|
|
@@ -407,7 +410,7 @@ Stored with keys `["follower", actorId]`. Actor objects typically range from | |
|
|
||
| ### Cryptographic keys | ||
|
|
||
| Two key pairs are generated and stored: | ||
| The relay generates and stores two key pairs. | ||
|
|
||
| | Key | Purpose | | ||
| | --------------------------------- | ----------------------------------------------- | | ||
|
|
@@ -424,50 +427,26 @@ Security considerations | |
|
|
||
| ### Signature verification | ||
|
|
||
| The relay automatically verifies incoming activities using: | ||
|
|
||
| - [HTTP Signatures] | ||
| - [Linked Data Signatures] | ||
| - [Object Integrity Proofs] | ||
|
|
||
| Invalid signatures are silently ignored. Enable [logging](./log.md) for the | ||
| `["fedify", "sig"]` category to debug verification failures. | ||
|
|
||
| [HTTP Signatures]: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12 | ||
| [Linked Data Signatures]: https://web.archive.org/web/20170923124140/https://w3c-dvcg.github.io/ld-signatures/ | ||
| [Object Integrity Proofs]: https://w3id.org/fep/8b32 | ||
|
|
||
| ### Subscription abuse | ||
|
|
||
| Protect against abuse by: | ||
|
|
||
| 1. Implementing a `subscriptionHandler` to validate requests | ||
| 2. Maintaining a blocklist | ||
| 3. Rate limiting at the infrastructure level | ||
| 4. Monitoring activity volumes | ||
|
|
||
| ### Content moderation | ||
|
|
||
| > [!WARNING] | ||
| > Running a relay makes you responsible for forwarded content. Establish clear | ||
| > policies and vet subscribing instances. | ||
|
|
||
| ### Privacy | ||
| Incoming activities pass through Fedify's normal signature verification | ||
| pipeline. A valid signature authenticates the sender but does not make the | ||
| activity trusted. | ||
|
|
||
| The relay has access to all activities that pass through it. Do not store or | ||
| log activity content beyond operational needs. | ||
| The `subscriptionHandler` controls which actors receive forwarded activities. | ||
| It does not restrict which actors can submit activities to the relay, and | ||
| `createRelay()` does not check whether an activity addresses the Public | ||
| collection before forwarding it. | ||
|
|
||
| > [!CAUTION] | ||
| > Never forward non-public activities. The relay is designed only for public | ||
| > content distribution. | ||
| Deployments should apply appropriate access controls, rate limiting, and | ||
| moderation to the relay inbox. Avoid logging activity content unless it is | ||
| needed for operation or debugging. | ||
|
|
||
|
|
||
| Monitoring | ||
| ---------- | ||
|
|
||
| ### Logging | ||
|
|
||
| Enable relay-specific logging: | ||
| The following example enables Fedify logging, including relay operations. | ||
|
|
||
| ~~~~ typescript twoslash | ||
| import { configure, getConsoleSink } from "@logtape/logtape"; | ||
|
|
@@ -480,22 +459,23 @@ await configure({ | |
| }); | ||
| ~~~~ | ||
|
|
||
| Key log categories: | ||
| You can enable logging relevant to relay operation as follows. | ||
|
|
||
| | Category | Description | | ||
| | ------------------------------------ | ---------------------- | | ||
| | `["fedify", "relay"]` | Relay-specific events | | ||
| | `["fedify", "federation", "inbox"]` | Incoming activities | | ||
| | `["fedify", "federation", "outbox"]` | Outgoing activities | | ||
| | `["fedify", "sig"]` | Signature verification | | ||
|
|
||
| ### OpenTelemetry | ||
|
|
||
| The relay supports [OpenTelemetry](./opentelemetry.md) tracing. Key spans: | ||
| Relay operations are included in [OpenTelemetry](./opentelemetry.md). | ||
|
|
||
| | Span | Description | | ||
| | ------------------------------------- | ----------------------- | | ||
| | `activitypub.inbox` | Receiving activities | | ||
| | `activitypub.send_activity` | Forwarding activities | | ||
| | `activitypub.dispatch_inbox_listener` | Processing inbox events | | ||
| | Span | Description | | ||
| | ------------------------------------- | ---------------------------- | | ||
| | `activitypub.inbox` | Receiving an activity | | ||
| | `activitypub.send_activity` | Sending a relayed activity | | ||
| | `activitypub.dispatch_inbox_listener` | Processing an inbox activity | | ||
|
|
||
| <!-- cSpell: ignore LitePub --> | ||


Uh oh!
There was an error while loading. Please reload this page.