@dwk/activitypub 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +135 -0
- package/dist/as2.d.ts +117 -0
- package/dist/as2.d.ts.map +1 -0
- package/dist/as2.js +174 -0
- package/dist/as2.js.map +1 -0
- package/dist/config.d.ts +148 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/delivery.d.ts +43 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +131 -0
- package/dist/delivery.js.map +1 -0
- package/dist/handler.d.ts +21 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +293 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +57 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +53 -0
- package/dist/log.js.map +1 -0
- package/dist/nodeinfo.d.ts +33 -0
- package/dist/nodeinfo.d.ts.map +1 -0
- package/dist/nodeinfo.js +61 -0
- package/dist/nodeinfo.js.map +1 -0
- package/dist/object.d.ts +21 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +722 -0
- package/dist/object.js.map +1 -0
- package/dist/signature.d.ts +108 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +234 -0
- package/dist/signature.js.map +1 -0
- package/package.json +50 -0
- package/src/as2.ts +257 -0
- package/src/config.ts +291 -0
- package/src/delivery.ts +155 -0
- package/src/handler.ts +370 -0
- package/src/index.ts +90 -0
- package/src/log.ts +62 -0
- package/src/nodeinfo.ts +91 -0
- package/src/object.ts +883 -0
- package/src/signature.ts +355 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David W. Keith
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# `@dwk/activitypub`
|
|
2
|
+
|
|
3
|
+
> Edge-native ActivityPub actor: inbox/outbox, follower collections, signed
|
|
4
|
+
> server-to-server federation. Endpoint package + Durable Object.
|
|
5
|
+
|
|
6
|
+
Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
|
|
7
|
+
[package specification](../../spec/packages/activitypub.md) for the full
|
|
8
|
+
requirements.
|
|
9
|
+
|
|
10
|
+
A native [ActivityPub](https://www.w3.org/TR/activitypub/) actor rooted at the
|
|
11
|
+
user's **own** domain — making the self-owned presence a first-class fediverse
|
|
12
|
+
citizen (followers, replies, boosts) rather than a bridged guest. It mirrors the
|
|
13
|
+
architecture proven in [`@dwk/solid-pod`](../solid-pod/README.md): a **stateless
|
|
14
|
+
front door** (routing + edge HTTP-signature verification) over a **per-actor
|
|
15
|
+
Durable Object** that is the consistency authority for activity-`id` dedup, the
|
|
16
|
+
follower/following/outbox collections, and the signed outbound delivery queue.
|
|
17
|
+
This is the second `@dwk` package to ship a Durable Object.
|
|
18
|
+
|
|
19
|
+
## What v1 covers
|
|
20
|
+
|
|
21
|
+
- **Actor document** (`Person`) served as `application/activity+json`, with the
|
|
22
|
+
public key embedded inline so peers can verify this actor's signatures.
|
|
23
|
+
- **Collections** — `outbox`, `followers`, `following` as paged
|
|
24
|
+
`OrderedCollection`s; `inbox` is write-only to peers.
|
|
25
|
+
- **Server-to-server (inbound)** `POST /inbox`: HTTP-signature verification at
|
|
26
|
+
the edge, dedup by activity `id`, and handling of `Follow` / `Undo` /
|
|
27
|
+
`Accept` / `Create` / `Update` / `Like` / `Announce` / `Delete`.
|
|
28
|
+
- **Server-to-server (outbound)**: auto-`Accept` of follows and signed fan-out
|
|
29
|
+
delivery to follower inboxes, with retry/backoff driven by **DO alarms**.
|
|
30
|
+
- **NodeInfo** — the `/.well-known/nodeinfo` discovery document and a
|
|
31
|
+
mostly-static `nodeinfo/2.1` document (live `usage` counts pulled from the DO).
|
|
32
|
+
- **Owner publish endpoint** (`POST <actor>/outbox`, bearer-token gated) — the
|
|
33
|
+
publish → `Create` fan-out seam for [`@dwk/micropub`](../micropub/README.md).
|
|
34
|
+
Full client-to-server authoring is **out of scope for v1**.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createActivityPub, ActivityPubObject } from "@dwk/activitypub";
|
|
40
|
+
|
|
41
|
+
const activitypub = createActivityPub({
|
|
42
|
+
baseUrl: "https://example.com",
|
|
43
|
+
actor: { username: "alice", name: "Alice", summary: "Hello, fediverse." },
|
|
44
|
+
publicKeyPem: env.AP_PUBLIC_KEY, // published in the actor document
|
|
45
|
+
privateKeyPem: env.AP_PRIVATE_KEY, // signs outbound deliveries (secret binding)
|
|
46
|
+
publishToken: env.AP_PUBLISH_TOKEN, // optional: enables POST <actor>/outbox
|
|
47
|
+
software: { name: "anglesite", version: "1.2.3" }, // NodeInfo
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// In your Worker's fetch handler:
|
|
51
|
+
// GET /users/alice → actor document
|
|
52
|
+
// GET /users/alice/{outbox,followers,following}[?page=N]
|
|
53
|
+
// POST /users/alice/inbox → signed S2S delivery
|
|
54
|
+
// GET /.well-known/nodeinfo, /nodeinfo/2.1
|
|
55
|
+
return activitypub(request, env, ctx);
|
|
56
|
+
|
|
57
|
+
// Bind the Durable Object the package ships:
|
|
58
|
+
export { ActivityPubObject };
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
// wrangler.jsonc — the binding the package declares
|
|
63
|
+
{
|
|
64
|
+
"durable_objects": {
|
|
65
|
+
"bindings": [{ "name": "ACTOR", "class_name": "ActivityPubObject" }],
|
|
66
|
+
},
|
|
67
|
+
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["ActivityPubObject"] }],
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The actor IRI and every collection IRI are derived from `baseUrl` + `username`
|
|
72
|
+
(`https://example.com/users/alice`, `…/inbox`, `…#main-key`). Mount the package
|
|
73
|
+
under a path prefix by including it in `baseUrl` (e.g.
|
|
74
|
+
`https://example.com/ap`) — the handler routes purely on the request URL.
|
|
75
|
+
|
|
76
|
+
## Bindings (declared `Env` fragment)
|
|
77
|
+
|
|
78
|
+
- `ACTOR` — the Durable Object namespace for the per-actor class
|
|
79
|
+
(`ActivityPubObject`). The single authoritative store for dedup, collections,
|
|
80
|
+
and the delivery queue. The handler **fails loudly** at startup if it is
|
|
81
|
+
missing.
|
|
82
|
+
|
|
83
|
+
No KV is used for any authoritative state, per
|
|
84
|
+
[`spec/non-functional-requirements.md`](../../spec/non-functional-requirements.md):
|
|
85
|
+
follower lists, dedup, and the delivery queue all live in the DO's SQLite.
|
|
86
|
+
|
|
87
|
+
## HTTP signatures
|
|
88
|
+
|
|
89
|
+
Inbound `POST /inbox` deliveries are authenticated, and outbound deliveries are
|
|
90
|
+
signed, with the de-facto fediverse `draft-cavage-http-signatures` profile
|
|
91
|
+
(RSA-SHA256 over a covered header set, body integrity via `Digest`). The
|
|
92
|
+
implementation is RSA-only with an explicit algorithm allow-list (no `none`, no
|
|
93
|
+
symmetric algorithms), mirroring the [`@dwk/dpop`](../dpop/README.md) hardening
|
|
94
|
+
posture.
|
|
95
|
+
|
|
96
|
+
Signing/verification sit behind the `verifyInboxSignature` config seam, so the
|
|
97
|
+
forthcoming cross-standard `@dwk/http-signatures` package (RFC 9421 +
|
|
98
|
+
draft-cavage; [#59](https://github.com/davidwkeith/workers/issues/59)) can be
|
|
99
|
+
swapped in unchanged once it lands.
|
|
100
|
+
|
|
101
|
+
## Delivery safety
|
|
102
|
+
|
|
103
|
+
Outbound deliveries target attacker-influenced URLs (a follower's advertised
|
|
104
|
+
inbox), so every target passes a syntactic SSRF guard before any request leaves:
|
|
105
|
+
HTTPS only, and private / loopback / link-local / cloud-metadata hosts are
|
|
106
|
+
refused. (DNS rebinding is out of scope — the Workers runtime does not expose
|
|
107
|
+
name resolution to user code; same limitation as
|
|
108
|
+
[`@dwk/webmention`](../webmention/README.md)'s safe-fetch.) A `4xx` from a peer
|
|
109
|
+
is a permanent failure (dropped); `5xx`, `408`, `429`, and network errors are
|
|
110
|
+
retried with exponential backoff up to `deliveryMaxAttempts`.
|
|
111
|
+
|
|
112
|
+
## Design
|
|
113
|
+
|
|
114
|
+
The front door is **stateless** and serves the static actor + NodeInfo documents
|
|
115
|
+
directly; everything that touches authoritative state is routed to the per-actor
|
|
116
|
+
Durable Object. Per the composition contract, the actor profile, key material,
|
|
117
|
+
and delivery policy are all passed into `createActivityPub` — nothing is read
|
|
118
|
+
from the global environment — so an actor can be instantiated multiple times and
|
|
119
|
+
tested in isolation.
|
|
120
|
+
|
|
121
|
+
ActivityStreams 2.0 documents are emitted in the **compact** JSON-LD form the
|
|
122
|
+
fediverse interoperates over (Mastodon et al.), with the AS2 + security
|
|
123
|
+
`@context`. Full RDF content-negotiation via [`@dwk/rdf`](../rdf/README.md) is a
|
|
124
|
+
future enhancement (its v1 JSON-LD subset and the AS2 context are tracked in
|
|
125
|
+
[`spec/open-questions.md`](../../spec/open-questions.md) §4).
|
|
126
|
+
|
|
127
|
+
## Observability
|
|
128
|
+
|
|
129
|
+
Federation events flow through the injected `@dwk/log` `Logger`/`Metrics` seams
|
|
130
|
+
(default no-op): `activitypub.signature.{accepted,rejected}`,
|
|
131
|
+
`activitypub.inbox.{accepted,duplicate}`,
|
|
132
|
+
`activitypub.delivery.{succeeded,failed,blocked}`, and
|
|
133
|
+
`activitypub.publish.rejected`. Per the redaction policy, only reason codes,
|
|
134
|
+
activity types, and sanitized hosts are recorded — never key material, tokens,
|
|
135
|
+
or bodies.
|
package/dist/as2.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActivityStreams 2.0 vocabulary helpers for `@dwk/activitypub`.
|
|
3
|
+
*
|
|
4
|
+
* ActivityStreams 2.0 documents are JSON-LD, but the entire fediverse interops
|
|
5
|
+
* over the **compact** form with a fixed `@context`, so this module emits and
|
|
6
|
+
* parses that compact JSON shape directly rather than running a full JSON-LD
|
|
7
|
+
* processor. (Whether `@dwk/rdf`'s v1 JSON-LD subset covers the AS2 context is
|
|
8
|
+
* an open question — see `spec/open-questions.md` §4 — so v1 stays on the
|
|
9
|
+
* compact form Mastodon and friends actually speak.)
|
|
10
|
+
*
|
|
11
|
+
* Pure data: every function takes plain values and returns plain JSON objects,
|
|
12
|
+
* so the vocabulary is unit-testable without a Workers runtime or any binding.
|
|
13
|
+
*/
|
|
14
|
+
/** The ActivityStreams 2.0 namespace IRI. */
|
|
15
|
+
export declare const AS2_NS = "https://www.w3.org/ns/activitystreams";
|
|
16
|
+
/** The W3C security vocabulary namespace (carries `publicKey`). */
|
|
17
|
+
export declare const SECURITY_NS = "https://w3id.org/security/v1";
|
|
18
|
+
/** The special "public" collection that marks an activity world-readable. */
|
|
19
|
+
export declare const PUBLIC_AUDIENCE = "https://www.w3.org/ns/activitystreams#Public";
|
|
20
|
+
/**
|
|
21
|
+
* The `@context` an actor document advertises: the AS2 base plus the security
|
|
22
|
+
* vocabulary (so `publicKey` resolves) and the handful of property aliases
|
|
23
|
+
* (`manuallyApprovesFollowers`, `discoverable`) the fediverse expects.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ACTOR_CONTEXT: readonly unknown[];
|
|
26
|
+
/** Media type for an ActivityStreams 2.0 / ActivityPub document. */
|
|
27
|
+
export declare const AS2_CONTENT_TYPE = "application/activity+json; charset=utf-8";
|
|
28
|
+
/**
|
|
29
|
+
* The JSON-LD profile variant of AS2. A strict client may content-negotiate for
|
|
30
|
+
* `application/ld+json` carrying the ActivityStreams profile rather than the
|
|
31
|
+
* `application/activity+json` alias the fediverse speaks; both name the same
|
|
32
|
+
* document (§3.2).
|
|
33
|
+
*/
|
|
34
|
+
export declare const AS2_LD_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8";
|
|
35
|
+
/** Whether an `Accept` header is asking for the ActivityStreams representation. */
|
|
36
|
+
export declare function wantsActivityJson(accept: string | null): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Pick the AS2 media type to serve for a given `Accept` (§3.2 content
|
|
39
|
+
* negotiation). A peer that asks specifically for the JSON-LD profile variant
|
|
40
|
+
* (`application/ld+json`, without also naming `application/activity+json`) gets
|
|
41
|
+
* {@link AS2_LD_CONTENT_TYPE}; everyone else — plain browsers, and the
|
|
42
|
+
* `application/activity+json` alias the fediverse speaks — gets
|
|
43
|
+
* {@link AS2_CONTENT_TYPE}.
|
|
44
|
+
*/
|
|
45
|
+
export declare function as2ContentType(accept: string | null): string;
|
|
46
|
+
/** A minimal JSON value type for AS2 documents. */
|
|
47
|
+
export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
|
|
48
|
+
readonly [key: string]: JsonValue;
|
|
49
|
+
};
|
|
50
|
+
/** A parsed ActivityStreams object/activity (compact JSON). */
|
|
51
|
+
export interface ActivityObject {
|
|
52
|
+
readonly id?: string;
|
|
53
|
+
readonly type?: string;
|
|
54
|
+
readonly actor?: JsonValue;
|
|
55
|
+
readonly object?: JsonValue;
|
|
56
|
+
readonly target?: JsonValue;
|
|
57
|
+
readonly to?: JsonValue;
|
|
58
|
+
readonly cc?: JsonValue;
|
|
59
|
+
readonly [key: string]: JsonValue | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** The derived IRI set that identifies one actor and its collections. */
|
|
62
|
+
export interface ActorIris {
|
|
63
|
+
readonly id: string;
|
|
64
|
+
readonly inbox: string;
|
|
65
|
+
readonly outbox: string;
|
|
66
|
+
readonly followers: string;
|
|
67
|
+
readonly following: string;
|
|
68
|
+
readonly keyId: string;
|
|
69
|
+
}
|
|
70
|
+
/** The human-facing profile fields of an actor. */
|
|
71
|
+
export interface ActorProfile {
|
|
72
|
+
/** `preferredUsername` — the local part of the `acct:` handle. */
|
|
73
|
+
readonly username: string;
|
|
74
|
+
/** Display name; defaults to {@link username}. */
|
|
75
|
+
readonly name?: string;
|
|
76
|
+
/** Short bio rendered as `summary` (may contain HTML). */
|
|
77
|
+
readonly summary?: string;
|
|
78
|
+
/** Avatar image URL, surfaced as the actor `icon`. */
|
|
79
|
+
readonly icon?: string;
|
|
80
|
+
/** Whether follows require manual approval. Defaults to `false` (auto-accept). */
|
|
81
|
+
readonly manuallyApprovesFollowers?: boolean;
|
|
82
|
+
/** Whether the actor opts into discovery/search (Mastodon `toot:discoverable`). */
|
|
83
|
+
readonly discoverable?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/** Optional extras woven into the actor document. */
|
|
86
|
+
export interface ActorDocumentOptions {
|
|
87
|
+
/**
|
|
88
|
+
* The instance-level shared inbox IRI, advertised under `endpoints` (§4.1 /
|
|
89
|
+
* §7.1.3) so large peers can batch-deliver to this actor. Omitted entirely
|
|
90
|
+
* when the deployment does not serve one.
|
|
91
|
+
*/
|
|
92
|
+
readonly sharedInbox?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the `Person` actor document served at the actor IRI. The public key is
|
|
96
|
+
* embedded inline (PEM) under the security vocabulary so verifiers can resolve
|
|
97
|
+
* `keyId` without a second fetch, exactly as Mastodon publishes it.
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildActorDocument(iris: ActorIris, profile: ActorProfile, publicKeyPem: string, options?: ActorDocumentOptions): Record<string, JsonValue>;
|
|
100
|
+
/**
|
|
101
|
+
* Build a paged `OrderedCollection` head document — the response to a bare
|
|
102
|
+
* collection `GET` (no `page` parameter). It advertises the total count and the
|
|
103
|
+
* `first`/`last` page links; the members live on the pages.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildCollection(id: string, totalItems: number, pageSize: number): Record<string, JsonValue>;
|
|
106
|
+
/**
|
|
107
|
+
* Build one `OrderedCollectionPage` of members. `next` is present only when a
|
|
108
|
+
* further page exists, so a crawler walks `first → next → …` until it stops.
|
|
109
|
+
*/
|
|
110
|
+
export declare function buildCollectionPage(collectionId: string, page: number, pageSize: number, totalItems: number, items: readonly JsonValue[]): Record<string, JsonValue>;
|
|
111
|
+
/** Read the `id` of an activity's `object`, whether it is an IRI or nested object. */
|
|
112
|
+
export declare function objectId(value: JsonValue | undefined): string | undefined;
|
|
113
|
+
/** Read the `type` of an activity's `object` when it is a nested object. */
|
|
114
|
+
export declare function objectType(value: JsonValue | undefined): string | undefined;
|
|
115
|
+
/** Coerce an actor reference (IRI string or embedded object) to its IRI. */
|
|
116
|
+
export declare function actorIri(value: JsonValue | undefined): string | undefined;
|
|
117
|
+
//# sourceMappingURL=as2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"as2.d.ts","sourceRoot":"","sources":["../src/as2.ts"],"names":[],"mappings":"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"}
|
package/dist/as2.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActivityStreams 2.0 vocabulary helpers for `@dwk/activitypub`.
|
|
3
|
+
*
|
|
4
|
+
* ActivityStreams 2.0 documents are JSON-LD, but the entire fediverse interops
|
|
5
|
+
* over the **compact** form with a fixed `@context`, so this module emits and
|
|
6
|
+
* parses that compact JSON shape directly rather than running a full JSON-LD
|
|
7
|
+
* processor. (Whether `@dwk/rdf`'s v1 JSON-LD subset covers the AS2 context is
|
|
8
|
+
* an open question — see `spec/open-questions.md` §4 — so v1 stays on the
|
|
9
|
+
* compact form Mastodon and friends actually speak.)
|
|
10
|
+
*
|
|
11
|
+
* Pure data: every function takes plain values and returns plain JSON objects,
|
|
12
|
+
* so the vocabulary is unit-testable without a Workers runtime or any binding.
|
|
13
|
+
*/
|
|
14
|
+
/** The ActivityStreams 2.0 namespace IRI. */
|
|
15
|
+
export const AS2_NS = "https://www.w3.org/ns/activitystreams";
|
|
16
|
+
/** The W3C security vocabulary namespace (carries `publicKey`). */
|
|
17
|
+
export const SECURITY_NS = "https://w3id.org/security/v1";
|
|
18
|
+
/** The special "public" collection that marks an activity world-readable. */
|
|
19
|
+
export const PUBLIC_AUDIENCE = "https://www.w3.org/ns/activitystreams#Public";
|
|
20
|
+
/**
|
|
21
|
+
* The `@context` an actor document advertises: the AS2 base plus the security
|
|
22
|
+
* vocabulary (so `publicKey` resolves) and the handful of property aliases
|
|
23
|
+
* (`manuallyApprovesFollowers`, `discoverable`) the fediverse expects.
|
|
24
|
+
*/
|
|
25
|
+
export const ACTOR_CONTEXT = [
|
|
26
|
+
AS2_NS,
|
|
27
|
+
SECURITY_NS,
|
|
28
|
+
{
|
|
29
|
+
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
|
30
|
+
toot: "http://joinmastodon.org/ns#",
|
|
31
|
+
discoverable: "toot:discoverable",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
/** Media type for an ActivityStreams 2.0 / ActivityPub document. */
|
|
35
|
+
export const AS2_CONTENT_TYPE = "application/activity+json; charset=utf-8";
|
|
36
|
+
/**
|
|
37
|
+
* The JSON-LD profile variant of AS2. A strict client may content-negotiate for
|
|
38
|
+
* `application/ld+json` carrying the ActivityStreams profile rather than the
|
|
39
|
+
* `application/activity+json` alias the fediverse speaks; both name the same
|
|
40
|
+
* document (§3.2).
|
|
41
|
+
*/
|
|
42
|
+
export const AS2_LD_CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
|
43
|
+
/**
|
|
44
|
+
* The media types a federation peer uses to request AS2. A `GET` whose `Accept`
|
|
45
|
+
* names any of these (or the matching `profile`) wants the activity JSON rather
|
|
46
|
+
* than an HTML profile page.
|
|
47
|
+
*/
|
|
48
|
+
const AS2_ACCEPT_TOKENS = [
|
|
49
|
+
"application/activity+json",
|
|
50
|
+
"application/ld+json",
|
|
51
|
+
'profile="https://www.w3.org/ns/activitystreams"',
|
|
52
|
+
];
|
|
53
|
+
/** Whether an `Accept` header is asking for the ActivityStreams representation. */
|
|
54
|
+
export function wantsActivityJson(accept) {
|
|
55
|
+
if (!accept)
|
|
56
|
+
return false;
|
|
57
|
+
const lower = accept.toLowerCase();
|
|
58
|
+
return AS2_ACCEPT_TOKENS.some((token) => lower.includes(token));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pick the AS2 media type to serve for a given `Accept` (§3.2 content
|
|
62
|
+
* negotiation). A peer that asks specifically for the JSON-LD profile variant
|
|
63
|
+
* (`application/ld+json`, without also naming `application/activity+json`) gets
|
|
64
|
+
* {@link AS2_LD_CONTENT_TYPE}; everyone else — plain browsers, and the
|
|
65
|
+
* `application/activity+json` alias the fediverse speaks — gets
|
|
66
|
+
* {@link AS2_CONTENT_TYPE}.
|
|
67
|
+
*/
|
|
68
|
+
export function as2ContentType(accept) {
|
|
69
|
+
if (accept && wantsActivityJson(accept)) {
|
|
70
|
+
const lower = accept.toLowerCase();
|
|
71
|
+
if (lower.includes("application/ld+json") &&
|
|
72
|
+
!lower.includes("application/activity+json")) {
|
|
73
|
+
return AS2_LD_CONTENT_TYPE;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return AS2_CONTENT_TYPE;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build the `Person` actor document served at the actor IRI. The public key is
|
|
80
|
+
* embedded inline (PEM) under the security vocabulary so verifiers can resolve
|
|
81
|
+
* `keyId` without a second fetch, exactly as Mastodon publishes it.
|
|
82
|
+
*/
|
|
83
|
+
export function buildActorDocument(iris, profile, publicKeyPem, options = {}) {
|
|
84
|
+
const doc = {
|
|
85
|
+
"@context": ACTOR_CONTEXT,
|
|
86
|
+
id: iris.id,
|
|
87
|
+
type: "Person",
|
|
88
|
+
preferredUsername: profile.username,
|
|
89
|
+
name: profile.name ?? profile.username,
|
|
90
|
+
inbox: iris.inbox,
|
|
91
|
+
outbox: iris.outbox,
|
|
92
|
+
followers: iris.followers,
|
|
93
|
+
following: iris.following,
|
|
94
|
+
manuallyApprovesFollowers: profile.manuallyApprovesFollowers ?? false,
|
|
95
|
+
discoverable: profile.discoverable ?? true,
|
|
96
|
+
publicKey: {
|
|
97
|
+
id: iris.keyId,
|
|
98
|
+
owner: iris.id,
|
|
99
|
+
publicKeyPem,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
if (profile.summary !== undefined)
|
|
103
|
+
doc.summary = profile.summary;
|
|
104
|
+
if (profile.icon !== undefined) {
|
|
105
|
+
doc.icon = { type: "Image", url: profile.icon };
|
|
106
|
+
}
|
|
107
|
+
if (options.sharedInbox !== undefined) {
|
|
108
|
+
doc.endpoints = { sharedInbox: options.sharedInbox };
|
|
109
|
+
}
|
|
110
|
+
return doc;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build a paged `OrderedCollection` head document — the response to a bare
|
|
114
|
+
* collection `GET` (no `page` parameter). It advertises the total count and the
|
|
115
|
+
* `first`/`last` page links; the members live on the pages.
|
|
116
|
+
*/
|
|
117
|
+
export function buildCollection(id, totalItems, pageSize) {
|
|
118
|
+
const doc = {
|
|
119
|
+
"@context": AS2_NS,
|
|
120
|
+
id,
|
|
121
|
+
type: "OrderedCollection",
|
|
122
|
+
totalItems,
|
|
123
|
+
};
|
|
124
|
+
if (totalItems > 0) {
|
|
125
|
+
doc.first = `${id}?page=1`;
|
|
126
|
+
doc.last = `${id}?page=${Math.max(1, Math.ceil(totalItems / pageSize))}`;
|
|
127
|
+
}
|
|
128
|
+
return doc;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build one `OrderedCollectionPage` of members. `next` is present only when a
|
|
132
|
+
* further page exists, so a crawler walks `first → next → …` until it stops.
|
|
133
|
+
*/
|
|
134
|
+
export function buildCollectionPage(collectionId, page, pageSize, totalItems, items) {
|
|
135
|
+
const doc = {
|
|
136
|
+
"@context": AS2_NS,
|
|
137
|
+
id: `${collectionId}?page=${page}`,
|
|
138
|
+
type: "OrderedCollectionPage",
|
|
139
|
+
partOf: collectionId,
|
|
140
|
+
totalItems,
|
|
141
|
+
orderedItems: items,
|
|
142
|
+
};
|
|
143
|
+
if (page * pageSize < totalItems) {
|
|
144
|
+
doc.next = `${collectionId}?page=${page + 1}`;
|
|
145
|
+
}
|
|
146
|
+
if (page > 1)
|
|
147
|
+
doc.prev = `${collectionId}?page=${page - 1}`;
|
|
148
|
+
return doc;
|
|
149
|
+
}
|
|
150
|
+
/** Read the `id` of an activity's `object`, whether it is an IRI or nested object. */
|
|
151
|
+
export function objectId(value) {
|
|
152
|
+
if (typeof value === "string")
|
|
153
|
+
return value;
|
|
154
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
155
|
+
const id = value.id;
|
|
156
|
+
if (typeof id === "string")
|
|
157
|
+
return id;
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
/** Read the `type` of an activity's `object` when it is a nested object. */
|
|
162
|
+
export function objectType(value) {
|
|
163
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
164
|
+
const type = value.type;
|
|
165
|
+
if (typeof type === "string")
|
|
166
|
+
return type;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
/** Coerce an actor reference (IRI string or embedded object) to its IRI. */
|
|
171
|
+
export function actorIri(value) {
|
|
172
|
+
return objectId(value);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=as2.js.map
|
package/dist/as2.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"as2.js","sourceRoot":"","sources":["../src/as2.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration, the declared Cloudflare `Env` fragment, and config resolution
|
|
3
|
+
* for `@dwk/activitypub`.
|
|
4
|
+
*
|
|
5
|
+
* Per the composition contract the package never reads the global environment
|
|
6
|
+
* directly: the actor profile, key material, and delivery policy are all passed
|
|
7
|
+
* into {@link createActivityPub}, so an actor can be instantiated multiple times
|
|
8
|
+
* and unit-tested in isolation. The only runtime coupling is the per-actor
|
|
9
|
+
* Durable Object namespace, and a missing binding fails loudly at startup.
|
|
10
|
+
*/
|
|
11
|
+
import { type Logger, type Metrics } from "@dwk/log";
|
|
12
|
+
import type { ActorIris, ActorProfile } from "./as2";
|
|
13
|
+
import type { KeyResolver, VerifyResult, InboxRequest } from "./signature";
|
|
14
|
+
import type { SoftwareInfo } from "./nodeinfo";
|
|
15
|
+
import type { ActivityPubObject } from "./object";
|
|
16
|
+
/** Cloudflare bindings required by the ActivityPub handler and Durable Object. */
|
|
17
|
+
export interface ActivityPubEnv {
|
|
18
|
+
/**
|
|
19
|
+
* Durable Object namespace for the per-actor class
|
|
20
|
+
* ({@link ActivityPubObject}). The single authoritative store for the inbox,
|
|
21
|
+
* outbox, follower/following collections, activity-`id` dedup, and the
|
|
22
|
+
* delivery queue.
|
|
23
|
+
*/
|
|
24
|
+
readonly ACTOR: DurableObjectNamespace<ActivityPubObject>;
|
|
25
|
+
}
|
|
26
|
+
/** An override for inbound signature verification (see `@dwk/http-signatures`, #59). */
|
|
27
|
+
export type InboxVerifier = (request: InboxRequest) => Promise<VerifyResult> | VerifyResult;
|
|
28
|
+
/** Configuration passed to {@link createActivityPub}. */
|
|
29
|
+
export interface ActivityPubConfig {
|
|
30
|
+
/**
|
|
31
|
+
* The actor's identity root / base URL, e.g. `https://example.com`. The actor
|
|
32
|
+
* IRI and every collection IRI are derived from it. No trailing slash. Mount
|
|
33
|
+
* the package under a path prefix by including it here (e.g.
|
|
34
|
+
* `https://example.com/ap`).
|
|
35
|
+
*/
|
|
36
|
+
readonly baseUrl: string;
|
|
37
|
+
/** The single actor this deployment serves (v1 is one actor per `baseUrl`). */
|
|
38
|
+
readonly actor: ActorProfile;
|
|
39
|
+
/**
|
|
40
|
+
* PEM-encoded SPKI **public** key, published inline in the actor document so
|
|
41
|
+
* peers can verify this actor's outbound signatures.
|
|
42
|
+
*/
|
|
43
|
+
readonly publicKeyPem: string;
|
|
44
|
+
/**
|
|
45
|
+
* PEM-encoded PKCS#8 **private** key (a secret binding's value) used to sign
|
|
46
|
+
* outbound deliveries. Required to federate outbound (e.g. `Accept` a follow);
|
|
47
|
+
* omit it for a read-only actor that never delivers.
|
|
48
|
+
*/
|
|
49
|
+
readonly privateKeyPem?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Bearer token authorizing the owner-only publish endpoint
|
|
52
|
+
* (`POST <actor>/outbox`). When unset, that endpoint is disabled. This is the
|
|
53
|
+
* `@dwk/micropub` publish → `Create` fan-out seam; full C2S is out of scope
|
|
54
|
+
* for v1.
|
|
55
|
+
*/
|
|
56
|
+
readonly publishToken?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Whether to serve and advertise an instance-level **shared inbox** at
|
|
59
|
+
* `${baseUrl}/inbox` (ActivityPub §4.1 / §7.1.3), letting large peers
|
|
60
|
+
* batch-deliver to this actor. Defaults to `true`; set `false` to publish no
|
|
61
|
+
* `endpoints.sharedInbox` and serve no shared-inbox route.
|
|
62
|
+
*/
|
|
63
|
+
readonly sharedInbox?: boolean;
|
|
64
|
+
/** Members served per `OrderedCollection` page. Defaults to 50. */
|
|
65
|
+
readonly pageSize?: number;
|
|
66
|
+
/** Max delivery attempts before a queued activity is dropped. Defaults to 8. */
|
|
67
|
+
readonly deliveryMaxAttempts?: number;
|
|
68
|
+
/** Base backoff (ms) for delivery retries (doubled per attempt). Defaults to 60_000. */
|
|
69
|
+
readonly deliveryBaseDelayMs?: number;
|
|
70
|
+
/** Accepted clock skew (seconds) on inbound signed `Date` headers. Defaults to 300. */
|
|
71
|
+
readonly clockSkewSeconds?: number;
|
|
72
|
+
/** Software identity for the NodeInfo document. Defaults to this package. */
|
|
73
|
+
readonly software?: SoftwareInfo;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a `keyId` to its owner + PEM key for inbound verification. Defaults
|
|
76
|
+
* to fetching the `keyId` (an actor or key IRI) as AS2 and reading
|
|
77
|
+
* `publicKey`. Supply your own to add caching or a key store.
|
|
78
|
+
*/
|
|
79
|
+
readonly keyResolver?: KeyResolver;
|
|
80
|
+
/**
|
|
81
|
+
* Override inbound signature verification entirely (e.g. to plug in
|
|
82
|
+
* `@dwk/http-signatures`). When set, the built-in draft-cavage verifier and
|
|
83
|
+
* {@link keyResolver} are bypassed.
|
|
84
|
+
*/
|
|
85
|
+
readonly verifyInboxSignature?: InboxVerifier;
|
|
86
|
+
/** `fetch` used for key resolution and outbound delivery. Defaults to global `fetch`. */
|
|
87
|
+
readonly fetch?: typeof fetch;
|
|
88
|
+
/** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now`. */
|
|
89
|
+
readonly now?: () => number;
|
|
90
|
+
/** Structured-logging seam; defaults to a no-op. */
|
|
91
|
+
readonly logger?: Logger;
|
|
92
|
+
/** Metrics seam; defaults to a no-op. */
|
|
93
|
+
readonly metrics?: Metrics;
|
|
94
|
+
}
|
|
95
|
+
/** Fully-resolved configuration with defaults applied and IRIs derived. */
|
|
96
|
+
export interface ResolvedConfig {
|
|
97
|
+
readonly baseUrl: string;
|
|
98
|
+
readonly actor: ActorProfile;
|
|
99
|
+
readonly iris: ActorIris;
|
|
100
|
+
/** Instance-level shared inbox IRI, or `undefined` when not served. */
|
|
101
|
+
readonly sharedInbox?: string;
|
|
102
|
+
readonly publicKeyPem: string;
|
|
103
|
+
readonly privateKeyPem?: string;
|
|
104
|
+
readonly publishToken?: string;
|
|
105
|
+
readonly pageSize: number;
|
|
106
|
+
readonly deliveryMaxAttempts: number;
|
|
107
|
+
readonly deliveryBaseDelayMs: number;
|
|
108
|
+
readonly clockSkewSeconds: number;
|
|
109
|
+
readonly software: SoftwareInfo;
|
|
110
|
+
readonly keyResolver: KeyResolver;
|
|
111
|
+
readonly verifyInboxSignature?: InboxVerifier;
|
|
112
|
+
readonly fetch: typeof fetch;
|
|
113
|
+
readonly now: () => number;
|
|
114
|
+
readonly logger: Logger;
|
|
115
|
+
readonly metrics: Metrics;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Internal headers the trusted front door uses to hand verified facts and the
|
|
119
|
+
* config subset the DO needs (including signing key material, which never leaves
|
|
120
|
+
* Cloudflare's network) to the Durable Object.
|
|
121
|
+
*/
|
|
122
|
+
export declare const INTERNAL_HEADERS: {
|
|
123
|
+
/** The verified actor IRI that signed an inbound `POST /inbox` (absent ⇒ unverified). */
|
|
124
|
+
readonly signedActor: "x-ap-signed-actor";
|
|
125
|
+
/** JSON config subset the DO needs (IRIs, delivery policy, signing key). */
|
|
126
|
+
readonly config: "x-ap-config";
|
|
127
|
+
/** Marks an owner-authorized publish request (`POST <actor>/outbox`). */
|
|
128
|
+
readonly publish: "x-ap-publish";
|
|
129
|
+
};
|
|
130
|
+
/** The config subset the front door forwards to the DO via {@link INTERNAL_HEADERS.config}. */
|
|
131
|
+
export interface ForwardedConfig {
|
|
132
|
+
readonly iris: ActorIris;
|
|
133
|
+
readonly actorName: string;
|
|
134
|
+
/** Shared inbox IRI the DO should also accept inbound `POST`s on, if served. */
|
|
135
|
+
readonly sharedInbox?: string;
|
|
136
|
+
readonly manuallyApprovesFollowers: boolean;
|
|
137
|
+
readonly pageSize: number;
|
|
138
|
+
readonly deliveryMaxAttempts: number;
|
|
139
|
+
readonly deliveryBaseDelayMs: number;
|
|
140
|
+
readonly keyId: string;
|
|
141
|
+
/** Private key (PKCS#8 PEM) so the DO can sign deliveries from its alarm. */
|
|
142
|
+
readonly privateKeyPem?: string;
|
|
143
|
+
}
|
|
144
|
+
/** Derive the actor + collection IRIs from a base URL and username. */
|
|
145
|
+
export declare function deriveIris(baseUrl: string, username: string): ActorIris;
|
|
146
|
+
/** Apply defaults and derive IRIs from raw {@link ActivityPubConfig}. */
|
|
147
|
+
export declare function resolveConfig(config: ActivityPubConfig): ResolvedConfig;
|
|
148
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|