fedipod-server 0.30.0 → 0.31.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/README.md +5 -0
- package/dist/claims.d.ts +2 -0
- package/dist/claims.js +13 -4
- package/dist/fedipod.d.ts +0 -1
- package/dist/front.js +2 -4
- package/dist/handler.js +10 -1
- package/dist/identities.d.ts +9 -0
- package/dist/private-read.d.ts +4 -0
- package/dist/private-read.js +34 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -156,6 +156,11 @@ that document's address and registers nothing here. Once in, it posts to the
|
|
|
156
156
|
outbox the actor names, and reads what the identity received at `/ap/inbox`,
|
|
157
157
|
which nobody but the owner may read.
|
|
158
158
|
|
|
159
|
+
A followers-only or direct post of the identity's is given, at its own
|
|
160
|
+
address, to a server it was sent to when that server signs its request, the
|
|
161
|
+
way Mastodon gives its own: a follower's server gets a followers-only post,
|
|
162
|
+
the named person's server gets a direct one, and nobody else gets either.
|
|
163
|
+
|
|
159
164
|
A subdomained pod gets an origin of its own and answers at its root; a
|
|
160
165
|
suffixed pod answers under its own path (`/aisha/ap/actor`, `/aisha/api/…`,
|
|
161
166
|
`/aisha/oauth/…`, its door at `/aisha/fp/`). So a host carries at most one
|
package/dist/claims.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare function claims(input: {
|
|
|
5
5
|
pathname: string;
|
|
6
6
|
}, frontHost: string, runPath?: string): boolean;
|
|
7
7
|
export declare const isInboxPath: (pathname: string) => boolean;
|
|
8
|
+
export declare const isPrivateReadPath: (pathname: string) => boolean;
|
|
8
9
|
/**
|
|
9
10
|
* True when this path belongs to an identity's client surface.
|
|
10
11
|
*
|
|
@@ -18,4 +19,5 @@ export declare const isInboxPath: (pathname: string) => boolean;
|
|
|
18
19
|
export declare function agentClaims(input: {
|
|
19
20
|
pathname: string;
|
|
20
21
|
method?: string;
|
|
22
|
+
signed?: boolean;
|
|
21
23
|
}, uiPath?: string): boolean;
|
package/dist/claims.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// front's apex the gateway answers the fediverse routes; a pod subdomain is a
|
|
5
5
|
// real Solid pod and is never claimed.
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.isInboxPath = exports.DEFAULT_RUN_PATH = void 0;
|
|
7
|
+
exports.isPrivateReadPath = exports.isInboxPath = exports.DEFAULT_RUN_PATH = void 0;
|
|
8
8
|
exports.claims = claims;
|
|
9
9
|
exports.agentClaims = agentClaims;
|
|
10
10
|
// Signing up is CSS's, untouched: a person makes an account and a pod the way
|
|
@@ -18,8 +18,8 @@ const FRONT_PATHS = new Set(['/roster',
|
|
|
18
18
|
'/.well-known/webfinger', '/api/handle', '/api/attach', '/api/agent',
|
|
19
19
|
'/api/roster', '/api/revoke',
|
|
20
20
|
// What the pages that remain load: the sign-in library the opt-in and
|
|
21
|
-
// roster pages use
|
|
22
|
-
'/solid-oidc-client.js',
|
|
21
|
+
// roster pages use.
|
|
22
|
+
'/solid-oidc-client.js',
|
|
23
23
|
// Each page's own script, served beside it; a page without its script has
|
|
24
24
|
// buttons that never wake up.
|
|
25
25
|
'/new-account.js', '/run.js', '/admin.js']);
|
|
@@ -58,6 +58,10 @@ const AGENT_PREFIXES = ['/api/', '/oauth/', '/@']; // /@handle: the short profil
|
|
|
58
58
|
// `/aisha/fedipod/ap/inbox/` arrives here already stripped to `/fedipod/ap/inbox/`.
|
|
59
59
|
const isInboxPath = (pathname) => /^\/(?:[^/]+\/)+ap\/inbox\/$/u.test(pathname);
|
|
60
60
|
exports.isInboxPath = isInboxPath;
|
|
61
|
+
// A document in the identity's private folder — a followers-only or direct
|
|
62
|
+
// post, or one of the owner's own lists — matched by shape like the inbox.
|
|
63
|
+
const isPrivateReadPath = (pathname) => /^\/(?:[^/]+\/)+ap\/private\/[^/]+$/u.test(pathname);
|
|
64
|
+
exports.isPrivateReadPath = isPrivateReadPath;
|
|
61
65
|
/**
|
|
62
66
|
* True when this path belongs to an identity's client surface.
|
|
63
67
|
*
|
|
@@ -70,8 +74,13 @@ exports.isInboxPath = isInboxPath;
|
|
|
70
74
|
*/
|
|
71
75
|
function agentClaims(input, uiPath = '/fp/') {
|
|
72
76
|
const { pathname } = input;
|
|
77
|
+
const method = String(input.method ?? '').toUpperCase();
|
|
73
78
|
if ((0, exports.isInboxPath)(pathname))
|
|
74
|
-
return
|
|
79
|
+
return method === 'POST';
|
|
80
|
+
// A signed read of a private post is answered at the door, to a server the
|
|
81
|
+
// post was sent to (private-read.ts). Unsigned, the document is the pod's.
|
|
82
|
+
if ((0, exports.isPrivateReadPath)(pathname))
|
|
83
|
+
return !!input.signed && (method === 'GET' || method === 'HEAD');
|
|
75
84
|
if (AGENT_PATHS.has(pathname))
|
|
76
85
|
return true;
|
|
77
86
|
if (AGENT_PREFIXES.some((prefix) => pathname.startsWith(prefix)))
|
package/dist/fedipod.d.ts
CHANGED
package/dist/front.js
CHANGED
|
@@ -39,14 +39,12 @@ async function serveFront(h, request, response) {
|
|
|
39
39
|
// The notices page renders here too, and says the server keeps none:
|
|
40
40
|
// a notices store is the Netlify front's, not this component's.
|
|
41
41
|
noticesPage: pages.noticesPage,
|
|
42
|
-
// The opt-in and roster pages load the sign-in library
|
|
43
|
-
//
|
|
44
|
-
// cannot be used.
|
|
42
|
+
// The opt-in and roster pages load the sign-in library; without it the
|
|
43
|
+
// pages render but cannot be used.
|
|
45
44
|
authBundle: pages.authBundle,
|
|
46
45
|
// Each page's own script, so the pages can be served under
|
|
47
46
|
// `script-src 'self'` (see fedipod/front).
|
|
48
47
|
pageScripts: pages.pageScripts,
|
|
49
|
-
installScript: pages.installScript,
|
|
50
48
|
lookup: (handle) => h.dir.lookup(handle),
|
|
51
49
|
putDirectory: (handle, rec) => h.dir.putDirectory(handle, rec),
|
|
52
50
|
podPut: (_handle, url, body, ct) => s.podPut(url, body, ct),
|
package/dist/handler.js
CHANGED
|
@@ -52,6 +52,7 @@ const mounts_1 = require("./mounts");
|
|
|
52
52
|
const identities = __importStar(require("./identities"));
|
|
53
53
|
const optIn = __importStar(require("./opt-in"));
|
|
54
54
|
const door_1 = require("./door");
|
|
55
|
+
const private_read_1 = require("./private-read");
|
|
55
56
|
const front_1 = require("./front");
|
|
56
57
|
class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
57
58
|
args;
|
|
@@ -125,7 +126,8 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
125
126
|
// relative to the claim's mount, so a suffix pod's `/aisha/ap/actor` is
|
|
126
127
|
// judged as `/ap/actor`.
|
|
127
128
|
const c = (0, mounts_1.resolveClaim)(this, host, pathname);
|
|
128
|
-
if (c && (0, claims_1.agentClaims)({ pathname: (0, mounts_1.stripMount)(pathname, c.mount), method: request.method
|
|
129
|
+
if (c && (0, claims_1.agentClaims)({ pathname: (0, mounts_1.stripMount)(pathname, c.mount), method: request.method,
|
|
130
|
+
signed: !!request.headers.signature }, this.uiPath))
|
|
129
131
|
return;
|
|
130
132
|
throw new Error('not a gateway route'); // reject → CSS's LDP handler takes it
|
|
131
133
|
}
|
|
@@ -185,6 +187,13 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
185
187
|
if (pathname === inboxPath && String(request.method).toUpperCase() === 'POST') {
|
|
186
188
|
return (0, door_1.deliverAtDoor)(this, identity, request, response);
|
|
187
189
|
}
|
|
190
|
+
// A signed read of a private post — a followers-only or direct one, fetched
|
|
191
|
+
// at its address by a server it was sent to — is answered here (§3.2).
|
|
192
|
+
const privatePrefix = inboxPath.replace(/ap\/inbox\/$/u, 'ap/private/');
|
|
193
|
+
const method = String(request.method).toUpperCase();
|
|
194
|
+
if (pathname.startsWith(privatePrefix) && (method === 'GET' || method === 'HEAD') && request.headers.signature) {
|
|
195
|
+
return (0, private_read_1.readPrivateAtDoor)(this, identity, request, response);
|
|
196
|
+
}
|
|
188
197
|
await identity.surface.handler(request, response);
|
|
189
198
|
}
|
|
190
199
|
}
|
package/dist/identities.d.ts
CHANGED
|
@@ -16,6 +16,15 @@ export interface EmbeddedIdentity {
|
|
|
16
16
|
getConfig?: () => {
|
|
17
17
|
kind?: string;
|
|
18
18
|
} | null | undefined;
|
|
19
|
+
getContacts?: () => {
|
|
20
|
+
followers?: {
|
|
21
|
+
actor?: string;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
urls?: {
|
|
26
|
+
followers?: string;
|
|
27
|
+
toPod?: (u: string) => string;
|
|
19
28
|
};
|
|
20
29
|
};
|
|
21
30
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HttpHandlerInput } from '@solid/community-server';
|
|
2
|
+
import type { EmbeddedIdentity } from './identities';
|
|
3
|
+
import type { FediPodServerHandler } from './handler';
|
|
4
|
+
export declare function readPrivateAtDoor(h: FediPodServerHandler, identity: EmbeddedIdentity, request: HttpHandlerInput['request'], response: HttpHandlerInput['response']): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// private-read.ts — a signed read of a followers-only or direct post at its
|
|
3
|
+
// address on this pod, answered to a server the post was sent to (ActivityPub
|
|
4
|
+
// §3.2). The same rule the front applies (lib/gateway/private-read.mjs); the
|
|
5
|
+
// post is read through the server's own store and the followers come from the
|
|
6
|
+
// identity's live state. Anyone the post was not sent to gets 404.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.readPrivateAtDoor = readPrivateAtDoor;
|
|
9
|
+
const adapt_1 = require("./adapt");
|
|
10
|
+
const fedipod_1 = require("./fedipod");
|
|
11
|
+
async function readPrivateAtDoor(h, identity, request, response) {
|
|
12
|
+
const s = h.internals();
|
|
13
|
+
const { readPrivateForSigner } = await (0, fedipod_1.esmImport)(fedipod_1.EMBED);
|
|
14
|
+
let whatwg;
|
|
15
|
+
try {
|
|
16
|
+
// The pod's own origin: the signature covers the path and the Host header, and both are the pod's.
|
|
17
|
+
whatwg = await (0, adapt_1.nodeToWhatwg)(request, new URL(identity.podHome).origin);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
response.writeHead(400, { 'content-type': 'application/json' });
|
|
21
|
+
response.end(JSON.stringify({ error: e.message }));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const out = await readPrivateForSigner(identity.agent, whatwg, { read: (url) => s.io.read(url) });
|
|
25
|
+
s.logger.info(`FediPod: private read for @${identity.handle} — ${out.reason} (${out.status})`);
|
|
26
|
+
const noStore = { 'cache-control': 'no-store', vary: 'Signature' };
|
|
27
|
+
if (out.status !== 200) {
|
|
28
|
+
response.writeHead(out.status, noStore);
|
|
29
|
+
response.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
response.writeHead(200, { ...noStore, 'content-type': 'application/activity+json' });
|
|
33
|
+
response.end(String(request.method).toUpperCase() === 'HEAD' ? '' : out.body);
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fedipod-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "The FediPod Server: a full ActivityPub server as a Community Solid Server component.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"@solid/community-server": "^7.0.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"fedipod": "^1.
|
|
18
|
+
"fedipod": "^1.45.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"typescript": "^5.5.3",
|
|
22
|
-
"componentsjs-generator": "^3.1.2",
|
|
23
|
-
"@types/node": "^20.0.0",
|
|
24
|
-
"@solid/community-server": "^7.2.0",
|
|
25
21
|
"@fedify/fedify": "^2.3.4",
|
|
22
|
+
"@solid/community-server": "^7.2.0",
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"componentsjs-generator": "^3.1.2",
|
|
26
25
|
"n3": "^2.1.1",
|
|
27
|
-
"rdflib": "^2.4.0"
|
|
26
|
+
"rdflib": "^2.4.0",
|
|
27
|
+
"typescript": "^5.5.3"
|
|
28
28
|
},
|
|
29
29
|
"overrides": {
|
|
30
30
|
"@opentelemetry/core": "^2.8.0",
|