@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2492
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 +3 -0
- package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
- package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
- package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
- package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
- package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
- package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
- package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
- package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
- package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
- package/dist/docloader-DCZkiTeF.mjs +136 -0
- package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +6 -3
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +22 -7
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.d.mts +2 -0
- package/dist/federation/inbox-actorless.test.mjs +487 -0
- package/dist/federation/inbox-authentication.test.d.mts +2 -0
- package/dist/federation/inbox-authentication.test.mjs +116 -0
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.d.mts +2 -0
- package/dist/federation/inbox-view.test.mjs +369 -0
- package/dist/federation/inbox.test.mjs +22 -12
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +470 -35
- package/dist/federation/mod.cjs +2 -1
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +8 -2
- package/dist/federation/mq.test.mjs +36 -1
- package/dist/federation/outgoing-proof.test.mjs +9 -6
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +13 -7
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +502 -93
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
- package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
- package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
- package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
- package/dist/inbox-BjihTKLo.mjs +312 -0
- package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
- package/dist/inbox-view-D-skypg-.mjs +385 -0
- package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
- package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
- package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
- package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
- package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
- package/dist/ld-CWVpo_NM.d.cts +250 -0
- package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
- package/dist/ld-Inagww4R.d.ts +250 -0
- package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
- package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
- package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
- package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
- package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
- package/dist/mod-B4kBw1-8.d.ts +154 -0
- package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
- package/dist/mod-C1nT74IG.d.cts +154 -0
- package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
- package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
- package/dist/mod.cjs +6 -5
- package/dist/mod.d.cts +9 -9
- package/dist/mod.d.ts +9 -9
- package/dist/mod.js +6 -6
- package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +2 -2
- 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/otel/exporter.test.mjs +257 -1
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
- package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
- package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
- package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
- package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
- package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
- package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +553 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- 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 +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +45 -5
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +288 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
- package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
- package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +344 -36
- 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/dist/docloader-B-0nqOB1.mjs +0 -91
- package/dist/inbox-CHvMaIFX.mjs +0 -188
- package/dist/mod-BRpER-B0.d.ts +0 -320
- package/dist/mod-xAfaLS4T.d.cts +0 -320
- package/dist/owner-BF0L9cMt.d.cts +0 -78
- package/dist/owner-BSzr5-I8.d.ts +0 -78
|
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
|
17
17
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
18
18
|
//#region deno.json
|
|
19
19
|
var name = "@fedify/fedify";
|
|
20
|
-
var version = "2.5.0-dev.
|
|
20
|
+
var version = "2.5.0-dev.2492+4648c04e";
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/sig/portable-key-id.ts
|
|
23
23
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -5184,6 +5184,7 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
|
|
|
5184
5184
|
actorId: actorId.href,
|
|
5185
5185
|
error
|
|
5186
5186
|
});
|
|
5187
|
+
if (propagateContextFetchErrors) throw new KeyContextFetchError(error);
|
|
5187
5188
|
return null;
|
|
5188
5189
|
}
|
|
5189
5190
|
let object;
|
|
@@ -6130,8 +6131,26 @@ function createMemoizedDocumentLoader(documentLoader) {
|
|
|
6130
6131
|
return cloneRemoteDocument(await remoteDocument);
|
|
6131
6132
|
};
|
|
6132
6133
|
}
|
|
6134
|
+
const effectiveContextLoaderPolicies = /* @__PURE__ */ new WeakMap();
|
|
6135
|
+
/**
|
|
6136
|
+
* Marks a context loader as the *effective* loader of a policy, i.e., one
|
|
6137
|
+
* that already applies what {@link wrapContextLoaderForJsonLd} (`"standard"`)
|
|
6138
|
+
* or {@link getNormalizationContextLoader} (`"normalization"`) would add.
|
|
6139
|
+
* Those functions return a marked loader as is instead of wrapping it again,
|
|
6140
|
+
* so that a caller can observe every response a parse actually uses,
|
|
6141
|
+
* including Fedify's own baseline contexts.
|
|
6142
|
+
* @param loader The effective context loader.
|
|
6143
|
+
* @param policy The policy the loader already applies.
|
|
6144
|
+
* @returns The given loader.
|
|
6145
|
+
* @internal
|
|
6146
|
+
*/
|
|
6147
|
+
function markEffectiveContextLoader(loader, policy) {
|
|
6148
|
+
effectiveContextLoaderPolicies.set(loader, policy);
|
|
6149
|
+
return loader;
|
|
6150
|
+
}
|
|
6133
6151
|
/** @internal */
|
|
6134
6152
|
function wrapContextLoaderForJsonLd(contextLoader) {
|
|
6153
|
+
if (contextLoader != null && effectiveContextLoaderPolicies.has(contextLoader)) return contextLoader;
|
|
6135
6154
|
const loader = contextLoader ?? builtInContextLoader;
|
|
6136
6155
|
return async (url, options) => {
|
|
6137
6156
|
try {
|
|
@@ -6145,6 +6164,7 @@ function wrapContextLoaderForJsonLd(contextLoader) {
|
|
|
6145
6164
|
}
|
|
6146
6165
|
/** @internal */
|
|
6147
6166
|
function getNormalizationContextLoader(contextLoader) {
|
|
6167
|
+
if (contextLoader != null && effectiveContextLoaderPolicies.get(contextLoader) === "normalization") return contextLoader;
|
|
6148
6168
|
const loader = wrapContextLoaderForJsonLd(contextLoader);
|
|
6149
6169
|
return createMemoizedDocumentLoader(async (url, options) => {
|
|
6150
6170
|
if (URL.canParse(url)) {
|
|
@@ -6597,10 +6617,17 @@ function getLdSignatureObject(jsonLd) {
|
|
|
6597
6617
|
* @returns `true` if the document is authentic; `false` otherwise.
|
|
6598
6618
|
*/
|
|
6599
6619
|
async function verifyJsonLd(jsonLd, options = {}) {
|
|
6600
|
-
return await verifyJsonLdInternal(jsonLd, options, true);
|
|
6620
|
+
return (await verifyJsonLdInternal(jsonLd, options, true)).verified;
|
|
6601
6621
|
}
|
|
6602
|
-
/**
|
|
6603
|
-
|
|
6622
|
+
/**
|
|
6623
|
+
* Same as {@link verifyCompactJsonLd}, but also returns the key that verified
|
|
6624
|
+
* the signature, whose `owner` {@link fetchKey} has confirmed.
|
|
6625
|
+
* @param jsonLd The compacted JSON-LD document to verify.
|
|
6626
|
+
* @param options Options for verifying the JSON-LD document.
|
|
6627
|
+
* @returns The result.
|
|
6628
|
+
* @internal
|
|
6629
|
+
*/
|
|
6630
|
+
async function verifyCompactJsonLdDetailed(jsonLd, options = {}) {
|
|
6604
6631
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
6605
6632
|
}
|
|
6606
6633
|
async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
@@ -6610,7 +6637,7 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6610
6637
|
}, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
|
|
6611
6638
|
...options,
|
|
6612
6639
|
[verificationObservation]: observation
|
|
6613
|
-
}, compact), (value) => value);
|
|
6640
|
+
}, compact), (value) => value.verified);
|
|
6614
6641
|
const observation = options[verificationObservation];
|
|
6615
6642
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
6616
6643
|
const start = performance.now();
|
|
@@ -6642,12 +6669,18 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6642
6669
|
const key = await verifySignature(compacted, verificationOptions);
|
|
6643
6670
|
if (key == null) {
|
|
6644
6671
|
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
6645
|
-
return
|
|
6672
|
+
return {
|
|
6673
|
+
verified: false,
|
|
6674
|
+
key: null
|
|
6675
|
+
};
|
|
6646
6676
|
}
|
|
6647
6677
|
if (key.ownerId == null) {
|
|
6648
6678
|
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
6649
6679
|
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
6650
|
-
return
|
|
6680
|
+
return {
|
|
6681
|
+
verified: false,
|
|
6682
|
+
key: null
|
|
6683
|
+
};
|
|
6651
6684
|
}
|
|
6652
6685
|
attributions.delete(key.ownerId.href);
|
|
6653
6686
|
if (attributions.size > 0) {
|
|
@@ -6656,10 +6689,16 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6656
6689
|
attributionIds: [...attributions].map((id) => new URL(id))
|
|
6657
6690
|
};
|
|
6658
6691
|
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
6659
|
-
return
|
|
6692
|
+
return {
|
|
6693
|
+
verified: false,
|
|
6694
|
+
key: null
|
|
6695
|
+
};
|
|
6660
6696
|
}
|
|
6661
6697
|
verified = true;
|
|
6662
|
-
return
|
|
6698
|
+
return {
|
|
6699
|
+
verified: true,
|
|
6700
|
+
key
|
|
6701
|
+
};
|
|
6663
6702
|
} catch (error) {
|
|
6664
6703
|
threw = true;
|
|
6665
6704
|
span.setStatus({
|
|
@@ -7741,6 +7780,12 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
7741
7780
|
keyId: proof.verificationMethodId.href,
|
|
7742
7781
|
error
|
|
7743
7782
|
});
|
|
7783
|
+
const check = options[verificationObservation]?.check;
|
|
7784
|
+
if (check != null && check.reason == null) check.reason = {
|
|
7785
|
+
type: "keyFetchError",
|
|
7786
|
+
keyId: new URL(proof.verificationMethodId.href),
|
|
7787
|
+
result: { error: error instanceof Error ? error : new Error(String(error)) }
|
|
7788
|
+
};
|
|
7744
7789
|
return null;
|
|
7745
7790
|
}
|
|
7746
7791
|
const publicKey = fetchedKey.key;
|
|
@@ -8269,6 +8314,39 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
8269
8314
|
* @since 0.10.0
|
|
8270
8315
|
*/
|
|
8271
8316
|
async function verifyObject(cls, jsonLd, options = {}) {
|
|
8317
|
+
const result = await verifyObjectDetailed(cls, jsonLd, options);
|
|
8318
|
+
return result.status === "verified" ? result.object : null;
|
|
8319
|
+
}
|
|
8320
|
+
/**
|
|
8321
|
+
* Same as {@link verifyObject}, but also tells who signed the verified proofs,
|
|
8322
|
+
* and whether a failure was caused by a key that could not be obtained for
|
|
8323
|
+
* a retryable reason.
|
|
8324
|
+
* @param cls The class of the object to verify.
|
|
8325
|
+
* @param jsonLd The JSON-LD object to verify.
|
|
8326
|
+
* @param options Additional options.
|
|
8327
|
+
* @returns The result.
|
|
8328
|
+
* @throws {TypeError} If the object is invalid or unsupported.
|
|
8329
|
+
* @internal
|
|
8330
|
+
*/
|
|
8331
|
+
async function verifyObjectDetailed(cls, jsonLd, options = {}) {
|
|
8332
|
+
const classify = (result, checks) => {
|
|
8333
|
+
if (result != null) return {
|
|
8334
|
+
status: "verified",
|
|
8335
|
+
object: result.object,
|
|
8336
|
+
signers: result.signers
|
|
8337
|
+
};
|
|
8338
|
+
for (const check of checks) {
|
|
8339
|
+
if (check.status === "error" && (0, _fedify_vocab_runtime.isTransientFetchError)(check.error)) return {
|
|
8340
|
+
status: "unavailable",
|
|
8341
|
+
error: check.error
|
|
8342
|
+
};
|
|
8343
|
+
if (check.status === "rejected" && check.reason.type === "keyFetchError" && isRetryableKeyFetchError(check.reason.result)) return {
|
|
8344
|
+
status: "unavailable",
|
|
8345
|
+
error: "error" in check.reason.result ? check.reason.result.error : /* @__PURE__ */ new Error(`Fetching key ${check.reason.keyId.href} failed with ${check.reason.result.status}.`)
|
|
8346
|
+
};
|
|
8347
|
+
}
|
|
8348
|
+
return { status: "invalid" };
|
|
8349
|
+
};
|
|
8272
8350
|
if (options[verificationObservation]?.attempt == null) {
|
|
8273
8351
|
const observedOptions = {
|
|
8274
8352
|
...options,
|
|
@@ -8280,16 +8358,16 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
8280
8358
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
|
|
8281
8359
|
try {
|
|
8282
8360
|
return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
|
|
8283
|
-
const
|
|
8361
|
+
const result = await verifyObjectCore(cls, jsonLd, {
|
|
8284
8362
|
...options,
|
|
8285
8363
|
[verificationObservation]: observation
|
|
8286
8364
|
});
|
|
8287
|
-
if (
|
|
8365
|
+
if (result == null) {
|
|
8288
8366
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
8289
8367
|
span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
|
|
8290
8368
|
}
|
|
8291
|
-
return
|
|
8292
|
-
}, (value) => value
|
|
8369
|
+
return classify(result, observation.attempt?.checks ?? []);
|
|
8370
|
+
}, (value) => value.status === "verified");
|
|
8293
8371
|
} catch (error) {
|
|
8294
8372
|
span.setStatus({
|
|
8295
8373
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
@@ -8301,6 +8379,15 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
8301
8379
|
}
|
|
8302
8380
|
});
|
|
8303
8381
|
}
|
|
8382
|
+
const checks = options[verificationObservation].attempt.checks;
|
|
8383
|
+
const before = checks.length;
|
|
8384
|
+
return classify(await verifyObjectCore(cls, jsonLd, options), checks.slice(before));
|
|
8385
|
+
}
|
|
8386
|
+
function isRetryableKeyFetchError(result) {
|
|
8387
|
+
if ("error" in result) return (0, _fedify_vocab_runtime.isTransientFetchError)(result.error);
|
|
8388
|
+
return result.status >= 500 || result.status === 408 || result.status === 429;
|
|
8389
|
+
}
|
|
8390
|
+
async function verifyObjectCore(cls, jsonLd, options) {
|
|
8304
8391
|
const observation = options[verificationObservation];
|
|
8305
8392
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
8306
8393
|
"fedify",
|
|
@@ -8313,6 +8400,7 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
8313
8400
|
const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
8314
8401
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
8315
8402
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
8403
|
+
const signers = [];
|
|
8316
8404
|
if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
8317
8405
|
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
8318
8406
|
const rawProofCandidates = isJsonLdNode(jsonLd) ? await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader) : [];
|
|
@@ -8371,6 +8459,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
8371
8459
|
continue;
|
|
8372
8460
|
}
|
|
8373
8461
|
deleteAuthenticatedAttribution(attributions, key.controllerId, proof.verificationMethodId);
|
|
8462
|
+
const signer = getAuthenticatedController(key.controllerId, proof.verificationMethodId);
|
|
8463
|
+
if (signer != null && !signers.some((s) => s.kind === signer.kind && s.id.href === signer.id.href && s.keyId.href === proof.verificationMethodId.href)) signers.push({
|
|
8464
|
+
...signer,
|
|
8465
|
+
keyId: proof.verificationMethodId
|
|
8466
|
+
});
|
|
8374
8467
|
}
|
|
8375
8468
|
if (acceptedPortableProofs < loadedPortableProofs) {
|
|
8376
8469
|
logger.debug("Some portable proof references could not be dereferenced.");
|
|
@@ -8384,7 +8477,41 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
8384
8477
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
8385
8478
|
return null;
|
|
8386
8479
|
}
|
|
8387
|
-
return
|
|
8480
|
+
return {
|
|
8481
|
+
object,
|
|
8482
|
+
signers
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
/**
|
|
8486
|
+
* Tells whom a key that verified an Object Integrity Proof authenticates.
|
|
8487
|
+
* @param key The key, i.e., the proof's verification method.
|
|
8488
|
+
* @returns The signer, or `null` if the key authenticates nobody.
|
|
8489
|
+
* @internal
|
|
8490
|
+
*/
|
|
8491
|
+
function getProofSigner(key) {
|
|
8492
|
+
if (key.id == null || key.controllerId == null) return null;
|
|
8493
|
+
const signer = getAuthenticatedController(key.controllerId, key.id);
|
|
8494
|
+
return signer == null ? null : {
|
|
8495
|
+
...signer,
|
|
8496
|
+
keyId: key.id
|
|
8497
|
+
};
|
|
8498
|
+
}
|
|
8499
|
+
/**
|
|
8500
|
+
* Tells whom a verified proof authenticates: the key's controller if it is an
|
|
8501
|
+
* ordinary actor, or the DID of a portable controller if the verification
|
|
8502
|
+
* method belongs to the same DID.
|
|
8503
|
+
*/
|
|
8504
|
+
function getAuthenticatedController(controllerId, verificationMethodId) {
|
|
8505
|
+
if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) return {
|
|
8506
|
+
kind: "actor",
|
|
8507
|
+
id: controllerId
|
|
8508
|
+
};
|
|
8509
|
+
const did = getPortableDid(controllerId);
|
|
8510
|
+
if (did == null || !hasCryptographicOrigin(verificationMethodId.href) || getPortableDid(verificationMethodId) !== did || !URL.canParse(did)) return null;
|
|
8511
|
+
return {
|
|
8512
|
+
kind: "portableAuthority",
|
|
8513
|
+
id: new URL(did)
|
|
8514
|
+
};
|
|
8388
8515
|
}
|
|
8389
8516
|
function deleteAuthenticatedAttribution(attributions, controllerId, verificationMethodId) {
|
|
8390
8517
|
if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) {
|
|
@@ -10038,10 +10165,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
|
|
|
10038
10165
|
cache: request.cache
|
|
10039
10166
|
});
|
|
10040
10167
|
}
|
|
10041
|
-
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
10168
|
+
async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
|
|
10042
10169
|
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
10043
10170
|
for (let attempt = 1;; attempt++) try {
|
|
10044
|
-
return await fetch(signedRequest, {
|
|
10171
|
+
return await session.fetch(signedRequest, {
|
|
10045
10172
|
redirect: "manual",
|
|
10046
10173
|
signal
|
|
10047
10174
|
});
|
|
@@ -10092,125 +10219,194 @@ function getAbortReason(signal) {
|
|
|
10092
10219
|
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
10093
10220
|
}
|
|
10094
10221
|
/**
|
|
10095
|
-
* Performs a double-knock request
|
|
10096
|
-
*
|
|
10097
|
-
*
|
|
10222
|
+
* Performs a double-knock request like {@link doubleKnock}, opening a session
|
|
10223
|
+
* for every hop with the given function, and passes the final response to
|
|
10224
|
+
* the given function. The final session is closed after `consume` settles,
|
|
10225
|
+
* so `consume` must read or cancel the response body before it returns.
|
|
10226
|
+
* This is not exported from any entry point.
|
|
10098
10227
|
* @param request The request to send.
|
|
10099
10228
|
* @param identity The identity to use for signing the request.
|
|
10100
10229
|
* @param options The options for double-knock requests.
|
|
10101
|
-
* @
|
|
10102
|
-
*
|
|
10230
|
+
* @param openHop The function to open a session for each hop. The session of
|
|
10231
|
+
* a hop is closed before the next hop is opened.
|
|
10232
|
+
* @param consume The function to consume the final response.
|
|
10233
|
+
* @returns The value returned by `consume`.
|
|
10234
|
+
*/
|
|
10235
|
+
async function doubleKnockScoped(request, identity, options, openHop, consume) {
|
|
10236
|
+
const { response, session } = await doubleKnockCore(request, identity, options, openHop);
|
|
10237
|
+
try {
|
|
10238
|
+
return await consume(response);
|
|
10239
|
+
} catch (error) {
|
|
10240
|
+
(0, _logtape_logtape.getLogger)([
|
|
10241
|
+
"fedify",
|
|
10242
|
+
"sig",
|
|
10243
|
+
"http"
|
|
10244
|
+
]).debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
10245
|
+
url: request.url,
|
|
10246
|
+
error
|
|
10247
|
+
});
|
|
10248
|
+
throw error;
|
|
10249
|
+
} finally {
|
|
10250
|
+
await session.close();
|
|
10251
|
+
}
|
|
10252
|
+
}
|
|
10253
|
+
function isRedirectResponse(response) {
|
|
10254
|
+
return response.status >= 300 && response.status < 400 && response.headers.has("Location");
|
|
10255
|
+
}
|
|
10256
|
+
/**
|
|
10257
|
+
* Starts cancelling the body of a response that is not passed to the caller,
|
|
10258
|
+
* without waiting for it to settle. The cancellation of a branch of a tee
|
|
10259
|
+
* (e.g., a body cloned by a fetch mock) settles only after the other branch
|
|
10260
|
+
* is cancelled too, per the Streams standard, so awaiting it could block
|
|
10261
|
+
* forever. Failures are only logged.
|
|
10103
10262
|
*/
|
|
10104
|
-
|
|
10105
|
-
|
|
10263
|
+
function cancelResponseBody(response, url) {
|
|
10264
|
+
response.body?.cancel().catch((error) => {
|
|
10265
|
+
(0, _logtape_logtape.getLogger)([
|
|
10266
|
+
"fedify",
|
|
10267
|
+
"sig",
|
|
10268
|
+
"http"
|
|
10269
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
10270
|
+
url,
|
|
10271
|
+
error
|
|
10272
|
+
});
|
|
10273
|
+
});
|
|
10106
10274
|
}
|
|
10107
|
-
async function
|
|
10275
|
+
async function doubleKnockCore(request, identity, options, openHop) {
|
|
10108
10276
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
10109
10277
|
signal?.throwIfAborted();
|
|
10110
10278
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
10111
|
-
visited
|
|
10112
|
-
const origin = new URL(request.url).origin;
|
|
10113
|
-
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
10114
|
-
signal?.throwIfAborted();
|
|
10279
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10115
10280
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
10116
|
-
let
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10281
|
+
let session = await openHop(request.url);
|
|
10282
|
+
try {
|
|
10283
|
+
for (let redirected = 0;; redirected++) {
|
|
10284
|
+
visited.add(request.url);
|
|
10285
|
+
const origin = new URL(request.url).origin;
|
|
10286
|
+
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
10287
|
+
signal?.throwIfAborted();
|
|
10288
|
+
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
10289
|
+
spec: firstTrySpec,
|
|
10290
|
+
tracerProvider,
|
|
10291
|
+
body
|
|
10292
|
+
});
|
|
10293
|
+
signal?.throwIfAborted();
|
|
10294
|
+
log?.(signedRequest);
|
|
10295
|
+
let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
10296
|
+
const firstRedirected = isRedirectResponse(response);
|
|
10297
|
+
if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
|
|
10298
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
10299
|
+
"fedify",
|
|
10300
|
+
"sig",
|
|
10301
|
+
"http"
|
|
10302
|
+
]);
|
|
10303
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
10304
|
+
if (acceptSigHeader != null) {
|
|
10305
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
10306
|
+
const localKeyId = identity.keyId.href;
|
|
10307
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
10308
|
+
let fulfilled = false;
|
|
10309
|
+
let challengeRequest;
|
|
10310
|
+
for (const entry of entries) {
|
|
10311
|
+
signal?.throwIfAborted();
|
|
10312
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
10313
|
+
if (rfc9421 == null) continue;
|
|
10314
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
10315
|
+
label: rfc9421.label,
|
|
10316
|
+
components: rfc9421.components
|
|
10317
|
+
});
|
|
10318
|
+
try {
|
|
10319
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
10320
|
+
spec: "rfc9421",
|
|
10321
|
+
tracerProvider,
|
|
10322
|
+
body,
|
|
10323
|
+
rfc9421
|
|
10324
|
+
});
|
|
10325
|
+
fulfilled = true;
|
|
10326
|
+
} catch (error) {
|
|
10327
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
10328
|
+
label: entry.label,
|
|
10329
|
+
error
|
|
10330
|
+
});
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
if (fulfilled && challengeRequest != null) {
|
|
10334
|
+
signal?.throwIfAborted();
|
|
10335
|
+
cancelResponseBody(response, request.url);
|
|
10336
|
+
signedRequest = challengeRequest;
|
|
10337
|
+
log?.(signedRequest);
|
|
10338
|
+
response = await session.fetch(signedRequest, {
|
|
10339
|
+
redirect: "manual",
|
|
10340
|
+
signal
|
|
10341
|
+
});
|
|
10342
|
+
}
|
|
10343
|
+
if (fulfilled && !isRedirectResponse(response)) {
|
|
10344
|
+
if (response.status < 300) {
|
|
10345
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
10346
|
+
return {
|
|
10347
|
+
response,
|
|
10348
|
+
session
|
|
10349
|
+
};
|
|
10350
|
+
}
|
|
10351
|
+
if (response.status !== 400 && response.status !== 401) return {
|
|
10352
|
+
response,
|
|
10353
|
+
session
|
|
10354
|
+
};
|
|
10355
|
+
}
|
|
10356
|
+
}
|
|
10357
|
+
if (!isRedirectResponse(response)) {
|
|
10358
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
10359
|
+
(0, _logtape_logtape.getLogger)([
|
|
10360
|
+
"fedify",
|
|
10361
|
+
"sig",
|
|
10362
|
+
"http"
|
|
10363
|
+
]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
10364
|
+
spec: firstTrySpec,
|
|
10365
|
+
secondSpec: spec,
|
|
10366
|
+
status: response.status,
|
|
10367
|
+
statusText: response.statusText
|
|
10159
10368
|
});
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10369
|
+
cancelResponseBody(response, request.url);
|
|
10370
|
+
signal?.throwIfAborted();
|
|
10371
|
+
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
10372
|
+
spec,
|
|
10373
|
+
tracerProvider,
|
|
10374
|
+
body
|
|
10165
10375
|
});
|
|
10376
|
+
signal?.throwIfAborted();
|
|
10377
|
+
log?.(signedRequest);
|
|
10378
|
+
response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
10379
|
+
if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
|
|
10166
10380
|
}
|
|
10167
|
-
}
|
|
10168
|
-
if (
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
signal
|
|
10175
|
-
});
|
|
10176
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
|
|
10177
|
-
...options,
|
|
10178
|
-
body
|
|
10179
|
-
});
|
|
10180
|
-
}
|
|
10181
|
-
if (fulfilled && response.status < 300) {
|
|
10182
|
-
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
10183
|
-
return response;
|
|
10184
|
-
}
|
|
10185
|
-
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
10186
|
-
}
|
|
10187
|
-
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
10188
|
-
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
10189
|
-
spec: firstTrySpec,
|
|
10190
|
-
secondSpec: spec,
|
|
10191
|
-
status: response.status,
|
|
10192
|
-
statusText: response.statusText
|
|
10193
|
-
});
|
|
10194
|
-
signal?.throwIfAborted();
|
|
10195
|
-
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
10196
|
-
spec,
|
|
10197
|
-
tracerProvider,
|
|
10198
|
-
body
|
|
10199
|
-
});
|
|
10200
|
-
signal?.throwIfAborted();
|
|
10201
|
-
log?.(signedRequest);
|
|
10202
|
-
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
10203
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
10381
|
+
} else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
|
|
10382
|
+
if (!isRedirectResponse(response)) return {
|
|
10383
|
+
response,
|
|
10384
|
+
session
|
|
10385
|
+
};
|
|
10386
|
+
cancelResponseBody(response, request.url);
|
|
10387
|
+
await session.close();
|
|
10204
10388
|
if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
10205
|
-
const
|
|
10389
|
+
const location = response.headers.get("Location");
|
|
10390
|
+
const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
|
|
10206
10391
|
if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10392
|
+
request = redirectRequest;
|
|
10393
|
+
session = await openHop(request.url);
|
|
10394
|
+
}
|
|
10395
|
+
} catch (error) {
|
|
10396
|
+
try {
|
|
10397
|
+
await session.close();
|
|
10398
|
+
} catch (closeError) {
|
|
10399
|
+
(0, _logtape_logtape.getLogger)([
|
|
10400
|
+
"fedify",
|
|
10401
|
+
"sig",
|
|
10402
|
+
"http"
|
|
10403
|
+
]).debug("Failed to close the session for {url}: {error}", {
|
|
10404
|
+
url: request.url,
|
|
10405
|
+
error: closeError
|
|
10406
|
+
});
|
|
10407
|
+
}
|
|
10408
|
+
throw error;
|
|
10409
|
+
}
|
|
10214
10410
|
}
|
|
10215
10411
|
/**
|
|
10216
10412
|
* Performs a timing-safe equality comparison between two `Uint8Array` values.
|
|
@@ -10306,10 +10502,10 @@ Object.defineProperty(exports, "detachSignature", {
|
|
|
10306
10502
|
return detachSignature;
|
|
10307
10503
|
}
|
|
10308
10504
|
});
|
|
10309
|
-
Object.defineProperty(exports, "
|
|
10505
|
+
Object.defineProperty(exports, "doubleKnockScoped", {
|
|
10310
10506
|
enumerable: true,
|
|
10311
10507
|
get: function() {
|
|
10312
|
-
return
|
|
10508
|
+
return doubleKnockScoped;
|
|
10313
10509
|
}
|
|
10314
10510
|
});
|
|
10315
10511
|
Object.defineProperty(exports, "exportJwk", {
|
|
@@ -10402,6 +10598,12 @@ Object.defineProperty(exports, "getPortableDid", {
|
|
|
10402
10598
|
return getPortableDid;
|
|
10403
10599
|
}
|
|
10404
10600
|
});
|
|
10601
|
+
Object.defineProperty(exports, "getProofSigner", {
|
|
10602
|
+
enumerable: true,
|
|
10603
|
+
get: function() {
|
|
10604
|
+
return getProofSigner;
|
|
10605
|
+
}
|
|
10606
|
+
});
|
|
10405
10607
|
Object.defineProperty(exports, "getRawCanonicalPortableId", {
|
|
10406
10608
|
enumerable: true,
|
|
10407
10609
|
get: function() {
|
|
@@ -10516,6 +10718,12 @@ Object.defineProperty(exports, "listRequestSignatures", {
|
|
|
10516
10718
|
return listRequestSignatures;
|
|
10517
10719
|
}
|
|
10518
10720
|
});
|
|
10721
|
+
Object.defineProperty(exports, "markEffectiveContextLoader", {
|
|
10722
|
+
enumerable: true,
|
|
10723
|
+
get: function() {
|
|
10724
|
+
return markEffectiveContextLoader;
|
|
10725
|
+
}
|
|
10726
|
+
});
|
|
10519
10727
|
Object.defineProperty(exports, "name", {
|
|
10520
10728
|
enumerable: true,
|
|
10521
10729
|
get: function() {
|
|
@@ -10696,10 +10904,10 @@ Object.defineProperty(exports, "verificationObservation", {
|
|
|
10696
10904
|
return verificationObservation;
|
|
10697
10905
|
}
|
|
10698
10906
|
});
|
|
10699
|
-
Object.defineProperty(exports, "
|
|
10907
|
+
Object.defineProperty(exports, "verifyCompactJsonLdDetailed", {
|
|
10700
10908
|
enumerable: true,
|
|
10701
10909
|
get: function() {
|
|
10702
|
-
return
|
|
10910
|
+
return verifyCompactJsonLdDetailed;
|
|
10703
10911
|
}
|
|
10704
10912
|
});
|
|
10705
10913
|
Object.defineProperty(exports, "verifyJsonLd", {
|
|
@@ -10726,6 +10934,12 @@ Object.defineProperty(exports, "verifyObject", {
|
|
|
10726
10934
|
return verifyObject;
|
|
10727
10935
|
}
|
|
10728
10936
|
});
|
|
10937
|
+
Object.defineProperty(exports, "verifyObjectDetailed", {
|
|
10938
|
+
enumerable: true,
|
|
10939
|
+
get: function() {
|
|
10940
|
+
return verifyObjectDetailed;
|
|
10941
|
+
}
|
|
10942
|
+
});
|
|
10729
10943
|
Object.defineProperty(exports, "verifyPortableObjectProof", {
|
|
10730
10944
|
enumerable: true,
|
|
10731
10945
|
get: function() {
|