@fedify/fedify 1.3.0-dev.482 → 1.3.0-dev.485
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/CHANGES.md +24 -0
- package/esm/deno.js +101 -0
- package/esm/federation/middleware.js +16 -11
- package/esm/nodeinfo/client.js +17 -2
- package/esm/runtime/docloader.js +101 -39
- package/esm/sig/key.js +2 -2
- package/esm/sig/ld.js +2 -2
- package/esm/sig/owner.js +3 -3
- package/esm/vocab/actor.js +7 -5
- package/esm/vocab/lookup.js +6 -3
- package/esm/vocab/vocab.js +413 -413
- package/esm/webfinger/lookup.js +9 -2
- package/package.json +1 -1
- package/types/deno.d.ts +84 -0
- package/types/deno.d.ts.map +1 -0
- package/types/federation/middleware.d.ts +9 -1
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/nodeinfo/client.d.ts +9 -0
- package/types/nodeinfo/client.d.ts.map +1 -1
- package/types/runtime/docloader.d.ts +84 -3
- package/types/runtime/docloader.d.ts.map +1 -1
- package/types/vocab/actor.d.ts +17 -2
- package/types/vocab/actor.d.ts.map +1 -1
- package/types/vocab/lookup.d.ts +9 -1
- package/types/vocab/lookup.d.ts.map +1 -1
- package/types/webfinger/lookup.d.ts +16 -1
- package/types/webfinger/lookup.d.ts.map +1 -1
package/CHANGES.md
CHANGED
|
@@ -8,6 +8,30 @@ Version 1.3.0
|
|
|
8
8
|
|
|
9
9
|
To be released.
|
|
10
10
|
|
|
11
|
+
- Fedify now makes HTTP requests with the proper `User-Agent` header. [[#162]]
|
|
12
|
+
|
|
13
|
+
- Added `getUserAgent()` function.
|
|
14
|
+
- Added `GetUserAgentOptions` interface.
|
|
15
|
+
- Added `getDocumentLoader()` function.
|
|
16
|
+
- Added `GetDocumentLoaderOptions` interface.
|
|
17
|
+
- The type of `getAuthenticatedDocumentLoader()` function's second
|
|
18
|
+
parameter became `GetAuthenticatedDocumentLoaderOptions | undefined`
|
|
19
|
+
(was `boolean | undefined`).
|
|
20
|
+
- Added `GetAuthenticatedDocumentLoaderOptions` interface.
|
|
21
|
+
- Deprecated `fetchDocumentLoader()` function.
|
|
22
|
+
- Added `LookupObjectOptions.userAgent` option.
|
|
23
|
+
- Added the type of `getActorHandle()` function's second parameter became
|
|
24
|
+
`GetActorHandleOptions | undefined` (was `NormalizeActorHandleOptions |
|
|
25
|
+
undefined`).
|
|
26
|
+
- Added `GetActorHandleOptions` interface.
|
|
27
|
+
- Added the optional second parameter to `lookupWebFinger()` function.
|
|
28
|
+
- Added `LookupWebFingerOptions` interface.
|
|
29
|
+
- Added `GetNodeInfoOptions.userAgent` option.
|
|
30
|
+
- Added `-u`/--user-agent` option to `fedify lookup` subcommand.
|
|
31
|
+
- Added `-u`/--user-agent` option to `fedify node` subcommand.
|
|
32
|
+
|
|
33
|
+
[#162]: https://github.com/dahlia/fedify/issues/162
|
|
34
|
+
|
|
11
35
|
|
|
12
36
|
Version 1.2.2
|
|
13
37
|
-------------
|
package/esm/deno.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"name": "@fedify/fedify",
|
|
3
|
+
"version": "1.3.0-dev.485+cfe509f3",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./mod.ts",
|
|
7
|
+
"./federation": "./federation/mod.ts",
|
|
8
|
+
"./nodeinfo": "./nodeinfo/mod.ts",
|
|
9
|
+
"./runtime": "./runtime/mod.ts",
|
|
10
|
+
"./sig": "./sig/mod.ts",
|
|
11
|
+
"./vocab": "./vocab/mod.ts",
|
|
12
|
+
"./webfinger": "./webfinger/mod.ts",
|
|
13
|
+
"./x/denokv": "./x/denokv.ts",
|
|
14
|
+
"./x/fresh": "./x/fresh.ts",
|
|
15
|
+
"./x/hono": "./x/hono.ts"
|
|
16
|
+
},
|
|
17
|
+
"imports": {
|
|
18
|
+
"@cfworker/json-schema": "npm:@cfworker/json-schema@^2.0.1",
|
|
19
|
+
"@david/which-runtime": "jsr:@david/which-runtime@^0.2.0",
|
|
20
|
+
"@deno/dnt": "jsr:@deno/dnt@0.41.2",
|
|
21
|
+
"@fedify/fedify": "./mod.ts",
|
|
22
|
+
"@fedify/fedify/federation": "./federation/mod.ts",
|
|
23
|
+
"@fedify/fedify/nodeinfo": "./nodeinfo/mod.ts",
|
|
24
|
+
"@fedify/fedify/runtime": "./runtime/mod.ts",
|
|
25
|
+
"@fedify/fedify/sig": "./sig/mod.ts",
|
|
26
|
+
"@fedify/fedify/vocab": "./vocab/mod.ts",
|
|
27
|
+
"@fedify/fedify/webfinger": "./webfinger/mod.ts",
|
|
28
|
+
"@fedify/fedify/x/denokv": "./x/denokv.ts",
|
|
29
|
+
"@fedify/fedify/x/fresh": "./x/fresh.ts",
|
|
30
|
+
"@fedify/fedify/x/hono": "./x/hono.ts",
|
|
31
|
+
"@hongminhee/aitertools": "jsr:@hongminhee/aitertools@^0.6.0",
|
|
32
|
+
"@hugoalh/http-header-link": "jsr:@hugoalh/http-header-link@^1.0.2",
|
|
33
|
+
"@logtape/logtape": "jsr:@logtape/logtape@^0.7.1",
|
|
34
|
+
"@phensley/language-tag": "npm:@phensley/language-tag@^1.9.0",
|
|
35
|
+
"@std/assert": "jsr:@std/assert@^0.226.0",
|
|
36
|
+
"@std/async": "jsr:@std/async@^1.0.5",
|
|
37
|
+
"@std/bytes": "jsr:@std/bytes@^1.0.2",
|
|
38
|
+
"@std/collections": "jsr:@std/collections@^1.0.6",
|
|
39
|
+
"@std/encoding": "jsr:@std/encoding@^1.0.5",
|
|
40
|
+
"@std/fs": "jsr:@std/fs@^1.0.3",
|
|
41
|
+
"@std/http": "jsr:@std/http@^1.0.6",
|
|
42
|
+
"@std/path": "jsr:@std/path@^1.0.6",
|
|
43
|
+
"@std/semver": "jsr:@std/semver@^1.0.3",
|
|
44
|
+
"@std/testing": "jsr:@std/testing@^0.224.0",
|
|
45
|
+
"@std/text": "jsr:@std/text@^1.0.6",
|
|
46
|
+
"@std/url": "jsr:@std/url@^0.225.1",
|
|
47
|
+
"@std/yaml": "jsr:@std/yaml@^0.224.3",
|
|
48
|
+
"asn1js": "npm:asn1js@^3.0.5",
|
|
49
|
+
"fast-check": "npm:fast-check@^3.22.0",
|
|
50
|
+
"json-canon": "npm:json-canon@^1.0.1",
|
|
51
|
+
"jsonld": "npm:jsonld@^8.3.2",
|
|
52
|
+
"mock_fetch": "jsr:@hongminhee/deno-mock-fetch@^0.3.2",
|
|
53
|
+
"multibase": "npm:multibase@^4.0.6",
|
|
54
|
+
"multicodec": "npm:multicodec@^3.2.1",
|
|
55
|
+
"pkijs": "npm:pkijs@^3.2.4",
|
|
56
|
+
"uri-template-router": "npm:uri-template-router@^0.0.16",
|
|
57
|
+
"url-template": "npm:url-template@^3.1.1"
|
|
58
|
+
},
|
|
59
|
+
"include": [
|
|
60
|
+
"vocab/vocab.ts"
|
|
61
|
+
],
|
|
62
|
+
"exclude": [
|
|
63
|
+
".git/",
|
|
64
|
+
".github/",
|
|
65
|
+
".vscode/",
|
|
66
|
+
".zed/",
|
|
67
|
+
"apidoc/",
|
|
68
|
+
"cli/",
|
|
69
|
+
"codegen/schema.yaml",
|
|
70
|
+
"docs/",
|
|
71
|
+
"examples/",
|
|
72
|
+
"logo.svg",
|
|
73
|
+
"npm/",
|
|
74
|
+
"vocab/*.yaml",
|
|
75
|
+
"!vocab/vocab.ts"
|
|
76
|
+
],
|
|
77
|
+
"tasks": {
|
|
78
|
+
"cache": "deno task codegen && deno cache mod.ts",
|
|
79
|
+
"check": "deno task codegen && deno fmt --check && deno lint && deno check */*.ts",
|
|
80
|
+
"codegen": "deno run --allow-read --allow-write --check codegen/main.ts vocab/ ../runtime/ > vocab/vocab.ts && deno fmt vocab/vocab.ts && deno cache vocab/vocab.ts && deno check vocab/vocab.ts",
|
|
81
|
+
"test-without-codegen": "deno test --check --doc --allow-read --allow-write --allow-env --unstable-kv --trace-leaks",
|
|
82
|
+
"test": "deno task codegen && deno task test-without-codegen",
|
|
83
|
+
"coverage": "rm -rf coverage/ && deno task test --coverage && deno coverage --html coverage",
|
|
84
|
+
"bench": "deno task codegen && deno bench --allow-read --allow-write --allow-net --allow-env --allow-run --unstable-kv",
|
|
85
|
+
"apidoc": "deno task codegen && deno doc --html --name=Fedify --output=apidoc/ mod.ts",
|
|
86
|
+
"check-version": "deno run ../cli/scripts/check_version.ts",
|
|
87
|
+
"publish": "deno task codegen && deno publish",
|
|
88
|
+
"dnt-without-codegen": "deno run -A dnt.ts",
|
|
89
|
+
"dnt": "deno task codegen && deno task dnt-without-codegen",
|
|
90
|
+
"test-all": "deno task check && deno task test-without-codegen && deno task dnt-without-codegen && cd npm/ && bun run ./test_runner.js && cd ../",
|
|
91
|
+
"update": "deno run --allow-env --allow-read --allow-write=. --allow-run=git,deno --allow-net=jsr.io,registry.npmjs.org jsr:@molt/cli ./*.ts",
|
|
92
|
+
"update:commit": "deno task -q update --commit --pre-commit=fmt,lint",
|
|
93
|
+
"hooks:install": "deno run --allow-read=deno.json,.git/hooks/ --allow-write=.git/hooks/ jsr:@hongminhee/deno-task-hooks",
|
|
94
|
+
"hooks:pre-commit": "deno task check && deno task check-version"
|
|
95
|
+
},
|
|
96
|
+
"unstable": [
|
|
97
|
+
"kv",
|
|
98
|
+
"temporal"
|
|
99
|
+
],
|
|
100
|
+
"lock": false
|
|
101
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
|
2
2
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
3
3
|
import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getAuthenticatedDocumentLoader, getDocumentLoader, kvCache, } from "../runtime/docloader.js";
|
|
5
5
|
import { verifyRequest } from "../sig/http.js";
|
|
6
6
|
import { exportJwk, importJwk, validateCryptoKey } from "../sig/key.js";
|
|
7
7
|
import { hasSignature, signJsonLd } from "../sig/ld.js";
|
|
@@ -50,6 +50,7 @@ export class FederationImpl {
|
|
|
50
50
|
documentLoader;
|
|
51
51
|
contextLoader;
|
|
52
52
|
authenticatedDocumentLoaderFactory;
|
|
53
|
+
userAgent;
|
|
53
54
|
onOutboxError;
|
|
54
55
|
signatureTimeWindow;
|
|
55
56
|
skipSignatureVerification;
|
|
@@ -75,31 +76,34 @@ export class FederationImpl {
|
|
|
75
76
|
this.router.add("/.well-known/nodeinfo", "nodeInfoJrd");
|
|
76
77
|
this.objectCallbacks = {};
|
|
77
78
|
this.objectTypeIds = {};
|
|
78
|
-
if (options.allowPrivateAddress) {
|
|
79
|
+
if (options.allowPrivateAddress || options.userAgent != null) {
|
|
79
80
|
if (options.documentLoader != null) {
|
|
80
|
-
throw new TypeError("Cannot set documentLoader with allowPrivateAddress
|
|
81
|
+
throw new TypeError("Cannot set documentLoader with allowPrivateAddress or " +
|
|
82
|
+
"userAgent options.");
|
|
81
83
|
}
|
|
82
84
|
else if (options.contextLoader != null) {
|
|
83
|
-
throw new TypeError("Cannot set contextLoader with allowPrivateAddress
|
|
85
|
+
throw new TypeError("Cannot set contextLoader with allowPrivateAddress or " +
|
|
86
|
+
"userAgent options.");
|
|
84
87
|
}
|
|
85
88
|
else if (options.authenticatedDocumentLoaderFactory != null) {
|
|
86
89
|
throw new TypeError("Cannot set authenticatedDocumentLoaderFactory with " +
|
|
87
|
-
"allowPrivateAddress
|
|
90
|
+
"allowPrivateAddress or userAgent options.");
|
|
88
91
|
}
|
|
89
92
|
}
|
|
93
|
+
const { allowPrivateAddress, userAgent } = options;
|
|
90
94
|
this.documentLoader = options.documentLoader ?? kvCache({
|
|
91
|
-
loader:
|
|
92
|
-
? (url) => fetchDocumentLoader(url, true)
|
|
93
|
-
: fetchDocumentLoader,
|
|
95
|
+
loader: getDocumentLoader({ allowPrivateAddress, userAgent }),
|
|
94
96
|
kv: options.kv,
|
|
95
97
|
prefix: this.kvPrefixes.remoteDocument,
|
|
96
98
|
});
|
|
97
99
|
this.contextLoader = options.contextLoader ?? this.documentLoader;
|
|
98
100
|
this.authenticatedDocumentLoaderFactory =
|
|
99
101
|
options.authenticatedDocumentLoaderFactory ??
|
|
100
|
-
(
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
((identity) => getAuthenticatedDocumentLoader(identity, {
|
|
103
|
+
allowPrivateAddress,
|
|
104
|
+
userAgent,
|
|
105
|
+
}));
|
|
106
|
+
this.userAgent = userAgent;
|
|
103
107
|
this.onOutboxError = options.onOutboxError;
|
|
104
108
|
this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
|
|
105
109
|
this.skipSignatureVerification = options.skipSignatureVerification ?? false;
|
|
@@ -1554,6 +1558,7 @@ export class ContextImpl {
|
|
|
1554
1558
|
return lookupObject(identifier, {
|
|
1555
1559
|
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
1556
1560
|
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
1561
|
+
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
1557
1562
|
});
|
|
1558
1563
|
}
|
|
1559
1564
|
traverseCollection(collection, options = {}) {
|
package/esm/nodeinfo/client.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { getLogger } from "@logtape/logtape";
|
|
2
2
|
import { parse } from "../deps/jsr.io/@std/semver/1.0.3/mod.js";
|
|
3
|
+
import { getUserAgent, } from "../runtime/docloader.js";
|
|
3
4
|
const logger = getLogger(["fedify", "nodeinfo", "client"]);
|
|
4
5
|
export async function getNodeInfo(url, options = {}) {
|
|
5
6
|
try {
|
|
6
7
|
let nodeInfoUrl = url;
|
|
7
8
|
if (!options.direct) {
|
|
8
9
|
const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
|
|
9
|
-
const wellKnownResponse = await fetch(wellKnownUrl
|
|
10
|
+
const wellKnownResponse = await fetch(wellKnownUrl, {
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: "application/json",
|
|
13
|
+
"User-Agent": typeof options.userAgent === "string"
|
|
14
|
+
? options.userAgent
|
|
15
|
+
: getUserAgent(options.userAgent),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
10
18
|
if (!wellKnownResponse.ok) {
|
|
11
19
|
logger.error("Failed to fetch {url}: {status} {statusText}", {
|
|
12
20
|
url: wellKnownUrl.href,
|
|
@@ -28,7 +36,14 @@ export async function getNodeInfo(url, options = {}) {
|
|
|
28
36
|
}
|
|
29
37
|
nodeInfoUrl = link.href;
|
|
30
38
|
}
|
|
31
|
-
const response = await fetch(nodeInfoUrl
|
|
39
|
+
const response = await fetch(nodeInfoUrl, {
|
|
40
|
+
headers: {
|
|
41
|
+
Accept: "application/json",
|
|
42
|
+
"User-Agent": typeof options.userAgent === "string"
|
|
43
|
+
? options.userAgent
|
|
44
|
+
: getUserAgent(options.userAgent),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
32
47
|
if (!response.ok) {
|
|
33
48
|
logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
|
|
34
49
|
url: nodeInfoUrl.toString(),
|
package/esm/runtime/docloader.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
|
2
2
|
import { HTTPHeaderLink } from "@hugoalh/http-header-link";
|
|
3
3
|
import { getLogger } from "@logtape/logtape";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import metadata from "../deno.js";
|
|
4
6
|
import { signRequest } from "../sig/http.js";
|
|
5
7
|
import { validateCryptoKey } from "../sig/key.js";
|
|
6
8
|
import preloadedContexts from "./contexts.js";
|
|
@@ -26,10 +28,13 @@ export class FetchError extends Error {
|
|
|
26
28
|
this.url = typeof url === "string" ? new URL(url) : url;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
|
-
function createRequest(url) {
|
|
31
|
+
function createRequest(url, options = {}) {
|
|
30
32
|
return new Request(url, {
|
|
31
33
|
headers: {
|
|
32
34
|
Accept: "application/activity+json, application/ld+json",
|
|
35
|
+
"User-Agent": typeof options.userAgent === "string"
|
|
36
|
+
? options.userAgent
|
|
37
|
+
: getUserAgent(options.userAgent),
|
|
33
38
|
},
|
|
34
39
|
redirect: "manual",
|
|
35
40
|
});
|
|
@@ -125,6 +130,65 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
125
130
|
documentUrl,
|
|
126
131
|
};
|
|
127
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
135
|
+
*
|
|
136
|
+
* The created loader preloads the below frequently used contexts by default
|
|
137
|
+
* (unless `options.ignorePreloadedContexts` is set to `true`):
|
|
138
|
+
*
|
|
139
|
+
* - <https://www.w3.org/ns/activitystreams>
|
|
140
|
+
* - <https://w3id.org/security/v1>
|
|
141
|
+
* - <https://w3id.org/security/data-integrity/v1>
|
|
142
|
+
* - <https://www.w3.org/ns/did/v1>
|
|
143
|
+
* - <https://w3id.org/security/multikey/v1>
|
|
144
|
+
* - <https://purl.archive.org/socialweb/webfinger>
|
|
145
|
+
* - <http://schema.org/>
|
|
146
|
+
* @param options Options for the document loader.
|
|
147
|
+
* @returns The document loader.
|
|
148
|
+
* @since 1.3.0
|
|
149
|
+
*/
|
|
150
|
+
export function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
|
|
151
|
+
async function load(url) {
|
|
152
|
+
if (!skipPreloadedContexts && url in preloadedContexts) {
|
|
153
|
+
logger.debug("Using preloaded context: {url}.", { url });
|
|
154
|
+
return {
|
|
155
|
+
contextUrl: null,
|
|
156
|
+
document: preloadedContexts[url],
|
|
157
|
+
documentUrl: url,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (!allowPrivateAddress) {
|
|
161
|
+
try {
|
|
162
|
+
await validatePublicUrl(url);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof UrlError) {
|
|
166
|
+
logger.error("Disallowed private URL: {url}", { url, error });
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const request = createRequest(url, { userAgent });
|
|
172
|
+
logRequest(request);
|
|
173
|
+
const response = await fetch(request, {
|
|
174
|
+
// Since Bun has a bug that ignores the `Request.redirect` option,
|
|
175
|
+
// to work around it we specify `redirect: "manual"` here too:
|
|
176
|
+
// https://github.com/oven-sh/bun/issues/10754
|
|
177
|
+
redirect: "manual",
|
|
178
|
+
});
|
|
179
|
+
// Follow redirects manually to get the final URL:
|
|
180
|
+
if (response.status >= 300 && response.status < 400 &&
|
|
181
|
+
response.headers.has("Location")) {
|
|
182
|
+
return load(response.headers.get("Location"));
|
|
183
|
+
}
|
|
184
|
+
return getRemoteDocument(url, response, load);
|
|
185
|
+
}
|
|
186
|
+
return load;
|
|
187
|
+
}
|
|
188
|
+
const _fetchDocumentLoader = getDocumentLoader();
|
|
189
|
+
const _fetchDocumentLoader_allowPrivateAddress = getDocumentLoader({
|
|
190
|
+
allowPrivateAddress: true,
|
|
191
|
+
});
|
|
128
192
|
/**
|
|
129
193
|
* A JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
130
194
|
*
|
|
@@ -135,45 +199,20 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
135
199
|
* - <https://w3id.org/security/data-integrity/v1>
|
|
136
200
|
* - <https://www.w3.org/ns/did/v1>
|
|
137
201
|
* - <https://w3id.org/security/multikey/v1>
|
|
202
|
+
* - <https://purl.archive.org/socialweb/webfinger>
|
|
203
|
+
* - <http://schema.org/>
|
|
138
204
|
* @param url The URL of the document to load.
|
|
139
205
|
* @param allowPrivateAddress Whether to allow fetching private network
|
|
140
206
|
* addresses. Turned off by default.
|
|
141
207
|
* @returns The remote document.
|
|
208
|
+
* @deprecated Use {@link getDocumentLoader} instead.
|
|
142
209
|
*/
|
|
143
|
-
export
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
documentUrl: url,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
if (!allowPrivateAddress) {
|
|
153
|
-
try {
|
|
154
|
-
await validatePublicUrl(url);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
if (error instanceof UrlError) {
|
|
158
|
-
logger.error("Disallowed private URL: {url}", { url, error });
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const request = createRequest(url);
|
|
164
|
-
logRequest(request);
|
|
165
|
-
const response = await fetch(request, {
|
|
166
|
-
// Since Bun has a bug that ignores the `Request.redirect` option,
|
|
167
|
-
// to work around it we specify `redirect: "manual"` here too:
|
|
168
|
-
// https://github.com/oven-sh/bun/issues/10754
|
|
169
|
-
redirect: "manual",
|
|
170
|
-
});
|
|
171
|
-
// Follow redirects manually to get the final URL:
|
|
172
|
-
if (response.status >= 300 && response.status < 400 &&
|
|
173
|
-
response.headers.has("Location")) {
|
|
174
|
-
return fetchDocumentLoader(response.headers.get("Location"), allowPrivateAddress);
|
|
175
|
-
}
|
|
176
|
-
return getRemoteDocument(url, response, (url) => fetchDocumentLoader(url, allowPrivateAddress));
|
|
210
|
+
export function fetchDocumentLoader(url, allowPrivateAddress = false) {
|
|
211
|
+
logger.warn("fetchDocumentLoader() function is deprecated. " +
|
|
212
|
+
"Use getDocumentLoader() function instead.");
|
|
213
|
+
return (allowPrivateAddress
|
|
214
|
+
? _fetchDocumentLoader_allowPrivateAddress
|
|
215
|
+
: _fetchDocumentLoader)(url);
|
|
177
216
|
}
|
|
178
217
|
/**
|
|
179
218
|
* Gets an authenticated {@link DocumentLoader} for the given identity.
|
|
@@ -181,13 +220,12 @@ export async function fetchDocumentLoader(url, allowPrivateAddress = false) {
|
|
|
181
220
|
* the fetched documents.
|
|
182
221
|
* @param identity The identity to get the document loader for.
|
|
183
222
|
* The actor's key pair.
|
|
184
|
-
* @param
|
|
185
|
-
* addresses. Turned off by default.
|
|
223
|
+
* @param options The options for the document loader.
|
|
186
224
|
* @returns The authenticated document loader.
|
|
187
225
|
* @throws {TypeError} If the key is invalid or unsupported.
|
|
188
226
|
* @since 0.4.0
|
|
189
227
|
*/
|
|
190
|
-
export function getAuthenticatedDocumentLoader(identity, allowPrivateAddress =
|
|
228
|
+
export function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent } = {}) {
|
|
191
229
|
validateCryptoKey(identity.privateKey);
|
|
192
230
|
async function load(url) {
|
|
193
231
|
if (!allowPrivateAddress) {
|
|
@@ -201,7 +239,7 @@ export function getAuthenticatedDocumentLoader(identity, allowPrivateAddress = f
|
|
|
201
239
|
throw error;
|
|
202
240
|
}
|
|
203
241
|
}
|
|
204
|
-
let request = createRequest(url);
|
|
242
|
+
let request = createRequest(url, { userAgent });
|
|
205
243
|
request = await signRequest(request, identity.privateKey, identity.keyId);
|
|
206
244
|
logRequest(request);
|
|
207
245
|
const response = await fetch(request, {
|
|
@@ -268,3 +306,27 @@ export function kvCache({ loader, kv, prefix, rules }) {
|
|
|
268
306
|
return cache;
|
|
269
307
|
};
|
|
270
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Gets the user agent string for the given application and URL.
|
|
311
|
+
* @param options The options for making the user agent string.
|
|
312
|
+
* @returns The user agent string.
|
|
313
|
+
* @since 1.3.0
|
|
314
|
+
*/
|
|
315
|
+
export function getUserAgent({ software, url } = {}) {
|
|
316
|
+
const fedify = `Fedify/${metadata.version}`;
|
|
317
|
+
const runtime = "Deno" in dntShim.dntGlobalThis
|
|
318
|
+
? `Deno/${dntShim.Deno.version.deno}`
|
|
319
|
+
: "Bun" in dntShim.dntGlobalThis
|
|
320
|
+
// @ts-ignore: `Bun` is a global variable in Bun
|
|
321
|
+
? `Bun/${Bun.version}`
|
|
322
|
+
: "process" in dntShim.dntGlobalThis
|
|
323
|
+
? `Node.js/${process.version}`
|
|
324
|
+
: null;
|
|
325
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
326
|
+
if (runtime != null)
|
|
327
|
+
userAgent.push(runtime);
|
|
328
|
+
if (url != null)
|
|
329
|
+
userAgent.push(`+${url.toString()}`);
|
|
330
|
+
const first = userAgent.shift();
|
|
331
|
+
return `${first} (${userAgent.join("; ")})`;
|
|
332
|
+
}
|
package/esm/sig/key.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
|
2
2
|
import { getLogger } from "@logtape/logtape";
|
|
3
|
-
import {
|
|
3
|
+
import { getDocumentLoader, } from "../runtime/docloader.js";
|
|
4
4
|
import { isActor } from "../vocab/actor.js";
|
|
5
5
|
import { CryptographicKey, Object } from "../vocab/vocab.js";
|
|
6
6
|
/**
|
|
@@ -123,7 +123,7 @@ cls, { documentLoader, contextLoader, keyCache } = {}) {
|
|
|
123
123
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
124
124
|
let document;
|
|
125
125
|
try {
|
|
126
|
-
const remoteDocument = await (documentLoader ??
|
|
126
|
+
const remoteDocument = await (documentLoader ?? getDocumentLoader())(keyId);
|
|
127
127
|
document = remoteDocument.document;
|
|
128
128
|
}
|
|
129
129
|
catch (_) {
|
package/esm/sig/ld.js
CHANGED
|
@@ -4,7 +4,7 @@ import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@std/encoding/1.0.5/b
|
|
|
4
4
|
import { encodeHex } from "../deps/jsr.io/@std/encoding/1.0.5/hex.js";
|
|
5
5
|
// @ts-ignore TS7016
|
|
6
6
|
import jsonld from "jsonld";
|
|
7
|
-
import {
|
|
7
|
+
import { getDocumentLoader, } from "../runtime/docloader.js";
|
|
8
8
|
import { Activity, CryptographicKey, Object } from "../vocab/vocab.js";
|
|
9
9
|
import { fetchKey, validateCryptoKey } from "./key.js";
|
|
10
10
|
const logger = getLogger(["fedify", "sig", "ld"]);
|
|
@@ -200,7 +200,7 @@ export async function verifyJsonLd(jsonLd, options = {}) {
|
|
|
200
200
|
async function hashJsonLd(jsonLd, contextLoader) {
|
|
201
201
|
const canon = await jsonld.canonize(jsonLd, {
|
|
202
202
|
format: "application/n-quads",
|
|
203
|
-
documentLoader: contextLoader ??
|
|
203
|
+
documentLoader: contextLoader ?? getDocumentLoader(),
|
|
204
204
|
});
|
|
205
205
|
const encoder = new TextEncoder();
|
|
206
206
|
const hash = await dntShim.crypto.subtle.digest("SHA-256", encoder.encode(canon));
|
package/esm/sig/owner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDocumentLoader, } from "../runtime/docloader.js";
|
|
2
2
|
import { isActor } from "../vocab/actor.js";
|
|
3
3
|
import { CryptographicKey, Object as ASObject, } from "../vocab/vocab.js";
|
|
4
4
|
export { exportJwk, generateCryptoKeyPair, importJwk } from "./key.js";
|
|
@@ -32,8 +32,8 @@ export async function doesActorOwnKey(activity, key, options) {
|
|
|
32
32
|
* owner.
|
|
33
33
|
*/
|
|
34
34
|
export async function getKeyOwner(keyId, options) {
|
|
35
|
-
const documentLoader = options.documentLoader ??
|
|
36
|
-
const contextLoader = options.contextLoader ??
|
|
35
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
36
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
37
37
|
let object;
|
|
38
38
|
if (keyId instanceof CryptographicKey) {
|
|
39
39
|
object = keyId;
|
package/esm/vocab/actor.js
CHANGED
|
@@ -67,7 +67,7 @@ export function getActorClassByTypeName(typeName) {
|
|
|
67
67
|
* ```
|
|
68
68
|
*
|
|
69
69
|
* @param actor The actor or actor URI to get the handle from.
|
|
70
|
-
* @param options The options for
|
|
70
|
+
* @param options The extra options for getting the actor handle.
|
|
71
71
|
* @returns The actor handle. It starts with `@` and is followed by the
|
|
72
72
|
* username and domain, separated by `@` by default (it can be
|
|
73
73
|
* customized with the options).
|
|
@@ -78,7 +78,9 @@ export function getActorClassByTypeName(typeName) {
|
|
|
78
78
|
export async function getActorHandle(actor, options = {}) {
|
|
79
79
|
const actorId = actor instanceof URL ? actor : actor.id;
|
|
80
80
|
if (actorId != null) {
|
|
81
|
-
const result = await lookupWebFinger(actorId
|
|
81
|
+
const result = await lookupWebFinger(actorId, {
|
|
82
|
+
userAgent: options.userAgent,
|
|
83
|
+
});
|
|
82
84
|
if (result != null) {
|
|
83
85
|
const aliases = [...(result.aliases ?? [])];
|
|
84
86
|
if (result.subject != null)
|
|
@@ -88,7 +90,7 @@ export async function getActorHandle(actor, options = {}) {
|
|
|
88
90
|
if (match != null) {
|
|
89
91
|
const hostname = new URL(`https://${match[2]}/`).hostname;
|
|
90
92
|
if (hostname !== actorId.hostname &&
|
|
91
|
-
!await verifyCrossOriginActorHandle(actorId.href, alias)) {
|
|
93
|
+
!await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent)) {
|
|
92
94
|
continue;
|
|
93
95
|
}
|
|
94
96
|
return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
|
|
@@ -102,8 +104,8 @@ export async function getActorHandle(actor, options = {}) {
|
|
|
102
104
|
}
|
|
103
105
|
throw new TypeError("Actor does not have enough information to get the handle.");
|
|
104
106
|
}
|
|
105
|
-
async function verifyCrossOriginActorHandle(actorId, alias) {
|
|
106
|
-
const response = await lookupWebFinger(alias);
|
|
107
|
+
async function verifyCrossOriginActorHandle(actorId, alias, userAgent) {
|
|
108
|
+
const response = await lookupWebFinger(alias, { userAgent });
|
|
107
109
|
if (response == null)
|
|
108
110
|
return false;
|
|
109
111
|
for (const alias of response.aliases ?? []) {
|
package/esm/vocab/lookup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
|
2
2
|
import { getLogger } from "@logtape/logtape";
|
|
3
3
|
import { delay } from "../deps/jsr.io/@std/async/1.0.8/delay.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getDocumentLoader, } from "../runtime/docloader.js";
|
|
5
5
|
import { lookupWebFinger } from "../webfinger/lookup.js";
|
|
6
6
|
import { Object } from "./vocab.js";
|
|
7
7
|
const logger = getLogger(["fedify", "vocab", "lookup"]);
|
|
@@ -39,7 +39,8 @@ const handleRegexp = /^@?((?:[-A-Za-z0-9._~!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)@([^@]
|
|
|
39
39
|
* @since 0.2.0
|
|
40
40
|
*/
|
|
41
41
|
export async function lookupObject(identifier, options = {}) {
|
|
42
|
-
const documentLoader = options.documentLoader ??
|
|
42
|
+
const documentLoader = options.documentLoader ??
|
|
43
|
+
getDocumentLoader({ userAgent: options.userAgent });
|
|
43
44
|
if (typeof identifier === "string") {
|
|
44
45
|
const match = handleRegexp.exec(identifier);
|
|
45
46
|
if (match)
|
|
@@ -57,7 +58,9 @@ export async function lookupObject(identifier, options = {}) {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
if (document == null) {
|
|
60
|
-
const jrd = await lookupWebFinger(identifier
|
|
61
|
+
const jrd = await lookupWebFinger(identifier, {
|
|
62
|
+
userAgent: options.userAgent,
|
|
63
|
+
});
|
|
61
64
|
if (jrd?.links == null)
|
|
62
65
|
return null;
|
|
63
66
|
for (const l of jrd.links) {
|