@fedify/fedify 2.4.0-dev.2159 → 2.4.0-dev.2169
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/dist/{builder-D89oOfHC.mjs → builder-B2d_Pt0j.mjs} +2 -2
- 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-B02Os8h2.mjs → compound-proof-D28gMFqP.mjs} +2 -2
- package/dist/{context-JDNkUtnu.d.ts → context-BykNUf04.d.ts} +1 -1
- package/dist/{context-DpsaHkk1.d.cts → context-L0Fnib9j.d.cts} +1 -1
- package/dist/{deno-CE_s0yQb.mjs → deno-DU8dvnWk.mjs} +1 -1
- package/dist/{docloader-C5p26tpa.mjs → docloader-CghLH7Zi.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +67 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- 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-gateway-keys.test.mjs +180 -6
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-dBOqmJcA.cjs → federation-DrNgkLfJ.cjs} +48 -24
- package/dist/{http-wdsia0-O.cjs → http-5CJBdU8n.cjs} +392 -65
- package/dist/{http-nTZuTacw.js → http-BGpexPO7.js} +368 -61
- package/dist/{http-DV_yW4dv.mjs → http-Bd6rZlJX.mjs} +7 -5
- package/dist/{http-C-x_OLSp.d.ts → http-Cok5-vIC.d.ts} +5 -0
- package/dist/{http-CjezPjFX.d.cts → http-DC1cqAQ2.d.cts} +5 -0
- package/dist/{key-C6jnekFI.mjs → key-gIhVZO3p.mjs} +58 -7
- package/dist/key-owner-evidence-Bo3yaeL5.mjs +108 -0
- package/dist/{keycache-BInaECGj.mjs → keycache-PiOM9pM0.mjs} +46 -21
- package/dist/{kv-cache-Bxm0a0Ww.mjs → kv-cache-D0qjSeWY.mjs} +1 -1
- package/dist/{kv-cache-BHNtQi9e.cjs → kv-cache-YRi_g8GB.cjs} +1 -1
- package/dist/{kv-cache-DhIl9YlV.js → kv-cache-iR55fqlk.js} +1 -1
- package/dist/{ld-ya2C1Xip.mjs → ld-BsTkRrwF.mjs} +3 -3
- package/dist/{metrics-ByOQOHHS.mjs → metrics-jUDFfVgi.mjs} +1 -1
- package/dist/{middleware-Bdwd_Gg8.mjs → middleware-Coqgp-K0.mjs} +15 -15
- package/dist/{middleware-WiaN4RS3.js → middleware-DlxMxVp5.js} +48 -24
- package/dist/{middleware-DoVWq8mE.mjs → middleware-Dq-OCmb6.mjs} +1 -1
- package/dist/{mod-Bz9tFXI2.d.ts → mod--jv6vyrP.d.ts} +1 -1
- package/dist/{mod-DaiBrWDW.d.ts → mod-B5z_q9TH.d.ts} +1 -1
- package/dist/{mod-Mt4aO3DX.d.cts → mod-BA_F2x53.d.cts} +1 -1
- package/dist/{mod-DSKkTwH2.d.cts → mod-BLOXGs6m.d.cts} +1 -1
- package/dist/{mod-S--JxYjo.d.cts → mod-CPPqkyV_.d.cts} +1 -1
- package/dist/{mod-_4K05HZF.d.ts → mod-DK0y9fd-.d.ts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-C6Nv5yyw.mjs → owner-D3GoetXS.mjs} +18 -3
- package/dist/{portable-collection-B-mU1F1-.js → portable-collection-Bkdh_WpL.js} +15 -1
- package/dist/{portable-collection-uWj3hfzv.mjs → portable-collection-C60_acmx.mjs} +2 -2
- package/dist/{portable-collection-C65SwL2l.cjs → portable-collection-CnlCamup.cjs} +15 -1
- package/dist/{portable-key-BXsVLSMa.mjs → portable-key-DyF5AnZo.mjs} +209 -55
- package/dist/{proof-BdO3BbLh.mjs → proof-CQAYqWdj.mjs} +4 -4
- package/dist/{send-BwANxryy.mjs → send-DjkQrxt5.mjs} +3 -3
- 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 +2 -2
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- 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-key.test.mjs +253 -7
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{temporal-CbPfRGZq.mjs → temporal-oRKP803M.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- 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
|
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
|
16
16
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
17
17
|
//#region deno.json
|
|
18
18
|
var name = "@fedify/fedify";
|
|
19
|
-
var version = "2.4.0-dev.
|
|
19
|
+
var version = "2.4.0-dev.2169+0a45df07";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/sig/portable-key-id.ts
|
|
22
22
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -3760,7 +3760,7 @@ const Now = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
3760
3760
|
}
|
|
3761
3761
|
})
|
|
3762
3762
|
});
|
|
3763
|
-
const Temporal$
|
|
3763
|
+
const Temporal$5 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
3764
3764
|
...createStringTagDescriptors("Temporal"),
|
|
3765
3765
|
...createPropDescriptors({
|
|
3766
3766
|
PlainYearMonth,
|
|
@@ -3776,7 +3776,7 @@ const Temporal$3 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
3776
3776
|
});
|
|
3777
3777
|
//#endregion
|
|
3778
3778
|
//#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/index.js
|
|
3779
|
-
const Temporal$
|
|
3779
|
+
const Temporal$4 = NativeTemporal || Temporal$5;
|
|
3780
3780
|
NativeTemporal && Date.prototype.toTemporalInstant;
|
|
3781
3781
|
//#endregion
|
|
3782
3782
|
//#region src/sig/accept.ts
|
|
@@ -4747,7 +4747,111 @@ function getDurationMs(start) {
|
|
|
4747
4747
|
return Math.max(0, performance.now() - start);
|
|
4748
4748
|
}
|
|
4749
4749
|
//#endregion
|
|
4750
|
+
//#region src/sig/key-owner-evidence.ts
|
|
4751
|
+
const Temporal$3 = Temporal$4;
|
|
4752
|
+
/**
|
|
4753
|
+
* How long a key of an [FEP-ef61] portable actor is trusted after its actor's
|
|
4754
|
+
* signed document vouched for it, however long the proof of the document is
|
|
4755
|
+
* valid. The actor can drop the key or the gateway from its document at any
|
|
4756
|
+
* time, so the key is looked up again at least this often.
|
|
4757
|
+
*
|
|
4758
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
4759
|
+
* @internal
|
|
4760
|
+
*/
|
|
4761
|
+
const MAX_PORTABLE_KEY_TTL = Temporal$3.Duration.from({ hours: 1 });
|
|
4762
|
+
const candidates = /* @__PURE__ */ new WeakMap();
|
|
4763
|
+
const verified = /* @__PURE__ */ new WeakMap();
|
|
4764
|
+
/**
|
|
4765
|
+
* Attaches to a key that was just resolved the evidence of the portable actor
|
|
4766
|
+
* that owns it. The evidence is not used until the key verifies
|
|
4767
|
+
* a signature; see {@link promoteKeyOwnerEvidence}.
|
|
4768
|
+
*
|
|
4769
|
+
* Nothing is attached unless the document, the key's owner, and the actor
|
|
4770
|
+
* the key ID belongs to agree on the actor's canonical portable ID.
|
|
4771
|
+
* @param key The key, whose owner the document vouched for.
|
|
4772
|
+
* @param document The expanded root node of the actor's verified document.
|
|
4773
|
+
* @param expires When the evidence must not be used anymore.
|
|
4774
|
+
* @returns Whether the evidence was attached.
|
|
4775
|
+
* @internal
|
|
4776
|
+
*/
|
|
4777
|
+
function attachKeyOwnerEvidence(key, document, expires) {
|
|
4778
|
+
if (!(key instanceof _fedify_vocab.CryptographicKey) || key.id == null || key.ownerId == null || key.publicKey == null || document == null || typeof document !== "object" || Array.isArray(document)) return false;
|
|
4779
|
+
const rawId = document["@id"];
|
|
4780
|
+
if (typeof rawId !== "string") return false;
|
|
4781
|
+
const keyBase = new URL(key.id.href);
|
|
4782
|
+
keyBase.hash = "";
|
|
4783
|
+
const ownerId = getCanonicalPortableId(key.ownerId);
|
|
4784
|
+
if (ownerId == null || getRawCanonicalPortableId(rawId) !== ownerId || getCanonicalPortableId(keyBase) !== ownerId) return false;
|
|
4785
|
+
candidates.set(key, {
|
|
4786
|
+
keyId: key.id.href,
|
|
4787
|
+
keyOwner: key.ownerId.href,
|
|
4788
|
+
publicKey: key.publicKey,
|
|
4789
|
+
ownerId,
|
|
4790
|
+
document: structuredClone(document),
|
|
4791
|
+
expires
|
|
4792
|
+
});
|
|
4793
|
+
return true;
|
|
4794
|
+
}
|
|
4795
|
+
/**
|
|
4796
|
+
* Makes the evidence attached to a key usable, as the key has just verified
|
|
4797
|
+
* an HTTP Signature. Nothing happens if the key has no evidence.
|
|
4798
|
+
* @param key The key that verified a signature.
|
|
4799
|
+
* @internal
|
|
4800
|
+
*/
|
|
4801
|
+
function promoteKeyOwnerEvidence(key) {
|
|
4802
|
+
const evidence = candidates.get(key);
|
|
4803
|
+
if (evidence == null) return;
|
|
4804
|
+
candidates.delete(key);
|
|
4805
|
+
verified.set(key, evidence);
|
|
4806
|
+
}
|
|
4807
|
+
/**
|
|
4808
|
+
* Gets the evidence of the portable actor that owns a key HTTP Signature
|
|
4809
|
+
* verification returned.
|
|
4810
|
+
* @param key The key.
|
|
4811
|
+
* @param now The current time.
|
|
4812
|
+
* @returns The evidence, or `undefined` if the key was not returned by HTTP
|
|
4813
|
+
* Signature verification, if it has no evidence, if the evidence
|
|
4814
|
+
* expired, or if the key has been changed since.
|
|
4815
|
+
* @internal
|
|
4816
|
+
*/
|
|
4817
|
+
function getVerifiedKeyOwnerEvidence(key, now = Temporal$3.Now.instant()) {
|
|
4818
|
+
const evidence = verified.get(key);
|
|
4819
|
+
if (evidence == null) return void 0;
|
|
4820
|
+
if (Temporal$3.Instant.compare(now, evidence.expires) >= 0) {
|
|
4821
|
+
verified.delete(key);
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
if (key.id?.href !== evidence.keyId || key.ownerId?.href !== evidence.keyOwner || key.publicKey !== evidence.publicKey) return;
|
|
4825
|
+
return evidence;
|
|
4826
|
+
}
|
|
4827
|
+
/**
|
|
4828
|
+
* Parses the actor from the evidence, i.e., from the expansion of its
|
|
4829
|
+
* document whose proof was verified. Every call returns a new object.
|
|
4830
|
+
* @param evidence The evidence.
|
|
4831
|
+
* @param options Options for parsing the document.
|
|
4832
|
+
* @returns The actor, or `null` if the document is not the actor that
|
|
4833
|
+
* the evidence is about, or the evidence expired meanwhile.
|
|
4834
|
+
* @internal
|
|
4835
|
+
*/
|
|
4836
|
+
async function parseKeyOwnerEvidence(evidence, options = {}) {
|
|
4837
|
+
const refuse = (url) => Promise.reject(/* @__PURE__ */ new Error(`Refusing to fetch ${url}.`));
|
|
4838
|
+
let actor;
|
|
4839
|
+
try {
|
|
4840
|
+
actor = await _fedify_vocab.Object.fromJsonLd(structuredClone(evidence.document), {
|
|
4841
|
+
documentLoader: refuse,
|
|
4842
|
+
contextLoader: options.contextLoader,
|
|
4843
|
+
tracerProvider: options.tracerProvider
|
|
4844
|
+
});
|
|
4845
|
+
} catch (error) {
|
|
4846
|
+
if (!(error instanceof TypeError)) throw error;
|
|
4847
|
+
return null;
|
|
4848
|
+
}
|
|
4849
|
+
if (!isPortableActorDocument(actor) || getCanonicalPortableId(actor.id) !== evidence.ownerId || Temporal$3.Instant.compare(Temporal$3.Now.instant(), evidence.expires) >= 0) return null;
|
|
4850
|
+
return actor;
|
|
4851
|
+
}
|
|
4852
|
+
//#endregion
|
|
4750
4853
|
//#region src/sig/key.ts
|
|
4854
|
+
const Temporal$2 = Temporal$4;
|
|
4751
4855
|
/**
|
|
4752
4856
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
4753
4857
|
* otherwise throws an error.
|
|
@@ -4969,6 +5073,7 @@ function bypassKeyCacheReads(keyCache) {
|
|
|
4969
5073
|
const scoped = cache.compatibleKeyScope(scope);
|
|
4970
5074
|
return {
|
|
4971
5075
|
get: () => Promise.resolve(void 0),
|
|
5076
|
+
getEntry: () => Promise.resolve(void 0),
|
|
4972
5077
|
set: async (keyId, key, options) => await scoped.set(keyId, key, options),
|
|
4973
5078
|
delete: async (keyId) => await scoped.delete(keyId)
|
|
4974
5079
|
};
|
|
@@ -5136,6 +5241,49 @@ function checkCachedKeyHit(cachedKey, keyId, cls, logger) {
|
|
|
5136
5241
|
}
|
|
5137
5242
|
return null;
|
|
5138
5243
|
}
|
|
5244
|
+
/**
|
|
5245
|
+
* Looks up a key at a compatible identifier, or at an `ap:` or `ap+ef61:`
|
|
5246
|
+
* URI, in the cache namespace of a purpose, like {@link getCachedFetchKey}.
|
|
5247
|
+
* A key that the verified document of its portable actor was cached with
|
|
5248
|
+
* gets the actor's evidence attached from the same entry; see
|
|
5249
|
+
* {@link attachKeyOwnerEvidence}.
|
|
5250
|
+
*/
|
|
5251
|
+
async function getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger) {
|
|
5252
|
+
if (scopedCache?.getEntry == null) return await getCachedFetchKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
5253
|
+
const entry = await scopedCache.getEntry(cacheKey);
|
|
5254
|
+
if (entry === void 0) return null;
|
|
5255
|
+
if (entry === null) {
|
|
5256
|
+
logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
|
|
5257
|
+
return {
|
|
5258
|
+
key: null,
|
|
5259
|
+
cached: true
|
|
5260
|
+
};
|
|
5261
|
+
}
|
|
5262
|
+
const hit = checkCachedKeyHit(entry.key, keyId, cls, logger);
|
|
5263
|
+
if (hit?.key instanceof _fedify_vocab.CryptographicKey && entry.owner != null) attachKeyOwnerEvidence(hit.key, entry.owner, entry.expires);
|
|
5264
|
+
return hit;
|
|
5265
|
+
}
|
|
5266
|
+
/**
|
|
5267
|
+
* Attaches the evidence of the portable actor whose document has just
|
|
5268
|
+
* vouched for a key to the key. The evidence expires with the cache entry
|
|
5269
|
+
* of the key, so that it is never trusted longer than the key is cached.
|
|
5270
|
+
* Without a cache, it expires with the proof of the document, but no later
|
|
5271
|
+
* than {@link MAX_PORTABLE_KEY_TTL}, as a cache entry would.
|
|
5272
|
+
* @param resolution The resolution of the key.
|
|
5273
|
+
* @param scopedCache The cache namespace the key was stored in, if any.
|
|
5274
|
+
* @param cached When the cache entry expires, if the cache stored one.
|
|
5275
|
+
*/
|
|
5276
|
+
function attachResolvedKeyOwnerEvidence(resolution, scopedCache, cached) {
|
|
5277
|
+
let expires;
|
|
5278
|
+
if (scopedCache != null) {
|
|
5279
|
+
if (cached == null) return;
|
|
5280
|
+
expires = cached;
|
|
5281
|
+
} else {
|
|
5282
|
+
expires = Temporal$2.Now.instant().add(MAX_PORTABLE_KEY_TTL);
|
|
5283
|
+
if (resolution.expires != null && Temporal$2.Instant.compare(resolution.expires, expires) < 0) expires = resolution.expires;
|
|
5284
|
+
}
|
|
5285
|
+
attachKeyOwnerEvidence(resolution.key, resolution.document, expires);
|
|
5286
|
+
}
|
|
5139
5287
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
5140
5288
|
await keyCache?.setFetchError?.(keyId, null);
|
|
5141
5289
|
}
|
|
@@ -5260,7 +5408,10 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
5260
5408
|
cached: false
|
|
5261
5409
|
};
|
|
5262
5410
|
}
|
|
5263
|
-
await scopedCache?.set(cacheKey, resolution.key, {
|
|
5411
|
+
attachResolvedKeyOwnerEvidence(resolution, scopedCache, await scopedCache?.set(cacheKey, resolution.key, {
|
|
5412
|
+
expires: resolution.expires,
|
|
5413
|
+
owner: resolution.document
|
|
5414
|
+
}));
|
|
5264
5415
|
return {
|
|
5265
5416
|
key: resolution.key,
|
|
5266
5417
|
cached: false
|
|
@@ -5481,7 +5632,7 @@ async function fetchCompatibleKey(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
5481
5632
|
const keyId = cacheKey.href;
|
|
5482
5633
|
const sharedCache = options.keyCache;
|
|
5483
5634
|
const scopedCache = sharedCache?.compatibleKeyScope?.(getCompatibleKeyScope(cls, options));
|
|
5484
|
-
const cached = await
|
|
5635
|
+
const cached = await getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
5485
5636
|
if (cached != null) return {
|
|
5486
5637
|
result: cached,
|
|
5487
5638
|
outcome: { result: "hit" }
|
|
@@ -5545,7 +5696,7 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
5545
5696
|
}
|
|
5546
5697
|
const sharedCache = options.keyCache;
|
|
5547
5698
|
const scopedCache = sharedCache?.compatibleKeyScope?.("httpSignature");
|
|
5548
|
-
const cached = await
|
|
5699
|
+
const cached = await getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
5549
5700
|
if (cached != null) return {
|
|
5550
5701
|
result: cached,
|
|
5551
5702
|
outcome: { result: "hit" }
|
|
@@ -5560,7 +5711,10 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
5560
5711
|
logger.debug("Resolving key {keyId} to verify signature...", { keyId });
|
|
5561
5712
|
const resolution = await resolvers.actorKey(cacheKey);
|
|
5562
5713
|
if (resolution.type === "verified") {
|
|
5563
|
-
await scopedCache?.set(cacheKey, resolution.key, {
|
|
5714
|
+
attachResolvedKeyOwnerEvidence(resolution, scopedCache, await scopedCache?.set(cacheKey, resolution.key, {
|
|
5715
|
+
expires: resolution.expires,
|
|
5716
|
+
owner: resolution.document
|
|
5717
|
+
}));
|
|
5564
5718
|
return {
|
|
5565
5719
|
result: {
|
|
5566
5720
|
key: resolution.key,
|
|
@@ -6615,7 +6769,7 @@ async function normalizeOutgoingActivityJsonLd(jsonLd, contextLoader, options =
|
|
|
6615
6769
|
}
|
|
6616
6770
|
//#endregion
|
|
6617
6771
|
//#region src/sig/proof.ts
|
|
6618
|
-
const Temporal$1 = Temporal$
|
|
6772
|
+
const Temporal$1 = Temporal$4;
|
|
6619
6773
|
/**
|
|
6620
6774
|
* Known Object Integrity Proof `cryptosuite` values, used to keep
|
|
6621
6775
|
* `object_integrity_proofs.cryptosuite` on a bounded set of spec-defined
|
|
@@ -7901,6 +8055,7 @@ async function verifyPortableKeyDocument(document, actor, keyId, isGatewayKey, o
|
|
|
7901
8055
|
type: "verified",
|
|
7902
8056
|
actor,
|
|
7903
8057
|
key,
|
|
8058
|
+
document: root,
|
|
7904
8059
|
...expires == null ? {} : { expires }
|
|
7905
8060
|
};
|
|
7906
8061
|
}
|
|
@@ -7947,6 +8102,21 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
7947
8102
|
});
|
|
7948
8103
|
}
|
|
7949
8104
|
/**
|
|
8105
|
+
* The maximum number of `@gateway` location hints followed for one `ap:` or
|
|
8106
|
+
* `ap+ef61:` key ID. Whoever made a signature chooses the hints of its key
|
|
8107
|
+
* ID, so fewer of them are followed than when dereferencing objects, which
|
|
8108
|
+
* bounds the gateways one signature can make Fedify ask. Further hints are
|
|
8109
|
+
* ignored.
|
|
8110
|
+
*/
|
|
8111
|
+
const MAX_KEY_ID_GATEWAY_HINTS = 3;
|
|
8112
|
+
/**
|
|
8113
|
+
* The default of {@link PortableActorKeyOptions.lookupTimeout}, in
|
|
8114
|
+
* milliseconds.
|
|
8115
|
+
*/
|
|
8116
|
+
const DEFAULT_KEY_ID_LOOKUP_TIMEOUT = 1e4;
|
|
8117
|
+
/** The longest delay that `setTimeout()` supports, in milliseconds. */
|
|
8118
|
+
const MAX_TIMEOUT = 2 ** 31 - 1;
|
|
8119
|
+
/**
|
|
7950
8120
|
* Resolves a key of a portable actor at an `ap:` or `ap+ef61:` key ID, e.g.,
|
|
7951
8121
|
* `ap://did:key:z6Mk…/actor#main-key`. Such a key ID names no gateway, so
|
|
7952
8122
|
* the actor's document, i.e., the key ID without its fragment, is fetched
|
|
@@ -7974,12 +8144,25 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
7974
8144
|
* unreachable gateway would have served, so the result is an `unavailable`
|
|
7975
8145
|
* that is not cacheable.
|
|
7976
8146
|
*
|
|
8147
|
+
* Only the first {@link MAX_KEY_ID_GATEWAY_HINTS} location hints are
|
|
8148
|
+
* followed, and all the gateways share one timeout, see
|
|
8149
|
+
* {@link PortableActorKeyOptions.lookupTimeout}. A gateway that does not
|
|
8150
|
+
* serve the document in time failed to serve it, like an unreachable one,
|
|
8151
|
+
* with an error that has no HTTP status. A gateway whose document could not
|
|
8152
|
+
* be processed in time, and the gateways left untried, make the result an
|
|
8153
|
+
* `unavailable` that is not cacheable. A result that arrives after
|
|
8154
|
+
* the timeout is ignored, even if it vouches for the key.
|
|
8155
|
+
*
|
|
7977
8156
|
* @param keyId The key ID, which must be an `ap:` or `ap+ef61:` URI.
|
|
7978
|
-
* @param options Loaders and
|
|
8157
|
+
* @param options Loaders, telemetry providers, and the timeout.
|
|
7979
8158
|
* @returns The resolution, which is never `legacy`.
|
|
8159
|
+
* @throws {RangeError} If the timeout is not a non-negative number of
|
|
8160
|
+
* milliseconds that `setTimeout()` supports.
|
|
7980
8161
|
* @internal
|
|
7981
8162
|
*/
|
|
7982
8163
|
async function resolvePortableActorKey(keyId, options = {}) {
|
|
8164
|
+
const lookupTimeout = options.lookupTimeout ?? DEFAULT_KEY_ID_LOOKUP_TIMEOUT;
|
|
8165
|
+
if (!Number.isFinite(lookupTimeout) || lookupTimeout < 0 || lookupTimeout > MAX_TIMEOUT) throw new RangeError(`The lookup timeout must be between 0 and ${MAX_TIMEOUT} ms: ${lookupTimeout}`);
|
|
7983
8166
|
const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
7984
8167
|
const contextLoader = options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
7985
8168
|
let formattedKeyId = keyId.href;
|
|
@@ -7998,75 +8181,67 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
7998
8181
|
if (keyId.hash === "" || keyId.hash === "#") return reject("The key ID has no fragment; only keys embedded in actor documents are supported.");
|
|
7999
8182
|
const actorUrl = new URL(keyId.href);
|
|
8000
8183
|
actorUrl.hash = "";
|
|
8184
|
+
const gateways = (0, _fedify_vocab_runtime_internal_portable_dereference.getPortableGatewayCandidates)(actorUrl);
|
|
8185
|
+
if (gateways.length > MAX_KEY_ID_GATEWAY_HINTS) logger.debug("Following only the first {max} location hints of key {keyId}.", {
|
|
8186
|
+
keyId: formattedKeyId,
|
|
8187
|
+
max: MAX_KEY_ID_GATEWAY_HINTS
|
|
8188
|
+
});
|
|
8001
8189
|
const requestUrls = [];
|
|
8002
|
-
for (const gateway of (0,
|
|
8190
|
+
for (const gateway of gateways.slice(0, MAX_KEY_ID_GATEWAY_HINTS)) try {
|
|
8003
8191
|
requestUrls.push((0, _fedify_vocab_runtime.toCompatibleEf61Id)(actorUrl, gateway).href);
|
|
8004
8192
|
} catch (error) {
|
|
8005
8193
|
if (!(error instanceof TypeError)) throw error;
|
|
8006
8194
|
return reject(`The key ID cannot be fetched from gateways: ${error}`);
|
|
8007
8195
|
}
|
|
8008
8196
|
if (requestUrls.length < 1) requestUrls.push((0, _fedify_vocab_runtime.formatIri)(actorUrl));
|
|
8197
|
+
const controller = new AbortController();
|
|
8198
|
+
const timeoutError = new DOMException(`Timed out looking up the actor of key ${formattedKeyId} after ${lookupTimeout} ms.`, "TimeoutError");
|
|
8199
|
+
let timer;
|
|
8200
|
+
if (lookupTimeout > 0) timer = setTimeout(() => controller.abort(timeoutError), lookupTimeout);
|
|
8201
|
+
else controller.abort(timeoutError);
|
|
8202
|
+
const { signal } = controller;
|
|
8203
|
+
const attemptOptions = {
|
|
8204
|
+
...options,
|
|
8205
|
+
documentLoader: withDeadline(documentLoader, signal),
|
|
8206
|
+
contextLoader: withDeadline(contextLoader, signal),
|
|
8207
|
+
signal,
|
|
8208
|
+
formattedKeyId
|
|
8209
|
+
};
|
|
8009
8210
|
const attempts = [];
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
} catch (error) {
|
|
8015
|
-
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
8016
|
-
keyId: formattedKeyId,
|
|
8017
|
-
url,
|
|
8018
|
-
error
|
|
8019
|
-
});
|
|
8020
|
-
attempts.push({
|
|
8021
|
-
type: "retrievalFailed",
|
|
8022
|
-
error
|
|
8023
|
-
});
|
|
8024
|
-
continue;
|
|
8025
|
-
}
|
|
8026
|
-
let resolution;
|
|
8027
|
-
try {
|
|
8028
|
-
const object = await _fedify_vocab.Object.fromJsonLd(remoteDocument.document, {
|
|
8029
|
-
documentLoader,
|
|
8030
|
-
contextLoader,
|
|
8031
|
-
tracerProvider: options.tracerProvider
|
|
8032
|
-
});
|
|
8033
|
-
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, {
|
|
8034
|
-
...options,
|
|
8035
|
-
documentLoader,
|
|
8036
|
-
contextLoader
|
|
8037
|
-
}) : {
|
|
8038
|
-
type: "rejected",
|
|
8039
|
-
reason: "The document is not a portable actor."
|
|
8040
|
-
};
|
|
8041
|
-
} catch (error) {
|
|
8042
|
-
if (error instanceof TypeError) resolution = {
|
|
8043
|
-
type: "rejected",
|
|
8044
|
-
reason: `The document is malformed: ${error.message}`
|
|
8045
|
-
};
|
|
8046
|
-
else {
|
|
8047
|
-
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
8211
|
+
try {
|
|
8212
|
+
for (const url of requestUrls) {
|
|
8213
|
+
if (signal.aborted) {
|
|
8214
|
+
logger.debug("Ran out of time looking up the actor of key {keyId}; gateways from {url} on were not tried.", {
|
|
8048
8215
|
keyId: formattedKeyId,
|
|
8049
|
-
url
|
|
8050
|
-
error
|
|
8216
|
+
url
|
|
8051
8217
|
});
|
|
8052
8218
|
attempts.push({
|
|
8053
8219
|
type: "indeterminate",
|
|
8054
|
-
error
|
|
8220
|
+
error: signal.reason
|
|
8221
|
+
});
|
|
8222
|
+
break;
|
|
8223
|
+
}
|
|
8224
|
+
const state = { processing: false };
|
|
8225
|
+
let attempt;
|
|
8226
|
+
try {
|
|
8227
|
+
attempt = await raceAbort(attemptGateway(url, keyId, attemptOptions, state), signal);
|
|
8228
|
+
} catch (error) {
|
|
8229
|
+
if (!signal.aborted) throw error;
|
|
8230
|
+
logger.debug("Ran out of time looking up the actor of key {keyId} at {url}.", {
|
|
8231
|
+
keyId: formattedKeyId,
|
|
8232
|
+
url
|
|
8233
|
+
});
|
|
8234
|
+
attempts.push({
|
|
8235
|
+
type: state.processing ? "indeterminate" : "retrievalFailed",
|
|
8236
|
+
error: signal.reason
|
|
8055
8237
|
});
|
|
8056
8238
|
continue;
|
|
8057
8239
|
}
|
|
8240
|
+
if (attempt.type === "verified") return attempt;
|
|
8241
|
+
attempts.push(attempt);
|
|
8058
8242
|
}
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
8062
|
-
keyId: formattedKeyId,
|
|
8063
|
-
url,
|
|
8064
|
-
reason
|
|
8065
|
-
});
|
|
8066
|
-
attempts.push({
|
|
8067
|
-
type: "rejected",
|
|
8068
|
-
reason
|
|
8069
|
-
});
|
|
8243
|
+
} finally {
|
|
8244
|
+
clearTimeout(timer);
|
|
8070
8245
|
}
|
|
8071
8246
|
const rejections = attempts.flatMap((a) => a.type === "rejected" ? [a.reason] : []);
|
|
8072
8247
|
if (rejections.length === attempts.length) return reject(rejections.join(" "));
|
|
@@ -8087,6 +8262,139 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
8087
8262
|
};
|
|
8088
8263
|
}
|
|
8089
8264
|
/**
|
|
8265
|
+
* Fetches the actor's document of a key at an `ap:` or `ap+ef61:` key ID from
|
|
8266
|
+
* one gateway, and tells whether it vouches for the key. It never rejects;
|
|
8267
|
+
* the caller races it against the lookup's timeout, and ignores what it
|
|
8268
|
+
* resolves to after the timeout.
|
|
8269
|
+
*/
|
|
8270
|
+
async function attemptGateway(url, keyId, options, state) {
|
|
8271
|
+
const { documentLoader, contextLoader, signal, formattedKeyId } = options;
|
|
8272
|
+
let remoteDocument;
|
|
8273
|
+
try {
|
|
8274
|
+
remoteDocument = await documentLoader(url);
|
|
8275
|
+
} catch (error) {
|
|
8276
|
+
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
8277
|
+
keyId: formattedKeyId,
|
|
8278
|
+
url,
|
|
8279
|
+
error
|
|
8280
|
+
});
|
|
8281
|
+
return {
|
|
8282
|
+
type: "retrievalFailed",
|
|
8283
|
+
error
|
|
8284
|
+
};
|
|
8285
|
+
}
|
|
8286
|
+
if (signal.aborted) return {
|
|
8287
|
+
type: "indeterminate",
|
|
8288
|
+
error: signal.reason
|
|
8289
|
+
};
|
|
8290
|
+
state.processing = true;
|
|
8291
|
+
let resolution;
|
|
8292
|
+
try {
|
|
8293
|
+
const object = await _fedify_vocab.Object.fromJsonLd(remoteDocument.document, {
|
|
8294
|
+
documentLoader,
|
|
8295
|
+
contextLoader,
|
|
8296
|
+
tracerProvider: options.tracerProvider
|
|
8297
|
+
});
|
|
8298
|
+
signal.throwIfAborted();
|
|
8299
|
+
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, options) : {
|
|
8300
|
+
type: "rejected",
|
|
8301
|
+
reason: "The document is not a portable actor."
|
|
8302
|
+
};
|
|
8303
|
+
} catch (error) {
|
|
8304
|
+
if (error instanceof TypeError && !signal.aborted) resolution = {
|
|
8305
|
+
type: "rejected",
|
|
8306
|
+
reason: `The document is malformed: ${error.message}`
|
|
8307
|
+
};
|
|
8308
|
+
else {
|
|
8309
|
+
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
8310
|
+
keyId: formattedKeyId,
|
|
8311
|
+
url,
|
|
8312
|
+
error
|
|
8313
|
+
});
|
|
8314
|
+
return {
|
|
8315
|
+
type: "indeterminate",
|
|
8316
|
+
error
|
|
8317
|
+
};
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
if (resolution.type === "verified") return resolution;
|
|
8321
|
+
const reason = resolution.type === "rejected" ? resolution.reason : "The document is not a portable actor.";
|
|
8322
|
+
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
8323
|
+
keyId: formattedKeyId,
|
|
8324
|
+
url,
|
|
8325
|
+
reason
|
|
8326
|
+
});
|
|
8327
|
+
return {
|
|
8328
|
+
type: "rejected",
|
|
8329
|
+
reason
|
|
8330
|
+
};
|
|
8331
|
+
}
|
|
8332
|
+
/**
|
|
8333
|
+
* Wraps a document loader so that it gives up when the given signal aborts,
|
|
8334
|
+
* as well as when the signal in its own options does. The wrapped loader is
|
|
8335
|
+
* passed a signal that aborts in either case, but in case it ignores that
|
|
8336
|
+
* signal, what it resolves to after the abort is ignored, and the wrapper
|
|
8337
|
+
* rejects with the reason of the abort at once. After the abort, the wrapped
|
|
8338
|
+
* loader is not called at all.
|
|
8339
|
+
*/
|
|
8340
|
+
function withDeadline(loader, signal) {
|
|
8341
|
+
return (url, options) => {
|
|
8342
|
+
const combined = combineSignals(signal, options?.signal);
|
|
8343
|
+
return raceAbort(combined.signal.aborted ? Promise.reject(combined.signal.reason) : new Promise((resolve) => resolve(loader(url, {
|
|
8344
|
+
...options,
|
|
8345
|
+
signal: combined.signal
|
|
8346
|
+
}))), combined.signal).finally(combined.dispose);
|
|
8347
|
+
};
|
|
8348
|
+
}
|
|
8349
|
+
/**
|
|
8350
|
+
* Makes a signal that aborts when either of the given signals does, with
|
|
8351
|
+
* the reason of the one that aborted first. `AbortSignal.any()` is not
|
|
8352
|
+
* available on every supported runtime, e.g., Bun before 1.1.4.
|
|
8353
|
+
* @returns The signal, and a function that stops listening to the given
|
|
8354
|
+
* signals.
|
|
8355
|
+
*/
|
|
8356
|
+
function combineSignals(signal, other) {
|
|
8357
|
+
if (other == null || other === signal) return {
|
|
8358
|
+
signal,
|
|
8359
|
+
dispose() {}
|
|
8360
|
+
};
|
|
8361
|
+
const controller = new AbortController();
|
|
8362
|
+
const sources = [signal, other];
|
|
8363
|
+
const dispose = () => {
|
|
8364
|
+
for (const source of sources) source.removeEventListener("abort", onAbort);
|
|
8365
|
+
};
|
|
8366
|
+
function onAbort() {
|
|
8367
|
+
dispose();
|
|
8368
|
+
controller.abort(this.reason);
|
|
8369
|
+
}
|
|
8370
|
+
const aborted = sources.find((source) => source.aborted);
|
|
8371
|
+
if (aborted == null) for (const source of sources) source.addEventListener("abort", onAbort);
|
|
8372
|
+
else controller.abort(aborted.reason);
|
|
8373
|
+
return {
|
|
8374
|
+
signal: controller.signal,
|
|
8375
|
+
dispose
|
|
8376
|
+
};
|
|
8377
|
+
}
|
|
8378
|
+
/**
|
|
8379
|
+
* Waits for the given promise unless the given signal aborts first, in which
|
|
8380
|
+
* case it rejects with the reason of the abort. If the signal has aborted by
|
|
8381
|
+
* the time the promise settles, the abort wins as well, so that nothing that
|
|
8382
|
+
* settles after it counts.
|
|
8383
|
+
*/
|
|
8384
|
+
function raceAbort(promise, signal) {
|
|
8385
|
+
return new Promise((resolve, reject) => {
|
|
8386
|
+
const onAbort = () => reject(signal.reason);
|
|
8387
|
+
const settle = (settled) => {
|
|
8388
|
+
signal.removeEventListener("abort", onAbort);
|
|
8389
|
+
if (signal.aborted) onAbort();
|
|
8390
|
+
else settled();
|
|
8391
|
+
};
|
|
8392
|
+
promise.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
8393
|
+
if (signal.aborted) onAbort();
|
|
8394
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
8395
|
+
});
|
|
8396
|
+
}
|
|
8397
|
+
/**
|
|
8090
8398
|
* Finds the nodes that stand for the key with the given canonical portable
|
|
8091
8399
|
* ID. A node whose ID is an `ap:` or `ap+ef61:` URI stands for the key
|
|
8092
8400
|
* itself, e.g., Mitra lists the keys of portable actors that way while
|
|
@@ -8119,7 +8427,7 @@ function isReference(node) {
|
|
|
8119
8427
|
}
|
|
8120
8428
|
//#endregion
|
|
8121
8429
|
//#region src/sig/http.ts
|
|
8122
|
-
const Temporal = Temporal$
|
|
8430
|
+
const Temporal = Temporal$4;
|
|
8123
8431
|
const DEFAULT_MAX_REDIRECTION = 20;
|
|
8124
8432
|
const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
8125
8433
|
/**
|
|
@@ -8578,7 +8886,8 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
8578
8886
|
if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
|
|
8579
8887
|
else result = await verifyRequestDraft(request, span, metricsContext, options);
|
|
8580
8888
|
recordVerificationResult(span, result);
|
|
8581
|
-
if (
|
|
8889
|
+
if (result.verified) promoteKeyOwnerEvidence(result.key);
|
|
8890
|
+
else span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
8582
8891
|
return result;
|
|
8583
8892
|
} catch (error) {
|
|
8584
8893
|
threw = true;
|
|
@@ -9301,10 +9610,16 @@ Object.defineProperty(exports, "InvalidPortableObjectIdError", {
|
|
|
9301
9610
|
return InvalidPortableObjectIdError;
|
|
9302
9611
|
}
|
|
9303
9612
|
});
|
|
9613
|
+
Object.defineProperty(exports, "MAX_PORTABLE_KEY_TTL", {
|
|
9614
|
+
enumerable: true,
|
|
9615
|
+
get: function() {
|
|
9616
|
+
return MAX_PORTABLE_KEY_TTL;
|
|
9617
|
+
}
|
|
9618
|
+
});
|
|
9304
9619
|
Object.defineProperty(exports, "Temporal", {
|
|
9305
9620
|
enumerable: true,
|
|
9306
9621
|
get: function() {
|
|
9307
|
-
return Temporal$
|
|
9622
|
+
return Temporal$4;
|
|
9308
9623
|
}
|
|
9309
9624
|
});
|
|
9310
9625
|
Object.defineProperty(exports, "assertSafeJsonLd", {
|
|
@@ -9451,6 +9766,12 @@ Object.defineProperty(exports, "getRemoteHost", {
|
|
|
9451
9766
|
return getRemoteHost;
|
|
9452
9767
|
}
|
|
9453
9768
|
});
|
|
9769
|
+
Object.defineProperty(exports, "getVerifiedKeyOwnerEvidence", {
|
|
9770
|
+
enumerable: true,
|
|
9771
|
+
get: function() {
|
|
9772
|
+
return getVerifiedKeyOwnerEvidence;
|
|
9773
|
+
}
|
|
9774
|
+
});
|
|
9454
9775
|
Object.defineProperty(exports, "hasPortableActor", {
|
|
9455
9776
|
enumerable: true,
|
|
9456
9777
|
get: function() {
|
|
@@ -9559,6 +9880,12 @@ Object.defineProperty(exports, "parseAcceptSignature", {
|
|
|
9559
9880
|
return parseAcceptSignature;
|
|
9560
9881
|
}
|
|
9561
9882
|
});
|
|
9883
|
+
Object.defineProperty(exports, "parseKeyOwnerEvidence", {
|
|
9884
|
+
enumerable: true,
|
|
9885
|
+
get: function() {
|
|
9886
|
+
return parseKeyOwnerEvidence;
|
|
9887
|
+
}
|
|
9888
|
+
});
|
|
9562
9889
|
Object.defineProperty(exports, "parseRfc9421SignatureInput", {
|
|
9563
9890
|
enumerable: true,
|
|
9564
9891
|
get: function() {
|