@fedify/fedify 2.4.0-dev.1666 → 2.4.0-dev.1727
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 +1 -1
- package/dist/{assert-CJ1KXcvv.mjs → assert-CebeUf0C.mjs} +1 -1
- package/dist/assert_exists-DtzkXC47.mjs +28 -0
- package/dist/assert_false-3pLLLsO4.mjs +23 -0
- package/dist/assert_greater_or_equal-BApOTh1h.mjs +30 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-g3WcyYNR.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-BN7yDMWe.mjs} +1 -1
- package/dist/assert_rejects-wFbSPn1O.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-Bpjl4Psw.mjs} +1 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BJ5wuvR2.mjs} +1 -1
- package/dist/{builder-BGpBs-US.mjs → builder-f5-YwHfj.mjs} +5 -3
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-BT7CiPbz.mjs} +191 -9
- package/dist/circuit-breaker-test-utils-BYIHoliq.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CtxvmazV.mjs} +63 -5
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +3 -3
- package/dist/compat/public-audience.test.mjs +3 -3
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-C05Rgxmu.d.cts → context-B-dsBjZ9.d.cts} +281 -7
- package/dist/{context-Cjnr0ig1.d.ts → context-DrLXudbA.d.ts} +281 -7
- package/dist/{deno-CaQlEENR.mjs → deno-Deyoxesx.mjs} +1 -1
- package/dist/{docloader-CTm2Lcpn.mjs → docloader-B3kuDBg9.mjs} +2 -2
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/builder.test.mjs +18 -5
- package/dist/federation/circuit-breaker.test.mjs +626 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +14 -13
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +4 -4
- package/dist/federation/middleware.test.mjs +37 -28
- package/dist/federation/mod.cjs +18 -540
- package/dist/federation/mod.d.cts +3 -4
- package/dist/federation/mod.d.ts +3 -4
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +32 -5
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +7 -7
- package/dist/federation/send.test.mjs +28 -26
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +4 -4
- package/dist/federation/tasks/tasks.test.mjs +6 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +74 -12
- package/dist/{middleware-Dnzqy4sk.cjs → federation-BzRjeOky.cjs} +855 -114
- package/dist/{temporal-polyfill-BCdTHexd.cjs → http-AyKngJ_h.cjs} +2801 -104
- package/dist/{http-BX5xiNUh.js → http-Bw6s_dOB.js} +3 -3
- package/dist/{http-BaSuRiWJ.mjs → http-TjhZfiSN.mjs} +5 -5
- package/dist/{key-BvJYxJoY.mjs → key-b4kf4yZT.mjs} +2 -2
- package/dist/{kv-cache-BPapnm-V.cjs → kv-cache-BTVaZrf-.cjs} +2 -4
- package/dist/{kv-cache-C04NtL2n.js → kv-cache-CQsh-KUw.js} +1 -1
- package/dist/{kv-cache-B9fTkVpc.mjs → kv-cache-CpILvhUV.mjs} +1 -1
- package/dist/{ld-DU12IAt8.mjs → ld-CKujkAfG.mjs} +5 -5
- package/dist/{metrics-BYUxSscw.mjs → metrics-AlCGTDrr.mjs} +2 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-BEd40QBF.mjs → middleware-C9Y5lLZN.mjs} +60 -43
- package/dist/{middleware-_VQWtF1B.js → middleware-CZcuedl-.js} +241 -40
- package/dist/{middleware-C4oS_tdq.mjs → middleware-LPbZz6-1.mjs} +1 -1
- package/dist/{mod-BrpVsSfY.d.cts → mod-CLHkCwLm.d.cts} +3 -3
- package/dist/{mod-B0hW12_O.d.cts → mod-CuDDaIIt.d.cts} +1 -1
- package/dist/{mod-COIAjwRS.d.ts → mod-D-IhOEOP.d.ts} +1 -1
- package/dist/{mod-DdONo-ep.d.cts → mod-D-abvu2C.d.cts} +1 -1
- package/dist/{mod-BsCE5Ihq.d.ts → mod-D6O8C7eN.d.ts} +1 -1
- package/dist/{mod-CNef4in0.d.ts → mod-DypxPS9H.d.ts} +3 -3
- package/dist/mod.cjs +24 -25
- package/dist/mod.d.cts +8 -9
- package/dist/mod.d.ts +8 -9
- package/dist/mod.js +7 -7
- package/dist/nodeinfo/client.test.mjs +64 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-BQSHRLhg.mjs} +2 -2
- package/dist/{owner-DFF9ltAs.mjs → owner-D1O8v8FB.mjs} +2 -2
- package/dist/{proof-rOoEb_P4.js → proof-BTBolSnd.js} +6 -6
- package/dist/{proof-DcbeTQMG.cjs → proof-DcSm-2eG.cjs} +10 -11
- package/dist/{proof-DEV-fO9I.mjs → proof-DeMR7Akd.mjs} +6 -6
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--9hx7IGx.mjs} +1 -1
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/{send-BVkabUz0.mjs → send-DG7JG280.mjs} +3 -3
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +37 -11
- package/dist/sig/key.test.mjs +6 -6
- package/dist/sig/ld.test.mjs +9 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +10 -8
- package/dist/std__assert-CxFT0MNt.mjs +8 -0
- package/dist/{temporal-X0BllGgC.mjs → temporal-DA3jx8x1.mjs} +3 -3
- package/dist/testing/mod.d.mts +10 -0
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-CO7ftO8A.mjs → testing-BX0Nicyq.mjs} +2 -2
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-Dtyf8lVj.js} +63 -5
- package/dist/{types-CPym83zc.cjs → types-RXm5YxpC.cjs} +62 -5
- package/dist/utils/docloader.test.mjs +7 -7
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/skills/fedify/SKILL.md +462 -0
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/http-BIJdA1mF.cjs +0 -2694
- package/dist/metrics-DN13mCAw.d.cts +0 -294
- package/dist/metrics-v_e0n5GQ.d.ts +0 -294
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-CBlRfA-_.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{codec-BBga-FWI.mjs → codec-peqOdBaE.mjs} +0 -0
- /package/dist/{context-BJXxPElm.mjs → context-Dt36KFEJ.mjs} +0 -0
- /package/dist/{http-VyDTd4G3.d.cts → http-6TI0tI03.d.cts} +0 -0
- /package/dist/{http-lf8Hsd91.d.ts → http-CdrSn2ip.d.ts} +0 -0
- /package/dist/{keycache-_DHqz5z_.mjs → keycache-0Jk78XmB.mjs} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-D6hNiMTK.d.ts → kv-BhNbN8Ed.d.cts} +0 -0
- /package/dist/{kv-gJ8LYbxX.d.cts → kv-BhNbN8Ed.d.ts} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{mq-Bo7Etgh6.mjs → mq-Dm1Wmtm-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{owner-CnngXDNJ.d.ts → owner-DGSfntI-.d.ts} +0 -0
- /package/dist/{owner-DEvZuyOE.d.cts → owner-r_waTiQj.d.cts} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
package/dist/http-BIJdA1mF.cjs
DELETED
|
@@ -1,2694 +0,0 @@
|
|
|
1
|
-
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
require("./chunk-Czpeue4M.cjs");
|
|
3
|
-
const require_temporal_polyfill = require("./temporal-polyfill-BCdTHexd.cjs");
|
|
4
|
-
let _logtape_logtape = require("@logtape/logtape");
|
|
5
|
-
let _fedify_vocab = require("@fedify/vocab");
|
|
6
|
-
let _opentelemetry_api = require("@opentelemetry/api");
|
|
7
|
-
let byte_encodings_hex = require("byte-encodings/hex");
|
|
8
|
-
let structured_field_values = require("structured-field-values");
|
|
9
|
-
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
10
|
-
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
11
|
-
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
12
|
-
//#region deno.json
|
|
13
|
-
var name = "@fedify/fedify";
|
|
14
|
-
var version = "2.4.0-dev.1666+690d9b28";
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/sig/accept.ts
|
|
17
|
-
/**
|
|
18
|
-
* `Accept-Signature` header parsing, serialization, and validation utilities
|
|
19
|
-
* for RFC 9421 §5 challenge-response negotiation.
|
|
20
|
-
*
|
|
21
|
-
* @module
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Parses an `Accept-Signature` header value (RFC 9421 §5.1) into an
|
|
25
|
-
* array of {@link AcceptSignatureMember} objects.
|
|
26
|
-
*
|
|
27
|
-
* The `Accept-Signature` field is a Dictionary Structured Field
|
|
28
|
-
* (RFC 8941 §3.2). Each dictionary member describes a single
|
|
29
|
-
* requested message signature.
|
|
30
|
-
*
|
|
31
|
-
* On parse failure (malformed or empty header), returns an empty array.
|
|
32
|
-
*
|
|
33
|
-
* @param header The raw `Accept-Signature` header value string.
|
|
34
|
-
* @returns An array of parsed members. Empty if the header is
|
|
35
|
-
* malformed or empty.
|
|
36
|
-
* @since 2.1.0
|
|
37
|
-
*/
|
|
38
|
-
function parseAcceptSignature(header) {
|
|
39
|
-
try {
|
|
40
|
-
return parseEachSignature((0, structured_field_values.decodeDict)(header));
|
|
41
|
-
} catch {
|
|
42
|
-
(0, _logtape_logtape.getLogger)([
|
|
43
|
-
"fedify",
|
|
44
|
-
"sig",
|
|
45
|
-
"http"
|
|
46
|
-
]).warn("Failed to parse Accept-Signature header: {header}", { header });
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const compactObject = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== void 0));
|
|
51
|
-
const parseEachSignature = (dict) => Object.entries(dict).filter(([_, item]) => Array.isArray(item.value)).map(([label, item]) => ({
|
|
52
|
-
label,
|
|
53
|
-
components: item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
54
|
-
value: subitem.value,
|
|
55
|
-
params: subitem.params ?? {}
|
|
56
|
-
})),
|
|
57
|
-
parameters: compactParams(item)
|
|
58
|
-
}));
|
|
59
|
-
const compactParams = (item) => {
|
|
60
|
-
const { keyid, alg, created, expires, nonce, tag } = item.params ?? {};
|
|
61
|
-
return compactObject({
|
|
62
|
-
keyid: stringOrUndefined(keyid),
|
|
63
|
-
alg: stringOrUndefined(alg),
|
|
64
|
-
created: trueOrUndefined(created),
|
|
65
|
-
expires: trueOrUndefined(expires),
|
|
66
|
-
nonce: stringOrUndefined(nonce),
|
|
67
|
-
tag: stringOrUndefined(tag)
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
const stringOrUndefined = (v) => typeof v === "string" ? v : void 0;
|
|
71
|
-
const trueOrUndefined = (v) => v === true ? true : void 0;
|
|
72
|
-
/**
|
|
73
|
-
* Serializes an array of {@link AcceptSignatureMember} objects into an
|
|
74
|
-
* `Accept-Signature` header value string (RFC 9421 §5.1).
|
|
75
|
-
*
|
|
76
|
-
* The output is a Dictionary Structured Field (RFC 8941 §3.2).
|
|
77
|
-
*
|
|
78
|
-
* @param members The members to serialize.
|
|
79
|
-
* @returns The serialized header value string.
|
|
80
|
-
* @since 2.1.0
|
|
81
|
-
*/
|
|
82
|
-
function formatAcceptSignature(members) {
|
|
83
|
-
const items = members.map((member) => [member.label, new structured_field_values.Item(compToItems(member), compactParameters(member))]);
|
|
84
|
-
return (0, structured_field_values.encodeDict)(Object.fromEntries(items));
|
|
85
|
-
}
|
|
86
|
-
const compToItems = (member) => member.components.map((c) => new structured_field_values.Item(c.value, c.params));
|
|
87
|
-
const compactParameters = (member) => {
|
|
88
|
-
const { keyid, alg, created, expires, nonce, tag } = member.parameters;
|
|
89
|
-
return compactObject({
|
|
90
|
-
keyid,
|
|
91
|
-
alg,
|
|
92
|
-
created,
|
|
93
|
-
expires,
|
|
94
|
-
nonce,
|
|
95
|
-
tag
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* Filters out {@link AcceptSignatureMember} entries whose covered
|
|
100
|
-
* components include response-only identifiers (`@status`) that are
|
|
101
|
-
* not applicable to request-target messages, as required by
|
|
102
|
-
* [RFC 9421 §5](https://www.rfc-editor.org/rfc/rfc9421#section-5).
|
|
103
|
-
*
|
|
104
|
-
* A warning is logged for each discarded entry.
|
|
105
|
-
*
|
|
106
|
-
* @param members The parsed `Accept-Signature` entries to validate.
|
|
107
|
-
* @returns Only entries that are valid for request-target messages.
|
|
108
|
-
* @since 2.1.0
|
|
109
|
-
*/
|
|
110
|
-
function validateAcceptSignature(members) {
|
|
111
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
112
|
-
"fedify",
|
|
113
|
-
"sig",
|
|
114
|
-
"http"
|
|
115
|
-
]);
|
|
116
|
-
return members.filter((member) => {
|
|
117
|
-
if (member.components.every((c) => c.value !== "@status")) return true;
|
|
118
|
-
logLabel(logger, member.label);
|
|
119
|
-
return false;
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
const logLabel = (logger, label) => logger.warn("Discarding Accept-Signature member {label}: covered components include response-only identifier @status.", { label });
|
|
123
|
-
/**
|
|
124
|
-
* Attempts to translate an {@link AcceptSignatureMember} challenge into
|
|
125
|
-
* RFC 9421 signing options that the local signer can fulfill.
|
|
126
|
-
*
|
|
127
|
-
* Returns `null` if the challenge cannot be fulfilled—for example, if
|
|
128
|
-
* the requested `alg` or `keyid` is incompatible with the local key.
|
|
129
|
-
*
|
|
130
|
-
* Safety constraints:
|
|
131
|
-
* - `alg`: only honored if it matches `localAlg`.
|
|
132
|
-
* - `keyid`: only honored if it matches `localKeyId`.
|
|
133
|
-
* - `components`: passed through exactly as requested, per RFC 9421 §5.2.
|
|
134
|
-
* - `nonce`, `tag`, and `expires` are passed through directly.
|
|
135
|
-
*
|
|
136
|
-
* @param entry The challenge entry from the `Accept-Signature` header.
|
|
137
|
-
* @param localKeyId The local key identifier (e.g., the actor key URL).
|
|
138
|
-
* @param localAlg The algorithm of the local private key
|
|
139
|
-
* (e.g., `"rsa-v1_5-sha256"`).
|
|
140
|
-
* @returns Signing options if the challenge can be fulfilled, or `null`.
|
|
141
|
-
* @since 2.1.0
|
|
142
|
-
*/
|
|
143
|
-
function fulfillAcceptSignature(entry, localKeyId, localAlg) {
|
|
144
|
-
if (entry.parameters.alg != null && entry.parameters.alg !== localAlg) return null;
|
|
145
|
-
if (entry.parameters.keyid != null && entry.parameters.keyid !== localKeyId) return null;
|
|
146
|
-
return {
|
|
147
|
-
label: entry.label,
|
|
148
|
-
components: entry.components,
|
|
149
|
-
nonce: entry.parameters.nonce,
|
|
150
|
-
tag: entry.parameters.tag,
|
|
151
|
-
expires: entry.parameters.expires
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
//#endregion
|
|
155
|
-
//#region src/federation/metrics.ts
|
|
156
|
-
var FederationMetrics = class {
|
|
157
|
-
deliverySent;
|
|
158
|
-
deliveryPermanentFailure;
|
|
159
|
-
signatureVerificationFailure;
|
|
160
|
-
signatureVerificationDuration;
|
|
161
|
-
signatureKeyFetchDuration;
|
|
162
|
-
deliveryDuration;
|
|
163
|
-
inboxProcessingDuration;
|
|
164
|
-
httpServerRequestCount;
|
|
165
|
-
httpServerRequestDuration;
|
|
166
|
-
queueTaskEnqueued;
|
|
167
|
-
queueTaskStarted;
|
|
168
|
-
queueTaskCompleted;
|
|
169
|
-
queueTaskFailed;
|
|
170
|
-
queueTaskDuration;
|
|
171
|
-
queueTaskInFlight;
|
|
172
|
-
queueDepth;
|
|
173
|
-
fanoutRecipients;
|
|
174
|
-
inboxActivity;
|
|
175
|
-
outboxActivity;
|
|
176
|
-
circuitBreakerStateChange;
|
|
177
|
-
keyLookup;
|
|
178
|
-
keyLookupDuration;
|
|
179
|
-
documentFetch;
|
|
180
|
-
documentFetchDuration;
|
|
181
|
-
documentCache;
|
|
182
|
-
webFingerHandle;
|
|
183
|
-
webFingerHandleDuration;
|
|
184
|
-
collectionRequest;
|
|
185
|
-
collectionDispatchDuration;
|
|
186
|
-
collectionPageItems;
|
|
187
|
-
collectionTotalItems;
|
|
188
|
-
constructor(meterProvider) {
|
|
189
|
-
const meter = meterProvider.getMeter(name, version);
|
|
190
|
-
this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
|
|
191
|
-
description: "ActivityPub delivery attempts.",
|
|
192
|
-
unit: "{attempt}"
|
|
193
|
-
});
|
|
194
|
-
this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
|
|
195
|
-
description: "ActivityPub deliveries abandoned as permanent failures.",
|
|
196
|
-
unit: "{failure}"
|
|
197
|
-
});
|
|
198
|
-
this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
|
|
199
|
-
description: "ActivityPub signature verification failures.",
|
|
200
|
-
unit: "{failure}"
|
|
201
|
-
});
|
|
202
|
-
this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
|
|
203
|
-
description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
|
|
204
|
-
unit: "ms",
|
|
205
|
-
advice: { explicitBucketBoundaries: [
|
|
206
|
-
.1,
|
|
207
|
-
.25,
|
|
208
|
-
.5,
|
|
209
|
-
1,
|
|
210
|
-
2.5,
|
|
211
|
-
5,
|
|
212
|
-
10,
|
|
213
|
-
25,
|
|
214
|
-
50,
|
|
215
|
-
100,
|
|
216
|
-
250,
|
|
217
|
-
500,
|
|
218
|
-
1e3
|
|
219
|
-
] }
|
|
220
|
-
});
|
|
221
|
-
this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
|
|
222
|
-
description: "Duration of public key lookup performed during ActivityPub signature verification.",
|
|
223
|
-
unit: "ms"
|
|
224
|
-
});
|
|
225
|
-
this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
|
|
226
|
-
description: "Duration of ActivityPub delivery attempts.",
|
|
227
|
-
unit: "ms"
|
|
228
|
-
});
|
|
229
|
-
this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
|
|
230
|
-
description: "Duration of ActivityPub inbox listener processing.",
|
|
231
|
-
unit: "ms"
|
|
232
|
-
});
|
|
233
|
-
this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
|
|
234
|
-
description: "HTTP requests handled by Federation.fetch().",
|
|
235
|
-
unit: "{request}"
|
|
236
|
-
});
|
|
237
|
-
this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
|
|
238
|
-
description: "Duration of HTTP requests handled by Federation.fetch().",
|
|
239
|
-
unit: "ms",
|
|
240
|
-
advice: { explicitBucketBoundaries: [
|
|
241
|
-
5,
|
|
242
|
-
10,
|
|
243
|
-
25,
|
|
244
|
-
50,
|
|
245
|
-
75,
|
|
246
|
-
100,
|
|
247
|
-
250,
|
|
248
|
-
500,
|
|
249
|
-
750,
|
|
250
|
-
1e3,
|
|
251
|
-
2500,
|
|
252
|
-
5e3,
|
|
253
|
-
7500,
|
|
254
|
-
1e4
|
|
255
|
-
] }
|
|
256
|
-
});
|
|
257
|
-
this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
|
|
258
|
-
description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
|
|
259
|
-
unit: "{task}"
|
|
260
|
-
});
|
|
261
|
-
this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
|
|
262
|
-
description: "Tasks Fedify began processing as a queue worker.",
|
|
263
|
-
unit: "{task}"
|
|
264
|
-
});
|
|
265
|
-
this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
|
|
266
|
-
description: "Queue tasks Fedify finished processing without throwing.",
|
|
267
|
-
unit: "{task}"
|
|
268
|
-
});
|
|
269
|
-
this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
|
|
270
|
-
description: "Queue tasks Fedify abandoned because processing threw.",
|
|
271
|
-
unit: "{task}"
|
|
272
|
-
});
|
|
273
|
-
this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
|
|
274
|
-
description: "Duration of queue task processing in Fedify workers.",
|
|
275
|
-
unit: "ms",
|
|
276
|
-
advice: { explicitBucketBoundaries: [
|
|
277
|
-
5,
|
|
278
|
-
10,
|
|
279
|
-
25,
|
|
280
|
-
50,
|
|
281
|
-
75,
|
|
282
|
-
100,
|
|
283
|
-
250,
|
|
284
|
-
500,
|
|
285
|
-
750,
|
|
286
|
-
1e3,
|
|
287
|
-
2500,
|
|
288
|
-
5e3,
|
|
289
|
-
7500,
|
|
290
|
-
1e4
|
|
291
|
-
] }
|
|
292
|
-
});
|
|
293
|
-
this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
|
|
294
|
-
description: "Queue tasks currently being processed in this Fedify process.",
|
|
295
|
-
unit: "{task}"
|
|
296
|
-
});
|
|
297
|
-
this.queueDepth = meter.createObservableGauge("fedify.queue.depth", {
|
|
298
|
-
description: "Messages waiting in configured Fedify queues, as reported by the queue backend.",
|
|
299
|
-
unit: "{message}"
|
|
300
|
-
});
|
|
301
|
-
this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
|
|
302
|
-
description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
|
|
303
|
-
unit: "{recipient}"
|
|
304
|
-
});
|
|
305
|
-
this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
|
|
306
|
-
description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
|
|
307
|
-
unit: "{activity}"
|
|
308
|
-
});
|
|
309
|
-
this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
|
|
310
|
-
description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
|
|
311
|
-
unit: "{activity}"
|
|
312
|
-
});
|
|
313
|
-
this.circuitBreakerStateChange = meter.createCounter("activitypub.circuit_breaker.state_change", {
|
|
314
|
-
description: "Outbound ActivityPub delivery circuit breaker changes.",
|
|
315
|
-
unit: "{change}"
|
|
316
|
-
});
|
|
317
|
-
this.keyLookup = meter.createCounter("activitypub.key.lookup", {
|
|
318
|
-
description: "Public-key lookup attempts performed by Fedify, including cache hits, local resolutions, and remote fetches.",
|
|
319
|
-
unit: "{lookup}"
|
|
320
|
-
});
|
|
321
|
-
this.keyLookupDuration = meter.createHistogram("activitypub.key.lookup.duration", {
|
|
322
|
-
description: "Duration of public-key lookups performed by Fedify, including cache hits, local resolutions, and any remote fetch.",
|
|
323
|
-
unit: "ms",
|
|
324
|
-
advice: { explicitBucketBoundaries: [
|
|
325
|
-
5,
|
|
326
|
-
10,
|
|
327
|
-
25,
|
|
328
|
-
50,
|
|
329
|
-
75,
|
|
330
|
-
100,
|
|
331
|
-
250,
|
|
332
|
-
500,
|
|
333
|
-
750,
|
|
334
|
-
1e3,
|
|
335
|
-
2500,
|
|
336
|
-
5e3,
|
|
337
|
-
7500,
|
|
338
|
-
1e4
|
|
339
|
-
] }
|
|
340
|
-
});
|
|
341
|
-
this.documentFetch = meter.createCounter("activitypub.document.fetch", {
|
|
342
|
-
description: "Remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
343
|
-
unit: "{fetch}"
|
|
344
|
-
});
|
|
345
|
-
this.documentFetchDuration = meter.createHistogram("activitypub.document.fetch.duration", {
|
|
346
|
-
description: "Duration of remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
347
|
-
unit: "ms",
|
|
348
|
-
advice: { explicitBucketBoundaries: [
|
|
349
|
-
5,
|
|
350
|
-
10,
|
|
351
|
-
25,
|
|
352
|
-
50,
|
|
353
|
-
75,
|
|
354
|
-
100,
|
|
355
|
-
250,
|
|
356
|
-
500,
|
|
357
|
-
750,
|
|
358
|
-
1e3,
|
|
359
|
-
2500,
|
|
360
|
-
5e3,
|
|
361
|
-
7500,
|
|
362
|
-
1e4
|
|
363
|
-
] }
|
|
364
|
-
});
|
|
365
|
-
this.documentCache = meter.createCounter("activitypub.document.cache", {
|
|
366
|
-
description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
|
|
367
|
-
unit: "{lookup}"
|
|
368
|
-
});
|
|
369
|
-
this.webFingerHandle = meter.createCounter("webfinger.handle", {
|
|
370
|
-
description: "Incoming WebFinger requests handled by Fedify, classified by terminal outcome.",
|
|
371
|
-
unit: "{request}"
|
|
372
|
-
});
|
|
373
|
-
this.webFingerHandleDuration = meter.createHistogram("webfinger.handle.duration", {
|
|
374
|
-
description: "Duration of incoming WebFinger request handling in Fedify.",
|
|
375
|
-
unit: "ms",
|
|
376
|
-
advice: { explicitBucketBoundaries: [
|
|
377
|
-
5,
|
|
378
|
-
10,
|
|
379
|
-
25,
|
|
380
|
-
50,
|
|
381
|
-
75,
|
|
382
|
-
100,
|
|
383
|
-
250,
|
|
384
|
-
500,
|
|
385
|
-
750,
|
|
386
|
-
1e3,
|
|
387
|
-
2500,
|
|
388
|
-
5e3,
|
|
389
|
-
7500,
|
|
390
|
-
1e4
|
|
391
|
-
] }
|
|
392
|
-
});
|
|
393
|
-
this.collectionRequest = meter.createCounter("activitypub.collection.request", {
|
|
394
|
-
description: "ActivityPub collection and collection-page requests handled by Fedify.",
|
|
395
|
-
unit: "{request}"
|
|
396
|
-
});
|
|
397
|
-
this.collectionDispatchDuration = meter.createHistogram("activitypub.collection.dispatch.duration", {
|
|
398
|
-
description: "Duration of ActivityPub collection dispatcher callbacks.",
|
|
399
|
-
unit: "ms",
|
|
400
|
-
advice: { explicitBucketBoundaries: [
|
|
401
|
-
5,
|
|
402
|
-
10,
|
|
403
|
-
25,
|
|
404
|
-
50,
|
|
405
|
-
75,
|
|
406
|
-
100,
|
|
407
|
-
250,
|
|
408
|
-
500,
|
|
409
|
-
750,
|
|
410
|
-
1e3,
|
|
411
|
-
2500,
|
|
412
|
-
5e3,
|
|
413
|
-
7500,
|
|
414
|
-
1e4
|
|
415
|
-
] }
|
|
416
|
-
});
|
|
417
|
-
this.collectionPageItems = meter.createHistogram("activitypub.collection.page.items", {
|
|
418
|
-
description: "Number of items Fedify materialized for an ActivityPub collection response.",
|
|
419
|
-
unit: "{item}"
|
|
420
|
-
});
|
|
421
|
-
this.collectionTotalItems = meter.createHistogram("activitypub.collection.total_items", {
|
|
422
|
-
description: "Total item count reported by ActivityPub collection counters.",
|
|
423
|
-
unit: "{item}"
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
recordDelivery(inbox, durationMs, success, activityType) {
|
|
427
|
-
const deliveryAttributes = {
|
|
428
|
-
"activitypub.remote.host": getRemoteHost(inbox),
|
|
429
|
-
"activitypub.delivery.success": success
|
|
430
|
-
};
|
|
431
|
-
if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
|
|
432
|
-
this.deliverySent.add(1, deliveryAttributes);
|
|
433
|
-
this.deliveryDuration.record(durationMs, deliveryAttributes);
|
|
434
|
-
}
|
|
435
|
-
recordPermanentFailure(inbox, statusCode) {
|
|
436
|
-
this.deliveryPermanentFailure.add(1, {
|
|
437
|
-
"activitypub.remote.host": getRemoteHost(inbox),
|
|
438
|
-
"http.response.status_code": statusCode
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
recordSignatureVerificationFailure(reason, remoteHost) {
|
|
442
|
-
const attributes = { "activitypub.verification.failure_reason": reason };
|
|
443
|
-
if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
|
|
444
|
-
this.signatureVerificationFailure.add(1, attributes);
|
|
445
|
-
}
|
|
446
|
-
recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
|
|
447
|
-
const attributes = {
|
|
448
|
-
"activitypub.signature.kind": kind,
|
|
449
|
-
"activitypub.signature.result": result
|
|
450
|
-
};
|
|
451
|
-
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
452
|
-
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
453
|
-
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
454
|
-
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
455
|
-
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
456
|
-
}
|
|
457
|
-
recordSignatureKeyFetchDuration(durationMs, kind, result) {
|
|
458
|
-
this.signatureKeyFetchDuration.record(durationMs, {
|
|
459
|
-
"activitypub.signature.kind": kind,
|
|
460
|
-
"activitypub.signature.key_fetch.result": result
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
recordInboxProcessingDuration(activityType, durationMs) {
|
|
464
|
-
this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
|
|
465
|
-
}
|
|
466
|
-
recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
|
|
467
|
-
const attributes = {
|
|
468
|
-
"http.request.method": normalizeHttpMethod(method),
|
|
469
|
-
"fedify.endpoint": endpoint
|
|
470
|
-
};
|
|
471
|
-
if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
|
|
472
|
-
if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
|
|
473
|
-
this.httpServerRequestCount.add(1, attributes);
|
|
474
|
-
this.httpServerRequestDuration.record(durationMs, attributes);
|
|
475
|
-
}
|
|
476
|
-
recordQueueTaskEnqueued(common, attempt, count = 1) {
|
|
477
|
-
const attributes = buildQueueTaskAttributes(common);
|
|
478
|
-
attributes["fedify.queue.task.attempt"] = attempt;
|
|
479
|
-
this.queueTaskEnqueued.add(count, attributes);
|
|
480
|
-
}
|
|
481
|
-
recordQueueTaskStarted(common) {
|
|
482
|
-
this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
|
|
483
|
-
}
|
|
484
|
-
incrementQueueTaskInFlight(common) {
|
|
485
|
-
this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
|
|
486
|
-
}
|
|
487
|
-
decrementQueueTaskInFlight(common) {
|
|
488
|
-
this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
|
|
489
|
-
}
|
|
490
|
-
recordQueueTaskOutcome(common, result, durationMs, failureReason) {
|
|
491
|
-
const attributes = buildQueueTaskAttributes(common);
|
|
492
|
-
attributes["fedify.queue.task.result"] = result;
|
|
493
|
-
if (failureReason != null && result === "failed") attributes["fedify.task.failure_reason"] = failureReason;
|
|
494
|
-
if (result === "completed") this.queueTaskCompleted.add(1, attributes);
|
|
495
|
-
else if (result === "failed") this.queueTaskFailed.add(1, attributes);
|
|
496
|
-
this.queueTaskDuration.record(durationMs, attributes);
|
|
497
|
-
}
|
|
498
|
-
recordFanoutRecipients(recipientCount, activityType) {
|
|
499
|
-
const attributes = {};
|
|
500
|
-
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
501
|
-
this.fanoutRecipients.record(recipientCount, attributes);
|
|
502
|
-
}
|
|
503
|
-
recordInboxActivity(result, activityType) {
|
|
504
|
-
this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
505
|
-
}
|
|
506
|
-
recordOutboxActivity(result, activityType) {
|
|
507
|
-
this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
508
|
-
}
|
|
509
|
-
recordCircuitBreakerStateChange(remoteHost, state) {
|
|
510
|
-
this.circuitBreakerStateChange.add(1, {
|
|
511
|
-
"activitypub.remote.host": remoteHost,
|
|
512
|
-
"activitypub.circuit_breaker.state": state
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
recordKeyLookup(attrs) {
|
|
516
|
-
const attributes = {
|
|
517
|
-
"activitypub.lookup.kind": "public_key",
|
|
518
|
-
"activitypub.lookup.result": attrs.result,
|
|
519
|
-
"activitypub.cache.enabled": attrs.cacheEnabled
|
|
520
|
-
};
|
|
521
|
-
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
522
|
-
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
523
|
-
this.keyLookup.add(1, attributes);
|
|
524
|
-
this.keyLookupDuration.record(attrs.durationMs, attributes);
|
|
525
|
-
}
|
|
526
|
-
recordDocumentFetch(attrs) {
|
|
527
|
-
const attributes = {
|
|
528
|
-
"activitypub.lookup.kind": attrs.kind,
|
|
529
|
-
"activitypub.lookup.result": attrs.result
|
|
530
|
-
};
|
|
531
|
-
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
532
|
-
if (attrs.cacheEnabled != null) attributes["activitypub.cache.enabled"] = attrs.cacheEnabled;
|
|
533
|
-
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
534
|
-
this.documentFetch.add(1, attributes);
|
|
535
|
-
this.documentFetchDuration.record(attrs.durationMs, attributes);
|
|
536
|
-
}
|
|
537
|
-
recordDocumentCache(attrs) {
|
|
538
|
-
const attributes = {
|
|
539
|
-
"activitypub.lookup.kind": attrs.kind,
|
|
540
|
-
"activitypub.lookup.result": attrs.result
|
|
541
|
-
};
|
|
542
|
-
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
543
|
-
this.documentCache.add(1, attributes);
|
|
544
|
-
}
|
|
545
|
-
recordWebFingerHandle(attrs) {
|
|
546
|
-
const attributes = { "webfinger.handle.result": attrs.result };
|
|
547
|
-
if (attrs.scheme != null) attributes["webfinger.resource.scheme"] = attrs.scheme;
|
|
548
|
-
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
549
|
-
this.webFingerHandle.add(1, attributes);
|
|
550
|
-
this.webFingerHandleDuration.record(attrs.durationMs, attributes);
|
|
551
|
-
}
|
|
552
|
-
recordCollectionRequest(attrs) {
|
|
553
|
-
this.collectionRequest.add(1, buildCollectionAttributes(attrs));
|
|
554
|
-
}
|
|
555
|
-
recordCollectionDispatchDuration(durationMs, attrs) {
|
|
556
|
-
this.collectionDispatchDuration.record(durationMs, buildCollectionAttributes(attrs));
|
|
557
|
-
}
|
|
558
|
-
recordCollectionPageItems(itemCount, attrs) {
|
|
559
|
-
this.collectionPageItems.record(itemCount, buildCollectionAttributes(attrs));
|
|
560
|
-
}
|
|
561
|
-
recordCollectionTotalItems(totalItems, attrs) {
|
|
562
|
-
this.collectionTotalItems.record(totalItems, buildCollectionAttributes(attrs));
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
function buildCollectionAttributes(attrs) {
|
|
566
|
-
const attributes = {
|
|
567
|
-
"activitypub.collection.kind": attrs.kind,
|
|
568
|
-
"activitypub.collection.page": attrs.page,
|
|
569
|
-
"activitypub.collection.result": attrs.result,
|
|
570
|
-
"fedify.collection.dispatcher": attrs.dispatcher
|
|
571
|
-
};
|
|
572
|
-
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
573
|
-
return attributes;
|
|
574
|
-
}
|
|
575
|
-
function buildActivityLifecycleAttributes(result, activityType) {
|
|
576
|
-
const attributes = { "activitypub.processing.result": result };
|
|
577
|
-
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
578
|
-
return attributes;
|
|
579
|
-
}
|
|
580
|
-
function buildQueueTaskAttributes(common) {
|
|
581
|
-
const attributes = { "fedify.queue.role": common.role };
|
|
582
|
-
const backend = getQueueBackend(common.queue);
|
|
583
|
-
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
584
|
-
const nativeRetrial = common.queue?.nativeRetrial;
|
|
585
|
-
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
586
|
-
if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
|
|
587
|
-
if (common.taskName != null) attributes["fedify.task.name"] = common.taskName;
|
|
588
|
-
return attributes;
|
|
589
|
-
}
|
|
590
|
-
function buildQueueTaskInFlightAttributes(common) {
|
|
591
|
-
return buildQueueTaskAttributes({
|
|
592
|
-
role: common.role,
|
|
593
|
-
queue: common.queue
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Returns the constructor name of the given message queue, when it is a
|
|
598
|
-
* meaningful identifier. Used as a best-effort `fedify.queue.backend`
|
|
599
|
-
* attribute on queue task metrics; returns `undefined` for plain object
|
|
600
|
-
* literals (whose constructor is `Object`) so the attribute does not appear
|
|
601
|
-
* with a non-informative value.
|
|
602
|
-
* @since 2.3.0
|
|
603
|
-
*/
|
|
604
|
-
function getQueueBackend(queue) {
|
|
605
|
-
const name = queue?.constructor?.name;
|
|
606
|
-
if (name == null || name === "" || name === "Object") return void 0;
|
|
607
|
-
return name;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Registers a callback for observing queue backend depth.
|
|
611
|
-
* @since 2.3.0
|
|
612
|
-
*/
|
|
613
|
-
function registerQueueDepthGauge(meterProvider, entries, options = {}) {
|
|
614
|
-
const uniqueQueues = /* @__PURE__ */ new Map();
|
|
615
|
-
for (const { role, queue } of entries) {
|
|
616
|
-
if (queue?.getDepth == null) continue;
|
|
617
|
-
const roles = uniqueQueues.get(queue);
|
|
618
|
-
if (roles == null) uniqueQueues.set(queue, [role]);
|
|
619
|
-
else if (!roles.includes(role)) roles.push(role);
|
|
620
|
-
}
|
|
621
|
-
if (uniqueQueues.size < 1) return;
|
|
622
|
-
const queueEntries = Array.from(uniqueQueues.entries());
|
|
623
|
-
getFederationMetrics(meterProvider).queueDepth.addCallback(async (observableResult) => {
|
|
624
|
-
await Promise.all(queueEntries.map(async ([queue, roles]) => {
|
|
625
|
-
let depth;
|
|
626
|
-
try {
|
|
627
|
-
depth = await queue.getDepth();
|
|
628
|
-
} catch {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (depth == null) return;
|
|
632
|
-
const attributes = buildQueueDepthAttributes(queue, roles, options);
|
|
633
|
-
observableResult.observe(depth.queued, {
|
|
634
|
-
...attributes,
|
|
635
|
-
"fedify.queue.depth.state": "queued"
|
|
636
|
-
});
|
|
637
|
-
if (depth.ready != null) observableResult.observe(depth.ready, {
|
|
638
|
-
...attributes,
|
|
639
|
-
"fedify.queue.depth.state": "ready"
|
|
640
|
-
});
|
|
641
|
-
if (depth.delayed != null) observableResult.observe(depth.delayed, {
|
|
642
|
-
...attributes,
|
|
643
|
-
"fedify.queue.depth.state": "delayed"
|
|
644
|
-
});
|
|
645
|
-
}));
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
function buildQueueDepthAttributes(queue, roles, options) {
|
|
649
|
-
const sortedRoles = roles.toSorted();
|
|
650
|
-
const role = sortedRoles.length === 1 ? sortedRoles[0] : "shared";
|
|
651
|
-
const attributes = { "fedify.queue.role": role };
|
|
652
|
-
if (options.sourceId != null) attributes["fedify.federation.instance_id"] = options.sourceId;
|
|
653
|
-
if (role === "shared") attributes["fedify.queue.roles"] = sortedRoles.join(",");
|
|
654
|
-
const backend = getQueueBackend(queue);
|
|
655
|
-
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
656
|
-
const nativeRetrial = queue.nativeRetrial;
|
|
657
|
-
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
658
|
-
return attributes;
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
|
|
662
|
-
* for the initial attempt, also records
|
|
663
|
-
* `activitypub.outbox.activity{queued}`.
|
|
664
|
-
*
|
|
665
|
-
* Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
|
|
666
|
-
* outbox messages with the same metric attributes (role, queue, activity
|
|
667
|
-
* type, attempt), so they share this helper rather than each defining a local
|
|
668
|
-
* closure. Retry enqueues (attempt > 0) intentionally do not record a
|
|
669
|
-
* second `activitypub.outbox.activity{queued}`; retries are reported as
|
|
670
|
-
* `result=retried` from the retry-scheduling site, which has the failure
|
|
671
|
-
* context.
|
|
672
|
-
* @since 2.3.0
|
|
673
|
-
*/
|
|
674
|
-
function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
|
|
675
|
-
const metrics = getFederationMetrics(meterProvider);
|
|
676
|
-
metrics.recordQueueTaskEnqueued({
|
|
677
|
-
role: "outbox",
|
|
678
|
-
queue: outboxQueue,
|
|
679
|
-
activityType: message.activityType
|
|
680
|
-
}, message.attempt);
|
|
681
|
-
if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Records `activitypub.fanout.recipients` with the number of recipient
|
|
685
|
-
* inboxes a single fanout produced. The histogram is unitless count
|
|
686
|
-
* (one measurement per fanout enqueue). Recipient URLs are deliberately
|
|
687
|
-
* not recorded; only the activity type, when known.
|
|
688
|
-
* @since 2.3.0
|
|
689
|
-
*/
|
|
690
|
-
function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
|
|
691
|
-
getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Records one `activitypub.inbox.activity` measurement. The
|
|
695
|
-
* `activitypub.processing.result` attribute is always present;
|
|
696
|
-
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
697
|
-
* the activity type.
|
|
698
|
-
* @since 2.3.0
|
|
699
|
-
*/
|
|
700
|
-
function recordInboxActivity(meterProvider, result, activityType) {
|
|
701
|
-
getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Records one `activitypub.outbox.activity` measurement. The
|
|
705
|
-
* `activitypub.processing.result` attribute is always present;
|
|
706
|
-
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
707
|
-
* the activity type (it is always known for outbox lifecycle events).
|
|
708
|
-
* @since 2.3.0
|
|
709
|
-
*/
|
|
710
|
-
function recordOutboxActivity(meterProvider, result, activityType) {
|
|
711
|
-
getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Records one outbound delivery circuit breaker state transition.
|
|
715
|
-
* @since 2.3.0
|
|
716
|
-
*/
|
|
717
|
-
function recordCircuitBreakerStateChange(meterProvider, remoteHost, state) {
|
|
718
|
-
getFederationMetrics(meterProvider).recordCircuitBreakerStateChange(remoteHost, state);
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Records one measurement on `activitypub.key.lookup` (counter) and
|
|
722
|
-
* `activitypub.key.lookup.duration` (histogram) for a public-key lookup.
|
|
723
|
-
*
|
|
724
|
-
* `activitypub.lookup.kind` is always recorded as `public_key`; the result
|
|
725
|
-
* classification, remote host, HTTP status code (when an HTTP response was
|
|
726
|
-
* received), and `activitypub.cache.enabled` are recorded as attributes on
|
|
727
|
-
* both measurements. Full key URLs and key IDs are deliberately omitted to
|
|
728
|
-
* keep cardinality bounded.
|
|
729
|
-
* @since 2.3.0
|
|
730
|
-
*/
|
|
731
|
-
function recordKeyLookup(meterProvider, attrs) {
|
|
732
|
-
getFederationMetrics(meterProvider).recordKeyLookup(attrs);
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* Records one measurement each on `activitypub.document.fetch` (counter)
|
|
736
|
-
* and `activitypub.document.fetch.duration` (histogram) for one remote
|
|
737
|
-
* JSON-LD document loader invocation, with bounded
|
|
738
|
-
* `activitypub.lookup.kind` and `activitypub.lookup.result` attributes
|
|
739
|
-
* plus the optional remote-host, cache-enabled, and HTTP status-code
|
|
740
|
-
* attributes. Counter and histogram are always recorded together so
|
|
741
|
-
* aggregate rate and latency views stay in sync.
|
|
742
|
-
* @since 2.3.0
|
|
743
|
-
*/
|
|
744
|
-
function recordDocumentFetch(meterProvider, attrs) {
|
|
745
|
-
getFederationMetrics(meterProvider).recordDocumentFetch(attrs);
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Records one `activitypub.document.cache` measurement, classifying the
|
|
749
|
-
* lookup as `hit` (the cache returned an entry) or `miss` (the cache was
|
|
750
|
-
* consulted and returned nothing, prompting a delegate fetch).
|
|
751
|
-
* @since 2.3.0
|
|
752
|
-
*/
|
|
753
|
-
function recordDocumentCache(meterProvider, attrs) {
|
|
754
|
-
getFederationMetrics(meterProvider).recordDocumentCache(attrs);
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Records one measurement on `webfinger.handle` (counter) and
|
|
758
|
-
* `webfinger.handle.duration` (histogram) for an incoming WebFinger
|
|
759
|
-
* request handled by Fedify. Counter and histogram are always recorded
|
|
760
|
-
* together, with `webfinger.handle.result` set to one of `resolved`,
|
|
761
|
-
* `invalid`, `not_found`, `tombstoned`, or `error`. The queried
|
|
762
|
-
* resource string is deliberately excluded; it remains on the
|
|
763
|
-
* `webfinger.handle` span for trace-level investigation.
|
|
764
|
-
* @since 2.3.0
|
|
765
|
-
*/
|
|
766
|
-
function recordWebFingerHandle(meterProvider, attrs) {
|
|
767
|
-
getFederationMetrics(meterProvider).recordWebFingerHandle(attrs);
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Records one `activitypub.collection.request` measurement for a
|
|
771
|
-
* collection or collection-page request handled by Fedify.
|
|
772
|
-
* @since 2.3.0
|
|
773
|
-
*/
|
|
774
|
-
function recordCollectionRequest(meterProvider, attrs) {
|
|
775
|
-
getFederationMetrics(meterProvider).recordCollectionRequest(attrs);
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Records one `activitypub.collection.dispatch.duration` measurement for a
|
|
779
|
-
* collection dispatcher callback invocation.
|
|
780
|
-
* @since 2.3.0
|
|
781
|
-
*/
|
|
782
|
-
function recordCollectionDispatchDuration(meterProvider, durationMs, attrs) {
|
|
783
|
-
getFederationMetrics(meterProvider).recordCollectionDispatchDuration(durationMs, attrs);
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Records one `activitypub.collection.page.items` measurement when Fedify
|
|
787
|
-
* has materialized collection items in memory.
|
|
788
|
-
* @since 2.3.0
|
|
789
|
-
*/
|
|
790
|
-
function recordCollectionPageItems(meterProvider, itemCount, attrs) {
|
|
791
|
-
getFederationMetrics(meterProvider).recordCollectionPageItems(itemCount, attrs);
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Records one `activitypub.collection.total_items` measurement when a
|
|
795
|
-
* collection counter has already reported a total item count.
|
|
796
|
-
* @since 2.3.0
|
|
797
|
-
*/
|
|
798
|
-
function recordCollectionTotalItems(meterProvider, totalItems, attrs) {
|
|
799
|
-
getFederationMetrics(meterProvider).recordCollectionTotalItems(totalItems, attrs);
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Classifies a thrown value from a key or document fetch into the bounded
|
|
803
|
-
* {@link LookupResult} taxonomy and, when an HTTP response was received,
|
|
804
|
-
* surfaces its status code.
|
|
805
|
-
*
|
|
806
|
-
* - `FetchError` with a `Response` whose status is `404` or `410`:
|
|
807
|
-
* `result=not_found` and the response status code.
|
|
808
|
-
* - `FetchError` with any other `Response`: `result=error` and the
|
|
809
|
-
* response status code.
|
|
810
|
-
* - `FetchError` without a `Response`: `result=network_error`.
|
|
811
|
-
* - An `AbortError` (typically from a cancelled fetch): `result=network_error`.
|
|
812
|
-
* - A bare `TypeError` (the shape native `fetch()` raises on DNS, connect,
|
|
813
|
-
* and TLS failures before any response is observed):
|
|
814
|
-
* `result=network_error`.
|
|
815
|
-
* - Any other value: `result=error`.
|
|
816
|
-
* @since 2.3.0
|
|
817
|
-
*/
|
|
818
|
-
function classifyFetchError(error) {
|
|
819
|
-
if (error instanceof _fedify_vocab_runtime.FetchError) {
|
|
820
|
-
if (error.response != null) {
|
|
821
|
-
const status = error.response.status;
|
|
822
|
-
return {
|
|
823
|
-
result: status === 404 || status === 410 ? "not_found" : "error",
|
|
824
|
-
statusCode: status
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
return { result: "network_error" };
|
|
828
|
-
}
|
|
829
|
-
if (isAbortError$1(error)) return { result: "network_error" };
|
|
830
|
-
if (error instanceof TypeError) return { result: "network_error" };
|
|
831
|
-
return { result: "error" };
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Wraps a {@link DocumentLoader} so each invocation records one
|
|
835
|
-
* measurement on `activitypub.document.fetch` (counter) and one on
|
|
836
|
-
* `activitypub.document.fetch.duration` (histogram), classifying the
|
|
837
|
-
* outcome via {@link classifyFetchError} when the wrapped loader throws
|
|
838
|
-
* and as `fetched` on success. The wrapper rethrows whatever the
|
|
839
|
-
* wrapped loader throws so caller behavior is unchanged.
|
|
840
|
-
*
|
|
841
|
-
* The wrapper records the host of the requested URL, including any
|
|
842
|
-
* non-default port, on `activitypub.remote.host` when the URL parses; full
|
|
843
|
-
* URLs, paths, and query strings are deliberately excluded to keep
|
|
844
|
-
* cardinality bounded.
|
|
845
|
-
* HTTP status codes are recorded only when the failure carries a
|
|
846
|
-
* `Response` (currently, when the wrapped loader throws a
|
|
847
|
-
* {@link FetchError} with a non-`null` `response`).
|
|
848
|
-
* @since 2.3.0
|
|
849
|
-
*/
|
|
850
|
-
function instrumentDocumentLoader(loader, options) {
|
|
851
|
-
const meterProvider = options.meterProvider;
|
|
852
|
-
if (meterProvider == null) return loader;
|
|
853
|
-
return async (url, opts) => {
|
|
854
|
-
const start = performance.now();
|
|
855
|
-
let remoteUrl;
|
|
856
|
-
try {
|
|
857
|
-
remoteUrl = new URL(url);
|
|
858
|
-
} catch {
|
|
859
|
-
remoteUrl = void 0;
|
|
860
|
-
}
|
|
861
|
-
try {
|
|
862
|
-
const result = await loader(url, opts);
|
|
863
|
-
recordDocumentFetch(meterProvider, {
|
|
864
|
-
durationMs: getDurationMs(start),
|
|
865
|
-
kind: options.kind,
|
|
866
|
-
result: "fetched",
|
|
867
|
-
remoteUrl,
|
|
868
|
-
cacheEnabled: options.cacheEnabled
|
|
869
|
-
});
|
|
870
|
-
return result;
|
|
871
|
-
} catch (error) {
|
|
872
|
-
const classified = classifyFetchError(error);
|
|
873
|
-
recordDocumentFetch(meterProvider, {
|
|
874
|
-
durationMs: getDurationMs(start),
|
|
875
|
-
kind: options.kind,
|
|
876
|
-
result: classified.result,
|
|
877
|
-
remoteUrl,
|
|
878
|
-
cacheEnabled: options.cacheEnabled,
|
|
879
|
-
statusCode: classified.statusCode
|
|
880
|
-
});
|
|
881
|
-
throw error;
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
|
-
* Times an awaited public key fetch and records exactly one
|
|
887
|
-
* `activitypub.signature.key_fetch.duration` measurement, classifying the
|
|
888
|
-
* outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
|
|
889
|
-
* whether the returned key is non-null. Errors thrown by the fetch are
|
|
890
|
-
* reported as `error` and rethrown, so verifier behavior is unchanged.
|
|
891
|
-
*
|
|
892
|
-
* Shared by the three signature verifiers (HTTP, Linked Data, Object
|
|
893
|
-
* Integrity Proofs); the only per-call variation is the
|
|
894
|
-
* `activitypub.signature.kind` attribute value.
|
|
895
|
-
* @since 2.3.0
|
|
896
|
-
*/
|
|
897
|
-
async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
|
|
898
|
-
const start = performance.now();
|
|
899
|
-
try {
|
|
900
|
-
const result = await fetch();
|
|
901
|
-
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
|
|
902
|
-
return result;
|
|
903
|
-
} catch (error) {
|
|
904
|
-
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
|
|
905
|
-
throw error;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Whether the given thrown value is an `AbortError`.
|
|
910
|
-
*
|
|
911
|
-
* `processQueuedTask` distinguishes aborted tasks (recorded as
|
|
912
|
-
* `fedify.queue.task.result=aborted`) from other failures so that backend
|
|
913
|
-
* shutdown signals do not inflate the `fedify.queue.task.failed` counter.
|
|
914
|
-
* @since 2.3.0
|
|
915
|
-
*/
|
|
916
|
-
function isAbortError$1(error) {
|
|
917
|
-
if (error == null || typeof error !== "object") return false;
|
|
918
|
-
const name = error.name;
|
|
919
|
-
return typeof name === "string" && name === "AbortError";
|
|
920
|
-
}
|
|
921
|
-
const KNOWN_HTTP_METHODS = new Set([
|
|
922
|
-
"CONNECT",
|
|
923
|
-
"DELETE",
|
|
924
|
-
"GET",
|
|
925
|
-
"HEAD",
|
|
926
|
-
"OPTIONS",
|
|
927
|
-
"PATCH",
|
|
928
|
-
"POST",
|
|
929
|
-
"PUT",
|
|
930
|
-
"QUERY",
|
|
931
|
-
"TRACE"
|
|
932
|
-
]);
|
|
933
|
-
function normalizeHttpMethod(method) {
|
|
934
|
-
const upper = method.toUpperCase();
|
|
935
|
-
return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
|
|
936
|
-
}
|
|
937
|
-
const federationMetrics = /* @__PURE__ */ new WeakMap();
|
|
938
|
-
/**
|
|
939
|
-
* Gets the cached Fedify metric instruments for a meter provider.
|
|
940
|
-
* @since 2.3.0
|
|
941
|
-
*/
|
|
942
|
-
function getFederationMetrics(meterProvider = _opentelemetry_api.metrics.getMeterProvider()) {
|
|
943
|
-
let instruments = federationMetrics.get(meterProvider);
|
|
944
|
-
if (instruments == null) {
|
|
945
|
-
instruments = new FederationMetrics(meterProvider);
|
|
946
|
-
federationMetrics.set(meterProvider, instruments);
|
|
947
|
-
}
|
|
948
|
-
return instruments;
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Gets the bounded remote host attribute value for a URL.
|
|
952
|
-
* @since 2.3.0
|
|
953
|
-
*/
|
|
954
|
-
function getRemoteHost(url) {
|
|
955
|
-
return url.host;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Gets an elapsed duration in milliseconds from a `performance.now()` value.
|
|
959
|
-
* @since 2.3.0
|
|
960
|
-
*/
|
|
961
|
-
function getDurationMs(start) {
|
|
962
|
-
return Math.max(0, performance.now() - start);
|
|
963
|
-
}
|
|
964
|
-
//#endregion
|
|
965
|
-
//#region src/sig/key.ts
|
|
966
|
-
/**
|
|
967
|
-
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
968
|
-
* otherwise throws an error.
|
|
969
|
-
* @param key The key to check.
|
|
970
|
-
* @param type Which type of key to check. If not specified, the key can be
|
|
971
|
-
* either public or private.
|
|
972
|
-
* @throws {TypeError} If the key is invalid or unsupported.
|
|
973
|
-
*/
|
|
974
|
-
function validateCryptoKey(key, type) {
|
|
975
|
-
if (type != null && key.type !== type) throw new TypeError(`The key is not a ${type} key.`);
|
|
976
|
-
if (!key.extractable) throw new TypeError("The key is not extractable.");
|
|
977
|
-
if (key.algorithm.name !== "RSASSA-PKCS1-v1_5" && key.algorithm.name !== "Ed25519") throw new TypeError("Currently only RSASSA-PKCS1-v1_5 and Ed25519 keys are supported. More algorithms will be added in the future!");
|
|
978
|
-
if (key.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
979
|
-
if (key.algorithm.hash.name !== "SHA-256") throw new TypeError("For compatibility with the existing Fediverse software (e.g., Mastodon), hash algorithm for RSASSA-PKCS1-v1_5 keys must be SHA-256.");
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* Generates a key pair which is appropriate for Fedify.
|
|
984
|
-
* @param algorithm The algorithm to use. Currently only RSASSA-PKCS1-v1_5 and
|
|
985
|
-
* Ed25519 are supported.
|
|
986
|
-
* @returns The generated key pair.
|
|
987
|
-
* @throws {TypeError} If the algorithm is unsupported.
|
|
988
|
-
*/
|
|
989
|
-
function generateCryptoKeyPair(algorithm) {
|
|
990
|
-
if (algorithm == null) (0, _logtape_logtape.getLogger)([
|
|
991
|
-
"fedify",
|
|
992
|
-
"sig",
|
|
993
|
-
"key"
|
|
994
|
-
]).warn("No algorithm specified. Using RSASSA-PKCS1-v1_5 by default, but it is recommended to specify the algorithm explicitly as the parameter will be required in the future.");
|
|
995
|
-
if (algorithm == null || algorithm === "RSASSA-PKCS1-v1_5") return crypto.subtle.generateKey({
|
|
996
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
997
|
-
modulusLength: 4096,
|
|
998
|
-
publicExponent: new Uint8Array([
|
|
999
|
-
1,
|
|
1000
|
-
0,
|
|
1001
|
-
1
|
|
1002
|
-
]),
|
|
1003
|
-
hash: "SHA-256"
|
|
1004
|
-
}, true, ["sign", "verify"]);
|
|
1005
|
-
else if (algorithm === "Ed25519") return crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
1006
|
-
throw new TypeError("Unsupported algorithm: " + algorithm);
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Exports a key in JWK format.
|
|
1010
|
-
* @param key The key to export. Either public or private key.
|
|
1011
|
-
* @returns The exported key in JWK format. The key is suitable for
|
|
1012
|
-
* serialization and storage.
|
|
1013
|
-
* @throws {TypeError} If the key is invalid or unsupported.
|
|
1014
|
-
*/
|
|
1015
|
-
async function exportJwk(key) {
|
|
1016
|
-
validateCryptoKey(key);
|
|
1017
|
-
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
1018
|
-
if (jwk.crv === "Ed25519") jwk.alg = "Ed25519";
|
|
1019
|
-
return jwk;
|
|
1020
|
-
}
|
|
1021
|
-
/**
|
|
1022
|
-
* Imports a key from JWK format.
|
|
1023
|
-
* @param jwk The key in JWK format.
|
|
1024
|
-
* @param type Which type of key to import, either `"public"` or `"private"`.
|
|
1025
|
-
* @returns The imported key.
|
|
1026
|
-
* @throws {TypeError} If the key is invalid or unsupported.
|
|
1027
|
-
*/
|
|
1028
|
-
async function importJwk(jwk, type) {
|
|
1029
|
-
let key;
|
|
1030
|
-
if (jwk.kty === "RSA" && jwk.alg === "RS256") key = await crypto.subtle.importKey("jwk", jwk, {
|
|
1031
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
1032
|
-
hash: "SHA-256"
|
|
1033
|
-
}, true, type === "public" ? ["verify"] : ["sign"]);
|
|
1034
|
-
else if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
|
|
1035
|
-
if (navigator?.userAgent === "Cloudflare-Workers") {
|
|
1036
|
-
jwk = { ...jwk };
|
|
1037
|
-
delete jwk.alg;
|
|
1038
|
-
}
|
|
1039
|
-
key = await crypto.subtle.importKey("jwk", jwk, "Ed25519", true, type === "public" ? ["verify"] : ["sign"]);
|
|
1040
|
-
} else throw new TypeError("Unsupported JWK format.");
|
|
1041
|
-
validateCryptoKey(key, type);
|
|
1042
|
-
return key;
|
|
1043
|
-
}
|
|
1044
|
-
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
1045
|
-
tracerProvider ??= _opentelemetry_api.trace.getTracerProvider();
|
|
1046
|
-
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
1047
|
-
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
1048
|
-
attributes: {
|
|
1049
|
-
"http.method": "GET",
|
|
1050
|
-
"url.full": keyId.href,
|
|
1051
|
-
"url.scheme": keyId.protocol.replace(/:$/, ""),
|
|
1052
|
-
"url.domain": keyId.hostname,
|
|
1053
|
-
"url.path": keyId.pathname,
|
|
1054
|
-
"url.query": keyId.search.replace(/^\?/, ""),
|
|
1055
|
-
"url.fragment": keyId.hash.replace(/^#/, "")
|
|
1056
|
-
}
|
|
1057
|
-
}, async (span) => {
|
|
1058
|
-
try {
|
|
1059
|
-
const result = await fetcher();
|
|
1060
|
-
span.setAttribute("activitypub.actor.key.cached", result.cached);
|
|
1061
|
-
return result;
|
|
1062
|
-
} catch (e) {
|
|
1063
|
-
span.setStatus({
|
|
1064
|
-
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
1065
|
-
message: String(e)
|
|
1066
|
-
});
|
|
1067
|
-
throw e;
|
|
1068
|
-
} finally {
|
|
1069
|
-
span.end();
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
|
|
1075
|
-
* If the given URL contains an {@link Actor} object, it tries to find
|
|
1076
|
-
* the corresponding key in the `publicKey` or `assertionMethod` property.
|
|
1077
|
-
* @template T The type of the key to fetch. Either {@link CryptographicKey}
|
|
1078
|
-
* or {@link Multikey}.
|
|
1079
|
-
* @param keyId The URL of the key.
|
|
1080
|
-
* @param cls The class of the key to fetch. Either {@link CryptographicKey}
|
|
1081
|
-
* or {@link Multikey}.
|
|
1082
|
-
* @param options Options for fetching the key. See {@link FetchKeyOptions}.
|
|
1083
|
-
* @returns The fetched key or `null` if the key is not found.
|
|
1084
|
-
* @since 1.3.0
|
|
1085
|
-
*/
|
|
1086
|
-
function fetchKey(keyId, cls, options = {}) {
|
|
1087
|
-
keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
|
|
1088
|
-
return withFetchKeySpan(keyId, options.tracerProvider, () => fetchKeyInternal(keyId, cls, options));
|
|
1089
|
-
}
|
|
1090
|
-
/**
|
|
1091
|
-
* Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL,
|
|
1092
|
-
* preserving transport-level fetch failures for callers that need to inspect
|
|
1093
|
-
* why the key could not be loaded.
|
|
1094
|
-
*
|
|
1095
|
-
* @template T The type of the key to fetch. Either {@link CryptographicKey}
|
|
1096
|
-
* or {@link Multikey}.
|
|
1097
|
-
* @param keyId The URL of the key.
|
|
1098
|
-
* @param cls The class of the key to fetch. Either {@link CryptographicKey}
|
|
1099
|
-
* or {@link Multikey}.
|
|
1100
|
-
* @param options Options for fetching the key.
|
|
1101
|
-
* @returns The fetched key, or detailed fetch failure information.
|
|
1102
|
-
* @since 2.1.0
|
|
1103
|
-
*/
|
|
1104
|
-
async function fetchKeyDetailed(keyId, cls, options = {}) {
|
|
1105
|
-
const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
|
|
1106
|
-
return await withFetchKeySpan(cacheKey, options.tracerProvider, async () => {
|
|
1107
|
-
return await fetchKeyWithResult(cacheKey, cls, options, async (cacheKey, keyId, keyCache, logger) => {
|
|
1108
|
-
const fetchError = await keyCache?.getFetchError?.(cacheKey);
|
|
1109
|
-
if (fetchError != null) {
|
|
1110
|
-
logger.debug("Entry {keyId} found in cache with preserved fetch failure details.", { keyId });
|
|
1111
|
-
return {
|
|
1112
|
-
key: null,
|
|
1113
|
-
cached: true,
|
|
1114
|
-
fetchError
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
logger.debug("Entry {keyId} found in cache, but no fetch failure details are available.", { keyId });
|
|
1118
|
-
return {
|
|
1119
|
-
key: null,
|
|
1120
|
-
cached: true
|
|
1121
|
-
};
|
|
1122
|
-
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
1123
|
-
logger.debug("Failed to fetch key {keyId}.", {
|
|
1124
|
-
keyId,
|
|
1125
|
-
error
|
|
1126
|
-
});
|
|
1127
|
-
await keyCache?.set(cacheKey, null);
|
|
1128
|
-
if (error instanceof _fedify_vocab_runtime.FetchError && error.response != null) {
|
|
1129
|
-
const fetchError = {
|
|
1130
|
-
status: error.response.status,
|
|
1131
|
-
response: error.response.clone()
|
|
1132
|
-
};
|
|
1133
|
-
await keyCache?.setFetchError?.(cacheKey, fetchError);
|
|
1134
|
-
return {
|
|
1135
|
-
key: null,
|
|
1136
|
-
cached: false,
|
|
1137
|
-
fetchError
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
const fetchError = { error: error instanceof Error ? error : new Error(String(error)) };
|
|
1141
|
-
await keyCache?.setFetchError?.(cacheKey, fetchError);
|
|
1142
|
-
return {
|
|
1143
|
-
key: null,
|
|
1144
|
-
cached: false,
|
|
1145
|
-
fetchError
|
|
1146
|
-
};
|
|
1147
|
-
});
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
1151
|
-
if (keyCache == null) return null;
|
|
1152
|
-
const cachedKey = await keyCache.get(cacheKey);
|
|
1153
|
-
const hit = checkCachedKeyHit(cachedKey, keyId, cls, logger);
|
|
1154
|
-
if (hit != null) return hit;
|
|
1155
|
-
if (cachedKey === null) {
|
|
1156
|
-
logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
|
|
1157
|
-
return {
|
|
1158
|
-
key: null,
|
|
1159
|
-
cached: true
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
return null;
|
|
1163
|
-
}
|
|
1164
|
-
function checkCachedKeyHit(cachedKey, keyId, cls, logger) {
|
|
1165
|
-
if (cachedKey instanceof cls && cachedKey.publicKey != null) {
|
|
1166
|
-
logger.debug("Key {keyId} found in cache.", { keyId });
|
|
1167
|
-
return {
|
|
1168
|
-
key: cachedKey,
|
|
1169
|
-
cached: true
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
1175
|
-
await keyCache?.setFetchError?.(keyId, null);
|
|
1176
|
-
}
|
|
1177
|
-
function isDidKeyUrl(keyId) {
|
|
1178
|
-
return keyId.protocol === "did:" && keyId.pathname.startsWith("key:");
|
|
1179
|
-
}
|
|
1180
|
-
async function doRawKeysMatch(left, right) {
|
|
1181
|
-
const [leftRaw, rightRaw] = await Promise.all([crypto.subtle.exportKey("raw", left), crypto.subtle.exportKey("raw", right)]);
|
|
1182
|
-
if (leftRaw.byteLength !== rightRaw.byteLength) return false;
|
|
1183
|
-
const leftBytes = new Uint8Array(leftRaw);
|
|
1184
|
-
const rightBytes = new Uint8Array(rightRaw);
|
|
1185
|
-
return leftBytes.every((byte, index) => byte === rightBytes[index]);
|
|
1186
|
-
}
|
|
1187
|
-
async function isCachedDidKeyValid(key, verificationMethod) {
|
|
1188
|
-
try {
|
|
1189
|
-
return key.id?.href === verificationMethod.id.href && key.controllerId?.href === verificationMethod.controller.href && await doRawKeysMatch(key.publicKey, verificationMethod.publicKey);
|
|
1190
|
-
} catch {
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
async function resolveDidKey(cacheKey, cls, keyCache, logger) {
|
|
1195
|
-
if (!isDidKeyUrl(cacheKey)) return null;
|
|
1196
|
-
const keyId = cacheKey.href;
|
|
1197
|
-
if (!(cls === _fedify_vocab.Multikey)) {
|
|
1198
|
-
logger.debug("Failed to resolve did:key {keyId}; did:key verification methods are only supported as Multikey values.", { keyId });
|
|
1199
|
-
return {
|
|
1200
|
-
key: null,
|
|
1201
|
-
cached: false
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
let verificationMethod;
|
|
1205
|
-
try {
|
|
1206
|
-
verificationMethod = await (0, _fedify_vocab_runtime.parseDidKeyVerificationMethod)(cacheKey);
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
logger.debug("Failed to resolve did:key verification method {keyId}: {error}", {
|
|
1209
|
-
keyId,
|
|
1210
|
-
error
|
|
1211
|
-
});
|
|
1212
|
-
return {
|
|
1213
|
-
key: null,
|
|
1214
|
-
cached: false
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
const hit = checkCachedKeyHit(await keyCache?.get(cacheKey), keyId, cls, logger);
|
|
1218
|
-
if (hit?.key instanceof _fedify_vocab.Multikey && await isCachedDidKeyValid(hit.key, verificationMethod)) return hit;
|
|
1219
|
-
const key = new _fedify_vocab.Multikey({
|
|
1220
|
-
id: verificationMethod.id,
|
|
1221
|
-
controller: verificationMethod.controller,
|
|
1222
|
-
publicKey: verificationMethod.publicKey
|
|
1223
|
-
});
|
|
1224
|
-
await keyCache?.set(cacheKey, key);
|
|
1225
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1226
|
-
logger.debug("Resolved did:key verification method {keyId}.", { keyId });
|
|
1227
|
-
return {
|
|
1228
|
-
key,
|
|
1229
|
-
cached: false
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
1233
|
-
let object;
|
|
1234
|
-
try {
|
|
1235
|
-
object = await _fedify_vocab.Object.fromJsonLd(document, {
|
|
1236
|
-
documentLoader,
|
|
1237
|
-
contextLoader,
|
|
1238
|
-
tracerProvider
|
|
1239
|
-
});
|
|
1240
|
-
} catch (e) {
|
|
1241
|
-
if (!(e instanceof TypeError)) throw e;
|
|
1242
|
-
try {
|
|
1243
|
-
object = await cls.fromJsonLd(document, {
|
|
1244
|
-
documentLoader,
|
|
1245
|
-
contextLoader,
|
|
1246
|
-
tracerProvider
|
|
1247
|
-
});
|
|
1248
|
-
} catch (e) {
|
|
1249
|
-
if (e instanceof TypeError) {
|
|
1250
|
-
logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
|
|
1251
|
-
await keyCache?.set(cacheKey, null);
|
|
1252
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1253
|
-
return {
|
|
1254
|
-
key: null,
|
|
1255
|
-
cached: false
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
throw e;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
let key = null;
|
|
1262
|
-
if (object instanceof cls) key = object;
|
|
1263
|
-
else if ((0, _fedify_vocab.isActor)(object)) {
|
|
1264
|
-
const keys = cls === _fedify_vocab.CryptographicKey ? object.getPublicKeys({
|
|
1265
|
-
documentLoader,
|
|
1266
|
-
contextLoader,
|
|
1267
|
-
suppressError: true,
|
|
1268
|
-
tracerProvider
|
|
1269
|
-
}) : object.getAssertionMethods({
|
|
1270
|
-
documentLoader,
|
|
1271
|
-
contextLoader,
|
|
1272
|
-
suppressError: true,
|
|
1273
|
-
tracerProvider
|
|
1274
|
-
});
|
|
1275
|
-
let length = 0;
|
|
1276
|
-
let lastKey = null;
|
|
1277
|
-
try {
|
|
1278
|
-
for await (const k of keys) {
|
|
1279
|
-
length++;
|
|
1280
|
-
lastKey = k;
|
|
1281
|
-
if (k.id?.href === keyId) {
|
|
1282
|
-
key = k;
|
|
1283
|
-
break;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
} catch (e) {
|
|
1287
|
-
if (!(e instanceof TypeError)) throw e;
|
|
1288
|
-
logger.debug("Failed to verify; a malformed key was encountered while iterating the keys of {keyId}; treating it as a missing key: {error}", {
|
|
1289
|
-
keyId,
|
|
1290
|
-
error: e
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
const keyIdUrl = new URL(keyId);
|
|
1294
|
-
if (key == null && keyIdUrl.hash === "" && length === 1) key = lastKey;
|
|
1295
|
-
if (key == null) {
|
|
1296
|
-
logger.debug("Failed to verify; object {keyId} returned an {actorType}, but has no key matching {keyId}.", {
|
|
1297
|
-
keyId,
|
|
1298
|
-
actorType: object.constructor.name
|
|
1299
|
-
});
|
|
1300
|
-
await keyCache?.set(cacheKey, null);
|
|
1301
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1302
|
-
return {
|
|
1303
|
-
key: null,
|
|
1304
|
-
cached: false
|
|
1305
|
-
};
|
|
1306
|
-
}
|
|
1307
|
-
} else {
|
|
1308
|
-
logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
|
|
1309
|
-
await keyCache?.set(cacheKey, null);
|
|
1310
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1311
|
-
return {
|
|
1312
|
-
key: null,
|
|
1313
|
-
cached: false
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
if (key.publicKey == null) {
|
|
1317
|
-
logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
|
|
1318
|
-
await keyCache?.set(cacheKey, null);
|
|
1319
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1320
|
-
return {
|
|
1321
|
-
key: null,
|
|
1322
|
-
cached: false
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
if (keyCache != null) {
|
|
1326
|
-
await keyCache.set(cacheKey, key);
|
|
1327
|
-
logger.debug("Key {keyId} cached.", { keyId });
|
|
1328
|
-
}
|
|
1329
|
-
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1330
|
-
return {
|
|
1331
|
-
key,
|
|
1332
|
-
cached: false
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
|
|
1336
|
-
const start = performance.now();
|
|
1337
|
-
let outcome = { result: "error" };
|
|
1338
|
-
try {
|
|
1339
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
1340
|
-
"fedify",
|
|
1341
|
-
"sig",
|
|
1342
|
-
"key"
|
|
1343
|
-
]);
|
|
1344
|
-
const keyId = cacheKey.href;
|
|
1345
|
-
const keyCache = options.keyCache;
|
|
1346
|
-
const didKey = await resolveDidKey(cacheKey, cls, keyCache, logger);
|
|
1347
|
-
if (didKey != null) {
|
|
1348
|
-
outcome = { result: didKey.key == null ? "invalid" : didKey.cached ? "hit" : "fetched" };
|
|
1349
|
-
return didKey;
|
|
1350
|
-
}
|
|
1351
|
-
const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
|
|
1352
|
-
if (cached?.key === null && cached.cached) {
|
|
1353
|
-
const cachedUnavailable = await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
|
|
1354
|
-
outcome = { result: "hit" };
|
|
1355
|
-
return cachedUnavailable;
|
|
1356
|
-
}
|
|
1357
|
-
if (cached != null) {
|
|
1358
|
-
outcome = { result: "hit" };
|
|
1359
|
-
return cached;
|
|
1360
|
-
}
|
|
1361
|
-
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
1362
|
-
let document;
|
|
1363
|
-
try {
|
|
1364
|
-
document = (await (options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)())(keyId)).document;
|
|
1365
|
-
} catch (error) {
|
|
1366
|
-
const classified = classifyFetchError(error);
|
|
1367
|
-
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
1368
|
-
outcome = classified;
|
|
1369
|
-
return errored;
|
|
1370
|
-
}
|
|
1371
|
-
const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
1372
|
-
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
1373
|
-
return resolved;
|
|
1374
|
-
} finally {
|
|
1375
|
-
recordKeyLookup(options.meterProvider, {
|
|
1376
|
-
durationMs: getDurationMs(start),
|
|
1377
|
-
result: outcome.result,
|
|
1378
|
-
remoteUrl: cacheKey,
|
|
1379
|
-
cacheEnabled: options.keyCache != null,
|
|
1380
|
-
statusCode: outcome.statusCode
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
1385
|
-
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
1386
|
-
return {
|
|
1387
|
-
key: null,
|
|
1388
|
-
cached: true
|
|
1389
|
-
};
|
|
1390
|
-
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
1391
|
-
logger.debug("Failed to fetch key {keyId}.", {
|
|
1392
|
-
keyId,
|
|
1393
|
-
error
|
|
1394
|
-
});
|
|
1395
|
-
await keyCache?.set(cacheKey, null);
|
|
1396
|
-
if (error instanceof _fedify_vocab_runtime.FetchError && error.response != null) await keyCache?.setFetchError?.(cacheKey, {
|
|
1397
|
-
status: error.response.status,
|
|
1398
|
-
response: error.response.clone()
|
|
1399
|
-
});
|
|
1400
|
-
else await keyCache?.setFetchError?.(cacheKey, { error: error instanceof Error ? error : new Error(String(error)) });
|
|
1401
|
-
return {
|
|
1402
|
-
key: null,
|
|
1403
|
-
cached: false
|
|
1404
|
-
};
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1407
|
-
//#endregion
|
|
1408
|
-
//#region src/sig/http.ts
|
|
1409
|
-
const Temporal = require_temporal_polyfill.Temporal;
|
|
1410
|
-
const DEFAULT_MAX_REDIRECTION = 20;
|
|
1411
|
-
const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
1412
|
-
/**
|
|
1413
|
-
* Signs a request using the given private key.
|
|
1414
|
-
* @param request The request to sign.
|
|
1415
|
-
* @param privateKey The private key to use for signing.
|
|
1416
|
-
* @param keyId The key ID to use for the signature. It will be used by the
|
|
1417
|
-
* verifier.
|
|
1418
|
-
* @returns The signed request.
|
|
1419
|
-
* @throws {TypeError} If the private key is invalid or unsupported.
|
|
1420
|
-
*/
|
|
1421
|
-
async function signRequest(request, privateKey, keyId, options = {}) {
|
|
1422
|
-
validateCryptoKey(privateKey, "private");
|
|
1423
|
-
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.sign", async (span) => {
|
|
1424
|
-
try {
|
|
1425
|
-
const spec = options.spec ?? "draft-cavage-http-signatures-12";
|
|
1426
|
-
let signed;
|
|
1427
|
-
if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body, options.rfc9421);
|
|
1428
|
-
else signed = await signRequestDraft(request, privateKey, keyId, span, options.currentTime, options.body);
|
|
1429
|
-
if (span.isRecording()) {
|
|
1430
|
-
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, signed.method);
|
|
1431
|
-
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, signed.url);
|
|
1432
|
-
for (const [name, value] of signed.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
|
|
1433
|
-
span.setAttribute("http_signatures.key_id", keyId.href);
|
|
1434
|
-
}
|
|
1435
|
-
return signed;
|
|
1436
|
-
} catch (error) {
|
|
1437
|
-
span.setStatus({
|
|
1438
|
-
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
1439
|
-
message: String(error)
|
|
1440
|
-
});
|
|
1441
|
-
throw error;
|
|
1442
|
-
} finally {
|
|
1443
|
-
span.end();
|
|
1444
|
-
}
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
async function signRequestDraft(request, privateKey, keyId, span, currentTime, bodyBuffer) {
|
|
1448
|
-
if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
1449
|
-
const url = new URL(request.url);
|
|
1450
|
-
const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
1451
|
-
const headers = new Headers(request.headers);
|
|
1452
|
-
if (!headers.has("Host")) headers.set("Host", url.host);
|
|
1453
|
-
if (!headers.has("Digest") && body != null) {
|
|
1454
|
-
const digest = await crypto.subtle.digest("SHA-256", body);
|
|
1455
|
-
headers.set("Digest", `SHA-256=${(0, byte_encodings_base64.encodeBase64)(digest)}`);
|
|
1456
|
-
if (span.isRecording()) span.setAttribute("http_signatures.digest.sha-256", (0, byte_encodings_hex.encodeHex)(digest));
|
|
1457
|
-
}
|
|
1458
|
-
if (!headers.has("Date")) headers.set("Date", currentTime == null ? (/* @__PURE__ */ new Date()).toUTCString() : new Date(currentTime.toString()).toUTCString());
|
|
1459
|
-
const serialized = [["(request-target)", `${request.method.toLowerCase()} ${url.pathname}`], ...headers];
|
|
1460
|
-
const headerNames = serialized.map(([name]) => name);
|
|
1461
|
-
const message = serialized.map(([name, value]) => `${name}: ${value.trim()}`).join("\n");
|
|
1462
|
-
const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(message));
|
|
1463
|
-
const sigHeader = `keyId="${keyId.href}",algorithm="rsa-sha256",headers="${headerNames.join(" ")}",signature="${(0, byte_encodings_base64.encodeBase64)(signature)}"`;
|
|
1464
|
-
headers.set("Signature", sigHeader);
|
|
1465
|
-
if (span.isRecording()) {
|
|
1466
|
-
span.setAttribute("http_signatures.algorithm", "rsa-sha256");
|
|
1467
|
-
span.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(signature));
|
|
1468
|
-
}
|
|
1469
|
-
return new Request(request, {
|
|
1470
|
-
headers,
|
|
1471
|
-
body
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
function formatRfc9421SignatureParameters(params) {
|
|
1475
|
-
return Array.from(iterRfc9421(params)).join(";");
|
|
1476
|
-
}
|
|
1477
|
-
function* iterRfc9421(params) {
|
|
1478
|
-
yield `alg="${params.algorithm}"`;
|
|
1479
|
-
yield `keyid="${params.keyId.href}"`;
|
|
1480
|
-
yield `created=${params.created}`;
|
|
1481
|
-
if (params.expires != null) yield `expires=${params.expires}`;
|
|
1482
|
-
if (params.nonce != null) yield `nonce="${escapeSfString(params.nonce)}"`;
|
|
1483
|
-
if (params.tag != null) yield `tag="${escapeSfString(params.tag)}"`;
|
|
1484
|
-
}
|
|
1485
|
-
const escapeSfString = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
1486
|
-
function formatComponentId(component) {
|
|
1487
|
-
return (0, structured_field_values.encodeItem)(new structured_field_values.Item(component.value, component.params));
|
|
1488
|
-
}
|
|
1489
|
-
/**
|
|
1490
|
-
* Creates a signature base for a request according to RFC 9421.
|
|
1491
|
-
* @param request The request to create a signature base for.
|
|
1492
|
-
* @param components The components to include in the signature base.
|
|
1493
|
-
* @param parameters The signature parameters to include in the signature base.
|
|
1494
|
-
* @returns The signature base as a string.
|
|
1495
|
-
*/
|
|
1496
|
-
function createRfc9421SignatureBase(request, components, parameters) {
|
|
1497
|
-
const url = new URL(request.url);
|
|
1498
|
-
return components.map((component) => {
|
|
1499
|
-
const id = formatComponentId(component);
|
|
1500
|
-
const derived = derivedComponents[component.value]?.(request, url);
|
|
1501
|
-
if (derived != null) return `${id}: ${derived}`;
|
|
1502
|
-
if (component.value.startsWith("@")) throw new Error(`Unsupported derived component: ${component.value}`);
|
|
1503
|
-
const header = request.headers.get(component.value);
|
|
1504
|
-
if (header == null) throw new Error(`Missing header: ${component.value}`);
|
|
1505
|
-
return `${id}: ${header}`;
|
|
1506
|
-
}).concat([`"@signature-params": (${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`]).join("\n");
|
|
1507
|
-
}
|
|
1508
|
-
const derivedComponents = {
|
|
1509
|
-
"@method": (request) => request.method.toUpperCase(),
|
|
1510
|
-
"@target-uri": (_, url) => url.href,
|
|
1511
|
-
"@authority": (_, url) => url.host,
|
|
1512
|
-
"@scheme": (_, url) => url.protocol.slice(0, -1),
|
|
1513
|
-
"@request-target": (request, url) => `${request.method.toLowerCase()} ${url.pathname}${url.search}`,
|
|
1514
|
-
"@path": (_, url) => url.pathname,
|
|
1515
|
-
"@query": (_, { search }) => search.startsWith("?") ? search.slice(1) : search,
|
|
1516
|
-
"@query-param": () => {
|
|
1517
|
-
throw new Error("@query-param requires a parameter name");
|
|
1518
|
-
},
|
|
1519
|
-
"@status": () => {
|
|
1520
|
-
throw new Error("@status is only valid for responses");
|
|
1521
|
-
}
|
|
1522
|
-
};
|
|
1523
|
-
/**
|
|
1524
|
-
* Formats a signature using rfc9421 format.
|
|
1525
|
-
* @param signature The raw signature bytes.
|
|
1526
|
-
* @param components The components that were signed.
|
|
1527
|
-
* @param parameters The signature parameters.
|
|
1528
|
-
* @returns The formatted signature string.
|
|
1529
|
-
*/
|
|
1530
|
-
function formatRfc9421Signature(signature, components, parameters, label = "sig1") {
|
|
1531
|
-
return [`${label}=(${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`, `${label}=:${(0, byte_encodings_base64.encodeBase64)(signature)}:`];
|
|
1532
|
-
}
|
|
1533
|
-
/**
|
|
1534
|
-
* Parse RFC 9421 Signature-Input header.
|
|
1535
|
-
* @param signatureInput The Signature-Input header value.
|
|
1536
|
-
* @returns Parsed signature input parameters.
|
|
1537
|
-
*/
|
|
1538
|
-
function parseRfc9421SignatureInput(signatureInput) {
|
|
1539
|
-
let dict;
|
|
1540
|
-
try {
|
|
1541
|
-
dict = (0, structured_field_values.decodeDict)(signatureInput);
|
|
1542
|
-
} catch (error) {
|
|
1543
|
-
(0, _logtape_logtape.getLogger)([
|
|
1544
|
-
"fedify",
|
|
1545
|
-
"sig",
|
|
1546
|
-
"http"
|
|
1547
|
-
]).debug("Failed to parse Signature-Input header: {signatureInput}", {
|
|
1548
|
-
signatureInput,
|
|
1549
|
-
error
|
|
1550
|
-
});
|
|
1551
|
-
return {};
|
|
1552
|
-
}
|
|
1553
|
-
const result = {};
|
|
1554
|
-
for (const [label, item] of Object.entries(dict)) {
|
|
1555
|
-
if (!Array.isArray(item.value) || typeof item.params.keyid !== "string" || typeof item.params.created !== "number") continue;
|
|
1556
|
-
const components = item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
1557
|
-
value: subitem.value,
|
|
1558
|
-
params: subitem.params ?? {}
|
|
1559
|
-
}));
|
|
1560
|
-
const params = (0, structured_field_values.encodeItem)(new structured_field_values.Item(0, item.params));
|
|
1561
|
-
result[label] = {
|
|
1562
|
-
keyId: item.params.keyid,
|
|
1563
|
-
alg: item.params.alg,
|
|
1564
|
-
created: item.params.created,
|
|
1565
|
-
nonce: typeof item.params.nonce === "string" ? item.params.nonce : void 0,
|
|
1566
|
-
tag: typeof item.params.tag === "string" ? item.params.tag : void 0,
|
|
1567
|
-
components,
|
|
1568
|
-
parameters: params.slice(params.indexOf(";") + 1)
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
return result;
|
|
1572
|
-
}
|
|
1573
|
-
/**
|
|
1574
|
-
* Parse RFC 9421 Signature header.
|
|
1575
|
-
* @param signature The Signature header value.
|
|
1576
|
-
* @returns Parsed signature values.
|
|
1577
|
-
*/
|
|
1578
|
-
function parseRfc9421Signature(signature) {
|
|
1579
|
-
let dict;
|
|
1580
|
-
try {
|
|
1581
|
-
dict = (0, structured_field_values.decodeDict)(signature);
|
|
1582
|
-
} catch (error) {
|
|
1583
|
-
(0, _logtape_logtape.getLogger)([
|
|
1584
|
-
"fedify",
|
|
1585
|
-
"sig",
|
|
1586
|
-
"http"
|
|
1587
|
-
]).debug("Failed to parse Signature header: {signature}", {
|
|
1588
|
-
signature,
|
|
1589
|
-
error
|
|
1590
|
-
});
|
|
1591
|
-
return {};
|
|
1592
|
-
}
|
|
1593
|
-
const result = {};
|
|
1594
|
-
for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
|
|
1595
|
-
return result;
|
|
1596
|
-
}
|
|
1597
|
-
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
|
|
1598
|
-
if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
1599
|
-
const url = new URL(request.url);
|
|
1600
|
-
const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
1601
|
-
const headers = new Headers(request.headers);
|
|
1602
|
-
if (!headers.has("Host")) headers.set("Host", url.host);
|
|
1603
|
-
if (!headers.has("Content-Digest") && body != null) {
|
|
1604
|
-
const digest = await crypto.subtle.digest("SHA-256", body);
|
|
1605
|
-
headers.set("Content-Digest", `sha-256=:${(0, byte_encodings_base64.encodeBase64)(digest)}:`);
|
|
1606
|
-
if (span.isRecording()) span.setAttribute("http_signatures.digest.sha-256", (0, byte_encodings_hex.encodeHex)(digest));
|
|
1607
|
-
}
|
|
1608
|
-
currentTime ??= Temporal.Now.instant();
|
|
1609
|
-
const created = currentTime.epochMilliseconds / 1e3 | 0;
|
|
1610
|
-
if (!headers.has("Date")) headers.set("Date", new Date(currentTime.toString()).toUTCString());
|
|
1611
|
-
const label = rfc9421Options?.label ?? "sig1";
|
|
1612
|
-
const components = [...rfc9421Options?.components ?? [
|
|
1613
|
-
{
|
|
1614
|
-
value: "@method",
|
|
1615
|
-
params: {}
|
|
1616
|
-
},
|
|
1617
|
-
{
|
|
1618
|
-
value: "@target-uri",
|
|
1619
|
-
params: {}
|
|
1620
|
-
},
|
|
1621
|
-
{
|
|
1622
|
-
value: "@authority",
|
|
1623
|
-
params: {}
|
|
1624
|
-
},
|
|
1625
|
-
{
|
|
1626
|
-
value: "host",
|
|
1627
|
-
params: {}
|
|
1628
|
-
},
|
|
1629
|
-
{
|
|
1630
|
-
value: "date",
|
|
1631
|
-
params: {}
|
|
1632
|
-
}
|
|
1633
|
-
], ...body != null ? [{
|
|
1634
|
-
value: "content-digest",
|
|
1635
|
-
params: {}
|
|
1636
|
-
}] : []];
|
|
1637
|
-
const signatureParams = formatRfc9421SignatureParameters({
|
|
1638
|
-
algorithm: "rsa-v1_5-sha256",
|
|
1639
|
-
keyId,
|
|
1640
|
-
created,
|
|
1641
|
-
expires: rfc9421Options?.expires === true ? (currentTime.epochMilliseconds / 1e3 | 0) + 3600 : void 0,
|
|
1642
|
-
nonce: rfc9421Options?.nonce,
|
|
1643
|
-
tag: rfc9421Options?.tag
|
|
1644
|
-
});
|
|
1645
|
-
let signatureBase;
|
|
1646
|
-
try {
|
|
1647
|
-
signatureBase = createRfc9421SignatureBase(new Request(request.url, {
|
|
1648
|
-
method: request.method,
|
|
1649
|
-
headers
|
|
1650
|
-
}), components, signatureParams);
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
throw new TypeError(`Failed to create signature base: ${String(error)}; it is probably a bug in the implementation. Please report it at Fedify's issue tracker.`);
|
|
1653
|
-
}
|
|
1654
|
-
const signatureBytes = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(signatureBase));
|
|
1655
|
-
const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams, label);
|
|
1656
|
-
const existingInput = headers.get("Signature-Input");
|
|
1657
|
-
headers.set("Signature-Input", existingInput != null ? `${existingInput}, ${signatureInput}` : signatureInput);
|
|
1658
|
-
const existingSignature = headers.get("Signature");
|
|
1659
|
-
headers.set("Signature", existingSignature != null ? `${existingSignature}, ${signature}` : signature);
|
|
1660
|
-
if (span.isRecording()) {
|
|
1661
|
-
span.setAttribute("http_signatures.algorithm", "rsa-v1_5-sha256");
|
|
1662
|
-
span.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(signatureBytes));
|
|
1663
|
-
span.setAttribute("http_signatures.created", created.toString());
|
|
1664
|
-
}
|
|
1665
|
-
return new Request(request, {
|
|
1666
|
-
headers,
|
|
1667
|
-
body
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
const supportedHashAlgorithms = {
|
|
1671
|
-
"sha": "SHA-1",
|
|
1672
|
-
"sha-256": "SHA-256",
|
|
1673
|
-
"sha-512": "SHA-512"
|
|
1674
|
-
};
|
|
1675
|
-
function noSignatureResult() {
|
|
1676
|
-
return {
|
|
1677
|
-
verified: false,
|
|
1678
|
-
reason: { type: "noSignature" }
|
|
1679
|
-
};
|
|
1680
|
-
}
|
|
1681
|
-
function invalidSignatureResult(keyId) {
|
|
1682
|
-
return keyId == null ? {
|
|
1683
|
-
verified: false,
|
|
1684
|
-
reason: { type: "invalidSignature" }
|
|
1685
|
-
} : {
|
|
1686
|
-
verified: false,
|
|
1687
|
-
reason: {
|
|
1688
|
-
type: "invalidSignature",
|
|
1689
|
-
keyId
|
|
1690
|
-
}
|
|
1691
|
-
};
|
|
1692
|
-
}
|
|
1693
|
-
function keyFetchErrorResult(keyId, result) {
|
|
1694
|
-
return {
|
|
1695
|
-
verified: false,
|
|
1696
|
-
reason: {
|
|
1697
|
-
type: "keyFetchError",
|
|
1698
|
-
keyId,
|
|
1699
|
-
result
|
|
1700
|
-
}
|
|
1701
|
-
};
|
|
1702
|
-
}
|
|
1703
|
-
function parseKeyId(value) {
|
|
1704
|
-
if (value == null) return null;
|
|
1705
|
-
try {
|
|
1706
|
-
return new URL(value);
|
|
1707
|
-
} catch {
|
|
1708
|
-
return null;
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
function getKeyFetchErrorName(error) {
|
|
1712
|
-
return error.name || error.constructor.name || "Error";
|
|
1713
|
-
}
|
|
1714
|
-
/**
|
|
1715
|
-
* Known draft-cavage `algorithm` parameter values, used to keep the
|
|
1716
|
-
* `http_signatures.algorithm` metric attribute on a bounded set. The header
|
|
1717
|
-
* field is attacker-controlled and not used to select the verification
|
|
1718
|
-
* algorithm, so unknown values are dropped from the metric to prevent
|
|
1719
|
-
* cardinality blow-up.
|
|
1720
|
-
*/
|
|
1721
|
-
const DRAFT_KNOWN_ALGORITHMS = new Set([
|
|
1722
|
-
"ecdsa-sha256",
|
|
1723
|
-
"ecdsa-sha384",
|
|
1724
|
-
"ecdsa-sha512",
|
|
1725
|
-
"ed25519",
|
|
1726
|
-
"hs2019",
|
|
1727
|
-
"rsa-sha1",
|
|
1728
|
-
"rsa-sha256",
|
|
1729
|
-
"rsa-sha512"
|
|
1730
|
-
]);
|
|
1731
|
-
function classifyHttpVerifyResult(result) {
|
|
1732
|
-
if (result.verified) return "verified";
|
|
1733
|
-
return result.reason.type === "noSignature" ? "missing" : "rejected";
|
|
1734
|
-
}
|
|
1735
|
-
function recordVerificationResult(span, result) {
|
|
1736
|
-
span.setAttribute("http_signatures.verified", result.verified);
|
|
1737
|
-
if (result.verified === true) return;
|
|
1738
|
-
const reason = result.reason;
|
|
1739
|
-
span.setAttribute("http_signatures.failure_reason", reason.type);
|
|
1740
|
-
if ("keyId" in reason && reason.keyId != null) span.setAttribute("http_signatures.key_id", reason.keyId.href);
|
|
1741
|
-
if (reason.type !== "keyFetchError") return;
|
|
1742
|
-
if ("status" in reason.result) span.setAttribute("http_signatures.key_fetch_status", reason.result.status);
|
|
1743
|
-
else span.setAttribute("http_signatures.key_fetch_error", getKeyFetchErrorName(reason.result.error));
|
|
1744
|
-
}
|
|
1745
|
-
/**
|
|
1746
|
-
* Verifies the signature of a request.
|
|
1747
|
-
*
|
|
1748
|
-
* Note that this function consumes the request body, so it should not be used
|
|
1749
|
-
* if the request body is already consumed. Consuming the request body after
|
|
1750
|
-
* calling this function is okay, since this function clones the request
|
|
1751
|
-
* under the hood.
|
|
1752
|
-
*
|
|
1753
|
-
* @param request The request to verify.
|
|
1754
|
-
* @param options Options for verifying the request.
|
|
1755
|
-
* @returns The public key of the verified signature, or `null` if the signature
|
|
1756
|
-
* could not be verified.
|
|
1757
|
-
*/
|
|
1758
|
-
async function verifyRequest(request, options = {}) {
|
|
1759
|
-
const result = await verifyRequestDetailed(request, options);
|
|
1760
|
-
return result.verified ? result.key : null;
|
|
1761
|
-
}
|
|
1762
|
-
/**
|
|
1763
|
-
* Verifies the signature of a request and returns a structured failure reason
|
|
1764
|
-
* when verification does not succeed.
|
|
1765
|
-
*
|
|
1766
|
-
* @param request The request to verify.
|
|
1767
|
-
* @param options Options for verifying the request.
|
|
1768
|
-
* @returns The verified public key, or a structured verification failure.
|
|
1769
|
-
* @since 2.1.0
|
|
1770
|
-
*/
|
|
1771
|
-
async function verifyRequestDetailed(request, options = {}) {
|
|
1772
|
-
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
1773
|
-
if (span.isRecording()) {
|
|
1774
|
-
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
1775
|
-
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, request.url);
|
|
1776
|
-
for (const [name, value] of request.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
|
|
1777
|
-
}
|
|
1778
|
-
const start = performance.now();
|
|
1779
|
-
const metricsContext = {};
|
|
1780
|
-
let result;
|
|
1781
|
-
let threw = false;
|
|
1782
|
-
try {
|
|
1783
|
-
let spec = options.spec;
|
|
1784
|
-
if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1785
|
-
if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
|
|
1786
|
-
else result = await verifyRequestDraft(request, span, metricsContext, options);
|
|
1787
|
-
recordVerificationResult(span, result);
|
|
1788
|
-
if (!result.verified) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
1789
|
-
return result;
|
|
1790
|
-
} catch (error) {
|
|
1791
|
-
threw = true;
|
|
1792
|
-
span.setStatus({
|
|
1793
|
-
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
1794
|
-
message: String(error)
|
|
1795
|
-
});
|
|
1796
|
-
throw error;
|
|
1797
|
-
} finally {
|
|
1798
|
-
const classified = threw ? "error" : classifyHttpVerifyResult(result);
|
|
1799
|
-
const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
|
|
1800
|
-
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
|
|
1801
|
-
algorithm: metricsContext.algorithm,
|
|
1802
|
-
failureReason
|
|
1803
|
-
});
|
|
1804
|
-
span.end();
|
|
1805
|
-
}
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
1809
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
1810
|
-
"fedify",
|
|
1811
|
-
"sig",
|
|
1812
|
-
"http"
|
|
1813
|
-
]);
|
|
1814
|
-
if (request.bodyUsed) {
|
|
1815
|
-
logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
|
|
1816
|
-
return noSignatureResult();
|
|
1817
|
-
} else if (request.body?.locked) {
|
|
1818
|
-
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
1819
|
-
return noSignatureResult();
|
|
1820
|
-
}
|
|
1821
|
-
const originalRequest = request;
|
|
1822
|
-
request = request.clone();
|
|
1823
|
-
const sigHeader = request.headers.get("Signature");
|
|
1824
|
-
if (sigHeader == null) {
|
|
1825
|
-
logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
1826
|
-
return noSignatureResult();
|
|
1827
|
-
}
|
|
1828
|
-
const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
|
|
1829
|
-
const parsedKeyId = parseKeyId(sigValues.keyId);
|
|
1830
|
-
const dateHeader = request.headers.get("Date");
|
|
1831
|
-
if (dateHeader == null) {
|
|
1832
|
-
logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
1833
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1834
|
-
}
|
|
1835
|
-
const digestHeader = request.headers.get("Digest");
|
|
1836
|
-
if (request.method !== "GET" && request.method !== "HEAD" && digestHeader == null) {
|
|
1837
|
-
logger.debug("Failed to verify; no Digest header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
1838
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1839
|
-
}
|
|
1840
|
-
let body = null;
|
|
1841
|
-
if (digestHeader != null) {
|
|
1842
|
-
body = await request.arrayBuffer();
|
|
1843
|
-
const digests = digestHeader.split(",").map((pair) => pair.includes("=") ? pair.split("=", 2) : [pair, ""]);
|
|
1844
|
-
let matched = false;
|
|
1845
|
-
for (let [algo, digestBase64] of digests) {
|
|
1846
|
-
algo = algo.trim().toLowerCase();
|
|
1847
|
-
if (!(algo in supportedHashAlgorithms)) continue;
|
|
1848
|
-
let digest;
|
|
1849
|
-
try {
|
|
1850
|
-
digest = (0, byte_encodings_base64.decodeBase64)(digestBase64);
|
|
1851
|
-
} catch (error) {
|
|
1852
|
-
logger.debug("Failed to verify; invalid base64 encoding: {digest}.", {
|
|
1853
|
-
digest: digestBase64,
|
|
1854
|
-
error
|
|
1855
|
-
});
|
|
1856
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1857
|
-
}
|
|
1858
|
-
if (span.isRecording()) span.setAttribute(`http_signatures.digest.${algo}`, (0, byte_encodings_hex.encodeHex)(digest));
|
|
1859
|
-
const expectedDigest = await crypto.subtle.digest(supportedHashAlgorithms[algo], body);
|
|
1860
|
-
if (!timingSafeEqual(digest, new Uint8Array(expectedDigest))) {
|
|
1861
|
-
logger.debug("Failed to verify; digest mismatch ({algorithm}): {digest} != {expectedDigest}.", {
|
|
1862
|
-
algorithm: algo,
|
|
1863
|
-
digest: digestBase64,
|
|
1864
|
-
expectedDigest: (0, byte_encodings_base64.encodeBase64)(expectedDigest)
|
|
1865
|
-
});
|
|
1866
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1867
|
-
}
|
|
1868
|
-
matched = true;
|
|
1869
|
-
}
|
|
1870
|
-
if (!matched) {
|
|
1871
|
-
logger.debug("Failed to verify; no supported digest algorithm found. Supported: {supportedAlgorithms}; found: {algorithms}.", {
|
|
1872
|
-
supportedAlgorithms: Object.keys(supportedHashAlgorithms),
|
|
1873
|
-
algorithms: digests.map(([algo]) => algo)
|
|
1874
|
-
});
|
|
1875
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
const date = Temporal.Instant.from(new Date(dateHeader).toISOString());
|
|
1879
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
1880
|
-
if (timeWindow !== false) {
|
|
1881
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
1882
|
-
if (Temporal.Instant.compare(date, now.add(tw)) > 0) {
|
|
1883
|
-
logger.debug("Failed to verify; Date is too far in the future.", {
|
|
1884
|
-
date: date.toString(),
|
|
1885
|
-
now: now.toString()
|
|
1886
|
-
});
|
|
1887
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1888
|
-
} else if (Temporal.Instant.compare(date, now.subtract(tw)) < 0) {
|
|
1889
|
-
logger.debug("Failed to verify; Date is too far in the past.", {
|
|
1890
|
-
date: date.toString(),
|
|
1891
|
-
now: now.toString()
|
|
1892
|
-
});
|
|
1893
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
if (!("keyId" in sigValues)) {
|
|
1897
|
-
logger.debug("Failed to verify; no keyId field found in the Signature header.", { signature: sigHeader });
|
|
1898
|
-
return invalidSignatureResult(null);
|
|
1899
|
-
} else if (!("headers" in sigValues)) {
|
|
1900
|
-
logger.debug("Failed to verify; no headers field found in the Signature header.", { signature: sigHeader });
|
|
1901
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1902
|
-
} else if (!("signature" in sigValues)) {
|
|
1903
|
-
logger.debug("Failed to verify; no signature field found in the Signature header.", { signature: sigHeader });
|
|
1904
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1905
|
-
}
|
|
1906
|
-
if ("expires" in sigValues) {
|
|
1907
|
-
const expiresSeconds = parseInt(sigValues.expires);
|
|
1908
|
-
if (!Number.isInteger(expiresSeconds)) {
|
|
1909
|
-
logger.debug("Failed to verify; invalid expires field in the Signature header: {expires}.", {
|
|
1910
|
-
expires: sigValues.expires,
|
|
1911
|
-
signature: sigHeader
|
|
1912
|
-
});
|
|
1913
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1914
|
-
}
|
|
1915
|
-
const expires = Temporal.Instant.fromEpochMilliseconds(expiresSeconds * 1e3);
|
|
1916
|
-
if (Temporal.Instant.compare(now, expires) > 0) {
|
|
1917
|
-
logger.debug("Failed to verify; signature expired at {expires} (now: {now}).", {
|
|
1918
|
-
expires: expires.toString(),
|
|
1919
|
-
now: now.toString(),
|
|
1920
|
-
signature: sigHeader
|
|
1921
|
-
});
|
|
1922
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
if ("created" in sigValues) {
|
|
1926
|
-
const createdSeconds = parseInt(sigValues.created);
|
|
1927
|
-
if (!Number.isInteger(createdSeconds)) {
|
|
1928
|
-
logger.debug("Failed to verify; invalid created field in the Signature header: {created}.", {
|
|
1929
|
-
created: sigValues.created,
|
|
1930
|
-
signature: sigHeader
|
|
1931
|
-
});
|
|
1932
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1933
|
-
}
|
|
1934
|
-
if (timeWindow !== false) {
|
|
1935
|
-
const created = Temporal.Instant.fromEpochMilliseconds(createdSeconds * 1e3);
|
|
1936
|
-
const tw = timeWindow ?? { minutes: 1 };
|
|
1937
|
-
if (Temporal.Instant.compare(created, now.add(tw)) > 0) {
|
|
1938
|
-
logger.debug("Failed to verify; created is too far in the future.", {
|
|
1939
|
-
created: created.toString(),
|
|
1940
|
-
now: now.toString()
|
|
1941
|
-
});
|
|
1942
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1943
|
-
} else if (Temporal.Instant.compare(created, now.subtract(tw)) < 0) {
|
|
1944
|
-
logger.debug("Failed to verify; created is too far in the past.", {
|
|
1945
|
-
created: created.toString(),
|
|
1946
|
-
now: now.toString()
|
|
1947
|
-
});
|
|
1948
|
-
return invalidSignatureResult(parsedKeyId);
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
const { keyId, headers, signature } = sigValues;
|
|
1953
|
-
const keyIdUrl = parseKeyId(keyId);
|
|
1954
|
-
if (keyIdUrl == null) return invalidSignatureResult(null);
|
|
1955
|
-
span?.setAttribute("http_signatures.key_id", keyId);
|
|
1956
|
-
if ("algorithm" in sigValues) {
|
|
1957
|
-
span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
|
|
1958
|
-
const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
|
|
1959
|
-
if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
|
|
1960
|
-
}
|
|
1961
|
-
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
|
|
1962
|
-
documentLoader,
|
|
1963
|
-
contextLoader,
|
|
1964
|
-
keyCache,
|
|
1965
|
-
tracerProvider,
|
|
1966
|
-
meterProvider
|
|
1967
|
-
}));
|
|
1968
|
-
if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
|
|
1969
|
-
if (key == null) return invalidSignatureResult(keyIdUrl);
|
|
1970
|
-
const headerNames = headers.split(/\s+/g);
|
|
1971
|
-
if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
|
|
1972
|
-
logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
|
|
1973
|
-
return invalidSignatureResult(keyIdUrl);
|
|
1974
|
-
}
|
|
1975
|
-
if (body != null && !headerNames.includes("digest")) {
|
|
1976
|
-
logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
|
|
1977
|
-
return invalidSignatureResult(keyIdUrl);
|
|
1978
|
-
}
|
|
1979
|
-
const message = headerNames.map((name) => `${name}: ` + (name === "(request-target)" ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}` : name === "(created)" ? sigValues.created ?? "" : name === "(expires)" ? sigValues.expires ?? "" : name === "host" ? request.headers.get("host") ?? new URL(request.url).host : request.headers.get(name))).join("\n");
|
|
1980
|
-
const sig = (0, byte_encodings_base64.decodeBase64)(signature);
|
|
1981
|
-
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
|
|
1982
|
-
if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
|
|
1983
|
-
if (cached) {
|
|
1984
|
-
logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
|
|
1985
|
-
keyId,
|
|
1986
|
-
signature,
|
|
1987
|
-
message
|
|
1988
|
-
});
|
|
1989
|
-
return await verifyRequestDraft(originalRequest, span, metricsContext, {
|
|
1990
|
-
documentLoader,
|
|
1991
|
-
contextLoader,
|
|
1992
|
-
timeWindow,
|
|
1993
|
-
currentTime,
|
|
1994
|
-
keyCache: {
|
|
1995
|
-
get: () => Promise.resolve(void 0),
|
|
1996
|
-
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
1997
|
-
},
|
|
1998
|
-
meterProvider,
|
|
1999
|
-
tracerProvider
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
|
|
2003
|
-
keyId,
|
|
2004
|
-
signature,
|
|
2005
|
-
message
|
|
2006
|
-
});
|
|
2007
|
-
return invalidSignatureResult(keyIdUrl);
|
|
2008
|
-
}
|
|
2009
|
-
return {
|
|
2010
|
-
verified: true,
|
|
2011
|
-
key
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
/**
|
|
2015
|
-
* RFC 9421 map of algorithm identifiers to WebCrypto algorithms
|
|
2016
|
-
*/
|
|
2017
|
-
const rfc9421AlgorithmMap = {
|
|
2018
|
-
"rsa-v1_5-sha256": {
|
|
2019
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
2020
|
-
hash: "SHA-256"
|
|
2021
|
-
},
|
|
2022
|
-
"rsa-v1_5-sha512": {
|
|
2023
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
2024
|
-
hash: "SHA-512"
|
|
2025
|
-
},
|
|
2026
|
-
"rsa-pss-sha512": {
|
|
2027
|
-
name: "RSA-PSS",
|
|
2028
|
-
hash: "SHA-512"
|
|
2029
|
-
},
|
|
2030
|
-
"ecdsa-p256-sha256": {
|
|
2031
|
-
name: "ECDSA",
|
|
2032
|
-
hash: "SHA-256"
|
|
2033
|
-
},
|
|
2034
|
-
"ecdsa-p384-sha384": {
|
|
2035
|
-
name: "ECDSA",
|
|
2036
|
-
hash: "SHA-384"
|
|
2037
|
-
},
|
|
2038
|
-
"ed25519": { name: "Ed25519" }
|
|
2039
|
-
};
|
|
2040
|
-
/**
|
|
2041
|
-
* Verifies a Content-Digest header according to RFC 9421.
|
|
2042
|
-
* @param digestHeader The Content-Digest header value.
|
|
2043
|
-
* @param body The message body to verify against.
|
|
2044
|
-
* @returns Whether the digest is valid.
|
|
2045
|
-
*/
|
|
2046
|
-
async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
2047
|
-
const digests = digestHeader.split(",").map((pair) => {
|
|
2048
|
-
pair = pair.trim();
|
|
2049
|
-
const pos = pair.indexOf("=");
|
|
2050
|
-
const algo = pos < 0 ? pair : pair.slice(0, pos);
|
|
2051
|
-
const value = pos < 0 ? "" : pair.slice(pos + 1);
|
|
2052
|
-
return {
|
|
2053
|
-
algo: algo.trim().toLowerCase(),
|
|
2054
|
-
value: value.trim()
|
|
2055
|
-
};
|
|
2056
|
-
});
|
|
2057
|
-
for (const { algo, value } of digests) {
|
|
2058
|
-
let hashAlgo;
|
|
2059
|
-
if (algo === "sha-256") hashAlgo = "SHA-256";
|
|
2060
|
-
else if (algo === "sha-512") hashAlgo = "SHA-512";
|
|
2061
|
-
else continue;
|
|
2062
|
-
const base64Match = value.match(/^:([^:]+):$/);
|
|
2063
|
-
if (!base64Match) continue;
|
|
2064
|
-
let digest;
|
|
2065
|
-
try {
|
|
2066
|
-
digest = (0, byte_encodings_base64.decodeBase64)(base64Match[1]);
|
|
2067
|
-
} catch {
|
|
2068
|
-
continue;
|
|
2069
|
-
}
|
|
2070
|
-
const calculatedDigest = await crypto.subtle.digest(hashAlgo, body);
|
|
2071
|
-
if (timingSafeEqual(digest, new Uint8Array(calculatedDigest))) return true;
|
|
2072
|
-
}
|
|
2073
|
-
return false;
|
|
2074
|
-
}
|
|
2075
|
-
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
2076
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
2077
|
-
"fedify",
|
|
2078
|
-
"sig",
|
|
2079
|
-
"http"
|
|
2080
|
-
]);
|
|
2081
|
-
if (request.bodyUsed) {
|
|
2082
|
-
logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
|
|
2083
|
-
return noSignatureResult();
|
|
2084
|
-
} else if (request.body?.locked) {
|
|
2085
|
-
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
2086
|
-
return noSignatureResult();
|
|
2087
|
-
}
|
|
2088
|
-
const originalRequest = request;
|
|
2089
|
-
request = request.clone();
|
|
2090
|
-
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
2091
|
-
if (!signatureInputHeader) {
|
|
2092
|
-
logger.debug("Failed to verify; no Signature-Input header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
2093
|
-
return noSignatureResult();
|
|
2094
|
-
}
|
|
2095
|
-
const signatureHeader = request.headers.get("Signature");
|
|
2096
|
-
if (!signatureHeader) {
|
|
2097
|
-
logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
2098
|
-
return noSignatureResult();
|
|
2099
|
-
}
|
|
2100
|
-
const signatureInputs = parseRfc9421SignatureInput(signatureInputHeader);
|
|
2101
|
-
logger.debug("Parsed Signature-Input header: {signatureInputs}", { signatureInputs });
|
|
2102
|
-
const signatures = parseRfc9421Signature(signatureHeader);
|
|
2103
|
-
const signatureNames = Object.keys(signatureInputs);
|
|
2104
|
-
if (signatureNames.length === 0) {
|
|
2105
|
-
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
2106
|
-
return invalidSignatureResult(null);
|
|
2107
|
-
}
|
|
2108
|
-
let failure = noSignatureResult();
|
|
2109
|
-
let failureAlgorithm;
|
|
2110
|
-
const setFailure = (result, algorithm) => {
|
|
2111
|
-
failure = result;
|
|
2112
|
-
failureAlgorithm = algorithm;
|
|
2113
|
-
};
|
|
2114
|
-
for (const sigName of signatureNames) {
|
|
2115
|
-
if (!signatures[sigName]) {
|
|
2116
|
-
setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
|
|
2117
|
-
continue;
|
|
2118
|
-
}
|
|
2119
|
-
const sigInput = signatureInputs[sigName];
|
|
2120
|
-
const sigBytes = signatures[sigName];
|
|
2121
|
-
const keyId = parseKeyId(sigInput.keyId);
|
|
2122
|
-
if (!sigInput.keyId) {
|
|
2123
|
-
logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
|
|
2124
|
-
signatureName: sigName,
|
|
2125
|
-
signatureInput: signatureInputHeader
|
|
2126
|
-
});
|
|
2127
|
-
setFailure(invalidSignatureResult(null));
|
|
2128
|
-
continue;
|
|
2129
|
-
}
|
|
2130
|
-
if (!sigInput.created) {
|
|
2131
|
-
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
2132
|
-
signatureName: sigName,
|
|
2133
|
-
signatureInput: signatureInputHeader
|
|
2134
|
-
});
|
|
2135
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2136
|
-
continue;
|
|
2137
|
-
}
|
|
2138
|
-
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
2139
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
2140
|
-
if (timeWindow !== false) {
|
|
2141
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
2142
|
-
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
2143
|
-
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
2144
|
-
created: signatureCreated.toString(),
|
|
2145
|
-
now: now.toString()
|
|
2146
|
-
});
|
|
2147
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2148
|
-
continue;
|
|
2149
|
-
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
2150
|
-
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
2151
|
-
created: signatureCreated.toString(),
|
|
2152
|
-
now: now.toString()
|
|
2153
|
-
});
|
|
2154
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2155
|
-
continue;
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
|
|
2159
|
-
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
2160
|
-
if (!contentDigestHeader) {
|
|
2161
|
-
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
2162
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2163
|
-
continue;
|
|
2164
|
-
}
|
|
2165
|
-
if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
|
|
2166
|
-
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
2167
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2168
|
-
continue;
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
span?.setAttribute("http_signatures.key_id", sigInput.keyId);
|
|
2172
|
-
span?.setAttribute("http_signatures.created", sigInput.created.toString());
|
|
2173
|
-
if (keyId == null) {
|
|
2174
|
-
setFailure(invalidSignatureResult(null));
|
|
2175
|
-
continue;
|
|
2176
|
-
}
|
|
2177
|
-
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
2178
|
-
documentLoader,
|
|
2179
|
-
contextLoader,
|
|
2180
|
-
keyCache,
|
|
2181
|
-
tracerProvider,
|
|
2182
|
-
meterProvider
|
|
2183
|
-
}));
|
|
2184
|
-
if (fetchError != null) {
|
|
2185
|
-
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
2186
|
-
continue;
|
|
2187
|
-
}
|
|
2188
|
-
if (!key) {
|
|
2189
|
-
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
2190
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
let alg = sigInput.alg?.toLowerCase();
|
|
2194
|
-
if (alg == null) {
|
|
2195
|
-
if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
|
|
2196
|
-
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
2197
|
-
else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
|
|
2198
|
-
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
2199
|
-
}
|
|
2200
|
-
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
2201
|
-
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
2202
|
-
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
2203
|
-
if (!algorithm) {
|
|
2204
|
-
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
2205
|
-
algorithm: sigInput.alg,
|
|
2206
|
-
supported: Object.keys(rfc9421AlgorithmMap)
|
|
2207
|
-
});
|
|
2208
|
-
setFailure(invalidSignatureResult(keyId));
|
|
2209
|
-
continue;
|
|
2210
|
-
}
|
|
2211
|
-
let signatureBase;
|
|
2212
|
-
try {
|
|
2213
|
-
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
2214
|
-
} catch (error) {
|
|
2215
|
-
logger.debug("Failed to create signature base for verification: {error}", {
|
|
2216
|
-
error,
|
|
2217
|
-
signatureInput: sigInput
|
|
2218
|
-
});
|
|
2219
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
2220
|
-
continue;
|
|
2221
|
-
}
|
|
2222
|
-
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
2223
|
-
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
2224
|
-
try {
|
|
2225
|
-
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
|
|
2226
|
-
metricsContext.algorithm = candidateAlgorithm;
|
|
2227
|
-
return {
|
|
2228
|
-
verified: true,
|
|
2229
|
-
key,
|
|
2230
|
-
signatureLabel: sigName
|
|
2231
|
-
};
|
|
2232
|
-
} else if (cached) {
|
|
2233
|
-
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
2234
|
-
return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
|
|
2235
|
-
documentLoader,
|
|
2236
|
-
contextLoader,
|
|
2237
|
-
timeWindow,
|
|
2238
|
-
currentTime,
|
|
2239
|
-
keyCache: {
|
|
2240
|
-
get: () => Promise.resolve(void 0),
|
|
2241
|
-
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
2242
|
-
},
|
|
2243
|
-
spec: "rfc9421",
|
|
2244
|
-
meterProvider,
|
|
2245
|
-
tracerProvider
|
|
2246
|
-
});
|
|
2247
|
-
} else {
|
|
2248
|
-
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
2249
|
-
keyId: sigInput.keyId,
|
|
2250
|
-
signatureBase
|
|
2251
|
-
});
|
|
2252
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
2253
|
-
}
|
|
2254
|
-
} catch (error) {
|
|
2255
|
-
logger.debug("Error during signature verification: {error}", {
|
|
2256
|
-
error,
|
|
2257
|
-
keyId: sigInput.keyId,
|
|
2258
|
-
algorithm: sigInput.alg
|
|
2259
|
-
});
|
|
2260
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
metricsContext.algorithm = failureAlgorithm;
|
|
2264
|
-
return failure;
|
|
2265
|
-
}
|
|
2266
|
-
/**
|
|
2267
|
-
* Helper function to create a new Request for redirect handling.
|
|
2268
|
-
* @param request The original request.
|
|
2269
|
-
* @param location The redirect location.
|
|
2270
|
-
* @param body The request body as ArrayBuffer or null.
|
|
2271
|
-
* @returns A new Request object for the redirect.
|
|
2272
|
-
*/
|
|
2273
|
-
function createRedirectRequest(request, location, body) {
|
|
2274
|
-
const url = new URL(location, request.url);
|
|
2275
|
-
return new Request(url, {
|
|
2276
|
-
method: request.method,
|
|
2277
|
-
headers: request.headers,
|
|
2278
|
-
body,
|
|
2279
|
-
redirect: "manual",
|
|
2280
|
-
signal: request.signal,
|
|
2281
|
-
mode: request.mode,
|
|
2282
|
-
credentials: request.credentials,
|
|
2283
|
-
referrer: request.referrer,
|
|
2284
|
-
referrerPolicy: request.referrerPolicy,
|
|
2285
|
-
integrity: request.integrity,
|
|
2286
|
-
keepalive: request.keepalive,
|
|
2287
|
-
cache: request.cache
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
2291
|
-
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
2292
|
-
for (let attempt = 1;; attempt++) try {
|
|
2293
|
-
return await fetch(signedRequest, {
|
|
2294
|
-
redirect: "manual",
|
|
2295
|
-
signal
|
|
2296
|
-
});
|
|
2297
|
-
} catch (error) {
|
|
2298
|
-
const abortedSignal = getAbortedSignal(signal, request.signal, signedRequest.signal);
|
|
2299
|
-
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
2300
|
-
if (isAbortError(error)) throw error;
|
|
2301
|
-
if (attempt >= maxAttempts) throw createFetchError(request.url, error);
|
|
2302
|
-
await sleep(DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS, signal, request.signal, signedRequest.signal);
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
function createFetchError(url, cause) {
|
|
2306
|
-
const error = new _fedify_vocab_runtime.FetchError(url, cause instanceof Error ? cause.message : String(cause));
|
|
2307
|
-
error.cause = cause;
|
|
2308
|
-
return error;
|
|
2309
|
-
}
|
|
2310
|
-
function isAbortError(error) {
|
|
2311
|
-
return error instanceof Error && error.name === "AbortError";
|
|
2312
|
-
}
|
|
2313
|
-
async function sleep(ms, ...signals) {
|
|
2314
|
-
const abortSignals = signals.filter((signal) => signal != null);
|
|
2315
|
-
const abortedSignal = getAbortedSignal(...abortSignals);
|
|
2316
|
-
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
2317
|
-
if (abortSignals.length < 1) {
|
|
2318
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
await new Promise((resolve, reject) => {
|
|
2322
|
-
const removeAbortListeners = () => {
|
|
2323
|
-
for (const signal of abortSignals) signal.removeEventListener("abort", handleAbort);
|
|
2324
|
-
};
|
|
2325
|
-
const timeout = setTimeout(() => {
|
|
2326
|
-
removeAbortListeners();
|
|
2327
|
-
resolve();
|
|
2328
|
-
}, ms);
|
|
2329
|
-
function handleAbort(event) {
|
|
2330
|
-
clearTimeout(timeout);
|
|
2331
|
-
removeAbortListeners();
|
|
2332
|
-
reject(getAbortReason(event.currentTarget));
|
|
2333
|
-
}
|
|
2334
|
-
for (const signal of abortSignals) signal.addEventListener("abort", handleAbort, { once: true });
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
function getAbortedSignal(...signals) {
|
|
2338
|
-
return signals.find((signal) => signal?.aborted);
|
|
2339
|
-
}
|
|
2340
|
-
function getAbortReason(signal) {
|
|
2341
|
-
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
2342
|
-
}
|
|
2343
|
-
/**
|
|
2344
|
-
* Performs a double-knock request to the given URL. For the details of
|
|
2345
|
-
* double-knocking, see
|
|
2346
|
-
* <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
|
|
2347
|
-
* @param request The request to send.
|
|
2348
|
-
* @param identity The identity to use for signing the request.
|
|
2349
|
-
* @param options The options for double-knock requests.
|
|
2350
|
-
* @returns The response to the request.
|
|
2351
|
-
* @since 1.6.0
|
|
2352
|
-
*/
|
|
2353
|
-
async function doubleKnock(request, identity, options = {}) {
|
|
2354
|
-
return await doubleKnockInternal(request, identity, options);
|
|
2355
|
-
}
|
|
2356
|
-
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
2357
|
-
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
2358
|
-
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
2359
|
-
visited.add(request.url);
|
|
2360
|
-
const origin = new URL(request.url).origin;
|
|
2361
|
-
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
2362
|
-
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
2363
|
-
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
2364
|
-
spec: firstTrySpec,
|
|
2365
|
-
tracerProvider,
|
|
2366
|
-
body
|
|
2367
|
-
});
|
|
2368
|
-
log?.(signedRequest);
|
|
2369
|
-
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
2370
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
2371
|
-
if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
2372
|
-
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|
|
2373
|
-
if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
2374
|
-
return doubleKnockInternal(redirectRequest, identity, {
|
|
2375
|
-
...options,
|
|
2376
|
-
body
|
|
2377
|
-
}, redirected + 1, visited);
|
|
2378
|
-
} else if (response.status === 400 || response.status === 401 || response.status > 401) {
|
|
2379
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
2380
|
-
"fedify",
|
|
2381
|
-
"sig",
|
|
2382
|
-
"http"
|
|
2383
|
-
]);
|
|
2384
|
-
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
2385
|
-
if (acceptSigHeader != null) {
|
|
2386
|
-
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
2387
|
-
const localKeyId = identity.keyId.href;
|
|
2388
|
-
const localAlg = "rsa-v1_5-sha256";
|
|
2389
|
-
let fulfilled = false;
|
|
2390
|
-
let challengeRequest;
|
|
2391
|
-
for (const entry of entries) {
|
|
2392
|
-
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
2393
|
-
if (rfc9421 == null) continue;
|
|
2394
|
-
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
2395
|
-
label: rfc9421.label,
|
|
2396
|
-
components: rfc9421.components
|
|
2397
|
-
});
|
|
2398
|
-
try {
|
|
2399
|
-
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
2400
|
-
spec: "rfc9421",
|
|
2401
|
-
tracerProvider,
|
|
2402
|
-
body,
|
|
2403
|
-
rfc9421
|
|
2404
|
-
});
|
|
2405
|
-
fulfilled = true;
|
|
2406
|
-
} catch (error) {
|
|
2407
|
-
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
2408
|
-
label: entry.label,
|
|
2409
|
-
error
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
if (fulfilled && challengeRequest != null) {
|
|
2414
|
-
signedRequest = challengeRequest;
|
|
2415
|
-
log?.(signedRequest);
|
|
2416
|
-
response = await fetch(signedRequest, {
|
|
2417
|
-
redirect: "manual",
|
|
2418
|
-
signal
|
|
2419
|
-
});
|
|
2420
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(createRedirectRequest(request, response.headers.get("Location"), body), identity, {
|
|
2421
|
-
...options,
|
|
2422
|
-
body
|
|
2423
|
-
});
|
|
2424
|
-
}
|
|
2425
|
-
if (fulfilled && response.status < 300) {
|
|
2426
|
-
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
2427
|
-
return response;
|
|
2428
|
-
}
|
|
2429
|
-
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
2430
|
-
}
|
|
2431
|
-
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
2432
|
-
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
2433
|
-
spec: firstTrySpec,
|
|
2434
|
-
secondSpec: spec,
|
|
2435
|
-
status: response.status,
|
|
2436
|
-
statusText: response.statusText
|
|
2437
|
-
});
|
|
2438
|
-
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
2439
|
-
spec,
|
|
2440
|
-
tracerProvider,
|
|
2441
|
-
body
|
|
2442
|
-
});
|
|
2443
|
-
log?.(signedRequest);
|
|
2444
|
-
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
2445
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
2446
|
-
if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
2447
|
-
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|
|
2448
|
-
if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
2449
|
-
return doubleKnockInternal(redirectRequest, identity, {
|
|
2450
|
-
...options,
|
|
2451
|
-
body
|
|
2452
|
-
}, redirected + 1, visited);
|
|
2453
|
-
} else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
|
|
2454
|
-
} else await specDeterminer?.rememberSpec(origin, firstTrySpec);
|
|
2455
|
-
return response;
|
|
2456
|
-
}
|
|
2457
|
-
/**
|
|
2458
|
-
* Performs a timing-safe equality comparison between two `Uint8Array` values.
|
|
2459
|
-
*
|
|
2460
|
-
* This function is designed to take a constant amount of time to execute,
|
|
2461
|
-
* dependent only on the length of the longer of the two arrays,
|
|
2462
|
-
* regardless of where the first difference in bytes occurs. This helps
|
|
2463
|
-
* prevent timing attacks.
|
|
2464
|
-
*
|
|
2465
|
-
* @param a The first bytes.
|
|
2466
|
-
* @param b The second bytes.
|
|
2467
|
-
* @returns `true` if the arrays are of the same length and contain the same
|
|
2468
|
-
* bytes, `false` otherwise.
|
|
2469
|
-
* @since 1.6.0
|
|
2470
|
-
*/
|
|
2471
|
-
function timingSafeEqual(a, b) {
|
|
2472
|
-
const lenA = a.length;
|
|
2473
|
-
const lenB = b.length;
|
|
2474
|
-
const commonLength = Math.max(lenA, lenB);
|
|
2475
|
-
let result = 0;
|
|
2476
|
-
for (let i = 0; i < commonLength; i++) {
|
|
2477
|
-
const byteA = i < lenA ? a[i] : 0;
|
|
2478
|
-
const byteB = i < lenB ? b[i] : 0;
|
|
2479
|
-
result |= byteA ^ byteB;
|
|
2480
|
-
}
|
|
2481
|
-
result |= lenA ^ lenB;
|
|
2482
|
-
return result === 0;
|
|
2483
|
-
}
|
|
2484
|
-
//#endregion
|
|
2485
|
-
Object.defineProperty(exports, "doubleKnock", {
|
|
2486
|
-
enumerable: true,
|
|
2487
|
-
get: function() {
|
|
2488
|
-
return doubleKnock;
|
|
2489
|
-
}
|
|
2490
|
-
});
|
|
2491
|
-
Object.defineProperty(exports, "exportJwk", {
|
|
2492
|
-
enumerable: true,
|
|
2493
|
-
get: function() {
|
|
2494
|
-
return exportJwk;
|
|
2495
|
-
}
|
|
2496
|
-
});
|
|
2497
|
-
Object.defineProperty(exports, "fetchKey", {
|
|
2498
|
-
enumerable: true,
|
|
2499
|
-
get: function() {
|
|
2500
|
-
return fetchKey;
|
|
2501
|
-
}
|
|
2502
|
-
});
|
|
2503
|
-
Object.defineProperty(exports, "fetchKeyDetailed", {
|
|
2504
|
-
enumerable: true,
|
|
2505
|
-
get: function() {
|
|
2506
|
-
return fetchKeyDetailed;
|
|
2507
|
-
}
|
|
2508
|
-
});
|
|
2509
|
-
Object.defineProperty(exports, "formatAcceptSignature", {
|
|
2510
|
-
enumerable: true,
|
|
2511
|
-
get: function() {
|
|
2512
|
-
return formatAcceptSignature;
|
|
2513
|
-
}
|
|
2514
|
-
});
|
|
2515
|
-
Object.defineProperty(exports, "fulfillAcceptSignature", {
|
|
2516
|
-
enumerable: true,
|
|
2517
|
-
get: function() {
|
|
2518
|
-
return fulfillAcceptSignature;
|
|
2519
|
-
}
|
|
2520
|
-
});
|
|
2521
|
-
Object.defineProperty(exports, "generateCryptoKeyPair", {
|
|
2522
|
-
enumerable: true,
|
|
2523
|
-
get: function() {
|
|
2524
|
-
return generateCryptoKeyPair;
|
|
2525
|
-
}
|
|
2526
|
-
});
|
|
2527
|
-
Object.defineProperty(exports, "getDurationMs", {
|
|
2528
|
-
enumerable: true,
|
|
2529
|
-
get: function() {
|
|
2530
|
-
return getDurationMs;
|
|
2531
|
-
}
|
|
2532
|
-
});
|
|
2533
|
-
Object.defineProperty(exports, "getFederationMetrics", {
|
|
2534
|
-
enumerable: true,
|
|
2535
|
-
get: function() {
|
|
2536
|
-
return getFederationMetrics;
|
|
2537
|
-
}
|
|
2538
|
-
});
|
|
2539
|
-
Object.defineProperty(exports, "getRemoteHost", {
|
|
2540
|
-
enumerable: true,
|
|
2541
|
-
get: function() {
|
|
2542
|
-
return getRemoteHost;
|
|
2543
|
-
}
|
|
2544
|
-
});
|
|
2545
|
-
Object.defineProperty(exports, "importJwk", {
|
|
2546
|
-
enumerable: true,
|
|
2547
|
-
get: function() {
|
|
2548
|
-
return importJwk;
|
|
2549
|
-
}
|
|
2550
|
-
});
|
|
2551
|
-
Object.defineProperty(exports, "instrumentDocumentLoader", {
|
|
2552
|
-
enumerable: true,
|
|
2553
|
-
get: function() {
|
|
2554
|
-
return instrumentDocumentLoader;
|
|
2555
|
-
}
|
|
2556
|
-
});
|
|
2557
|
-
Object.defineProperty(exports, "isAbortError", {
|
|
2558
|
-
enumerable: true,
|
|
2559
|
-
get: function() {
|
|
2560
|
-
return isAbortError$1;
|
|
2561
|
-
}
|
|
2562
|
-
});
|
|
2563
|
-
Object.defineProperty(exports, "measureSignatureKeyFetch", {
|
|
2564
|
-
enumerable: true,
|
|
2565
|
-
get: function() {
|
|
2566
|
-
return measureSignatureKeyFetch;
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2569
|
-
Object.defineProperty(exports, "name", {
|
|
2570
|
-
enumerable: true,
|
|
2571
|
-
get: function() {
|
|
2572
|
-
return name;
|
|
2573
|
-
}
|
|
2574
|
-
});
|
|
2575
|
-
Object.defineProperty(exports, "parseAcceptSignature", {
|
|
2576
|
-
enumerable: true,
|
|
2577
|
-
get: function() {
|
|
2578
|
-
return parseAcceptSignature;
|
|
2579
|
-
}
|
|
2580
|
-
});
|
|
2581
|
-
Object.defineProperty(exports, "parseRfc9421SignatureInput", {
|
|
2582
|
-
enumerable: true,
|
|
2583
|
-
get: function() {
|
|
2584
|
-
return parseRfc9421SignatureInput;
|
|
2585
|
-
}
|
|
2586
|
-
});
|
|
2587
|
-
Object.defineProperty(exports, "recordCircuitBreakerStateChange", {
|
|
2588
|
-
enumerable: true,
|
|
2589
|
-
get: function() {
|
|
2590
|
-
return recordCircuitBreakerStateChange;
|
|
2591
|
-
}
|
|
2592
|
-
});
|
|
2593
|
-
Object.defineProperty(exports, "recordCollectionDispatchDuration", {
|
|
2594
|
-
enumerable: true,
|
|
2595
|
-
get: function() {
|
|
2596
|
-
return recordCollectionDispatchDuration;
|
|
2597
|
-
}
|
|
2598
|
-
});
|
|
2599
|
-
Object.defineProperty(exports, "recordCollectionPageItems", {
|
|
2600
|
-
enumerable: true,
|
|
2601
|
-
get: function() {
|
|
2602
|
-
return recordCollectionPageItems;
|
|
2603
|
-
}
|
|
2604
|
-
});
|
|
2605
|
-
Object.defineProperty(exports, "recordCollectionRequest", {
|
|
2606
|
-
enumerable: true,
|
|
2607
|
-
get: function() {
|
|
2608
|
-
return recordCollectionRequest;
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
Object.defineProperty(exports, "recordCollectionTotalItems", {
|
|
2612
|
-
enumerable: true,
|
|
2613
|
-
get: function() {
|
|
2614
|
-
return recordCollectionTotalItems;
|
|
2615
|
-
}
|
|
2616
|
-
});
|
|
2617
|
-
Object.defineProperty(exports, "recordDocumentCache", {
|
|
2618
|
-
enumerable: true,
|
|
2619
|
-
get: function() {
|
|
2620
|
-
return recordDocumentCache;
|
|
2621
|
-
}
|
|
2622
|
-
});
|
|
2623
|
-
Object.defineProperty(exports, "recordFanoutRecipients", {
|
|
2624
|
-
enumerable: true,
|
|
2625
|
-
get: function() {
|
|
2626
|
-
return recordFanoutRecipients;
|
|
2627
|
-
}
|
|
2628
|
-
});
|
|
2629
|
-
Object.defineProperty(exports, "recordInboxActivity", {
|
|
2630
|
-
enumerable: true,
|
|
2631
|
-
get: function() {
|
|
2632
|
-
return recordInboxActivity;
|
|
2633
|
-
}
|
|
2634
|
-
});
|
|
2635
|
-
Object.defineProperty(exports, "recordOutboxActivity", {
|
|
2636
|
-
enumerable: true,
|
|
2637
|
-
get: function() {
|
|
2638
|
-
return recordOutboxActivity;
|
|
2639
|
-
}
|
|
2640
|
-
});
|
|
2641
|
-
Object.defineProperty(exports, "recordOutboxEnqueue", {
|
|
2642
|
-
enumerable: true,
|
|
2643
|
-
get: function() {
|
|
2644
|
-
return recordOutboxEnqueue;
|
|
2645
|
-
}
|
|
2646
|
-
});
|
|
2647
|
-
Object.defineProperty(exports, "recordWebFingerHandle", {
|
|
2648
|
-
enumerable: true,
|
|
2649
|
-
get: function() {
|
|
2650
|
-
return recordWebFingerHandle;
|
|
2651
|
-
}
|
|
2652
|
-
});
|
|
2653
|
-
Object.defineProperty(exports, "registerQueueDepthGauge", {
|
|
2654
|
-
enumerable: true,
|
|
2655
|
-
get: function() {
|
|
2656
|
-
return registerQueueDepthGauge;
|
|
2657
|
-
}
|
|
2658
|
-
});
|
|
2659
|
-
Object.defineProperty(exports, "signRequest", {
|
|
2660
|
-
enumerable: true,
|
|
2661
|
-
get: function() {
|
|
2662
|
-
return signRequest;
|
|
2663
|
-
}
|
|
2664
|
-
});
|
|
2665
|
-
Object.defineProperty(exports, "validateAcceptSignature", {
|
|
2666
|
-
enumerable: true,
|
|
2667
|
-
get: function() {
|
|
2668
|
-
return validateAcceptSignature;
|
|
2669
|
-
}
|
|
2670
|
-
});
|
|
2671
|
-
Object.defineProperty(exports, "validateCryptoKey", {
|
|
2672
|
-
enumerable: true,
|
|
2673
|
-
get: function() {
|
|
2674
|
-
return validateCryptoKey;
|
|
2675
|
-
}
|
|
2676
|
-
});
|
|
2677
|
-
Object.defineProperty(exports, "verifyRequest", {
|
|
2678
|
-
enumerable: true,
|
|
2679
|
-
get: function() {
|
|
2680
|
-
return verifyRequest;
|
|
2681
|
-
}
|
|
2682
|
-
});
|
|
2683
|
-
Object.defineProperty(exports, "verifyRequestDetailed", {
|
|
2684
|
-
enumerable: true,
|
|
2685
|
-
get: function() {
|
|
2686
|
-
return verifyRequestDetailed;
|
|
2687
|
-
}
|
|
2688
|
-
});
|
|
2689
|
-
Object.defineProperty(exports, "version", {
|
|
2690
|
-
enumerable: true,
|
|
2691
|
-
get: function() {
|
|
2692
|
-
return version;
|
|
2693
|
-
}
|
|
2694
|
-
});
|